rootr-cli 0.25.0 → 0.29.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/lib/client.js CHANGED
@@ -754,6 +754,19 @@ export class RootrClient {
754
754
  return [];
755
755
  }
756
756
 
757
+ /** List members of a workspace (id, email, name, role). */
758
+ async listMembers(workspace) {
759
+ return this.request(`/workspaces/${encodeURIComponent(workspace)}/members`);
760
+ }
761
+
762
+ /** MEMBER-MSG — send a direct notification-style message to another member of this workspace. */
763
+ async messageMember(workspace, userId, body) {
764
+ return this.request(`/workspaces/${encodeURIComponent(workspace)}/members/${encodeURIComponent(userId)}/message`, {
765
+ method: 'POST',
766
+ body: { body },
767
+ });
768
+ }
769
+
757
770
  // ---- Scaffolding ----------------------------------------------------------
758
771
 
759
772
  /** Plan a scaffold tree from a natural-language intent. No workspace required. */
@@ -970,6 +983,54 @@ export class RootrClient {
970
983
  });
971
984
  }
972
985
 
986
+ // ---- Custom domains (CUSTOM-DOMAIN) -------------------------------------
987
+ // 발행한 페이지(PAGE)를 고객 도메인(www.customer.co.kr)으로 연다. 워크스페이스 관리자 이상, 등록·확인은 Team 플랜부터.
988
+
989
+ async listCustomDomains(workspace) {
990
+ return this.request(`/workspaces/${encodeURIComponent(workspace)}/domains`);
991
+ }
992
+
993
+ /** 등록 전에 — 이 도메인의 DNS 를 어디서 관리하는지(가비아·Cloudflare…)와 그 화면 기준 안내. */
994
+ async detectCustomDomain(workspace, host, provider) {
995
+ const qs = new URLSearchParams({ host });
996
+ if (provider) qs.set('provider', provider);
997
+ return this.request(`/workspaces/${encodeURIComponent(workspace)}/domains/detect?${qs}`);
998
+ }
999
+
1000
+ /** hostname(www.회사.co.kr·acme.rootr.site) 또는 subdomain(acme). confirmCharge = 애드온 수량 +1 청구에 사람이 동의함. */
1001
+ async addCustomDomain(workspace, { hostname, subdomain, rootNodeId, provider, confirmCharge } = {}) {
1002
+ return this.request(`/workspaces/${encodeURIComponent(workspace)}/domains`, {
1003
+ method: 'POST',
1004
+ body: { hostname, subdomain, rootNodeId, provider, ...(confirmCharge ? { confirmCharge: true } : {}) },
1005
+ });
1006
+ }
1007
+
1008
+ /** 무료 주소 이름을 쓸 수 있나(acme → acme.rootr.site). */
1009
+ async customSubdomainAvailability(workspace, name) {
1010
+ return this.request(`/workspaces/${encodeURIComponent(workspace)}/domains/subdomain?${new URLSearchParams({ name })}`);
1011
+ }
1012
+
1013
+ async checkCustomDomain(workspace, domainId) {
1014
+ return this.request(`/workspaces/${encodeURIComponent(workspace)}/domains/${encodeURIComponent(domainId)}/check`, {
1015
+ method: 'POST',
1016
+ });
1017
+ }
1018
+
1019
+ async renameCustomDomain(workspace, domainId, { hostname, provider } = {}) {
1020
+ const body = { hostname };
1021
+ if (provider) body.provider = provider;
1022
+ return this.request(`/workspaces/${encodeURIComponent(workspace)}/domains/${encodeURIComponent(domainId)}`, {
1023
+ method: 'PATCH',
1024
+ body,
1025
+ });
1026
+ }
1027
+
1028
+ async removeCustomDomain(workspace, domainId) {
1029
+ return this.request(`/workspaces/${encodeURIComponent(workspace)}/domains/${encodeURIComponent(domainId)}`, {
1030
+ method: 'DELETE',
1031
+ });
1032
+ }
1033
+
973
1034
  // ---- Webhooks ---------------------------------------------------------
974
1035
 
975
1036
  async listWebhooks(workspace) {
@@ -1071,11 +1132,11 @@ export class RootrClient {
1071
1132
  */
1072
1133
  async createDatabaseView(
1073
1134
  databaseId,
1074
- { name, type, groupBy, dateProp, endDateProp, filters, sort, colWidths, calendarScale } = {},
1135
+ { name, type, groupBy, dateProp, endDateProp, filters, sort, colWidths, calendarScale, density, frozenColumns } = {},
1075
1136
  ) {
1076
1137
  return this.request(`/databases/${encodeURIComponent(databaseId)}/views`, {
1077
1138
  method: 'POST',
1078
- body: { name, type, groupBy, dateProp, endDateProp, filters, sort, colWidths, calendarScale },
1139
+ body: { name, type, groupBy, dateProp, endDateProp, filters, sort, colWidths, calendarScale, density, frozenColumns },
1079
1140
  });
1080
1141
  }
1081
1142
 
@@ -1242,31 +1303,94 @@ export class RootrClient {
1242
1303
 
1243
1304
  async addColumn(
1244
1305
  databaseId,
1245
- { name, type, options, relationDatabaseId, reverseName, formula, rollup, prefix, searchExcluded, dimensions } = {},
1306
+ { name, type, options, relationDatabaseId, reverseName, formula, rollup, prefix, searchExcluded, dimensions, align, wrap, highlightOverdue, allowTime, allowRange } = {},
1246
1307
  ) {
1247
1308
  return this.request(`/databases/${encodeURIComponent(databaseId)}/columns`, {
1248
1309
  method: 'POST',
1249
1310
  // 계산 칸(수식·롤업·번호)은 설정이 곧 그 칸이라 같이 보내야 한다.
1250
1311
  // reverseName 은 상대 표에 짝 칸을 만든다(양방향 관계).
1251
1312
  // searchExcluded 는 "검색 색인에서 제외"(표 관리자만).
1252
- body: { name, type, options, relationDatabaseId, reverseName, formula, rollup, prefix, searchExcluded, dimensions },
1313
+ // align·wrap·highlightOverdue·allowTime·allowRange 는 칸 표시 설정(정렬·줄바꿈·지난 날짜 빨강·시간·기간).
1314
+ body: { name, type, options, relationDatabaseId, reverseName, formula, rollup, prefix, searchExcluded, dimensions, align, wrap, highlightOverdue, allowTime, allowRange },
1253
1315
  });
1254
1316
  }
1255
1317
 
1256
1318
  async updateColumn(
1257
1319
  databaseId,
1258
1320
  propertyId,
1259
- { name, options, optionRenames, formula, rollup, prefix, reverseName, searchExcluded, dedupe, dedupeShow, relationFilter } = {},
1321
+ { name, options, optionRenames, formula, rollup, prefix, reverseName, searchExcluded, dedupe, dedupeShow, relationFilter, align, wrap, highlightOverdue, allowTime, allowRange, dedupeCarry, validate, identifyProps } = {},
1260
1322
  ) {
1261
1323
  return this.request(
1262
1324
  `/databases/${encodeURIComponent(databaseId)}/columns/${encodeURIComponent(propertyId)}`,
1263
1325
  {
1264
1326
  method: 'PATCH',
1265
- body: { name, options, optionRenames, formula, rollup, prefix, reverseName, searchExcluded, dedupe, dedupeShow, relationFilter },
1327
+ // identifyProps — 제목 칸만: 다른 표에서 이 표의 행을 고를 때 제목 옆에 붙일 칸(REL-PICK-IDENTIFY).
1328
+ body: { name, options, optionRenames, formula, rollup, prefix, reverseName, searchExcluded, dedupe, dedupeShow, relationFilter, align, wrap, highlightOverdue, allowTime, allowRange, dedupeCarry, validate, identifyProps },
1266
1329
  },
1267
1330
  );
1268
1331
  }
1269
1332
 
1333
+ /** DEDUPE-CARRY(P1) — 같은 사람의 이전 행 값을 `rowId` 행으로 가져온다(`from` 없으면 같은 값의 가장 최근 행). 이전 행은 그대로. */
1334
+ async carryOver(databaseId, rowId, from) {
1335
+ return this.request(`/databases/${encodeURIComponent(databaseId)}/rows/${encodeURIComponent(rowId)}/carry-over`, {
1336
+ method: 'POST',
1337
+ body: from ? { from } : {},
1338
+ });
1339
+ }
1340
+
1341
+ /**
1342
+ * DEDUPE-RULES — 이 행과 같은 값의 행들을 표 규칙대로 처리(dryRun 이면 미리 보기만). `rules` 는 이번만 덮는 dedupeCarry 꼴.
1343
+ */
1344
+ async dedupe(databaseId, rowId, { action, rules, fromRowIds, targetRowId, dryRun } = {}) {
1345
+ const path = `/databases/${encodeURIComponent(databaseId)}/rows/${encodeURIComponent(rowId)}/${dryRun ? 'dedupe-preview' : 'dedupe'}`;
1346
+ return this.request(path, {
1347
+ method: 'POST',
1348
+ body: { ...(action ? { action } : {}), ...(rules ? { rules } : {}), ...(fromRowIds?.length ? { fromRowIds } : {}), ...(targetRowId ? { targetRowId } : {}) },
1349
+ });
1350
+ }
1351
+
1352
+ /** DEDUPE-RULES — 처리 한 번(opId)을 통째로 되돌린다(고친 칸은 전 값으로, 지운 행은 되살린다). */
1353
+ async dedupeUndo(databaseId, opId) {
1354
+ return this.request(`/databases/${encodeURIComponent(databaseId)}/dedupe-ops/${encodeURIComponent(opId)}/undo`, { method: 'POST', body: {} });
1355
+ }
1356
+
1357
+ /** VALUE-RULE(P2) — 지금 들어 있는 값 가운데 칸 검사에 안 맞는 것(고치지 않는다). */
1358
+ async ruleViolations(databaseId, { limit } = {}) {
1359
+ const q = limit ? `?limit=${encodeURIComponent(limit)}` : '';
1360
+ return this.request(`/databases/${encodeURIComponent(databaseId)}/rule-violations${q}`);
1361
+ }
1362
+
1363
+ /** VALUE-RULE — 미리 채운 예시 꼴(제품이 정한 규칙이 아니라 고쳐 쓸 출발점). */
1364
+ async valueRulePresets() {
1365
+ return this.request('/value-rules/presets');
1366
+ }
1367
+
1368
+ /** VALUE-RULE — 규칙을 걸기 전에 시험 값으로 돌려 본다(아무것도 저장하지 않는다). */
1369
+ async testValueRule(rule, values, type) {
1370
+ return this.request('/value-rules/test', { method: 'POST', body: { rule, values, ...(type ? { type } : {}) } });
1371
+ }
1372
+
1373
+ /**
1374
+ * COL-RENAME — 이 칸을 `name` 으로 바꾸면 함께 바뀌는 것(수식·버튼/자동 규칙 치환·다른 표 롤업·이어진 폼 질문·페이지 블록)과
1375
+ * 따라갈 수 없는 것(warnings). 아무것도 쓰지 않는다. 실제로 바꾸면 updateColumn 응답의 `renameCascade` 에 같은 모양으로 온다.
1376
+ */
1377
+ async columnRenameImpact(databaseId, propertyId, name) {
1378
+ return this.request(
1379
+ `/databases/${encodeURIComponent(databaseId)}/columns/${encodeURIComponent(propertyId)}/rename-impact?name=${encodeURIComponent(name)}`,
1380
+ );
1381
+ }
1382
+
1383
+ /**
1384
+ * REL-PICK — 관계 칸에 이을 행을 표 전체에서 제목(과 구분 칸)으로 찾는다. `{ rows: [{ id, title, subtitle }], total, hasMore }`.
1385
+ * subtitle 은 그 표 제목 칸의 identifyProps(예: 연락처 끝 4자리 · 지원일) — 같은 이름을 가른다.
1386
+ */
1387
+ async pickRows(databaseId, { q, filters, ids, limit } = {}) {
1388
+ return this.request(`/databases/${encodeURIComponent(databaseId)}/rows/pick`, {
1389
+ method: 'POST',
1390
+ body: { q, filters, ids, limit },
1391
+ });
1392
+ }
1393
+
1270
1394
  /**
1271
1395
  * DEDUPE(F5) — 중복 감지 칸 값이 같은 기존 행. `rowId` 를 주면 그 행의 지금 값으로(그 행은 빠진다), 아니면 `values`
1272
1396
  * ({칸 id 또는 이름: 값})로 찾는다. 기존 행마다 보일 칸(preview)과 이어진 행 최근 5건(history)이 함께 온다.
@@ -1278,7 +1402,7 @@ export class RootrClient {
1278
1402
  });
1279
1403
  }
1280
1404
 
1281
- /** DEDUPE — `rowId` 행을 `into` 행에 합친다(빈 칸만 채우고 "재지원" 댓글, `rowId` 행은 지운다). */
1405
+ /** DEDUPE — `rowId` 행을 `into` 행에 합친다(빈 칸만 채우고 합친 댓글, `rowId` 행은 지운다). */
1282
1406
  async mergeRows(databaseId, rowId, into) {
1283
1407
  return this.request(`/databases/${encodeURIComponent(databaseId)}/rows/${encodeURIComponent(rowId)}/merge`, {
1284
1408
  method: 'POST',
@@ -0,0 +1,52 @@
1
+ /**
2
+ * CUSTOM-DOMAIN 과금 — 서버의 402(도메인 결제 필요)를 사람이 할 일로 바꾼다. MCP 도구(domains.js)와 CLI(`rootr domain add`)가 같이 쓴다.
3
+ * 에이전트·CLI 는 결제를 할 수 없다(결제창은 사람이 연다) — 그래서 "어디서 무엇을 누르면 되는지"와 "다시 부르는 법"을 말한다.
4
+ * `tr(ko, en)` 을 주면 그 언어로, 안 주면 영어(MCP 는 영어 설명을 쓴다).
5
+ */
6
+ export function domainPaymentHelp(err, tr = (_ko, en) => en) {
7
+ const code = err?.body?.code;
8
+ const d = err?.body?.details ?? {};
9
+ const url = d.billingUrl ?? tr('rootr.io › 설정 › 결제', 'rootr.io → Settings → Billing');
10
+ const month = d.priceUsd?.month ?? 10;
11
+ const year = d.priceUsd?.year ?? 96;
12
+ if (code === 'CUSTOM_DOMAIN_PAYMENT_REQUIRED') {
13
+ return tr(
14
+ `결제가 필요합니다: 내 도메인은 1개당 월 $${month}(연 결제 시 월 $${year / 12})입니다. 유료 자리 ${d.slots ?? 0}개 중 ${d.used ?? 0}개를 쓰는 중. ` +
15
+ `${url} 에서 "도메인 연결"을 시작하거나(또는 페이지 공유 창 › 도메인 연결에서 결제창을 열고) 다시 실행하세요. ` +
16
+ '결제 없이 쓰려면 무료 주소: --subdomain <이름> (이름.rootr.site)',
17
+ `Payment needed: own domains cost $${month}/month each ($${year / 12}/month billed yearly). ${d.used ?? 0} of ${d.slots ?? 0} paid slot(s) in use. ` +
18
+ `Ask the person to open ${url} and start the "Custom domain" add-on (or connect it once from the page's Share → "Connect domain" ` +
19
+ 'window, which opens the checkout), then call again. A free address (subdomain: "name" → name.rootr.site) needs no payment.',
20
+ );
21
+ }
22
+ if (code === 'CUSTOM_DOMAIN_CHARGE_CONFIRM_REQUIRED') {
23
+ const price = d.interval === 'year' ? `$${year}/year` : `$${month}/month`;
24
+ return tr(
25
+ `도메인 연결 애드온이 ${d.quantity ?? '?'}개로 1개 늘어납니다(도메인당 ${price}, 이번 주기는 일할 계산). 동의하면 --confirm-charge 를 붙여 다시 실행하세요.`,
26
+ `This adds one slot to the existing custom-domain add-on (new quantity ${d.quantity ?? '?'}, ${price} per domain, prorated now). ` +
27
+ 'Tell the person the price; only after they agree, call again with confirmCharge: true.',
28
+ );
29
+ }
30
+ if (code === 'CUSTOM_DOMAIN_PAYMENT_PAST_DUE') {
31
+ return tr(
32
+ `도메인 연결 결제가 실패한 상태입니다. ${url} 에서 카드를 먼저 바꾼 뒤 다시 실행하세요.`,
33
+ `The custom-domain add-on payment failed. Ask the person to update the card at ${url}, then try again.`,
34
+ );
35
+ }
36
+ return null;
37
+ }
38
+
39
+ /** 목록 위에 붙는 자리 한 줄(사람이 읽는 글). */
40
+ export function slotLine(slots, tr = (_ko, en) => en) {
41
+ if (!slots) return '';
42
+ const addon = slots.addon?.active
43
+ ? tr(
44
+ ` · 애드온 ${slots.addon.interval === 'year' ? '연' : '월'} 결제${slots.addon.cancelAt ? `(${String(slots.addon.cancelAt).slice(0, 10)} 종료 예약)` : ''}`,
45
+ ` · add-on billed ${slots.addon.interval === 'year' ? 'yearly' : 'monthly'}${slots.addon.cancelAt ? ` (ends ${String(slots.addon.cancelAt).slice(0, 10)})` : ''}`,
46
+ )
47
+ : '';
48
+ return tr(
49
+ `내 도메인 ${slots.used}/${slots.total}자리${slots.granted ? `(부여 ${slots.granted})` : ''}${addon} · 무료 주소 ${slots.free?.used ?? 0}/${slots.free?.limit ?? 3} · 가격 월 $${slots.price?.monthUsd ?? 10}(연 $${slots.price?.yearUsd ?? 96})`,
50
+ `own domains ${slots.used}/${slots.total} slot(s)${slots.granted ? ` (${slots.granted} granted)` : ''}${addon} · free addresses ${slots.free?.used ?? 0}/${slots.free?.limit ?? 3} · price $${slots.price?.monthUsd ?? 10}/month ($${slots.price?.yearUsd ?? 96}/year)`,
51
+ );
52
+ }
@@ -50,12 +50,22 @@ const GOOGLE_SCHEMA = z.object({
50
50
  busyCalendarIds: z
51
51
  .array(z.string())
52
52
  .optional()
53
- .describe('Calendars whose busy times block bookings. Only free/busy is read — never event titles.'),
53
+ .describe(
54
+ 'Calendars whose busy times block bookings. Only busy times and the dates of all-day events are read — never titles or guests.',
55
+ ),
54
56
  writeCalendarId: z
55
57
  .string()
56
58
  .nullable()
57
59
  .optional()
58
60
  .describe('Calendar that receives confirmed bookings. null = do not write anything to Google.'),
61
+ allDayBusy: z
62
+ .boolean()
63
+ .optional()
64
+ .describe(
65
+ 'All-day events block the whole day (default true). Google marks all-day events "free" by default, so busy times alone miss ' +
66
+ 'vacations, trips and holidays; a public holiday calendar in busyCalendarIds then closes holidays. Working-location, birthday ' +
67
+ 'and declined events never block. false = only busy times block.',
68
+ ),
59
69
  meet: z
60
70
  .boolean()
61
71
  .optional()