cdsa-harness 0.21.0 → 0.21.1
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 +0 -1
- package/package.json +1 -1
- package/src/builtins.js +1 -1
- package/src/cli.js +39 -30
- package/src/config.js +8 -2
package/README.md
CHANGED
package/package.json
CHANGED
package/src/builtins.js
CHANGED
package/src/cli.js
CHANGED
|
@@ -846,31 +846,7 @@ export async function main(argv = []) {
|
|
|
846
846
|
// 첫 실행 온보딩(한 번만 — ~/.cdsa_harness/.welcomed 표시): 작업 폴더 설정 + 튜토리얼
|
|
847
847
|
const markerPath = path.join(configDir(), ".welcomed");
|
|
848
848
|
if (stdin.isTTY && !fs.existsSync(markerPath)) {
|
|
849
|
-
console.log(
|
|
850
|
-
[
|
|
851
|
-
"AI 가 파일을 다룰 ‘작업 폴더’를 정하세요.",
|
|
852
|
-
c.dim("이 폴더 밖은 절대 건드리지 않아요(안전장치)."),
|
|
853
|
-
"",
|
|
854
|
-
` ${c.bold("엔터")} ${c.cyan("지금 이 폴더")} 를 그대로 사용 (터미널답게, 기본)`,
|
|
855
|
-
` ${c.bold("경로")} 따로 격리하고 싶으면 입력 — 예) ${c.cyan("./sandbox")} 또는 ${c.cyan("C:\\작업\\프로젝트")}`,
|
|
856
|
-
],
|
|
857
|
-
{ title: "📁 작업 폴더 설정 (처음 한 번)", color: "cyan" }
|
|
858
|
-
));
|
|
859
|
-
const wsAns = await ask(c.cyan("작업 폴더 [엔터=기본]: "));
|
|
860
|
-
if (wsAns !== null && wsAns.trim()) {
|
|
861
|
-
cfg.workspace = wsAns.trim();
|
|
862
|
-
const rebuilt = await buildExtensions(cfg, mcp);
|
|
863
|
-
toolbox = rebuilt.toolbox;
|
|
864
|
-
skills = rebuilt.skills;
|
|
865
|
-
loop.toolbox = toolbox;
|
|
866
|
-
loop.reset();
|
|
867
|
-
}
|
|
868
|
-
try {
|
|
869
|
-
saveConfig(cfg);
|
|
870
|
-
} catch {
|
|
871
|
-
/* 저장 실패 무시 */
|
|
872
|
-
}
|
|
873
|
-
console.log(c.green(`작업 폴더: ${cfg.workspacePath()}`) + c.dim(" (나중에 /workspace 로 변경 가능)\n"));
|
|
849
|
+
console.log(c.dim(`작업 폴더 = 지금 이 폴더 (${cfg.workspacePath()}) — 바꾸려면 /workspace <경로>\n`));
|
|
874
850
|
|
|
875
851
|
const a = await ask(c.cyan("짧은 튜토리얼을 볼까요? [Y/n] "));
|
|
876
852
|
if (a !== null && ["", "y", "yes"].includes(a.trim().toLowerCase())) await runTutorial(ask);
|
|
@@ -882,8 +858,16 @@ export async function main(argv = []) {
|
|
|
882
858
|
}
|
|
883
859
|
}
|
|
884
860
|
|
|
861
|
+
let pendingInput = null; // 메뉴에서 고른 명령을 다음 루프에서 입력처럼 실행
|
|
885
862
|
while (true) {
|
|
886
|
-
|
|
863
|
+
let raw;
|
|
864
|
+
if (pendingInput !== null) {
|
|
865
|
+
raw = pendingInput;
|
|
866
|
+
pendingInput = null;
|
|
867
|
+
console.log(c.bold(c.cyan("› ")) + raw);
|
|
868
|
+
} else {
|
|
869
|
+
raw = await ask(c.bold(c.cyan("› ")));
|
|
870
|
+
}
|
|
887
871
|
if (raw === null) break; // Ctrl+D / Ctrl+C / 스트림 종료
|
|
888
872
|
const user = raw.trim();
|
|
889
873
|
if (!user) continue;
|
|
@@ -891,7 +875,27 @@ export async function main(argv = []) {
|
|
|
891
875
|
|
|
892
876
|
if (["/quit", "/exit", "quit", "exit", ":q"].includes(low)) break;
|
|
893
877
|
if (user === "/") {
|
|
878
|
+
// ↑↓ 메뉴로 명령 선택 → 바로 실행 (Tab 몰라도 됨)
|
|
879
|
+
const CMD_DESC = {
|
|
880
|
+
"/new": "새 대화", "/compact": "대화를 모델이 요약·압축", "/resume": "지난 대화 이어가기",
|
|
881
|
+
"/undo": "마지막 파일 변경 되돌리기", "/context": "모델에 보내는 컨텍스트 보기",
|
|
882
|
+
"/init": "모델이 AGENT.md 규칙 생성", "/memory": "규칙 파일 보기", "/workspace": "작업 폴더 보기/변경",
|
|
883
|
+
"/setup": "AI 연결 마법사", "/models": "모델 목록에서 선택", "/auto": "자동 수락 토글",
|
|
884
|
+
"/status": "상태·누적 토큰", "/update": "최신 버전으로 업데이트", "/teach": "교육 모드 토글",
|
|
885
|
+
"/stream": "스트리밍 토글", "/skills": "스킬 목록", "/plugins": "플러그인 목록", "/mcp": "MCP 서버",
|
|
886
|
+
"/guide": "빠른 시작 안내", "/tutorial": "튜토리얼", "/help": "전체 도움말", "/about": "정보", "/quit": "종료",
|
|
887
|
+
};
|
|
894
888
|
const skillNames = Object.keys(skills).sort();
|
|
889
|
+
const menuItems = [
|
|
890
|
+
...Object.entries(CMD_DESC).map(([k, v]) => `${k} ${v}`),
|
|
891
|
+
...skillNames.map((s) => `/${s} ${(skills[s].description || "").slice(0, 34)}`),
|
|
892
|
+
];
|
|
893
|
+
const sel = await selectMenu(menuItems, { title: "⌨️ 명령 선택 — ↑↓ 이동 · Enter 실행" , window: 14 });
|
|
894
|
+
if (sel !== undefined) {
|
|
895
|
+
if (sel) pendingInput = sel.split(/\s{2,}/)[0];
|
|
896
|
+
continue;
|
|
897
|
+
}
|
|
898
|
+
// (비TTY 폴백) 텍스트 팔레트
|
|
895
899
|
console.log(panel([
|
|
896
900
|
c.bold("대화")+" "+c.cyan("/new /compact /resume /undo /context"),
|
|
897
901
|
c.bold("프로젝트")+" "+c.cyan("/init /memory /workspace"),
|
|
@@ -1196,12 +1200,17 @@ export async function main(argv = []) {
|
|
|
1196
1200
|
await runTurn(renderSkill(skills[name], argStr));
|
|
1197
1201
|
} else {
|
|
1198
1202
|
const { BUILTIN_COMMANDS } = await import("./completion.js");
|
|
1199
|
-
const all = [...BUILTIN_COMMANDS, ...Object.keys(skills).map((s) => "/" + s)];
|
|
1200
|
-
const near = all.filter((n) => n.
|
|
1203
|
+
const all = [...new Set([...BUILTIN_COMMANDS, ...Object.keys(skills).map((s) => "/" + s)])].sort();
|
|
1204
|
+
const near = all.filter((n) => n.slice(1).startsWith(name) || n.includes(name.slice(0, 3)));
|
|
1205
|
+
if (near.length) {
|
|
1206
|
+
const sel = await selectMenu(near.slice(0, 20), { title: `'/${name}' — 이 중에 찾으시나요? (↑↓·Enter)` });
|
|
1207
|
+
if (sel) { pendingInput = sel; continue; }
|
|
1208
|
+
if (sel === null) continue; // 취소
|
|
1209
|
+
}
|
|
1201
1210
|
console.log(
|
|
1202
1211
|
c.yellow(`알 수 없는 명령: /${name}`) +
|
|
1203
|
-
(near.length ? c.dim(" 혹시? ") + c.cyan(near.join(" ")) : "") +
|
|
1204
|
-
c.dim(" · '/'
|
|
1212
|
+
(near.length ? c.dim(" 혹시? ") + c.cyan(near.slice(0, 4).join(" ")) : "") +
|
|
1213
|
+
c.dim(" · '/' 입력=명령 메뉴")
|
|
1205
1214
|
);
|
|
1206
1215
|
}
|
|
1207
1216
|
continue;
|
package/src/config.js
CHANGED
|
@@ -91,7 +91,10 @@ export class Config {
|
|
|
91
91
|
|
|
92
92
|
toJSON() {
|
|
93
93
|
const out = {};
|
|
94
|
-
for (const key of Object.keys(DEFAULTS))
|
|
94
|
+
for (const key of Object.keys(DEFAULTS)) {
|
|
95
|
+
if (key === "workspace") continue; // 실행 폴더가 곧 워크스페이스 — 저장하지 않는다
|
|
96
|
+
out[key] = this[key];
|
|
97
|
+
}
|
|
95
98
|
return out;
|
|
96
99
|
}
|
|
97
100
|
}
|
|
@@ -100,7 +103,10 @@ export function loadConfig() {
|
|
|
100
103
|
const p = configPath();
|
|
101
104
|
try {
|
|
102
105
|
if (fs.existsSync(p)) {
|
|
103
|
-
|
|
106
|
+
const data = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
107
|
+
// workspace 는 '실행한 폴더'가 진리 — 파일에 저장된 값(옛 버전 잔재)은 무시한다.
|
|
108
|
+
delete data.workspace;
|
|
109
|
+
return new Config(data);
|
|
104
110
|
}
|
|
105
111
|
} catch {
|
|
106
112
|
// 손상된 설정은 무시하고 기본값
|