democut 0.3.0 → 0.4.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 +28 -11
  2. package/dist/cli.js +324 -29
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -80,7 +80,7 @@ democut video status <job_id> [--wait] [--output <경로>]
80
80
  - **생성이 성공하면 과금된다** — 서버는 성공을 처음 관측한 시점에 원장에 기록한다(폴링을 반복해도 한 번).
81
81
  실패한 생성은 원장에 남지 않지만, 제출이 업스트림에 접수된 뒤의 재시도는 별개 생성이라 다시 과금된다.
82
82
  - **산출물 URL 은 24시간 후 만료된다** — `--output` 으로 즉시 받아두지 않으면 재생성해야 한다(재과금).
83
- - **CLI 는 제출을 자동 재시도하지 않는다** — BytePlus 멱등키(Idempotency-Key)를 지원하지 않아 재시도가 곧 재과금이다.
83
+ - **CLI 는 제출을 자동 재시도하지 않는다** — 공급자가 멱등키(Idempotency-Key)를 지원하지 않아 재시도가 곧 재과금이다.
84
84
  실패해 보여도 먼저 `democut video status <job_id>` 로 확인할 것.
85
85
  - **`--wait` 시한 초과는 실패가 아니다** — 작업은 서버에서 계속 진행된다(종료 코드 5). 새로 제출하지 말고 `status` 로 확인한다.
86
86
 
@@ -123,9 +123,15 @@ npm run build # dist/cli.js (bin: democut) 번들
123
123
  에이전트가 도구로 직접 영상을 만든다.
124
124
 
125
125
  ```bash
126
- claude mcp add democut -- democut mcp
126
+ claude mcp add democut -- npx -y democut mcp
127
127
  ```
128
128
 
129
+ `npx -y` 를 붙이는 이유는 전역 설치가 **연결의 선행 조건이 되지 않게** 하기 위해서다.
130
+ 설치가 빠지면 등록은 성립하는데(등록은 바이너리 존재를 검사하지 않는다) 연결만 실패하고,
131
+ 그 실패는 클라이언트의 `Failed to reconnect …` 한 줄로만 드러난다 — 서버가 뜨지 못하므로
132
+ 진단을 낼 주체가 없다. 자격증명은 패키지가 아니라 `~/.democut/credentials.json` 에 있어
133
+ 실행 방식을 바꿔도 로그인 상태는 그대로다. 전역 설치는 선택이다.
134
+
129
135
  노출 도구 4개:
130
136
 
131
137
  | 도구 | 부작용 |
@@ -145,18 +151,29 @@ CLI 는 TTY 에 `y/N` 을 물어 사람을 막지만 MCP 에는 물어볼 화면
145
151
  보내야 하고, 서버가 방금 계산한 값과 대조해 어긋나면 거부한다. 모델이 추정을 건너뛰고
146
152
  바로 생성하는 것을 막고, 사람에게 보여준 금액과 실제 청구액이 갈라지지 않게 한다.
147
153
 
148
- ### 원격 HTTP 아니라 stdio 인가
154
+ ### stdio 원격 HTTP 대체가 아니라 병존
155
+
156
+ **stdio(이 CLI)가 기본이다.** 이 프로세스가 `~/.democut/credentials.json` 을 읽고 만료 시
157
+ refresh 로 회전하므로 **클라이언트 설정 파일에 토큰이 남지 않는다.** 그 장점은 원격이
158
+ 생겨도 사라지지 않는다.
159
+
160
+ **원격 HTTP 도 이미 있다** — `https://democut.ai/mcp` (2026-08-25 배포). 로컬 바이너리를 못
161
+ 띄우는 클라이언트용이다:
149
162
 
150
- 원격 MCP 서버로 만들면 클라이언트가 OAuth 2.1 discovery(RFC 9728 + RFC 8414 + PKCE +
151
- 동적 클라이언트 등록)를 요구한다. 우리가 가진 것은 device flow(RFC 8628)라 그 체인을 새로
152
- 쌓아야 하고, 정적 Bearer 를 클라이언트 설정에 박는 우회는 **access token 이 1시간이면
153
- 만료**돼 곧 깨진다.
163
+ ```bash
164
+ claude mcp add --transport http --client-id <client_id> --callback-port <포트> \
165
+ democut https://democut.ai/mcp
166
+ ```
154
167
 
155
- stdio 문제가 통째로 사라진다 프로세스가 이미 `~/.democut/credentials.json`
156
- 읽고 만료 refresh 회전한다. **클라이언트 설정에 토큰이 남지 않고**, 서버에 새 인증
157
- 표면을 만들지 않으며, CLI 이미 검증해 둔 경로를 그대로 쓴다.
168
+ `--client-id` `--callback-port` 필요한 이유는 서버 설계에서 온다 동적 클라이언트
169
+ 등록(DCR)을 **일부러 두고**, Clerk loopback redirect 포트를 정확 일치로 요구하기
170
+ 때문이다. 근거는 `apps/api/app/mcp/auth.py` docstring SSOT.
158
171
 
159
- 원격 HTTP 로컬 바이너리를 돌리는 클라이언트를 위해 나중에 별도로 얹는다.
172
+ > ⚠️ 문단은 원래 "원격은 동적 클라이언트 등록을 요구해서 체인을 새로 쌓아야 한다" 고
173
+ > 적혀 있었는데 **틀렸다.** DCR 은 MCP 명세에서 MUST 가 아니라 SHOULD 이고, 무엇보다 우리
174
+ > 인증 공급자(Clerk)가 이미 완전한 OAuth 2.1 인가서버라 **우리가 만든 AS 는 0줄**이다.
175
+ > 오류의 모양이 재발 포인트라 남긴다 — *우리 코드*만 재고 조사하고 **이미 구입한 SaaS
176
+ > 기능은 세지 않았다.**
160
177
 
161
178
  ## 게시 (npm publish)
162
179
 
package/dist/cli.js CHANGED
@@ -6885,8 +6885,8 @@ var require_dist = __commonJS({
6885
6885
  return ajv;
6886
6886
  }
6887
6887
  const [formats, exportName] = opts.mode === "fast" ? [formats_1.fastFormats, fastName] : [formats_1.fullFormats, fullName];
6888
- const list = opts.formats || formats_1.formatNames;
6889
- addFormats(ajv, list, formats, exportName);
6888
+ const list2 = opts.formats || formats_1.formatNames;
6889
+ addFormats(ajv, list2, formats, exportName);
6890
6890
  if (opts.keywords)
6891
6891
  (0, limit_1.default)(ajv);
6892
6892
  return ajv;
@@ -6898,11 +6898,11 @@ var require_dist = __commonJS({
6898
6898
  throw new Error(`Unknown format "${name}"`);
6899
6899
  return f;
6900
6900
  };
6901
- function addFormats(ajv, list, fs, exportName) {
6901
+ function addFormats(ajv, list2, fs, exportName) {
6902
6902
  var _a3;
6903
6903
  var _b;
6904
6904
  (_a3 = (_b = ajv.opts.code).formats) !== null && _a3 !== void 0 ? _a3 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
6905
- for (const f of list)
6905
+ for (const f of list2)
6906
6906
  ajv.addFormat(f, fs[f]);
6907
6907
  }
6908
6908
  module.exports = exports = formatsPlugin;
@@ -7362,6 +7362,16 @@ function classifyApiError(e, ctx) {
7362
7362
  ]
7363
7363
  };
7364
7364
  }
7365
+ if (e.status === 404) {
7366
+ const lines = [];
7367
+ if (e.code === "VIDEOGEN_PROJECT_NOT_FOUND") {
7368
+ lines.push(
7369
+ "democut_list_projects \uB85C \uC774 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uD504\uB85C\uC81D\uD2B8 \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uACE0 \uADF8\uC911 \uD558\uB098\uC758 \uC2AC\uB7EC\uADF8\uB97C \uC4F0\uC138\uC694. \uBAA9\uB85D\uC774 \uBE44\uC5B4 \uC788\uC73C\uBA74 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uD504\uB85C\uC81D\uD2B8\uB97C \uBA3C\uC800 \uB9CC\uB4E4\uC5B4 \uB2EC\uB77C\uACE0 \uC54C\uB9AC\uC138\uC694."
7370
+ );
7371
+ }
7372
+ lines.push("\uC544\uC9C1 \uACFC\uAE08\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4 \u2014 \uAC12\uC744 \uACE0\uCCD0 \uB2E4\uC2DC \uC2DC\uB3C4\uD558\uC138\uC694.");
7373
+ return { safety: "fixable", notCharged: true, lines };
7374
+ }
7365
7375
  if (e.status === 403) {
7366
7376
  return {
7367
7377
  safety: "unknown",
@@ -7437,13 +7447,14 @@ async function download(videoUrl, outPath, fetchImpl = fetch) {
7437
7447
  const res = await fetchImpl(videoUrl);
7438
7448
  if (!res.ok) {
7439
7449
  throw new Error(
7440
- `\uC0B0\uCD9C\uBB3C \uB0B4\uB824\uBC1B\uAE30 \uC2E4\uD328 (HTTP ${res.status}). \uC11C\uBA85 URL \uC740 24\uC2DC\uAC04 \uD6C4 \uB9CC\uB8CC\uB429\uB2C8\uB2E4.`
7450
+ `\uC0B0\uCD9C\uBB3C \uB0B4\uB824\uBC1B\uAE30 \uC2E4\uD328 (HTTP ${res.status}).`
7441
7451
  );
7442
7452
  }
7443
7453
  await writeFile(outPath, Buffer.from(await res.arrayBuffer()));
7444
7454
  }
7445
7455
 
7446
7456
  // src/commands/video.ts
7457
+ var MOCK_NOTICE = "\u26A0\uFE0F mock \uBAA8\uB4DC \u2014 \uC774 \uBC30\uD3EC\uB294 \uC601\uC0C1\uC744 \uC2E4\uC81C\uB85C \uC0DD\uC131\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 (API \uD0A4 \uBBF8\uC124\uC815). \uC544\uB798 \uAE08\uC561\xB7job id\xB7URL \uC740 \uC804\uBD80 \uAC00\uC9DC\uC774\uBA70 \uACFC\uAE08\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.";
7447
7458
  var EXIT = {
7448
7459
  OK: 0,
7449
7460
  /** 일반 오류 — 미로그인·사용자 취소·생성 실패·알 수 없음. */
@@ -7550,8 +7561,9 @@ function estimateCommand(opts, deps = {}) {
7550
7561
  const base = resolveBaseUrl(opts.baseUrl);
7551
7562
  const r = await estimate(base, buildEstimateIn(opts), deps);
7552
7563
  if (opts.json) {
7553
- emit(opts, true, { tokens: r.tokens, cost_usd: r.cost_usd, model: r.model });
7564
+ emit(opts, true, { tokens: r.tokens, cost_usd: r.cost_usd, model: r.model, mock: r.mock });
7554
7565
  } else {
7566
+ if (r.mock) err2(MOCK_NOTICE);
7555
7567
  out2(`\uC608\uC0C1 \uBE44\uC6A9: ${usd2(r.cost_usd)} (${r.tokens.toLocaleString()} tokens \xB7 ${r.model})`);
7556
7568
  }
7557
7569
  return EXIT.OK;
@@ -7562,10 +7574,16 @@ function generateCommand(prompt, opts, deps = {}) {
7562
7574
  if (!prompt || !prompt.trim()) {
7563
7575
  throw new UsageError('\uD504\uB86C\uD504\uD2B8\uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. \uC608: democut video generate "\uACE0\uC591\uC774\uAC00 \uCDA4\uCD98\uB2E4"');
7564
7576
  }
7577
+ if (!opts.project || !opts.project.trim()) {
7578
+ throw new UsageError(
7579
+ "--project <slug> \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. \uBE44\uC6A9\uC774 \uADC0\uC18D\uB420 \uBCF8\uC778 \uC18C\uC720 \uD504\uB85C\uC81D\uD2B8\uB97C \uC9C0\uC815\uD558\uC138\uC694. \uC2AC\uB7EC\uADF8\uB294 \uC6F9\uC758 \uD504\uB85C\uC81D\uD2B8 \uC8FC\uC18C `/projects/<\uC2AC\uB7EC\uADF8>` \uC5D0\uC11C \uD655\uC778\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."
7580
+ );
7581
+ }
7565
7582
  const base = resolveBaseUrl(opts.baseUrl);
7566
7583
  const est = buildEstimateIn(opts);
7567
7584
  const quote = await estimate(base, est, deps);
7568
7585
  if (!opts.json) {
7586
+ if (quote.mock) err2(MOCK_NOTICE);
7569
7587
  out2(`\uC608\uC0C1 \uBE44\uC6A9: ${usd2(quote.cost_usd)} (${quote.tokens.toLocaleString()} tokens \xB7 ${quote.model})`);
7570
7588
  out2(` ${est.duration_s}\uCD08 \xB7 ${est.resolution} \xB7 ${est.ratio}`);
7571
7589
  }
@@ -7591,7 +7609,8 @@ function generateCommand(prompt, opts, deps = {}) {
7591
7609
  return EXIT.FIXABLE;
7592
7610
  }
7593
7611
  const ask = deps.confirmImpl ?? confirm;
7594
- if (!await ask(`${usd2(quote.cost_usd)} \uAC00 \uCCAD\uAD6C\uB429\uB2C8\uB2E4. \uC0DD\uC131\uD560\uAE4C\uC694?`)) {
7612
+ const question = quote.mock ? "mock \uBAA8\uB4DC\uC785\uB2C8\uB2E4 \u2014 \uC2E4\uC81C \uC601\uC0C1\uC740 \uC0DD\uC131\uB418\uC9C0 \uC54A\uACE0 \uACFC\uAE08\uB3C4 \uC5C6\uC2B5\uB2C8\uB2E4. \uADF8\uB798\uB3C4 \uC9C4\uD589\uD560\uAE4C\uC694?" : `${usd2(quote.cost_usd)} \uAC00 \uCCAD\uAD6C\uB429\uB2C8\uB2E4. \uC0DD\uC131\uD560\uAE4C\uC694?`;
7613
+ if (!await ask(question)) {
7595
7614
  out2("\uCDE8\uC18C\uD588\uC2B5\uB2C8\uB2E4. \uACFC\uAE08\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.");
7596
7615
  return EXIT.GENERAL;
7597
7616
  }
@@ -7606,7 +7625,7 @@ function generateCommand(prompt, opts, deps = {}) {
7606
7625
  ...opts.model ? { model: opts.model } : {},
7607
7626
  ...opts.seed !== void 0 ? { seed: parseIntOpt(opts.seed, "seed") } : {},
7608
7627
  ...opts.imageUrls?.length ? { image_urls: opts.imageUrls } : {},
7609
- ...opts.project ? { project_slug: opts.project } : {}
7628
+ project_slug: opts.project
7610
7629
  };
7611
7630
  const job = await submit(base, body, deps);
7612
7631
  if (job.duplicate) {
@@ -7618,6 +7637,7 @@ function generateCommand(prompt, opts, deps = {}) {
7618
7637
  }
7619
7638
  if (!opts.json) out2(`\uAC19\uC740 \uC694\uCCAD\uC774 \uC774\uBBF8 \uC9C4\uD589 \uC911\uC785\uB2C8\uB2E4 (job ${job.job_id}). \uC0C8\uB85C \uACFC\uAE08\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.`);
7620
7639
  } else if (!opts.json) {
7640
+ if (job.mock) err2(MOCK_NOTICE);
7621
7641
  out2(`\uC81C\uCD9C\uB428: job ${job.job_id} (\uC608\uC0C1 ${usd2(job.estimated_cost_usd)})`);
7622
7642
  }
7623
7643
  if (!opts.wait) {
@@ -7628,11 +7648,11 @@ function generateCommand(prompt, opts, deps = {}) {
7628
7648
  model: job.model,
7629
7649
  estimated_cost_usd: job.estimated_cost_usd,
7630
7650
  duplicate: job.duplicate,
7631
- pending: job.pending
7651
+ pending: job.pending,
7652
+ mock: job.mock
7632
7653
  });
7633
7654
  } else {
7634
- const projectFlag = opts.project ? ` --project ${opts.project}` : "";
7635
- out2(`\uC644\uB8CC \uD655\uC778: democut video status ${job.job_id}${projectFlag}`);
7655
+ out2(`\uC644\uB8CC \uD655\uC778: democut video status ${job.job_id}`);
7636
7656
  }
7637
7657
  return EXIT.OK;
7638
7658
  }
@@ -7669,6 +7689,24 @@ async function report(job, opts, deps = {}) {
7669
7689
  }
7670
7690
  return EXIT.GENERAL;
7671
7691
  }
7692
+ if (job.mock) {
7693
+ if (opts.json) {
7694
+ emit(opts, true, {
7695
+ job_id: job.job_id,
7696
+ status: job.status,
7697
+ model: job.model,
7698
+ video_url: job.video_url ?? null,
7699
+ saved_to: null,
7700
+ mock: true
7701
+ });
7702
+ } else {
7703
+ err2(MOCK_NOTICE);
7704
+ out2(`\uC0C1\uD0DC: ${job.status} (\uAC00\uC9DC)`);
7705
+ if (job.video_url) out2(` URL : ${job.video_url} \u2190 \uC2E4\uC7AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4`);
7706
+ out2(" \uB0B4\uB824\uBC1B\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. \uC2E4\uC81C \uC0DD\uC131\uC744 \uD558\uB824\uBA74 \uC6B4\uC601\uC790\uC5D0\uAC8C API \uD0A4 \uC124\uC815\uC744 \uC694\uCCAD\uD558\uC138\uC694.");
7707
+ }
7708
+ return EXIT.OK;
7709
+ }
7672
7710
  let saved = null;
7673
7711
  let downloadError = null;
7674
7712
  if (job.video_url && opts.output) {
@@ -7699,9 +7737,9 @@ async function report(job, opts, deps = {}) {
7699
7737
  out2(` URL : ${job.video_url}`);
7700
7738
  if (downloadError) {
7701
7739
  err2(` \uB0B4\uB824\uBC1B\uAE30 \uC2E4\uD328: ${downloadError}`);
7702
- err2(" \u26A0\uFE0F \uC704 URL \uB85C \uC9C1\uC811 \uBC1B\uC73C\uC138\uC694 \u2014 24\uC2DC\uAC04 \uD6C4 \uB9CC\uB8CC\uB418\uBA70, \uB2E4\uC2DC \uB9CC\uB4E4\uBA74 \uB610 \uACFC\uAE08\uB429\uB2C8\uB2E4.");
7740
+ err2(" \u26A0\uFE0F \uC704 URL \uB85C \uC9C1\uC811 \uBC1B\uC73C\uC138\uC694 \u2014 \uC11C\uBC84\uB294 \uBCF4\uAD00\uD558\uC9C0 \uC54A\uC73C\uBA70, \uB2E4\uC2DC \uB9CC\uB4E4\uBA74 \uB610 \uACFC\uAE08\uB429\uB2C8\uB2E4.");
7703
7741
  } else {
7704
- out2(" \u26A0\uFE0F \uC774 URL \uC740 24\uC2DC\uAC04 \uD6C4 \uB9CC\uB8CC\uB429\uB2C8\uB2E4 \u2014 \uC9C0\uAE08 \uB0B4\uB824\uBC1B\uC73C\uC138\uC694 (--output <\uACBD\uB85C>).");
7742
+ out2(" \u26A0\uFE0F \uC11C\uBC84\uB294 \uC774 \uC0B0\uCD9C\uBB3C\uC744 \uBCF4\uAD00\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 \uC9C0\uAE08 \uB0B4\uB824\uBC1B\uC73C\uC138\uC694 (--output <\uACBD\uB85C>).");
7705
7743
  }
7706
7744
  }
7707
7745
  }
@@ -16275,10 +16313,42 @@ var StdioServerTransport = class {
16275
16313
  };
16276
16314
 
16277
16315
  // src/version.ts
16278
- var VERSION = "0.3.0";
16316
+ var VERSION = "0.4.0";
16279
16317
 
16280
16318
  // src/mcp/tools.ts
16281
16319
  import { existsSync as existsSync2 } from "fs";
16320
+
16321
+ // src/credits.ts
16322
+ var LEDGER_MAX_LIMIT = 200;
16323
+ var LEDGER_DEFAULT_LIMIT = 10;
16324
+ function balance(baseUrl, workspaceId, deps = {}) {
16325
+ return apiRequest(
16326
+ baseUrl,
16327
+ `/api/workspaces/${encodeURIComponent(workspaceId)}/credits`,
16328
+ {},
16329
+ deps
16330
+ );
16331
+ }
16332
+ function ledger(baseUrl, workspaceId, opts = {}, deps = {}) {
16333
+ const q = new URLSearchParams();
16334
+ q.set("limit", String(opts.limit ?? LEDGER_DEFAULT_LIMIT));
16335
+ if (opts.beforeId !== void 0) q.set("before_id", String(opts.beforeId));
16336
+ return apiRequest(
16337
+ baseUrl,
16338
+ `/api/workspaces/${encodeURIComponent(workspaceId)}/credits/ledger?${q}`,
16339
+ {},
16340
+ deps
16341
+ );
16342
+ }
16343
+
16344
+ // src/projects.ts
16345
+ var PROJECTS_DEFAULT_LIMIT = 30;
16346
+ var PROJECTS_MAX_LIMIT = 100;
16347
+ function list(baseUrl, deps = {}) {
16348
+ return apiRequest(baseUrl, "/api/projects", {}, deps);
16349
+ }
16350
+
16351
+ // src/mcp/tools.ts
16282
16352
  var COST_TOLERANCE_USD = 5e-3;
16283
16353
  function usd3(v) {
16284
16354
  return v < 0.01 ? `$${v.toFixed(4)}` : `$${v.toFixed(2)}`;
@@ -16330,10 +16400,22 @@ function toEstimateIn(a) {
16330
16400
  ...a.model ? { model: a.model } : {}
16331
16401
  };
16332
16402
  }
16403
+ var MOCK_BANNER = "\u26A0\uFE0F \uC774 \uBC30\uD3EC\uB294 mock \uBAA8\uB4DC\uC785\uB2C8\uB2E4 \u2014 \uC601\uC0C1\uC774 **\uC2E4\uC81C\uB85C \uC0DD\uC131\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4**. \uC544\uB798 \uAE08\uC561\xB7job_id\xB7URL \uC740 \uC804\uBD80 \uAC00\uC9DC\uC774\uBA70 \uC2E4\uC81C\uB85C \uACFC\uAE08\uB418\uC9C0\uB3C4 \uC54A\uC2B5\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC0DD\uC131\uC774 \uB418\uC5C8\uB2E4\uACE0 \uBCF4\uACE0\uD558\uC9C0 \uB9C8\uC138\uC694.";
16333
16404
  function estimateCost(a, deps = {}) {
16334
16405
  return guarded2(READ_ONLY, async () => {
16335
16406
  const base = resolveBaseUrl(deps.baseUrl);
16336
16407
  const r = await estimate(base, toEstimateIn(a), deps);
16408
+ if (r.mock) {
16409
+ return {
16410
+ text: [
16411
+ MOCK_BANNER,
16412
+ "",
16413
+ `(\uCC38\uACE0\uC6A9 \uCD94\uC815\uCE58: ${usd3(r.cost_usd)} \xB7 ${r.model})`,
16414
+ "",
16415
+ "\uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBE44\uC6A9 \uB3D9\uC758\uB97C \uAD6C\uD558\uC9C0 \uB9C8\uC138\uC694 \u2014 \uC774 \uBC30\uD3EC\uC5D0\uC11C\uB294 \uC81C\uCD9C\uD574\uB3C4 \uC2E4\uC81C \uC601\uC0C1\uC774 \uB9CC\uB4E4\uC5B4\uC9C0\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC6B4\uC601\uC790\uC5D0\uAC8C API \uD0A4 \uC124\uC815\uC744 \uC694\uCCAD\uD558\uC138\uC694."
16416
+ ].join("\n")
16417
+ };
16418
+ }
16337
16419
  return {
16338
16420
  text: [
16339
16421
  `\uC608\uC0C1 \uBE44\uC6A9: ${usd3(r.cost_usd)} (${r.tokens.toLocaleString()} tokens \xB7 ${r.model})`,
@@ -16349,6 +16431,12 @@ function generateVideo(a, deps = {}) {
16349
16431
  if (!a.prompt?.trim()) {
16350
16432
  return { isError: true, text: "prompt \uAC00 \uBE44\uC5B4 \uC788\uC2B5\uB2C8\uB2E4." };
16351
16433
  }
16434
+ if (typeof a.project_slug !== "string" || !a.project_slug.trim()) {
16435
+ return {
16436
+ isError: true,
16437
+ text: "project_slug \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4. \uBE44\uC6A9\uC744 \uADC0\uC18D\uC2DC\uD0AC \uBCF8\uC778 \uC18C\uC720 \uD504\uB85C\uC81D\uD2B8\uC758 \uC2AC\uB7EC\uADF8\uB97C \uB123\uC5B4 \uB2E4\uC2DC \uD638\uCD9C\uD558\uC138\uC694. \uC544\uC9C1 \uACFC\uAE08\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4.\n\uC2AC\uB7EC\uADF8\uB97C \uBAA8\uB974\uBA74 democut_list_projects \uB85C \uC774 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uD504\uB85C\uC81D\uD2B8 \uBAA9\uB85D\uC744 \uD655\uC778\uD558\uC138\uC694. \uBAA9\uB85D\uC774 \uBE44\uC5B4 \uC788\uC73C\uBA74 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uD504\uB85C\uC81D\uD2B8\uB97C \uBA3C\uC800 \uB9CC\uB4E4\uC5B4 \uB2EC\uB77C\uACE0 \uC54C\uB9AC\uC138\uC694."
16438
+ };
16439
+ }
16352
16440
  const base = resolveBaseUrl(deps.baseUrl);
16353
16441
  const est = toEstimateIn(a);
16354
16442
  const quote = await estimate(base, est, deps);
@@ -16374,7 +16462,7 @@ function generateVideo(a, deps = {}) {
16374
16462
  ...a.model ? { model: a.model } : {},
16375
16463
  ...a.seed !== void 0 ? { seed: a.seed } : {},
16376
16464
  ...a.image_urls?.length ? { image_urls: a.image_urls } : {},
16377
- ...a.project_slug ? { project_slug: a.project_slug } : {}
16465
+ project_slug: a.project_slug.trim()
16378
16466
  };
16379
16467
  const job = await submit(base, body, deps);
16380
16468
  if (job.duplicate) {
@@ -16382,6 +16470,17 @@ function generateVideo(a, deps = {}) {
16382
16470
  text: job.pending ? "\uAC19\uC740 \uC694\uCCAD\uC774 \uBC29\uAE08 \uC81C\uCD9C\uB418\uC5B4 \uC9C4\uD589 \uC911\uC785\uB2C8\uB2E4. job_id \uAC00 \uC544\uC9C1 \uC5C6\uC73C\uB2C8 \uC7A0\uC2DC \uD6C4 \uAC19\uC740 \uD30C\uB77C\uBBF8\uD130\uB85C \uB2E4\uC2DC \uD638\uCD9C\uD558\uC9C0 \uB9D0\uACE0 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC54C\uB9AC\uC138\uC694(\uC0C8\uB85C \uB9CC\uB4E4\uBA74 \uB2E4\uC2DC \uACFC\uAE08\uB429\uB2C8\uB2E4)." : `\uAC19\uC740 \uC694\uCCAD\uC774 \uC774\uBBF8 \uC9C4\uD589 \uC911\uC785\uB2C8\uB2E4 (job_id: ${job.job_id}). \uC0C8\uB85C \uACFC\uAE08\uB418\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4. get_video_status \uB85C \uD655\uC778\uD558\uC138\uC694.`
16383
16471
  };
16384
16472
  }
16473
+ if (job.mock) {
16474
+ return {
16475
+ text: [
16476
+ MOCK_BANNER,
16477
+ "",
16478
+ `(\uAC00\uC9DC job_id: ${job.job_id} \xB7 \uBAA8\uB378 ${job.model})`,
16479
+ "",
16480
+ "\uC2E4\uC81C\uB85C \uC81C\uCD9C\uB418\uC9C0 \uC54A\uC558\uACE0 \uACFC\uAE08\uB3C4 \uC5C6\uC2B5\uB2C8\uB2E4. \uC774 job_id \uB85C \uC0C1\uD0DC\uB97C \uC870\uD68C\uD558\uBA74 \uAC00\uC9DC URL \uC774 \uB098\uC635\uB2C8\uB2E4 \u2014 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC601\uC0C1\uC774 \uB9CC\uB4E4\uC5B4\uC84C\uB2E4\uACE0 \uB9D0\uD558\uC9C0 \uB9C8\uC138\uC694."
16481
+ ].join("\n")
16482
+ };
16483
+ }
16385
16484
  return {
16386
16485
  text: [
16387
16486
  `\uC81C\uCD9C\uB428 \u2014 job_id: ${job.job_id}`,
@@ -16398,6 +16497,17 @@ function videoStatus(a, deps = {}) {
16398
16497
  if (!a.job_id?.trim()) return { isError: true, text: "job_id \uAC00 \uD544\uC694\uD569\uB2C8\uB2E4." };
16399
16498
  const base = resolveBaseUrl(deps.baseUrl);
16400
16499
  const job = await status(base, a.job_id, a.project_slug, deps);
16500
+ if (job.mock) {
16501
+ return {
16502
+ text: [
16503
+ MOCK_BANNER,
16504
+ "",
16505
+ `(\uAC00\uC9DC \uC0C1\uD0DC: ${job.status} \xB7 \uAC00\uC9DC \uACB0\uACFC: ${job.video_url ?? "\uC5C6\uC74C"})`,
16506
+ "",
16507
+ "\uC774 URL \uC740 \uC2E4\uC7AC\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uB0B4\uB824\uBC1B\uC9C0 \uC54A\uC558\uACE0, \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC804\uB2EC\uD558\uC9C0 \uB9C8\uC138\uC694. \uC644\uB8CC\uB97C \uAE30\uB2E4\uB9AC\uBA70 \uB2E4\uC2DC \uC870\uD68C\uD558\uC9C0\uB3C4 \uB9C8\uC138\uC694."
16508
+ ].join("\n")
16509
+ };
16510
+ }
16401
16511
  if (job.status === "failed" || job.status === "cancelled") {
16402
16512
  return {
16403
16513
  isError: true,
@@ -16417,7 +16527,7 @@ function videoStatus(a, deps = {}) {
16417
16527
  if (existsSync2(a.output_path)) {
16418
16528
  lines.push(`\uC800\uC7A5\uD558\uC9C0 \uC54A\uC74C: ${a.output_path} \uC5D0 \uC774\uBBF8 \uD30C\uC77C\uC774 \uC788\uC2B5\uB2C8\uB2E4(\uB36E\uC5B4\uC4F0\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4).`);
16419
16529
  lines.push(`URL: ${job.video_url}`);
16420
- lines.push("\uB2E4\uB978 \uACBD\uB85C\uB97C \uC9C0\uC815\uD558\uAC70\uB098 \uC704 URL \uB85C \uC9C1\uC811 \uBC1B\uC73C\uC138\uC694 \u2014 24\uC2DC\uAC04 \uD6C4 \uB9CC\uB8CC\uB429\uB2C8\uB2E4.");
16530
+ lines.push("\uB2E4\uB978 \uACBD\uB85C\uB97C \uC9C0\uC815\uD558\uAC70\uB098 \uC704 URL \uB85C \uC9C1\uC811 \uBC1B\uC73C\uC138\uC694 \u2014 \uC11C\uBC84\uB294 \uBCF4\uAD00\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4.");
16421
16531
  return { text: lines.join("\n") };
16422
16532
  }
16423
16533
  try {
@@ -16426,15 +16536,153 @@ function videoStatus(a, deps = {}) {
16426
16536
  } catch (e) {
16427
16537
  lines.push(`\uB0B4\uB824\uBC1B\uAE30 \uC2E4\uD328: ${e?.message ?? String(e)}`);
16428
16538
  lines.push(`URL: ${job.video_url}`);
16429
- lines.push("\u26A0\uFE0F \uC704 URL \uB85C \uC9C1\uC811 \uBC1B\uC73C\uC138\uC694 \u2014 24\uC2DC\uAC04 \uD6C4 \uB9CC\uB8CC\uB418\uBA70 \uB2E4\uC2DC \uB9CC\uB4E4\uBA74 \uB610 \uACFC\uAE08\uB429\uB2C8\uB2E4.");
16539
+ lines.push("\u26A0\uFE0F \uC704 URL \uB85C \uC9C1\uC811 \uBC1B\uC73C\uC138\uC694 \u2014 \uC11C\uBC84\uB294 \uBCF4\uAD00\uD558\uC9C0 \uC54A\uC73C\uBA70 \uB2E4\uC2DC \uB9CC\uB4E4\uBA74 \uB610 \uACFC\uAE08\uB429\uB2C8\uB2E4.");
16430
16540
  }
16431
16541
  } else if (job.video_url) {
16432
16542
  lines.push(`URL: ${job.video_url}`);
16433
- lines.push("\u26A0\uFE0F \uC774 URL \uC740 24\uC2DC\uAC04 \uD6C4 \uB9CC\uB8CC\uB429\uB2C8\uB2E4 \u2014 output_path \uB97C \uC8FC\uBA74 \uBC14\uB85C \uC800\uC7A5\uD569\uB2C8\uB2E4.");
16543
+ lines.push("\u26A0\uFE0F \uC11C\uBC84\uB294 \uC774 \uC0B0\uCD9C\uBB3C\uC744 \uBCF4\uAD00\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 output_path \uB97C \uC8FC\uBA74 \uBC14\uB85C \uC800\uC7A5\uD569\uB2C8\uB2E4.");
16544
+ }
16545
+ return { text: lines.join("\n") };
16546
+ });
16547
+ }
16548
+ var krw = (v) => `${v.toLocaleString("ko-KR")}\uC6D0`;
16549
+ var NO_WORKSPACE = {
16550
+ isError: true,
16551
+ text: "\uD65C\uC131 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. democut_auth_status \uB85C \uB85C\uADF8\uC778 \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uACE0, \uC18C\uC18D\uB41C \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uAC00 \uC5C6\uB2E4\uBA74 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uCD08\uB300\uB97C \uBC1B\uC544\uC57C \uD55C\uB2E4\uACE0 \uC54C\uB9AC\uC138\uC694."
16552
+ };
16553
+ async function resolveWorkspace(base, deps) {
16554
+ const token = await getValidAccessToken(base, deps);
16555
+ const me = await whoami(base, token, deps.fetchImpl);
16556
+ return me.workspace_id ?? null;
16557
+ }
16558
+ function creditBalance(deps = {}) {
16559
+ return guarded2(READ_ONLY, async () => {
16560
+ const base = resolveBaseUrl(deps.baseUrl);
16561
+ const ws = await resolveWorkspace(base, deps);
16562
+ if (!ws) return NO_WORKSPACE;
16563
+ const b = await balance(base, ws, deps);
16564
+ if (b.total_krw == null) {
16565
+ return {
16566
+ text: "\uC774 \uBC30\uD3EC\uC5D0\uB294 \uD06C\uB808\uB527 \uAE30\uB2A5\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uC794\uC561\uC774\uB77C\uB294 \uAC1C\uB150 \uC790\uCCB4\uAC00 \uC5C6\uACE0, \uC794\uC561 \uBD80\uC871\uC73C\uB85C \uC791\uC5C5\uC774 \uB9C9\uD788\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC794\uC561\uC744 0\uC6D0\uC73C\uB85C \uC77D\uC9C0 \uB9C8\uC138\uC694."
16567
+ };
16568
+ }
16569
+ const lines = [
16570
+ `\uC794\uC561: ${krw(b.total_krw)} (\uAD6C\uB3C5 ${krw(b.subscription_krw ?? 0)} \xB7 \uCDA9\uC804 ${krw(b.topup_krw ?? 0)})`,
16571
+ `\uC694\uAE08\uC81C: ${b.tier ?? "-"} \xB7 \uC9C0\uAE09 \uC8FC\uAE30: ${b.period_key ?? "\uC5C6\uC74C"}`,
16572
+ ""
16573
+ ];
16574
+ if (!b.enabled) {
16575
+ lines.push(
16576
+ "\uACFC\uAE08 \uCC28\uB2E8\uC774 **\uAEBC\uC838 \uC788\uC2B5\uB2C8\uB2E4** \u2014 \uC794\uC561\uC774 \uBAA8\uC790\uB77C\uB3C4 \uC791\uC5C5\uC774 \uAC70\uBD80\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC704 \uC794\uC561\uC740 \uCC38\uACE0\uC6A9\uC774\uBA70, \uC794\uC561 \uBD80\uC871\uC744 \uC774\uC720\uB85C \uC791\uC5C5\uC744 \uD3EC\uAE30\uD558\uC9C0 \uB9C8\uC138\uC694."
16577
+ );
16578
+ } else if (b.total_krw <= 0) {
16579
+ lines.push(
16580
+ "\uC794\uC561\uC774 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uB3C8\uC774 \uB098\uAC00\uB294 \uC791\uC5C5\uC740 402 \uB85C \uAC70\uBD80\uB429\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC54C\uB9AC\uACE0 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uAD00\uB9AC\uC790\uC5D0\uAC8C \uCDA9\uC804\uC744 \uC694\uCCAD\uD558\uB3C4\uB85D \uC548\uB0B4\uD558\uC138\uC694. \uC81C\uCD9C\uC744 \uBC18\uBCF5\uD558\uC9C0 \uB9C8\uC138\uC694."
16581
+ );
16582
+ } else {
16583
+ lines.push(
16584
+ "\uB3C8\uC774 \uB098\uAC00\uB294 \uC791\uC5C5 \uC804\uC5D0 democut_estimate_video_cost \uB85C \uAE08\uC561\uC744 \uD655\uC778\uD558\uACE0 \uC774 \uC794\uC561\uACFC \uACAC\uC8FC\uC138\uC694. \uD06C\uB808\uB527\uC740 \uC6D0(KRW)\uC774\uACE0 \uACAC\uC801\uC740 USD \uB77C \uB2E8\uC704\uAC00 \uB2E4\uB985\uB2C8\uB2E4 \u2014 \uD655\uC2E4\uD558\uC9C0 \uC54A\uC73C\uBA74 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uB450 \uAC12\uC744 \uADF8\uB300\uB85C \uBCF4\uC5EC \uC8FC\uC138\uC694."
16585
+ );
16434
16586
  }
16435
16587
  return { text: lines.join("\n") };
16436
16588
  });
16437
16589
  }
16590
+ function creditLedger(a = {}, deps = {}) {
16591
+ return guarded2(READ_ONLY, async () => {
16592
+ if (a.limit !== void 0 && !Number.isFinite(a.limit)) {
16593
+ return { isError: true, text: `limit \uC740 \uC22B\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4: ${JSON.stringify(a.limit)}` };
16594
+ }
16595
+ if (a.before_id !== void 0 && !Number.isFinite(a.before_id)) {
16596
+ return {
16597
+ isError: true,
16598
+ text: `before_id \uB294 \uC22B\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4: ${JSON.stringify(a.before_id)}`
16599
+ };
16600
+ }
16601
+ const limit = Math.max(
16602
+ 1,
16603
+ Math.min(Math.trunc(a.limit ?? LEDGER_DEFAULT_LIMIT), LEDGER_MAX_LIMIT)
16604
+ );
16605
+ if (a.before_id !== void 0 && a.before_id < 1) {
16606
+ return { isError: true, text: "before_id \uB294 1 \uC774\uC0C1\uC774\uC5B4\uC57C \uD569\uB2C8\uB2E4." };
16607
+ }
16608
+ const base = resolveBaseUrl(deps.baseUrl);
16609
+ const ws = await resolveWorkspace(base, deps);
16610
+ if (!ws) return NO_WORKSPACE;
16611
+ const out3 = await ledger(
16612
+ base,
16613
+ ws,
16614
+ { limit, ...a.before_id !== void 0 ? { beforeId: Math.trunc(a.before_id) } : {} },
16615
+ deps
16616
+ );
16617
+ if (out3.entries.length === 0) {
16618
+ return {
16619
+ text: !out3.enabled && out3.next_before_id == null ? "\uC774 \uBC30\uD3EC\uC5D0\uB294 \uD06C\uB808\uB527 \uAE30\uB2A5\uC774 \uC5C6\uC5B4 \uC0AC\uC6A9 \uB0B4\uC5ED\uC774 \uC5C6\uC2B5\uB2C8\uB2E4(0\uAC74\uC774 \uC544\uB2C8\uB77C \uAC1C\uB150 \uBD80\uC7AC)." : "\uC0AC\uC6A9 \uB0B4\uC5ED\uC774 \uC5C6\uC2B5\uB2C8\uB2E4."
16620
+ };
16621
+ }
16622
+ const lines = ["\uD06C\uB808\uB527 \uC0AC\uC6A9 \uB0B4\uC5ED (\uCD5C\uC2E0\uC21C)", ""];
16623
+ for (const e of out3.entries) {
16624
+ const when = (e.created_at ?? "").slice(0, 16).replace("T", " ");
16625
+ const parts = [e.display_name];
16626
+ if (e.units_label) parts.push(e.units_label);
16627
+ if (e.project_slug) parts.push(e.project_slug);
16628
+ if (e.reason) parts.push(e.reason);
16629
+ const sign = e.amount_krw > 0 ? "+" : "";
16630
+ lines.push(
16631
+ `${when} \xB7 ${parts.join(" \xB7 ")} \xB7 ${sign}${e.amount_krw.toLocaleString("ko-KR")}\uC6D0 (\uC794\uC561 ${e.balance_after_krw.toLocaleString("ko-KR")}\uC6D0)`
16632
+ );
16633
+ }
16634
+ if (out3.next_before_id != null) {
16635
+ lines.push("", `\uB354 \uC788\uC2B5\uB2C8\uB2E4 \u2014 before_id=${out3.next_before_id} \uB85C \uB2E4\uC2DC \uD638\uCD9C\uD558\uC138\uC694.`);
16636
+ }
16637
+ return { text: lines.join("\n") };
16638
+ });
16639
+ }
16640
+ function projectLine(p) {
16641
+ const parts = [p.slug ?? "?", p.title || "(\uC81C\uBAA9 \uC5C6\uC74C)"];
16642
+ if (p.status) parts.push(p.status);
16643
+ if (p.kind === "shorts") parts.push("shorts");
16644
+ const updated = (p.updated_at ?? "").slice(0, 10);
16645
+ if (updated) parts.push(updated);
16646
+ return parts.join(" \xB7 ");
16647
+ }
16648
+ function listProjects(a = {}, deps = {}) {
16649
+ return guarded2(READ_ONLY, async () => {
16650
+ if (a.limit !== void 0 && !Number.isFinite(a.limit)) {
16651
+ return { isError: true, text: `limit \uC740 \uC22B\uC790\uC5EC\uC57C \uD569\uB2C8\uB2E4: ${JSON.stringify(a.limit)}` };
16652
+ }
16653
+ const limit = Math.max(
16654
+ 1,
16655
+ Math.min(Math.trunc(a.limit ?? PROJECTS_DEFAULT_LIMIT), PROJECTS_MAX_LIMIT)
16656
+ );
16657
+ const query = (a.query ?? "").trim().toLowerCase();
16658
+ const base = resolveBaseUrl(deps.baseUrl);
16659
+ const out3 = await list(base, deps);
16660
+ const all = out3.projects ?? [];
16661
+ const total = all.length;
16662
+ const matched = query ? all.filter(
16663
+ (p) => (p.slug ?? "").toLowerCase().includes(query) || (p.title ?? "").toLowerCase().includes(query)
16664
+ ) : all.slice();
16665
+ matched.sort((x, y) => (y.updated_at ?? "").localeCompare(x.updated_at ?? ""));
16666
+ const shown = matched.slice(0, limit);
16667
+ if (shown.length === 0) {
16668
+ return {
16669
+ text: query ? `'${query}' \uC640 \uC77C\uCE58\uD558\uB294 \uD504\uB85C\uC81D\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. (\uC774 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0 \uD504\uB85C\uC81D\uD2B8 ${total}\uAC1C) query \uB97C \uBE7C\uACE0 \uB2E4\uC2DC \uD638\uCD9C\uD574 \uC804\uCCB4\uB97C \uBCF4\uC138\uC694.` : "\uC774 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC5D0\uB294 \uC544\uC9C1 \uD504\uB85C\uC81D\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4. \uC0AC\uC6A9\uC790\uC5D0\uAC8C democut.ai \uC5D0\uC11C \uD504\uB85C\uC81D\uD2B8\uB97C \uD558\uB098 \uB9CC\uB4E0 \uB4A4 \uB2E4\uC2DC \uC694\uCCAD\uD574 \uB2EC\uB77C\uACE0 \uC54C\uB9AC\uC138\uC694 \u2014 \uB124\uAC00 \uC9C1\uC811 \uB9CC\uB4E4 \uC218\uB294 \uC5C6\uC2B5\uB2C8\uB2E4. \uC601\uC0C1 \uC0DD\uC131\uC740 \uADC0\uC18D\uC2DC\uD0AC \uD504\uB85C\uC81D\uD2B8\uAC00 \uC788\uC5B4\uC57C \uD569\uB2C8\uB2E4."
16670
+ };
16671
+ }
16672
+ const lines = ["\uD504\uB85C\uC81D\uD2B8 (\uCD5C\uADFC \uC218\uC815\uC21C) \u2014 slug \xB7 \uC81C\uBAA9 \xB7 \uC0C1\uD0DC \xB7 \uC218\uC815\uC77C", ""];
16673
+ for (const p of shown) lines.push(projectLine(p));
16674
+ lines.push("");
16675
+ if (matched.length > shown.length) {
16676
+ lines.push(
16677
+ `\uCD1D ${matched.length}\uAC1C \uC911 ${shown.length}\uAC1C\uB9CC \uD45C\uC2DC\uD588\uC2B5\uB2C8\uB2E4 \u2014 limit \uC744 \uC62C\uB9AC\uAC70\uB098 query \uB85C \uC881\uD788\uC138\uC694.`
16678
+ );
16679
+ }
16680
+ lines.push(
16681
+ "democut_generate_video \uC758 project_slug \uC5D0\uB294 \uC704 **slug**(\uB9E8 \uC55E \uAC12)\uB97C \uADF8\uB300\uB85C \uB123\uC73C\uC138\uC694. \uC5B4\uB290 \uAC83\uC5D0 \uADC0\uC18D\uC2DC\uD0AC\uC9C0 \uBD84\uBA85\uD558\uC9C0 \uC54A\uC73C\uBA74 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uBB3C\uC73C\uC138\uC694."
16682
+ );
16683
+ return { text: lines.join("\n") };
16684
+ });
16685
+ }
16438
16686
 
16439
16687
  // src/mcp/server.ts
16440
16688
  var RESOLUTIONS = ["480p", "720p", "1080p", "4k"];
@@ -16451,7 +16699,7 @@ var GENERATION_PARAMS = {
16451
16699
  var TOOLS = [
16452
16700
  {
16453
16701
  name: "democut_auth_status",
16454
- description: "DemoCut \uB85C\uADF8\uC778 \uC0C1\uD0DC\uC640 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\xB7\uC5ED\uD560\xB7\uD2F0\uC5B4\uB97C \uD655\uC778\uD55C\uB2E4. \uBD80\uC791\uC6A9\uB3C4 \uACFC\uAE08\uB3C4 \uC5C6\uB2E4. \uB2E4\uB978 \uB3C4\uAD6C\uAC00 '\uB85C\uADF8\uC778 \uD544\uC694' \uB85C \uC2E4\uD328\uD558\uBA74 \uC774\uAC78\uB85C \uC0C1\uD0DC\uB97C \uBA3C\uC800 \uD655\uC778\uD558\uB77C.",
16702
+ description: "DemoCut \uB85C\uADF8\uC778 \uC0C1\uD0DC\uC640 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\xB7\uC5ED\uD560\xB7\uD2F0\uC5B4\uB97C \uD655\uC778\uD55C\uB2E4. \uBD80\uC791\uC6A9\uB3C4 \uACFC\uAE08\uB3C4 \uC5C6\uB2E4. \uB2E4\uB978 \uB3C4\uAD6C\uAC00 '\uAD8C\uD55C \uC5C6\uC74C' \uC73C\uB85C \uC2E4\uD328\uD558\uBA74 \uC774\uAC78\uB85C \uC0C1\uD0DC\uB97C \uBA3C\uC800 \uD655\uC778\uD558\uB77C.",
16455
16703
  inputSchema: { type: "object", properties: {}, additionalProperties: false }
16456
16704
  },
16457
16705
  {
@@ -16465,7 +16713,7 @@ var TOOLS = [
16465
16713
  },
16466
16714
  {
16467
16715
  name: "democut_generate_video",
16468
- description: "\uC601\uC0C1 \uC0DD\uC131\uC744 \uC81C\uCD9C\uD55C\uB2E4. **\uC2E4\uC81C\uB85C \uB3C8\uC774 \uB098\uAC04\uB2E4.** \uBA3C\uC800 democut_estimate_video_cost \uB85C \uAE08\uC561\uC744 \uD655\uC778\uD558\uACE0 \uC0AC\uC6A9\uC790 \uB3D9\uC758\uB97C \uBC1B\uC740 \uB4A4, \uADF8 \uAE08\uC561\uC744 confirmed_cost_usd \uC5D0 \uADF8\uB300\uB85C \uB123\uC5B4 \uD638\uCD9C\uD558\uB77C \u2014 \uC11C\uBC84\uAC00 \uB2E4\uC2DC \uACC4\uC0B0\uD574 \uB300\uC870\uD558\uACE0 \uC5B4\uAE0B\uB098\uBA74 \uAC70\uBD80\uD55C\uB2E4. \uC2E4\uD328\uD574\uB3C4 **\uB2E4\uC2DC \uC81C\uCD9C\uD558\uC9C0 \uB9C8\uB77C**: \uC7AC\uC81C\uCD9C\uC740 \uBCC4\uAC1C \uC0DD\uC131\uC774\uB77C \uB2E4\uC2DC \uACFC\uAE08\uB41C\uB2E4.",
16716
+ description: "\uC601\uC0C1 \uC0DD\uC131\uC744 \uC81C\uCD9C\uD55C\uB2E4. **\uC2E4\uC81C\uB85C \uB3C8\uC774 \uB098\uAC04\uB2E4.** \uBA3C\uC800 democut_estimate_video_cost \uB85C \uAE08\uC561\uC744 \uD655\uC778\uD558\uACE0 \uC0AC\uC6A9\uC790 \uB3D9\uC758\uB97C \uBC1B\uC740 \uB4A4, \uADF8 \uAE08\uC561\uC744 confirmed_cost_usd \uC5D0 \uADF8\uB300\uB85C \uB123\uC5B4 \uD638\uCD9C\uD558\uB77C \u2014 \uC11C\uBC84\uAC00 \uB2E4\uC2DC \uACC4\uC0B0\uD574 \uB300\uC870\uD558\uACE0 \uC5B4\uAE0B\uB098\uBA74 \uAC70\uBD80\uD55C\uB2E4. \uC2E4\uD328\uD574\uB3C4 **\uB2E4\uC2DC \uC81C\uCD9C\uD558\uC9C0 \uB9C8\uB77C**: \uC7AC\uC81C\uCD9C\uC740 \uBCC4\uAC1C \uC0DD\uC131\uC774\uB77C \uB2E4\uC2DC \uACFC\uAE08\uB41C\uB2E4. \uC911\uBCF5 \uC81C\uCD9C \uBC29\uC5B4\uB294 \uC11C\uBC84 \uD504\uB85C\uC138\uC2A4 \uC548\uC5D0\uC11C\uB9CC \uB3D9\uC791\uD558\uBBC0\uB85C, \uAC19\uC740 \uC694\uCCAD\uC744 \uB3D9\uC2DC\uC5D0 \uB450 \uBC88 \uBCF4\uB0B4\uBA74 \uB450 \uAC74\uC774 \uC81C\uCD9C\uB420 \uC218 \uC788\uB2E4 \u2014 \uD55C \uBC88\uB9CC \uBCF4\uB0B4\uACE0 \uACB0\uACFC\uB97C \uC870\uD68C\uD558\uB77C.",
16469
16717
  inputSchema: {
16470
16718
  type: "object",
16471
16719
  properties: {
@@ -16483,25 +16731,66 @@ var TOOLS = [
16483
16731
  },
16484
16732
  generate_audio: { type: "boolean", description: "\uC624\uB514\uC624 \uD568\uAED8 \uC0DD\uC131. \uAE30\uBCF8 false." },
16485
16733
  watermark: { type: "boolean", description: "\uC6CC\uD130\uB9C8\uD06C. \uAE30\uBCF8 false." },
16486
- project_slug: { type: "string", description: "\uBE44\uC6A9\uC744 \uC774 \uD504\uB85C\uC81D\uD2B8\uB85C \uADC0\uC18D(\uC120\uD0DD)." }
16734
+ project_slug: {
16735
+ type: "string",
16736
+ description: "\uBE44\uC6A9\uC774 \uADC0\uC18D\uB420 \uD504\uB85C\uC81D\uD2B8 \uC2AC\uB7EC\uADF8 \u2014 **\uD544\uC218**. \uBCF8\uC778 \uC18C\uC720\uAC00 \uC544\uB2C8\uBA74 404. \uC601\uC0C1 \uC0DD\uC131\uC740 \uACFC\uAE08 \uB300\uC0C1\uC774\uB77C \uADC0\uC18D \uC5C6\uB294 \uC9C0\uCD9C\uC740 \uB300\uC870\xB7\uC815\uC0B0\uC774 \uBD88\uAC00\uB2A5\uD558\uB2E4. \uC2AC\uB7EC\uADF8\uB97C \uBAA8\uB974\uBA74 democut_list_projects \uB85C \uBA3C\uC800 \uD655\uC778\uD558\uB77C \u2014 \uCD94\uCE21\uD558\uC9C0 \uB9C8\uB77C."
16737
+ }
16487
16738
  },
16488
- required: ["prompt", "confirmed_cost_usd"],
16739
+ required: ["prompt", "confirmed_cost_usd", "project_slug"],
16489
16740
  additionalProperties: false
16490
16741
  }
16491
16742
  },
16492
16743
  {
16493
16744
  name: "democut_get_video_status",
16494
- description: "\uC0DD\uC131 \uC791\uC5C5 \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uACE0, \uC644\uB8CC\uB410\uC73C\uBA74 \uC0B0\uCD9C\uBB3C\uC744 \uB0B4\uB824\uBC1B\uB294\uB2E4. \uBD80\uC791\uC6A9\uB3C4 \uACFC\uAE08\uB3C4 \uC5C6\uB2E4. \uC0B0\uCD9C\uBB3C URL \uC740 24\uC2DC\uAC04 \uD6C4 \uB9CC\uB8CC\uB418\uBBC0\uB85C \uC644\uB8CC \uC989\uC2DC output_path \uB85C \uC800\uC7A5\uD558\uB77C.",
16745
+ description: "\uC0DD\uC131 \uC791\uC5C5 \uC0C1\uD0DC\uB97C \uD655\uC778\uD558\uACE0, \uC644\uB8CC\uB410\uC73C\uBA74 \uC0B0\uCD9C\uBB3C\uC744 \uB0B4\uB824\uBC1B\uB294\uB2E4. \uBD80\uC791\uC6A9\uB3C4 \uACFC\uAE08\uB3C4 \uC5C6\uB2E4. \uC11C\uBC84\uB294 \uC0B0\uCD9C\uBB3C\uC744 \uBCF4\uAD00\uD558\uC9C0 \uC54A\uC73C\uBBC0\uB85C \uC644\uB8CC \uC989\uC2DC output_path \uB85C \uC800\uC7A5\uD558\uB77C.",
16495
16746
  inputSchema: {
16496
16747
  type: "object",
16497
16748
  properties: {
16498
16749
  job_id: { type: "string", description: "democut_generate_video \uAC00 \uB3CC\uB824\uC900 job_id." },
16499
16750
  output_path: { type: "string", description: "\uC800\uC7A5\uD560 \uB85C\uCEEC \uACBD\uB85C(\uC808\uB300\uACBD\uB85C \uAD8C\uC7A5)." },
16500
- project_slug: { type: "string", description: "\uBE44\uC6A9 \uADC0\uC18D \uD504\uB85C\uC81D\uD2B8(\uC81C\uCD9C \uB54C\uC640 \uAC19\uAC8C)." }
16751
+ project_slug: {
16752
+ type: "string",
16753
+ description: "(\uBB34\uC2DC\uB428) \uADC0\uC18D\uC740 \uC81C\uCD9C \uC2DC\uC810 \uAC12\uC73C\uB85C \uC11C\uBC84\uAC00 \uACB0\uC815\uD55C\uB2E4."
16754
+ }
16501
16755
  },
16502
16756
  required: ["job_id"],
16503
16757
  additionalProperties: false
16504
16758
  }
16759
+ },
16760
+ {
16761
+ name: "democut_credit_balance",
16762
+ description: "DemoCut \uD06C\uB808\uB527 \uC794\uC561\uC744 \uD655\uC778\uD55C\uB2E4. \uBD80\uC791\uC6A9\uB3C4 \uACFC\uAE08\uB3C4 \uC5C6\uB2E4. \uB3C8\uC774 \uB098\uAC00\uB294 \uC791\uC5C5(democut_generate_video)\uC744 \uC81C\uCD9C\uD558\uAE30 **\uC804\uC5D0** \uD638\uCD9C\uD574 \uC794\uC561\uC774 \uCDA9\uBD84\uD55C\uC9C0 \uBCF4\uB77C \u2014 \uBD80\uC871\uD558\uBA74 \uC81C\uCD9C\uC774 402 \uB85C \uAC70\uBD80\uB418\uACE0, \uADF8\uAC83\uC744 \uBBF8\uB9AC \uC544\uB294 \uD3B8\uC774 \uB0AB\uB2E4. \uC794\uC561\uC774 \uBAA8\uC790\uB77C\uBA74 \uC0AC\uC6A9\uC790\uC5D0\uAC8C \uC54C\uB9AC\uACE0 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4 \uAD00\uB9AC\uC790\uC5D0\uAC8C \uCDA9\uC804\uC744 \uC694\uCCAD\uD558\uB3C4\uB85D \uC548\uB0B4\uD558\uB77C. \uB124\uAC00 \uC9C1\uC811 \uCDA9\uC804\uD560 \uC218\uB294 \uC5C6\uB2E4.",
16763
+ inputSchema: { type: "object", properties: {}, additionalProperties: false }
16764
+ },
16765
+ {
16766
+ name: "democut_credit_ledger",
16767
+ description: '\uD06C\uB808\uB527 \uC0AC\uC6A9 \uB0B4\uC5ED\uC744 \uCD5C\uC2E0\uC21C\uC73C\uB85C \uD655\uC778\uD55C\uB2E4. \uBD80\uC791\uC6A9\uB3C4 \uACFC\uAE08\uB3C4 \uC5C6\uB2E4. "\uC65C \uC794\uC561\uC774 \uC774\uB9CC\uD07C \uC904\uC5C8\uB098" \uB97C \uB2F5\uD560 \uB54C \uC4F4\uB2E4 \u2014 \uAC01 \uD589\uC740 \uBB34\uC5C7\uC5D0\xB7\uC5B8\uC81C\xB7\uC5BC\uB9C8\uC640, \uAC00\uB2A5\uD558\uBA74 \uC0AC\uC6A9\uB7C9(\uC608: 40,594 tokens)\uAE4C\uC9C0 \uB9D0\uD55C\uB2E4.',
16768
+ inputSchema: {
16769
+ type: "object",
16770
+ properties: {
16771
+ limit: { type: "number", description: "\uAC00\uC838\uC62C \uAC74\uC218 1~200. \uAE30\uBCF8 10." },
16772
+ before_id: {
16773
+ type: "number",
16774
+ description: "\uC774\uC804 \uD638\uCD9C\uC774 \uB3CC\uB824\uC900 next_before_id. \uB2E4\uC74C \uD398\uC774\uC9C0\uB97C \uBCFC \uB54C\uB9CC \uB123\uC5B4\uB77C."
16775
+ }
16776
+ },
16777
+ additionalProperties: false
16778
+ }
16779
+ },
16780
+ {
16781
+ name: "democut_list_projects",
16782
+ description: "\uC774 \uC6CC\uD06C\uC2A4\uD398\uC774\uC2A4\uC758 \uD504\uB85C\uC81D\uD2B8 \uBAA9\uB85D\uC744 \uCD5C\uADFC \uC218\uC815\uC21C\uC73C\uB85C \uD655\uC778\uD55C\uB2E4. \uBD80\uC791\uC6A9\uB3C4 \uACFC\uAE08\uB3C4 \uC5C6\uB2E4. democut_generate_video \uC758 project_slug \uB294 **\uC2E4\uC7AC\uD558\uB294 \uBCF8\uC778 \uD504\uB85C\uC81D\uD2B8\uC5EC\uC57C** \uD558\uACE0 \uD2C0\uB9AC\uBA74 404 \uB2E4 \u2014 \uC2AC\uB7EC\uADF8\uB97C \uBAA8\uB974\uBA74 \uCD94\uCE21\uD558\uC9C0 \uB9D0\uACE0 \uC774\uAC78\uB85C \uBA3C\uC800 \uD655\uC778\uD558\uB77C. \uBAA9\uB85D\uC774 \uBE44\uC5B4 \uC788\uC73C\uBA74 \uC544\uC9C1 \uD504\uB85C\uC81D\uD2B8\uAC00 \uC5C6\uB2E4\uB294 \uB73B\uC774\uB2C8, \uC0AC\uC6A9\uC790\uC5D0\uAC8C democut.ai \uC5D0\uC11C \uD558\uB098 \uB9CC\uB4E0 \uB4A4 \uB2E4\uC2DC \uC694\uCCAD\uD574 \uB2EC\uB77C\uACE0 \uC54C\uB824\uB77C(\uB124\uAC00 \uC9C1\uC811 \uB9CC\uB4E4 \uC218\uB294 \uC5C6\uB2E4).",
16783
+ inputSchema: {
16784
+ type: "object",
16785
+ properties: {
16786
+ limit: { type: "number", description: "\uAC00\uC838\uC62C \uAC74\uC218 1~100. \uAE30\uBCF8 30." },
16787
+ query: {
16788
+ type: "string",
16789
+ description: "\uC2AC\uB7EC\uADF8\xB7\uC81C\uBAA9\uC5D0 \uC774 \uBB38\uC790\uC5F4\uC774 \uB4E0 \uAC83\uB9CC (\uB300\uC18C\uBB38\uC790 \uBB34\uC2DC). \uCC3E\uB294 \uD504\uB85C\uC81D\uD2B8 \uC774\uB984\uC744 \uC54C \uB54C\uB9CC \uC4F0\uB77C \u2014 \uBE44\uC6B0\uBA74 \uCD5C\uADFC \uC218\uC815\uC21C \uC804\uCCB4\uB2E4."
16790
+ }
16791
+ },
16792
+ additionalProperties: false
16793
+ }
16505
16794
  }
16506
16795
  ];
16507
16796
  function dispatch(name, args, deps = {}) {
@@ -16514,6 +16803,12 @@ function dispatch(name, args, deps = {}) {
16514
16803
  return generateVideo(args, deps);
16515
16804
  case "democut_get_video_status":
16516
16805
  return videoStatus(args, deps);
16806
+ case "democut_credit_balance":
16807
+ return creditBalance(deps);
16808
+ case "democut_credit_ledger":
16809
+ return creditLedger(args, deps);
16810
+ case "democut_list_projects":
16811
+ return listProjects(args, deps);
16517
16812
  default:
16518
16813
  return Promise.resolve({ isError: true, text: `\uC54C \uC218 \uC5C6\uB294 \uB3C4\uAD6C: ${name}` });
16519
16814
  }
@@ -16549,11 +16844,11 @@ var HELP = `democut \u2014 DemoCut \uC5D0\uC774\uC804\uD2B8 CLI (OAuth2 device f
16549
16844
  democut auth logout [--base-url <url>]
16550
16845
 
16551
16846
  democut video estimate [--duration <\uCD08>] [--resolution <\uD574\uC0C1\uB3C4>] [--ratio <\uBE44\uC728>] [--model <id>]
16552
- democut video generate "<\uD504\uB86C\uD504\uD2B8>" [--yes] [--wait] [--output <\uACBD\uB85C>] [\uC0DD\uC131 \uC635\uC158\u2026]
16553
- democut video status <job_id> [--wait] [--output <\uACBD\uB85C>] [--project <slug>]
16847
+ democut video generate "<\uD504\uB86C\uD504\uD2B8>" --project <slug> [--yes] [--wait] [--output <\uACBD\uB85C>] [\uC0DD\uC131 \uC635\uC158\u2026]
16848
+ democut video status <job_id> [--wait] [--output <\uACBD\uB85C>]
16554
16849
 
16555
16850
  democut mcp MCP \uC11C\uBC84(stdio) \u2014 \uC5D0\uC774\uC804\uD2B8\uC5D0 \uB3C4\uAD6C\uB85C \uBD99\uC778\uB2E4
16556
- \uB4F1\uB85D: claude mcp add democut -- democut mcp
16851
+ \uB4F1\uB85D: claude mcp add democut -- npx -y democut mcp
16557
16852
 
16558
16853
  \uC0DD\uC131 \uC635\uC158:
16559
16854
  --duration <\uCD08> \uAE30\uBCF8 5 (\uBAA8\uB378\uBCC4 \uD5C8\uC6A9 \uBC94\uC704 \uB2E4\uB984: 2.0=4~15, 2.5=4~30)
@@ -16564,13 +16859,13 @@ var HELP = `democut \u2014 DemoCut \uC5D0\uC774\uC804\uD2B8 CLI (OAuth2 device f
16564
16859
  --image-url <url> image\u2192video \uCC38\uC870 (\uACF5\uAC1C HTTPS URL, \uCD5C\uB300 4\uD68C \uBC18\uBCF5)
16565
16860
  --audio \uC624\uB514\uC624 \uD568\uAED8 \uC0DD\uC131
16566
16861
  --watermark \uC6CC\uD130\uB9C8\uD06C \uB123\uAE30
16567
- --project <slug> \uBE44\uC6A9\uC744 \uC774 \uD504\uB85C\uC81D\uD2B8\uB85C \uADC0\uC18D
16862
+ --project <slug> \uBE44\uC6A9\uC744 \uC774 \uD504\uB85C\uC81D\uD2B8\uB85C \uADC0\uC18D (generate \uD544\uC218 \u2014 \uBCF8\uC778 \uC18C\uC720 \uC2AC\uB7EC\uADF8)
16568
16863
 
16569
16864
  \uACF5\uD1B5 \uC635\uC158:
16570
16865
  -y, --yes \uBE44\uC6A9 \uD655\uC778 \uC5C6\uC774 \uC9C4\uD589 (\uBE44\uB300\uD654\uD615\uC5D0\uC11C\uB294 \uD544\uC218)
16571
16866
  --wait \uC644\uB8CC\uAE4C\uC9C0 \uAE30\uB2E4\uB9BC
16572
16867
  --timeout <\uCD08> --wait \uC2DC\uD55C (\uAE30\uBCF8 900)
16573
- -o, --output <\uACBD\uB85C> \uC0B0\uCD9C\uBB3C \uC800\uC7A5 (\uC11C\uBA85 URL \uC740 24\uC2DC\uAC04 \uD6C4 \uB9CC\uB8CC \u2014 \uBC1B\uC544 \uB450\uC138\uC694)
16868
+ -o, --output <\uACBD\uB85C> \uC0B0\uCD9C\uBB3C \uC800\uC7A5 (\uC11C\uBC84\uB294 \uBCF4\uAD00\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4 \u2014 \uBC1B\uC544 \uB450\uC138\uC694)
16574
16869
  --json \uAE30\uACC4 \uD310\uB3C5\uC6A9 JSON \uD55C \uC904
16575
16870
  --base-url <u> DemoCut \uC11C\uBC84 URL (\uAE30\uBCF8: $DEMOCUT_BASE_URL \uB610\uB294 prod)
16576
16871
  -h, --help \uB3C4\uC6C0\uB9D0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "democut",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "description": "DemoCut 에이전트 CLI — device flow 로그인(RFC 8628) + 영상 생성",
5
5
  "license": "MIT",
6
6
  "author": "InfoGrab",