reborn-omniroute 1.0.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 +94 -0
- package/cli.mjs +402 -0
- package/package.json +46 -0
- package/skill/SKILL.md +93 -0
- package/skill/omni.json +110 -0
- package/tail.mjs +24 -0
- package//354/225/210/353/202/264/354/212/244/355/202/254.md +75 -0
package/README.md
ADDED
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
# reborn-omniroute
|
|
2
|
+
|
|
3
|
+
**OmniRoute 를 깔기 전에 이 컴퓨터가 되는지 먼저 재 주는 안내 스킬 설치기입니다.**
|
|
4
|
+
|
|
5
|
+
```
|
|
6
|
+
npx --yes reborn-omniroute --check ← 깔지 않고 이 컴퓨터를 잽니다
|
|
7
|
+
npx --yes reborn-omniroute ← 안내 스킬을 깝니다
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## 이게 무엇인가
|
|
11
|
+
|
|
12
|
+
[OmniRoute](https://github.com/diegosouzapw/OmniRoute) 는 클로드 코드·커서·코덱스가 부르는
|
|
13
|
+
주소를 **내 컴퓨터의 주소 하나**로 바꿔, 수백 곳의 AI 서비스로 대신 보내 주는 게이트웨이입니다.
|
|
14
|
+
한도가 차면 다음에 쓸 수 있는 모델로 스스로 갈아타고, 보내기 전에 프롬프트를 줄여 토큰을 아낍니다.
|
|
15
|
+
`diegosouzapw` 님이 **MIT** 로 공개한 도구입니다 — 우리가 만든 것이 아닙니다.
|
|
16
|
+
|
|
17
|
+
**이 패키지는 OmniRoute 가 아닙니다.** OmniRoute 를 깔기 전에
|
|
18
|
+
이 컴퓨터가 되는지 재고, 안 되면 무엇을 해야 하는지 알려 주는 **안내 스킬**을 깝니다.
|
|
19
|
+
|
|
20
|
+
## 왜 필요한가 — 여기서 가장 많이 막힙니다
|
|
21
|
+
|
|
22
|
+
OmniRoute 의 `engines` 는 이렇습니다:
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
>=22.22.2 <23 || >=24.0.0 <27
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**아주 좁습니다.** 요즘 LTS 안내가 가리키는 20.x 나 22.14 에서는
|
|
29
|
+
`npm install -g omniroute` 가 `EBADENGINE` 으로 그 자리에서 멈춥니다.
|
|
30
|
+
「설치가 안 된다」로 보이지만 사실은 판이 안 맞는 것뿐입니다.
|
|
31
|
+
|
|
32
|
+
`--check` 는 그 판을 실제로 읽고, 포트 20128 에 실제로 말을 걸고, 실행 파일을 실제로 찾습니다.
|
|
33
|
+
못 보면 「모른다」고 합니다 — 「된다」고 하지 않습니다.
|
|
34
|
+
|
|
35
|
+
## 무엇을 깝니까
|
|
36
|
+
|
|
37
|
+
`~/.claude/skills/omniroute-guide/` 하나뿐입니다.
|
|
38
|
+
|
|
39
|
+
| 파일 | 무엇 |
|
|
40
|
+
|---|---|
|
|
41
|
+
| `SKILL.md` | 클로드가 읽는 안내. 답하는 순서가 여기 있습니다 |
|
|
42
|
+
| `omni.json` | 원장. 모든 사실의 주인 |
|
|
43
|
+
|
|
44
|
+
깐 뒤 클로드 코드를 다시 시작하고 **「옴니라우트 붙여 줘」** 라고 하시면 됩니다.
|
|
45
|
+
|
|
46
|
+
## 명령
|
|
47
|
+
|
|
48
|
+
| 명령 | 무엇 |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `npx --yes reborn-omniroute` | 안내 스킬을 깝니다 |
|
|
51
|
+
| `npx --yes reborn-omniroute --check` | **깔지 않고** 이 컴퓨터를 잽니다 |
|
|
52
|
+
| `npx --yes reborn-omniroute --dry-run` | 무엇을 할지 보여주기만 합니다 |
|
|
53
|
+
| `npx --yes reborn-omniroute --local` | 홈이 아니라 현재 폴더에 깝니다 |
|
|
54
|
+
| `npx --yes reborn-omniroute --self-test` | 자기시험 (네트워크 안 씁니다) |
|
|
55
|
+
|
|
56
|
+
★`npx` 뒤의 `--yes` 는 빼지 마세요. 없으면 클로드 코드 안에서 물음이 뜨는데
|
|
57
|
+
그 물음이 화면에 안 보여서 **무음으로 멈춥니다.**
|
|
58
|
+
|
|
59
|
+
## 무엇이 정말 공짜인가
|
|
60
|
+
|
|
61
|
+
| 값이 없는 것 | 따로 드는 것 |
|
|
62
|
+
|---|---|
|
|
63
|
+
| OmniRoute 자체 (MIT) | 노드 판 올리기 — 0원, 시간 10~20분 |
|
|
64
|
+
| 이 설치기와 안내 스킬 | 무료 티어 계정 만들기 — 곳마다 5분 |
|
|
65
|
+
| 키 없이 답하는 무료 프로바이더 일부 | 유료 모델을 섞어 쓸 때의 종량제 요금 |
|
|
66
|
+
| | 게이트웨이가 도는 동안 이 컴퓨터가 켜져 있어야 합니다 |
|
|
67
|
+
|
|
68
|
+
**한도에 걸려 멈추는 일을 줄여 주는 도구**입니다.
|
|
69
|
+
클로드를 공짜로 쓰게 해 주는 도구가 아닙니다.
|
|
70
|
+
|
|
71
|
+
## 안전
|
|
72
|
+
|
|
73
|
+
- 게이트웨이는 **내 컴퓨터에서** 돕니다. 프롬프트가 남의 서버를 거치지 않습니다
|
|
74
|
+
- 기본값은 `127.0.0.1:20128` 입니다. **`0.0.0.0` 으로 열지 마세요** —
|
|
75
|
+
여는 순간 같은 망의 누구나 내 키로 모델을 부를 수 있습니다
|
|
76
|
+
- 키가 암호화돼 저장돼도 그 컴퓨터를 쓰는 사람은 다 쓸 수 있습니다. 공용 PC 에 올리지 마세요
|
|
77
|
+
|
|
78
|
+
## 숫자는 언제 잰 것인가
|
|
79
|
+
|
|
80
|
+
2026-09-04 에 npm 레지스트리 · GitHub API · README 원문을 직접 읽었습니다.
|
|
81
|
+
`skill/omni.json` 의 `확인일` · `확인방법` 을 보세요.
|
|
82
|
+
|
|
83
|
+
## 다 깔고 나면
|
|
84
|
+
|
|
85
|
+
이 안내 스킬처럼 **깔기 전에 무엇이 막히는지 먼저 재 주는** 스킬을
|
|
86
|
+
같은 방식으로 48종 골라 담아 두었습니다 — 리본랩스 스킬팩(8,900원).
|
|
87
|
+
|
|
88
|
+
https://rebornlabs.kr/skillpack?utm_source=installer&utm_medium=cli&utm_campaign=omni
|
|
89
|
+
|
|
90
|
+
목록과 직접 까는 명령까지 그 페이지에 그대로 적어 두었습니다. 안 사셔도 됩니다.
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
MIT · [REBORN LABS](https://rebornlabs.kr) · 문의 https://mobility.rebornlabs.kr/cs
|
package/cli.mjs
ADDED
|
@@ -0,0 +1,402 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* omniroute-guide 설치기 — **OmniRoute 를 깔기 전에 이 컴퓨터를 잰다.**
|
|
4
|
+
*
|
|
5
|
+
* npx --yes reborn-omniroute 안내 스킬을 깐다 (이게 전부다)
|
|
6
|
+
* npx --yes reborn-omniroute --check ★깔지 않고 **이 컴퓨터를 잰다**
|
|
7
|
+
* npx --yes reborn-omniroute --dry-run 무엇을 할지 보여주기만 한다
|
|
8
|
+
* npx --yes reborn-omniroute --local 홈(~/.claude/skills)이 아니라 현재 폴더에 깐다
|
|
9
|
+
* npx --yes reborn-omniroute --self-test 자기시험 (네트워크 안 씀)
|
|
10
|
+
*
|
|
11
|
+
* ★★왜 우리가 대신 안 깔고 **안내 스킬**을 까나
|
|
12
|
+
* `npm install -g omniroute` 한 줄은 이미 공개돼 있다. 우리가 그 줄을 대신 실행해 준다고
|
|
13
|
+
* 아껴지는 게 없다. 실제로 막히는 자리는 **노드 판**이다:
|
|
14
|
+
* engines 가 `>=22.22.2 <23 || >=24.0.0 <27` 이다.
|
|
15
|
+
* 요즘 LTS 안내가 가리키는 20.x·22.14 에서는 그 자리에서 EBADENGINE 으로 멈춘다.
|
|
16
|
+
* 그리고 **무료의 범위**가 영상이 말한 것과 다르다 — 월 15.1억은 여러 무료 티어를 합친
|
|
17
|
+
* 값이고, 각 티어의 분당·일당 한도는 그대로 살아 있다.
|
|
18
|
+
*
|
|
19
|
+
* ★★--check 가 이 설치기의 존재 이유다
|
|
20
|
+
* 말로 「되실 겁니다」 하지 않는다. 노드 판을 실제로 읽고, 포트 20128 에 실제로 말을 걸고,
|
|
21
|
+
* 실행 파일을 실제로 찾는다. 못 보면 「모른다」고 한다 — 「된다」고 하지 않는다.
|
|
22
|
+
*
|
|
23
|
+
* ★★깔았다고 「됐다」고 하지 않는다.
|
|
24
|
+
* 복사는 조용히 실패한다(권한·경로·한글 홈디렉터리). 쓴 뒤 **다시 읽어 본다**
|
|
25
|
+
* [[kit-update-selfcheck]].
|
|
26
|
+
*/
|
|
27
|
+
import { execFileSync } from "node:child_process";
|
|
28
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
29
|
+
import { connect } from "node:net";
|
|
30
|
+
import { homedir, platform } from "node:os";
|
|
31
|
+
import { dirname, join } from "node:path";
|
|
32
|
+
import { fileURLToPath } from "node:url";
|
|
33
|
+
import { 링크, TAIL, 마지막 } from "./tail.mjs";
|
|
34
|
+
|
|
35
|
+
const HOME = homedir();
|
|
36
|
+
const 여기 = dirname(fileURLToPath(import.meta.url));
|
|
37
|
+
const 인자 = process.argv.slice(2);
|
|
38
|
+
const 확인만 = 인자.includes("--check");
|
|
39
|
+
const 미리보기 = 인자.includes("--dry-run");
|
|
40
|
+
const 여기에 = 인자.includes("--local");
|
|
41
|
+
const 자기시험모드 = 인자.includes("--self-test");
|
|
42
|
+
|
|
43
|
+
const c = { g: "\x1b[32m", r: "\x1b[31m", y: "\x1b[33m", d: "\x1b[90m", cy: "\x1b[36m", b: "\x1b[1m", 0: "\x1b[0m" };
|
|
44
|
+
const 굵게 = (t) => String(t).replace(/\*\*(.+?)\*\*/g, `${c[0]}${c.b}$1${c[0]}${c.d}`);
|
|
45
|
+
|
|
46
|
+
const 퍼널 = "omni";
|
|
47
|
+
const 스킬이름 = "omniroute-guide";
|
|
48
|
+
const 원본 = join(여기, "skill");
|
|
49
|
+
const 대상뿌리 = 여기에 ? join(process.cwd(), ".claude", "skills") : join(HOME, ".claude", "skills");
|
|
50
|
+
const 대상 = join(대상뿌리, 스킬이름);
|
|
51
|
+
|
|
52
|
+
/* ══ 원장 ═══════════════════════════════════════════════════════ */
|
|
53
|
+
|
|
54
|
+
function 원장읽기() {
|
|
55
|
+
const p = join(원본, "omni.json");
|
|
56
|
+
if (!existsSync(p)) return { 됨: false, 이유: `원장이 없다 — ${p}` };
|
|
57
|
+
try { return { 됨: true, d: JSON.parse(readFileSync(p, "utf8")) }; }
|
|
58
|
+
catch (e) { return { 됨: false, 이유: `원장을 못 읽는다 — ${e.message}` }; }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** ★조건 없이 단정하는 말. 홍보문에 섞이면 잡는다. [[kmong-no-unbounded-guarantee]] */
|
|
62
|
+
const 금칙 = [/완전\s*무료/, /무제한/, /평생/, /100\s*%\s*보장/, /누구나\s*바로/, /무조건/,
|
|
63
|
+
/설정\s*없이/, /깔면\s*끝/, /전혀\s*없/, /절대\s*안/];
|
|
64
|
+
|
|
65
|
+
/** ★「그 말을 하지 마라」고 적어 둔 문장까지 잡으면 원장을 못 쓴다. */
|
|
66
|
+
const 금칙면제 = /(하지\s*않는다|말하지\s*마라|쓰지\s*않는다|아니다|틀리다|금칙|안\s*된다|하지\s*마라|말하지\s*않는다|은\s*아니다|되지\s*않는다|겨누지\s*마라|열지\s*마라|올리지\s*마라|마라)/;
|
|
67
|
+
|
|
68
|
+
/** ★낫표 안은 **인용**이다 [[forbidden-word-checker-blocks-its-own-quotes]] */
|
|
69
|
+
const 인용벗기기 = (s) => String(s).replace(/「[^」]*」/g, "○");
|
|
70
|
+
|
|
71
|
+
function 금칙검사(텍스트, 어디) {
|
|
72
|
+
const 문제 = [];
|
|
73
|
+
for (const 원줄 of String(텍스트).split(/\n|(?<=[.。])\s+/)) {
|
|
74
|
+
const 줄 = 인용벗기기(원줄);
|
|
75
|
+
if (금칙면제.test(줄)) continue;
|
|
76
|
+
for (const re of 금칙) if (re.test(줄)) 문제.push(`${어디}에 조건 없는 단정: ${re.source}`);
|
|
77
|
+
}
|
|
78
|
+
return 문제;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function 원장검사(d) {
|
|
82
|
+
const 문제 = [];
|
|
83
|
+
const 있나 = (v) => v !== undefined && v !== null && v !== "";
|
|
84
|
+
|
|
85
|
+
if (!있나(d?.확인일)) 문제.push("확인일이 없다 — 언제 잰 값인지 모르면 사실은 못 쓴다");
|
|
86
|
+
if (!있나(d?.확인방법)) 문제.push("확인방법이 없다 — 어디서 읽었는지 없는 말은 지어낸 것과 구분이 안 된다");
|
|
87
|
+
if (!있나(d?.한글이름)) 문제.push("한글이름이 없다 — 릴스 자막이 이 글자를 읽는다");
|
|
88
|
+
|
|
89
|
+
const S = d?.실측 || {};
|
|
90
|
+
for (const k of ["저장소", "라이선스", "프로바이더", "무료토큰월", "패키지", "패키지버전",
|
|
91
|
+
"노드요구", "포트", "엔드포인트", "압축상한", "★지역"]) {
|
|
92
|
+
if (!있나(S[k])) 문제.push(`실측: ${k} 이 없다`);
|
|
93
|
+
}
|
|
94
|
+
if (있나(S.저장소) && !/^https:\/\//.test(String(S.저장소))) 문제.push("실측: 저장소가 https 주소가 아니다");
|
|
95
|
+
|
|
96
|
+
/* ★★노드 요구가 원장에 없으면 --check 가 아무것도 못 가른다. 이게 이 회차의 핵심 관문이다. */
|
|
97
|
+
if (있나(S.노드요구) && !/\d+\.\d+\.\d+/.test(String(S.노드요구)))
|
|
98
|
+
문제.push("실측: 노드요구가 판 번호 형태가 아니다 — 이 값으로 실제 판을 가른다");
|
|
99
|
+
|
|
100
|
+
/* ★설치 명령이 원장에 없으면 릴스·착지·DM 이 저마다 손으로 적게 된다. */
|
|
101
|
+
const 명령 = d?.설치?.["★명령"];
|
|
102
|
+
if (!있나(명령)) 문제.push("설치.★명령 이 없다 — 이 문자열 하나를 릴스·착지·DM 이 같이 읽는다");
|
|
103
|
+
else if (!/^npx\s+--yes\s+reborn-omniroute$/.test(String(명령)))
|
|
104
|
+
문제.push(`설치.★명령 이 「npx --yes reborn-omniroute」 가 아니다 — 지금 값: ${명령}`);
|
|
105
|
+
if (있나(명령) && /^npx\s+(?!--yes)/.test(String(명령)))
|
|
106
|
+
문제.push("npx 안내에 --yes 가 없다 — 클로드 코드 안에서 무음으로 멈춘다 [[npx-installer-needs-yes-flag]]");
|
|
107
|
+
|
|
108
|
+
/* ★공짜의 범위를 말하지 않으면 「전부 0원」으로 읽힌다. */
|
|
109
|
+
if (!Array.isArray(d?.["★공짜의범위"]?.["★따로_드는것"]) || d["★공짜의범위"]["★따로_드는것"].length < 3)
|
|
110
|
+
문제.push("★공짜의범위.★따로_드는것 이 3줄 미만이다 — 값이 드는 자리를 안 말하면 공짜로 읽힌다");
|
|
111
|
+
|
|
112
|
+
/* ★★판정 한 줄 — 「클로드를 공짜로 쓰게 해 주는 도구가 아니다」를 빼면
|
|
113
|
+
이 안내 스킬은 영상과 똑같은 말만 하게 된다. 그럼 있을 이유가 없다. */
|
|
114
|
+
if (!있나(d?.["★영상이_말하지_않은_것"]?.판정)) 문제.push("★영상이_말하지_않은_것.판정 이 없다 — 이 스킬이 있을 이유가 그 한 줄이다");
|
|
115
|
+
|
|
116
|
+
/* ★포트를 밖으로 열지 말라는 문장은 빼면 안 된다. */
|
|
117
|
+
if (!있나(d?.["★안전"]?.포트)) 문제.push("★안전.포트 칸이 없다 — 게이트웨이를 밖으로 열면 남이 내 키로 모델을 부른다");
|
|
118
|
+
|
|
119
|
+
문제.push(...금칙검사(`${d?.한줄}\n${JSON.stringify(d?.["★영상이_말하지_않은_것"] || {})}`, "원장 본문"));
|
|
120
|
+
return 문제;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/* ══ 이 컴퓨터를 잰다 ═══════════════════════════════════════════ */
|
|
124
|
+
|
|
125
|
+
function 실행파일있나(cmd) {
|
|
126
|
+
const 찾기 = platform() === "win32" ? "where" : "which";
|
|
127
|
+
try { execFileSync(찾기, [cmd], { stdio: "pipe", timeout: 4000 }); return true; }
|
|
128
|
+
catch { return false; }
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function 명령해보기(cmd, args, ms = 6000) {
|
|
132
|
+
try { return String(execFileSync(cmd, args, { stdio: "pipe", timeout: ms })).trim(); }
|
|
133
|
+
catch { return null; }
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function 노드판() {
|
|
137
|
+
const m = /^v(\d+)\.(\d+)\.(\d+)/.exec(process.version);
|
|
138
|
+
return m ? [Number(m[1]), Number(m[2]), Number(m[3])] : [0, 0, 0];
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* ★★노드 판이 요구 범위 안인가 — **이 회차에서 사람이 가장 많이 막히는 자리**다.
|
|
143
|
+
* 원장의 `>=22.22.2 <23 || >=24.0.0 <27` 을 그대로 읽어 가른다.
|
|
144
|
+
* ★손으로 22·24 를 박지 않는다. 저장소가 범위를 바꾸면 원장만 고치면 되게 둔다.
|
|
145
|
+
*/
|
|
146
|
+
function 노드요구맞나(요구, 판) {
|
|
147
|
+
const [M, m, p] = 판;
|
|
148
|
+
const 값 = M * 1e6 + m * 1e3 + p;
|
|
149
|
+
const 자르기 = (s) => {
|
|
150
|
+
const q = /(\d+)(?:\.(\d+))?(?:\.(\d+))?/.exec(s);
|
|
151
|
+
return q ? Number(q[1]) * 1e6 + Number(q[2] || 0) * 1e3 + Number(q[3] || 0) : null;
|
|
152
|
+
};
|
|
153
|
+
/* 「A || B」 중 하나라도 맞으면 된다. 각 갈래 안의 조건은 전부 맞아야 한다. */
|
|
154
|
+
for (const 갈래 of String(요구).split("||")) {
|
|
155
|
+
let 다맞나 = true, 봤나 = false;
|
|
156
|
+
for (const 조건 of 갈래.trim().split(/\s+/)) {
|
|
157
|
+
const r = /^(>=|<=|>|<)(.+)$/.exec(조건);
|
|
158
|
+
if (!r) continue;
|
|
159
|
+
const 기준 = 자르기(r[2]);
|
|
160
|
+
if (기준 == null) continue;
|
|
161
|
+
봤나 = true;
|
|
162
|
+
const ok = r[1] === ">=" ? 값 >= 기준 : r[1] === "<=" ? 값 <= 기준
|
|
163
|
+
: r[1] === ">" ? 값 > 기준 : 값 < 기준;
|
|
164
|
+
if (!ok) { 다맞나 = false; break; }
|
|
165
|
+
}
|
|
166
|
+
if (봤나 && 다맞나) return true;
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/** ★포트가 이미 쓰이고 있나 — 「깔렸나」가 아니라 **「지금 응답하나」**를 본다. */
|
|
172
|
+
function 포트열렸나(포트, ms = 900) {
|
|
173
|
+
return new Promise((resolve) => {
|
|
174
|
+
const s = connect({ host: "127.0.0.1", port: 포트 });
|
|
175
|
+
const 끝 = (v) => { try { s.destroy(); } catch { /* 이미 닫혔다 */ } resolve(v); };
|
|
176
|
+
s.setTimeout(ms);
|
|
177
|
+
s.once("connect", () => 끝(true));
|
|
178
|
+
s.once("timeout", () => 끝(false));
|
|
179
|
+
s.once("error", () => 끝(false));
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
function 이미깔렸나() {
|
|
184
|
+
const 자리 = [];
|
|
185
|
+
for (const 뿌리 of [join(HOME, ".claude", "skills"), join(process.cwd(), ".claude", "skills")]) {
|
|
186
|
+
const p = join(뿌리, 스킬이름);
|
|
187
|
+
if (existsSync(p) && statSync(p).isDirectory()) 자리.push({ 어디: p, 개수: readdirSync(p).length });
|
|
188
|
+
}
|
|
189
|
+
return 자리;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
async function 환경재기(D) {
|
|
193
|
+
const 판 = 노드판();
|
|
194
|
+
return {
|
|
195
|
+
노드: 판,
|
|
196
|
+
노드글: process.version,
|
|
197
|
+
노드맞나: 노드요구맞나(D.실측.노드요구, 판),
|
|
198
|
+
omniroute: 실행파일있나("omniroute"),
|
|
199
|
+
포트: await 포트열렸나(D.실측.포트),
|
|
200
|
+
npm: 실행파일있나("npm"),
|
|
201
|
+
도커: 실행파일있나("docker") ? !!명령해보기("docker", ["version", "--format", "{{.Server.Version}}"], 8000) : false,
|
|
202
|
+
클로드코드: 실행파일있나("claude"),
|
|
203
|
+
이미: 이미깔렸나(),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/** ★지금 어느 길로 가야 하나 — **잰 값으로** 가른다. 「아마 될 겁니다」를 하지 않는다. */
|
|
208
|
+
function 길찾기(E, D) {
|
|
209
|
+
if (E.omniroute && E.포트) {
|
|
210
|
+
return { 상태: "돎", 한줄: `이미 깔려 있고 ${D.실측.포트} 포트가 응답합니다`,
|
|
211
|
+
다음: [`도구 설정을 ${D.실측.엔드포인트} 로 바꾸면 끝입니다`] };
|
|
212
|
+
}
|
|
213
|
+
if (E.omniroute) {
|
|
214
|
+
return { 상태: "깔림", 한줄: "이미 깔려 있는데 지금 안 돌고 있습니다",
|
|
215
|
+
다음: ["`omniroute` 를 실행해 게이트웨이를 켜세요", `켜지면 ${D.실측.대시보드} 가 열립니다`] };
|
|
216
|
+
}
|
|
217
|
+
if (!E.노드맞나) {
|
|
218
|
+
return { 상태: "막힘", 한줄: `노드 ${E.노드글} 은 이 도구가 요구하는 판이 아닙니다 (${D.실측.노드요구})`,
|
|
219
|
+
다음: [
|
|
220
|
+
"nvm 이나 volta 로 맞는 판을 하나 더 깔면 됩니다 — 지금 판을 지울 필요는 없습니다",
|
|
221
|
+
"판을 안 건드리고 싶으면 도커 쪽 길이 있습니다:",
|
|
222
|
+
D.설치.도커,
|
|
223
|
+
] };
|
|
224
|
+
}
|
|
225
|
+
if (E.포트) {
|
|
226
|
+
return { 상태: "포트", 한줄: `${D.실측.포트} 포트를 이미 다른 것이 쓰고 있습니다`,
|
|
227
|
+
다음: ["그 프로그램을 끄거나, OmniRoute 를 다른 포트로 띄우세요"] };
|
|
228
|
+
}
|
|
229
|
+
return { 상태: "됨", 한줄: "지금 바로 깔 수 있습니다",
|
|
230
|
+
다음: [D.설치.원도구, `깔린 뒤 \`omniroute\` 로 켜고, 도구 설정을 ${D.실측.엔드포인트} 로 바꿉니다`] };
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/* ══ 보고 ═══════════════════════════════════════════════════════ */
|
|
234
|
+
|
|
235
|
+
function 환경보고(E, D, 길) {
|
|
236
|
+
const 표시 = {
|
|
237
|
+
됨: `${c.g}지금 바로${c[0]}`, 돎: `${c.g}이미 돌고 있음${c[0]}`,
|
|
238
|
+
깔림: `${c.y}켜기만 하면 됨${c[0]}`, 포트: `${c.y}포트가 물림${c[0]}`, 막힘: `${c.r}지금은 막힘${c[0]}`,
|
|
239
|
+
};
|
|
240
|
+
console.log(`\n${c.b}이 컴퓨터를 재 봤습니다${c[0]} ${c.d}(${D.확인일} 기준 원장)${c[0]}\n`);
|
|
241
|
+
console.log(` Node.js ${E.노드맞나 ? c.g + E.노드글 + c[0] : c.r + E.노드글 + c[0]} ${c.d}요구: ${D.실측.노드요구}${c[0]}`);
|
|
242
|
+
console.log(` omniroute ${E.omniroute ? `${c.g}있음${c[0]}` : `${c.d}없음${c[0]}`}`);
|
|
243
|
+
console.log(` 포트 ${D.실측.포트} ${E.포트 ? `${c.y}무언가 응답함${c[0]}` : `${c.d}비어 있음${c[0]}`}`);
|
|
244
|
+
console.log(` Docker ${E.도커 ? `${c.g}응답함${c[0]}` : `${c.d}없거나 응답 없음${c[0]}`}`);
|
|
245
|
+
console.log(` Claude Code ${E.클로드코드 ? `${c.g}있음${c[0]}` : `${c.d}없음 — 커서·코덱스에서도 됩니다${c[0]}`}`);
|
|
246
|
+
|
|
247
|
+
console.log(`\n${c.b}지금 어디까지 되나${c[0]} ${표시[길.상태]}`);
|
|
248
|
+
console.log(` ${c.d}${길.한줄}${c[0]}\n`);
|
|
249
|
+
for (const 줄 of 길.다음) {
|
|
250
|
+
if (/^(npm|npx|docker|omniroute)\s|^`/.test(줄)) console.log(` ${c.cy}${줄.replace(/`/g, "")}${c[0]}`);
|
|
251
|
+
else console.log(` ${c.d}${굵게(줄)}${c[0]}`);
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
console.log(`\n${c.b}영상이 말하지 않은 것${c[0]}`);
|
|
255
|
+
for (const [k, v] of Object.entries(D["★영상이_말하지_않은_것"])) {
|
|
256
|
+
if (k.startsWith("_") || k === "판정") continue; /* 판정은 아래에 굵게 따로 세운다 */
|
|
257
|
+
console.log(` ${c.d}${k} ${굵게(String(v))}${c[0]}`);
|
|
258
|
+
}
|
|
259
|
+
console.log(`\n ${c.b}판정${c[0]} ${c.d}${굵게(D["★영상이_말하지_않은_것"].판정)}${c[0]}`);
|
|
260
|
+
|
|
261
|
+
console.log(`\n${c.b}값이 드는 자리${c[0]}`);
|
|
262
|
+
for (const r of D["★공짜의범위"]["★따로_드는것"]) console.log(` ${c.d}· ${r.무엇} — ${r.값} (${r.근거})${c[0]}`);
|
|
263
|
+
|
|
264
|
+
console.log(`\n${c.b}안전${c[0]}`);
|
|
265
|
+
for (const [k, v] of Object.entries(D["★안전"])) console.log(` ${c.d}${k} ${굵게(String(v))}${c[0]}`);
|
|
266
|
+
|
|
267
|
+
console.log(`\n${c.d}OmniRoute 는 ${D.실측.만든이} 님이 ${D.실측.라이선스} 로 공개한 도구입니다 — 우리가 만든 것이 아닙니다. ${D.실측.저장소}${c[0]}\n`);
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/** 「끝난 뒤 한 칸」 — ★아무것도 못 깔았으면 안 띄운다. */
|
|
271
|
+
function 꼬리(깔았나) {
|
|
272
|
+
if (!깔았나) return;
|
|
273
|
+
const t = TAIL[퍼널];
|
|
274
|
+
console.log();
|
|
275
|
+
for (const 줄 of t.이음) console.log(`${c.d}${굵게(줄)}${c[0]}`);
|
|
276
|
+
console.log(`\n ${c.b}${t.제목}${c[0]} ${c.d}— ${t.한줄}${c[0]}`);
|
|
277
|
+
console.log(` ${c.cy}${링크(퍼널)}${c[0]}`);
|
|
278
|
+
console.log(`${c.d}${마지막}${c[0]}\n`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/* ══ 심기 ═══════════════════════════════════════════════════════ */
|
|
282
|
+
|
|
283
|
+
function 심기() {
|
|
284
|
+
mkdirSync(대상뿌리, { recursive: true });
|
|
285
|
+
cpSync(원본, 대상, { recursive: true });
|
|
286
|
+
|
|
287
|
+
/* ★★쓴 뒤 다시 읽어 본다 — 복사는 조용히 실패한다 [[kit-update-selfcheck]] */
|
|
288
|
+
for (const f of ["SKILL.md", "omni.json"]) {
|
|
289
|
+
const p = join(대상, f);
|
|
290
|
+
if (!existsSync(p)) throw new Error(`심었는데 ${f} 이 없다 — ${p}`);
|
|
291
|
+
if (readFileSync(p, "utf8").length < 200) throw new Error(`${f} 이 너무 짧다 — 복사가 끊긴 것 같다`);
|
|
292
|
+
}
|
|
293
|
+
const 원장 = JSON.parse(readFileSync(join(대상, "omni.json"), "utf8"));
|
|
294
|
+
if (!원장?.실측?.노드요구) throw new Error("심은 원장에 노드요구가 없다 — 복사가 깨졌다");
|
|
295
|
+
return 대상;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/* ══ 자기시험 — ★양방향으로 본다 [[verifier-must-be-tested-both-ways]] ══ */
|
|
299
|
+
|
|
300
|
+
function 자기시험() {
|
|
301
|
+
const 나쁨 = [];
|
|
302
|
+
const r = 원장읽기();
|
|
303
|
+
if (!r.됨) { console.log(`${c.r}✗ ${r.이유}${c[0]}`); process.exit(1); }
|
|
304
|
+
const D = r.d;
|
|
305
|
+
|
|
306
|
+
/* 오탐 — 멀쩡한 원장을 막으면 안 된다 */
|
|
307
|
+
const 문제 = 원장검사(D);
|
|
308
|
+
if (문제.length) 나쁨.push(...문제.map((m) => `오탐 또는 진짜 문제: ${m}`));
|
|
309
|
+
|
|
310
|
+
/* 미탐 ① 노드 요구가 빠지면 잡아야 한다 — 이 회차의 핵심 관문이다 */
|
|
311
|
+
{
|
|
312
|
+
const 흉내 = JSON.parse(JSON.stringify(D)); 흉내.실측.노드요구 = "";
|
|
313
|
+
if (!원장검사(흉내).some((m) => m.includes("노드요구"))) 나쁨.push("미탐: 노드요구를 지워도 통과한다");
|
|
314
|
+
}
|
|
315
|
+
/* 미탐 ② npx 에 --yes 가 빠지면 잡아야 한다 */
|
|
316
|
+
{
|
|
317
|
+
const 흉내 = JSON.parse(JSON.stringify(D)); 흉내.설치["★명령"] = "npx reborn-omniroute";
|
|
318
|
+
if (!원장검사(흉내).some((m) => m.includes("--yes"))) 나쁨.push("미탐: npx 에서 --yes 를 빼도 통과한다");
|
|
319
|
+
}
|
|
320
|
+
/* 미탐 ③ 조건 없는 단정이 섞이면 잡아야 한다 */
|
|
321
|
+
{
|
|
322
|
+
const 흉내 = JSON.parse(JSON.stringify(D)); 흉내.한줄 = "누구나 바로 됩니다. 설정 없이 깔면 끝입니다.";
|
|
323
|
+
if (!원장검사(흉내).some((m) => m.includes("단정"))) 나쁨.push("미탐: 조건 없는 단정이 섞여도 통과한다");
|
|
324
|
+
}
|
|
325
|
+
/* 미탐 ④ 판정 한 줄이 빠지면 잡아야 한다 */
|
|
326
|
+
{
|
|
327
|
+
const 흉내 = JSON.parse(JSON.stringify(D)); 흉내["★영상이_말하지_않은_것"].판정 = "";
|
|
328
|
+
if (!원장검사(흉내).some((m) => m.includes("판정"))) 나쁨.push("미탐: 판정 한 줄을 지워도 통과한다");
|
|
329
|
+
}
|
|
330
|
+
/* 미탐 ⑤ 포트 안전 문장이 빠지면 잡아야 한다 */
|
|
331
|
+
{
|
|
332
|
+
const 흉내 = JSON.parse(JSON.stringify(D)); delete 흉내["★안전"].포트;
|
|
333
|
+
if (!원장검사(흉내).some((m) => m.includes("포트"))) 나쁨.push("미탐: 포트 안전 문장을 지워도 통과한다");
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
/* ★★판 가르개를 **양쪽으로** 시험한다 — 전건 통과/전건 실패는 고장 신호다 */
|
|
337
|
+
const 요구 = D.실측.노드요구;
|
|
338
|
+
const 되어야 = [[22, 22, 2], [22, 30, 0], [24, 0, 0], [26, 9, 9]];
|
|
339
|
+
const 안되어야 = [[20, 19, 0], [22, 14, 0], [22, 22, 1], [23, 5, 0], [27, 0, 0], [18, 0, 0]];
|
|
340
|
+
for (const v of 되어야) if (!노드요구맞나(요구, v)) 나쁨.push(`판 가르개: ${v.join(".")} 는 되어야 하는데 막는다`);
|
|
341
|
+
for (const v of 안되어야) if (노드요구맞나(요구, v)) 나쁨.push(`판 가르개: ${v.join(".")} 는 막아야 하는데 통과시킨다`);
|
|
342
|
+
|
|
343
|
+
/* 심는 자리에 SKILL.md 가 실제로 있나 (네트워크 안 씀) */
|
|
344
|
+
if (!existsSync(join(원본, "SKILL.md"))) 나쁨.push("skill/SKILL.md 가 패키지에 없다 — 깔아도 빈 폴더가 된다");
|
|
345
|
+
/* 꼬리 문안이 있나 */
|
|
346
|
+
if (!TAIL[퍼널]) 나쁨.push(`tail.mjs 에 「${퍼널}」 문안이 없다`);
|
|
347
|
+
|
|
348
|
+
if (나쁨.length) { for (const m of 나쁨) console.log(`${c.r}✗${c[0]} ${m}`); process.exit(1); }
|
|
349
|
+
console.log(`${c.g}✓${c[0]} 자기시험 통과 — 확인일 ${D.확인일} · 원장 검사기 미탐 5종 · 판 가르개 양방향 ${되어야.length + 안되어야.length}건`);
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
/* ══ 본체 ═══════════════════════════════════════════════════════ */
|
|
353
|
+
|
|
354
|
+
async function 본체() {
|
|
355
|
+
if (자기시험모드) return 자기시험();
|
|
356
|
+
|
|
357
|
+
const r = 원장읽기();
|
|
358
|
+
if (!r.됨) { console.log(`${c.r}✗ ${r.이유}${c[0]}`); process.exit(1); }
|
|
359
|
+
const D = r.d;
|
|
360
|
+
const 문제 = 원장검사(D);
|
|
361
|
+
if (문제.length) {
|
|
362
|
+
console.log(`${c.r}✗ 원장이 규격에 안 맞습니다 — 화면에 잘못된 사실이 나갈 수 있어 멈춥니다${c[0]}`);
|
|
363
|
+
for (const m of 문제) console.log(` ${c.d}· ${m}${c[0]}`);
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const E = await 환경재기(D);
|
|
368
|
+
환경보고(E, D, 길찾기(E, D));
|
|
369
|
+
|
|
370
|
+
if (확인만) {
|
|
371
|
+
console.log(`${c.d}재기만 했습니다 — 아무것도 깔지 않았습니다. 안내 스킬을 깔려면 ${c.cy}${D.설치["★명령"]}${c.d} 를 그냥 실행하세요.${c[0]}\n`);
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (E.이미.length) {
|
|
376
|
+
for (const 자리 of E.이미) console.log(`${c.g}✓${c[0]} ${자리.어디} 에 ${c.b}이미 있습니다${c[0]} ${c.d}(파일 ${자리.개수}개) — 또 깔지 마세요${c[0]}`);
|
|
377
|
+
console.log(`${c.d}다시 깔려면 그 폴더를 지우고 실행하세요.${c[0]}\n`);
|
|
378
|
+
return;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (미리보기) {
|
|
382
|
+
console.log(`${c.d}깔 자리: ${대상}${c[0]}`);
|
|
383
|
+
console.log(`${c.d}넣을 것: ${readdirSync(원본).join(", ")}${c[0]}`);
|
|
384
|
+
console.log(`${c.d}미리보기라 아무것도 쓰지 않았습니다.${c[0]}\n`);
|
|
385
|
+
return;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
let 깔았나 = false;
|
|
389
|
+
try {
|
|
390
|
+
const p = 심기();
|
|
391
|
+
console.log(`${c.g}✓${c[0]} 안내 스킬을 깔았습니다 — ${c.b}${p}${c[0]}`);
|
|
392
|
+
console.log(`${c.d}클로드 코드를 다시 시작하면 「${스킬이름}」 이 잡힙니다. 「옴니라우트 붙여 줘」라고 하시면 됩니다.${c[0]}`);
|
|
393
|
+
깔았나 = true;
|
|
394
|
+
} catch (e) {
|
|
395
|
+
console.log(`${c.r}✗ 못 깔았습니다 — ${e.message}${c[0]}`);
|
|
396
|
+
console.log(`${c.d}위에 적힌 줄은 그대로 쓰실 수 있습니다. 안내 스킬 없이도 됩니다.${c[0]}`);
|
|
397
|
+
process.exitCode = 1;
|
|
398
|
+
}
|
|
399
|
+
꼬리(깔았나);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
본체();
|
package/package.json
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "reborn-omniroute",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "영상에서 소개한 OmniRoute(무료 AI 게이트웨이)를 깔기 전에 이 컴퓨터가 되는지 먼저 재 주는 안내 스킬입니다. OmniRoute 의 engines 는 노드 22.22.2~22.x 또는 24~26.x 로 아주 좁아서, 요즘 흔한 20.x·22.14 에서는 전역 설치가 EBADENGINE 으로 그 자리에서 멈춥니다. 노드 판·포트 20128·실행 파일을 실제로 재서 지금 바로 되는 길과 준비가 먼저인 길을 갈라 주고, 무료 토큰 합계가 여러 티어를 합친 값이라는 것까지 말합니다.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"claude",
|
|
7
|
+
"claude-code",
|
|
8
|
+
"omniroute",
|
|
9
|
+
"ai-gateway",
|
|
10
|
+
"llm-gateway",
|
|
11
|
+
"free-ai",
|
|
12
|
+
"fallback",
|
|
13
|
+
"token-saver",
|
|
14
|
+
"cursor",
|
|
15
|
+
"codex",
|
|
16
|
+
"cline",
|
|
17
|
+
"korean"
|
|
18
|
+
],
|
|
19
|
+
"homepage": "https://rebornlabs.kr/omni-route?utm_source=installer&utm_medium=cli&utm_campaign=omni",
|
|
20
|
+
"bugs": {
|
|
21
|
+
"url": "https://mobility.rebornlabs.kr/cs"
|
|
22
|
+
},
|
|
23
|
+
"license": "MIT",
|
|
24
|
+
"author": "REBORN LABS",
|
|
25
|
+
"type": "module",
|
|
26
|
+
"bin": {
|
|
27
|
+
"reborn-omniroute": "cli.mjs"
|
|
28
|
+
},
|
|
29
|
+
"files": [
|
|
30
|
+
"cli.mjs",
|
|
31
|
+
"tail.mjs",
|
|
32
|
+
"skill/",
|
|
33
|
+
"README.md",
|
|
34
|
+
"안내스킬.md"
|
|
35
|
+
],
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=18"
|
|
38
|
+
},
|
|
39
|
+
"scripts": {
|
|
40
|
+
"test": "node cli.mjs --self-test",
|
|
41
|
+
"check": "node cli.mjs --check"
|
|
42
|
+
},
|
|
43
|
+
"publishConfig": {
|
|
44
|
+
"access": "public"
|
|
45
|
+
}
|
|
46
|
+
}
|
package/skill/SKILL.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: omniroute-guide
|
|
3
|
+
description: OmniRoute(옴니라우트) — 클로드 코드·커서·코덱스가 부르는 주소를 내 컴퓨터의 주소 하나로 바꿔 수백 곳의 AI 서비스로 대신 보내 주는 무료 게이트웨이를, **깔기 전에 이 컴퓨터가 되는지 먼저 재 준다.** 노드 판이 아주 좁아서(22.22.2~22.x 또는 24~26.x) 여기서 막히는 사람이 가장 많다. "옴니라우트", "OmniRoute", "클로드 코드 한도", "사용량 다 쓰면", "무료 AI 라우터", "AI 게이트웨이", "토큰 아끼기", "프롬프트 압축", "무료 토큰", "모델 자동 전환", "limit reached", "쿼터 소진" 같은 이야기가 나올 때 쓴다.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# OmniRoute — 깔기 전에 이 컴퓨터부터 잰다
|
|
7
|
+
|
|
8
|
+
원장은 같은 폴더의 `omni.json` 이다. **여기 적힌 숫자는 전부 거기서 온다.**
|
|
9
|
+
이 문서에서 수치를 새로 지어내지 마라 — 원장을 읽고 말해라.
|
|
10
|
+
|
|
11
|
+
## 0. 먼저 말할 것 — 이게 무엇이고 무엇이 아닌가
|
|
12
|
+
|
|
13
|
+
**한도에 걸려 멈추는 일을 줄여 주는 도구**다.
|
|
14
|
+
클로드를 공짜로 쓰게 해 주는 도구가 **아니다.** 이 차이를 모르고 깔면 첫날 실망한다.
|
|
15
|
+
|
|
16
|
+
| 하는 일 | 안 하는 일 |
|
|
17
|
+
|---|---|
|
|
18
|
+
| 도구가 부르는 주소 하나를 내 컴퓨터로 돌린다 | 클로드 유료 모델을 무료로 바꿔 주지 않는다 |
|
|
19
|
+
| 한도가 차면 다음에 쓸 수 있는 것으로 갈아탄다 | **같은 급 모델로 간다는 뜻이 아니다** |
|
|
20
|
+
| 보내기 전에 프롬프트를 줄여 토큰을 아낀다 | 압축은 되돌릴 수 있는 것과 아닌 것이 섞여 있다 |
|
|
21
|
+
| 여러 무료 티어를 한 창구로 모은다 | 각 티어의 분당·일당 한도는 그대로 살아 있다 |
|
|
22
|
+
|
|
23
|
+
## 1. 이 컴퓨터가 되는지 먼저 잰다 — **이 순서를 건너뛰지 마라**
|
|
24
|
+
|
|
25
|
+
```
|
|
26
|
+
npx --yes reborn-omniroute --check
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
깔지 않고 재기만 한다. 무엇을 보나:
|
|
30
|
+
|
|
31
|
+
| 잰다 | 왜 |
|
|
32
|
+
|---|---|
|
|
33
|
+
| **노드 판** | ★★여기서 가장 많이 막힌다. `engines` 가 좁다 — 원장의 `실측.노드요구` 를 그대로 읽어 가른다 |
|
|
34
|
+
| `omniroute` 실행 파일 | 이미 깔려 있는데 또 까는 일을 막는다 |
|
|
35
|
+
| **포트 20128** | 다른 것이 물고 있으면 켜지지 않는다. 「응답하나」를 실제로 본다 |
|
|
36
|
+
| 도커 | 노드 판을 못 맞출 때의 우회로가 열려 있는지 |
|
|
37
|
+
| 클로드 코드 | 있으면 설정 바꾸는 자리를 바로 알려 준다 |
|
|
38
|
+
|
|
39
|
+
★**노드 판이 안 맞을 때 지금 판을 지우라고 하지 마라.** nvm·volta 로 판을 **하나 더** 깔면 된다.
|
|
40
|
+
그것도 싫으면 도커 쪽 길이 있다(원장 `설치.도커`).
|
|
41
|
+
|
|
42
|
+
## 2. 깐다
|
|
43
|
+
|
|
44
|
+
```
|
|
45
|
+
npm install -g omniroute # 원장 설치.원도구
|
|
46
|
+
omniroute # 게이트웨이를 켠다 (기본 포트 20128)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
켜지면 대시보드가 `http://localhost:20128` 에 열린다.
|
|
50
|
+
|
|
51
|
+
## 3. 도구를 물린다
|
|
52
|
+
|
|
53
|
+
**바꾸는 것은 주소 하나다.** OpenAI 호환 엔드포인트를 쓰는 도구면 다 같다.
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
http://localhost:20128/v1
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
클로드 코드·커서·클라인·코파일럿·코덱스·오픈코드가 여기에 붙는다.
|
|
60
|
+
정확한 설정 자리는 원장 `실측.붙는도구` 와 저장소 문서를 함께 보고 말해라 —
|
|
61
|
+
도구마다 환경변수 이름이 다르고, **기억으로 단정하지 마라.**
|
|
62
|
+
|
|
63
|
+
## 4. 무엇이 정말 공짜인가 — 이 자리에서 반드시 말한다
|
|
64
|
+
|
|
65
|
+
원장 `★공짜의범위` 를 읽어 그대로 말한다. 요약하면:
|
|
66
|
+
|
|
67
|
+
- OmniRoute 자체는 MIT 라 값이 없다
|
|
68
|
+
- **무료 토큰 월 합계는 여러 무료 티어를 합쳐 센 값**이다. 한 곳에서 다 나오지 않는다
|
|
69
|
+
- 그 합계를 다 쓰려면 **문서에 적힌 만큼 계정을 만들어야** 한다
|
|
70
|
+
- 폴백 1·2단계가 구독·API 키다. **무료는 마지막 단계**다
|
|
71
|
+
|
|
72
|
+
## 5. 안전 — 세 줄은 빼지 마라
|
|
73
|
+
|
|
74
|
+
원장 `★안전` 을 읽어 말한다. 특히:
|
|
75
|
+
|
|
76
|
+
- 게이트웨이는 **내 컴퓨터에서 돈다.** 프롬프트가 남의 서버를 거치지 않는다 (장점)
|
|
77
|
+
- 기본값은 `127.0.0.1` 에만 연다. **`0.0.0.0` 으로 열면** 같은 망의 누구나 내 키로 모델을 부른다
|
|
78
|
+
- 키가 암호화돼 저장돼도 **그 컴퓨터를 쓰는 사람은 다 쓸 수 있다.** 공용 PC 에 올리지 마라
|
|
79
|
+
|
|
80
|
+
## 6. 안 되면 어디를 보나
|
|
81
|
+
|
|
82
|
+
| 증상 | 볼 곳 |
|
|
83
|
+
|---|---|
|
|
84
|
+
| `EBADENGINE` 으로 설치가 멈춘다 | 노드 판. `--check` 가 이걸 먼저 본다 |
|
|
85
|
+
| 켰는데 대시보드가 안 열린다 | 포트 20128 을 다른 것이 물고 있는지 |
|
|
86
|
+
| 도구가 계속 원래 주소로 간다 | 그 도구의 base URL 설정이 실제로 바뀌었는지 |
|
|
87
|
+
| 답이 이상하게 짧거나 결이 다르다 | 지금 어느 티어로 폴백됐는지. 대시보드에서 본다 |
|
|
88
|
+
| 압축 때문에 결과가 달라진 것 같다 | 공격적인 단계가 켜져 있는지 |
|
|
89
|
+
|
|
90
|
+
## 7. 출처
|
|
91
|
+
|
|
92
|
+
OmniRoute 는 **우리가 만든 것이 아니다.** 원장 `실측.만든이` 가 MIT 로 공개한 도구다.
|
|
93
|
+
저장소 주소를 함께 말한다 — 사람들이 직접 가서 볼 수 있어야 한다.
|
package/skill/omni.json
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_doc": "OmniRoute 사실 원장 — 릴스·착지·DM·설치기가 전부 이 파일 하나를 본다. 지어낸 숫자·지어낸 이름이 화면에 뜨지 않게 하는 것이 목적이다. 고칠 일이 생기면 여기부터 고친다.",
|
|
3
|
+
"확인일": "2026-09-04",
|
|
4
|
+
"확인방법": "npm 레지스트리(registry.npmjs.org/omniroute)와 GitHub API(repos/diegosouzapw/OmniRoute)를 직접 호출해 버전·라이선스·엔진·별을 재고, README 원문(raw.githubusercontent.com)을 받아 무료 토큰 headline·압축률·포트·설치 명령을 그대로 읽었다. 사람이 기억으로 적은 값은 하나도 없다.",
|
|
5
|
+
"이름": "OmniRoute",
|
|
6
|
+
"한글이름": "옴니라우트",
|
|
7
|
+
"한줄": "클로드 코드·커서·코덱스가 부르는 주소를 내 컴퓨터의 주소 하나로 바꿔, 수백 곳의 AI 서비스로 대신 보내 주는 게이트웨이. 한도가 차면 다음 모델로 스스로 갈아타고, 보내기 전에 프롬프트를 줄여 토큰을 아낀다.",
|
|
8
|
+
"참고본": {
|
|
9
|
+
"계정": "@nick_saraev",
|
|
10
|
+
"파일": "문제해결/릴스/KakaoTalk_20260825_225639273.mp4",
|
|
11
|
+
"길이": 30.487,
|
|
12
|
+
"말한것": "클로드 코드를 거의 무제한으로, 그것도 완전 무료로. 200개 이상의 무료 AI 서비스와 연결해 매달 16억 무료 토큰",
|
|
13
|
+
"★참고본이_안한말": "노드 판이 아주 좁다(22.22.2 이상 23 미만, 또는 24 이상 27 미만). 요즘 흔한 20.x·22.14 같은 판에서는 전역 설치가 그 자리에서 막힌다. 그리고 무료 토큰은 무한이 아니라 월 약 15.1억이며, 그 수는 여러 무료 티어를 합쳐 센 값이다 — 한 곳에서 다 나오지 않는다.",
|
|
14
|
+
"★수치가_달라진_이유": "참고본은 251곳·90+무료·16억이라고 말한다. 그건 그 영상이 만들어진 시점의 값이고 지금은 352곳·15.1억이다. 옛 수치를 그대로 옮기면 잰 값이 아니라 지어낸 값이 된다."
|
|
15
|
+
},
|
|
16
|
+
"실측": {
|
|
17
|
+
"저장소": "https://github.com/diegosouzapw/OmniRoute",
|
|
18
|
+
"만든이": "diegosouzapw",
|
|
19
|
+
"라이선스": "MIT",
|
|
20
|
+
"별": 60900,
|
|
21
|
+
"포크": 8460,
|
|
22
|
+
"프로바이더": 352,
|
|
23
|
+
"무료프로바이더표기": "README 히어로와 GitHub About 이 둘 다 150+ free. 하한 150 으로 쓴다",
|
|
24
|
+
"모델": 1200,
|
|
25
|
+
"무료토큰월": 1510000000,
|
|
26
|
+
"무료토큰월표기": "약 15.1억 (README headline: ~1.51B tokens/month · 첫 달은 가입 크레딧까지 ~2.13B)",
|
|
27
|
+
"압축엔진": "RTK + Caveman 기본 조합 · 총 12종 조합 가능",
|
|
28
|
+
"압축하한": 78,
|
|
29
|
+
"압축상한": 95,
|
|
30
|
+
"압축표기": "README 압축표 기준 스택(RTK→Caveman) 78~95%. 히어로 문구는 15~95%(평균 약 89%)",
|
|
31
|
+
"패키지": "omniroute",
|
|
32
|
+
"패키지버전": "3.8.50",
|
|
33
|
+
"패키지공개일": "2026-02-14",
|
|
34
|
+
"패키지최신발행": "2026-08-28",
|
|
35
|
+
"노드요구": ">=22.22.2 <23 || >=24.0.0 <27",
|
|
36
|
+
"포트": 20128,
|
|
37
|
+
"엔드포인트": "http://localhost:20128/v1",
|
|
38
|
+
"대시보드": "http://localhost:20128",
|
|
39
|
+
"붙는도구": [
|
|
40
|
+
"Claude Code",
|
|
41
|
+
"Codex",
|
|
42
|
+
"Cursor",
|
|
43
|
+
"Cline",
|
|
44
|
+
"Copilot",
|
|
45
|
+
"OpenCode",
|
|
46
|
+
"Antigravity"
|
|
47
|
+
],
|
|
48
|
+
"폴백단계": [
|
|
49
|
+
"구독",
|
|
50
|
+
"API 키",
|
|
51
|
+
"저가",
|
|
52
|
+
"무료"
|
|
53
|
+
],
|
|
54
|
+
"★지역": "MIT — 지역 제한 조항 없음. 한국에서 쓰는 데 라이선스상 걸리는 것이 없다 [[copied-reel-claims-need-license-territory-check]]",
|
|
55
|
+
"프로바이더_저장소설명": 352,
|
|
56
|
+
"프로바이더_README히어로": 356,
|
|
57
|
+
"프로바이더하한": 350,
|
|
58
|
+
"★프로바이더가_두값인이유": "2026-09-04 같은 날 같은 사이트가 두 값을 말한다 — GitHub About 과 npm 설명은 352, README 히어로(라이브 SVG)는 356. 저장소가 하루에도 여러 번 밀려 값이 계속 오른다. ★그래서 화면과 대본에는 **하한(350곳 이상)**만 쓴다. 어느 쪽을 골라도 우리 캡처와 어긋나지 않는다. 정확한 수를 말하면 그 수가 찍힌 캡처를 같은 화면에 띄워야 하는데, 그 캡처가 내일 달라진다.",
|
|
59
|
+
"무료프로바이더하한": 150
|
|
60
|
+
},
|
|
61
|
+
"설치": {
|
|
62
|
+
"★명령": "npx --yes reborn-omniroute",
|
|
63
|
+
"원도구": "npm install -g omniroute",
|
|
64
|
+
"도커": "docker run -d -p 127.0.0.1:20128:20128 -v omniroute-data:/app/data diegosouzapw/omniroute:latest",
|
|
65
|
+
"★왜_우리것을_먼저": "원도구 한 줄은 공개돼 있고 우리가 대신 쳐 준다고 아껴지는 게 없다. 실제로 막히는 자리는 노드 판이라, 우리 설치기는 그 판을 먼저 재고 안 되면 무엇을 해야 하는지 알려 준다."
|
|
66
|
+
},
|
|
67
|
+
"★영상이_말하지_않은_것": {
|
|
68
|
+
"노드판": "**노드 22.22.2 이상 23 미만, 또는 24 이상 27 미만**이라야 전역 설치가 붙는다. 20.x 나 22.14 에서는 `npm i -g omniroute` 가 EBADENGINE 으로 멈춘다. 그런데 요즘 LTS 안내가 20·22 를 가리켜서, 이 자리에서 막히는 사람이 가장 많다.",
|
|
69
|
+
"무료의범위": "무료 토큰 월 약 15.1억은 **여러 무료 티어를 합쳐 센 값**이다. 한 곳에서 다 나오지 않고, 각 티어의 분당·일당 한도는 그대로 살아 있다. 큰 작업 하나를 한 모델로 밀어붙이는 데는 못 쓴다.",
|
|
70
|
+
"품질": "폴백은 「그다음 쓸 수 있는 것」으로 간다. **같은 급 모델로 간다는 뜻이 아니다.** 클로드 한도가 차서 무료 티어로 내려가면 결과의 결이 달라지는 순간이 온다.",
|
|
71
|
+
"압축": "압축은 **되돌릴 수 있는 것과 아닌 것**이 섞여 있다. 저장소도 lossy 모드를 따로 표시해 둔다. 코드·구조화된 내용은 보호 장치가 붙지만, 공격적인 단계를 켜면 원문이 그대로 오지는 않는다.",
|
|
72
|
+
"키": "무료 티어라도 **가입은 각자 해야 한다.** 키 없이 바로 답하는 곳은 몇 곳뿐이고, 15.1억을 다 쓰려면 문서에 적힌 만큼 계정을 만들어야 한다.",
|
|
73
|
+
"판정": "**한도에 걸려 멈추는 일을 줄여 주는 도구**다. 클로드를 공짜로 쓰게 해 주는 도구가 아니다. 이 차이를 모르고 깔면 첫날 실망한다."
|
|
74
|
+
},
|
|
75
|
+
"★공짜의범위": {
|
|
76
|
+
"무료": [
|
|
77
|
+
"OmniRoute 자체 — MIT. 소스도 공개돼 있고 값이 없다",
|
|
78
|
+
"우리 설치기와 안내 스킬 — 값이 없다",
|
|
79
|
+
"키 없이 답하는 무료 프로바이더 일부 — 가입 없이 바로 붙는다"
|
|
80
|
+
],
|
|
81
|
+
"★따로_드는것": [
|
|
82
|
+
{
|
|
83
|
+
"무엇": "노드 판 올리기",
|
|
84
|
+
"값": "0원 · 시간 10~20분",
|
|
85
|
+
"근거": "npm 레지스트리 engines 필드 >=22.22.2 <23 || >=24.0.0 <27"
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"무엇": "무료 티어 계정 만들기",
|
|
89
|
+
"값": "0원 · 곳마다 5분",
|
|
90
|
+
"근거": "README 무료 티어 표 — 대부분 가입이 있어야 키가 나온다"
|
|
91
|
+
},
|
|
92
|
+
{
|
|
93
|
+
"무엇": "유료 모델을 섞어 쓸 때",
|
|
94
|
+
"값": "그 서비스 종량제 요금",
|
|
95
|
+
"근거": "폴백 1·2단계가 구독·API 키다 — 무료 단계는 마지막이다"
|
|
96
|
+
},
|
|
97
|
+
{
|
|
98
|
+
"무엇": "이 컴퓨터가 계속 켜져 있어야 한다",
|
|
99
|
+
"값": "전기 · 또는 서버 비용",
|
|
100
|
+
"근거": "게이트웨이가 내 컴퓨터의 20128 포트에서 돈다"
|
|
101
|
+
}
|
|
102
|
+
]
|
|
103
|
+
},
|
|
104
|
+
"★안전": {
|
|
105
|
+
"로컬": "게이트웨이가 **내 컴퓨터에서 돈다.** 프롬프트가 우리 서버를 거치지 않는다 — 이건 이 도구의 장점이다",
|
|
106
|
+
"포트": "20128 은 기본값으로 127.0.0.1 에만 연다. **0.0.0.0 으로 열지 마라** — 여는 순간 같은 망의 누구나 내 키로 모델을 부를 수 있다",
|
|
107
|
+
"키보관": "저장소는 키를 AES-256-GCM 로 암호화해 둔다고 적는다. 그래도 **그 컴퓨터를 쓰는 사람은 다 쓸 수 있다** — 공용 PC 에 올리지 마라",
|
|
108
|
+
"postinstall": "이 패키지는 설치 중에 로컬 `.env` 를 만들 수 있다고 README 가 적어 둔다. 그 파일은 커밋하지 마라"
|
|
109
|
+
}
|
|
110
|
+
}
|
package/tail.mjs
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 「끝난 뒤 한 칸」 문안 — 정본은 `skillpack/tail_copy.mjs` 다.
|
|
3
|
+
* 사본이 여기 있는 이유는 하나뿐이다: npm 패키지는 폴더 밖을 import 할 수 없다.
|
|
4
|
+
* 고칠 일이 생기면 정본을 먼저 고치고 여기로 옮긴다. 반대로 하지 마라.
|
|
5
|
+
*
|
|
6
|
+
* 규율: ① 끝난 뒤 딱 한 칸 · ② 못 깔았으면 안 띄운다 · ③ 파일을 안 남긴다
|
|
7
|
+
* ④ 고지 블록 금지 · ⑤ 첫 줄은 방금 한 일에서 이어져야 한다
|
|
8
|
+
* ★클로드킷·자비스 풀스택은 여기서 말하지 않는다 — 스킬팩 구매 이후다(2026-08-24 전략).
|
|
9
|
+
*/
|
|
10
|
+
export const 랜딩 = "https://rebornlabs.kr/skillpack";
|
|
11
|
+
export const 링크 = (퍼널) => `${랜딩}?utm_source=installer&utm_medium=cli&utm_campaign=${퍼널}`;
|
|
12
|
+
|
|
13
|
+
export const TAIL = {
|
|
14
|
+
omni: {
|
|
15
|
+
이음: [
|
|
16
|
+
"방금 한 일은 **깔기 전에 이 컴퓨터의 노드 판을 잰 것**입니다 — OmniRoute 가 요구하는 판이 아주 좁아서, 여기서 막히는 사람이 가장 많습니다.",
|
|
17
|
+
"소개 영상은 무엇을 해 주는지까지 말합니다. 막히는 자리는 늘 **그 다음**입니다.",
|
|
18
|
+
],
|
|
19
|
+
제목: "리본랩스 스킬팩",
|
|
20
|
+
한줄: "같은 방식으로 골라 담은 48종을 한 줄로. 8,900원",
|
|
21
|
+
},
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
export const 마지막 = "목록과 직접 까는 명령까지 그 페이지에 그대로 적어 두었습니다. 안 사셔도 됩니다.";
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
# 안내스킬 — `omniroute-guide`
|
|
2
|
+
|
|
3
|
+
이 패키지가 `~/.claude/skills/omniroute-guide/` 에 심는 것.
|
|
4
|
+
|
|
5
|
+
| 파일 | 무엇 |
|
|
6
|
+
|---|---|
|
|
7
|
+
| `SKILL.md` | 클로드가 읽는 안내. **답하는 순서**가 여기 있다 |
|
|
8
|
+
| `omni.json` | **원장.** 모든 사실의 주인. 여기 없는 숫자·명령은 말하지 않는다 |
|
|
9
|
+
|
|
10
|
+
★심는 것은 이 폴더 하나뿐이다. **공유 안내스킬에는 손대지 않는다** —
|
|
11
|
+
같은 파일을 여러 설치기가 덮는 사고를 애초에 안 만든다.
|
|
12
|
+
|
|
13
|
+
## 왜 원장을 따로 두나
|
|
14
|
+
|
|
15
|
+
사실을 `SKILL.md` 본문에 적으면 두 곳이 서로 다른 말을 하기 시작한다.
|
|
16
|
+
설치 명령·버전·라이선스·별 수·프로바이더 수·무료 토큰 합계·확인일은
|
|
17
|
+
**한 곳에서만** 나온다. [[one-metric-one-owner]]
|
|
18
|
+
|
|
19
|
+
★특히 **설치 명령 문자열**과 **엔드포인트 주소**가 그렇다.
|
|
20
|
+
릴스 자막·터미널 목업·착지 페이지·DM 이 전부 원장의 `설치.★명령` 과 `실측.엔드포인트` 를
|
|
21
|
+
읽어 간다. 손으로 옮겨 적으면 어느 한 곳이 조용히 어긋나고,
|
|
22
|
+
그 자리가 고객 터미널에서 멈추는 자리가 된다.
|
|
23
|
+
|
|
24
|
+
원장에는 **확인일과 확인방법**이 반드시 있다. 없으면 설치기가 막는다.
|
|
25
|
+
|
|
26
|
+
## ★이 킷의 존재 이유는 `★영상이_말하지_않은_것` 이다
|
|
27
|
+
|
|
28
|
+
참고본은 OmniRoute 가 **무엇을 해 주는지**까지만 말한다. 그건 맞다.
|
|
29
|
+
**남는 것이 있다:**
|
|
30
|
+
|
|
31
|
+
- ① **노드 판이 아주 좁다.** `engines` 가 `>=22.22.2 <23 || >=24.0.0 <27` 이다.
|
|
32
|
+
요즘 LTS 안내가 가리키는 20.x·22.14 에서는 `npm i -g omniroute` 가
|
|
33
|
+
`EBADENGINE` 으로 그 자리에서 멈춘다 — **여기서 막히는 사람이 가장 많다**
|
|
34
|
+
- ② **무료 토큰 월 합계는 여러 무료 티어를 합쳐 센 값**이다. 한 곳에서 다 나오지 않고
|
|
35
|
+
각 티어의 분당·일당 한도는 그대로 살아 있다
|
|
36
|
+
- ③ **폴백은 「그다음 쓸 수 있는 것」으로 간다.** 같은 급 모델로 간다는 뜻이 아니다
|
|
37
|
+
- ④ **압축에는 되돌릴 수 있는 것과 아닌 것이 섞여 있다.** 저장소도 lossy 모드를 따로 표시한다
|
|
38
|
+
- ⑤ **무료 티어라도 가입은 각자 해야 한다.** 키 없이 바로 답하는 곳은 몇 곳뿐이다
|
|
39
|
+
|
|
40
|
+
원장에서 `판정` 한 줄을 지우면 설치기가 **깔지 않고 멈춘다.**
|
|
41
|
+
그게 없으면 우리는 참고본과 똑같은 말만 하는 패키지가 되기 때문이다.
|
|
42
|
+
|
|
43
|
+
## ★포트 문장은 빼면 안 된다
|
|
44
|
+
|
|
45
|
+
게이트웨이는 기본값으로 `127.0.0.1:20128` 에만 연다.
|
|
46
|
+
**`0.0.0.0` 으로 여는 순간** 같은 망의 누구나 내 키로 모델을 부를 수 있다.
|
|
47
|
+
「로컬만 허용」이라고 믿고 열었다가 인터넷 전체에 열린 사고를 우리가 이미 한 번 겪었다
|
|
48
|
+
[[tunnel-makes-islocal-see-loopback]].
|
|
49
|
+
원장의 `★안전.포트` 가 비면 설치기가 막는다. 이건 편의 문구가 아니라 안전 장치다.
|
|
50
|
+
|
|
51
|
+
## ★숫자는 잰 값만 쓴다
|
|
52
|
+
|
|
53
|
+
버전·라이선스·별 수·프로바이더 수·무료 토큰 합계는 2026-09-04 에
|
|
54
|
+
npm 레지스트리·GitHub API·README 원문을 직접 읽은 값이다.
|
|
55
|
+
기억으로 적은 값은 하나도 없다. 확인일이 지났으면 다시 재고 원장을 먼저 고친다.
|
|
56
|
+
|
|
57
|
+
★**참고본의 수치를 그대로 옮기지 않았다.** 참고본은 251곳·16억이라고 말하는데
|
|
58
|
+
그건 그 영상이 만들어진 시점의 값이다. 옛 수치를 옮기면 잰 값이 아니라 지어낸 값이 된다
|
|
59
|
+
[[content-number-fabrication-guard]].
|
|
60
|
+
|
|
61
|
+
## ★출처는 세 겹으로 박는다
|
|
62
|
+
|
|
63
|
+
OmniRoute 는 우리가 만든 것이 아니다. `diegosouzapw` 님이 MIT 로 공개한 도구다.
|
|
64
|
+
설치기 화면 끝 · 안내 스킬 §7 · 착지 페이지에 각각 저장소 주소가 박혀 있다
|
|
65
|
+
[[reel-borrowed-logo-needs-attribution]].
|
|
66
|
+
|
|
67
|
+
## 자기시험
|
|
68
|
+
|
|
69
|
+
```
|
|
70
|
+
node cli.mjs --self-test
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
원장 검사기를 **양방향으로** 시험한다(오탐 1 + 미탐 5), 그리고
|
|
74
|
+
노드 판 가르개를 되어야 할 판 4개 · 막아야 할 판 6개로 눌러 본다.
|
|
75
|
+
전건 통과나 전건 실패는 고장 신호다 [[verifier-must-be-tested-both-ways]].
|