papermark 0.6.0 → 0.8.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/dist/index.js +549 -32
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -269,6 +269,13 @@ var api = {
|
|
|
269
269
|
createLink: (body) => request("/v1/links", { method: "POST", body }),
|
|
270
270
|
updateLink: (id, body) => request(`/v1/links/${id}`, { method: "PATCH", body }),
|
|
271
271
|
deleteLink: (id) => request(`/v1/links/${id}`, { method: "DELETE" }),
|
|
272
|
+
getLinkPermissions: (id) => request(`/v1/links/${id}/permissions`),
|
|
273
|
+
// Full-replace semantics: the payload is the complete desired state;
|
|
274
|
+
// items not listed lose their override.
|
|
275
|
+
setLinkPermissions: (id, body) => request(`/v1/links/${id}/permissions`, {
|
|
276
|
+
method: "PUT",
|
|
277
|
+
body
|
|
278
|
+
}),
|
|
272
279
|
// Views
|
|
273
280
|
listViews: (linkId, query) => request(`/v1/links/${linkId}/views`, { query }),
|
|
274
281
|
// Datarooms
|
|
@@ -308,6 +315,44 @@ var api = {
|
|
|
308
315
|
method: "POST",
|
|
309
316
|
body
|
|
310
317
|
}),
|
|
318
|
+
// Dataroom groups
|
|
319
|
+
listDataroomGroups: (id, query) => request(`/v1/datarooms/${id}/groups`, { query }),
|
|
320
|
+
createDataroomGroup: (id, body) => request(`/v1/datarooms/${id}/groups`, {
|
|
321
|
+
method: "POST",
|
|
322
|
+
body
|
|
323
|
+
}),
|
|
324
|
+
getDataroomGroup: (id, gid) => request(`/v1/datarooms/${id}/groups/${gid}`),
|
|
325
|
+
updateDataroomGroup: (id, gid, body) => request(`/v1/datarooms/${id}/groups/${gid}`, {
|
|
326
|
+
method: "PATCH",
|
|
327
|
+
body
|
|
328
|
+
}),
|
|
329
|
+
// Also deletes every share link pointing at the group (matching the
|
|
330
|
+
// dashboard).
|
|
331
|
+
deleteDataroomGroup: (id, gid) => request(`/v1/datarooms/${id}/groups/${gid}`, {
|
|
332
|
+
method: "DELETE"
|
|
333
|
+
}),
|
|
334
|
+
listDataroomGroupMembers: (id, gid, query) => request(
|
|
335
|
+
`/v1/datarooms/${id}/groups/${gid}/members`,
|
|
336
|
+
{ query }
|
|
337
|
+
),
|
|
338
|
+
addDataroomGroupMembers: (id, gid, body) => request(
|
|
339
|
+
`/v1/datarooms/${id}/groups/${gid}/members`,
|
|
340
|
+
{ method: "POST", body }
|
|
341
|
+
),
|
|
342
|
+
removeDataroomGroupMember: (id, gid, mid) => request(
|
|
343
|
+
`/v1/datarooms/${id}/groups/${gid}/members/${mid}`,
|
|
344
|
+
{ method: "DELETE" }
|
|
345
|
+
),
|
|
346
|
+
listDataroomGroupPermissions: (id, gid, query) => request(
|
|
347
|
+
`/v1/datarooms/${id}/groups/${gid}/permissions`,
|
|
348
|
+
{ query }
|
|
349
|
+
),
|
|
350
|
+
// Upsert-delta semantics: entries sent are upserted, entries omitted keep
|
|
351
|
+
// their current state; ancestor folders of visible items are auto-shown.
|
|
352
|
+
setDataroomGroupPermissions: (id, gid, body) => request(
|
|
353
|
+
`/v1/datarooms/${id}/groups/${gid}/permissions`,
|
|
354
|
+
{ method: "PUT", body }
|
|
355
|
+
),
|
|
311
356
|
// Visitors
|
|
312
357
|
listVisitors: (query) => request("/v1/visitors", { query }),
|
|
313
358
|
getVisitor: (id) => request(`/v1/visitors/${id}`),
|
|
@@ -1073,6 +1118,92 @@ function registerConfigCommands(program2) {
|
|
|
1073
1118
|
|
|
1074
1119
|
// src/commands/datarooms.ts
|
|
1075
1120
|
import chalk5 from "chalk";
|
|
1121
|
+
|
|
1122
|
+
// src/commands/permissions-input.ts
|
|
1123
|
+
import { readFileSync as readFileSync2 } from "fs";
|
|
1124
|
+
var ITEM_TYPES = {
|
|
1125
|
+
document: "dataroom_document",
|
|
1126
|
+
dataroom_document: "dataroom_document",
|
|
1127
|
+
folder: "dataroom_folder",
|
|
1128
|
+
dataroom_folder: "dataroom_folder"
|
|
1129
|
+
};
|
|
1130
|
+
function onOff(v, flag, fallback) {
|
|
1131
|
+
if (v === void 0) return fallback;
|
|
1132
|
+
const norm = v.toLowerCase();
|
|
1133
|
+
if (norm === "on") return true;
|
|
1134
|
+
if (norm === "off") return false;
|
|
1135
|
+
throw new Error(
|
|
1136
|
+
`Invalid value for ${flag}: ${JSON.stringify(v)} (expected "on" or "off").`
|
|
1137
|
+
);
|
|
1138
|
+
}
|
|
1139
|
+
function parsePermissionEntries(opts) {
|
|
1140
|
+
if (opts.file && opts.item) {
|
|
1141
|
+
throw new Error("Pass either --file or --item, not both.");
|
|
1142
|
+
}
|
|
1143
|
+
if (opts.file) {
|
|
1144
|
+
let raw;
|
|
1145
|
+
try {
|
|
1146
|
+
raw = readFileSync2(opts.file, "utf8");
|
|
1147
|
+
} catch (err) {
|
|
1148
|
+
throw new Error(
|
|
1149
|
+
`Could not read ${opts.file}: ${err instanceof Error ? err.message : String(err)}`
|
|
1150
|
+
);
|
|
1151
|
+
}
|
|
1152
|
+
let parsed;
|
|
1153
|
+
try {
|
|
1154
|
+
parsed = JSON.parse(raw);
|
|
1155
|
+
} catch {
|
|
1156
|
+
throw new Error(`${opts.file} is not valid JSON.`);
|
|
1157
|
+
}
|
|
1158
|
+
if (!Array.isArray(parsed)) {
|
|
1159
|
+
throw new Error(
|
|
1160
|
+
`${opts.file} must contain a JSON array of { item_id, item_type, can_view, can_download }.`
|
|
1161
|
+
);
|
|
1162
|
+
}
|
|
1163
|
+
return parsed.map((entry, i) => {
|
|
1164
|
+
const e = entry;
|
|
1165
|
+
const itemType = ITEM_TYPES[String(e.item_type)];
|
|
1166
|
+
if (typeof e.item_id !== "string" || !e.item_id) {
|
|
1167
|
+
throw new Error(`Entry ${i}: missing or invalid item_id.`);
|
|
1168
|
+
}
|
|
1169
|
+
if (!itemType) {
|
|
1170
|
+
throw new Error(
|
|
1171
|
+
`Entry ${i}: invalid item_type ${JSON.stringify(e.item_type)} (expected dataroom_document or dataroom_folder).`
|
|
1172
|
+
);
|
|
1173
|
+
}
|
|
1174
|
+
if (typeof e.can_view !== "boolean" || typeof e.can_download !== "boolean") {
|
|
1175
|
+
throw new Error(`Entry ${i}: can_view and can_download must be booleans.`);
|
|
1176
|
+
}
|
|
1177
|
+
return {
|
|
1178
|
+
item_id: e.item_id,
|
|
1179
|
+
item_type: itemType,
|
|
1180
|
+
can_view: e.can_view,
|
|
1181
|
+
can_download: e.can_download
|
|
1182
|
+
};
|
|
1183
|
+
});
|
|
1184
|
+
}
|
|
1185
|
+
if (opts.item) {
|
|
1186
|
+
const itemType = ITEM_TYPES[String(opts.type ?? "")];
|
|
1187
|
+
if (!itemType) {
|
|
1188
|
+
throw new Error(
|
|
1189
|
+
"Pass --type document or --type folder together with --item."
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
return [
|
|
1193
|
+
{
|
|
1194
|
+
item_id: opts.item,
|
|
1195
|
+
item_type: itemType,
|
|
1196
|
+
can_view: onOff(opts.view, "--view", true),
|
|
1197
|
+
can_download: onOff(opts.download, "--download", false)
|
|
1198
|
+
}
|
|
1199
|
+
];
|
|
1200
|
+
}
|
|
1201
|
+
throw new Error(
|
|
1202
|
+
"Pass --file <path> (JSON array) or --item <id> --type <document|folder> [--view on|off] [--download on|off]."
|
|
1203
|
+
);
|
|
1204
|
+
}
|
|
1205
|
+
|
|
1206
|
+
// src/commands/datarooms.ts
|
|
1076
1207
|
function registerDataroomsCommands(program2) {
|
|
1077
1208
|
const dr = program2.command("datarooms").alias("dr").description("Manage datarooms");
|
|
1078
1209
|
dr.command("list").description("List datarooms").option("-l, --limit <n>", "max results (1-100)", "25").option("-c, --cursor <id>", "pagination cursor").option("-q, --query <substring>", "filter by dataroom name").option("--json", "output raw JSON (same as --json on the root command)").action(
|
|
@@ -1344,6 +1475,249 @@ More results: --cursor ${res.next_cursor}`) : out;
|
|
|
1344
1475
|
}
|
|
1345
1476
|
}
|
|
1346
1477
|
);
|
|
1478
|
+
const drGroups = dr.command("groups").description(
|
|
1479
|
+
"Manage viewer groups: audiences with per-item permissions, shared via group links"
|
|
1480
|
+
);
|
|
1481
|
+
drGroups.command("list <id>").description("List viewer groups in a dataroom").option("-l, --limit <n>", "max results (1-100)", "25").option("-c, --cursor <id>", "pagination cursor").option("--json", "output raw JSON").action(
|
|
1482
|
+
async (id, opts) => {
|
|
1483
|
+
if (opts.json) setRuntime({ json: true });
|
|
1484
|
+
try {
|
|
1485
|
+
const res = await api.listDataroomGroups(id, {
|
|
1486
|
+
limit: opts.limit ? Number(opts.limit) : void 0,
|
|
1487
|
+
cursor: opts.cursor
|
|
1488
|
+
});
|
|
1489
|
+
emit(res, () => {
|
|
1490
|
+
const rows = res.data.map((g) => ({
|
|
1491
|
+
id: g.id,
|
|
1492
|
+
name: truncate2(g.name, 30),
|
|
1493
|
+
members: String(g.member_count),
|
|
1494
|
+
links: String(g.link_count),
|
|
1495
|
+
domains: truncate2(g.domains.join(","), 30),
|
|
1496
|
+
allow_all: g.allow_all ? "yes" : ""
|
|
1497
|
+
}));
|
|
1498
|
+
const out = table(rows, [
|
|
1499
|
+
"id",
|
|
1500
|
+
"name",
|
|
1501
|
+
"members",
|
|
1502
|
+
"links",
|
|
1503
|
+
"domains",
|
|
1504
|
+
"allow_all"
|
|
1505
|
+
]);
|
|
1506
|
+
return res.next_cursor ? out + chalk5.dim(`
|
|
1507
|
+
|
|
1508
|
+
More results: --cursor ${res.next_cursor}`) : out;
|
|
1509
|
+
});
|
|
1510
|
+
} catch (err) {
|
|
1511
|
+
die(err);
|
|
1512
|
+
}
|
|
1513
|
+
}
|
|
1514
|
+
);
|
|
1515
|
+
drGroups.command("get <id> <gid>").description("Fetch one viewer group").action(async (id, gid) => {
|
|
1516
|
+
try {
|
|
1517
|
+
const g = await api.getDataroomGroup(id, gid);
|
|
1518
|
+
emit(g, () => json(g));
|
|
1519
|
+
} catch (err) {
|
|
1520
|
+
die(err);
|
|
1521
|
+
}
|
|
1522
|
+
});
|
|
1523
|
+
drGroups.command("create <id>").description("Create a viewer group in a dataroom").requiredOption("-n, --name <name>", "group name").option(
|
|
1524
|
+
"--domains <domains>",
|
|
1525
|
+
"comma-separated email domains treated as members (e.g. 'acme.com')"
|
|
1526
|
+
).option(
|
|
1527
|
+
"--allow-all",
|
|
1528
|
+
"treat anyone passing the link's access gates as a member",
|
|
1529
|
+
false
|
|
1530
|
+
).action(
|
|
1531
|
+
async (id, opts) => {
|
|
1532
|
+
try {
|
|
1533
|
+
const g = await api.createDataroomGroup(id, {
|
|
1534
|
+
name: opts.name,
|
|
1535
|
+
allow_all: opts.allowAll || void 0,
|
|
1536
|
+
domains: opts.domains ? opts.domains.split(",").map((s) => s.trim()).filter(Boolean) : void 0
|
|
1537
|
+
});
|
|
1538
|
+
emit(
|
|
1539
|
+
g,
|
|
1540
|
+
() => chalk5.green("\u2713") + ` Group created: ${chalk5.bold(g.id)}
|
|
1541
|
+
` + json(g)
|
|
1542
|
+
);
|
|
1543
|
+
} catch (err) {
|
|
1544
|
+
die(err);
|
|
1545
|
+
}
|
|
1546
|
+
}
|
|
1547
|
+
);
|
|
1548
|
+
drGroups.command("update <id> <gid>").description("Update a viewer group").option("-n, --name <name>").option(
|
|
1549
|
+
"--domains <domains>",
|
|
1550
|
+
"comma-separated email domains (empty string to clear)"
|
|
1551
|
+
).option("--allow-all <on|off>", "toggle the allow-anyone flag").action(
|
|
1552
|
+
async (id, gid, opts) => {
|
|
1553
|
+
try {
|
|
1554
|
+
const body = {};
|
|
1555
|
+
if (opts.name !== void 0) body.name = opts.name;
|
|
1556
|
+
if (opts.domains !== void 0)
|
|
1557
|
+
body.domains = opts.domains === "" ? [] : opts.domains.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1558
|
+
if (opts.allowAll !== void 0) {
|
|
1559
|
+
const norm = opts.allowAll.toLowerCase();
|
|
1560
|
+
if (norm !== "on" && norm !== "off") {
|
|
1561
|
+
throw new Error(
|
|
1562
|
+
`Invalid value for --allow-all: ${JSON.stringify(opts.allowAll)} (expected "on" or "off").`
|
|
1563
|
+
);
|
|
1564
|
+
}
|
|
1565
|
+
body.allow_all = norm === "on";
|
|
1566
|
+
}
|
|
1567
|
+
if (Object.keys(body).length === 0) {
|
|
1568
|
+
throw new Error(
|
|
1569
|
+
"Pass at least one of --name, --domains, --allow-all to update."
|
|
1570
|
+
);
|
|
1571
|
+
}
|
|
1572
|
+
const g = await api.updateDataroomGroup(id, gid, body);
|
|
1573
|
+
emit(
|
|
1574
|
+
g,
|
|
1575
|
+
() => chalk5.green("\u2713") + ` Group updated: ${chalk5.bold(g.id)}
|
|
1576
|
+
` + json(g)
|
|
1577
|
+
);
|
|
1578
|
+
} catch (err) {
|
|
1579
|
+
die(err);
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
);
|
|
1583
|
+
drGroups.command("delete <id> <gid>").description(
|
|
1584
|
+
"Delete a viewer group. WARNING: also deletes every share link pointing at the group \u2014 active group links stop resolving immediately."
|
|
1585
|
+
).action(async (id, gid) => {
|
|
1586
|
+
try {
|
|
1587
|
+
const deleted = await api.deleteDataroomGroup(id, gid);
|
|
1588
|
+
emit(
|
|
1589
|
+
deleted,
|
|
1590
|
+
() => chalk5.green("\u2713") + ` Group ${gid} deleted (including its share links).`
|
|
1591
|
+
);
|
|
1592
|
+
} catch (err) {
|
|
1593
|
+
die(err);
|
|
1594
|
+
}
|
|
1595
|
+
});
|
|
1596
|
+
const drGroupMembers = drGroups.command("members").description("Manage a group's email members");
|
|
1597
|
+
drGroupMembers.command("list <id> <gid>").description(
|
|
1598
|
+
"List a group's explicit email members (domain/allow-all admissions are not listed)"
|
|
1599
|
+
).option("-l, --limit <n>", "max results (1-100)", "50").option("-c, --cursor <id>", "pagination cursor").option("--json", "output raw JSON").action(
|
|
1600
|
+
async (id, gid, opts) => {
|
|
1601
|
+
if (opts.json) setRuntime({ json: true });
|
|
1602
|
+
try {
|
|
1603
|
+
const res = await api.listDataroomGroupMembers(id, gid, {
|
|
1604
|
+
limit: opts.limit ? Number(opts.limit) : void 0,
|
|
1605
|
+
cursor: opts.cursor
|
|
1606
|
+
});
|
|
1607
|
+
emit(res, () => {
|
|
1608
|
+
const rows = res.data.map((m) => ({
|
|
1609
|
+
id: m.id,
|
|
1610
|
+
email: sanitizeForTerminal(m.email),
|
|
1611
|
+
added: m.created.slice(0, 10)
|
|
1612
|
+
}));
|
|
1613
|
+
const out = table(rows, ["id", "email", "added"]);
|
|
1614
|
+
return res.next_cursor ? out + chalk5.dim(`
|
|
1615
|
+
|
|
1616
|
+
More results: --cursor ${res.next_cursor}`) : out;
|
|
1617
|
+
});
|
|
1618
|
+
} catch (err) {
|
|
1619
|
+
die(err);
|
|
1620
|
+
}
|
|
1621
|
+
}
|
|
1622
|
+
);
|
|
1623
|
+
drGroupMembers.command("add <id> <gid>").description(
|
|
1624
|
+
"Add members by email (idempotent; no invitation emails are sent)"
|
|
1625
|
+
).requiredOption(
|
|
1626
|
+
"-e, --emails <emails>",
|
|
1627
|
+
"comma-separated email addresses (max 500)"
|
|
1628
|
+
).action(async (id, gid, opts) => {
|
|
1629
|
+
try {
|
|
1630
|
+
const emails = opts.emails.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1631
|
+
if (emails.length === 0) {
|
|
1632
|
+
throw new Error("Pass at least one email via --emails.");
|
|
1633
|
+
}
|
|
1634
|
+
const res = await api.addDataroomGroupMembers(id, gid, { emails });
|
|
1635
|
+
emit(
|
|
1636
|
+
res,
|
|
1637
|
+
() => chalk5.green("\u2713") + ` ${res.data.length} member(s) in group after add:
|
|
1638
|
+
` + table(
|
|
1639
|
+
res.data.map((m) => ({
|
|
1640
|
+
id: m.id,
|
|
1641
|
+
email: sanitizeForTerminal(m.email)
|
|
1642
|
+
})),
|
|
1643
|
+
["id", "email"]
|
|
1644
|
+
)
|
|
1645
|
+
);
|
|
1646
|
+
} catch (err) {
|
|
1647
|
+
die(err);
|
|
1648
|
+
}
|
|
1649
|
+
});
|
|
1650
|
+
drGroupMembers.command("remove <id> <gid> <mid>").description(
|
|
1651
|
+
"Remove a member by membership id (from `members list`). The viewer itself is kept."
|
|
1652
|
+
).action(async (id, gid, mid) => {
|
|
1653
|
+
try {
|
|
1654
|
+
const deleted = await api.removeDataroomGroupMember(id, gid, mid);
|
|
1655
|
+
emit(
|
|
1656
|
+
deleted,
|
|
1657
|
+
() => chalk5.green("\u2713") + ` Member ${mid} removed from group.`
|
|
1658
|
+
);
|
|
1659
|
+
} catch (err) {
|
|
1660
|
+
die(err);
|
|
1661
|
+
}
|
|
1662
|
+
});
|
|
1663
|
+
const drGroupPermissions = drGroups.command("permissions").description("Manage a group's per-item view/download permissions");
|
|
1664
|
+
drGroupPermissions.command("get <id> <gid>").description(
|
|
1665
|
+
"List the group's access controls. Items without an entry are invisible to the group."
|
|
1666
|
+
).option("-l, --limit <n>", "max results (1-100)", "100").option("-c, --cursor <id>", "pagination cursor").option("--json", "output raw JSON").action(
|
|
1667
|
+
async (id, gid, opts) => {
|
|
1668
|
+
if (opts.json) setRuntime({ json: true });
|
|
1669
|
+
try {
|
|
1670
|
+
const res = await api.listDataroomGroupPermissions(id, gid, {
|
|
1671
|
+
limit: opts.limit ? Number(opts.limit) : void 0,
|
|
1672
|
+
cursor: opts.cursor
|
|
1673
|
+
});
|
|
1674
|
+
emit(res, () => {
|
|
1675
|
+
const rows = res.data.map((p) => ({
|
|
1676
|
+
item_id: p.item_id,
|
|
1677
|
+
type: p.item_type === "dataroom_document" ? "document" : "folder",
|
|
1678
|
+
view: p.can_view ? "yes" : "",
|
|
1679
|
+
download: p.can_download ? "yes" : ""
|
|
1680
|
+
}));
|
|
1681
|
+
const out = table(rows, ["item_id", "type", "view", "download"]);
|
|
1682
|
+
return res.next_cursor ? out + chalk5.dim(`
|
|
1683
|
+
|
|
1684
|
+
More results: --cursor ${res.next_cursor}`) : out;
|
|
1685
|
+
});
|
|
1686
|
+
} catch (err) {
|
|
1687
|
+
die(err);
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
);
|
|
1691
|
+
drGroupPermissions.command("set <id> <gid>").description(
|
|
1692
|
+
"Upsert per-item permissions (delta: omitted items keep their state; ancestor folders of visible items are auto-shown). Item ids come from `datarooms documents` / `datarooms folders list`."
|
|
1693
|
+
).option(
|
|
1694
|
+
"--file <path>",
|
|
1695
|
+
"JSON array of { item_id, item_type, can_view, can_download }"
|
|
1696
|
+
).option("--item <itemId>", "single item id (use with --type/--view/--download)").option("--type <document|folder>", "item type for --item").option("--view <on|off>", "allow viewing (default on)").option("--download <on|off>", "allow downloading (default off)").action(
|
|
1697
|
+
async (id, gid, opts) => {
|
|
1698
|
+
try {
|
|
1699
|
+
const permissions = parsePermissionEntries(opts);
|
|
1700
|
+
const res = await api.setDataroomGroupPermissions(id, gid, {
|
|
1701
|
+
permissions
|
|
1702
|
+
});
|
|
1703
|
+
emit(
|
|
1704
|
+
res,
|
|
1705
|
+
() => chalk5.green("\u2713") + ` ${res.data.length} permission row(s) now effective:
|
|
1706
|
+
` + table(
|
|
1707
|
+
res.data.map((p) => ({
|
|
1708
|
+
item_id: p.item_id,
|
|
1709
|
+
type: p.item_type === "dataroom_document" ? "document" : "folder",
|
|
1710
|
+
view: p.can_view ? "yes" : "",
|
|
1711
|
+
download: p.can_download ? "yes" : ""
|
|
1712
|
+
})),
|
|
1713
|
+
["item_id", "type", "view", "download"]
|
|
1714
|
+
)
|
|
1715
|
+
);
|
|
1716
|
+
} catch (err) {
|
|
1717
|
+
die(err);
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
);
|
|
1347
1721
|
const drFolders = dr.command("folders").description("Manage folders inside a dataroom");
|
|
1348
1722
|
drFolders.command("list <id>").description("List folders in a dataroom").option(
|
|
1349
1723
|
"--parent <fid>",
|
|
@@ -1736,40 +2110,49 @@ More results: --cursor ${res.next_cursor}`) : out;
|
|
|
1736
2110
|
create_link: opts.link ?? false
|
|
1737
2111
|
});
|
|
1738
2112
|
spinner.succeed(`Document created: ${chalk7.bold(doc.id)}`);
|
|
1739
|
-
|
|
1740
|
-
let linkWarning = null;
|
|
1741
|
-
if (opts.link) {
|
|
1742
|
-
try {
|
|
1743
|
-
const links = await api.listLinks({
|
|
1744
|
-
document_id: doc.id,
|
|
1745
|
-
limit: 1
|
|
1746
|
-
});
|
|
1747
|
-
link = links.data[0] ?? null;
|
|
1748
|
-
if (!link) {
|
|
1749
|
-
linkWarning = "--link was set but no link was returned. Try `papermark links list --document " + doc.id + "`.";
|
|
1750
|
-
}
|
|
1751
|
-
} catch (warnErr) {
|
|
1752
|
-
linkWarning = `Document created, but listing the share link failed: ${warnErr instanceof Error ? warnErr.message : String(warnErr)}`;
|
|
1753
|
-
}
|
|
1754
|
-
if (linkWarning) {
|
|
1755
|
-
process.stderr.write(chalk7.yellow("! ") + linkWarning + "\n");
|
|
1756
|
-
}
|
|
1757
|
-
}
|
|
1758
|
-
emit({ document: doc, link }, () => {
|
|
1759
|
-
const blocks = [json(doc)];
|
|
1760
|
-
if (link) {
|
|
1761
|
-
blocks.push(
|
|
1762
|
-
chalk7.green("\u2713") + " Share link: " + chalk7.bold(link.url)
|
|
1763
|
-
);
|
|
1764
|
-
}
|
|
1765
|
-
return blocks.join("\n");
|
|
1766
|
-
});
|
|
2113
|
+
await finalizeDocumentCreation(doc, opts.link ?? false);
|
|
1767
2114
|
} catch (err) {
|
|
1768
2115
|
spinner.fail();
|
|
1769
2116
|
die(err);
|
|
1770
2117
|
}
|
|
1771
2118
|
}
|
|
1772
2119
|
);
|
|
2120
|
+
docs.command("create-link <url>").description(
|
|
2121
|
+
"Create a document that points to an external URL (no file upload)"
|
|
2122
|
+
).requiredOption("-n, --name <name>", "document name").option("--link", "also create a default share link for this document").option("--folder-id <id>", "place in folder by id").action(
|
|
2123
|
+
async (url, opts) => {
|
|
2124
|
+
try {
|
|
2125
|
+
const doc = await api.createDocument({
|
|
2126
|
+
name: opts.name,
|
|
2127
|
+
type: "link",
|
|
2128
|
+
external_url: url,
|
|
2129
|
+
folder_id: opts.folderId,
|
|
2130
|
+
create_link: opts.link ?? false
|
|
2131
|
+
});
|
|
2132
|
+
await finalizeDocumentCreation(doc, opts.link ?? false);
|
|
2133
|
+
} catch (err) {
|
|
2134
|
+
die(err);
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
);
|
|
2138
|
+
docs.command("create-notion <url>").description(
|
|
2139
|
+
"Create a document from a public Notion page URL (no file upload)"
|
|
2140
|
+
).requiredOption("-n, --name <name>", "document name").option("--link", "also create a default share link for this document").option("--folder-id <id>", "place in folder by id").action(
|
|
2141
|
+
async (url, opts) => {
|
|
2142
|
+
try {
|
|
2143
|
+
const doc = await api.createDocument({
|
|
2144
|
+
name: opts.name,
|
|
2145
|
+
type: "notion",
|
|
2146
|
+
external_url: url,
|
|
2147
|
+
folder_id: opts.folderId,
|
|
2148
|
+
create_link: opts.link ?? false
|
|
2149
|
+
});
|
|
2150
|
+
await finalizeDocumentCreation(doc, opts.link ?? false);
|
|
2151
|
+
} catch (err) {
|
|
2152
|
+
die(err);
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
);
|
|
1773
2156
|
const versions = docs.command("versions").description("Manage document versions");
|
|
1774
2157
|
versions.command("list <id>").description("List versions of a document, primary first").option("--json", "output raw JSON").action(async (id, opts) => {
|
|
1775
2158
|
if (opts.json) setRuntime({ json: true });
|
|
@@ -1848,6 +2231,32 @@ More results: --cursor ${res.next_cursor}`) : out;
|
|
|
1848
2231
|
die(err);
|
|
1849
2232
|
}
|
|
1850
2233
|
});
|
|
2234
|
+
versions.command("add-link <id> <url>").description("Repoint a link document at a new URL (adds a new version)").action(async (id, url) => {
|
|
2235
|
+
try {
|
|
2236
|
+
const v = await api.addDocumentVersion(id, { external_url: url });
|
|
2237
|
+
emit(
|
|
2238
|
+
v,
|
|
2239
|
+
() => chalk7.green("\u2713") + ` Version ${v.version_number} added: ${chalk7.bold(v.id)}
|
|
2240
|
+
` + json(v)
|
|
2241
|
+
);
|
|
2242
|
+
} catch (err) {
|
|
2243
|
+
die(err);
|
|
2244
|
+
}
|
|
2245
|
+
});
|
|
2246
|
+
versions.command("add-notion <id> <url>").description(
|
|
2247
|
+
"Repoint a notion document at a new Notion page URL (adds a new version)"
|
|
2248
|
+
).action(async (id, url) => {
|
|
2249
|
+
try {
|
|
2250
|
+
const v = await api.addDocumentVersion(id, { external_url: url });
|
|
2251
|
+
emit(
|
|
2252
|
+
v,
|
|
2253
|
+
() => chalk7.green("\u2713") + ` Version ${v.version_number} added: ${chalk7.bold(v.id)}
|
|
2254
|
+
` + json(v)
|
|
2255
|
+
);
|
|
2256
|
+
} catch (err) {
|
|
2257
|
+
die(err);
|
|
2258
|
+
}
|
|
2259
|
+
});
|
|
1851
2260
|
versions.command("promote <id> <vid>").description("Promote a version to primary").action(async (id, vid) => {
|
|
1852
2261
|
try {
|
|
1853
2262
|
const v = await api.promoteDocumentVersion(id, vid);
|
|
@@ -1861,6 +2270,31 @@ More results: --cursor ${res.next_cursor}`) : out;
|
|
|
1861
2270
|
}
|
|
1862
2271
|
});
|
|
1863
2272
|
}
|
|
2273
|
+
async function finalizeDocumentCreation(doc, wantLink) {
|
|
2274
|
+
let link = null;
|
|
2275
|
+
let linkWarning = null;
|
|
2276
|
+
if (wantLink) {
|
|
2277
|
+
try {
|
|
2278
|
+
const links = await api.listLinks({ document_id: doc.id, limit: 1 });
|
|
2279
|
+
link = links.data[0] ?? null;
|
|
2280
|
+
if (!link) {
|
|
2281
|
+
linkWarning = "--link was set but no link was returned. Try `papermark links list --document " + doc.id + "`.";
|
|
2282
|
+
}
|
|
2283
|
+
} catch (warnErr) {
|
|
2284
|
+
linkWarning = `Document created, but listing the share link failed: ${warnErr instanceof Error ? warnErr.message : String(warnErr)}`;
|
|
2285
|
+
}
|
|
2286
|
+
if (linkWarning) {
|
|
2287
|
+
process.stderr.write(chalk7.yellow("! ") + linkWarning + "\n");
|
|
2288
|
+
}
|
|
2289
|
+
}
|
|
2290
|
+
emit({ document: doc, link }, () => {
|
|
2291
|
+
const blocks = [json(doc)];
|
|
2292
|
+
if (link) {
|
|
2293
|
+
blocks.push(chalk7.green("\u2713") + " Share link: " + chalk7.bold(link.url));
|
|
2294
|
+
}
|
|
2295
|
+
return blocks.join("\n");
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
1864
2298
|
function truncate3(s, n) {
|
|
1865
2299
|
const clean = sanitizeForTerminal(s);
|
|
1866
2300
|
return clean.length > n ? clean.slice(0, n - 1) + "\u2026" : clean;
|
|
@@ -2104,7 +2538,7 @@ function addWatermarkOptions(cmd) {
|
|
|
2104
2538
|
}
|
|
2105
2539
|
function registerLinksCommands(program2) {
|
|
2106
2540
|
const links = program2.command("links").description("Manage share links");
|
|
2107
|
-
links.command("list").description("List share links").option("-l, --limit <n>", "max results (1-100)", "25").option("-c, --cursor <id>", "pagination cursor").option("--document <id>", "filter by document id").option("--dataroom <id>", "filter by dataroom id").option("--json", "output raw JSON (same as --json on the root command)").action(
|
|
2541
|
+
links.command("list").description("List share links").option("-l, --limit <n>", "max results (1-100)", "25").option("-c, --cursor <id>", "pagination cursor").option("--document <id>", "filter by document id").option("--dataroom <id>", "filter by dataroom id").option("--group <gid>", "filter by dataroom group id (group links only)").option("--json", "output raw JSON (same as --json on the root command)").action(
|
|
2108
2542
|
async (opts) => {
|
|
2109
2543
|
if (opts.json) setRuntime({ json: true });
|
|
2110
2544
|
try {
|
|
@@ -2112,7 +2546,8 @@ function registerLinksCommands(program2) {
|
|
|
2112
2546
|
limit: opts.limit ? Number(opts.limit) : void 0,
|
|
2113
2547
|
cursor: opts.cursor,
|
|
2114
2548
|
document_id: opts.document,
|
|
2115
|
-
dataroom_id: opts.dataroom
|
|
2549
|
+
dataroom_id: opts.dataroom,
|
|
2550
|
+
group_id: opts.group
|
|
2116
2551
|
});
|
|
2117
2552
|
emit(res, () => {
|
|
2118
2553
|
const rows = res.data.map((l) => ({
|
|
@@ -2141,7 +2576,10 @@ More results: --cursor ${res.next_cursor}`) : out;
|
|
|
2141
2576
|
}
|
|
2142
2577
|
});
|
|
2143
2578
|
addWatermarkOptions(
|
|
2144
|
-
links.command("create").description("Create a share link for a document or dataroom").option("--document <id>", "document id").option("--dataroom <id>", "dataroom id").option(
|
|
2579
|
+
links.command("create").description("Create a share link for a document or dataroom").option("--document <id>", "document id").option("--dataroom <id>", "dataroom id").option(
|
|
2580
|
+
"--group <gid>",
|
|
2581
|
+
"scope to a dataroom group (dataroom links only; forces email protection)"
|
|
2582
|
+
).option("-n, --name <name>", "link name").option("--expires <iso-date>", "expiration date (ISO 8601)").option("--password <pw>", "require a password to view").option("--email-protected", "require email before viewing", false).option("--allow-download", "allow viewer to download the file", false).option(
|
|
2145
2583
|
"--confidential-view",
|
|
2146
2584
|
"reveal only a narrow band of each page at a time; blurs the rest to prevent full-page screenshots",
|
|
2147
2585
|
false
|
|
@@ -2152,11 +2590,16 @@ More results: --cursor ${res.next_cursor}`) : out;
|
|
|
2152
2590
|
if (!opts.document && !opts.dataroom) {
|
|
2153
2591
|
throw new Error("Either --document <id> or --dataroom <id> is required.");
|
|
2154
2592
|
}
|
|
2593
|
+
if (opts.group && !opts.dataroom) {
|
|
2594
|
+
throw new Error("--group requires --dataroom <id>.");
|
|
2595
|
+
}
|
|
2155
2596
|
const watermarkConfig = parseWatermarkConfig(opts);
|
|
2156
2597
|
const enableWatermark = opts.watermark || watermarkConfig !== void 0;
|
|
2157
2598
|
const link = await api.createLink({
|
|
2158
2599
|
document_id: opts.document,
|
|
2159
2600
|
dataroom_id: opts.dataroom,
|
|
2601
|
+
audience_type: opts.group ? "group" : void 0,
|
|
2602
|
+
group_id: opts.group,
|
|
2160
2603
|
name: opts.name,
|
|
2161
2604
|
expires_at: opts.expires,
|
|
2162
2605
|
password: opts.password,
|
|
@@ -2176,7 +2619,13 @@ More results: --cursor ${res.next_cursor}`) : out;
|
|
|
2176
2619
|
}
|
|
2177
2620
|
);
|
|
2178
2621
|
addWatermarkOptions(
|
|
2179
|
-
links.command("update <id>").description("Update an existing share link").option(
|
|
2622
|
+
links.command("update <id>").description("Update an existing share link").option(
|
|
2623
|
+
"--audience <general|group>",
|
|
2624
|
+
"switch the link's audience (group links need --group or an existing group)"
|
|
2625
|
+
).option(
|
|
2626
|
+
"--group <gid>",
|
|
2627
|
+
"dataroom group id (with --audience group, or alone to swap the group of a group link)"
|
|
2628
|
+
).option("-n, --name <name>").option("--expires <iso-date>", "expiration date (ISO 8601), or empty string to clear").option("--password <pw>", "set a password (empty string to clear)").option("--email-protected <on|off>").option("--email-authenticated <on|off>").option("--allow-download <on|off>").option(
|
|
2180
2629
|
"--allow-list <items>",
|
|
2181
2630
|
"comma-separated email/domain allow list (e.g. '@acme.com,bob@x.com')"
|
|
2182
2631
|
).option("--deny-list <items>", "comma-separated email/domain deny list").option("--watermark <on|off>", "enable watermark overlay").option("--watermark-clear", "clear the existing watermark config", false).option("--screenshot-protection <on|off>", "blur on screenshot").option(
|
|
@@ -2197,6 +2646,16 @@ More results: --cursor ${res.next_cursor}`) : out;
|
|
|
2197
2646
|
};
|
|
2198
2647
|
const list = (v) => v === void 0 ? void 0 : v === "" ? [] : v.split(",").map((s) => s.trim()).filter(Boolean);
|
|
2199
2648
|
const body = {};
|
|
2649
|
+
if (opts.audience !== void 0) {
|
|
2650
|
+
const norm = opts.audience.toLowerCase();
|
|
2651
|
+
if (norm !== "general" && norm !== "group") {
|
|
2652
|
+
throw new Error(
|
|
2653
|
+
`Invalid value for --audience: ${JSON.stringify(opts.audience)} (expected "general" or "group").`
|
|
2654
|
+
);
|
|
2655
|
+
}
|
|
2656
|
+
body.audience_type = norm;
|
|
2657
|
+
}
|
|
2658
|
+
if (opts.group !== void 0) body.group_id = opts.group;
|
|
2200
2659
|
if (opts.name !== void 0) body.name = opts.name;
|
|
2201
2660
|
if (opts.expires !== void 0)
|
|
2202
2661
|
body.expires_at = opts.expires === "" ? null : opts.expires;
|
|
@@ -2257,6 +2716,64 @@ More results: --cursor ${res.next_cursor}`) : out;
|
|
|
2257
2716
|
die(err);
|
|
2258
2717
|
}
|
|
2259
2718
|
});
|
|
2719
|
+
const linkPermissions = links.command("permissions").description(
|
|
2720
|
+
"Per-item file permissions for dataroom links (ignored on group-audience links \u2014 the group's permissions win)"
|
|
2721
|
+
);
|
|
2722
|
+
linkPermissions.command("get <id>").description("List a dataroom link's per-item permission overrides").option("--json", "output raw JSON").action(async (id, opts) => {
|
|
2723
|
+
if (opts.json) setRuntime({ json: true });
|
|
2724
|
+
try {
|
|
2725
|
+
const res = await api.getLinkPermissions(id);
|
|
2726
|
+
emit(res, () => {
|
|
2727
|
+
if (res.data.length === 0) {
|
|
2728
|
+
return chalk9.dim(
|
|
2729
|
+
"No per-link overrides \u2014 viewers see the full dataroom."
|
|
2730
|
+
);
|
|
2731
|
+
}
|
|
2732
|
+
const rows = res.data.map((p) => ({
|
|
2733
|
+
item_id: p.item_id,
|
|
2734
|
+
type: p.item_type === "dataroom_document" ? "document" : "folder",
|
|
2735
|
+
view: p.can_view ? "yes" : "",
|
|
2736
|
+
download: p.can_download ? "yes" : ""
|
|
2737
|
+
}));
|
|
2738
|
+
return table(rows, ["item_id", "type", "view", "download"]);
|
|
2739
|
+
});
|
|
2740
|
+
} catch (err) {
|
|
2741
|
+
die(err);
|
|
2742
|
+
}
|
|
2743
|
+
});
|
|
2744
|
+
linkPermissions.command("set <id>").description(
|
|
2745
|
+
"Replace a dataroom link's per-item permissions (full replace: items not listed lose their override; --clear removes all overrides and hides every item). Item ids come from `datarooms documents` / `datarooms folders list`."
|
|
2746
|
+
).option(
|
|
2747
|
+
"--file <path>",
|
|
2748
|
+
"JSON array of { item_id, item_type, can_view, can_download }"
|
|
2749
|
+
).option("--item <itemId>", "single item id (use with --type/--view/--download)").option("--type <document|folder>", "item type for --item").option("--view <on|off>", "allow viewing (default on)").option("--download <on|off>", "allow downloading (default off)").option("--clear", "remove all overrides (hides every item on this link)", false).action(
|
|
2750
|
+
async (id, opts) => {
|
|
2751
|
+
try {
|
|
2752
|
+
if (opts.clear && (opts.file || opts.item)) {
|
|
2753
|
+
throw new Error("Pass either --clear or permission input, not both.");
|
|
2754
|
+
}
|
|
2755
|
+
const permissions = opts.clear ? [] : parsePermissionEntries(opts);
|
|
2756
|
+
const res = await api.setLinkPermissions(id, { permissions });
|
|
2757
|
+
emit(res, () => {
|
|
2758
|
+
if (res.data.length === 0) {
|
|
2759
|
+
return chalk9.green("\u2713") + " All overrides cleared \u2014 every item on this link is now hidden.";
|
|
2760
|
+
}
|
|
2761
|
+
return chalk9.green("\u2713") + ` ${res.data.length} permission row(s) now effective:
|
|
2762
|
+
` + table(
|
|
2763
|
+
res.data.map((p) => ({
|
|
2764
|
+
item_id: p.item_id,
|
|
2765
|
+
type: p.item_type === "dataroom_document" ? "document" : "folder",
|
|
2766
|
+
view: p.can_view ? "yes" : "",
|
|
2767
|
+
download: p.can_download ? "yes" : ""
|
|
2768
|
+
})),
|
|
2769
|
+
["item_id", "type", "view", "download"]
|
|
2770
|
+
);
|
|
2771
|
+
});
|
|
2772
|
+
} catch (err) {
|
|
2773
|
+
die(err);
|
|
2774
|
+
}
|
|
2775
|
+
}
|
|
2776
|
+
);
|
|
2260
2777
|
}
|
|
2261
2778
|
function truncate5(s, n) {
|
|
2262
2779
|
const clean = sanitizeForTerminal(s);
|