reborn-strix 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 REBORN LABS
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # reborn-strix
2
+
3
+ **Strix — 돌리기 전에 볼 것.** 클로드 코드 스킬 설치기(설치 대행).
4
+
5
+ ```bash
6
+ npx --yes reborn-strix
7
+ ```
8
+
9
+ > **Strix 는 [usestrix/strix](https://github.com/usestrix/strix) 의 오픈소스입니다(Apache-2.0).**
10
+ > 이 패키지는 저희(REBORN LABS)가 만든 **한국어 안내와 설치 대행**입니다.
11
+ > Strix 자체를 만들지도, 고치지도, 재배포하지도 않습니다. 제휴 관계도 없습니다.
12
+
13
+ ## 무엇을 깔아 주나
14
+
15
+ | 명령 | 무엇 |
16
+ |---|---|
17
+ | `npx --yes reborn-strix` | `strix-guide` 안내 스킬 + 사실 원장(`strix.json`)을 `~/.claude/skills/` 에 |
18
+ | `npx --yes reborn-strix skills` | usestrix **공식 스킬 아홉 개**를 저장소에서 직접 받아 설치 |
19
+ | `npx --yes reborn-strix doctor` | 도커 · 파이썬 3.12 · LLM 키가 지금 준비됐는지 |
20
+ | `npx --yes reborn-strix run` | 첫 실행 한 줄과 틀린 명령 셋 |
21
+ | `npx --yes reborn-strix facts` | 원장 전체 |
22
+ | `npx --yes reborn-strix verify` | 저장소 · 문서 · PyPI 가 지금 응답하는지 |
23
+
24
+ ★공식 스킬은 **이 패키지에 사본으로 넣지 않았습니다.** 설치 시점에
25
+ `usestrix/strix` 에서 직접 받습니다 — 원본이 바뀌면 그대로 따라갑니다.
26
+ 공식 통로 `npx skills add usestrix/strix` 와 같은 것을 깔아 드리는 것뿐입니다.
27
+
28
+ ## 왜 만들었나
29
+
30
+ Strix 소개 영상은 대개 여기까지만 말합니다 — "완전 무료 오픈소스, 폴더만 가리키면 된다."
31
+ 맞는 말이지만 **처음 돌릴 때 실제로 걸리는 것 넷**이 빠져 있습니다.
32
+
33
+ 1. **「완전 무료」는 도구 값 이야기입니다.** 모델은 딸려오지 않습니다.
34
+ 게다가 `--scan-mode` 기본값이 `deep` 이라, 빠른시작 그대로 치면
35
+ **첫 실행이 가장 깊고 가장 비싼 실행**이 됩니다.
36
+ → 첫 실행은 `--scan-mode quick --max-budget 2`.
37
+ 2. **로컬 폴더는 쓰기 가능하게 마운트됩니다.**
38
+ 공식 문서 원문: *"A local directory is mounted into the sandbox live and **writable**,
39
+ so the agent edits your real files (.git excepted). Commit or stash first."*
40
+ 3. **전제 셋** — 도커가 떠 있어야 하고, 파이썬 **3.12 이상**이어야 하고
41
+ (PyPI 이름은 `strix` 가 아니라 **`strix-agent`** 입니다), LLM API 키가 따로 필요합니다.
42
+ 4. **`Authorized use only`.** 내가 소유했거나 문서로 허락받은 표적에만 겨눕니다.
43
+ 권한 없는 스캔은 대부분의 나라에서 위법입니다.
44
+
45
+ 원장(`skill/strix.json`)은 이 넷을 **확인일과 출처와 함께** 적어 둔 파일이고,
46
+ 설치기는 「복사가 됐나」가 아니라 **「원장이 조건을 숨기지 않았나」** 로 판정합니다.
47
+
48
+ ```bash
49
+ npx --yes reborn-strix --self-test # 검사기를 양방향으로 시험한다 (31건)
50
+ npx --yes reborn-strix --check # 깔렸는지 + 원장이 규칙을 지키는지
51
+ ```
52
+
53
+ ## 원본
54
+
55
+ - 저장소 <https://github.com/usestrix/strix> (Apache-2.0)
56
+ - 문서 <https://docs.strix.ai>
57
+ - PyPI <https://pypi.org/project/strix-agent/>
58
+
59
+ ## 라이선스
60
+
61
+ 이 설치기는 MIT. Strix 본체와 공식 스킬은 usestrix 의 Apache-2.0 저작물이며
62
+ 저작자 표시는 각 파일 머리말에 그대로 남습니다.
package/cli.mjs ADDED
@@ -0,0 +1,604 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * Strix — 돌리기 전에 볼 것. 스킬 설치기.
4
+ *
5
+ * npx --yes reborn-strix 클로드 코드에 안내 스킬을 깐다
6
+ * npx --yes reborn-strix run 맞는 첫 실행 한 줄과 틀린 명령을 나란히 (안 깔고)
7
+ * npx --yes reborn-strix facts 원장 전체를 찍는다
8
+ * npx --yes reborn-strix doctor 도커·파이썬·LLM 키가 지금 준비됐는지 잰다
9
+ * npx --yes reborn-strix skills usestrix 공식 스킬 아홉 개를 깐다 (설치 대행)
10
+ * npx --yes reborn-strix verify 저장소·문서·PyPI 가 지금 응답하는지 (네트워크)
11
+ * npx --yes reborn-strix --check 깔렸는지 + 원장이 규칙을 지키는지
12
+ * npx --yes reborn-strix --self-test 자기시험 (네트워크 안 씀)
13
+ *
14
+ * ★★이 설치기가 주는 건 파일이 아니라 **원장의 정직함**이다.
15
+ * 그래서 판정을 "복사가 됐나"로 하지 않는다. **원장이 조건을 숨기지 않았나**로 한다.
16
+ *
17
+ * ① **「완전 무료」의 조건** — 도구는 Apache-2.0 이라 0원이지만 LLM 토큰 값은 따로 든다.
18
+ * 게다가 --scan-mode 기본값이 **deep** 이라 빠른시작 그대로 치면 첫 실행이 제일 비싸다.
19
+ * 원본 릴스는 「완전 무료」라고만 하고 이 줄을 말하지 않는다.
20
+ * ② **로컬 폴더가 쓰기 가능하게 마운트된다** — 에이전트가 진짜 파일을 고친다.
21
+ * 공식 문서에 Note 로 적혀 있는데, 「폴더만 가리키면 된다」만 옮기면 통째로 빠진다.
22
+ * ③ **Authorized use only** — 권한 없는 표적 스캔은 대부분의 나라에서 위법이다.
23
+ * ④ **누가 만들었나** — usestrix 의 오픈소스다. 우리 것이 아니고 제휴도 없다.
24
+ *
25
+ * ★★★공식 스킬 아홉 개를 **벤더링하지 않는다.** 이 패키지 안에 남의 SKILL.md 사본을
26
+ * 넣어 두면 원본이 바뀌어도 우리 사본이 낡은 채로 계속 깔린다. 설치 시점에
27
+ * `usestrix/strix` 에서 **직접 받아** 깐다 — 그게 설치 대행이다.
28
+ *
29
+ * ★자기시험은 **양방향**이다. 좋은 원장을 통과시키는 것만으로는 아무것도 증명 못 한다.
30
+ * 일부러 조건을 지운 가짜 원장을 만들어 검사기가 **그걸 잡는지** 같이 본다
31
+ * [[verifier-must-be-tested-both-ways]].
32
+ */
33
+ import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, statSync } from "node:fs";
34
+ import { execFileSync } from "node:child_process";
35
+ import { homedir } from "node:os";
36
+ import { dirname, join, relative } from "node:path";
37
+ import { fileURLToPath } from "node:url";
38
+ import { 링크, TAIL, 마지막 } from "./tail.mjs";
39
+
40
+ const HOME = homedir();
41
+ const 여기 = dirname(fileURLToPath(import.meta.url));
42
+ const c = { g: "\x1b[32m", r: "\x1b[31m", y: "\x1b[33m", d: "\x1b[90m", cy: "\x1b[36m", b: "\x1b[1m", 0: "\x1b[0m" };
43
+ /** 원장 문자열은 **굵게** 를 쓴다. 터미널에서 별표가 글자로 나오면 손으로 쓴 티가 난다. */
44
+ const 굵게 = (t, 바탕 = c.d) => String(t).replace(/\*\*(.+?)\*\*/g, `${c[0]}${c.b}$1${c[0]}${바탕}`);
45
+
46
+ const 스킬이름 = "strix-guide";
47
+ const 퍼널 = "strix";
48
+ const 원본 = join(여기, "skill");
49
+ const 스킬뿌리 = join(HOME, ".claude", "skills");
50
+ const 대상 = join(스킬뿌리, 스킬이름);
51
+
52
+ /** ★PyPI 이름. `strix` 가 아니다 — 이 한 줄이 이 킷의 첫 번째 이유다. */
53
+ const PYPI = "strix-agent";
54
+
55
+ /** ★첫 실행에 반드시 붙어 있어야 하는 브레이크 둘 */
56
+ const 브레이크 = ["--scan-mode quick", "--max-budget"];
57
+
58
+ /** ★공식 스킬 — usestrix 것이다. 이름을 지어내면 안 깔린다. */
59
+ const 공식스킬 = [
60
+ "api-security-testing",
61
+ "application-security-testing",
62
+ "ci-security-scanning-with-strix",
63
+ "find-security-vulnerabilities-in-code",
64
+ "fix-security-vulnerabilities-with-strix",
65
+ "managed-pentesting-with-strix",
66
+ "owasp-top-10-testing",
67
+ "penetration-testing-with-strix",
68
+ "web-app-penetration-testing",
69
+ ];
70
+
71
+ /** ★「무료」를 조건 없이 말하면 막는다. 조건은 늘 있다. */
72
+ const 금칙 = [/완전\s*무료/, /무제한/, /제한\s*없이/, /보안\s*끝/];
73
+
74
+ function 머리말() {
75
+ console.log(`\n${c.b}Strix — 돌리기 전에 볼 것${c[0]} ${c.d}· 설치 대행 · REBORN LABS${c[0]}`);
76
+ console.log(`${c.d}Strix 는 usestrix 의 오픈소스입니다(Apache-2.0). 저희 도구가 아닙니다.${c[0]}`);
77
+ console.log(`${c.d}전제 셋과, 값이 어디서 붙는지와, 어디까지 겨눠도 되는지를 먼저 말합니다.${c[0]}\n`);
78
+ }
79
+
80
+ function 파일목록(뿌리) {
81
+ const 모음 = [];
82
+ for (const 이름 of readdirSync(뿌리)) {
83
+ const p = join(뿌리, 이름);
84
+ if (statSync(p).isDirectory()) 모음.push(...파일목록(p));
85
+ else 모음.push(p);
86
+ }
87
+ return 모음;
88
+ }
89
+
90
+ const 원장읽기 = (뿌리) => {
91
+ const f = join(뿌리, "strix.json");
92
+ if (!existsSync(f)) return null;
93
+ try { return JSON.parse(readFileSync(f, "utf8")); } catch { return null; }
94
+ };
95
+
96
+ /**
97
+ * ★원장 판정. 여기가 이 설치기의 심장이다.
98
+ * 깔렸는지가 아니라 **깔린 원장이 조건을 숨기지 않았는지**를 본다.
99
+ */
100
+ function 원장검사(d) {
101
+ if (!d) return { 됨: false, 이유: "strix.json 을 못 읽었습니다" };
102
+ if (!d.확인일) return { 됨: false, 이유: "확인일이 없습니다 — 언제 기준인지 모르면 못 믿습니다" };
103
+ if (!d.확인방법) return { 됨: false, 이유: "확인방법이 없습니다 — 어디서 잰 숫자인지 모르면 못 믿습니다" };
104
+
105
+ /* ── ① 누가 만들었나 — 남의 것을 우리 것처럼 말하지 않는다 ────── */
106
+ const P = d.제품;
107
+ if (!P) return { 됨: false, 이유: "제품 칸이 없습니다" };
108
+ if (!/usestrix/.test(String(P.만든곳 || "")))
109
+ return { 됨: false, 이유: "만든곳이 usestrix 라고 적혀 있지 않습니다 — 남의 오픈소스입니다" };
110
+ if (!/Apache-2\.0/.test(String(P.라이선스 || "")))
111
+ return { 됨: false, 이유: "라이선스(Apache-2.0)가 없습니다" };
112
+ if (!P.무엇이아닌가)
113
+ return { 됨: false, 이유: "「무엇이 아닌가」가 없습니다 — 실증형과 정적 분석기의 차이를 안 밝히면 과장이 됩니다" };
114
+
115
+ /* ── ② ★실행 — 이 킷이 존재하는 첫 번째 이유 ─────────────────── */
116
+ const E = d["★실행"];
117
+ if (!E) return { 됨: false, 이유: "실행 칸이 통째로 없습니다" };
118
+ if (!E.설치 || !/strix\.ai\/install/.test(E.설치))
119
+ return { 됨: false, 이유: "공식 설치 한 줄(curl -sSL https://strix.ai/install | bash)이 없습니다" };
120
+ if (!E.첫실행) return { 됨: false, 이유: "첫 실행 명령이 없습니다" };
121
+ for (const b of 브레이크)
122
+ if (!E.첫실행.includes(b))
123
+ return { 됨: false, 이유: `첫 실행에 「${b}」 가 없습니다 — 기본 깊이가 deep 이라 브레이크 없이 권하면 안 됩니다` };
124
+ if (!Array.isArray(E["★틀린명령"]) || E["★틀린명령"].length < 3)
125
+ return { 됨: false, 이유: "「틀린명령」이 3개 미만입니다 — 사람들이 실제로 치는 것을 적어야 합니다" };
126
+ for (const x of E["★틀린명령"]) {
127
+ if (!x?.명령 || !x?.무슨일)
128
+ return { 됨: false, 이유: "「틀린명령」에 실제로 무슨 일이 나는지가 빠진 항목이 있습니다 — 「쓰지 마라」만으로는 안 지킵니다" };
129
+ }
130
+ if (!E["★틀린명령"].some((x) => /pip install strix\b/.test(x.명령)))
131
+ return { 됨: false, 이유: `「pip install strix」 가 틀린 명령으로 적혀 있지 않습니다 — PyPI 이름은 ${PYPI} 입니다` };
132
+ if (!E["★틀린명령"].some((x) => /https?:\/\//.test(x.명령)))
133
+ return { 됨: false, 이유: "「남의 주소를 표적으로 넣는 것」이 틀린 명령에 없습니다 — 이 도구에서 가장 위험한 칸입니다" };
134
+ if (!E.결과폴더) return { 됨: false, 이유: "결과가 어디 남는지(strix_runs/<run-name>)가 없습니다" };
135
+
136
+ /* ── ③ ★값 — 「완전 무료」의 조건이 여기 있다 ─────────────────── */
137
+ const V = d["★값"];
138
+ if (!V) return { 됨: false, 이유: "값 칸이 없습니다 — 「완전 무료」만 남으면 첫 청구서에서 신뢰가 끝납니다" };
139
+ if (!V.따로드는것 || !/LLM|토큰|API/i.test(V.따로드는것))
140
+ return { 됨: false, 이유: "LLM 토큰 값이 따로 든다는 사실이 없습니다" };
141
+ if (V.기본깊이 !== "deep")
142
+ return { 됨: false, 이유: "기본 scan-mode 가 deep 이라는 사실이 없습니다 — 이걸 빼면 첫 실행이 가장 비싼 실행이 됩니다" };
143
+ if (!V.그래서어떻게 || !/max-budget/.test(V.그래서어떻게))
144
+ return { 됨: false, 이유: "「그래서 어떻게」에 --max-budget 이 없습니다 — 막기만 하면 아무도 안 씁니다" };
145
+ if (!V.출처) return { 됨: false, 이유: "값·깊이 이야기의 출처가 없습니다" };
146
+
147
+ /* ── ④ ★안전 — 이 도구에서 가장 먼저 말해야 하는 것 ──────────── */
148
+ const S = d["★안전"];
149
+ if (!S) return { 됨: false, 이유: "안전 칸이 없습니다 — 침투 도구에서 이걸 빼면 안 됩니다" };
150
+ if (!/Authorized use only/i.test(String(S.원문 || "")))
151
+ return { 됨: false, 이유: "저장소 원문(Authorized use only)이 없습니다 — 번역만 남기면 나중에 못 다툽니다" };
152
+ if (!S.설명은허용)
153
+ return { 됨: false, 이유: "무엇이 허용되는지가 없습니다 — 금지만 적으면 아무것도 못 하게 됩니다" };
154
+ if (!S.그래서어떻게) return { 됨: false, 이유: "안전 칸에 「그래서 어떻게」가 없습니다" };
155
+ if (!S.출처) return { 됨: false, 이유: "안전 규칙의 출처가 없습니다" };
156
+
157
+ /* ── ⑤ ★작업폴더 — 공식 문서 Note 를 옮겨야 한다 ─────────────── */
158
+ const Wk = d["★작업폴더"];
159
+ if (!Wk) return { 됨: false, 이유: "작업폴더 칸이 없습니다 — 로컬 폴더가 쓰기 가능하게 마운트됩니다" };
160
+ if (!/writable/i.test(String(Wk.원문 || "")))
161
+ return { 됨: false, 이유: "공식 문서 원문(… mounted … writable …)이 없습니다" };
162
+ if (!/커밋|스태시/.test(String(Wk.그래서어떻게 || "")))
163
+ return { 됨: false, 이유: "「먼저 커밋하거나 스태시하라」가 없습니다" };
164
+
165
+ /* ── ⑥ 전제 — 없으면 첫 줄에서 멈춘다 ────────────────────────── */
166
+ if (!Array.isArray(d.전제) || d.전제.length < 3)
167
+ return { 됨: false, 이유: "전제가 3개 미만입니다 (도커 · 파이썬 3.12 · LLM 키)" };
168
+ for (const t of d.전제)
169
+ if (!t?.이름 || !t?.필요 || !t?.왜 || !t?.확인)
170
+ return { 됨: false, 이유: "전제에 「왜」나 「확인 방법」이 빠진 항목이 있습니다" };
171
+ if (!d.전제.some((t) => /파이썬|python/i.test(t.이름) && /3\.12/.test(String(t.필요))))
172
+ return { 됨: false, 이유: "파이썬 3.12 이상이라는 전제가 없습니다 — 3.11 에는 안 깔립니다" };
173
+
174
+ /* ── ⑦ ★공식스킬 — 몇 개인지, 누가 만들었는지 ────────────────── */
175
+ const K = d["★공식스킬"];
176
+ if (!K) return { 됨: false, 이유: "공식스킬 칸이 없습니다" };
177
+ if (K.개수 !== 공식스킬.length)
178
+ return { 됨: false, 이유: `공식 스킬 수가 ${K.개수} 로 적혀 있습니다 — 저장소 skills/ 에는 ${공식스킬.length}개입니다` };
179
+ if (!Array.isArray(K.목록) || K.목록.length !== 공식스킬.length)
180
+ return { 됨: false, 이유: "공식 스킬 목록의 개수가 개수 칸과 다릅니다" };
181
+ for (const s of K.목록) {
182
+ if (!공식스킬.includes(String(s?.이름)))
183
+ return { 됨: false, 이유: `공식 스킬 목록에 없는 이름이 있습니다 — "${s?.이름}" (지어낸 이름은 안 깔립니다)` };
184
+ if (!s?.무엇) return { 됨: false, 이유: `공식 스킬 "${s.이름}" 에 「언제 쓰나」가 없습니다` };
185
+ }
186
+ if (!/우리가 만든 것이 아닙니다/.test(String(K.누가만들었나 || "")))
187
+ return { 됨: false, 이유: "공식 스킬이 남의 것이라는 사실이 적혀 있지 않습니다" };
188
+ if (!K.출처) return { 됨: false, 이유: "공식 스킬 목록의 출처가 없습니다" };
189
+
190
+ /* ── ⑧ 인기 — 스타는 관심이지 정확도가 아니다 ────────────────── */
191
+ const I = d.인기;
192
+ if (!I) return { 됨: false, 이유: "인기 칸이 없습니다" };
193
+ if (!Number.isFinite(I.스타)) return { 됨: false, 이유: "스타 수가 숫자가 아닙니다 — 「엄청 많다」는 셀 수 없습니다" };
194
+ if (!I.확인일) return { 됨: false, 이유: "스타 수에 확인일이 없습니다 — 숫자는 매일 바뀝니다" };
195
+ if (!I.주의) return { 됨: false, 이유: "스타 수에 「주의」가 없습니다 — 관심의 크기를 정확도로 읽게 두면 안 됩니다" };
196
+
197
+ /* ── ⑨ 금칙어 — 조건 없이 단정하지 않는다 ────────────────────── */
198
+ const 홍보문 = [
199
+ P.무엇, P.무엇이아닌가, V.도구값, V.그래서어떻게, S.그래서어떻게, K.누가만들었나,
200
+ ].join(" ");
201
+ for (const re of 금칙)
202
+ if (re.test(홍보문))
203
+ return { 됨: false, 이유: `설명에 「${re.source}」 에 걸리는 말이 있습니다 — 조건 없이 단정하지 않습니다` };
204
+
205
+ /* ── ⑩ 안 하는 말 — 무엇을 뺐느냐가 원장의 절반이다 ──────────── */
206
+ if (!Array.isArray(d.안하는말) || d.안하는말.length < 3)
207
+ return { 됨: false, 이유: "「안하는말」 이 3개 미만입니다 — 무엇을 안 말하는지가 원장의 절반입니다" };
208
+ for (const x of d.안하는말)
209
+ if (!x?.말 || !x?.이유) return { 됨: false, 이유: "「안하는말」 에 이유가 없는 항목이 있습니다" };
210
+ if (!d.안하는말.some((x) => /아무 사이트|권한/.test(x.말 + x.이유)))
211
+ return { 됨: false, 이유: "「안하는말」 에 권한 없는 표적 금지가 없습니다 — 이 킷의 가장 중요한 칸입니다" };
212
+
213
+ return { 됨: true, 스킬: K.개수, 스타: I.스타, 확인일: d.확인일 };
214
+ }
215
+
216
+ /** 깔린 스킬이 실제로 읽히는지 — 머리말까지 확인한다. */
217
+ function 진짜도나() {
218
+ const md = join(대상, "SKILL.md");
219
+ if (!existsSync(md)) return { 됨: false, 이유: "SKILL.md 가 없습니다" };
220
+ const 본문 = readFileSync(md, "utf8");
221
+ if (!new RegExp("^---[\\s\\S]*?\\bname:\\s*" + 스킬이름 + "\\b", "m").test(본문))
222
+ return { 됨: false, 이유: "SKILL.md 머리말이 깨졌습니다(name 을 못 읽습니다)" };
223
+ return 원장검사(원장읽기(대상));
224
+ }
225
+
226
+ const 원장고르기 = () => 원장읽기(existsSync(join(대상, "strix.json")) ? 대상 : 원본);
227
+ function 원장이나죽기() {
228
+ const d = 원장고르기();
229
+ const r = 원장검사(d);
230
+ if (!r.됨) { console.error(`${c.r}✗ 원장이 이상합니다${c[0]} — ${r.이유}`); process.exit(1); }
231
+ return d;
232
+ }
233
+
234
+ /* ── run ────────────────────────────────────────────────────────── */
235
+ function 실행보기() {
236
+ const d = 원장이나죽기();
237
+ 머리말();
238
+ const E = d["★실행"], V = d["★값"], Wk = d["★작업폴더"];
239
+
240
+ console.log(`${c.g}${c.b}✅ 이 순서입니다${c[0]}`);
241
+ console.log(` ${c.cy}${E.설치}${c[0]}`);
242
+ console.log(` ${c.cy}${E.설정1}${c[0]}`);
243
+ console.log(` ${c.cy}${E.설정2}${c[0]}`);
244
+ console.log(` ${c.cy}${c.b}${E.첫실행}${c[0]}`);
245
+ console.log(` ${c.d}${E.무엇이뜨나}${c[0]}`);
246
+ console.log(` ${c.d}필요한 것 — ${E.필요한것}${c[0]}\n`);
247
+
248
+ console.log(`${c.y}${c.b}★ 첫 실행에 옵션 둘을 붙인 이유${c[0]}`);
249
+ console.log(` ${c.d}${굵게(V["★기본깊이가_왜_문제인가"])}${c[0]}`);
250
+ console.log(` ${c.d}${굵게(V.그래서어떻게)}${c[0]}\n`);
251
+
252
+ console.log(`${c.r}${c.b}✗ 이렇게 치면 안 됩니다${c[0]}`);
253
+ for (const x of E["★틀린명령"]) {
254
+ console.log(` ${c.r}${x.명령}${c[0]}`);
255
+ console.log(` ${c.d}→ ${굵게(x.무슨일)}${c[0]}`);
256
+ if (x.왜생기나) console.log(` ${c.d} (${x.왜생기나})${c[0]}`);
257
+ }
258
+
259
+ console.log(`\n${c.y}${c.b}★ 돌리기 전에 커밋하세요${c[0]}`);
260
+ console.log(` ${c.d}"${Wk.원문}"${c[0]}`);
261
+ console.log(` ${c.d}${굵게(Wk.그래서어떻게)}${c[0]}\n`);
262
+ console.log(`${c.d}확인일 ${d.확인일}. 버전·스타 수·값은 밖에서 바뀝니다.${c[0]}\n`);
263
+ }
264
+
265
+ /* ── facts ──────────────────────────────────────────────────────── */
266
+ function 원장보기() {
267
+ const d = 원장이나죽기();
268
+ 머리말();
269
+ const P = d.제품;
270
+ console.log(`${c.b}${P.정식이름}${c[0]} ${c.d}· ${P.만든곳} · ${P.라이선스} · ${P.파이썬패키지}${c[0]}`);
271
+ console.log(`${c.d}${P.무엇}${c[0]}`);
272
+ console.log(`${c.y}아닌 것 — ${P.무엇이아닌가}${c[0]}\n`);
273
+
274
+ const E = d["★실행"];
275
+ console.log(`${c.g}${c.b}첫 실행${c[0]} ${c.cy}${E.첫실행}${c[0]}`);
276
+ for (const x of E["★틀린명령"]) console.log(` ${c.r}✗ ${String(x.명령).padEnd(38)}${c[0]}${c.d}${x.무슨일.replace(/\*\*/g, "").slice(0, 58)}…${c[0]}`);
277
+
278
+ const V = d["★값"];
279
+ console.log(`\n${c.b}값${c[0]} ${c.cy}${V.도구값}${c[0]} ${c.d}+ ${V.따로드는것}${c[0]}`);
280
+ console.log(` ${c.y}기본 깊이 ${V.기본깊이}${c[0]} ${c.d}— ${굵게(V["★기본깊이가_왜_문제인가"])}${c[0]}`);
281
+
282
+ const S = d["★안전"];
283
+ console.log(`\n${c.r}${c.b}겨눠도 되는 곳${c[0]} ${c.d}(${S.출처})${c[0]}`);
284
+ console.log(` ${c.r}${S.핵심}${c[0]} ${c.d}"${S.원문}"${c[0]}`);
285
+ console.log(` ${c.g}허용 — ${S.설명은허용}${c[0]}`);
286
+
287
+ const Wk = d["★작업폴더"];
288
+ console.log(`\n${c.y}${c.b}작업 폴더${c[0]}`);
289
+ console.log(` ${c.y}${굵게(Wk.핵심, c.y)}${c[0]}`);
290
+ console.log(` ${c.d}${굵게(Wk.그래서어떻게)}${c[0]}`);
291
+
292
+ console.log(`\n${c.b}전제${c[0]}`);
293
+ for (const t of d.전제) console.log(` ${c.d}${String(t.이름).padEnd(12)}${c[0]}${c.y}${t.필요}${c[0]} ${c.d}· ${t.확인}${c[0]}`);
294
+
295
+ const K = d["★공식스킬"];
296
+ console.log(`\n${c.b}공식 스킬${c[0]} ${c.cy}${K.개수}개${c[0]} ${c.d}(${K.출처})${c[0]}`);
297
+ for (const s of K.목록) console.log(` ${c.d}${s.이름.padEnd(38)}${s.무엇}${c[0]}`);
298
+
299
+ const I = d.인기;
300
+ console.log(`\n${c.b}인기${c[0]} ${c.cy}스타 ${I.스타.toLocaleString()}${c[0]} ${c.d}(${I.확인일} 기준 · ${I.생성일} 생성 · 포크 ${I.포크.toLocaleString()})${c[0]}`);
301
+ console.log(` ${c.d}${I.주의}${c[0]}`);
302
+
303
+ console.log(`\n${c.b}이 스킬이 하지 않는 말${c[0]}`);
304
+ for (const x of d.안하는말) console.log(` ${c.r}✗${c[0]} ${x.말} ${c.d}— ${x.이유}${c[0]}`);
305
+ console.log(`\n${c.d}확인일 ${d.확인일} · ${d.확인방법}${c[0]}\n`);
306
+ }
307
+
308
+ /* ── doctor — 전제가 지금 준비됐나 (네트워크 안 씀) ──────────────── */
309
+ function 진단() {
310
+ const d = 원장이나죽기();
311
+ 머리말();
312
+ const 잰다 = (명령, 인자) => {
313
+ try {
314
+ return { 됨: true, 값: String(execFileSync(명령, 인자, { stdio: ["ignore", "pipe", "pipe"], timeout: 15000 })).trim().split("\n")[0] };
315
+ } catch (e) { return { 됨: false, 값: String(e.message).split("\n")[0].slice(0, 60) }; }
316
+ };
317
+
318
+ let 막힘 = 0;
319
+ const 줄 = (표, 이름, 말, 도움) => {
320
+ console.log(` ${표} ${이름.padEnd(14)} ${c.d}${말}${c[0]}`);
321
+ if (도움) console.log(` ${c.d}→ ${도움}${c[0]}`);
322
+ };
323
+
324
+ /* 도커 */
325
+ const dk = 잰다("docker", ["info", "--format", "{{.ServerVersion}}"]);
326
+ if (dk.됨) 줄(`${c.g}✓${c[0]}`, "도커", `엔진 ${dk.값} · 떠 있습니다`);
327
+ else { 막힘++; 줄(`${c.r}✗${c[0]}`, "도커", dk.값, "도커 데스크톱을 켜세요. 첫 실행이 샌드박스 이미지를 받습니다."); }
328
+
329
+ /* 파이썬 3.12+ */
330
+ let py = 잰다("python", ["--version"]);
331
+ if (!py.됨) py = 잰다("python3", ["--version"]);
332
+ const m = /(\d+)\.(\d+)/.exec(py.값 || "");
333
+ const 충분 = m && (Number(m[1]) > 3 || (Number(m[1]) === 3 && Number(m[2]) >= 12));
334
+ if (py.됨 && 충분) 줄(`${c.g}✓${c[0]}`, "파이썬", `${py.값} · 3.12 이상`);
335
+ else {
336
+ 막힘++;
337
+ 줄(`${c.r}✗${c[0]}`, "파이썬", py.됨 ? `${py.값} — 3.12 미만입니다` : py.값,
338
+ `${PYPI} 의 requires-python 이 >=3.12 입니다. 3.12 이상을 깔고 그쪽으로 설치하세요.`);
339
+ }
340
+
341
+ /* LLM 키 */
342
+ const 키 = process.env.LLM_API_KEY || process.env.OPENAI_API_KEY || process.env.ANTHROPIC_API_KEY || "";
343
+ if (키) 줄(`${c.g}✓${c[0]}`, "LLM 키", `환경변수에 있습니다 (…${키.slice(-4)})`);
344
+ else {
345
+ 막힘++;
346
+ 줄(`${c.y}!${c[0]}`, "LLM 키", "LLM_API_KEY 가 비어 있습니다",
347
+ "모델은 딸려오지 않습니다. 종량제 키 하나를 export 하세요.");
348
+ }
349
+
350
+ /* strix 가 이미 깔려 있나 */
351
+ const st = 잰다("strix", ["--version"]);
352
+ 줄(st.됨 ? `${c.g}✓${c[0]}` : `${c.d}·${c[0]}`, "strix", st.됨 ? st.값 : "아직 없습니다",
353
+ st.됨 ? "" : `설치: ${d["★실행"].설치}`);
354
+
355
+ console.log(막힘
356
+ ? `\n${c.y}막히는 것이 ${막힘}건 있습니다.${c[0]} ${c.d}위 「→」 를 먼저 처리하세요.${c[0]}\n`
357
+ : `\n${c.g}전제가 다 준비됐습니다.${c[0]} ${c.d}첫 실행: ${d["★실행"].첫실행}${c[0]}\n`);
358
+ process.exit(막힘 ? 1 : 0);
359
+ }
360
+
361
+ /* ── skills — usestrix 공식 스킬 아홉 개를 대신 깐다 ─────────────── */
362
+ async function 공식스킬설치() {
363
+ const d = 원장이나죽기();
364
+ 머리말();
365
+ const K = d["★공식스킬"];
366
+ console.log(`${c.d}${K.누가만들었나}${c[0]}`);
367
+ console.log(`${c.d}공식 통로는 ${c[0]}${c.cy}${K.설치}${c[0]}${c.d} 입니다. 같은 것을 저장소에서 직접 받아 깝니다.${c[0]}\n`);
368
+
369
+ /* ★사본을 패키지에 넣지 않는다 — 넣으면 원본이 바뀌어도 낡은 사본이 계속 깔린다. */
370
+ const 기본 = "https://raw.githubusercontent.com/usestrix/strix/main/skills";
371
+ let 깔림 = 0, 실패 = [];
372
+ for (const 이름 of 공식스킬) {
373
+ try {
374
+ const res = await fetch(`${기본}/${이름}/SKILL.md`, { redirect: "follow", signal: AbortSignal.timeout(30000) });
375
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
376
+ const 본문 = await res.text();
377
+ if (!new RegExp("^---[\\s\\S]*?\\bname:\\s*" + 이름 + "\\b", "m").test(본문))
378
+ throw new Error("머리말의 name 이 다릅니다 — 받은 파일이 그 스킬이 아닙니다");
379
+ const dir = join(스킬뿌리, 이름);
380
+ mkdirSync(dir, { recursive: true });
381
+ writeFileSync(join(dir, "SKILL.md"), 본문, "utf8");
382
+ 깔림++;
383
+ console.log(` ${c.g}✓${c[0]} ${이름}`);
384
+ } catch (e) {
385
+ 실패.push(이름);
386
+ console.log(` ${c.r}✗${c[0]} ${이름} ${c.d}— ${String(e.message).slice(0, 50)}${c[0]}`);
387
+ }
388
+ }
389
+ console.log(`\n${c.d}${깔림}/${공식스킬.length}개 → ${스킬뿌리}${c[0]}`);
390
+ if (실패.length) {
391
+ console.log(`${c.y}못 받은 것: ${실패.join(", ")}${c[0]}`);
392
+ console.log(`${c.d}공식 통로로 다시 시도해 보세요 — ${K.설치}${c[0]}\n`);
393
+ process.exit(1);
394
+ }
395
+ console.log(`${c.y}★ 클로드 코드를 껐다 켜야 스킬이 잡힙니다.${c[0]}`);
396
+ console.log(`${c.d}원저작자 표시(Apache-2.0)는 각 SKILL.md 머리말에 그대로 있습니다.${c[0]}\n`);
397
+ }
398
+
399
+ /* ── verify (네트워크) ──────────────────────────────────────────── */
400
+ async function 확인명령() {
401
+ const d = 원장이나죽기();
402
+ 머리말();
403
+ console.log(`${c.d}주소가 지금 응답하는지 봅니다. 응답한다고 여러분 표적에서 잘 돈다는 뜻은 아닙니다.${c[0]}\n`);
404
+ const 볼곳 = [
405
+ ["저장소", d.제품.저장소],
406
+ ["홈페이지", d.제품.홈페이지],
407
+ ["문서", d.제품.문서],
408
+ ["PyPI", `https://pypi.org/pypi/${PYPI}/json`],
409
+ ];
410
+ let 끊김 = 0;
411
+ for (const [이름, 주소] of 볼곳) {
412
+ let 표, 말;
413
+ try {
414
+ const res = await fetch(주소, { redirect: "follow", signal: AbortSignal.timeout(20000) });
415
+ if (res.ok) { 표 = `${c.g}✓${c[0]}`; 말 = String(res.status); }
416
+ else { 표 = `${c.y}!${c[0]}`; 말 = String(res.status); 끊김++; }
417
+ } catch (e) { 표 = `${c.r}✗${c[0]}`; 말 = String(e.message).slice(0, 40); 끊김++; }
418
+ console.log(` ${표} ${이름.padEnd(12)} ${c.d}${말}${c[0]}`);
419
+ }
420
+ console.log(끊김 ? `\n${c.y}${끊김}건이 응답하지 않습니다 — 원장을 다시 봐야 합니다.${c[0]}\n`
421
+ : `\n${c.g}전부 응답합니다.${c[0]}\n`);
422
+ process.exit(끊김 ? 1 : 0);
423
+ }
424
+
425
+ /* ── --check ────────────────────────────────────────────────────── */
426
+ function 확인만() {
427
+ 머리말();
428
+ const r = 진짜도나();
429
+ if (r.됨) {
430
+ console.log(`${c.g}✓ 설치되어 있고 원장이 규칙을 지킵니다${c[0]} ${c.d}(${대상})${c[0]}`);
431
+ console.log(`${c.d} 공식 스킬 ${r.스킬}개 · 스타 ${r.스타.toLocaleString()} · 확인일 ${r.확인일}${c[0]}\n`);
432
+ } else console.log(`${c.y}· 아직 없습니다${c[0]} ${c.d}— ${r.이유}${c[0]}\n`);
433
+ process.exit(r.됨 ? 0 : 1);
434
+ }
435
+
436
+ /* ── 자기시험 (네트워크 안 씀) ─────────────────────────────────── */
437
+ function 자기시험() {
438
+ const 결과 = [];
439
+ const T = (n, ok) => 결과.push([n, !!ok]);
440
+ const 원 = 원장읽기(원본);
441
+ const 복 = () => JSON.parse(JSON.stringify(원));
442
+ const 스킬본문 = () => readFileSync(join(원본, "SKILL.md"), "utf8");
443
+
444
+ /* ── 통과해야 하는 쪽 ─────────────────────────────────────── */
445
+ T("패키지 안에 skill/ 이 있다", existsSync(원본));
446
+ T("원장을 읽는다", !!원);
447
+ T("지금 원장은 통과한다", 원장검사(원).됨);
448
+ T("SKILL.md 머리말의 name 이 맞다",
449
+ new RegExp("^---[\\s\\S]*?\\bname:\\s*" + 스킬이름 + "\\b", "m").test(스킬본문()));
450
+ T("SKILL.md 가 스킬팩을 가리키지 클로드킷을 가리키지 않는다",
451
+ !/클로드킷|자비스\s*풀스택/.test(스킬본문()));
452
+ T("tail 문안이 스킬팩 한 칸만 말한다",
453
+ !/클로드킷|자비스\s*풀스택|일감\s*진단/.test(JSON.stringify(TAIL) + 마지막));
454
+ T("공식 스킬 이름 아홉이 원장과 코드에서 같다",
455
+ 원["★공식스킬"].목록.every((s) => 공식스킬.includes(s.이름)) && 원["★공식스킬"].개수 === 공식스킬.length);
456
+
457
+ /* ── ★잡아야 하는 쪽 — 전건 통과하는 검사기는 검사기가 아니다 ── */
458
+ T("★★값 칸을 지우면 잡는다", (() => { const d = 복(); delete d["★값"]; return !원장검사(d).됨; })());
459
+ T("★★기본 깊이를 quick 으로 속이면 잡는다", (() => {
460
+ const d = 복(); d["★값"].기본깊이 = "quick"; return !원장검사(d).됨;
461
+ })());
462
+ T("★★--max-budget 처방을 빼면 잡는다", (() => {
463
+ const d = 복(); d["★값"].그래서어떻게 = "적당히 짧게 돌리세요"; return !원장검사(d).됨;
464
+ })());
465
+ T("★★첫 실행에서 브레이크를 떼면 잡는다", (() => {
466
+ const d = 복(); d["★실행"].첫실행 = "strix --target ./app-directory"; return !원장검사(d).됨;
467
+ })());
468
+ T("★★안전 칸을 지우면 잡는다", (() => { const d = 복(); delete d["★안전"]; return !원장검사(d).됨; })());
469
+ T("★★안전 원문(Authorized use only)을 지우면 잡는다", (() => {
470
+ const d = 복(); d["★안전"].원문 = "허락받고 쓰세요"; return !원장검사(d).됨;
471
+ })());
472
+ T("★★안전에서 「허용되는 것」을 지우면 잡는다", (() => {
473
+ const d = 복(); delete d["★안전"].설명은허용; return !원장검사(d).됨;
474
+ })());
475
+ T("★★작업폴더 원문(writable)을 지우면 잡는다", (() => {
476
+ const d = 복(); d["★작업폴더"].원문 = "폴더가 마운트됩니다"; return !원장검사(d).됨;
477
+ })());
478
+ T("★★「먼저 커밋하라」를 지우면 잡는다", (() => {
479
+ const d = 복(); d["★작업폴더"].그래서어떻게 = "조심해서 쓰세요"; return !원장검사(d).됨;
480
+ })());
481
+ T("★★pip install strix 경고를 빼면 잡는다", (() => {
482
+ const d = 복(); d["★실행"]["★틀린명령"] = d["★실행"]["★틀린명령"].filter((x) => !/pip install/.test(x.명령));
483
+ while (d["★실행"]["★틀린명령"].length < 3) d["★실행"]["★틀린명령"].push({ 명령: "x", 무슨일: "y" });
484
+ return !원장검사(d).됨;
485
+ })());
486
+ T("★★남의 주소 경고를 빼면 잡는다", (() => {
487
+ const d = 복(); d["★실행"]["★틀린명령"] = d["★실행"]["★틀린명령"].filter((x) => !/https?:\/\//.test(x.명령));
488
+ while (d["★실행"]["★틀린명령"].length < 3) d["★실행"]["★틀린명령"].push({ 명령: "x", 무슨일: "y" });
489
+ return !원장검사(d).됨;
490
+ })());
491
+ T("★★파이썬 3.12 전제를 지우면 잡는다", (() => {
492
+ const d = 복(); d.전제 = d.전제.map((t) => (/파이썬/.test(t.이름) ? { ...t, 필요: "아무 버전" } : t));
493
+ return !원장검사(d).됨;
494
+ })());
495
+ T("★★공식 스킬 수를 부풀리면 잡는다", (() => {
496
+ const d = 복(); d["★공식스킬"].개수 = 12; return !원장검사(d).됨;
497
+ })());
498
+ T("★★공식 스킬 이름을 지어내면 잡는다", (() => {
499
+ const d = 복(); d["★공식스킬"].목록[0].이름 = "super-hacking-skill"; return !원장검사(d).됨;
500
+ })());
501
+ T("★★「우리가 만든 것이 아니다」를 지우면 잡는다", (() => {
502
+ const d = 복(); d["★공식스킬"].누가만들었나 = "아홉 개를 깔아 드립니다"; return !원장검사(d).됨;
503
+ })());
504
+ T("★★만든곳을 우리로 바꾸면 잡는다", (() => {
505
+ const d = 복(); d.제품.만든곳 = "REBORN LABS"; return !원장검사(d).됨;
506
+ })());
507
+ T("★★설명에 「완전 무료」를 넣으면 잡는다", (() => {
508
+ const d = 복(); d.제품.무엇 = "완전 무료로 쓰는 AI 침투테스트"; return !원장검사(d).됨;
509
+ })());
510
+ T("★★설명에 「보안 끝」을 넣으면 잡는다", (() => {
511
+ const d = 복(); d["★안전"].그래서어떻게 = "한 번 돌리면 보안 끝입니다"; return !원장검사(d).됨;
512
+ })());
513
+ T("★★스타 수의 확인일을 지우면 잡는다", (() => { const d = 복(); delete d.인기.확인일; return !원장검사(d).됨; })());
514
+ T("★★스타 수의 「주의」를 지우면 잡는다", (() => { const d = 복(); delete d.인기.주의; return !원장검사(d).됨; })());
515
+ T("★★「안하는말」 을 비우면 잡는다", (() => { const d = 복(); d.안하는말 = []; return !원장검사(d).됨; })());
516
+ T("★★「안하는말」 에서 권한 항목을 빼면 잡는다", (() => {
517
+ const d = 복(); d.안하는말 = d.안하는말.filter((x) => !/아무 사이트|권한/.test(x.말 + x.이유));
518
+ while (d.안하는말.length < 3) d.안하는말.push({ 말: "채움", 이유: "채움" });
519
+ return !원장검사(d).됨;
520
+ })());
521
+ T("★★확인일을 지우면 잡는다", (() => { const d = 복(); delete d.확인일; return !원장검사(d).됨; })());
522
+ T("★없는 원장은 통과하지 않는다", !원장검사(null).됨);
523
+
524
+ for (const [n, ok] of 결과) console.log(` ${ok ? c.g + "✓" : c.r + "✗"}${c[0]} ${n}`);
525
+ const 실패 = 결과.filter(([, ok]) => !ok).length;
526
+ console.log(실패 ? `\n${c.r}✗ 자기시험 ${실패}건 실패${c[0]}` : `\n${c.g}✓ 자기시험 ${결과.length}건 통과${c[0]}`);
527
+ return 실패 ? 1 : 0;
528
+ }
529
+
530
+ /* ── 설치 ───────────────────────────────────────────────────────── */
531
+ function 설치() {
532
+ 머리말();
533
+ if (!existsSync(원본)) {
534
+ console.error(`${c.r}✗ 패키지 안에 skill/ 폴더가 없습니다${c[0]} — 배포가 잘못된 것입니다.`);
535
+ process.exit(2);
536
+ }
537
+ let 쓴수 = 0;
538
+ for (const src of 파일목록(원본)) {
539
+ const 상대 = relative(원본, src);
540
+ const dst = join(대상, 상대);
541
+ mkdirSync(dirname(dst), { recursive: true });
542
+ writeFileSync(dst, readFileSync(src));
543
+ 쓴수++;
544
+ console.log(` ${c.d}·${c[0]} ${상대}`);
545
+ }
546
+ console.log(`\n${c.d}${쓴수}개 파일 → ${대상}${c[0]}`);
547
+ console.log(`${c.d}원장이 조건을 숨기지 않았는지 다시 확인하는 중…${c[0]}`);
548
+
549
+ const r = 진짜도나();
550
+ if (!r.됨) {
551
+ console.error(`\n${c.r}✗ 깔았는데 확인에서 걸렸습니다${c[0]} — ${r.이유}\n`);
552
+ process.exit(1);
553
+ }
554
+
555
+ const d = 원장읽기(대상);
556
+ const E = d["★실행"], V = d["★값"], S = d["★안전"], Wk = d["★작업폴더"];
557
+ console.log(`\n${c.g}${c.b}✓ 설치 완료${c[0]} ${c.d}— 공식 스킬 ${r.스킬}개 안내 · 스타 ${r.스타.toLocaleString()} (확인일 ${r.확인일})${c[0]}`);
558
+
559
+ console.log(`\n${c.g}${c.b}★ 첫 실행은 이 순서입니다${c[0]}`);
560
+ console.log(` ${c.cy}${E.설치}${c[0]}`);
561
+ console.log(` ${c.cy}${E.설정1}${c[0]}`);
562
+ console.log(` ${c.cy}${E.설정2}${c[0]}`);
563
+ console.log(` ${c.cy}${c.b}${E.첫실행}${c[0]}`);
564
+
565
+ console.log(`\n${c.y}${c.b}★ 「완전 무료」는 도구 값 이야기입니다${c[0]}`);
566
+ console.log(` ${c.d}${V.도구값} · ${V.따로드는것}${c[0]}`);
567
+ console.log(` ${c.d}${굵게(V["★기본깊이가_왜_문제인가"])}${c[0]}`);
568
+
569
+ console.log(`\n${c.y}${c.b}★ 돌리기 전에 커밋하세요${c[0]}`);
570
+ console.log(` ${c.d}${굵게(Wk.핵심)}${c[0]}`);
571
+
572
+ console.log(`\n${c.r}${c.b}★ 겨눠도 되는 곳${c[0]}`);
573
+ console.log(` ${c.r}${S.핵심}${c[0]} ${c.d}("${S.원문}")${c[0]}`);
574
+
575
+ console.log(`\n${c.y}★ 클로드 코드를 껐다 켜야 스킬이 잡힙니다.${c[0]}`);
576
+ console.log(`\n${c.b}쓰는 법${c[0]}`);
577
+ console.log(` 클로드에게 그냥 말하세요 — ${c.cy}"strix 돌리는 법 알려줘"${c[0]}`);
578
+ console.log(` 전제가 준비됐나: ${c.cy}npx --yes reborn-strix doctor${c[0]}`);
579
+ console.log(` 공식 스킬 아홉 개도: ${c.cy}npx --yes reborn-strix skills${c[0]}`);
580
+ console.log(` 원장 전체: ${c.cy}npx --yes reborn-strix facts${c[0]}`);
581
+ console.log(` ${c.d}스타 수·값·버전은 밖에서 바뀝니다. 확인일을 같이 적어 둔 이유입니다.${c[0]}\n`);
582
+ console.log(`${c.d}설명 · https://rebornlabs.kr/strix?utm_source=installer&utm_medium=cli&utm_campaign=${퍼널}${c[0]}`);
583
+
584
+ /* ★끝난 뒤 딱 한 칸. 못 깔았으면 여기까지 오지 않는다(위에서 exit 1).
585
+ ★가리키는 곳은 스킬팩(8,900원)이다. 클로드킷은 여기서 말하지 않는다 — 계단이 무너진다. */
586
+ const t = TAIL[퍼널];
587
+ console.log("");
588
+ for (const 줄 of t.이음) console.log(`${c.d}${줄.replace(/\*\*(.+?)\*\*/g, `${c[0]}${c.b}$1${c[0]}${c.d}`)}${c[0]}`);
589
+ console.log(`\n${c.b}${t.제목}${c[0]}${c.d} — ${t.한줄}${c[0]}`);
590
+ console.log(`${c.cy}${링크(퍼널)}${c[0]}`);
591
+ console.log(`${c.d}${마지막}${c[0]}\n`);
592
+ }
593
+
594
+ /* ══ 실행 ═══════════════════════════════════════════════════════ */
595
+ const 인자 = process.argv.slice(2);
596
+ const 첫 = 인자[0];
597
+ if (인자.includes("--self-test")) process.exit(자기시험());
598
+ else if (첫 === "run" || 첫 === "실행") 실행보기();
599
+ else if (첫 === "facts" || 첫 === "원장") 원장보기();
600
+ else if (첫 === "doctor" || 첫 === "진단") 진단();
601
+ else if (첫 === "skills" || 첫 === "스킬") await 공식스킬설치();
602
+ else if (첫 === "verify" || 첫 === "확인") await 확인명령();
603
+ else if (인자.includes("--check")) 확인만();
604
+ else 설치();
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "reborn-strix",
3
+ "version": "1.0.0",
4
+ "description": "Strix(오픈소스 AI 침투테스트)를 돌리기 전에 무엇이 막고 무엇이 값을 부르는지 알려 주는 클로드 코드 스킬. 도커·파이썬 3.12·LLM 키라는 전제, 기본 scan-mode 가 deep 이라 첫 실행이 가장 비싸다는 것, 로컬 폴더가 쓰기 가능하게 마운트된다는 것, 그리고 허락받은 표적에만 겨눈다는 경계를 먼저 말한다. usestrix 공식 스킬 아홉 개 설치도 대신한다.",
5
+ "keywords": [
6
+ "claude",
7
+ "claude-code",
8
+ "agent-skills",
9
+ "skill",
10
+ "strix",
11
+ "pentest",
12
+ "penetration-testing",
13
+ "ai-security",
14
+ "appsec",
15
+ "owasp",
16
+ "vulnerability-scanner",
17
+ "security"
18
+ ],
19
+ "homepage": "https://rebornlabs.kr/strix?utm_source=installer&utm_medium=cli&utm_campaign=strix",
20
+ "bugs": {
21
+ "url": "https://mobility.rebornlabs.kr/cs"
22
+ },
23
+ "license": "MIT",
24
+ "author": "REBORN LABS",
25
+ "type": "module",
26
+ "bin": {
27
+ "reborn-strix": "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,122 @@
1
+ ---
2
+ name: strix-guide
3
+ description: >
4
+ Strix(오픈소스 AI 침투테스트)를 **돌리기 전에** 무엇이 막고 무엇이 값을 부르는지 알려 준다.
5
+ 도커·파이썬 3.12·LLM 키라는 전제, 기본 scan-mode 가 deep 이라 첫 실행이 가장 비싸다는 것,
6
+ 로컬 폴더가 쓰기 가능하게 마운트돼 진짜 파일이 고쳐진다는 것, 그리고 「내 것에만 겨눈다」는
7
+ 경계를 먼저 말한다. "strix", "스트릭스", "AI 침투테스트", "AI 펜테스트", "취약점 자동 진단",
8
+ "내 앱 보안 점검", "오픈소스 펜테스트 도구", "strix 설치가 안 된다", "strix 값이 얼마 나오나",
9
+ "npx skills add usestrix" 같은 이야기가 나올 때 쓴다.
10
+ ---
11
+
12
+ # Strix — 돌리기 전에 볼 것 네 가지
13
+
14
+ > 이 파일은 **리본랩스(REBORN LABS)** 의 무료 설치기가 넣은 것입니다.
15
+ > 필요 없으시면 이 폴더를 지우세요.
16
+ >
17
+ > **Strix 자체는 [usestrix](https://github.com/usestrix/strix) 의 오픈소스입니다(Apache-2.0).
18
+ > 저희가 만든 도구가 아니고, 저희와 제휴 관계도 없습니다.** 저희는 한국어 안내와 설치를 대신합니다.
19
+
20
+ ## 방금 깐 것
21
+
22
+ `strix-guide` — **사실 원장**(`strix.json`)입니다. 실행 명령·전제·값·경계를 **확인일과 함께** 적어 두었습니다.
23
+
24
+ ```
25
+ npx --yes reborn-strix run 맞는 첫 실행 한 줄과 틀린 명령을 나란히 본다
26
+ npx --yes reborn-strix facts 원장 전체를 터미널에 찍는다
27
+ npx --yes reborn-strix doctor 도커·파이썬·키가 지금 준비됐는지 잰다
28
+ npx --yes reborn-strix skills usestrix 공식 스킬 아홉 개를 깐다(설치 대행)
29
+ npx --yes reborn-strix verify 저장소·문서·PyPI 가 지금 응답하는지 (네트워크)
30
+ ```
31
+
32
+ 클로드에게는 그냥 말하면 됩니다 — **"strix 돌리는 법 알려줘"**
33
+
34
+ ## ① ★「완전 무료」는 **도구 값** 이야기입니다
35
+
36
+ Strix 는 Apache-2.0 이라 도구 값은 0원이 맞습니다. 그런데 **모델은 딸려오지 않습니다.**
37
+ 에이전트가 스스로 판단하며 모델을 여러 번 부르므로 **LLM API 토큰 값이 따로** 듭니다.
38
+
39
+ 여기에 함정이 하나 더 있습니다 — **`--scan-mode` 의 기본값이 `deep`** 입니다.
40
+
41
+ | 깊이 | 걸리는 시간(공식 문서 기준) |
42
+ |---|---|
43
+ | `quick` | 몇 분 |
44
+ | `standard` | 30분~1시간 |
45
+ | **`deep` (기본값)** | 그보다 김 |
46
+
47
+ 즉 빠른시작을 그대로 치면 **첫 실행이 가장 깊고 가장 비싼 실행**이 됩니다.
48
+
49
+ ```bash
50
+ # 첫 실행은 이렇게 겁니다
51
+ strix --target ./app-directory --scan-mode quick --max-budget 2
52
+ ```
53
+
54
+ `--max-budget` 은 스캔 전체가 쓸 LLM 값의 상한(USD)입니다. 루트 에이전트와 자식 에이전트를
55
+ 합쳐 누적으로 재고, 모델 응답마다 확인해서 넘으면 멈춥니다.
56
+
57
+ ## ② ★로컬 폴더는 **쓰기 가능하게** 마운트됩니다
58
+
59
+ 공식 문서 원문입니다:
60
+
61
+ > A local directory is mounted into the sandbox live and **writable**,
62
+ > so the agent edits your real files (`.git` excepted). **Commit or stash first.**
63
+
64
+ 「폴더만 가리키면 된다」는 말은 맞지만, 그 폴더 안의 **진짜 파일이 고쳐집니다.**
65
+ 돌리기 전에 커밋하거나 스태시하세요.
66
+
67
+ ## ③ ★전제 셋 — 없으면 첫 줄에서 멈춥니다
68
+
69
+ | 전제 | 필요한 것 | 확인 |
70
+ |---|---|---|
71
+ | 도커 | **떠 있어야** 함 (첫 실행이 샌드박스 이미지를 받습니다) | `docker info` |
72
+ | 파이썬 | **3.12 이상** (`strix-agent` 의 requires-python) | `python --version` |
73
+ | LLM API 키 | 종량제 키 하나 (OpenAI·Anthropic·Google·OpenRouter 등) | `echo $LLM_API_KEY` |
74
+
75
+ 한 번에 재려면: `npx --yes reborn-strix doctor`
76
+
77
+ ★PyPI 패키지 이름은 `strix` 가 아니라 **`strix-agent`** 입니다.
78
+
79
+ ## ④ ★★내가 소유했거나 허락받은 표적에만
80
+
81
+ 저장소가 **Authorized use only** 를 명시합니다.
82
+
83
+ - ✅ 내 서비스 · 내 저장소 · **계약으로 진단을 맡은** 고객 자산 · 스스로 세운 연습용 표적
84
+ - ✅ 버그바운티는 그 프로그램의 **범위(scope) 문서가 허락한** 자산에만
85
+ - ❌ 그 밖의 주소. **넣을 수 있다는 것과 넣어도 된다는 것은 다릅니다.**
86
+
87
+ 표적을 적기 전에 「이건 누구 것인가」를 먼저 답하세요.
88
+ 답이 「내 것」이나 「문서로 허락받았다」가 아니면 거기서 멈춥니다.
89
+
90
+ ## 공식 스킬 아홉 개
91
+
92
+ usestrix 가 저장소에 직접 넣어 둔 스킬입니다. 클로드 코드가 Strix 를 **직접 돌리게** 됩니다.
93
+
94
+ ```
95
+ npx skills add usestrix/strix 공식 통로
96
+ npx --yes reborn-strix skills 같은 것을 대신 깔아 드립니다(한국어 안내 포함)
97
+ ```
98
+
99
+ | 스킬 | 언제 |
100
+ |---|---|
101
+ | `penetration-testing-with-strix` | 웹앱·API·저장소·URL·도메인·IP — 기본 침투테스트 |
102
+ | `web-app-penetration-testing` | 살아 있는 웹앱을 끝까지 블랙박스로 |
103
+ | `api-security-testing` | REST·GraphQL·gRPC — OWASP API Top 10 |
104
+ | `owasp-top-10-testing` | OWASP Top 10:2025 항목별 실증 |
105
+ | `find-security-vulnerabilities-in-code` | 소스를 읽는 화이트박스 리뷰 |
106
+ | `fix-security-vulnerabilities-with-strix` | 찾은 것을 고치고 다시 돌려 막혔는지 증명 |
107
+ | `ci-security-scanning-with-strix` | PR 마다 변경분만 스캔해 머지 전에 차단 |
108
+ | `application-security-testing` | 제품 전체에서 무엇을 어떻게 검사할지 고르기 |
109
+ | `managed-pentesting-with-strix` | 도커·키 없이 app.strix.ai 에서 |
110
+
111
+ ## 이 스킬이 하지 않는 말
112
+
113
+ - ❌ 「완전 무료」라고만 하기 — 도구는 0원이지만 **LLM 토큰 값은 따로** 듭니다
114
+ - ❌ 아무 사이트나 넣어 보라고 하기 — **권한 없는 스캔은 대부분의 나라에서 위법**입니다
115
+ - ❌ 「스캔 한 번이면 보안 끝」 — 실증형이라 **뚫은 것만** 올립니다. 안 나온 것이 없다는 뜻이 아닙니다
116
+ - ❌ 「우리가 만든 도구」 — usestrix 의 오픈소스입니다
117
+ - ❌ 옵션 없이 첫 실행 권하기 — 기본이 `deep` 입니다
118
+
119
+ ---
120
+
121
+ 원장: `strix.json` (확인일 2026-09-09 · GitHub API + README + docs.strix.ai 원문)
122
+ 원저작자: [usestrix/strix](https://github.com/usestrix/strix) · Apache-2.0
@@ -0,0 +1,109 @@
1
+ {
2
+ "확인일": "2026-09-09",
3
+ "확인방법": "github.com/usestrix/strix 의 README.md 원문 · docs.strix.ai/quickstart · docs.strix.ai/usage/cli · docs.strix.ai/usage/scan-modes · GitHub API(api.github.com/repos/usestrix/strix) 를 직접 읽어 옮겼다. 별 개수는 그날 API 응답값이다.",
4
+
5
+ "제품": {
6
+ "정식이름": "Strix",
7
+ "약칭": "strix",
8
+ "만든곳": "usestrix (hi@usestrix.com)",
9
+ "라이선스": "Apache-2.0",
10
+ "저장소": "https://github.com/usestrix/strix",
11
+ "홈페이지": "https://strix.ai",
12
+ "문서": "https://docs.strix.ai",
13
+ "파이썬패키지": "strix-agent",
14
+ "무엇": "표적을 실제로 돌려 보고, 뚫은 것만 재현 증거(PoC)와 함께 보고하는 오픈소스 AI 침투테스트 에이전트입니다.",
15
+ "무엇이아닌가": "정적 분석기가 아닙니다. 코드를 읽고 「의심된다」고 표시하는 게 아니라, 샌드박스에서 실제로 돌려 보고 뚫린 것만 올립니다. 대신 표적이 살아 있어야 하고 돌리는 데 시간과 토큰 값이 듭니다."
16
+ },
17
+
18
+ "★실행": {
19
+ "설치": "curl -sSL https://strix.ai/install | bash",
20
+ "설정1": "export STRIX_LLM=\"openrouter/z-ai/glm-5.3\"",
21
+ "설정2": "export LLM_API_KEY=\"your-api-key\"",
22
+ "첫실행": "strix --target ./app-directory --scan-mode quick --max-budget 2",
23
+ "무엇이뜨나": "TUI 가 뜨고 에이전트들이 표적을 훑습니다. 결과는 strix_runs/<run-name> 에 남고 strix view 로 로컬 대시보드에서 봅니다.",
24
+ "필요한것": "도커가 떠 있어야 하고, LLM API 키가 따로 있어야 합니다(파이썬 3.12 이상).",
25
+ "결과폴더": "strix_runs/<run-name>",
26
+ "★첫실행에_붙인_두_옵션": "공식 빠른시작에는 --scan-mode 와 --max-budget 이 없습니다. 우리가 붙였습니다 — 왜 붙였는지는 ★값 칸에 있습니다.",
27
+ "★틀린명령": [
28
+ {
29
+ "명령": "pip install strix",
30
+ "무슨일": "PyPI 의 패키지 이름은 strix 가 아니라 **strix-agent** 입니다. 이름이 달라 엉뚱한 것이 깔리거나 안 깔립니다.",
31
+ "왜생기나": "명령어와 저장소 이름이 둘 다 strix 라서 패키지 이름도 그럴 거라고 짐작하게 됩니다."
32
+ },
33
+ {
34
+ "명령": "strix --target https://남의사이트.com",
35
+ "무슨일": "**대부분의 나라에서 위법입니다.** 저장소가 「Authorized use only」 를 명시합니다. 소유했거나 문서로 허락받은 표적에만 겨눕니다.",
36
+ "왜생기나": "URL 을 받는 도구라 아무 주소나 넣어 볼 수 있게 보입니다. 넣을 수 있다는 것과 넣어도 된다는 것은 다릅니다."
37
+ },
38
+ {
39
+ "명령": "strix --target ./my-app",
40
+ "무슨일": "**옵션 없이 돌리면 scan-mode 가 deep 입니다.** 몇 시간이 걸리고 토큰 값이 얼마 나올지 모릅니다. 게다가 로컬 폴더는 샌드박스에 **쓰기 가능하게** 마운트되어 에이전트가 진짜 파일을 고칩니다.",
41
+ "왜생기나": "빠른시작에 적힌 그대로라서 그대로 칩니다. 빠른시작은 맞지만 첫 실행에는 브레이크가 없습니다."
42
+ }
43
+ ]
44
+ },
45
+
46
+ "★값": {
47
+ "도구값": "0원 (Apache-2.0 오픈소스)",
48
+ "따로드는것": "LLM API 토큰 값. 에이전트가 스스로 판단하며 모델을 여러 번 부르므로, 얼마 나올지는 표적과 깊이에 달렸습니다.",
49
+ "기본깊이": "deep",
50
+ "★기본깊이가_왜_문제인가": "--scan-mode 의 기본값이 deep 입니다. 공식 문서 기준 quick 은 몇 분, standard 는 30분~1시간, deep 은 그보다 깁니다. 「완전 무료」만 듣고 그대로 치면 첫 실행이 가장 깊고 가장 비싼 실행이 됩니다.",
51
+ "그래서어떻게": "첫 실행은 `--scan-mode quick --max-budget 2` 로 겁니다. --max-budget 은 스캔 전체가 쓸 LLM 값의 상한(USD)이고, 모델 응답마다 누적으로 재서 넘으면 멈춥니다.",
52
+ "출처": "docs.strix.ai/usage/cli · docs.strix.ai/usage/scan-modes"
53
+ },
54
+
55
+ "★안전": {
56
+ "핵심": "내가 소유했거나 문서로 허락받은 표적에만 씁니다.",
57
+ "원문": "Authorized use only.",
58
+ "출처": "github.com/usestrix/strix README · LICENSE",
59
+ "설명은허용": "우리 서비스·우리 저장소·우리가 계약으로 진단을 맡은 고객 자산, 그리고 스스로 세운 연습용 표적에는 마음껏 씁니다. 버그바운티는 그 프로그램의 범위(scope) 문서가 허락한 자산에만.",
60
+ "그래서어떻게": "표적을 적기 전에 「이건 누구 것인가」를 먼저 답하세요. 답이 「내 것」이나 「문서로 허락받았다」가 아니면 거기서 멈춥니다."
61
+ },
62
+
63
+ "★작업폴더": {
64
+ "핵심": "로컬 폴더를 표적으로 주면 샌드박스에 **쓰기 가능하게** 마운트됩니다 — 에이전트가 여러분의 진짜 파일을 고칩니다(.git 은 제외).",
65
+ "원문": "A local directory is mounted into the sandbox live and writable, so the agent edits your real files (.git excepted). Commit or stash first.",
66
+ "그래서어떻게": "돌리기 전에 커밋하거나 스태시하세요. 원본 릴스는 「폴더만 가리키면 된다」고만 하고 이 줄을 말하지 않습니다.",
67
+ "출처": "docs.strix.ai/usage/cli — --target 설명의 Note"
68
+ },
69
+
70
+ "전제": [
71
+ { "이름": "도커", "필요": "떠 있어야 함", "왜": "첫 실행이 샌드박스 이미지를 받습니다. 에이전트는 그 컨테이너 안에서 돕니다.", "확인": "docker info" },
72
+ { "이름": "파이썬", "필요": "3.12 이상", "왜": "strix-agent 의 requires-python 이 >=3.12 입니다. 3.11 에는 안 깔립니다.", "확인": "python --version" },
73
+ { "이름": "LLM API 키", "필요": "종량제 키 하나", "왜": "모델은 딸려오지 않습니다. OpenAI·Anthropic·Google·OpenRouter 등 지원 제공자 중 하나를 고릅니다.", "확인": "echo $LLM_API_KEY" }
74
+ ],
75
+
76
+ "★공식스킬": {
77
+ "개수": 9,
78
+ "누가만들었나": "usestrix — **우리가 만든 것이 아닙니다.** Apache-2.0 이고 저작자 표시는 각 SKILL.md 머리말에 그대로 남습니다.",
79
+ "설치": "npx skills add usestrix/strix",
80
+ "출처": "github.com/usestrix/strix/tree/main/skills · README 「Use Strix from Your Coding Agent」",
81
+ "목록": [
82
+ { "이름": "penetration-testing-with-strix", "무엇": "웹앱·API·저장소·URL·도메인·IP 를 대상으로 하는 기본 침투테스트" },
83
+ { "이름": "web-app-penetration-testing", "무엇": "살아 있는 웹앱을 끝까지 블랙박스로" },
84
+ { "이름": "api-security-testing", "무엇": "REST·GraphQL·gRPC — OWASP API Top 10 (2023)" },
85
+ { "이름": "owasp-top-10-testing", "무엇": "OWASP Top 10:2025 항목별 실증" },
86
+ { "이름": "find-security-vulnerabilities-in-code", "무엇": "소스를 읽는 화이트박스 리뷰" },
87
+ { "이름": "fix-security-vulnerabilities-with-strix", "무엇": "찾은 것을 고치고 다시 돌려 막혔는지 증명" },
88
+ { "이름": "ci-security-scanning-with-strix", "무엇": "PR 마다 변경분만 스캔해 머지 전에 막기" },
89
+ { "이름": "application-security-testing", "무엇": "제품 전체에서 무엇을 어떤 방식으로 검사할지 고르기" },
90
+ { "이름": "managed-pentesting-with-strix", "무엇": "도커·키 없이 app.strix.ai 에서 돌리기" }
91
+ ]
92
+ },
93
+
94
+ "인기": {
95
+ "스타": 61340,
96
+ "포크": 6710,
97
+ "확인일": "2026-09-09",
98
+ "생성일": "2025-08-05",
99
+ "주의": "스타는 관심의 크기이지 여러분 표적에서의 정확도가 아닙니다. 첫 판단은 quick 스캔 한 번으로 직접 하세요."
100
+ },
101
+
102
+ "안하는말": [
103
+ { "말": "「완전 무료」 라고만 하기", "이유": "도구는 Apache-2.0 이라 0원이 맞지만 LLM 토큰 값은 따로 듭니다. 조건을 빼고 말하면 첫 청구서에서 신뢰가 끝납니다." },
104
+ { "말": "아무 사이트나 넣어 보라고 하기", "이유": "저장소가 Authorized use only 를 명시합니다. 권한 없는 표적 스캔은 대부분의 나라에서 위법입니다." },
105
+ { "말": "「스캔 한 번이면 보안 끝」", "이유": "실증형이라 뚫은 것만 올립니다. 안 나온 것이 없다는 뜻이 아닙니다." },
106
+ { "말": "「우리가 만든 도구」", "이유": "usestrix 의 오픈소스입니다. 우리는 한국어 안내와 설치를 대신할 뿐입니다." },
107
+ { "말": "옵션 없이 첫 실행을 권하기", "이유": "기본 scan-mode 가 deep 입니다. 상한 없이 돌리면 값이 얼마 나올지 모릅니다." }
108
+ ]
109
+ }
package/tail.mjs ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * 「끝난 뒤 한 칸」 문안 — **정본은 `skillpack/tail_copy.mjs` 다.**
3
+ *
4
+ * 여기 사본이 있는 이유는 하나뿐이다: npm 패키지는 폴더 밖을 import 할 수 없다.
5
+ * 문구를 고칠 일이 생기면 **정본을 먼저 고치고** 이 파일에 옮긴다. 반대로 하지 마라.
6
+ *
7
+ * 규율(정본에서 그대로 온다):
8
+ * ① 끝난 뒤 딱 한 칸 · ② 못 깔았으면 안 띄운다 · ③ 파일을 남기지 않는다
9
+ * ④ 고지 블록 금지 — 대신 마지막 줄에서 목록과 직접 까는 법을 알려 준다
10
+ * ⑤ 첫 줄은 **방금 한 일에서 이어져야** 한다.
11
+ *
12
+ * ★가리키는 곳은 **스킬팩(8,900원)** 하나다. 클로드킷·자비스 풀스택은 여기서 말하지 않는다 —
13
+ * 0원 다음 칸이 곧바로 158,000원이면 둘 다 안 고른다(2026-08-19~24 실측).
14
+ */
15
+ export const 랜딩 = "https://rebornlabs.kr/skillpack";
16
+
17
+ export const 링크 = (퍼널) =>
18
+ `${랜딩}?utm_source=installer&utm_medium=cli&utm_campaign=${퍼널}`;
19
+
20
+ export const TAIL = {
21
+ strix: {
22
+ 이음: [
23
+ "방금 드린 건 **돌리기 전에 볼 눈**입니다 — 전제 셋, 값이 어디서 붙는지, 그리고 어디까지 겨눠도 되는지.",
24
+ "이런 걸 도구마다 매번 확인하다 보면 다음 질문은 대개 **\"그래서 이 판단을 매번 누가 기억하지\"** 입니다.",
25
+ ],
26
+ 제목: "리본랩스 스킬팩",
27
+ 한줄: "같은 방식으로 골라 담은 48종을 한 줄로. 8,900원",
28
+ },
29
+ };
30
+
31
+ export const 마지막 = "목록과 직접 까는 명령까지 그 페이지에 그대로 적어 두었습니다. 안 사셔도 됩니다.";
@@ -0,0 +1,46 @@
1
+ ---
2
+ name: reborn-strix-guide
3
+ description: >
4
+ Strix(오픈소스 AI 침투테스트)를 돌리기 전에 확인할 것 — 도커·파이썬 3.12·LLM 키라는 전제,
5
+ 기본 scan-mode 가 deep 이라 첫 실행이 가장 비싸다는 것, 로컬 폴더가 쓰기 가능하게 마운트돼
6
+ 진짜 파일이 고쳐진다는 것, 허락받은 표적에만 겨눈다는 경계. "strix", "스트릭스",
7
+ "AI 침투테스트", "AI 펜테스트", "내 앱 취약점 점검", "오픈소스 펜테스트" 이야기가 나올 때 쓴다.
8
+ ---
9
+
10
+ # Strix — 설치기가 남긴 안내
11
+
12
+ > **리본랩스(REBORN LABS)** 의 무료 설치기(`npx --yes reborn-strix`)가 넣은 파일입니다.
13
+ > 필요 없으시면 이 폴더를 지우세요. 지워도 `strix-guide` 스킬은 그대로 동작합니다.
14
+ >
15
+ > **Strix 는 [usestrix](https://github.com/usestrix/strix) 의 오픈소스입니다(Apache-2.0).
16
+ > 저희가 만든 도구가 아니고, 저희와 제휴 관계도 없습니다.**
17
+
18
+ ## 깔린 것
19
+
20
+ - `strix-guide` — 사실 원장(`strix.json`)을 가진 안내 스킬
21
+ - (`npx --yes reborn-strix skills` 를 돌리셨다면) usestrix 공식 스킬 **아홉 개**
22
+
23
+ ## 다시 볼 명령
24
+
25
+ ```
26
+ npx --yes reborn-strix run 첫 실행 한 줄 + 틀린 명령 셋
27
+ npx --yes reborn-strix doctor 도커·파이썬·LLM 키가 지금 준비됐나
28
+ npx --yes reborn-strix facts 원장 전체
29
+ npx --yes reborn-strix skills usestrix 공식 스킬 아홉 개 (설치 대행)
30
+ npx --yes reborn-strix verify 저장소·문서·PyPI 가 지금 응답하나
31
+ ```
32
+
33
+ ## 세 줄 요약
34
+
35
+ 1. **「완전 무료」는 도구 값 이야기입니다.** LLM 토큰 값은 따로 들고,
36
+ `--scan-mode` 기본값이 `deep` 이라 빠른시작 그대로 치면 첫 실행이 제일 비쌉니다.
37
+ → 첫 실행은 `--scan-mode quick --max-budget 2`.
38
+ 2. **로컬 폴더는 쓰기 가능하게 마운트됩니다.** 에이전트가 진짜 파일을 고칩니다.
39
+ → 돌리기 전에 커밋하거나 스태시하세요.
40
+ 3. **내가 소유했거나 문서로 허락받은 표적에만.** 저장소가 `Authorized use only` 를 명시합니다.
41
+
42
+ ---
43
+
44
+ 이런 판단을 도구마다 매번 하기가 번거로우면, 같은 방식으로 골라 담은 **리본랩스 스킬팩**(48종, 8,900원)이 있습니다.
45
+ 목록과 직접 까는 명령까지 그 페이지에 그대로 적어 두었습니다 — 안 사셔도 됩니다.
46
+ https://rebornlabs.kr/skillpack?utm_source=installer&utm_medium=cli&utm_campaign=strix