triflux 9.1.0 → 9.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/triflux.mjs +126 -7
- package/package.json +1 -1
- package/scripts/setup.mjs +57 -0
- package/skills/star-prompt/SKILL.md +122 -0
- package/tui/setup.mjs +51 -0
package/bin/triflux.mjs
CHANGED
|
@@ -16,8 +16,24 @@ const PKG_ROOT = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
|
16
16
|
const CLAUDE_DIR = join(homedir(), ".claude");
|
|
17
17
|
const CODEX_DIR = join(homedir(), ".codex");
|
|
18
18
|
const CODEX_CONFIG_PATH = join(CODEX_DIR, "config.toml");
|
|
19
|
+
const GEMINI_DIR = join(homedir(), ".gemini");
|
|
20
|
+
const GEMINI_PROFILES_PATH = join(GEMINI_DIR, "triflux-profiles.json");
|
|
19
21
|
const PKG = JSON.parse(readFileSync(join(PKG_ROOT, "package.json"), "utf8"));
|
|
20
22
|
|
|
23
|
+
// 이 배열에 포함된 버전에서만 star prompt를 표시한다 (빈 배열 = 모든 버전에서 표시)
|
|
24
|
+
const STAR_PROMPT_VERSIONS = ["9.2.0"];
|
|
25
|
+
|
|
26
|
+
const DEFAULT_GEMINI_PROFILES = {
|
|
27
|
+
model: "gemini-3.1-pro-preview",
|
|
28
|
+
profiles: {
|
|
29
|
+
pro31: { model: "gemini-3.1-pro-preview", hint: "3.1 Pro — 플래그십 (1M ctx, 멀티모달)" },
|
|
30
|
+
flash3: { model: "gemini-3-flash-preview", hint: "3.0 Flash — 빠른 응답, 비용 효율" },
|
|
31
|
+
pro25: { model: "gemini-2.5-pro", hint: "2.5 Pro — 안정 (추론 강화)" },
|
|
32
|
+
flash25: { model: "gemini-2.5-flash", hint: "2.5 Flash — 경량 범용" },
|
|
33
|
+
lite25: { model: "gemini-2.5-flash-lite", hint: "2.5 Flash Lite — 최경량" },
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
|
|
21
37
|
const REQUIRED_CODEX_PROFILES = [
|
|
22
38
|
{
|
|
23
39
|
name: "codex53_high",
|
|
@@ -496,6 +512,44 @@ function previewCodexProfiles() {
|
|
|
496
512
|
};
|
|
497
513
|
}
|
|
498
514
|
|
|
515
|
+
function ensureGeminiProfiles() {
|
|
516
|
+
try {
|
|
517
|
+
if (!existsSync(GEMINI_DIR)) mkdirSync(GEMINI_DIR, { recursive: true });
|
|
518
|
+
|
|
519
|
+
if (!existsSync(GEMINI_PROFILES_PATH)) {
|
|
520
|
+
writeFileSync(GEMINI_PROFILES_PATH, JSON.stringify(DEFAULT_GEMINI_PROFILES, null, 2) + "\n", "utf8");
|
|
521
|
+
return { ok: true, created: true, count: Object.keys(DEFAULT_GEMINI_PROFILES.profiles).length };
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
let cfg;
|
|
525
|
+
try {
|
|
526
|
+
cfg = JSON.parse(readFileSync(GEMINI_PROFILES_PATH, "utf8"));
|
|
527
|
+
} catch {
|
|
528
|
+
// 파싱 실패 → 재생성
|
|
529
|
+
writeFileSync(GEMINI_PROFILES_PATH, JSON.stringify(DEFAULT_GEMINI_PROFILES, null, 2) + "\n", "utf8");
|
|
530
|
+
return { ok: true, created: true, count: Object.keys(DEFAULT_GEMINI_PROFILES.profiles).length };
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
if (!cfg.profiles) cfg.profiles = {};
|
|
534
|
+
let added = 0;
|
|
535
|
+
for (const [name, value] of Object.entries(DEFAULT_GEMINI_PROFILES.profiles)) {
|
|
536
|
+
if (!cfg.profiles[name]) {
|
|
537
|
+
cfg.profiles[name] = value;
|
|
538
|
+
added++;
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
if (!cfg.model) cfg.model = DEFAULT_GEMINI_PROFILES.model;
|
|
542
|
+
|
|
543
|
+
if (added > 0) {
|
|
544
|
+
writeFileSync(GEMINI_PROFILES_PATH, JSON.stringify(cfg, null, 2) + "\n", "utf8");
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
return { ok: true, created: false, added, count: Object.keys(cfg.profiles).length };
|
|
548
|
+
} catch (e) {
|
|
549
|
+
return { ok: false, message: e.message };
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
|
|
499
553
|
function syncFile(src, dst, label) {
|
|
500
554
|
const dstDir = dirname(dst);
|
|
501
555
|
if (!existsSync(dstDir)) mkdirSync(dstDir, { recursive: true });
|
|
@@ -869,19 +923,42 @@ function cmdSetup(options = {}) {
|
|
|
869
923
|
}
|
|
870
924
|
}
|
|
871
925
|
|
|
926
|
+
// ── 결과 추적 ──
|
|
927
|
+
const summary = [];
|
|
928
|
+
|
|
872
929
|
const codexProfileResult = ensureCodexProfiles();
|
|
873
930
|
if (!codexProfileResult.ok) {
|
|
874
931
|
warn(`Codex profiles 설정 실패: ${codexProfileResult.message}`);
|
|
932
|
+
summary.push({ item: "Codex profiles", status: "⚠️", detail: codexProfileResult.message });
|
|
875
933
|
} else if (codexProfileResult.added > 0) {
|
|
876
934
|
ok(`Codex profiles: ${codexProfileResult.added}개 추가됨 (~/.codex/config.toml)`);
|
|
935
|
+
summary.push({ item: "Codex profiles", status: "✅", detail: `${codexProfileResult.added}개 추가됨` });
|
|
877
936
|
} else {
|
|
878
937
|
ok("Codex profiles: 이미 준비됨");
|
|
938
|
+
summary.push({ item: "Codex profiles", status: "✅", detail: "이미 준비됨" });
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
// Gemini 프로필
|
|
942
|
+
const geminiResult = ensureGeminiProfiles();
|
|
943
|
+
if (!geminiResult.ok) {
|
|
944
|
+
warn(`Gemini profiles 설정 실패: ${geminiResult.message}`);
|
|
945
|
+
summary.push({ item: "Gemini profiles", status: "⚠️", detail: geminiResult.message });
|
|
946
|
+
} else if (geminiResult.created) {
|
|
947
|
+
ok(`Gemini profiles: ${geminiResult.count}개 생성됨 (~/.gemini/triflux-profiles.json)`);
|
|
948
|
+
summary.push({ item: "Gemini profiles", status: "✅", detail: `${geminiResult.count}개 생성됨` });
|
|
949
|
+
} else if (geminiResult.added > 0) {
|
|
950
|
+
ok(`Gemini profiles: ${geminiResult.added}개 추가됨`);
|
|
951
|
+
summary.push({ item: "Gemini profiles", status: "✅", detail: `${geminiResult.added}개 추가됨 (총 ${geminiResult.count}개)` });
|
|
952
|
+
} else {
|
|
953
|
+
ok(`Gemini profiles: ${geminiResult.count}개 준비됨`);
|
|
954
|
+
summary.push({ item: "Gemini profiles", status: "✅", detail: `${geminiResult.count}개 준비됨` });
|
|
879
955
|
}
|
|
880
956
|
|
|
881
957
|
// hub MCP 사전 등록 (서버 미실행이어도 설정만 등록 — hub start 시 즉시 사용 가능)
|
|
882
958
|
if (existsSync(join(PKG_ROOT, "hub", "server.mjs"))) {
|
|
883
959
|
const defaultHubUrl = `http://127.0.0.1:${process.env.TFX_HUB_PORT || "27888"}/mcp`;
|
|
884
960
|
autoRegisterMcp(defaultHubUrl);
|
|
961
|
+
summary.push({ item: "Hub MCP", status: "✅", detail: "등록됨" });
|
|
885
962
|
console.log("");
|
|
886
963
|
}
|
|
887
964
|
|
|
@@ -900,6 +977,7 @@ function cmdSetup(options = {}) {
|
|
|
900
977
|
const currentCmd = settings.statusLine?.command || "";
|
|
901
978
|
if (currentCmd.includes("hud-qos-status.mjs")) {
|
|
902
979
|
ok("statusLine 이미 설정됨");
|
|
980
|
+
summary.push({ item: "HUD statusLine", status: "✅", detail: "이미 설정됨" });
|
|
903
981
|
} else {
|
|
904
982
|
const nodePath = process.execPath.replace(/\\/g, "/");
|
|
905
983
|
const hudForward = hudPath.replace(/\\/g, "/");
|
|
@@ -917,6 +995,7 @@ function cmdSetup(options = {}) {
|
|
|
917
995
|
|
|
918
996
|
writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf8");
|
|
919
997
|
ok("statusLine 설정 완료 — 세션 재시작 후 HUD 표시");
|
|
998
|
+
summary.push({ item: "HUD statusLine", status: "✅", detail: "설정 완료" });
|
|
920
999
|
}
|
|
921
1000
|
} catch (e) {
|
|
922
1001
|
throw createCliError(`settings.json 처리 실패: ${e.message}`, {
|
|
@@ -928,9 +1007,50 @@ function cmdSetup(options = {}) {
|
|
|
928
1007
|
}
|
|
929
1008
|
} else {
|
|
930
1009
|
warn("HUD 파일 없음 — 먼저 파일 동기화 필요");
|
|
1010
|
+
summary.push({ item: "HUD statusLine", status: "⚠️", detail: "HUD 파일 없음" });
|
|
1011
|
+
}
|
|
1012
|
+
|
|
1013
|
+
// CLI 존재 확인
|
|
1014
|
+
const cliChecks = [
|
|
1015
|
+
{ name: "codex", install: "npm i -g @openai/codex" },
|
|
1016
|
+
{ name: "gemini", install: "npm i -g @google/gemini-cli" },
|
|
1017
|
+
];
|
|
1018
|
+
for (const { name, install } of cliChecks) {
|
|
1019
|
+
if (which(name)) {
|
|
1020
|
+
summary.push({ item: `${name} CLI`, status: "✅", detail: "설치됨" });
|
|
1021
|
+
} else {
|
|
1022
|
+
summary.push({ item: `${name} CLI`, status: "⏭️", detail: `미설치 (${install})` });
|
|
1023
|
+
}
|
|
931
1024
|
}
|
|
932
1025
|
|
|
933
|
-
|
|
1026
|
+
// Star request (버전 게이팅)
|
|
1027
|
+
const showStar = STAR_PROMPT_VERSIONS.length === 0 || STAR_PROMPT_VERSIONS.includes(PKG.version);
|
|
1028
|
+
if (showStar) {
|
|
1029
|
+
try {
|
|
1030
|
+
execFileSync("gh", ["auth", "status"], { timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
|
|
1031
|
+
try {
|
|
1032
|
+
execFileSync("gh", ["api", "user/starred/tellang/triflux"], { timeout: 5000, stdio: ["pipe", "pipe", "pipe"] });
|
|
1033
|
+
console.log();
|
|
1034
|
+
ok(`이미 함께하고 계시군요. ${AMBER}⭐${RESET}`);
|
|
1035
|
+
} catch {
|
|
1036
|
+
console.log();
|
|
1037
|
+
info(`${AMBER}⭐${RESET} 하나가 큰 차이를 만듭니다. ${CYAN}https://github.com/tellang/triflux${RESET}`);
|
|
1038
|
+
}
|
|
1039
|
+
} catch {
|
|
1040
|
+
console.log();
|
|
1041
|
+
info(`${AMBER}⭐${RESET} 하나가 큰 차이를 만듭니다. ${CYAN}https://github.com/tellang/triflux${RESET}`);
|
|
1042
|
+
}
|
|
1043
|
+
}
|
|
1044
|
+
|
|
1045
|
+
// ── 결과 요약 테이블 ──
|
|
1046
|
+
console.log(`\n${BOLD}── 설정 요약 ──${RESET}`);
|
|
1047
|
+
const maxItem = Math.max(...summary.map((s) => s.item.length));
|
|
1048
|
+
for (const { item, status, detail } of summary) {
|
|
1049
|
+
console.log(` ${status} ${item.padEnd(maxItem)} ${DIM}${detail}${RESET}`);
|
|
1050
|
+
}
|
|
1051
|
+
|
|
1052
|
+
console.log(`\n${DIM}설치 위치: ${CLAUDE_DIR}${RESET}`);
|
|
1053
|
+
console.log(`${DIM}버전: v${PKG.version}${RESET}\n`);
|
|
934
1054
|
}
|
|
935
1055
|
|
|
936
1056
|
function addDoctorCheck(report, entry) {
|
|
@@ -1849,7 +1969,7 @@ function cmdUpdate() {
|
|
|
1849
1969
|
return;
|
|
1850
1970
|
}
|
|
1851
1971
|
|
|
1852
|
-
// 3. setup 재실행 (
|
|
1972
|
+
// 3. setup 재실행 (파일 동기화, 프로파일, HUD, CLI 확인)
|
|
1853
1973
|
if (updated) {
|
|
1854
1974
|
console.log("");
|
|
1855
1975
|
// 업데이트 후 새 버전 읽기
|
|
@@ -1865,18 +1985,17 @@ function cmdUpdate() {
|
|
|
1865
1985
|
ok(`버전: v${oldVer} (이미 최신)`);
|
|
1866
1986
|
}
|
|
1867
1987
|
|
|
1868
|
-
// setup 재실행
|
|
1869
|
-
console.log(
|
|
1870
|
-
info("setup 재실행 중...");
|
|
1988
|
+
// setup 재실행 — 개선된 cmdSetup()이 Gemini 프로필, CLI 확인, 요약 테이블 포함
|
|
1989
|
+
console.log(`\n${CYAN}── 설정 동기화 ──${RESET}`);
|
|
1871
1990
|
cmdSetup();
|
|
1872
1991
|
|
|
1873
1992
|
if (stoppedHubInfo) {
|
|
1874
|
-
if (startHubAfterUpdate(stoppedHubInfo))
|
|
1993
|
+
if (startHubAfterUpdate(stoppedHubInfo)) ok("hub 재기동 완료");
|
|
1875
1994
|
else warn("hub 재기동 실패 — `tfx hub start`로 수동 시작 필요");
|
|
1876
1995
|
}
|
|
1877
1996
|
}
|
|
1878
1997
|
|
|
1879
|
-
console.log(`${GREEN}${BOLD}업데이트 완료${RESET}\n`);
|
|
1998
|
+
console.log(`${GREEN}${BOLD}✓ 업데이트 완료${RESET}\n`);
|
|
1880
1999
|
}
|
|
1881
2000
|
|
|
1882
2001
|
function cmdList(options = {}) {
|
package/package.json
CHANGED
package/scripts/setup.mjs
CHANGED
|
@@ -331,6 +331,56 @@ function ensureCodexProfiles() {
|
|
|
331
331
|
}
|
|
332
332
|
}
|
|
333
333
|
|
|
334
|
+
const GEMINI_DIR = join(homedir(), ".gemini");
|
|
335
|
+
const GEMINI_PROFILES_PATH = join(GEMINI_DIR, "triflux-profiles.json");
|
|
336
|
+
|
|
337
|
+
const DEFAULT_GEMINI_PROFILES = {
|
|
338
|
+
model: "gemini-3.1-pro-preview",
|
|
339
|
+
profiles: {
|
|
340
|
+
pro31: { model: "gemini-3.1-pro-preview", hint: "3.1 Pro — 플래그십 (1M ctx, 멀티모달)" },
|
|
341
|
+
flash3: { model: "gemini-3-flash-preview", hint: "3.0 Flash — 빠른 응답, 비용 효율" },
|
|
342
|
+
pro25: { model: "gemini-2.5-pro", hint: "2.5 Pro — 안정 (추론 강화)" },
|
|
343
|
+
flash25: { model: "gemini-2.5-flash", hint: "2.5 Flash — 경량 범용" },
|
|
344
|
+
lite25: { model: "gemini-2.5-flash-lite", hint: "2.5 Flash Lite — 최경량" },
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
function ensureGeminiProfiles() {
|
|
349
|
+
try {
|
|
350
|
+
if (!existsSync(GEMINI_DIR)) mkdirSync(GEMINI_DIR, { recursive: true });
|
|
351
|
+
|
|
352
|
+
if (!existsSync(GEMINI_PROFILES_PATH)) {
|
|
353
|
+
writeFileSync(GEMINI_PROFILES_PATH, JSON.stringify(DEFAULT_GEMINI_PROFILES, null, 2) + "\n", "utf8");
|
|
354
|
+
return Object.keys(DEFAULT_GEMINI_PROFILES.profiles).length;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
let cfg;
|
|
358
|
+
try {
|
|
359
|
+
cfg = JSON.parse(readFileSync(GEMINI_PROFILES_PATH, "utf8"));
|
|
360
|
+
} catch {
|
|
361
|
+
writeFileSync(GEMINI_PROFILES_PATH, JSON.stringify(DEFAULT_GEMINI_PROFILES, null, 2) + "\n", "utf8");
|
|
362
|
+
return Object.keys(DEFAULT_GEMINI_PROFILES.profiles).length;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (!cfg.profiles) cfg.profiles = {};
|
|
366
|
+
let added = 0;
|
|
367
|
+
for (const [name, value] of Object.entries(DEFAULT_GEMINI_PROFILES.profiles)) {
|
|
368
|
+
if (!cfg.profiles[name]) {
|
|
369
|
+
cfg.profiles[name] = value;
|
|
370
|
+
added++;
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
if (!cfg.model) cfg.model = DEFAULT_GEMINI_PROFILES.model;
|
|
374
|
+
|
|
375
|
+
if (added > 0) {
|
|
376
|
+
writeFileSync(GEMINI_PROFILES_PATH, JSON.stringify(cfg, null, 2) + "\n", "utf8");
|
|
377
|
+
}
|
|
378
|
+
return added;
|
|
379
|
+
} catch {
|
|
380
|
+
return 0;
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
334
384
|
export { replaceProfileSection, hasProfileSection, detectDevMode, SYNC_MAP, BREADCRUMB_PATH, PLUGIN_ROOT, CLAUDE_DIR, SKILL_ALIASES };
|
|
335
385
|
|
|
336
386
|
async function main() {
|
|
@@ -813,6 +863,13 @@ if (codexProfilesAdded > 0) {
|
|
|
813
863
|
synced++;
|
|
814
864
|
}
|
|
815
865
|
|
|
866
|
+
// ── Gemini 프로필 자동 보정 ──
|
|
867
|
+
|
|
868
|
+
const geminiProfilesAdded = ensureGeminiProfiles();
|
|
869
|
+
if (geminiProfilesAdded > 0) {
|
|
870
|
+
synced++;
|
|
871
|
+
}
|
|
872
|
+
|
|
816
873
|
// ── MCP 인벤토리 백그라운드 갱신 ──
|
|
817
874
|
|
|
818
875
|
const mcpCheck = join(PLUGIN_ROOT, "scripts", "mcp-check.mjs");
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: star-prompt
|
|
3
|
+
description: >-
|
|
4
|
+
CLI 프로젝트의 setup/postinstall 흐름에 GitHub 스타 요청 프롬프트를 추가한다.
|
|
5
|
+
gh CLI 인증 확인 → 이미 스타 여부 감지 → 인터랙티브 confirm → gh API로 자동 스타.
|
|
6
|
+
Apple 스타일 UX 카피 포함. 'star prompt', '스타 요청', '리포 스타', 'star request',
|
|
7
|
+
'깃헙 스타 넣어줘', 'star 눌러달라고', '응원 요청' 같은 요청에 사용한다.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# tfx-star-prompt — GitHub Star Request Prompt
|
|
11
|
+
|
|
12
|
+
CLI 도구의 setup 완료 시점에 GitHub 리포 스타를 요청하는 프롬프트를 추가한다.
|
|
13
|
+
사용자 경험을 해치지 않으면서, 이미 스타한 사용자에겐 감사를 표하고, 아직 안 한 사용자에겐 부담 없이 한 번 물어본다.
|
|
14
|
+
|
|
15
|
+
## 동작 흐름
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
gh auth status ─── 실패 → URL만 표시 (비인터랙티브 폴백)
|
|
19
|
+
│
|
|
20
|
+
✓ 인증됨
|
|
21
|
+
│
|
|
22
|
+
gh api user/starred/{owner}/{repo} ─── 성공 → "이미 함께하고 계시군요. ⭐"
|
|
23
|
+
│
|
|
24
|
+
✗ 미스타
|
|
25
|
+
│
|
|
26
|
+
confirm("⭐ 하나가 큰 차이를 만듭니다.") ─── N → URL 조용히 표시
|
|
27
|
+
│
|
|
28
|
+
Y
|
|
29
|
+
│
|
|
30
|
+
gh api -X PUT /user/starred/{owner}/{repo} → "함께해 주셔서 감사합니다. ⭐"
|
|
31
|
+
│
|
|
32
|
+
실패 → URL 폴백
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## 구현 패턴
|
|
36
|
+
|
|
37
|
+
### 인터랙티브 (TUI / readline confirm)
|
|
38
|
+
|
|
39
|
+
setup 위저드 완료 후 호출. `confirm()` 으로 Y/n 입력 받고 `gh api`로 자동 스타.
|
|
40
|
+
|
|
41
|
+
```javascript
|
|
42
|
+
async function starRequest() {
|
|
43
|
+
let ghOk = false;
|
|
44
|
+
try {
|
|
45
|
+
execFileSync("gh", ["auth", "status"], {
|
|
46
|
+
timeout: 5000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"],
|
|
47
|
+
});
|
|
48
|
+
ghOk = true;
|
|
49
|
+
} catch {}
|
|
50
|
+
|
|
51
|
+
if (!ghOk) {
|
|
52
|
+
// gh 미설치/미인증 — URL만 표시
|
|
53
|
+
info(`⭐ 하나가 큰 차이를 만듭니다. https://github.com/{owner}/{repo}`);
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
let alreadyStarred = false;
|
|
58
|
+
try {
|
|
59
|
+
execFileSync("gh", ["api", "user/starred/{owner}/{repo}"], {
|
|
60
|
+
timeout: 5000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"],
|
|
61
|
+
});
|
|
62
|
+
alreadyStarred = true;
|
|
63
|
+
} catch {}
|
|
64
|
+
|
|
65
|
+
if (alreadyStarred) {
|
|
66
|
+
ok(`이미 함께하고 계시군요. ⭐`);
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (await confirm(`⭐ 하나가 큰 차이를 만듭니다.`, true)) {
|
|
71
|
+
try {
|
|
72
|
+
execFileSync("gh", ["api", "-X", "PUT", "/user/starred/{owner}/{repo}"], {
|
|
73
|
+
timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
|
|
74
|
+
});
|
|
75
|
+
ok(`함께해 주셔서 감사합니다. ⭐`);
|
|
76
|
+
} catch {
|
|
77
|
+
info(`https://github.com/{owner}/{repo}`);
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
// 거절 시 URL만 조용히
|
|
81
|
+
console.log(` https://github.com/{owner}/{repo}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### 비인터랙티브 (postinstall / CLI banner)
|
|
87
|
+
|
|
88
|
+
confirm 불가한 환경. 이미 스타 여부만 감지하고 메시지 분기.
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
try {
|
|
92
|
+
execFileSync("gh", ["auth", "status"], { timeout: 5000, stdio: ["pipe","pipe","pipe"] });
|
|
93
|
+
try {
|
|
94
|
+
execFileSync("gh", ["api", "user/starred/{owner}/{repo}"], { timeout: 5000, stdio: ["pipe","pipe","pipe"] });
|
|
95
|
+
ok(`이미 함께하고 계시군요. ⭐`);
|
|
96
|
+
} catch {
|
|
97
|
+
info(`⭐ 하나가 큰 차이를 만듭니다. https://github.com/{owner}/{repo}`);
|
|
98
|
+
}
|
|
99
|
+
} catch {
|
|
100
|
+
info(`⭐ 하나가 큰 차이를 만듭니다. https://github.com/{owner}/{repo}`);
|
|
101
|
+
}
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## 적용 시 규칙
|
|
105
|
+
|
|
106
|
+
1. `{owner}/{repo}`를 대상 리포로 치환한다
|
|
107
|
+
2. 프로젝트의 기존 ANSI 컬러 상수(AMBER, CYAN, RESET 등)를 사용한다
|
|
108
|
+
3. setup 완료 직후, 최종 요약 다음에 호출한다 — 핵심 설정 흐름을 방해하지 않는다
|
|
109
|
+
4. 모든 gh 호출은 `timeout: 5000`, `stdio: ["pipe","pipe","pipe"]`로 감싸서 실패해도 setup을 블로킹하지 않는다
|
|
110
|
+
5. 거절 시 죄책감을 주지 않는다 — URL만 조용히 남긴다
|
|
111
|
+
|
|
112
|
+
## UX 카피 톤
|
|
113
|
+
|
|
114
|
+
Apple 스타일: 짧고, 자신감 있고, 부담 없이.
|
|
115
|
+
|
|
116
|
+
| 상황 | 멘트 |
|
|
117
|
+
|------|------|
|
|
118
|
+
| 이미 스타 | `이미 함께하고 계시군요. ⭐` |
|
|
119
|
+
| 요청 | `⭐ 하나가 큰 차이를 만듭니다.` |
|
|
120
|
+
| 수락 후 | `함께해 주셔서 감사합니다. ⭐` |
|
|
121
|
+
| 거절 | URL만 |
|
|
122
|
+
| gh 없음 | `⭐ 하나가 큰 차이를 만듭니다.` + URL |
|
package/tui/setup.mjs
CHANGED
|
@@ -326,6 +326,56 @@ function showSummary(results) {
|
|
|
326
326
|
}
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
+
// ── Star Request ──
|
|
330
|
+
|
|
331
|
+
async function starRequest() {
|
|
332
|
+
let ghOk = false;
|
|
333
|
+
try {
|
|
334
|
+
execFileSync("gh", ["auth", "status"], {
|
|
335
|
+
timeout: 5000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"],
|
|
336
|
+
});
|
|
337
|
+
ghOk = true;
|
|
338
|
+
} catch {}
|
|
339
|
+
|
|
340
|
+
if (!ghOk) {
|
|
341
|
+
console.log();
|
|
342
|
+
info(`${AMBER}⭐${RESET} 하나가 큰 차이를 만듭니다. ${CYAN}https://github.com/tellang/triflux${RESET}`);
|
|
343
|
+
console.log();
|
|
344
|
+
return;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// gh 인증됨 — 스타 여부 확인
|
|
348
|
+
let alreadyStarred = false;
|
|
349
|
+
try {
|
|
350
|
+
execFileSync("gh", ["api", "user/starred/tellang/triflux"], {
|
|
351
|
+
timeout: 5000, encoding: "utf8", stdio: ["pipe", "pipe", "pipe"],
|
|
352
|
+
});
|
|
353
|
+
alreadyStarred = true;
|
|
354
|
+
} catch {}
|
|
355
|
+
|
|
356
|
+
console.log();
|
|
357
|
+
|
|
358
|
+
if (alreadyStarred) {
|
|
359
|
+
ok(`이미 함께하고 계시군요. ${AMBER}⭐${RESET}`);
|
|
360
|
+
console.log();
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (await confirm(`${AMBER}⭐${RESET} 하나가 큰 차이를 만듭니다.`, true)) {
|
|
365
|
+
try {
|
|
366
|
+
execFileSync("gh", ["api", "-X", "PUT", "/user/starred/tellang/triflux"], {
|
|
367
|
+
timeout: 5000, stdio: ["pipe", "pipe", "pipe"],
|
|
368
|
+
});
|
|
369
|
+
ok(`함께해 주셔서 감사합니다. ${AMBER}⭐${RESET}`);
|
|
370
|
+
} catch {
|
|
371
|
+
info(`${CYAN}https://github.com/tellang/triflux${RESET}`);
|
|
372
|
+
}
|
|
373
|
+
} else {
|
|
374
|
+
console.log(` ${DIM}${CYAN}https://github.com/tellang/triflux${RESET}`);
|
|
375
|
+
}
|
|
376
|
+
console.log();
|
|
377
|
+
}
|
|
378
|
+
|
|
329
379
|
// ── Main Menu ──
|
|
330
380
|
|
|
331
381
|
const MENU = [
|
|
@@ -357,6 +407,7 @@ async function main() {
|
|
|
357
407
|
case 0: {
|
|
358
408
|
const results = await runWizard();
|
|
359
409
|
showSummary(results);
|
|
410
|
+
await starRequest();
|
|
360
411
|
break;
|
|
361
412
|
}
|
|
362
413
|
|