cdsa-harness 0.13.0 → 0.14.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "cdsa-harness",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "description": "AI 에이전트의 내부 동작을 단계별로 드러내는 교육용 터미널 하네스. 실시간 스트리밍 + OpenAI/Claude/OpenRouter + MCP + npm 플러그인·크로스포맷 스킬.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/builtins.js CHANGED
@@ -2,7 +2,7 @@
2
2
  // 스킬/플러그인/버전을 바꾼 뒤 `node scripts/gen-builtins.mjs` 로 재생성하세요.
3
3
  import p0 from "../plugins/hwpx_read.mjs";
4
4
 
5
- export const VERSION = "0.13.0";
5
+ export const VERSION = "0.14.0";
6
6
 
7
7
  export const BUILTIN_PLUGINS = [p0];
8
8
 
package/src/cli.js CHANGED
@@ -197,6 +197,44 @@ function makeApproval(ask) {
197
197
  };
198
198
  }
199
199
 
200
+ function isNewer(a, b) {
201
+ const pa = String(a).split(".").map((n) => parseInt(n, 10) || 0);
202
+ const pb = String(b).split(".").map((n) => parseInt(n, 10) || 0);
203
+ for (let i = 0; i < 3; i++) {
204
+ if ((pa[i] || 0) > (pb[i] || 0)) return true;
205
+ if ((pa[i] || 0) < (pb[i] || 0)) return false;
206
+ }
207
+ return false;
208
+ }
209
+
210
+ // 시작 시 새 버전 확인(하루 1회, 네트워크 실패는 조용히 무시 → 폐쇄망 안전).
211
+ async function maybeCheckUpdate(cfg) {
212
+ if (cfg.update_check === false) return null;
213
+ const stamp = path.join(configDir(), ".update_check");
214
+ try {
215
+ if (Date.now() - Number(fs.readFileSync(stamp, "utf8")) < 24 * 3600 * 1000) return null;
216
+ } catch {
217
+ /* 첫 확인 */
218
+ }
219
+ try {
220
+ fs.mkdirSync(configDir(), { recursive: true });
221
+ fs.writeFileSync(stamp, String(Date.now())); // 결과와 무관하게 하루 1회로 제한
222
+ } catch {
223
+ /* ignore */
224
+ }
225
+ try {
226
+ const ctrl = new AbortController();
227
+ const t = setTimeout(() => ctrl.abort(), 1500);
228
+ const res = await fetch("https://registry.npmjs.org/cdsa-harness/latest", { signal: ctrl.signal });
229
+ clearTimeout(t);
230
+ if (!res.ok) return null;
231
+ const latest = (await res.json()).version;
232
+ return latest && isNewer(latest, VERSION) ? latest : null;
233
+ } catch {
234
+ return null;
235
+ }
236
+ }
237
+
200
238
  // 작업 폴더 기준으로 플러그인·스킬·도구상자를 구성한다(시작 시 + /workspace 변경 시).
201
239
  async function buildExtensions(cfg, mcp) {
202
240
  const filePlugins = await loadPlugins(cfg.workspacePath());
@@ -539,6 +577,16 @@ export async function main(argv = []) {
539
577
 
540
578
  printIntro(cfg);
541
579
 
580
+ // 새 버전 안내(있을 때만, 하루 1회)
581
+ const newer = await maybeCheckUpdate(cfg);
582
+ if (newer) {
583
+ console.log(
584
+ c.yellow(`⬆️ 새 버전 v${newer} 가 나왔어요!`) +
585
+ c.dim(` 업데이트: npm i -g cdsa-harness@latest · exe 는 Releases 에서 새로 받기`) +
586
+ "\n"
587
+ );
588
+ }
589
+
542
590
  // ③ MCP 서버(다른 에이전트와 공용 표준) 연결 — 1회
543
591
  if (cfg.mcpServers && Object.keys(cfg.mcpServers).length) {
544
592
  process.stdout.write(c.dim("MCP 서버 연결 중...\r"));
@@ -578,10 +626,37 @@ export async function main(argv = []) {
578
626
 
579
627
  const rule = () => console.log(c.grey("─".repeat(Math.min(80, stdout.columns || 80))));
580
628
 
581
- // 첫 실행이면 튜토리얼을 권한다(한 번만 — ~/.cdsa_harness/.welcomed 표시).
629
+ // 첫 실행 온보딩(한 번만 — ~/.cdsa_harness/.welcomed 표시): 작업 폴더 설정 + 튜토리얼
582
630
  const markerPath = path.join(configDir(), ".welcomed");
583
631
  if (stdin.isTTY && !fs.existsSync(markerPath)) {
584
- const a = await ask(c.cyan("처음 오셨네요! 👋 짧은 튜토리얼을 볼까요? [Y/n] "));
632
+ console.log(panel(
633
+ [
634
+ "AI 가 파일을 다룰 ‘작업 폴더’를 정하세요.",
635
+ c.dim("이 폴더 밖은 절대 건드리지 않아요(안전장치)."),
636
+ "",
637
+ ` ${c.bold("엔터")} 기본값 ${c.cyan("./workspace")} (하위 폴더 자동 생성)`,
638
+ ` ${c.bold(".")} 지금 이 폴더를 그대로 사용`,
639
+ ` ${c.bold("경로")} 예) ${c.cyan("./문서")} 또는 ${c.cyan("C:\\작업\\프로젝트")}`,
640
+ ],
641
+ { title: "📁 작업 폴더 설정 (처음 한 번)", color: "cyan" }
642
+ ));
643
+ const wsAns = await ask(c.cyan("작업 폴더 [엔터=기본]: "));
644
+ if (wsAns !== null && wsAns.trim()) {
645
+ cfg.workspace = wsAns.trim();
646
+ const rebuilt = await buildExtensions(cfg, mcp);
647
+ toolbox = rebuilt.toolbox;
648
+ skills = rebuilt.skills;
649
+ loop.toolbox = toolbox;
650
+ loop.reset();
651
+ }
652
+ try {
653
+ saveConfig(cfg);
654
+ } catch {
655
+ /* 저장 실패 무시 */
656
+ }
657
+ console.log(c.green(`작업 폴더: ${cfg.workspacePath()}`) + c.dim(" (나중에 /workspace 로 변경 가능)\n"));
658
+
659
+ const a = await ask(c.cyan("짧은 튜토리얼을 볼까요? [Y/n] "));
585
660
  if (a !== null && ["", "y", "yes"].includes(a.trim().toLowerCase())) await runTutorial(ask);
586
661
  try {
587
662
  fs.mkdirSync(configDir(), { recursive: true });
package/src/config.js CHANGED
@@ -43,6 +43,7 @@ const DEFAULTS = {
43
43
  import_foreign_skills: true, // .claude/commands 등 외부 포맷 스킬도 읽기(프로젝트+전역)
44
44
  skill_dirs: [], // 스킬을 추가로 읽어올 폴더(절대/상대 경로)
45
45
  no_color: false, // 색상 끄기(흑백)
46
+ update_check: true, // 시작 시 새 버전 확인(하루 1회, 실패 시 조용히 무시)
46
47
  plugins: [], // 추가로 불러올 npm 플러그인 패키지 이름(이름 규칙과 무관하게 강제 로드)
47
48
  mcpServers: {}, // MCP 서버 설정 (Claude Code/Cursor 와 동일한 형식)
48
49
  };