chiwaa 0.1.2 → 0.1.4

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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/bin/chiwaa.js +164 -2
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -19,6 +19,8 @@ npx chiwaa deploy
19
19
  | `chiwaa versions` | 남아 있는 버전 |
20
20
  | `chiwaa rollback [버전]` | 한 단계 전 또는 지정 버전으로 되돌리기 |
21
21
  | `chiwaa db` · `db on` · `db create <표>` · `db rows <표>` | 사이트 DB(문의·방명록 등 글 저장) — 켜기·표 만들기·글 보기 |
22
+ | `chiwaa domains` · `search` · `buy` · `add` · `check` | x-y.net 도메인 찾기·사기(결제는 x-y.net 결제창에서 직접)·붙이기 |
23
+ | `chiwaa agent` | AI 도구용 안내(AGENTS.md·CLAUDE.md) 만들기 — 처음 deploy 때 자동 |
22
24
  | `chiwaa whoami` / `logout` | 계정 확인 / 로그아웃 |
23
25
 
24
26
  ## AI 도구(Claude Code·Codex·Cursor)에서
package/bin/chiwaa.js CHANGED
@@ -27,7 +27,7 @@ const OUTPUTS = [
27
27
  ["parcel", "dist"],
28
28
  ];
29
29
  // 그대로 올리는 사이트에서 빼는 것
30
- const SKIP = new Set([".git", "node_modules", ".chiwaa.json", ".DS_Store", "Thumbs.db", ".vercel", ".netlify"]);
30
+ const SKIP = new Set([".git", "node_modules", ".chiwaa.json", ".DS_Store", "Thumbs.db", ".vercel", ".netlify", "AGENTS.md", "CLAUDE.md"]);
31
31
 
32
32
  const c = {
33
33
  dim: (s) => (TTY ? `\x1b[2m${s}\x1b[0m` : s),
@@ -307,9 +307,61 @@ function linkSite(dir, site) {
307
307
  if (site.source === "github")
308
308
  fail(`${site.domain} 은 깃허브(${site.repo})로 배포하는 사이트입니다. CLI 로는 다른 이름을 쓰세요: --name <새 이름>`);
309
309
  fs.writeFileSync(path.join(dir, LINK_FILE), JSON.stringify({ site: site.name }, null, 2) + "\n");
310
+ writeAgentDoc(dir, site.name, false);
310
311
  return site;
311
312
  }
312
313
 
314
+ // ---------- AGENTS.md (AI 코딩 도구가 스스로 읽는 안내) ----------
315
+ // Codex·Cursor 등은 AGENTS.md, Claude Code 는 CLAUDE.md 를 읽는다. 사용자가 쓴 내용은 두고 chiwaa 구간만 넣거나 바꾼다.
316
+ const AGENT_START = "<!-- chiwaa:start -->";
317
+ const AGENT_END = "<!-- chiwaa:end -->";
318
+
319
+ function agentSection(name) {
320
+ return `${AGENT_START}
321
+ ## chiwaa 배포 (이 폴더는 ${name}.chiwaa.com 사이트)
322
+
323
+ - 배포: \`npx chiwaa deploy -y\` — 끝나면 주소가 나온다. 잘못 올렸으면 \`npx chiwaa rollback\`.
324
+ - 이 폴더는 \`.chiwaa.json\` 으로 사이트가 정해져 있다. 다른 이름(--name)으로 올리지 않는다.
325
+ - 정적 사이트만 된다(HTML·CSS·JS, Vite·React·Vue·Astro·Next export 빌드). PHP·Express 같은 서버 코드는 안 된다.
326
+ - 글 저장(문의·방명록·게시판)은 사이트 DB: \`npx chiwaa db on\` → \`npx chiwaa db create <표> --read public|owner --write public|owner\`.
327
+ - 문의·신청서처럼 개인정보가 든 표는 반드시 \`--read owner\`. 사이트에서 안 읽힌다고 public 으로 바꾸지 않는다.
328
+ - 방문자 글은 화면에 \`textContent\` 로만 넣는다. 비밀번호는 \`<input type="password">\` 로 받는다.
329
+ - 로그인 토큰(~/.chiwaa/config.json, CHIWAA_TOKEN)은 출력하거나 파일·저장소에 적지 않는다.
330
+ - 자세한 규칙·예시: https://chiwaa.kr/llms.txt — 처음 작업하거나 헷갈리면 먼저 읽는다.
331
+ ${AGENT_END}`;
332
+ }
333
+
334
+ // 바뀐 파일 이름 목록을 돌려준다
335
+ function writeAgentDoc(dir, name, loud) {
336
+ const changed = [];
337
+ const agents = path.join(dir, "AGENTS.md");
338
+ const block = agentSection(name);
339
+ let text = fs.existsSync(agents) ? fs.readFileSync(agents, "utf8") : "";
340
+ const re = new RegExp(`${AGENT_START}[\\s\\S]*?${AGENT_END}`);
341
+ const next = re.test(text) ? text.replace(re, block) : (text ? `${text.replace(/\s*$/, "")}\n\n${block}\n` : `${block}\n`);
342
+ if (next !== text) {
343
+ fs.writeFileSync(agents, next);
344
+ changed.push("AGENTS.md");
345
+ }
346
+ // Claude Code 는 CLAUDE.md 를 읽는다 — 없을 때만 AGENTS.md 를 불러오는 한 줄짜리를 만든다
347
+ const claude = path.join(dir, "CLAUDE.md");
348
+ if (!fs.existsSync(claude)) {
349
+ fs.writeFileSync(claude, "@AGENTS.md\n");
350
+ changed.push("CLAUDE.md");
351
+ } else if (!/@AGENTS\.md|chiwaa:start/.test(fs.readFileSync(claude, "utf8"))) {
352
+ fs.appendFileSync(claude, "\n@AGENTS.md\n");
353
+ changed.push("CLAUDE.md");
354
+ }
355
+ if (changed.length) console.log(c.dim(`AI 도구용 안내를 ${changed.join("·")} 에 적었습니다(사이트에는 올라가지 않음).`));
356
+ else if (loud) console.log("AI 도구용 안내가 이미 최신입니다(AGENTS.md).");
357
+ return changed;
358
+ }
359
+
360
+ async function agent(args) {
361
+ const name = await siteName(args);
362
+ writeAgentDoc(process.cwd(), name, true);
363
+ }
364
+
313
365
  // ---------- deploy ----------
314
366
  async function deploy(args) {
315
367
  const dir = path.resolve(args._[1] || ".");
@@ -478,6 +530,109 @@ async function db(args) {
478
530
  fail(`모르는 db 명령입니다: ${sub}\n 쓸 수 있는 것: status · on · create · rules · rows · code`);
479
531
  }
480
532
 
533
+ // ---------- domains (x-y.net 도메인) ----------
534
+ // 결제는 x-y.net 결제창에서 사람이 직접 한다 — CLI 는 그 화면을 열어 줄 뿐 대신 결제하지 않는다.
535
+ const won = (n) => (n == null ? "-" : `${Number(n).toLocaleString("ko-KR")}원`);
536
+
537
+ function printRecords(records) {
538
+ console.log(c.bold("\n도메인을 산 곳(가비아·카페24 등)의 DNS 관리 화면에 넣을 레코드:"));
539
+ for (const r of records) console.log(` ${r.type.padEnd(6)} ${r.name.padEnd(34)} ${c.cyan(r.value)} ${c.dim(r.why)}`);
540
+ console.log(c.dim("\n넣은 뒤 몇 분~몇 시간 지나면: npx chiwaa domains check"));
541
+ }
542
+
543
+ async function domains(args) {
544
+ const sub = args._[1] || "status";
545
+ const arg = args._[2];
546
+
547
+ if (sub === "search") {
548
+ if (!arg) fail("찾을 이름을 적어 주세요. 예: npx chiwaa domains search haesol");
549
+ const r = await api("GET", `/api/v1/domains/search?q=${encodeURIComponent(arg)}`);
550
+ if (r.status !== 200) fail(r.data.error || "도메인을 찾지 못했습니다.");
551
+ if (!r.data.results.length) return console.log("찾지 못했습니다. 다른 이름으로 해 보세요.");
552
+ for (const x of r.data.results) {
553
+ const st = x.status === "available" ? c.green("살 수 있음") : x.status === "taken" ? c.dim("이미 등록됨") : c.dim("확인 안 됨");
554
+ console.log(` ${x.domain.padEnd(28)} ${st} ${x.status === "available" ? c.dim(`1년 ${won(x.price)}`) : ""}`);
555
+ }
556
+ return console.log(c.dim("\n사려면: npx chiwaa domains buy <도메인> (결제는 x-y.net 결제창에서 직접)"));
557
+ }
558
+ if (sub === "buy") {
559
+ if (!arg) fail("살 도메인을 적어 주세요. 예: npx chiwaa domains buy haesol.co.kr");
560
+ const r = await api("GET", `/api/v1/domains/search?q=${encodeURIComponent(arg)}`);
561
+ if (r.status !== 200) fail(r.data.error || "도메인을 확인하지 못했습니다.");
562
+ const hit = r.data.results.find((x) => x.domain === arg.toLowerCase());
563
+ if (!hit) fail(`${arg} 를 확인하지 못했습니다. npx chiwaa domains search 로 먼저 찾아 보세요.`);
564
+ if (hit.status !== "available") fail(`${arg} 는 ${hit.status === "taken" ? "이미 등록된 도메인입니다" : "지금 살 수 있는지 확인되지 않습니다"}.`);
565
+ console.log(`${c.bold(hit.domain)} — 1년 ${won(hit.price)} (x-y.net)`);
566
+ console.log(`\n브라우저에서 x-y.net 신청·결제 화면을 엽니다. ${c.bold("결제는 직접 해 주세요")}(AI 가 대신 결제하지 않습니다).`);
567
+ console.log(` ${c.cyan(hit.buy_url)}`);
568
+ if (!args["no-browser"]) openBrowser(hit.buy_url);
569
+ return console.log(c.dim(`\n결제가 끝나고 도메인이 등록되면: npx chiwaa domains add ${hit.domain}`));
570
+ }
571
+
572
+ const name = await siteName(args);
573
+ const base = `/api/v1/sites/${encodeURIComponent(name)}/domains`;
574
+
575
+ if (sub === "status") {
576
+ const r = await api("GET", base);
577
+ if (r.status !== 200) fail(r.data.error || "도메인 정보를 받지 못했습니다.");
578
+ const d = r.data;
579
+ console.log(c.bold(d.site));
580
+ if (d.connected) {
581
+ const st = d.connected.status === "live" ? c.green("연결됨") : "연결 중(주소가 퍼지는 중) — npx chiwaa domains check";
582
+ console.log(` 붙은 도메인: ${d.connected.domain} ${st}`);
583
+ } else console.log(" 붙은 도메인 없음");
584
+ if (d.pending) {
585
+ console.log(` 연결 신청 중: ${d.pending.domain} (DNS 레코드 확인 전)`);
586
+ printRecords(d.pending.records);
587
+ return;
588
+ }
589
+ if (!d.xy_account) {
590
+ if (d.external_allowed) console.log(c.dim("\n다른 곳(가비아 등)에서 산 도메인은 바로: npx chiwaa domains add <도메인>"));
591
+ console.log(`\nx-y.net 에서 산 도메인을 쓰려면 x-y.net 계정을 한 번 연결해 주세요(처음 한 번만):\n ${c.cyan(d.xy_connect_url)}`);
592
+ if (!args["no-browser"] && TTY) openBrowser(d.xy_connect_url);
593
+ return;
594
+ }
595
+ console.log(`\nx-y.net 계정 ${d.xy_account} 의 도메인:`);
596
+ if (!d.domains.length) console.log(c.dim(" 아직 없습니다. 사려면: npx chiwaa domains search <이름>"));
597
+ for (const x of d.domains) console.log(` ${x}`);
598
+ if (d.domains.length && !d.connected) console.log(c.dim(`\n붙이려면: npx chiwaa domains add <도메인>`));
599
+ return;
600
+ }
601
+ if (sub === "add") {
602
+ if (!arg) fail("붙일 도메인을 적어 주세요. 예: npx chiwaa domains add haesol.co.kr");
603
+ const r = await api("POST", base, { body: { domain: arg } });
604
+ if (r.status !== 200) {
605
+ const msg = r.data.message || r.data.error || "도메인을 붙이지 못했습니다.";
606
+ if (/x-y\.net 계정을 먼저/.test(msg)) fail(`${msg}\n npx chiwaa domains 를 실행하면 연결 주소가 나옵니다.`);
607
+ fail(msg);
608
+ }
609
+ console.log(c.green("✓"), r.data.message);
610
+ if (r.data.external) return printRecords(r.data.records);
611
+ return console.log(c.dim("몇 분 뒤: npx chiwaa domains check"));
612
+ }
613
+ if (sub === "check") {
614
+ const r = await api("POST", `${base}/verify`);
615
+ if (r.status === 200) return console.log(c.green("✓"), r.data.message);
616
+ if (r.status === 202) {
617
+ console.log(r.data.message);
618
+ if (r.data.records) printRecords(r.data.records);
619
+ return;
620
+ }
621
+ fail(r.data.message || r.data.error || "확인하지 못했습니다.");
622
+ }
623
+ if (sub === "remove") {
624
+ if (!args.yes) {
625
+ if (!TTY) fail("도메인 연결을 끊으려면 -y 를 붙여 주세요(그 도메인 주소로는 사이트가 열리지 않게 됩니다).");
626
+ const a = await ask("도메인 연결을 끊을까요? 그 주소로는 사이트가 열리지 않습니다. (y/N) ");
627
+ if (!/^y(es)?$/i.test(a)) return console.log("취소했습니다.");
628
+ }
629
+ const r = await api("POST", `${base}/remove`);
630
+ if (r.status !== 200) fail(r.data.message || r.data.error || "연결을 끊지 못했습니다.");
631
+ return console.log(c.green("✓"), r.data.message);
632
+ }
633
+ fail(`모르는 domains 명령입니다: ${sub}\n 쓸 수 있는 것: (없음=상태) · search · buy · add · check · remove`);
634
+ }
635
+
481
636
  async function whoami() {
482
637
  const r = await api("GET", "/api/v1/me");
483
638
  if (r.status !== 200) fail(r.data.error || "로그인 정보를 받지 못했습니다.");
@@ -509,6 +664,13 @@ const HELP = `chiwaa ${VERSION} — chiwaa.kr 에 사이트를 바로 올립니
509
664
  chiwaa db rules <표> 읽기·쓰기 권한 바꾸기
510
665
  chiwaa db rows <표> 저장된 글 보기 (--page 2)
511
666
  chiwaa db code <표> 사이트에 붙일 코드 다시 보기
667
+ chiwaa domains 이 사이트의 도메인 상태와 내 x-y.net 도메인
668
+ chiwaa domains search <이름> 살 수 있는 도메인과 1년 요금
669
+ chiwaa domains buy <도메인> x-y.net 신청·결제 화면 열기(결제는 직접)
670
+ chiwaa domains add <도메인> 도메인 붙이기(x-y.net 도메인은 바로, 다른 곳 도메인은 넣을 DNS 레코드 안내)
671
+ chiwaa domains check 연결됐는지 확인 · domains remove -y 로 연결 끊기
672
+ chiwaa agent AI 도구용 안내(AGENTS.md·CLAUDE.md)를 만들거나 새로 고침
673
+ (처음 deploy 할 때 자동으로 만든다)
512
674
  chiwaa whoami 로그인한 계정
513
675
  chiwaa logout 로그아웃
514
676
 
@@ -520,7 +682,7 @@ async function main() {
520
682
  const cmd = args._[0];
521
683
  if (args.version) return console.log(VERSION);
522
684
  if (args.help || !cmd || cmd === "help") return console.log(HELP);
523
- const cmds = { deploy, login, list, ls: list, versions, rollback, db, whoami, logout };
685
+ const cmds = { deploy, login, list, ls: list, versions, rollback, db, domains, domain: domains, agent, whoami, logout };
524
686
  if (!cmds[cmd]) fail(`모르는 명령입니다: ${cmd}\n\n${HELP}`);
525
687
  await cmds[cmd](args);
526
688
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chiwaa",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "chiwaa.kr 에 사이트를 터미널에서 바로 올리는 도구",
5
5
  "bin": {
6
6
  "chiwaa": "bin/chiwaa.js"