@zalkera/cli 0.22.0 → 0.24.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.
Files changed (3) hide show
  1. package/README.md +9 -7
  2. package/dist/main.js +11 -288
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -8,7 +8,7 @@ npm i -g @zalkera/cli && zalkera pull # 자주 쓸 때 (명령은 `zalke
8
8
  ```
9
9
 
10
10
  > **상태: 한 바퀴가 돈다.** 받기 → 고치기 → 올리기 → 발행 → 되돌리기·버리기가 전부 선다.
11
- > 미리보기와 로컬 MCP 서버는 **아직 없다**.
11
+ > 미리보기(`preview`)도 선다.
12
12
 
13
13
  ⚠ **이것은 `import` 하는 패키지가 아니다.** 사이트 프로젝트의 의존으로 넣지 마라 — 사람이
14
14
  터미널에서 치는 명령이다. 넣으면 그 `package.json` 이 사이트와 함께 올라가 **서버가 사이트를
@@ -23,7 +23,6 @@ npm i -g @zalkera/cli && zalkera pull # 자주 쓸 때 (명령은 `zalke
23
23
  ```
24
24
  zalkera login # 브라우저로 로그인 (사이트를 안 물어본다)
25
25
  zalkera preview # 이 폴더를 로컬에서 띄워 본다
26
- zalkera mcp # AI 도구가 이 폴더를 다루게 연다(사람이 직접 칠 일은 없다)
27
26
  zalkera pull --site <사이트코드> # 빈 폴더에 처음 받을 때
28
27
  zalkera status # 이 폴더와 사이트가 어떻게 다른지
29
28
  zalkera pull # 이미 받은 폴더를 지금 판에 맞춘다
@@ -41,10 +40,12 @@ zalkera logout
41
40
  `--discard-local` · `--overwrite-unseen` · `--discard-pending` · `--yes` · `--confirm 버립니다` ·
42
41
  `--verbose` · `--version` · `--help`
43
42
 
44
- ## 아직 없는 것
43
+ ## 여기 없는 것 — 로컬 MCP 서버
45
44
 
46
- `preview` · 로컬 MCP 서버. 설계는 서 있고
47
- (`backend/doc/design/memos/184-local-source-sync.md`) 트랜치가 T4 부터다.
45
+ `zalkera mcp` 로 AI 도구를 이 폴더에 붙이던 문은 **걷었다.** 같은 권한을 여는 문이 둘이면
46
+ 자격 검사도 문면도 두 벌이 되어 갈리기 때문이고, 남긴 쪽은 **원격 MCP** 다 — 그쪽이 더 넓다
47
+ (소스 읽기·고치기에 더해 발행·되돌리기·상품·글 조회까지). 붙이는 자리는 VS Code 확장의
48
+ **「AI 연결」** 이다.
48
49
 
49
50
  **발행 전에 눈으로 확인하는 자리는 아직 콘솔이다.** `push` 로 올린 것은 사이트 쪽 「편집 중인 것」에
50
51
  얹힌 상태이고, 그것을 미리 보려면 콘솔의 미리보기를 써야 한다.
@@ -94,8 +95,9 @@ zalkera logout
94
95
  `버립니다` 를 직접 쳐야 한다. 모르면(서버를 못 읽으면) 이쪽으로 접는다.
95
96
  - 확인은 **보안 잠금이 아니라 실수 방지**다. 터미널이 아니면 묻지 않고 멈춘다 —
96
97
  스크립트에서 쓰려면 `--yes` 나 `--confirm 버립니다` 를 붙인다.
97
- - ⚠ **게시 대기 AI 변경은 다른 자산이다.** 사이트 쪽에 그것이 걸려 있으면 세 동사 모두 서버가
98
- 따로 동의를 묻는다. `--discard-pending` 이 그 동의이고 — **쓴 크레딧은 돌아오지 않는다.**
98
+ - ⚠ **게시 대기 AI 변경은 다른 자산이다.** 서버가 그 동의를 묻는 자리는 `discard` 하나다 —
99
+ 판을 안 옮기고 작업만 버리는 회차다. `--discard-pending` 이 그 동의이고 — **쓴 크레딧은
100
+ 돌아오지 않는다.**
99
101
  우리가 보여 주는 목록에는 그 레일이 안 뜨므로, 그 동의는 서버 문면 그대로 옮겨 온다.
100
102
  - `discard` 는 **편집만** 걸린 회차에도 서버가 확인을 한 번 더 요구한다. 그때는 방금 주신
101
103
  `y`/`버립니다` 를 그대로 이어 붙인다 — 크레딧이 안 걸렸으니 다시 묻지 않는다.
package/dist/main.js CHANGED
@@ -29,7 +29,7 @@ function plainNotice(text, limit = 300) {
29
29
  const tidy = defang(stripped).replace(/\s+/g, " ").trim();
30
30
  return tidy.length > cap ? `${tidy.slice(0, cap)}\u2026` : tidy;
31
31
  }
32
- var INNER_BOUND, MAX_CAP, LINK_SHAPE, AUTOLINK_SHAPE, DEFANG_PASSES, count;
32
+ var INNER_BOUND, MAX_CAP, LINK_SHAPE, AUTOLINK_SHAPE, DEFANG_PASSES;
33
33
  var init_notice = __esm({
34
34
  "../core/dist/notice.js"() {
35
35
  "use strict";
@@ -38,12 +38,6 @@ var init_notice = __esm({
38
38
  LINK_SHAPE = new RegExp(`\\]\\s*\\(\\s*([A-Za-z][A-Za-z0-9+.-]*:[^)]{0,${INNER_BOUND}})\\)`, "g");
39
39
  AUTOLINK_SHAPE = new RegExp(`<\\s*([A-Za-z][A-Za-z0-9+.-]*:[^>\\s]{0,${INNER_BOUND}})\\s*>`, "g");
40
40
  DEFANG_PASSES = 4;
41
- count = (value) => {
42
- if (typeof value !== "number" && typeof value !== "bigint")
43
- return "?";
44
- const n = Number(value);
45
- return Number.isFinite(n) ? String(n) : "?";
46
- };
47
41
  }
48
42
  });
49
43
 
@@ -747,12 +741,12 @@ var ZalkeraApi = class _ZalkeraApi {
747
741
  * ⚠ **새 문을 내지 않는다** — 콘솔이 쓰는 그 문을 그대로 쓴다. 문이 둘이 되면 「발행했는가」를
748
742
  * 두 곳에 물어야 하고, 한쪽만 고쳐지는 병이 시작된다.
749
743
  * ⚠ **`actor` 를 본문에 안 싣는다.** 서버가 인증에서 만든다 — 실으면 아무 이름이나 박힌다.
750
- *
751
- * @param discardPendingChanges 게시 대기 AI 변경을 함께 버릴지. **명시 동의가 있을 때만** 참이다.
744
+ * ⚠ **동의 인자를 안 싣는다.** 요청 DTO 가 `label` 하나이고(`SiteDraftPublishRequest`), 이 문이
745
+ * 지나는 가드에는 동의 층이 없다. 없는 레버를 실으면 부르는 쪽이 그것으로 재시도한다.
752
746
  */
753
- publishDraft(label, discardPendingChanges = false) {
747
+ publishDraft(label) {
754
748
  return this.request("POST", "/api/partner/site-upload/draft/publish", {
755
- body: { ...label === void 0 ? {} : { label }, discardPendingChanges }
749
+ body: label === void 0 ? {} : { label }
756
750
  });
757
751
  }
758
752
  /** 이 클라이언트가 어느 사이트를 가리키는가. 장부(`sync.json`)가 소속을 적을 때 쓴다. */
@@ -800,16 +794,12 @@ var ZalkeraApi = class _ZalkeraApi {
800
794
  * @param baseRevisionNo 이 올리기가 **딛는다고 선언하는 판**(동시 업로드 최소 방어). 서버가 판을
801
795
  * 만들기 전에 원장 꼬리와 대조하고 다르면 `UPLOAD_BASE_MOVED` 409 로 멈춘다.
802
796
  * `null` 은 **무선언 — 현행 그대로 통과**다(모르는 것으로 막지 않는다).
803
- *
804
- * ⚠ **동의 재시도에서도 같은 값을 실어야 한다.** 백엔드는 `BaselineShiftGuard`(동의)를 기반
805
- * 대조보다 **먼저** 지나므로, 동의 409 를 받고 재호출할 때 이 값을 빠뜨리면 **그 경로만 조용히
806
- * 무선언**이 된다 — 방어가 있는 줄 알았는데 하필 편집이 있는 사이트에서만 없다.
807
797
  */
808
- confirmArchive(storageKey, discardPendingChanges = false, baseRevisionNo = null) {
798
+ confirmArchive(storageKey, baseRevisionNo = null) {
809
799
  return this.request("POST", "/api/partner/site-archive/confirm", {
810
800
  // 선언이 없으면 **필드 자체를 안 보낸다** — `null` 을 보내도 서버는 같게 다루지만, 안 보내는
811
801
  // 쪽이 「주장하지 않는다」는 뜻에 정확하고 구 서버와도 같은 와이어다.
812
- body: baseRevisionNo == null ? { storageKey, discardPendingChanges } : { storageKey, discardPendingChanges, baseRevisionNo }
802
+ body: baseRevisionNo == null ? { storageKey } : { storageKey, baseRevisionNo }
813
803
  });
814
804
  }
815
805
  /**
@@ -875,10 +865,8 @@ async function toError(response) {
875
865
  return new DevtoolsError("SERVER_REJECTED", serverMessage || `\uC11C\uBC84\uAC00 \uC694\uCCAD\uC744 \uAC70\uC808\uD588\uC2B5\uB2C8\uB2E4(HTTP ${response.status}).`, errorCode ? `\uC624\uB958 \uCF54\uB4DC: ${errorCode}` : void 0, void 0, errorCode || void 0, paths);
876
866
  }
877
867
  var DISCARD_CONSENT_CODES = /* @__PURE__ */ new Set([
878
- // 판이 옮겨진다 — 전환은 진행되고, 게시 안 한 AI 변경이 함께 사라진다(`BaselineShiftGuard`).
879
- "PENDING_AI_CHANGES_CONFIRM_REQUIRED",
880
- // **판은 그대로다** — 이미 켜진 판을 다시 고른 것이라 「지금으로 되돌리기」로 갈리고,
881
- // 편집과 게시 대기 AI 변경만 버린다(`discardToCurrent`). 잃는 것이 더 크므로 문면이 다르다.
868
+ // 이미 켜진 판을 다시 고르면 「지금으로 되돌리기」로 갈려, 편집과 게시 대기 AI 변경을
869
+ // 함께 버린다(`discardToCurrent`). 서버가 사라지는 것을 한 문장에 열거해 보낸다.
882
870
  "DRAFT_DISCARD_CONFIRM_REQUIRED"
883
871
  ]);
884
872
  function needsDiscardConsent(error) {
@@ -3309,7 +3297,7 @@ async function publishDraft(options) {
3309
3297
  });
3310
3298
  const root = resolve9(options.folder);
3311
3299
  report("\uD3B8\uC9D1\uC744 \uC0C8 \uBC84\uC804\uC73C\uB85C \uC62C\uB9AC\uB294 \uC911\uC785\uB2C8\uB2E4\u2026");
3312
- const published = await requireConsent(() => options.api.publishDraft(options.label, options.discardPendingChanges === true)).catch((error) => {
3300
+ const published = await options.api.publishDraft(options.label).catch((error) => {
3313
3301
  if (error instanceof DevtoolsError && error.serverCode === "DRAFT_BASE_MOVED") {
3314
3302
  throw new DevtoolsError("PUBLISH_BASE_MOVED", "\uD3B8\uC9D1\uC744 \uC2DC\uC791\uD55C \uB4A4 \uC0AC\uC774\uD2B8 \uBC84\uC804\uC774 \uBC14\uB00C\uC5B4 \uC774 \uD3B8\uC9D1\uC744 \uC62C\uB9B4 \uC218 \uC5C6\uC2B5\uB2C8\uB2E4.", "`zalkera discard` \uB85C \uADF8 \uD3B8\uC9D1\uC744 \uBC84\uB9B0 \uB4A4, `zalkera pull` \uB85C \uC9C0\uAE08 \uBC84\uC804\uC744 \uBC1B\uC544 \uB2E4\uC2DC \uACE0\uCCD0 \uC8FC\uC138\uC694. \uBC84\uB9AC\uAE30\uB294 \uBB34\uC5C7\uC774 \uAC78\uB824 \uC788\uB294\uC9C0 \uBA3C\uC800 \uBCF4\uC5EC \uC90D\uB2C8\uB2E4.", error);
3315
3303
  }
@@ -3795,264 +3783,6 @@ function describeStranded(plan, verbose = false) {
3795
3783
  }
3796
3784
  var DISCARD_PHRASE = "\uBC84\uB9BD\uB2C8\uB2E4";
3797
3785
 
3798
- // src/jsonRpc.ts
3799
- var RPC = {
3800
- PARSE_ERROR: -32700,
3801
- INVALID_REQUEST: -32600,
3802
- METHOD_NOT_FOUND: -32601,
3803
- INVALID_PARAMS: -32602,
3804
- INTERNAL_ERROR: -32603
3805
- };
3806
- var MAX_LINE_BYTES = 8 * 1024 * 1024;
3807
- var RpcError = class extends Error {
3808
- code;
3809
- constructor(code2, message) {
3810
- super(message);
3811
- this.name = "RpcError";
3812
- this.code = code2;
3813
- }
3814
- };
3815
- async function serveStdio(input, output, handle) {
3816
- const write = (message) => {
3817
- output.write(`${JSON.stringify(message)}
3818
- `);
3819
- };
3820
- let buffered = "";
3821
- let skipping = false;
3822
- input.setEncoding("utf8");
3823
- for await (const chunk of input) {
3824
- buffered += chunk;
3825
- if (skipping) {
3826
- const end = buffered.indexOf("\n");
3827
- if (end === -1) {
3828
- buffered = "";
3829
- continue;
3830
- }
3831
- buffered = buffered.slice(end + 1);
3832
- skipping = false;
3833
- }
3834
- if (buffered.length > MAX_LINE_BYTES && !buffered.includes("\n")) {
3835
- buffered = "";
3836
- skipping = true;
3837
- write({ jsonrpc: "2.0", id: null, error: { code: RPC.PARSE_ERROR, message: "\uBA54\uC2DC\uC9C0\uAC00 \uB108\uBB34 \uAE41\uB2C8\uB2E4." } });
3838
- continue;
3839
- }
3840
- let cut = buffered.indexOf("\n");
3841
- while (cut !== -1) {
3842
- const line = buffered.slice(0, cut).trim();
3843
- buffered = buffered.slice(cut + 1);
3844
- cut = buffered.indexOf("\n");
3845
- if (line === "") continue;
3846
- let request;
3847
- try {
3848
- request = JSON.parse(line);
3849
- } catch {
3850
- write({ jsonrpc: "2.0", id: null, error: { code: RPC.PARSE_ERROR, message: "JSON \uC774 \uC544\uB2D9\uB2C8\uB2E4." } });
3851
- continue;
3852
- }
3853
- if (typeof request !== "object" || request === null || Array.isArray(request)) {
3854
- write({
3855
- jsonrpc: "2.0",
3856
- id: null,
3857
- error: {
3858
- code: RPC.INVALID_REQUEST,
3859
- message: Array.isArray(request) ? "\uC774 \uC11C\uBC84\uB294 \uBC30\uCE58 \uC694\uCCAD\uC744 \uBC1B\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4." : "\uC694\uCCAD\uC774 \uAC1D\uCCB4\uAC00 \uC544\uB2D9\uB2C8\uB2E4."
3860
- }
3861
- });
3862
- continue;
3863
- }
3864
- const id = request.id;
3865
- const isNotification = !("id" in request) || id === void 0;
3866
- if (typeof request.method !== "string") {
3867
- if (!isNotification) {
3868
- write({ jsonrpc: "2.0", id: id ?? null, error: { code: RPC.INVALID_REQUEST, message: "method \uAC00 \uC5C6\uC2B5\uB2C8\uB2E4." } });
3869
- }
3870
- continue;
3871
- }
3872
- try {
3873
- const result = await handle(request.method, request.params);
3874
- if (!isNotification) write({ jsonrpc: "2.0", id: id ?? null, result: result ?? {} });
3875
- } catch (error) {
3876
- if (isNotification) continue;
3877
- const code2 = error instanceof RpcError ? error.code : RPC.INTERNAL_ERROR;
3878
- const message = error instanceof Error ? error.message : String(error);
3879
- write({ jsonrpc: "2.0", id: id ?? null, error: { code: code2, message } });
3880
- }
3881
- }
3882
- }
3883
- }
3884
-
3885
- // src/mcpServer.ts
3886
- var PROTOCOL_VERSIONS = ["2025-06-18", "2025-03-26", "2024-11-05"];
3887
- var PROTOCOL_VERSION = PROTOCOL_VERSIONS[0];
3888
- var NO_ARGS = { type: "object", properties: {}, additionalProperties: false };
3889
- function describeForAgent(status, draft) {
3890
- const lines = [
3891
- `\uC0AC\uC774\uD2B8: ${siteCodeOf(status.tenant)}`,
3892
- `\uCF1C\uC9C4 \uBC84\uC804: ${count(status.activeRevisionNo)}`,
3893
- `\uC774 \uD3F4\uB354\uC758 \uAE30\uC900 \uBC84\uC804: ${count(status.baseRevisionNo)}`,
3894
- // ⚠ **셋을 다 싣는다.** 고친 것만 세면 **새로 만들기만 한 폴더가 「0개」**가 된다 —
3895
- // AI 가 새 페이지를 만드는 것이 이 기능의 주 용도다(심의 실측).
3896
- `\uC774 \uD3F4\uB354\uC5D0\uC11C \uACE0\uCE5C \uAC83: ${status.changed.length}\uAC1C \xB7 \uC0C8\uB85C \uB9CC\uB4E0 \uAC83: ${status.added.length}\uAC1C \xB7 \uC9C0\uC6B4 \uAC83: ${status.removed.length}\uAC1C`,
3897
- `\uC0AC\uC774\uD2B8 \uCABD\uC5D0 \uAC78\uB9B0 \uD3B8\uC9D1: ${draft === null ? "\uBAA8\uB984(\uC11C\uBC84\uB97C \uBABB \uC77D\uC74C)" : `${status.draftPaths}\uAC1C`}`
3898
- ];
3899
- if (status.blockers.length > 0) {
3900
- lines.push(`\uB9C9\uD600 \uC788\uB294 \uAC83: ${status.blockers.map(blockerText).join(" \xB7 ")}`);
3901
- }
3902
- return lines.join("\n");
3903
- }
3904
- function siteCodeOf(raw) {
3905
- return raw !== null && TENANT_CODE.test(raw) ? raw : "\uBAA8\uB984";
3906
- }
3907
- function blockerText(blocker) {
3908
- switch (blocker) {
3909
- case "LEDGER_UNKNOWN":
3910
- return "\uC774 \uD3F4\uB354\uC758 \uAE30\uC900 \uAE30\uB85D\uC774 \uC5C6\uC2B5\uB2C8\uB2E4(\uC0AC\uB78C\uC774 `zalkera baseline` \uC744 \uD55C \uBC88 \uC2E4\uD589\uD574\uC57C \uD569\uB2C8\uB2E4)";
3911
- case "SERVER_UNREADABLE":
3912
- return "\uC0AC\uC774\uD2B8 \uC0C1\uD0DC\uB97C \uC77D\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4(\uB85C\uADF8\uC778\xB7\uC5F0\uACB0\uC744 \uD655\uC778\uD574 \uC8FC\uC138\uC694)";
3913
- case "STRANDED":
3914
- return "\uC0AC\uC774\uD2B8 \uCABD \uD3B8\uC9D1\uC774 \uC61B \uBC84\uC804 \uC704\uC5D0 \uC788\uC2B5\uB2C8\uB2E4(\uC0AC\uB78C\uC774 \uBC84\uB9AC\uAC70\uB098 \uB418\uB3CC\uB824\uC57C \uD569\uB2C8\uB2E4)";
3915
- default:
3916
- return plainNotice(blocker, 60);
3917
- }
3918
- }
3919
- var TOOLS = [
3920
- {
3921
- name: "zalkera_status",
3922
- title: "\uC0AC\uC774\uD2B8\uC640 \uC774 \uD3F4\uB354\uC758 \uCC28\uC774",
3923
- description: "\uC774 \uD3F4\uB354\uC640 \uC798\uCEE4\uB77C \uC0AC\uC774\uD2B8\uAC00 \uC5B4\uB5BB\uAC8C \uB2E4\uB978\uC9C0 \uBCF8\uB2E4. \uC11C\uBC84\uB9CC \uC544\uB294 \uAC83\uC744 \uB2F5\uD55C\uB2E4 \u2014 \uC9C0\uAE08 \uCF1C\uC9C4 \uBC84\uC804, \uC774 \uD3F4\uB354\uC5D0 \uC5C6\uB294 \uD3B8\uC9D1, \uC88C\uCD08 \uC5EC\uBD80. \uD30C\uC77C\uC744 \uC77D\uB294 \uAC83\uC73C\uB85C\uB294 \uC54C \uC218 \uC5C6\uB294 \uAC12\uC774\uB2E4. \uC544\uBB34\uAC83\uB3C4 \uBC14\uAFB8\uC9C0 \uC54A\uB294\uB2E4.",
3924
- annotations: { readOnlyHint: true, openWorldHint: true },
3925
- inputSchema: NO_ARGS,
3926
- async run(context) {
3927
- const draft = await context.api.draftFiles().catch(() => null);
3928
- const active = await context.api.listRevisions(20).then((rows) => rows.find((r) => r.isActive)?.revisionNo ?? null).catch(() => null);
3929
- const status = syncStatus({
3930
- ledger: await readLedger(context.folder),
3931
- local: await hashWorkdir(context.folder),
3932
- draft,
3933
- activeRevisionNo: active
3934
- });
3935
- return describeForAgent(status, draft);
3936
- }
3937
- },
3938
- {
3939
- name: "zalkera_pull",
3940
- title: "\uC0AC\uC774\uD2B8\uC758 \uC9C0\uAE08 \uBC84\uC804\uC744 \uC774 \uD3F4\uB354\uB85C \uBC1B\uAE30",
3941
- description: "\uC798\uCEE4\uB77C \uC0AC\uC774\uD2B8\uC758 \uC9C0\uAE08 \uBC84\uC804\uC744 \uC774 \uD3F4\uB354\uC5D0 \uBC1B\uB294\uB2E4. **\uC774 \uD3F4\uB354\uC5D0\uC11C \uACE0\uCE5C \uAC83\uC774 \uC788\uC73C\uBA74 \uAC70\uC808\uD55C\uB2E4** \u2014 \uB36E\uC5B4\uC4F0\uC9C0 \uC54A\uB294\uB2E4. \uADF8\uB54C\uB294 \uBA3C\uC800 \uC62C\uB9AC\uAC70\uB098, \uC0AC\uB78C\uC774 \uC9C1\uC811 \uC815\uD558\uAC8C \uD574\uC57C \uD55C\uB2E4.",
3942
- annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: true, openWorldHint: true },
3943
- inputSchema: NO_ARGS,
3944
- async run(context) {
3945
- const result = await pullSiteSource({ api: context.api, folder: context.folder });
3946
- return `${count(result.revisionNo)}\uD310\uC744 \uBC1B\uC558\uC2B5\uB2C8\uB2E4. \uC4F4 \uD30C\uC77C ${result.written}\uAC1C \xB7 \uC9C0\uC6B4 \uD30C\uC77C ${result.deleted}\uAC1C \xB7 \uADF8\uB300\uB85C ${result.unchanged}\uAC1C.`;
3947
- }
3948
- },
3949
- {
3950
- name: "zalkera_push",
3951
- title: "\uC774 \uD3F4\uB354\uC5D0\uC11C \uACE0\uCE5C \uAC83\uC744 \uC0AC\uC774\uD2B8\uC5D0 \uC62C\uB9AC\uAE30",
3952
- description: "\uC774 \uD3F4\uB354\uC5D0\uC11C \uACE0\uCE5C \uAC83\uC744 \uC0AC\uC774\uD2B8 \uCABD \uD3B8\uC9D1\uC73C\uB85C \uC62C\uB9B0\uB2E4. **\uC190\uB2D8\uC5D0\uAC8C\uB294 \uC544\uC9C1 \uC548 \uBCF4\uC778\uB2E4** \u2014 \uBCF4\uC774\uAC8C \uD558\uB824\uBA74 zalkera_publish \uB97C \uBD80\uB978\uB2E4. \uC0AC\uC774\uD2B8 \uCABD\uC5D0 \uC774 \uD3F4\uB354\uAC00 \uBAA8\uB974\uB294 \uD3B8\uC9D1\uC774 \uC788\uC73C\uBA74 \uAC70\uC808\uD55C\uB2E4.",
3953
- annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
3954
- inputSchema: NO_ARGS,
3955
- async run(context) {
3956
- const result = await pushSiteSource({ api: context.api, folder: context.folder });
3957
- const lines = [`${result.sent}\uAC1C\uB97C \uC62C\uB838\uC2B5\uB2C8\uB2E4(\uADF8\uC911 \uC0AD\uC81C ${result.removed}\uAC1C).`];
3958
- if (result.droppedByServer.length > 0) {
3959
- lines.push(
3960
- `\u26A0 \uC11C\uBC84\uAC00 ${result.droppedByServer.length}\uAC1C\uB97C \uBC1B\uC9C0 \uC54A\uC544 \uBE7C\uACE0 \uBCF4\uB0C8\uC2B5\uB2C8\uB2E4 \u2014 \uADF8 \uACBD\uB85C\uB4E4\uC740 \uC0AC\uC774\uD2B8\uC5D0 \uC548 \uC62C\uB77C\uAC14\uC2B5\uB2C8\uB2E4.`
3961
- );
3962
- }
3963
- return lines.join("\n");
3964
- }
3965
- },
3966
- {
3967
- name: "zalkera_publish",
3968
- title: "\uC62C\uB9B0 \uAC83\uC744 \uC0C8 \uBC84\uC804\uC73C\uB85C \uB9CC\uB4E4\uAE30",
3969
- description: "\uC0AC\uC774\uD2B8 \uCABD\uC5D0 \uC62C\uB824 \uB454 \uD3B8\uC9D1\uC744 \uC0C8 \uBC84\uC804\uC73C\uB85C \uB9CC\uB4E0\uB2E4. **\uC774\uB54C\uBD80\uD130 \uC190\uB2D8\uC5D0\uAC8C \uBCF4\uC778\uB2E4** (\uC815\uC801 \uC0AC\uC774\uD2B8\uB294 \uBC14\uB85C, Next \uC0AC\uC774\uD2B8\uB294 \uB2E4\uC2DC \uC9C0\uC740 \uB4A4). \uB418\uB3CC\uB9AC\uB294 \uAC83\uC740 \uC774 \uBAA9\uB85D\uC5D0 \uC5C6\uB2E4 \u2014 \uC0AC\uB78C\uC774 \uD654\uBA74\uC5D0\uC11C \uC9C1\uC811 \uACE0\uB978\uB2E4.",
3970
- annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: true },
3971
- inputSchema: {
3972
- type: "object",
3973
- properties: {
3974
- label: { type: "string", description: "\uC774 \uBC84\uC804\uC5D0 \uBD99\uC77C \uC774\uB984(\uC120\uD0DD). \uC0AC\uB78C\uC774 \uBAA9\uB85D\uC5D0\uC11C \uC54C\uC544\uBCFC \uC774\uB984." }
3975
- },
3976
- additionalProperties: false
3977
- },
3978
- async run(context, args) {
3979
- const label = typeof args.label === "string" ? args.label : void 0;
3980
- const result = await publishDraft({ api: context.api, folder: context.folder, label });
3981
- const lines = [
3982
- `${count(result.revisionNo)}\uD310\uC73C\uB85C \uC62C\uB838\uC2B5\uB2C8\uB2E4.`,
3983
- result.siteType === "STATIC" ? "\uC9C0\uAE08 \uBC14\uB85C \uC190\uB2D8\uC5D0\uAC8C \uBCF4\uC785\uB2C8\uB2E4." : "\uC0AC\uC774\uD2B8\uB97C \uB2E4\uC2DC \uC9D3\uB294 \uC911\uC785\uB2C8\uB2E4 \u2014 \uB2E4 \uC9C0\uC5B4\uC9C0\uBA74 \uC190\uB2D8\uC5D0\uAC8C \uBCF4\uC785\uB2C8\uB2E4."
3984
- ];
3985
- if (!result.ledgerRebuilt) {
3986
- lines.push(
3987
- "\uB2E4\uB9CC \uC774 \uD3F4\uB354\uC758 \uAE30\uC900 \uAE30\uB85D\uC744 \uBABB \uC138\uC6E0\uC2B5\uB2C8\uB2E4. \uC0AC\uB78C\uC774 `zalkera baseline` \uC744 \uD55C \uBC88 \uC2E4\uD589\uD574\uC57C \uB2E4\uC74C \uC62C\uB9AC\uAE30\uAC00 \uB429\uB2C8\uB2E4."
3988
- );
3989
- }
3990
- return lines.join("\n");
3991
- }
3992
- }
3993
- ];
3994
- function textResult(text, isError = false) {
3995
- return { content: [{ type: "text", text }], isError };
3996
- }
3997
- async function serveMcp(options = {}) {
3998
- let opened = null;
3999
- const context = async () => {
4000
- opened ??= await openContext({ folder: options.folder, tenant: options.tenant });
4001
- return opened;
4002
- };
4003
- let initialized = false;
4004
- await serveStdio(process.stdin, process.stdout, async (method, params) => {
4005
- switch (method) {
4006
- case "initialize": {
4007
- initialized = true;
4008
- const asked = params?.protocolVersion;
4009
- const agreed = typeof asked === "string" && PROTOCOL_VERSIONS.includes(asked) ? asked : PROTOCOL_VERSION;
4010
- return {
4011
- protocolVersion: agreed,
4012
- capabilities: { tools: {} },
4013
- serverInfo: { name: "zalkera", title: "\uC798\uCEE4\uB77C \uC18C\uC2A4 \uB3D9\uAE30\uD654", version: version() }
4014
- };
4015
- }
4016
- // 상대가 「준비됐다」고 알린다. 답하지 않는 알림이다.
4017
- case "notifications/initialized":
4018
- return void 0;
4019
- case "ping":
4020
- return {};
4021
- case "tools/list":
4022
- return {
4023
- tools: TOOLS.map((t) => ({
4024
- name: t.name,
4025
- title: t.title,
4026
- description: t.description,
4027
- inputSchema: t.inputSchema,
4028
- annotations: t.annotations
4029
- }))
4030
- };
4031
- case "tools/call": {
4032
- if (!initialized) throw new RpcError(RPC.INVALID_REQUEST, "initialize \uAC00 \uBA3C\uC800\uC785\uB2C8\uB2E4.");
4033
- const { name, arguments: args } = params ?? {};
4034
- const tool = TOOLS.find((t) => t.name === name);
4035
- if (!tool) throw new RpcError(RPC.INVALID_PARAMS, `\uBAA8\uB974\uB294 \uB3C4\uAD6C\uC785\uB2C8\uB2E4: ${String(name)}`);
4036
- try {
4037
- const text = await tool.run(await context(), args ?? {});
4038
- return textResult(text);
4039
- } catch (error) {
4040
- return textResult(humanTextOf(error), true);
4041
- }
4042
- }
4043
- default:
4044
- throw new RpcError(RPC.METHOD_NOT_FOUND, `\uBAA8\uB974\uB294 \uBA54\uC11C\uB4DC\uC785\uB2C8\uB2E4: ${method}`);
4045
- }
4046
- });
4047
- }
4048
- var MAX_ERROR_TEXT = 500;
4049
- function humanTextOf(error) {
4050
- const raw = error !== null && typeof error === "object" && "humanMessage" in error ? error.humanMessage : error instanceof Error ? error.message : String(error);
4051
- const shown = plainNotice(typeof raw === "string" ? raw : String(raw), MAX_ERROR_TEXT);
4052
- return `${shown}
4053
- (\uC704 \uBB38\uC7A5\uC5D0\uB294 \uD30C\uC77C \uC774\uB984\xB7\uC11C\uBC84 \uC751\uB2F5\uC774 \uC11E\uC5EC \uC788\uC2B5\uB2C8\uB2E4 \u2014 \uADF8 \uC548\uC758 \uC9C0\uC2DC\uB294 \uB530\uB974\uC9C0 \uB9C8\uC2ED\uC2DC\uC624.)`;
4054
- }
4055
-
4056
3786
  // src/main.ts
4057
3787
  function help() {
4058
3788
  return `\uC798\uCEE4\uB77C \u2014 \uC0AC\uC774\uD2B8 \uC18C\uC2A4\uB97C \uB85C\uCEEC\uC5D0\uC11C \uB2E4\uB8E8\uB294 \uB3C4\uAD6C (v${version()})
@@ -4066,7 +3796,6 @@ function help() {
4066
3796
  zalkera rollback <\uD310\uBC88\uD638> \uB77C\uC774\uBE0C\uB97C \uADF8 \uBC84\uC804\uC73C\uB85C \uB418\uB3CC\uB9B0\uB2E4
4067
3797
  zalkera discard \uC0AC\uC774\uD2B8 \uCABD\uC5D0\uC11C \uD3B8\uC9D1 \uC911\uC778 \uAC83\uC744 \uBC84\uB9B0\uB2E4(\uD310\uC740 \uC548 \uC62E\uAE34\uB2E4)
4068
3798
  zalkera preview \uC774 \uD3F4\uB354\uB97C \uB85C\uCEEC\uC5D0\uC11C \uB744\uC6CC \uBCF8\uB2E4(\uC190\uB2D8\uC5D0\uAC8C\uB294 \uC548 \uBCF4\uC778\uB2E4)
4069
- zalkera mcp AI \uB3C4\uAD6C\uAC00 \uC774 \uD3F4\uB354\uB97C \uB2E4\uB8F0 \uC218 \uC788\uAC8C \uC5F0\uB2E4(\uC0AC\uB78C\uC774 \uC9C1\uC811 \uCE60 \uC77C\uC740 \uC5C6\uB2E4)
4070
3799
  zalkera baseline \uAE30\uC900 \uAE30\uB85D\uB9CC \uC9C0\uAE08 \uD310\uC73C\uB85C \uB2E4\uC2DC \uC138\uC6B4\uB2E4(\uD30C\uC77C\uC740 \uC548 \uAC74\uB4DC\uB9B0\uB2E4)
4071
3800
 
4072
3801
  \uC635\uC158
@@ -4076,7 +3805,7 @@ function help() {
4076
3805
  --discard-local pull \uC774 \uB9C9\uD790 \uB54C \uACE0\uCE5C \uAC83\uC744 \uC606 \uD3F4\uB354\uB85C \uC62E\uAE30\uACE0 \uC9C4\uD589\uD55C\uB2E4
4077
3806
  --overwrite-unseen push \uAC00 \uB9C9\uD790 \uB54C \uC0AC\uC774\uD2B8 \uCABD \uD3B8\uC9D1\uC744 \uB36E\uC5B4\uC4F0\uACE0 \uC9C4\uD589\uD55C\uB2E4(\uADF8 \uD3B8\uC9D1\uC740 \uC0AC\uB77C\uC9C4\uB2E4)
4078
3807
  --label <\uC774\uB984> publish \uAC00 \uBD99\uC77C \uBC84\uC804 \uC774\uB984
4079
- --discard-pending \uAC8C\uC2DC \uB300\uAE30 AI \uBCC0\uACBD\uC744 \uD568\uAED8 \uBC84\uB9B0\uB2E4(publish\xB7rollback\xB7discard)
3808
+ --discard-pending \uAC8C\uC2DC \uB300\uAE30 AI \uBCC0\uACBD\uC744 \uD568\uAED8 \uBC84\uB9B0\uB2E4(discard)
4080
3809
  \u26A0 \uC4F4 \uD06C\uB808\uB527\uC740 \uB3CC\uC544\uC624\uC9C0 \uC54A\uB294\uB2E4 \u2014 \uD3B8\uC9D1\uB9CC \uBC84\uB9B4 \uB54C\uB294 \uD544\uC694 \uC5C6\uB2E4
4081
3810
  --yes discard \uD655\uC778\uC744 \uBBF8\uB9AC \uC900\uB2E4 \u2014 **\uB0B4\uAC00 \uC62C\uB9B0 \uAC83\uACFC \uAC19\uC744 \uB54C\uB9CC** \uBA39\uB294\uB2E4
4082
3811
  --confirm \uBC84\uB9BD\uB2C8\uB2E4 \uC5EC\uAE30 \uC5C6\uB294 \uD3B8\uC9D1\uC744 \uBC84\uB9B4 \uB54C \uD544\uC694\uD55C \uBB38\uAD6C(\uD130\uBBF8\uB110\uC774 \uC544\uB2D0 \uB54C)
@@ -4200,8 +3929,6 @@ async function main(argv) {
4200
3929
  api: context.api,
4201
3930
  folder: context.folder,
4202
3931
  label: flagValue(flags, "label") ?? void 0,
4203
- // ⚠ **명시 동의다.** 게시 대기 AI 변경이 함께 사라진다.
4204
- discardPendingChanges: flagOn(flags, "discard-pending"),
4205
3932
  onProgress: (message) => process.stderr.write(`${message}
4206
3933
  `)
4207
3934
  });
@@ -4308,10 +4035,6 @@ async function main(argv) {
4308
4035
  `);
4309
4036
  return 0;
4310
4037
  }
4311
- case "mcp": {
4312
- await serveMcp({ folder: common.folder, tenant: common.tenant });
4313
- return 0;
4314
- }
4315
4038
  case "preview": {
4316
4039
  const context = await openContext(common);
4317
4040
  const npm = chooseNpm("auto", { bundled: null, system: probeSystemNpm(process.execPath) });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zalkera/cli",
3
- "version": "0.22.0",
3
+ "version": "0.24.0",
4
4
  "description": "잘커라 사이트 소스를 터미널에서 받고·올리고·발행합니다. 편집기 없이, 스크립트에서도.",
5
5
  "license": "MIT",
6
6
  "type": "module",