@sinoia/hubdoc-tools 1.5.1 → 1.6.1
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/cli.js +561 -72
- package/docs/llm-usage.md +71 -1
- package/package.json +1 -1
package/cli.js
CHANGED
|
@@ -13297,9 +13297,9 @@ var require_groupBy = __commonJS({
|
|
|
13297
13297
|
} else {
|
|
13298
13298
|
duration = elementOrOptions.duration, element = elementOrOptions.element, connector = elementOrOptions.connector;
|
|
13299
13299
|
}
|
|
13300
|
-
var
|
|
13300
|
+
var groups2 = /* @__PURE__ */ new Map();
|
|
13301
13301
|
var notify = function(cb) {
|
|
13302
|
-
|
|
13302
|
+
groups2.forEach(cb);
|
|
13303
13303
|
cb(subscriber);
|
|
13304
13304
|
};
|
|
13305
13305
|
var handleError = function(err) {
|
|
@@ -13312,9 +13312,9 @@ var require_groupBy = __commonJS({
|
|
|
13312
13312
|
var groupBySourceSubscriber = new OperatorSubscriber_1.OperatorSubscriber(subscriber, function(value2) {
|
|
13313
13313
|
try {
|
|
13314
13314
|
var key_1 = keySelector(value2);
|
|
13315
|
-
var group_1 =
|
|
13315
|
+
var group_1 = groups2.get(key_1);
|
|
13316
13316
|
if (!group_1) {
|
|
13317
|
-
|
|
13317
|
+
groups2.set(key_1, group_1 = connector ? connector() : new Subject_1.Subject());
|
|
13318
13318
|
var grouped = createGroupedObservable(key_1, group_1);
|
|
13319
13319
|
subscriber.next(grouped);
|
|
13320
13320
|
if (duration) {
|
|
@@ -13322,7 +13322,7 @@ var require_groupBy = __commonJS({
|
|
|
13322
13322
|
group_1.complete();
|
|
13323
13323
|
durationSubscriber_1 === null || durationSubscriber_1 === void 0 ? void 0 : durationSubscriber_1.unsubscribe();
|
|
13324
13324
|
}, void 0, void 0, function() {
|
|
13325
|
-
return
|
|
13325
|
+
return groups2.delete(key_1);
|
|
13326
13326
|
});
|
|
13327
13327
|
groupBySourceSubscriber.add(innerFrom_1.innerFrom(duration(grouped)).subscribe(durationSubscriber_1));
|
|
13328
13328
|
}
|
|
@@ -13336,7 +13336,7 @@ var require_groupBy = __commonJS({
|
|
|
13336
13336
|
return consumer.complete();
|
|
13337
13337
|
});
|
|
13338
13338
|
}, handleError, function() {
|
|
13339
|
-
return
|
|
13339
|
+
return groups2.clear();
|
|
13340
13340
|
}, function() {
|
|
13341
13341
|
teardownAttempted = true;
|
|
13342
13342
|
return activeGroups === 0;
|
|
@@ -71642,8 +71642,8 @@ function estimateDataURLDecodedBytes(url2) {
|
|
|
71642
71642
|
pad++;
|
|
71643
71643
|
}
|
|
71644
71644
|
}
|
|
71645
|
-
const
|
|
71646
|
-
const bytes =
|
|
71645
|
+
const groups2 = Math.floor(effectiveLen / 4);
|
|
71646
|
+
const bytes = groups2 * 3 - (pad || 0);
|
|
71647
71647
|
return bytes > 0 ? bytes : 0;
|
|
71648
71648
|
}
|
|
71649
71649
|
return Buffer.byteLength(body, "utf8");
|
|
@@ -75269,6 +75269,12 @@ var GroupsApi = class extends BaseAPI {
|
|
|
75269
75269
|
return GroupsApiFp(this.configuration).apiV1GroupsPost(contentType, groupMutation, options).then((request) => request(this.axios, this.basePath));
|
|
75270
75270
|
}
|
|
75271
75271
|
};
|
|
75272
|
+
var ApiV1GroupsIdPatchContentTypeEnum = {
|
|
75273
|
+
ApplicationJson: "application/json"
|
|
75274
|
+
};
|
|
75275
|
+
var ApiV1GroupsPostContentTypeEnum = {
|
|
75276
|
+
ApplicationJson: "application/json"
|
|
75277
|
+
};
|
|
75272
75278
|
|
|
75273
75279
|
// packages/api-client/src/api/permissions-api.ts
|
|
75274
75280
|
var PermissionsApiAxiosParamCreator = function(configuration) {
|
|
@@ -76328,7 +76334,7 @@ var OAuthTokenService = class {
|
|
|
76328
76334
|
res.end("<h1>404 - Not Found</h1><p>This is the OAuth callback server.</p>");
|
|
76329
76335
|
}
|
|
76330
76336
|
});
|
|
76331
|
-
this.server.listen(port,
|
|
76337
|
+
this.server.listen(port, () => {
|
|
76332
76338
|
console.log(import_chalk2.default.gray(`\u{1F5A5}\uFE0F Local server started on port ${port}`));
|
|
76333
76339
|
});
|
|
76334
76340
|
this.server.on("error", (error) => {
|
|
@@ -76459,7 +76465,7 @@ var OAuthTokenService = class {
|
|
|
76459
76465
|
isPortAvailable(port) {
|
|
76460
76466
|
return new Promise((resolve) => {
|
|
76461
76467
|
const server = import_http4.default.createServer();
|
|
76462
|
-
server.listen(port,
|
|
76468
|
+
server.listen(port, () => {
|
|
76463
76469
|
server.close(() => resolve(true));
|
|
76464
76470
|
});
|
|
76465
76471
|
server.on("error", () => resolve(false));
|
|
@@ -77133,20 +77139,20 @@ var PermissionManager = class {
|
|
|
77133
77139
|
console.log(import_chalk5.default.gray("\u{1F4E5} Pre-loading users and groups cache..."));
|
|
77134
77140
|
try {
|
|
77135
77141
|
const usersResponse = await this.usersApi.apiV1UsersGet(void 0, void 0, 100);
|
|
77136
|
-
const
|
|
77137
|
-
|
|
77142
|
+
const users2 = usersResponse.data;
|
|
77143
|
+
users2.forEach((user) => {
|
|
77138
77144
|
if (user.email_address) {
|
|
77139
77145
|
this.userCache.set(user.email_address.toLowerCase(), user);
|
|
77140
77146
|
}
|
|
77141
77147
|
});
|
|
77142
77148
|
const groupsResponse = await this.groupsApi.apiV1GroupsGet(void 0, void 0, 100);
|
|
77143
|
-
const
|
|
77144
|
-
|
|
77149
|
+
const groups2 = groupsResponse.data;
|
|
77150
|
+
groups2.forEach((group) => {
|
|
77145
77151
|
if (group.name) {
|
|
77146
77152
|
this.groupCache.set(group.name.toLowerCase(), group);
|
|
77147
77153
|
}
|
|
77148
77154
|
});
|
|
77149
|
-
console.log(import_chalk5.default.gray(`\u{1F4E6} Cached ${
|
|
77155
|
+
console.log(import_chalk5.default.gray(`\u{1F4E6} Cached ${users2.length} users and ${groups2.length} groups`));
|
|
77150
77156
|
} catch (error) {
|
|
77151
77157
|
console.warn(import_chalk5.default.yellow(`\u26A0\uFE0F Warning: Failed to preload cache: ${error.message}`));
|
|
77152
77158
|
}
|
|
@@ -77168,9 +77174,9 @@ var PermissionManager = class {
|
|
|
77168
77174
|
1,
|
|
77169
77175
|
{ "q[email_address_eq]": normalizedEmail }
|
|
77170
77176
|
);
|
|
77171
|
-
const
|
|
77172
|
-
if (
|
|
77173
|
-
const user =
|
|
77177
|
+
const users2 = response.data;
|
|
77178
|
+
if (users2.length > 0) {
|
|
77179
|
+
const user = users2[0];
|
|
77174
77180
|
this.userCache.set(normalizedEmail, user);
|
|
77175
77181
|
return user.id;
|
|
77176
77182
|
}
|
|
@@ -77199,9 +77205,9 @@ var PermissionManager = class {
|
|
|
77199
77205
|
1,
|
|
77200
77206
|
{ "q[name_eq]": trimmedName }
|
|
77201
77207
|
);
|
|
77202
|
-
const
|
|
77203
|
-
if (
|
|
77204
|
-
const group =
|
|
77208
|
+
const groups2 = response.data;
|
|
77209
|
+
if (groups2.length > 0) {
|
|
77210
|
+
const group = groups2[0];
|
|
77205
77211
|
this.groupCache.set(normalizedName, group);
|
|
77206
77212
|
return group.id;
|
|
77207
77213
|
}
|
|
@@ -77215,12 +77221,12 @@ var PermissionManager = class {
|
|
|
77215
77221
|
/**
|
|
77216
77222
|
* Résout les permissions (emails et noms de groupes) vers des IDs
|
|
77217
77223
|
*/
|
|
77218
|
-
async resolvePermissions(
|
|
77224
|
+
async resolvePermissions(permissions2) {
|
|
77219
77225
|
const userIds = [];
|
|
77220
77226
|
const groupIds = [];
|
|
77221
77227
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
77222
|
-
if (
|
|
77223
|
-
for (const emailOrId of
|
|
77228
|
+
if (permissions2.users && permissions2.users.length > 0) {
|
|
77229
|
+
for (const emailOrId of permissions2.users) {
|
|
77224
77230
|
if (uuidRegex.test(emailOrId)) {
|
|
77225
77231
|
userIds.push(emailOrId);
|
|
77226
77232
|
} else {
|
|
@@ -77231,8 +77237,8 @@ var PermissionManager = class {
|
|
|
77231
77237
|
}
|
|
77232
77238
|
}
|
|
77233
77239
|
}
|
|
77234
|
-
if (
|
|
77235
|
-
for (const nameOrId of
|
|
77240
|
+
if (permissions2.groups && permissions2.groups.length > 0) {
|
|
77241
|
+
for (const nameOrId of permissions2.groups) {
|
|
77236
77242
|
if (uuidRegex.test(nameOrId)) {
|
|
77237
77243
|
groupIds.push(nameOrId);
|
|
77238
77244
|
} else {
|
|
@@ -77248,9 +77254,9 @@ var PermissionManager = class {
|
|
|
77248
77254
|
/**
|
|
77249
77255
|
* Assigne les permissions à un document
|
|
77250
77256
|
*/
|
|
77251
|
-
async assignDocumentPermissions(documentId,
|
|
77257
|
+
async assignDocumentPermissions(documentId, permissions2, level) {
|
|
77252
77258
|
try {
|
|
77253
|
-
const { userIds, groupIds } = await this.resolvePermissions(
|
|
77259
|
+
const { userIds, groupIds } = await this.resolvePermissions(permissions2);
|
|
77254
77260
|
if (userIds.length === 0 && groupIds.length === 0) {
|
|
77255
77261
|
console.warn(import_chalk5.default.yellow(`\u26A0\uFE0F No valid permissions to assign for document ${documentId}`));
|
|
77256
77262
|
return;
|
|
@@ -77630,6 +77636,9 @@ var HubDocApiService = class {
|
|
|
77630
77636
|
if (mapping.autoClassify !== void 0) {
|
|
77631
77637
|
formData.append("auto_classify", String(mapping.autoClassify));
|
|
77632
77638
|
}
|
|
77639
|
+
if (mapping.externalId) {
|
|
77640
|
+
formData.append("external_id", mapping.externalId);
|
|
77641
|
+
}
|
|
77633
77642
|
if (mapping.metadata) {
|
|
77634
77643
|
formData.append("metadata_user", JSON.stringify(mapping.metadata));
|
|
77635
77644
|
}
|
|
@@ -84522,6 +84531,7 @@ var CsvManager = class {
|
|
|
84522
84531
|
filePath,
|
|
84523
84532
|
targetFolder: "",
|
|
84524
84533
|
workspace: "",
|
|
84534
|
+
externalId: "",
|
|
84525
84535
|
metadata: "",
|
|
84526
84536
|
permissions: "",
|
|
84527
84537
|
autoClassify: "false",
|
|
@@ -84538,6 +84548,7 @@ var CsvManager = class {
|
|
|
84538
84548
|
{ id: "filePath", title: "File Path" },
|
|
84539
84549
|
{ id: "targetFolder", title: "Target Folder" },
|
|
84540
84550
|
{ id: "workspace", title: "Workspace" },
|
|
84551
|
+
{ id: "externalId", title: "External ID" },
|
|
84541
84552
|
{ id: "metadata", title: "Metadata (key: value, parent.child: value)" },
|
|
84542
84553
|
{ id: "permissions", title: "Permissions (users:r:email1,email2 | users:w:admin | groups:Group Name)" },
|
|
84543
84554
|
{ id: "autoClassify", title: "Auto Classify" },
|
|
@@ -84571,7 +84582,7 @@ var CsvManager = class {
|
|
|
84571
84582
|
})).on("data", (data) => {
|
|
84572
84583
|
try {
|
|
84573
84584
|
const permissionsStr = data["Permissions (users:r:email1,email2 | users:w:admin | groups:Group Name)"] || data["Permissions (users:r:email1@ex.com,email2@ex.com | users:w:admin@ex.com | groups:GroupName)"] || data["Permissions"] || data.Permissions || data.permissions || "";
|
|
84574
|
-
const
|
|
84585
|
+
const permissions2 = this.parsePermissions(permissionsStr);
|
|
84575
84586
|
const cleanValue = (value2) => {
|
|
84576
84587
|
if (value2 === void 0 || value2 === null) return void 0;
|
|
84577
84588
|
const trimmed = String(value2).trim();
|
|
@@ -84581,10 +84592,11 @@ var CsvManager = class {
|
|
|
84581
84592
|
filePath: data["File Path"] ?? data.filePath,
|
|
84582
84593
|
targetFolder: cleanValue(data["Target Folder"] ?? data.targetFolder),
|
|
84583
84594
|
workspace: cleanValue(data["Workspace"] ?? data.workspace),
|
|
84595
|
+
externalId: cleanValue(data["External ID"] ?? data.externalId),
|
|
84584
84596
|
metadata: this.parseMetadata(
|
|
84585
84597
|
data["Metadata (key: value, parent.child: value)"] || data["Metadata (JSON)"] || data.Metadata || data.metadata
|
|
84586
84598
|
),
|
|
84587
|
-
permissions,
|
|
84599
|
+
permissions: permissions2,
|
|
84588
84600
|
autoClassify: this.parseBoolean(data["Auto Classify"] || data.autoClassify),
|
|
84589
84601
|
status: data["Status"] || data.status || "pending",
|
|
84590
84602
|
progress: parseFloat(data["Progress (%)"] || data.progress || "0"),
|
|
@@ -84753,18 +84765,18 @@ var CsvManager = class {
|
|
|
84753
84765
|
}
|
|
84754
84766
|
} else if (type === "groups") {
|
|
84755
84767
|
if (parts2.length === 2) {
|
|
84756
|
-
const
|
|
84757
|
-
if (
|
|
84758
|
-
readPermissions.groups = [...readPermissions.groups || [], ...
|
|
84768
|
+
const groups2 = parts2[1].split(",").map((g) => g.trim()).filter((g) => g);
|
|
84769
|
+
if (groups2.length > 0) {
|
|
84770
|
+
readPermissions.groups = [...readPermissions.groups || [], ...groups2];
|
|
84759
84771
|
}
|
|
84760
84772
|
} else if (parts2.length === 3) {
|
|
84761
84773
|
const level = parts2[1];
|
|
84762
|
-
const
|
|
84763
|
-
if (
|
|
84774
|
+
const groups2 = parts2[2].split(",").map((g) => g.trim()).filter((g) => g);
|
|
84775
|
+
if (groups2.length > 0) {
|
|
84764
84776
|
if (level === "w") {
|
|
84765
|
-
writePermissions.groups = [...writePermissions.groups || [], ...
|
|
84777
|
+
writePermissions.groups = [...writePermissions.groups || [], ...groups2];
|
|
84766
84778
|
} else {
|
|
84767
|
-
readPermissions.groups = [...readPermissions.groups || [], ...
|
|
84779
|
+
readPermissions.groups = [...readPermissions.groups || [], ...groups2];
|
|
84768
84780
|
}
|
|
84769
84781
|
}
|
|
84770
84782
|
}
|
|
@@ -84880,6 +84892,7 @@ var CsvManager = class {
|
|
|
84880
84892
|
filePath: m.filePath,
|
|
84881
84893
|
targetFolder: m.targetFolder || "",
|
|
84882
84894
|
workspace: m.workspace || "",
|
|
84895
|
+
externalId: m.externalId || "",
|
|
84883
84896
|
metadata: this.serializeMetadata(m.metadata),
|
|
84884
84897
|
permissions: this.serializePermissions(m.permissions),
|
|
84885
84898
|
autoClassify: m.autoClassify !== void 0 ? String(m.autoClassify) : "false",
|
|
@@ -84896,6 +84909,7 @@ var CsvManager = class {
|
|
|
84896
84909
|
{ id: "filePath", title: "File Path" },
|
|
84897
84910
|
{ id: "targetFolder", title: "Target Folder" },
|
|
84898
84911
|
{ id: "workspace", title: "Workspace" },
|
|
84912
|
+
{ id: "externalId", title: "External ID" },
|
|
84899
84913
|
{ id: "metadata", title: "Metadata (key: value, parent.child: value)" },
|
|
84900
84914
|
{ id: "permissions", title: "Permissions (users:r:email1,email2 | users:w:admin | groups:Group Name)" },
|
|
84901
84915
|
{ id: "autoClassify", title: "Auto Classify" },
|
|
@@ -84915,20 +84929,20 @@ var CsvManager = class {
|
|
|
84915
84929
|
* Serialize permissions in new format:
|
|
84916
84930
|
* users:r:email1@ex.com,email2@ex.com | users:w:admin@ex.com | groups:Group Name | groups:w:Admin Group
|
|
84917
84931
|
*/
|
|
84918
|
-
serializePermissions(
|
|
84919
|
-
if (!
|
|
84932
|
+
serializePermissions(permissions2) {
|
|
84933
|
+
if (!permissions2) return "";
|
|
84920
84934
|
const parts2 = [];
|
|
84921
|
-
if (
|
|
84922
|
-
parts2.push(`users:r:${
|
|
84935
|
+
if (permissions2.read?.users && permissions2.read.users.length > 0) {
|
|
84936
|
+
parts2.push(`users:r:${permissions2.read.users.join(",")}`);
|
|
84923
84937
|
}
|
|
84924
|
-
if (
|
|
84925
|
-
parts2.push(`groups:${
|
|
84938
|
+
if (permissions2.read?.groups && permissions2.read.groups.length > 0) {
|
|
84939
|
+
parts2.push(`groups:${permissions2.read.groups.join(",")}`);
|
|
84926
84940
|
}
|
|
84927
|
-
if (
|
|
84928
|
-
parts2.push(`users:w:${
|
|
84941
|
+
if (permissions2.write?.users && permissions2.write.users.length > 0) {
|
|
84942
|
+
parts2.push(`users:w:${permissions2.write.users.join(",")}`);
|
|
84929
84943
|
}
|
|
84930
|
-
if (
|
|
84931
|
-
parts2.push(`groups:w:${
|
|
84944
|
+
if (permissions2.write?.groups && permissions2.write.groups.length > 0) {
|
|
84945
|
+
parts2.push(`groups:w:${permissions2.write.groups.join(",")}`);
|
|
84932
84946
|
}
|
|
84933
84947
|
return parts2.join(" | ");
|
|
84934
84948
|
}
|
|
@@ -85861,7 +85875,7 @@ var CsvManager2 = class {
|
|
|
85861
85875
|
})).on("data", (data) => {
|
|
85862
85876
|
try {
|
|
85863
85877
|
const permissionsStr = data["Permissions (users:r:email1,email2 | users:w:admin | groups:Group Name)"] || data["Permissions (users:r:email1@ex.com,email2@ex.com | users:w:admin@ex.com | groups:GroupName)"] || data["Permissions"] || data.Permissions || data.permissions || "";
|
|
85864
|
-
const
|
|
85878
|
+
const permissions2 = this.parsePermissions(permissionsStr);
|
|
85865
85879
|
const cleanValue = (value2) => {
|
|
85866
85880
|
if (value2 === void 0 || value2 === null) return void 0;
|
|
85867
85881
|
const trimmed = String(value2).trim();
|
|
@@ -85874,7 +85888,7 @@ var CsvManager2 = class {
|
|
|
85874
85888
|
metadata: this.parseMetadata(
|
|
85875
85889
|
data["Metadata (key: value, parent.child: value)"] || data["Metadata (JSON)"] || data.Metadata || data.metadata
|
|
85876
85890
|
),
|
|
85877
|
-
permissions,
|
|
85891
|
+
permissions: permissions2,
|
|
85878
85892
|
autoClassify: this.parseBoolean(data["Auto Classify"] || data.autoClassify),
|
|
85879
85893
|
status: data["Status"] || data.status || "pending",
|
|
85880
85894
|
progress: parseFloat(data["Progress (%)"] || data.progress || "0"),
|
|
@@ -86043,18 +86057,18 @@ var CsvManager2 = class {
|
|
|
86043
86057
|
}
|
|
86044
86058
|
} else if (type === "groups") {
|
|
86045
86059
|
if (parts2.length === 2) {
|
|
86046
|
-
const
|
|
86047
|
-
if (
|
|
86048
|
-
readPermissions.groups = [...readPermissions.groups || [], ...
|
|
86060
|
+
const groups2 = parts2[1].split(",").map((g) => g.trim()).filter((g) => g);
|
|
86061
|
+
if (groups2.length > 0) {
|
|
86062
|
+
readPermissions.groups = [...readPermissions.groups || [], ...groups2];
|
|
86049
86063
|
}
|
|
86050
86064
|
} else if (parts2.length === 3) {
|
|
86051
86065
|
const level = parts2[1];
|
|
86052
|
-
const
|
|
86053
|
-
if (
|
|
86066
|
+
const groups2 = parts2[2].split(",").map((g) => g.trim()).filter((g) => g);
|
|
86067
|
+
if (groups2.length > 0) {
|
|
86054
86068
|
if (level === "w") {
|
|
86055
|
-
writePermissions.groups = [...writePermissions.groups || [], ...
|
|
86069
|
+
writePermissions.groups = [...writePermissions.groups || [], ...groups2];
|
|
86056
86070
|
} else {
|
|
86057
|
-
readPermissions.groups = [...readPermissions.groups || [], ...
|
|
86071
|
+
readPermissions.groups = [...readPermissions.groups || [], ...groups2];
|
|
86058
86072
|
}
|
|
86059
86073
|
}
|
|
86060
86074
|
}
|
|
@@ -86205,20 +86219,20 @@ var CsvManager2 = class {
|
|
|
86205
86219
|
* Serialize permissions in new format:
|
|
86206
86220
|
* users:r:email1@ex.com,email2@ex.com | users:w:admin@ex.com | groups:Group Name | groups:w:Admin Group
|
|
86207
86221
|
*/
|
|
86208
|
-
serializePermissions(
|
|
86209
|
-
if (!
|
|
86222
|
+
serializePermissions(permissions2) {
|
|
86223
|
+
if (!permissions2) return "";
|
|
86210
86224
|
const parts2 = [];
|
|
86211
|
-
if (
|
|
86212
|
-
parts2.push(`users:r:${
|
|
86225
|
+
if (permissions2.read?.users && permissions2.read.users.length > 0) {
|
|
86226
|
+
parts2.push(`users:r:${permissions2.read.users.join(",")}`);
|
|
86213
86227
|
}
|
|
86214
|
-
if (
|
|
86215
|
-
parts2.push(`groups:${
|
|
86228
|
+
if (permissions2.read?.groups && permissions2.read.groups.length > 0) {
|
|
86229
|
+
parts2.push(`groups:${permissions2.read.groups.join(",")}`);
|
|
86216
86230
|
}
|
|
86217
|
-
if (
|
|
86218
|
-
parts2.push(`users:w:${
|
|
86231
|
+
if (permissions2.write?.users && permissions2.write.users.length > 0) {
|
|
86232
|
+
parts2.push(`users:w:${permissions2.write.users.join(",")}`);
|
|
86219
86233
|
}
|
|
86220
|
-
if (
|
|
86221
|
-
parts2.push(`groups:w:${
|
|
86234
|
+
if (permissions2.write?.groups && permissions2.write.groups.length > 0) {
|
|
86235
|
+
parts2.push(`groups:w:${permissions2.write.groups.join(",")}`);
|
|
86222
86236
|
}
|
|
86223
86237
|
return parts2.join(" | ");
|
|
86224
86238
|
}
|
|
@@ -86746,13 +86760,13 @@ var PluginImportService = class {
|
|
|
86746
86760
|
* Group mappings by connection ID
|
|
86747
86761
|
*/
|
|
86748
86762
|
groupMappingsByConnection(mappings) {
|
|
86749
|
-
return mappings.reduce((
|
|
86763
|
+
return mappings.reduce((groups2, mapping) => {
|
|
86750
86764
|
const { connectionId } = mapping;
|
|
86751
|
-
if (!
|
|
86752
|
-
|
|
86765
|
+
if (!groups2[connectionId]) {
|
|
86766
|
+
groups2[connectionId] = [];
|
|
86753
86767
|
}
|
|
86754
|
-
|
|
86755
|
-
return
|
|
86768
|
+
groups2[connectionId].push(mapping);
|
|
86769
|
+
return groups2;
|
|
86756
86770
|
}, {});
|
|
86757
86771
|
}
|
|
86758
86772
|
/**
|
|
@@ -87400,6 +87414,8 @@ async function buildApiContext(out) {
|
|
|
87400
87414
|
documents: new DocumentsApi(configuration, baseUrl, axiosInstance),
|
|
87401
87415
|
chunkedUploads: new ChunkedUploadsApi(configuration, baseUrl, axiosInstance),
|
|
87402
87416
|
users: new UsersApi(configuration, baseUrl, axiosInstance),
|
|
87417
|
+
groups: new GroupsApi(configuration, baseUrl, axiosInstance),
|
|
87418
|
+
permissions: new PermissionsApi(configuration, baseUrl, axiosInstance),
|
|
87403
87419
|
baseUrl,
|
|
87404
87420
|
token: token2,
|
|
87405
87421
|
tokenSource: resolved.source
|
|
@@ -89008,10 +89024,437 @@ async function handleTemplatesShow(idOrKey, opts) {
|
|
|
89008
89024
|
}
|
|
89009
89025
|
}
|
|
89010
89026
|
|
|
89027
|
+
// apps/cli/cli/handlers/user-handlers.ts
|
|
89028
|
+
var USER_COLUMNS = [
|
|
89029
|
+
{ header: "ID", key: "id" },
|
|
89030
|
+
{ header: "Email", key: "email_address" },
|
|
89031
|
+
{ header: "Name", key: "display_name" },
|
|
89032
|
+
{ header: "Role", key: "role" },
|
|
89033
|
+
{ header: "External", key: "external_id", maxWidth: 24 }
|
|
89034
|
+
];
|
|
89035
|
+
function matchesQuery(user, q) {
|
|
89036
|
+
const needle = q.toLowerCase();
|
|
89037
|
+
const haystack = [
|
|
89038
|
+
user.email_address,
|
|
89039
|
+
user.first_name,
|
|
89040
|
+
user.last_name,
|
|
89041
|
+
user.display_name,
|
|
89042
|
+
user.external_id
|
|
89043
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
89044
|
+
return haystack.includes(needle);
|
|
89045
|
+
}
|
|
89046
|
+
async function handleUsersList(opts) {
|
|
89047
|
+
const out = output(opts);
|
|
89048
|
+
const ctx = await buildApiContext(out);
|
|
89049
|
+
try {
|
|
89050
|
+
const res = await ctx.users.apiV1UsersGet(
|
|
89051
|
+
void 0,
|
|
89052
|
+
void 0,
|
|
89053
|
+
opts.limit ? Number(opts.limit) : void 0
|
|
89054
|
+
);
|
|
89055
|
+
let items = res.data ?? [];
|
|
89056
|
+
if (opts.q) items = items.filter((u) => matchesQuery(u, opts.q));
|
|
89057
|
+
if (items.length === 50 && !opts.q) {
|
|
89058
|
+
out.note("server may have capped result at 50 (see corex#460) \u2014 refine with --q to filter");
|
|
89059
|
+
}
|
|
89060
|
+
out.list(items, USER_COLUMNS);
|
|
89061
|
+
} catch (err) {
|
|
89062
|
+
out.fail(err);
|
|
89063
|
+
}
|
|
89064
|
+
}
|
|
89065
|
+
async function handleUsersShow(idOrEmail, opts) {
|
|
89066
|
+
const out = output(opts);
|
|
89067
|
+
const ctx = await buildApiContext(out);
|
|
89068
|
+
const looksLikeEmail = idOrEmail.includes("@");
|
|
89069
|
+
let found;
|
|
89070
|
+
try {
|
|
89071
|
+
if (!looksLikeEmail) {
|
|
89072
|
+
const res2 = await ctx.users.apiV1UsersIdGet(idOrEmail);
|
|
89073
|
+
out.show(res2.data, { title: `User ${idOrEmail}` });
|
|
89074
|
+
return;
|
|
89075
|
+
}
|
|
89076
|
+
const res = await ctx.users.apiV1UsersGet();
|
|
89077
|
+
const items = res.data ?? [];
|
|
89078
|
+
found = items.find((u) => (u.email_address || "").toLowerCase() === idOrEmail.toLowerCase());
|
|
89079
|
+
} catch (err) {
|
|
89080
|
+
out.fail(err);
|
|
89081
|
+
return;
|
|
89082
|
+
}
|
|
89083
|
+
if (!found) {
|
|
89084
|
+
out.fail(
|
|
89085
|
+
new Error(
|
|
89086
|
+
`User '${idOrEmail}' not found in the first 50 results. This is likely due to corex#460 (users API server-side filter is broken). Use the UUID directly if you have it.`
|
|
89087
|
+
)
|
|
89088
|
+
);
|
|
89089
|
+
}
|
|
89090
|
+
out.show(found, { title: `User ${idOrEmail}` });
|
|
89091
|
+
}
|
|
89092
|
+
|
|
89093
|
+
// apps/cli/cli/handlers/group-handlers.ts
|
|
89094
|
+
var GROUP_COLUMNS = [
|
|
89095
|
+
{ header: "ID", key: "id" },
|
|
89096
|
+
{ header: "Name", key: "name" },
|
|
89097
|
+
{ header: "Description", key: "description", maxWidth: 40 },
|
|
89098
|
+
{ header: "External", key: "external_id", maxWidth: 24 },
|
|
89099
|
+
{ header: "Updated", key: "updated_at" }
|
|
89100
|
+
];
|
|
89101
|
+
async function handleGroupsList(opts) {
|
|
89102
|
+
const out = output(opts);
|
|
89103
|
+
const ctx = await buildApiContext(out);
|
|
89104
|
+
try {
|
|
89105
|
+
const res = await ctx.groups.apiV1GroupsGet(
|
|
89106
|
+
void 0,
|
|
89107
|
+
void 0,
|
|
89108
|
+
opts.limit ? Number(opts.limit) : void 0,
|
|
89109
|
+
opts.page ? Number(opts.page) : void 0
|
|
89110
|
+
);
|
|
89111
|
+
let items = res.data ?? [];
|
|
89112
|
+
if (opts.q) {
|
|
89113
|
+
const needle = opts.q.toLowerCase();
|
|
89114
|
+
items = items.filter(
|
|
89115
|
+
(g) => (g.name || "").toLowerCase().includes(needle) || (g.description || "").toLowerCase().includes(needle)
|
|
89116
|
+
);
|
|
89117
|
+
}
|
|
89118
|
+
out.list(items, GROUP_COLUMNS);
|
|
89119
|
+
} catch (err) {
|
|
89120
|
+
out.fail(err);
|
|
89121
|
+
}
|
|
89122
|
+
}
|
|
89123
|
+
async function handleGroupsShow(id, opts) {
|
|
89124
|
+
const out = output(opts);
|
|
89125
|
+
const ctx = await buildApiContext(out);
|
|
89126
|
+
try {
|
|
89127
|
+
const res = await ctx.groups.apiV1GroupsIdGet(id);
|
|
89128
|
+
out.show(res.data, { title: `Group ${id}` });
|
|
89129
|
+
} catch (err) {
|
|
89130
|
+
out.fail(err);
|
|
89131
|
+
}
|
|
89132
|
+
}
|
|
89133
|
+
async function handleGroupsCreate(opts) {
|
|
89134
|
+
const out = output(opts);
|
|
89135
|
+
const ctx = await buildApiContext(out);
|
|
89136
|
+
const mutation = { name: opts.name };
|
|
89137
|
+
if (opts.description !== void 0) mutation.description = opts.description;
|
|
89138
|
+
if (opts.externalId !== void 0) mutation.external_id = opts.externalId;
|
|
89139
|
+
try {
|
|
89140
|
+
const res = await ctx.groups.apiV1GroupsPost(
|
|
89141
|
+
ApiV1GroupsPostContentTypeEnum.ApplicationJson,
|
|
89142
|
+
mutation
|
|
89143
|
+
);
|
|
89144
|
+
out.ok(`Group created (id=${res.data.id})`, res.data);
|
|
89145
|
+
} catch (err) {
|
|
89146
|
+
out.fail(err);
|
|
89147
|
+
}
|
|
89148
|
+
}
|
|
89149
|
+
async function handleGroupsUpdate(id, opts) {
|
|
89150
|
+
const out = output(opts);
|
|
89151
|
+
const ctx = await buildApiContext(out);
|
|
89152
|
+
if (opts.name === void 0 && opts.description === void 0 && opts.externalId === void 0) {
|
|
89153
|
+
out.fail(new Error("No fields to update"));
|
|
89154
|
+
}
|
|
89155
|
+
let nameValue = opts.name;
|
|
89156
|
+
if (nameValue === void 0) {
|
|
89157
|
+
try {
|
|
89158
|
+
const cur = await ctx.groups.apiV1GroupsIdGet(id);
|
|
89159
|
+
nameValue = cur.data.name;
|
|
89160
|
+
} catch (err) {
|
|
89161
|
+
out.fail(err);
|
|
89162
|
+
return;
|
|
89163
|
+
}
|
|
89164
|
+
}
|
|
89165
|
+
const mutation = { name: nameValue };
|
|
89166
|
+
if (opts.description !== void 0) mutation.description = opts.description;
|
|
89167
|
+
if (opts.externalId !== void 0) mutation.external_id = opts.externalId;
|
|
89168
|
+
try {
|
|
89169
|
+
const res = await ctx.groups.apiV1GroupsIdPatch(
|
|
89170
|
+
id,
|
|
89171
|
+
ApiV1GroupsIdPatchContentTypeEnum.ApplicationJson,
|
|
89172
|
+
mutation
|
|
89173
|
+
);
|
|
89174
|
+
out.ok(`Group ${id} updated`, res.data);
|
|
89175
|
+
} catch (err) {
|
|
89176
|
+
out.fail(err);
|
|
89177
|
+
}
|
|
89178
|
+
}
|
|
89179
|
+
async function handleGroupsDelete(id, opts) {
|
|
89180
|
+
const out = output(opts);
|
|
89181
|
+
const ctx = await buildApiContext(out);
|
|
89182
|
+
if (!opts.yes && !opts.json) {
|
|
89183
|
+
const { confirm } = await lib_default.prompt([
|
|
89184
|
+
{
|
|
89185
|
+
type: "confirm",
|
|
89186
|
+
name: "confirm",
|
|
89187
|
+
message: `Delete group ${id}? This removes all its members and permissions.`,
|
|
89188
|
+
default: false
|
|
89189
|
+
}
|
|
89190
|
+
]);
|
|
89191
|
+
if (!confirm) {
|
|
89192
|
+
out.note("Aborted");
|
|
89193
|
+
return;
|
|
89194
|
+
}
|
|
89195
|
+
} else if (!opts.yes && opts.json) {
|
|
89196
|
+
out.fail(new Error("Refusing to delete without --yes in --json mode"));
|
|
89197
|
+
}
|
|
89198
|
+
try {
|
|
89199
|
+
await ctx.groups.apiV1GroupsIdDelete(id);
|
|
89200
|
+
out.ok(`Group ${id} deleted`);
|
|
89201
|
+
} catch (err) {
|
|
89202
|
+
out.fail(err);
|
|
89203
|
+
}
|
|
89204
|
+
}
|
|
89205
|
+
|
|
89206
|
+
// apps/cli/cli/handlers/group-member-handlers.ts
|
|
89207
|
+
var MEMBER_COLUMNS = [
|
|
89208
|
+
{ header: "Membership ID", key: "id" },
|
|
89209
|
+
{ header: "Member ID", key: "member_id" },
|
|
89210
|
+
{ header: "Type", key: "member_type" },
|
|
89211
|
+
{ header: "External", key: "user_external_id", maxWidth: 24 },
|
|
89212
|
+
{ header: "Role", key: "role" }
|
|
89213
|
+
];
|
|
89214
|
+
function membersBase(groupId) {
|
|
89215
|
+
return `/api/v1/groups/${encodeURIComponent(groupId)}/members`;
|
|
89216
|
+
}
|
|
89217
|
+
async function handleGroupMembersList(opts) {
|
|
89218
|
+
const out = output(opts);
|
|
89219
|
+
const ctx = await buildApiContext(out);
|
|
89220
|
+
try {
|
|
89221
|
+
const res = await ctx.axios.get(membersBase(opts.group));
|
|
89222
|
+
out.list(res.data ?? [], MEMBER_COLUMNS);
|
|
89223
|
+
} catch (err) {
|
|
89224
|
+
out.fail(err);
|
|
89225
|
+
}
|
|
89226
|
+
}
|
|
89227
|
+
async function resolveExternalId(ctx, out, opts) {
|
|
89228
|
+
if (opts.userExternalId) return opts.userExternalId;
|
|
89229
|
+
if (opts.userId) {
|
|
89230
|
+
let ext2;
|
|
89231
|
+
try {
|
|
89232
|
+
const res = await ctx.users.apiV1UsersIdGet(opts.userId);
|
|
89233
|
+
ext2 = res.data?.external_id;
|
|
89234
|
+
} catch (err) {
|
|
89235
|
+
out.fail(err);
|
|
89236
|
+
return "";
|
|
89237
|
+
}
|
|
89238
|
+
if (!ext2) {
|
|
89239
|
+
out.fail(
|
|
89240
|
+
new Error(
|
|
89241
|
+
`User ${opts.userId} has no external_id \u2014 server requires it to create a membership.`
|
|
89242
|
+
)
|
|
89243
|
+
);
|
|
89244
|
+
}
|
|
89245
|
+
return ext2;
|
|
89246
|
+
}
|
|
89247
|
+
if (opts.userEmail) {
|
|
89248
|
+
let found;
|
|
89249
|
+
try {
|
|
89250
|
+
const res = await ctx.users.apiV1UsersGet();
|
|
89251
|
+
const items = res.data ?? [];
|
|
89252
|
+
found = items.find(
|
|
89253
|
+
(u) => (u.email_address || "").toLowerCase() === opts.userEmail.toLowerCase()
|
|
89254
|
+
);
|
|
89255
|
+
} catch (err) {
|
|
89256
|
+
out.fail(err);
|
|
89257
|
+
return "";
|
|
89258
|
+
}
|
|
89259
|
+
if (!found) {
|
|
89260
|
+
out.fail(
|
|
89261
|
+
new Error(
|
|
89262
|
+
`User '${opts.userEmail}' not found in the first 50 results. Server-side email filter is currently broken (corex#460). Look up the UUID via the web UI and pass --user-id, or pass --user-external-id directly.`
|
|
89263
|
+
)
|
|
89264
|
+
);
|
|
89265
|
+
}
|
|
89266
|
+
if (!found.external_id) {
|
|
89267
|
+
out.fail(
|
|
89268
|
+
new Error(
|
|
89269
|
+
`User '${opts.userEmail}' has no external_id \u2014 server requires it to create a membership.`
|
|
89270
|
+
)
|
|
89271
|
+
);
|
|
89272
|
+
}
|
|
89273
|
+
return found.external_id;
|
|
89274
|
+
}
|
|
89275
|
+
out.fail(
|
|
89276
|
+
new Error("Pass one of --user-external-id, --user-id, or --user-email to identify the user.")
|
|
89277
|
+
);
|
|
89278
|
+
return "";
|
|
89279
|
+
}
|
|
89280
|
+
async function handleGroupMembersAdd(opts) {
|
|
89281
|
+
const out = output(opts);
|
|
89282
|
+
const ctx = await buildApiContext(out);
|
|
89283
|
+
const externalId = await resolveExternalId(ctx, out, opts);
|
|
89284
|
+
try {
|
|
89285
|
+
const res = await ctx.axios.post(membersBase(opts.group), { user_external_id: externalId });
|
|
89286
|
+
out.ok(`Membership created (id=${res.data.id})`, res.data);
|
|
89287
|
+
} catch (err) {
|
|
89288
|
+
out.fail(err);
|
|
89289
|
+
}
|
|
89290
|
+
}
|
|
89291
|
+
async function handleGroupMembersRemove(membershipId, opts) {
|
|
89292
|
+
const out = output(opts);
|
|
89293
|
+
const ctx = await buildApiContext(out);
|
|
89294
|
+
if (!opts.yes && !opts.json) {
|
|
89295
|
+
const { confirm } = await lib_default.prompt([
|
|
89296
|
+
{
|
|
89297
|
+
type: "confirm",
|
|
89298
|
+
name: "confirm",
|
|
89299
|
+
message: `Remove membership ${membershipId} from group ${opts.group}?`,
|
|
89300
|
+
default: false
|
|
89301
|
+
}
|
|
89302
|
+
]);
|
|
89303
|
+
if (!confirm) {
|
|
89304
|
+
out.note("Aborted");
|
|
89305
|
+
return;
|
|
89306
|
+
}
|
|
89307
|
+
} else if (!opts.yes && opts.json) {
|
|
89308
|
+
out.fail(new Error("Refusing to remove without --yes in --json mode"));
|
|
89309
|
+
}
|
|
89310
|
+
try {
|
|
89311
|
+
await ctx.axios.delete(`${membersBase(opts.group)}/${encodeURIComponent(membershipId)}`);
|
|
89312
|
+
out.ok(`Membership ${membershipId} removed`);
|
|
89313
|
+
} catch (err) {
|
|
89314
|
+
out.fail(err);
|
|
89315
|
+
}
|
|
89316
|
+
}
|
|
89317
|
+
|
|
89318
|
+
// apps/cli/cli/handlers/permission-handlers.ts
|
|
89319
|
+
var PERM_COLUMNS = [
|
|
89320
|
+
{ header: "ID", key: "id" },
|
|
89321
|
+
{ header: "Actor", key: "actor_type", format: (v, row) => `${v}:${row.actor?.display_name ?? row.actor_id?.substring(0, 8)}` },
|
|
89322
|
+
{ header: "Level", key: "level" },
|
|
89323
|
+
{ header: "On", key: "permissible_type", format: (v) => v?.replace(/^Documents::/, "") ?? "" },
|
|
89324
|
+
{ header: "Resource ID", key: "permissible_id" },
|
|
89325
|
+
{ header: "Updated", key: "updated_at" }
|
|
89326
|
+
];
|
|
89327
|
+
var VALID_LEVELS = /* @__PURE__ */ new Set(["read", "write", "admin", "owner", "forbidden"]);
|
|
89328
|
+
var PERMISSIBLE_ALIASES = {
|
|
89329
|
+
workspace: "Documents::Workspace",
|
|
89330
|
+
folder: "Documents::Folder",
|
|
89331
|
+
file: "Documents::File",
|
|
89332
|
+
share_link: "Documents::ShareLink"
|
|
89333
|
+
};
|
|
89334
|
+
var ACTOR_ALIASES = {
|
|
89335
|
+
user: "User",
|
|
89336
|
+
group: "Group",
|
|
89337
|
+
contact: "Contact"
|
|
89338
|
+
};
|
|
89339
|
+
function normalizePermissibleType(input, out) {
|
|
89340
|
+
const lower = input.toLowerCase();
|
|
89341
|
+
if (PERMISSIBLE_ALIASES[lower]) return PERMISSIBLE_ALIASES[lower];
|
|
89342
|
+
if (input.startsWith("Documents::")) return input;
|
|
89343
|
+
out.fail(
|
|
89344
|
+
new Error(
|
|
89345
|
+
`Invalid --on-type: '${input}'. Use workspace/folder/file/share_link or a fully-qualified Documents::\u2026 class name.`
|
|
89346
|
+
)
|
|
89347
|
+
);
|
|
89348
|
+
return "";
|
|
89349
|
+
}
|
|
89350
|
+
function normalizeActorType(input, out) {
|
|
89351
|
+
const lower = input.toLowerCase();
|
|
89352
|
+
if (ACTOR_ALIASES[lower]) return ACTOR_ALIASES[lower];
|
|
89353
|
+
if (["User", "Group", "Contact"].includes(input)) return input;
|
|
89354
|
+
out.fail(new Error(`Invalid --to-type: '${input}'. Use user/group/contact.`));
|
|
89355
|
+
return "";
|
|
89356
|
+
}
|
|
89357
|
+
async function handlePermissionsList(opts) {
|
|
89358
|
+
const out = output(opts);
|
|
89359
|
+
const ctx = await buildApiContext(out);
|
|
89360
|
+
if (!opts.on) {
|
|
89361
|
+
out.fail(
|
|
89362
|
+
new Error(
|
|
89363
|
+
"--on=<resource-id> is required (the server rejects listing without permissible_id/type)."
|
|
89364
|
+
)
|
|
89365
|
+
);
|
|
89366
|
+
}
|
|
89367
|
+
const onType = opts.onType ? normalizePermissibleType(opts.onType, out) : "Documents::Workspace";
|
|
89368
|
+
try {
|
|
89369
|
+
const res = await ctx.axios.get("/api/v1/permissions", {
|
|
89370
|
+
params: {
|
|
89371
|
+
permissible_id: opts.on,
|
|
89372
|
+
permissible_type: onType
|
|
89373
|
+
}
|
|
89374
|
+
});
|
|
89375
|
+
let items = res.data ?? [];
|
|
89376
|
+
if (opts.to) items = items.filter((p) => p.actor_id === opts.to);
|
|
89377
|
+
if (opts.toType) {
|
|
89378
|
+
const norm = normalizeActorType(opts.toType, out);
|
|
89379
|
+
items = items.filter((p) => p.actor_type === norm);
|
|
89380
|
+
}
|
|
89381
|
+
if (opts.level) items = items.filter((p) => p.level === opts.level);
|
|
89382
|
+
out.list(items, PERM_COLUMNS);
|
|
89383
|
+
} catch (err) {
|
|
89384
|
+
out.fail(err);
|
|
89385
|
+
}
|
|
89386
|
+
}
|
|
89387
|
+
async function handlePermissionsGrant(opts) {
|
|
89388
|
+
const out = output(opts);
|
|
89389
|
+
const ctx = await buildApiContext(out);
|
|
89390
|
+
if (!VALID_LEVELS.has(opts.level)) {
|
|
89391
|
+
out.fail(
|
|
89392
|
+
new Error(
|
|
89393
|
+
`Invalid --level: '${opts.level}'. Expected one of: ${[...VALID_LEVELS].join(", ")}`
|
|
89394
|
+
)
|
|
89395
|
+
);
|
|
89396
|
+
}
|
|
89397
|
+
const actorType = normalizeActorType(opts.toType, out);
|
|
89398
|
+
const permissibleType = normalizePermissibleType(opts.onType, out);
|
|
89399
|
+
try {
|
|
89400
|
+
const body = {
|
|
89401
|
+
actor_id: opts.to,
|
|
89402
|
+
actor_type: actorType,
|
|
89403
|
+
permissible_id: opts.on,
|
|
89404
|
+
permissible_type: permissibleType,
|
|
89405
|
+
level: opts.level
|
|
89406
|
+
};
|
|
89407
|
+
if (opts.temporary) body.temporary = true;
|
|
89408
|
+
const res = await ctx.axios.post("/api/v1/permissions", body);
|
|
89409
|
+
out.ok(
|
|
89410
|
+
`Permission granted (${actorType} \u2192 ${opts.level} on ${permissibleType})`,
|
|
89411
|
+
res.data
|
|
89412
|
+
);
|
|
89413
|
+
} catch (err) {
|
|
89414
|
+
out.fail(err);
|
|
89415
|
+
}
|
|
89416
|
+
}
|
|
89417
|
+
async function handlePermissionsRevoke(id, opts) {
|
|
89418
|
+
const out = output(opts);
|
|
89419
|
+
const ctx = await buildApiContext(out);
|
|
89420
|
+
if (!opts.yes && !opts.json) {
|
|
89421
|
+
const { confirm } = await lib_default.prompt([
|
|
89422
|
+
{
|
|
89423
|
+
type: "confirm",
|
|
89424
|
+
name: "confirm",
|
|
89425
|
+
message: `Revoke permission ${id}?`,
|
|
89426
|
+
default: false
|
|
89427
|
+
}
|
|
89428
|
+
]);
|
|
89429
|
+
if (!confirm) {
|
|
89430
|
+
out.note("Aborted");
|
|
89431
|
+
return;
|
|
89432
|
+
}
|
|
89433
|
+
} else if (!opts.yes && opts.json) {
|
|
89434
|
+
out.fail(new Error("Refusing to revoke without --yes in --json mode"));
|
|
89435
|
+
}
|
|
89436
|
+
try {
|
|
89437
|
+
await ctx.permissions.apiV1PermissionsIdDelete(id);
|
|
89438
|
+
out.ok(`Permission ${id} revoked`);
|
|
89439
|
+
} catch (err) {
|
|
89440
|
+
out.fail(err);
|
|
89441
|
+
}
|
|
89442
|
+
}
|
|
89443
|
+
async function handlePermissionsShow(id, opts) {
|
|
89444
|
+
const out = output(opts);
|
|
89445
|
+
const ctx = await buildApiContext(out);
|
|
89446
|
+
try {
|
|
89447
|
+
const res = await ctx.permissions.apiV1PermissionsIdGet(id);
|
|
89448
|
+
out.show(res.data, { title: `Permission ${id}` });
|
|
89449
|
+
} catch (err) {
|
|
89450
|
+
out.fail(err);
|
|
89451
|
+
}
|
|
89452
|
+
}
|
|
89453
|
+
|
|
89011
89454
|
// apps/cli/cli.ts
|
|
89012
89455
|
var getVersion = () => {
|
|
89013
89456
|
try {
|
|
89014
|
-
if (true) return "1.
|
|
89457
|
+
if (true) return "1.6.1";
|
|
89015
89458
|
} catch {
|
|
89016
89459
|
}
|
|
89017
89460
|
for (const candidate of [
|
|
@@ -89241,6 +89684,52 @@ templates.command("ls").description("List active templates").action(async (optio
|
|
|
89241
89684
|
templates.command("show <id-or-key>").description("Show a template (full default_parts + page_settings)").action(async (idOrKey, options) => {
|
|
89242
89685
|
await handleTemplatesShow(idOrKey, withGlobals(options));
|
|
89243
89686
|
});
|
|
89687
|
+
var users = program2.command("users").description("Look up users (read-only)");
|
|
89688
|
+
users.command("ls").description("List users (currently capped at ~50 by corex#460)").option("--q <string>", "Filter results client-side by name/email/external_id").option("--limit <n>", "Server-side per_page (currently ignored, see corex#460)").action(async (options) => {
|
|
89689
|
+
await handleUsersList(withGlobals(options));
|
|
89690
|
+
});
|
|
89691
|
+
users.command("show <id-or-email>").description("Show a user by UUID or email (email lookup is capped, see corex#460)").action(async (id, options) => {
|
|
89692
|
+
await handleUsersShow(id, withGlobals(options));
|
|
89693
|
+
});
|
|
89694
|
+
var groups = program2.command("groups").description("Manage groups and members");
|
|
89695
|
+
groups.command("ls").description("List groups").option("--q <string>", "Filter by name/description (client-side)").option("--limit <n>").option("--page <n>").action(async (options) => {
|
|
89696
|
+
await handleGroupsList(withGlobals(options));
|
|
89697
|
+
});
|
|
89698
|
+
groups.command("show <id>").description("Show a group by id").action(async (id, options) => {
|
|
89699
|
+
await handleGroupsShow(id, withGlobals(options));
|
|
89700
|
+
});
|
|
89701
|
+
groups.command("create").description("Create a new group").requiredOption("--name <name>", "Group name (must be unique)").option("--description <text>").option("--external-id <id>", "External system identifier").action(async (options) => {
|
|
89702
|
+
await handleGroupsCreate(withGlobals(options));
|
|
89703
|
+
});
|
|
89704
|
+
groups.command("update <id>").description("Update group fields").option("--name <name>").option("--description <text>").option("--external-id <id>").action(async (id, options) => {
|
|
89705
|
+
await handleGroupsUpdate(id, withGlobals(options));
|
|
89706
|
+
});
|
|
89707
|
+
groups.command("delete <id>").description("Delete a group (cascades to memberships and permissions)").option("--yes", "Skip the interactive confirmation").action(async (id, options) => {
|
|
89708
|
+
await handleGroupsDelete(id, withGlobals(options));
|
|
89709
|
+
});
|
|
89710
|
+
var members = groups.command("members").description("Manage the membership of a group");
|
|
89711
|
+
members.command("ls").description("List members of a group").requiredOption("--group <id>", "Parent group id").action(async (options) => {
|
|
89712
|
+
await handleGroupMembersList(withGlobals(options));
|
|
89713
|
+
});
|
|
89714
|
+
members.command("add").description("Add a user to a group").requiredOption("--group <id>", "Parent group id").option("--user-external-id <ext>", "External id of the user (the API requires this)").option("--user-id <uuid>", "User UUID \u2014 looked up via GET /users/:id to extract external_id").option("--user-email <email>", "User email \u2014 best-effort lookup, blocked by corex#460 beyond 50 users").action(async (options) => {
|
|
89715
|
+
await handleGroupMembersAdd(withGlobals(options));
|
|
89716
|
+
});
|
|
89717
|
+
members.command("remove <membership-id>").description("Remove a membership by its id (from `members ls`)").requiredOption("--group <id>", "Parent group id").option("--yes", "Skip the interactive confirmation").action(async (id, options) => {
|
|
89718
|
+
await handleGroupMembersRemove(id, withGlobals(options));
|
|
89719
|
+
});
|
|
89720
|
+
var permissions = program2.command("permissions").description("Manage ACL: grant / list / revoke permissions on documents resources");
|
|
89721
|
+
permissions.command("ls").description("List permissions on a specific resource").requiredOption("--on <resource-id>", "Permissible resource UUID").option("--on-type <type>", "workspace | folder | file | share_link", "workspace").option("--to <actor-id>", "Filter by actor id (client-side)").option("--to-type <type>", "Filter by actor type (user | group | contact)").option("--level <level>", "Filter by level (read|write|admin|owner|forbidden)").action(async (options) => {
|
|
89722
|
+
await handlePermissionsList(withGlobals(options));
|
|
89723
|
+
});
|
|
89724
|
+
permissions.command("show <id>").description("Show a permission by id").action(async (id, options) => {
|
|
89725
|
+
await handlePermissionsShow(id, withGlobals(options));
|
|
89726
|
+
});
|
|
89727
|
+
permissions.command("grant").description("Grant a permission on a resource to a user or group").requiredOption("--to <actor-id>", "Actor UUID (user/group/contact)").requiredOption("--to-type <type>", "user | group | contact").requiredOption("--on <resource-id>", "Permissible resource UUID").requiredOption("--on-type <type>", "workspace | folder | file | share_link").requiredOption("--level <level>", "read | write | admin | owner | forbidden").option("--temporary", "Mark as temporary (auto-expiring per server policy)").action(async (options) => {
|
|
89728
|
+
await handlePermissionsGrant(withGlobals(options));
|
|
89729
|
+
});
|
|
89730
|
+
permissions.command("revoke <id>").description("Revoke a permission by id").option("--yes", "Skip the interactive confirmation").action(async (id, options) => {
|
|
89731
|
+
await handlePermissionsRevoke(id, withGlobals(options));
|
|
89732
|
+
});
|
|
89244
89733
|
program2.parse();
|
|
89245
89734
|
/*! Bundled license information:
|
|
89246
89735
|
|
package/docs/llm-usage.md
CHANGED
|
@@ -291,12 +291,82 @@ Response shape (passed through unchanged in `--json` mode):
|
|
|
291
291
|
Errors: `{"error": "q is required", "code": "http_400"}` if the query is
|
|
292
292
|
empty (caught client-side too, before any HTTP call).
|
|
293
293
|
|
|
294
|
+
### Users / Groups / Permissions (actors + ACL)
|
|
295
|
+
|
|
296
|
+
These cover the actors model (who) and the ACL model (what they can do on
|
|
297
|
+
what). All read+write, except `users` which is read-only.
|
|
298
|
+
|
|
299
|
+
```bash
|
|
300
|
+
# Users (capped at ~50 by corex#460 server-side; filter client-side via --q)
|
|
301
|
+
hubdoc users ls [--q="alice"] [--limit=N] --json
|
|
302
|
+
hubdoc users show <id-or-email> --json
|
|
303
|
+
|
|
304
|
+
# Groups
|
|
305
|
+
hubdoc groups ls [--q="..."] [--limit=N] [--page=N] --json
|
|
306
|
+
hubdoc groups show <id> --json
|
|
307
|
+
hubdoc groups create --name="..." [--description="..."] [--external-id=ID] --json
|
|
308
|
+
hubdoc groups update <id> [--name=...] [--description=...] [--external-id=...] --json
|
|
309
|
+
hubdoc groups delete <id> --yes --json
|
|
310
|
+
|
|
311
|
+
# Group members (nested under `groups`)
|
|
312
|
+
hubdoc groups members ls --group=<group-id> --json
|
|
313
|
+
hubdoc groups members add --group=<group-id> \
|
|
314
|
+
[--user-external-id=EXT | --user-id=UUID | --user-email=EMAIL] --json
|
|
315
|
+
hubdoc groups members remove <membership-id> --group=<group-id> --yes --json
|
|
316
|
+
|
|
317
|
+
# Permissions (ACL: actor × permissible × level)
|
|
318
|
+
hubdoc permissions ls --on=<resource-id> --on-type=workspace|folder|file|share_link \
|
|
319
|
+
[--to=<actor-id>] [--to-type=user|group|contact] [--level=read|write|admin|owner|forbidden] --json
|
|
320
|
+
hubdoc permissions show <id> --json
|
|
321
|
+
hubdoc permissions grant \
|
|
322
|
+
--to=<actor-id> --to-type=user|group|contact \
|
|
323
|
+
--on=<resource-id> --on-type=workspace|folder|file|share_link \
|
|
324
|
+
--level=read|write|admin|owner|forbidden \
|
|
325
|
+
[--temporary] --json
|
|
326
|
+
hubdoc permissions revoke <id> --yes --json
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
`--on-type` and `--to-type` accept short aliases (`workspace`, `group`, …)
|
|
330
|
+
or the fully-qualified class names (`Documents::Workspace`, `Group`, …).
|
|
331
|
+
|
|
332
|
+
> ⚠️ `users` commands are partially blocked by [corex#460](https://code.plugandwork.net/sinoia/corex/-/issues/460) — `GET /api/v1/users` ignores `per_page`/`page`/`q` server-side and caps silently at 50. `users ls --q` and `users show <email>` work best-effort on those 50 rows. Use UUIDs directly when possible. `groups members add --user-email` is subject to the same limitation.
|
|
333
|
+
|
|
334
|
+
#### End-to-end example: create a team and share a workspace with it
|
|
335
|
+
|
|
336
|
+
Reproduces the real operation that motivated this section:
|
|
337
|
+
|
|
338
|
+
```bash
|
|
339
|
+
# 1. Workspace + group
|
|
340
|
+
WS=$(hubdoc workspaces create --name="Informations techniques" --json | jq -r '.data.id')
|
|
341
|
+
GRP=$(hubdoc groups create --name="sysadmins" --description="System admins" --json | jq -r '.data.id')
|
|
342
|
+
|
|
343
|
+
# 2. Add members (auto-resolves external_id from email when possible)
|
|
344
|
+
for email in \
|
|
345
|
+
olivier.dirrenberger@sinoia.fr \
|
|
346
|
+
benoit.nicolaeff@plugandwork.fr \
|
|
347
|
+
gaetan.marquet@plugandwork.fr ; do
|
|
348
|
+
hubdoc groups members add --group="$GRP" --user-email="$email" --json
|
|
349
|
+
done
|
|
350
|
+
|
|
351
|
+
# 3. Upload a sensitive file into the workspace
|
|
352
|
+
FILE=$(hubdoc files upload ./creds.txt --workspace="$WS" --json | jq -r '.data.id')
|
|
353
|
+
|
|
354
|
+
# 4. Grant admin permission to the group on the whole workspace
|
|
355
|
+
hubdoc permissions grant \
|
|
356
|
+
--to="$GRP" --to-type=group \
|
|
357
|
+
--on="$WS" --on-type=workspace \
|
|
358
|
+
--level=admin --json
|
|
359
|
+
|
|
360
|
+
# 5. Audit
|
|
361
|
+
hubdoc permissions ls --on="$WS" --on-type=workspace --json \
|
|
362
|
+
| jq '.[] | {actor: .actor.display_name, level}'
|
|
363
|
+
```
|
|
364
|
+
|
|
294
365
|
## Out of scope (planned)
|
|
295
366
|
|
|
296
367
|
- `hubdoc templates create|update|delete` — depends on corex#417 backend
|
|
297
368
|
extension (the controller is read-only today). The CLI will follow once
|
|
298
369
|
the API is in place.
|
|
299
|
-
- `hubdoc permissions ...` — to be scoped if/when demand confirms.
|
|
300
370
|
- JSON-API for PAT management (`hubdoc auth tokens ls/revoke`) — the
|
|
301
371
|
Corex PAT controller is Inertia/web-only today.
|
|
302
372
|
- `/api/v1/me` — not yet shipped; `whoami` falls back to JWT decode.
|