chiwaa 0.1.13 → 0.1.15

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 +11 -3
  2. package/bin/chiwaa.js +71 -28
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -1,12 +1,20 @@
1
1
  # chiwaa
2
2
 
3
- [chiwaa.kr](https://chiwaa.kr)에 정적 사이트를 내 컴퓨터에서 바로 올리는 명령 도구입니다.
3
+ [chiwaa.kr](https://chiwaa.kr) — Vercel 처럼 쓰는 국내 배포 서비스의 CLI 입니다. 정적 사이트·프레임워크 빌드·서버 함수(`api/*.js`)·사이트 DB·파일 저장을 원화 정액으로.
4
4
 
5
5
  ```bash
6
- npx chiwaa deploy
6
+ npx chiwaa deploy # 운영
7
+ npx chiwaa deploy --preview # 미리보기 주소
7
8
  ```
8
9
 
9
- Claude Code·Codex·Cursor 에게 「chiwaa 에 올려 줘. 방법은 https://chiwaa.kr/llms.txt」라고 말하면 AI 가 알아서 합니다.
10
+ | Vercel | chiwaa |
11
+ |---|---|
12
+ | `vercel` / `vercel --prod` | `npx chiwaa deploy --preview` / `npx chiwaa deploy` |
13
+ | `vercel env add` | `npx chiwaa env set` |
14
+ | `vercel logs` · `vercel rollback` | `npx chiwaa logs` · `npx chiwaa rollback` |
15
+ | `vercel domains add` | `npx chiwaa domains add` |
16
+
17
+ AI 에이전트(Claude Code·Codex·Cursor)는 https://chiwaa.kr/llms.txt 를 읽고 배포합니다. CI 에서는 환경변수 `CHIWAA_TOKEN`.
10
18
 
11
19
  처음이면 브라우저에서 로그인을 승인하고 사이트 이름을 정합니다. 끝나면 `<이름>.chiwaa.com` 주소가 나옵니다.
12
20
  `package.json`에 `build`가 있으면 먼저 빌드하고 결과 폴더를 올립니다.
package/bin/chiwaa.js CHANGED
@@ -124,32 +124,65 @@ async function login(args) {
124
124
  console.log(c.green("✓"), `${me.data.user} 계정으로 로그인했습니다.`);
125
125
  return;
126
126
  }
127
- const s = await api("POST", "/api/v1/login", { auth: false });
128
- if (s.status !== 200) fail(s.data.error || "로그인을 시작하지 못했습니다.");
129
- console.log(`브라우저에서 아래 주소를 열고 코드를 확인한 뒤 ${c.bold("승인")}을 눌러 주세요.\n`);
130
- console.log(` ${c.cyan(s.data.verify_url)}`);
131
- console.log(` 코드: ${c.bold(s.data.user_code)}\n`);
132
- if (!args["no-browser"]) openBrowser(s.data.verify_url);
133
- const until = Date.now() + s.data.expires_in * 1000;
127
+ // 앞서 시작한 로그인이 아직 살아 있으면 새 코드를 만들지 않고 이어서 기다린다(AI 도구가 대기 중에 명령을 끊는 경우)
128
+ if (await resumePendingLogin()) return;
129
+ let s = readJSON(PENDING_FILE);
130
+ const resumed = !!s;
131
+ if (!s) {
132
+ const r = await api("POST", "/api/v1/login", { auth: false });
133
+ if (r.status !== 200) fail(r.data.error || "로그인을 시작하지 못했습니다.");
134
+ s = { ...r.data, until: Date.now() + r.data.expires_in * 1000 };
135
+ fs.mkdirSync(CONF_DIR, { recursive: true });
136
+ fs.writeFileSync(PENDING_FILE, JSON.stringify(s), { mode: 0o600 });
137
+ }
138
+ console.log(`브라우저에서 아래 주소를 열고 코드를 확인한 뒤 ${c.bold("승인")}을 눌러 주세요.${resumed ? c.dim(" (앞서 연 로그인을 이어서 기다립니다)") : ""}\n`);
139
+ console.log(` ${c.cyan(s.verify_url)}`);
140
+ console.log(` 코드: ${c.bold(s.user_code)}\n`);
141
+ console.log(c.dim(`승인할 때까지 이 명령이 기다립니다(최대 ${Math.ceil((s.until - Date.now()) / 60000)}분). 중간에 끊겼으면 같은 명령을 다시 실행하면 이어집니다 — 다시 승인할 필요 없음.`));
142
+ if (!args["no-browser"] && !resumed) openBrowser(s.verify_url);
134
143
  process.stdout.write(c.dim("승인을 기다리는 중…"));
135
- while (Date.now() < until) {
136
- await new Promise((r) => setTimeout(r, (s.data.interval || 3) * 1000));
137
- const p = await api("POST", "/api/v1/login/poll", { auth: false, body: { device_code: s.data.device_code } });
138
- if (p.status === 200 && p.data.token) {
139
- saveToken(p.data.token);
140
- process.stdout.write("\n");
141
- console.log(c.green("✓"), `${p.data.user} 계정으로 로그인했습니다.`);
142
- return;
143
- }
144
- if (p.status !== 202) {
145
- process.stdout.write("\n");
146
- fail(p.data.error || "로그인하지 못했습니다.");
147
- }
144
+ while (Date.now() < s.until) {
145
+ await new Promise((r) => setTimeout(r, (s.interval || 3) * 1000));
146
+ const done = await pollLogin(s);
147
+ if (done === "wait") continue;
148
+ process.stdout.write("\n");
149
+ if (done) return console.log(c.green("✓"), `${done} 계정으로 로그인했습니다.`);
150
+ fail("로그인하지 못했습니다. 다시 `chiwaa login` 해 주세요.");
148
151
  }
152
+ fs.rmSync(PENDING_FILE, { force: true });
149
153
  process.stdout.write("\n");
150
154
  fail("시간이 지났습니다. 다시 `chiwaa login` 해 주세요.");
151
155
  }
152
156
 
157
+ // 로그인 승인을 한 번 확인한다: 계정 이름(성공) · "wait"(아직) · null(끝남, 대기 파일 지움)
158
+ const PENDING_FILE = path.join(CONF_DIR, "login-pending.json");
159
+ async function pollLogin(s) {
160
+ const p = await api("POST", "/api/v1/login/poll", { auth: false, body: { device_code: s.device_code } });
161
+ if (p.status === 202) return "wait";
162
+ fs.rmSync(PENDING_FILE, { force: true });
163
+ if (p.status === 200 && p.data.token) {
164
+ saveToken(p.data.token);
165
+ return p.data.user || "내";
166
+ }
167
+ return null;
168
+ }
169
+
170
+ // 승인은 했는데 기다리던 명령이 끊긴 경우: 다음 명령에서 승인 결과를 받아 로그인을 마친다
171
+ async function resumePendingLogin() {
172
+ const s = readJSON(PENDING_FILE);
173
+ if (!s || token()) return false;
174
+ if (!(s.until > Date.now())) {
175
+ fs.rmSync(PENDING_FILE, { force: true });
176
+ return false;
177
+ }
178
+ const done = await pollLogin(s);
179
+ if (done && done !== "wait") {
180
+ console.log(c.green("✓"), `앞서 승인한 로그인을 이어받았습니다(${done} 계정).`);
181
+ return true;
182
+ }
183
+ return false;
184
+ }
185
+
153
186
  // ---------- tar.gz (의존성 없이 직접 만든다) ----------
154
187
  function tarHeader(name, size, mode, type, mtime) {
155
188
  const b = Buffer.alloc(512);
@@ -475,7 +508,7 @@ async function deploy(args) {
475
508
  const dir = path.resolve(args._[1] || ".");
476
509
  if (!fs.existsSync(dir) || !fs.statSync(dir).isDirectory()) fail(`폴더가 없습니다: ${dir}`);
477
510
  if (!token()) {
478
- if (!TTY) fail("로그인이 필요합니다. 먼저 아래 명령으로 로그인하고(브라우저에서 승인), 같은 배포 명령을 다시 실행하세요.\n\n npx chiwaa login\n\n (자동화·AI 도구에서는 환경변수 CHIWAA_TOKEN 으로도 됩니다)");
511
+ if (!TTY) fail("로그인이 필요합니다. 먼저 아래 명령으로 로그인하고(브라우저에서 승인), 같은 배포 명령을 다시 실행하세요.\n\n npx chiwaa login\n\n (login 은 승인할 때까지 기다리는 명령입니다. 끊겼으면 다시 실행하면 같은 승인을 이어서 기다립니다. 자동화에서는 환경변수 CHIWAA_TOKEN 으로도 됩니다)");
479
512
  await login(args);
480
513
  }
481
514
  const site = await resolveSite(dir, args);
@@ -569,7 +602,7 @@ function snippet(table, read, write) {
569
602
  <button type="submit">보내기</button>
570
603
  </form>
571
604
 
572
- <!-- 글 목록 보기(읽기 권한이 public 일 때) -->
605
+ ${read === "public" ? `<!-- 글 목록 보기 -->
573
606
  <ul id="${table}-list"></ul>
574
607
  <script>
575
608
  chiwaa.table("${table}").list({ limit: 20 }).then(function (items) {
@@ -582,7 +615,10 @@ function snippet(table, read, write) {
582
615
  });
583
616
  </script>
584
617
 
585
- <!-- 스크립트로 직접:
618
+ ` : `<!-- 읽기가 「나만」이라 받은 글을 사이트에 보여 주지 않습니다(목록을 붙이지 마세요).
619
+ 받은 글 보기: chiwaa.kr → 내 사이트 → 이 사이트 「자세히」 → 「DB」 → ${table} 줄의 「데이터 보기」, 또는 npx chiwaa db rows ${table} -->
620
+
621
+ `}<!-- 스크립트로 직접:
586
622
  chiwaa.table("${table}").add({ 제목, 내용, _pw }) 글쓰기(_pw 를 넣으면 그 비밀번호로 수정·삭제 가능)
587
623
  chiwaa.table("${table}").list({ limit: 20, before: 마지막id }) 목록(최신순, 다음 쪽은 before)
588
624
  chiwaa.table("${table}").get(id) 글 하나(상세 화면)
@@ -966,11 +1002,17 @@ async function whoami() {
966
1002
  console.log(`${r.data.user} · ${r.data.plan_name} 요금제 · 사이트 ${r.data.sites}/${r.data.sites_limit}`);
967
1003
  }
968
1004
 
969
- function logout() {
970
- try {
971
- fs.unlinkSync(CONF_FILE);
972
- } catch {}
973
- console.log("로그아웃했습니다.");
1005
+ // 이 컴퓨터의 로그인 토큰을 서버에서도 지운다(파일만 지우면 토큰이 서버에 살아 있으므로)
1006
+ async function logout() {
1007
+ fs.rmSync(PENDING_FILE, { force: true });
1008
+ const saved = (readJSON(CONF_FILE) || {}).token;
1009
+ if (saved) {
1010
+ const r = await api("POST", "/api/v1/logout", { headers: { Authorization: `Bearer ${saved}` }, auth: false }).catch(() => null);
1011
+ if (!r || (r.status !== 200 && r.status !== 401)) console.log(c.dim("서버의 토큰은 지우지 못했습니다. https://chiwaa.kr/tokens 에서 지워 주세요."));
1012
+ fs.rmSync(CONF_FILE, { force: true });
1013
+ }
1014
+ console.log(saved ? "로그아웃했습니다(서버의 토큰도 지움)." : "이 컴퓨터에는 로그인 정보가 없습니다.");
1015
+ if (process.env.CHIWAA_TOKEN) console.log(c.dim("환경변수 CHIWAA_TOKEN 의 토큰은 그대로입니다. 지우려면 https://chiwaa.kr/tokens"));
974
1016
  }
975
1017
 
976
1018
  const HELP = `chiwaa ${VERSION} — chiwaa.kr 에 사이트를 바로 올립니다
@@ -1019,6 +1061,7 @@ async function main() {
1019
1061
  if (args.help || !cmd || cmd === "help") return console.log(HELP);
1020
1062
  const cmds = { deploy, login, list, ls: list, versions, rollback, db, files, domains, domain: domains, stats, env, logs, lock, unlock, open, usage, agent, whoami, logout };
1021
1063
  if (!cmds[cmd]) fail(`모르는 명령입니다: ${cmd}\n\n${HELP}`);
1064
+ if (cmd !== "login" && cmd !== "logout") await resumePendingLogin();
1022
1065
  await cmds[cmd](args);
1023
1066
  }
1024
1067
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chiwaa",
3
- "version": "0.1.13",
3
+ "version": "0.1.15",
4
4
  "description": "chiwaa.kr 에 사이트를 터미널에서 바로 올리는 도구",
5
5
  "bin": {
6
6
  "chiwaa": "bin/chiwaa.js"