rootr-cli 0.31.0 → 0.32.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/bin/rootr.js +8 -4
- package/lib/client.js +2 -2
- package/lib/mcp-tools/databases.js +28 -8
- package/lib/mcp-tools/forms.js +3 -0
- package/package.json +1 -1
package/bin/rootr.js
CHANGED
|
@@ -44,7 +44,7 @@ const USAGE_KO = `Rootr CLI v${pkg.version}
|
|
|
44
44
|
rootr db automations <path|id> [--file rules.json] [--clear] 자동 규칙 — "값이 이렇게 되면 이 일을 해라"·정해진 시각
|
|
45
45
|
rootr db import-sheets <파일.xlsx> --ws <id> [--dry] 시트 수백 장짜리 엑셀을 표 하나로 접어 가져오기
|
|
46
46
|
rootr db view <path|id> [뷰이름] [--scale week|month|year] [--name 새이름] [--cards "칸,칸"] 뷰 목록·달력 주/달/해 단위·보드 카드에 보일 칸 ·[--density compact|normal] [--freeze 0~3] 촘촘하게·왼쪽 칸 고정
|
|
47
|
-
rootr db view <path|id> <뷰이름> [--row-hidden on|off] [--quick "칸,칸"] [--entry-row on|off] [--day-pager 날짜칸|off] 행 열면 감춘 칸 보이기·필터 맨 위 고정 칸·맨 아래 입력 줄·하루씩 보기
|
|
47
|
+
rootr db view <path|id> <뷰이름> [--row-hidden on|off] [--quick "칸,칸"] [--entry-row on|off|top|bottom] [--personal-filters on|off] [--day-pager 날짜칸|off] 행 열면 감춘 칸 보이기·필터 맨 위 고정 칸·맨 아래 입력 줄·하루씩 보기
|
|
48
48
|
rootr db column <path|id> <칸> [--align left|center|right] [--wrap on|off] [--overdue on|off] [--time on|off] [--range on|off] 칸 정렬·줄바꿈·지난 날짜 빨강·시간·기간
|
|
49
49
|
rootr db row-add <path|id> --values '<json>' [--body <md>|--body-file <f>] 행 추가(상세 내용 포함)
|
|
50
50
|
rootr db row-body <path|id> <rowId> [--file <f>] 행의 상세 내용(본문 메모) 쓰기 — 없으면 표준입력
|
|
@@ -187,7 +187,7 @@ usage:
|
|
|
187
187
|
rootr db automations <path|id> [--file rules.json] [--clear] automatic rules — "when a value becomes X, do Y", or at a set time
|
|
188
188
|
rootr db import-sheets <file.xlsx> --ws <id> [--dry] fold a one-sheet-per-record workbook into ONE table
|
|
189
189
|
rootr db view <path|id> [view name] [--scale week|month|year] [--name <new name>] list views; calendar week/month/year [--density compact|normal] [--freeze 0-3] compact rows · frozen left columns
|
|
190
|
-
rootr db view <path|id> <view name> [--row-hidden on|off] [--quick "field,field"] [--entry-row on|off] [--day-pager <date field>|off] hidden fields shown in the row page · pinned filters · entry row · one day at a time
|
|
190
|
+
rootr db view <path|id> <view name> [--row-hidden on|off] [--quick "field,field"] [--entry-row on|off|top|bottom] [--personal-filters on|off] [--day-pager <date field>|off] hidden fields shown in the row page · pinned filters · entry row · one day at a time
|
|
191
191
|
rootr db column <path|id> <column> [--align left|center|right] [--wrap on|off] [--overdue on|off] [--time on|off] [--range on|off] column alignment · wrapping · overdue red · time · end date
|
|
192
192
|
rootr db row-add <path|id> --values '<json>' [--body <md>|--body-file <f>] add a row (with its body)
|
|
193
193
|
rootr db row-body <path|id> <rowId> [--file <f>] write a row's body — reads stdin if no file
|
|
@@ -3001,7 +3001,11 @@ async function cmdDbView(args) {
|
|
|
3001
3001
|
throw new Error(tr(`--${flag} 는 on 또는 off`, `--${flag} must be on or off`));
|
|
3002
3002
|
};
|
|
3003
3003
|
if (args['row-hidden'] !== undefined) patch.rowPaneShowHidden = onOff('row-hidden', args['row-hidden']);
|
|
3004
|
-
if (args['
|
|
3004
|
+
if (args['personal-filters'] !== undefined) patch.filtersPersonalOnly = onOff('personal-filters', args['personal-filters']);
|
|
3005
|
+
if (args['entry-row'] !== undefined) {
|
|
3006
|
+
const v = String(args['entry-row']).toLowerCase();
|
|
3007
|
+
patch.entryRow = v === 'top' || v === 'bottom' ? v : onOff('entry-row', args['entry-row']);
|
|
3008
|
+
}
|
|
3005
3009
|
if (typeof args.quick === 'string') {
|
|
3006
3010
|
const quick = args.quick.split(',').map((c) => c.trim()).filter(Boolean);
|
|
3007
3011
|
patch.quickFilters = quick.length ? quick : null;
|
|
@@ -3011,7 +3015,7 @@ async function cmdDbView(args) {
|
|
|
3011
3015
|
patch.dayPager = !v || v === 'off' || v === 'false' ? null : { dateProp: v };
|
|
3012
3016
|
}
|
|
3013
3017
|
if (!Object.keys(patch).length) {
|
|
3014
|
-
throw new Error(tr('바꿀 것을 주세요: --scale week|month|year, --name 새이름, --cards "칸,칸", --density compact|normal, --freeze 0~3, --row-hidden on|off, --quick "칸,칸", --entry-row on|off 또는 --day-pager 날짜칸|off', `Give something to change: --scale week|month|year, --name <new name>, --cards "field,field", --density compact|normal, --freeze 0-3, --row-hidden on|off, --quick "field,field", --entry-row on|off, or --day-pager <date field>|off`));
|
|
3018
|
+
throw new Error(tr('바꿀 것을 주세요: --scale week|month|year, --name 새이름, --cards "칸,칸", --density compact|normal, --freeze 0~3, --row-hidden on|off, --quick "칸,칸", --entry-row on|off|top|bottom, --personal-filters on|off 또는 --day-pager 날짜칸|off', `Give something to change: --scale week|month|year, --name <new name>, --cards "field,field", --density compact|normal, --freeze 0-3, --row-hidden on|off, --quick "field,field", --entry-row on|off|top|bottom, --personal-filters on|off, or --day-pager <date field>|off`));
|
|
3015
3019
|
}
|
|
3016
3020
|
|
|
3017
3021
|
const view = await client.updateDatabaseView(id, viewRef, patch);
|
package/lib/client.js
CHANGED
|
@@ -1133,11 +1133,11 @@ export class RootrClient {
|
|
|
1133
1133
|
*/
|
|
1134
1134
|
async createDatabaseView(
|
|
1135
1135
|
databaseId,
|
|
1136
|
-
{ name, type, groupBy, dateProp, endDateProp, filters, sort, colWidths, calendarScale, density, frozenColumns, rowPaneShowHidden, quickFilters, entryRow, dayPager } = {},
|
|
1136
|
+
{ name, type, groupBy, dateProp, endDateProp, filters, sort, colWidths, calendarScale, density, frozenColumns, rowPaneShowHidden, quickFilters, entryRow, filtersPersonalOnly, dayPager } = {},
|
|
1137
1137
|
) {
|
|
1138
1138
|
return this.request(`/databases/${encodeURIComponent(databaseId)}/views`, {
|
|
1139
1139
|
method: 'POST',
|
|
1140
|
-
body: { name, type, groupBy, dateProp, endDateProp, filters, sort, colWidths, calendarScale, density, frozenColumns, rowPaneShowHidden, quickFilters, entryRow, dayPager },
|
|
1140
|
+
body: { name, type, groupBy, dateProp, endDateProp, filters, sort, colWidths, calendarScale, density, frozenColumns, rowPaneShowHidden, quickFilters, entryRow, filtersPersonalOnly, dayPager },
|
|
1141
1141
|
});
|
|
1142
1142
|
}
|
|
1143
1143
|
|
|
@@ -105,14 +105,24 @@ const VIEW_EXTRA_SHAPE = {
|
|
|
105
105
|
'has value, relation → pick linked rows, date → that day. null or [] turns it off.',
|
|
106
106
|
),
|
|
107
107
|
entryRow: z
|
|
108
|
-
.boolean()
|
|
108
|
+
.union([z.boolean(), z.enum(['top', 'bottom'])])
|
|
109
109
|
.nullable()
|
|
110
110
|
.optional()
|
|
111
111
|
.describe(
|
|
112
|
-
'table view: keep one always-empty entry row
|
|
112
|
+
'table view: keep one always-empty entry row (spreadsheet-like) — true/"bottom" at the bottom, "top" above the rows ' +
|
|
113
|
+
'(for tables read newest-first; rows made there stick to the top). Typing a value into any of its cells ' +
|
|
113
114
|
'creates the row (pre-filled to match the view filters, like the "new row" button) and a fresh empty row appears. ' +
|
|
114
115
|
'Focus/click alone creates nothing. false/null = off.',
|
|
115
116
|
),
|
|
117
|
+
filtersPersonalOnly: z
|
|
118
|
+
.boolean()
|
|
119
|
+
.nullable()
|
|
120
|
+
.optional()
|
|
121
|
+
.describe(
|
|
122
|
+
'table view: filters set in this view always apply only to the person setting them (no "share with everyone" switch) — ' +
|
|
123
|
+
'for views several people search at once. Conditions already shared stay. false/null = off. (Filters are just-me by default anyway; ' +
|
|
124
|
+
'this removes the switch.)',
|
|
125
|
+
),
|
|
116
126
|
dayPager: z
|
|
117
127
|
.union([z.object({ dateProp: z.string() }), z.string()])
|
|
118
128
|
.nullable()
|
|
@@ -390,7 +400,7 @@ export function registerDatabaseTools(server) {
|
|
|
390
400
|
...VIEW_EXTRA_SHAPE,
|
|
391
401
|
},
|
|
392
402
|
},
|
|
393
|
-
async ({ databaseId, name, type, groupBy, dateProp, endDateProp, filters, sort, colWidths, calendarScale, density, frozenColumns, rowPaneShowHidden, quickFilters, entryRow, dayPager }) => {
|
|
403
|
+
async ({ databaseId, name, type, groupBy, dateProp, endDateProp, filters, sort, colWidths, calendarScale, density, frozenColumns, rowPaneShowHidden, quickFilters, entryRow, filtersPersonalOnly, dayPager }) => {
|
|
394
404
|
try {
|
|
395
405
|
const client = makeClient();
|
|
396
406
|
const id = await resolveNodeId(client, databaseId);
|
|
@@ -409,6 +419,7 @@ export function registerDatabaseTools(server) {
|
|
|
409
419
|
rowPaneShowHidden,
|
|
410
420
|
quickFilters,
|
|
411
421
|
entryRow,
|
|
422
|
+
filtersPersonalOnly,
|
|
412
423
|
dayPager,
|
|
413
424
|
});
|
|
414
425
|
return jsonResult(view);
|
|
@@ -455,8 +466,7 @@ export function registerDatabaseTools(server) {
|
|
|
455
466
|
'Pass an empty array to show everything again. Only the fields you pass change, and other views are untouched. ' +
|
|
456
467
|
'For an at-a-glance table also pass density "compact" (lower rows, smaller text) and frozenColumns N (first N visible ' +
|
|
457
468
|
'columns stay fixed while scrolling sideways). Data-entry helpers: rowPaneShowHidden (hidden columns still show when a row ' +
|
|
458
|
-
'is opened), quickFilters (columns pinned at the top of the filter panel, just type), entryRow (always-empty
|
|
459
|
-
'Excel-like) and dayPager {dateProp} (one day per page with ◀ ▶ / Today, new rows get that day).',
|
|
469
|
+
'is opened), quickFilters (columns pinned at the top of the filter panel, just type), entryRow (always-empty row, Excel-like; "top" puts it above the rows), filtersPersonalOnly (filters always just-me) and dayPager {dateProp} (one day per page with ◀ ▶ / Today, new rows get that day).',
|
|
460
470
|
annotations: { readOnlyHint: false, destructiveHint: false },
|
|
461
471
|
inputSchema: {
|
|
462
472
|
databaseId: z.string().describe(DATABASE_TARGET_DESC),
|
|
@@ -548,6 +558,7 @@ export function registerDatabaseTools(server) {
|
|
|
548
558
|
rowPaneShowHidden,
|
|
549
559
|
quickFilters,
|
|
550
560
|
entryRow,
|
|
561
|
+
filtersPersonalOnly,
|
|
551
562
|
dayPager,
|
|
552
563
|
}) => {
|
|
553
564
|
try {
|
|
@@ -570,6 +581,7 @@ export function registerDatabaseTools(server) {
|
|
|
570
581
|
rowPaneShowHidden,
|
|
571
582
|
quickFilters,
|
|
572
583
|
entryRow,
|
|
584
|
+
filtersPersonalOnly,
|
|
573
585
|
dayPager,
|
|
574
586
|
});
|
|
575
587
|
return jsonResult({ view });
|
|
@@ -1376,7 +1388,9 @@ export function registerDatabaseTools(server) {
|
|
|
1376
1388
|
'sources?: "all" (default — per column take the most recent earlier row that HAS a value) | "latest" (only the latest earlier row); ' +
|
|
1377
1389
|
'strategy?: default per column, one of fill_empty (default) | prefer_new | prefer_old | latest | skip; columns?: { "<column>": ' +
|
|
1378
1390
|
'fill_empty | prefer_new | prefer_old | latest | skip | max | min | sum (number) | earliest | latest_date (date) | union ' +
|
|
1379
|
-
'(multi_select/relation/files/person) | { append: { sep?: " / ", datePrefix?: true
|
|
1391
|
+
'(multi_select/relation/files/person) | { append: { sep?: " / ", datePrefix?: true, unique?: true, emptyText?: "기존 메모 없음", ' +
|
|
1392
|
+
'history?: { format: "{date}지원", dateFormat?: "YY-MM-DD", sep?: ", ", only?: true } } } (text — unique keeps a piece once, ' +
|
|
1393
|
+
'emptyText stands in for an empty earlier note, history adds ONE line of earlier rows\' dates, only = just that line) } — a strategy on the wrong column ' +
|
|
1380
1394
|
'type is a 400; exclude = skip, overwrite = prefer_old; representative?: "latest" | "oldest", representativeWhen?: [{ propId, op, ' +
|
|
1381
1395
|
'values }] (first row matching wins). Every operation returns an opId (undo with rootr_dedupe_undo); preview with ' +
|
|
1382
1396
|
'rootr_dedupe_preview. ' +
|
|
@@ -2053,11 +2067,17 @@ export function registerDatabaseTools(server) {
|
|
|
2053
2067
|
'· {kind:"update_rows", target:"created"|"selected", values} — set values on the rows this button just ' +
|
|
2054
2068
|
'made, or on the rows the person ticked in the table.\n' +
|
|
2055
2069
|
'· {kind:"ai_fill", target, propId, prompt} — let AI write ONE cell. The prompt may quote the row with ' +
|
|
2056
|
-
'{칸이름}, e.g. "{제목}과 {메모}를 한 문장으로 요약해". If the AI fails the button keeps going
|
|
2070
|
+
'{칸이름}, e.g. "{제목}과 {메모}를 한 문장으로 요약해". If the AI fails the button keeps going. ' +
|
|
2071
|
+
'Pass fields:[{propId, prompt?}] (≤20) instead of propId to fill SEVERAL columns with ONE AI call from the same prompt ' +
|
|
2072
|
+
'(e.g. split one pasted text into many columns in one step; missing values stay empty).\n' +
|
|
2057
2073
|
'· {kind:"open_row"} — open the row this button made (UI only; over the API it just reports the id).\n' +
|
|
2058
2074
|
'Values accept @today, @today+7 and @me — the server fills them in at press time, not now. A text value ' +
|
|
2059
2075
|
'may also quote the pressed row with {칸이름}, e.g. "{이름} {통화 건수+1}차": computed columns (rollup, ' +
|
|
2060
|
-
'formula) are read too, {칸이름+N} / {칸이름-N} adds to a numeric value
|
|
2076
|
+
'formula) are read too, {칸이름+N} / {칸이름-N} adds to a numeric value (on a DATE column: N days later/earlier, ' +
|
|
2077
|
+
'{투입예정일+3}); {칸:YY-MM-DD} formats a date; {칸+3|@today+3} or {메모|없음} gives the value used when the column is ' +
|
|
2078
|
+
'empty; {@today} / {@today+7} / {@today:YY-MM-DD} work inside text too. APPEND A LINE: a value { append: "{@today:YY-MM-DD} ' +
|
|
2079
|
+
'{상태}", sep?: "\\n", unique?: true } on a text column adds that line to the END of the cell at write time (same line already ' +
|
|
2080
|
+
'there → skipped) instead of rewriting the whole cell. When several rows are ' +
|
|
2061
2081
|
'selected each new row is filled from its own source row. A relation column resolves to the linked row: ' +
|
|
2062
2082
|
'{지원자} is its TITLE (not the id) and {지원자.통화 건수} reads one of its columns — EXCEPT when the value ' +
|
|
2063
2083
|
'is exactly "{관계 칸}" and the column it goes into is a relation to the SAME table: then the row ids are ' +
|
package/lib/mcp-tools/forms.js
CHANGED
|
@@ -116,6 +116,9 @@ const FORM_CONFIG_DESC =
|
|
|
116
116
|
'`mirror: { fieldMap, defaults: { <표의 칸 이름 또는 id>: value } }` — `defaults` stamps extra values onto the ' +
|
|
117
117
|
'row a response creates, for columns the person did not answer; use it to start a 신청 at "상신 중" so a ' +
|
|
118
118
|
'manager has something to move to 승인/거절. What the person actually answered always wins. ' +
|
|
119
|
+
// FORM-DEFAULTS-SUBMITTER(이슈 #227/#228) — 적어 두지 않으면 에이전트는 "누가 냈는지"를 칸에 남기는 길을 모른다.
|
|
120
|
+
'A default of `"@me"` becomes the logged-in submitter\'s name (person column → [name], title/text → name; ' +
|
|
121
|
+
'left empty for anonymous submissions) — use it to record who applied; `"@today"`/`"@today+7"` for dates. ' +
|
|
119
122
|
'`publicSubmit` ("anon"|"login") — who may answer from outside the workspace. Publishing the form ' +
|
|
120
123
|
'(`rootr_publish_node`, or the 웹에 공개 switch) turns this on by itself, so you almost never set it by hand; ' +
|
|
121
124
|
'to stop taking answers use `closesAt`/`maxResponses`, or unpublish. ' +
|