@sinoia/hubdoc-tools 1.5.1 → 1.6.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/cli.js +551 -70
- 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) {
|
|
@@ -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;
|
|
@@ -84571,7 +84577,7 @@ var CsvManager = class {
|
|
|
84571
84577
|
})).on("data", (data) => {
|
|
84572
84578
|
try {
|
|
84573
84579
|
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
|
|
84580
|
+
const permissions2 = this.parsePermissions(permissionsStr);
|
|
84575
84581
|
const cleanValue = (value2) => {
|
|
84576
84582
|
if (value2 === void 0 || value2 === null) return void 0;
|
|
84577
84583
|
const trimmed = String(value2).trim();
|
|
@@ -84584,7 +84590,7 @@ var CsvManager = class {
|
|
|
84584
84590
|
metadata: this.parseMetadata(
|
|
84585
84591
|
data["Metadata (key: value, parent.child: value)"] || data["Metadata (JSON)"] || data.Metadata || data.metadata
|
|
84586
84592
|
),
|
|
84587
|
-
permissions,
|
|
84593
|
+
permissions: permissions2,
|
|
84588
84594
|
autoClassify: this.parseBoolean(data["Auto Classify"] || data.autoClassify),
|
|
84589
84595
|
status: data["Status"] || data.status || "pending",
|
|
84590
84596
|
progress: parseFloat(data["Progress (%)"] || data.progress || "0"),
|
|
@@ -84753,18 +84759,18 @@ var CsvManager = class {
|
|
|
84753
84759
|
}
|
|
84754
84760
|
} else if (type === "groups") {
|
|
84755
84761
|
if (parts2.length === 2) {
|
|
84756
|
-
const
|
|
84757
|
-
if (
|
|
84758
|
-
readPermissions.groups = [...readPermissions.groups || [], ...
|
|
84762
|
+
const groups2 = parts2[1].split(",").map((g) => g.trim()).filter((g) => g);
|
|
84763
|
+
if (groups2.length > 0) {
|
|
84764
|
+
readPermissions.groups = [...readPermissions.groups || [], ...groups2];
|
|
84759
84765
|
}
|
|
84760
84766
|
} else if (parts2.length === 3) {
|
|
84761
84767
|
const level = parts2[1];
|
|
84762
|
-
const
|
|
84763
|
-
if (
|
|
84768
|
+
const groups2 = parts2[2].split(",").map((g) => g.trim()).filter((g) => g);
|
|
84769
|
+
if (groups2.length > 0) {
|
|
84764
84770
|
if (level === "w") {
|
|
84765
|
-
writePermissions.groups = [...writePermissions.groups || [], ...
|
|
84771
|
+
writePermissions.groups = [...writePermissions.groups || [], ...groups2];
|
|
84766
84772
|
} else {
|
|
84767
|
-
readPermissions.groups = [...readPermissions.groups || [], ...
|
|
84773
|
+
readPermissions.groups = [...readPermissions.groups || [], ...groups2];
|
|
84768
84774
|
}
|
|
84769
84775
|
}
|
|
84770
84776
|
}
|
|
@@ -84915,20 +84921,20 @@ var CsvManager = class {
|
|
|
84915
84921
|
* Serialize permissions in new format:
|
|
84916
84922
|
* users:r:email1@ex.com,email2@ex.com | users:w:admin@ex.com | groups:Group Name | groups:w:Admin Group
|
|
84917
84923
|
*/
|
|
84918
|
-
serializePermissions(
|
|
84919
|
-
if (!
|
|
84924
|
+
serializePermissions(permissions2) {
|
|
84925
|
+
if (!permissions2) return "";
|
|
84920
84926
|
const parts2 = [];
|
|
84921
|
-
if (
|
|
84922
|
-
parts2.push(`users:r:${
|
|
84927
|
+
if (permissions2.read?.users && permissions2.read.users.length > 0) {
|
|
84928
|
+
parts2.push(`users:r:${permissions2.read.users.join(",")}`);
|
|
84923
84929
|
}
|
|
84924
|
-
if (
|
|
84925
|
-
parts2.push(`groups:${
|
|
84930
|
+
if (permissions2.read?.groups && permissions2.read.groups.length > 0) {
|
|
84931
|
+
parts2.push(`groups:${permissions2.read.groups.join(",")}`);
|
|
84926
84932
|
}
|
|
84927
|
-
if (
|
|
84928
|
-
parts2.push(`users:w:${
|
|
84933
|
+
if (permissions2.write?.users && permissions2.write.users.length > 0) {
|
|
84934
|
+
parts2.push(`users:w:${permissions2.write.users.join(",")}`);
|
|
84929
84935
|
}
|
|
84930
|
-
if (
|
|
84931
|
-
parts2.push(`groups:w:${
|
|
84936
|
+
if (permissions2.write?.groups && permissions2.write.groups.length > 0) {
|
|
84937
|
+
parts2.push(`groups:w:${permissions2.write.groups.join(",")}`);
|
|
84932
84938
|
}
|
|
84933
84939
|
return parts2.join(" | ");
|
|
84934
84940
|
}
|
|
@@ -85861,7 +85867,7 @@ var CsvManager2 = class {
|
|
|
85861
85867
|
})).on("data", (data) => {
|
|
85862
85868
|
try {
|
|
85863
85869
|
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
|
|
85870
|
+
const permissions2 = this.parsePermissions(permissionsStr);
|
|
85865
85871
|
const cleanValue = (value2) => {
|
|
85866
85872
|
if (value2 === void 0 || value2 === null) return void 0;
|
|
85867
85873
|
const trimmed = String(value2).trim();
|
|
@@ -85874,7 +85880,7 @@ var CsvManager2 = class {
|
|
|
85874
85880
|
metadata: this.parseMetadata(
|
|
85875
85881
|
data["Metadata (key: value, parent.child: value)"] || data["Metadata (JSON)"] || data.Metadata || data.metadata
|
|
85876
85882
|
),
|
|
85877
|
-
permissions,
|
|
85883
|
+
permissions: permissions2,
|
|
85878
85884
|
autoClassify: this.parseBoolean(data["Auto Classify"] || data.autoClassify),
|
|
85879
85885
|
status: data["Status"] || data.status || "pending",
|
|
85880
85886
|
progress: parseFloat(data["Progress (%)"] || data.progress || "0"),
|
|
@@ -86043,18 +86049,18 @@ var CsvManager2 = class {
|
|
|
86043
86049
|
}
|
|
86044
86050
|
} else if (type === "groups") {
|
|
86045
86051
|
if (parts2.length === 2) {
|
|
86046
|
-
const
|
|
86047
|
-
if (
|
|
86048
|
-
readPermissions.groups = [...readPermissions.groups || [], ...
|
|
86052
|
+
const groups2 = parts2[1].split(",").map((g) => g.trim()).filter((g) => g);
|
|
86053
|
+
if (groups2.length > 0) {
|
|
86054
|
+
readPermissions.groups = [...readPermissions.groups || [], ...groups2];
|
|
86049
86055
|
}
|
|
86050
86056
|
} else if (parts2.length === 3) {
|
|
86051
86057
|
const level = parts2[1];
|
|
86052
|
-
const
|
|
86053
|
-
if (
|
|
86058
|
+
const groups2 = parts2[2].split(",").map((g) => g.trim()).filter((g) => g);
|
|
86059
|
+
if (groups2.length > 0) {
|
|
86054
86060
|
if (level === "w") {
|
|
86055
|
-
writePermissions.groups = [...writePermissions.groups || [], ...
|
|
86061
|
+
writePermissions.groups = [...writePermissions.groups || [], ...groups2];
|
|
86056
86062
|
} else {
|
|
86057
|
-
readPermissions.groups = [...readPermissions.groups || [], ...
|
|
86063
|
+
readPermissions.groups = [...readPermissions.groups || [], ...groups2];
|
|
86058
86064
|
}
|
|
86059
86065
|
}
|
|
86060
86066
|
}
|
|
@@ -86205,20 +86211,20 @@ var CsvManager2 = class {
|
|
|
86205
86211
|
* Serialize permissions in new format:
|
|
86206
86212
|
* users:r:email1@ex.com,email2@ex.com | users:w:admin@ex.com | groups:Group Name | groups:w:Admin Group
|
|
86207
86213
|
*/
|
|
86208
|
-
serializePermissions(
|
|
86209
|
-
if (!
|
|
86214
|
+
serializePermissions(permissions2) {
|
|
86215
|
+
if (!permissions2) return "";
|
|
86210
86216
|
const parts2 = [];
|
|
86211
|
-
if (
|
|
86212
|
-
parts2.push(`users:r:${
|
|
86217
|
+
if (permissions2.read?.users && permissions2.read.users.length > 0) {
|
|
86218
|
+
parts2.push(`users:r:${permissions2.read.users.join(",")}`);
|
|
86213
86219
|
}
|
|
86214
|
-
if (
|
|
86215
|
-
parts2.push(`groups:${
|
|
86220
|
+
if (permissions2.read?.groups && permissions2.read.groups.length > 0) {
|
|
86221
|
+
parts2.push(`groups:${permissions2.read.groups.join(",")}`);
|
|
86216
86222
|
}
|
|
86217
|
-
if (
|
|
86218
|
-
parts2.push(`users:w:${
|
|
86223
|
+
if (permissions2.write?.users && permissions2.write.users.length > 0) {
|
|
86224
|
+
parts2.push(`users:w:${permissions2.write.users.join(",")}`);
|
|
86219
86225
|
}
|
|
86220
|
-
if (
|
|
86221
|
-
parts2.push(`groups:w:${
|
|
86226
|
+
if (permissions2.write?.groups && permissions2.write.groups.length > 0) {
|
|
86227
|
+
parts2.push(`groups:w:${permissions2.write.groups.join(",")}`);
|
|
86222
86228
|
}
|
|
86223
86229
|
return parts2.join(" | ");
|
|
86224
86230
|
}
|
|
@@ -86746,13 +86752,13 @@ var PluginImportService = class {
|
|
|
86746
86752
|
* Group mappings by connection ID
|
|
86747
86753
|
*/
|
|
86748
86754
|
groupMappingsByConnection(mappings) {
|
|
86749
|
-
return mappings.reduce((
|
|
86755
|
+
return mappings.reduce((groups2, mapping) => {
|
|
86750
86756
|
const { connectionId } = mapping;
|
|
86751
|
-
if (!
|
|
86752
|
-
|
|
86757
|
+
if (!groups2[connectionId]) {
|
|
86758
|
+
groups2[connectionId] = [];
|
|
86753
86759
|
}
|
|
86754
|
-
|
|
86755
|
-
return
|
|
86760
|
+
groups2[connectionId].push(mapping);
|
|
86761
|
+
return groups2;
|
|
86756
86762
|
}, {});
|
|
86757
86763
|
}
|
|
86758
86764
|
/**
|
|
@@ -87400,6 +87406,8 @@ async function buildApiContext(out) {
|
|
|
87400
87406
|
documents: new DocumentsApi(configuration, baseUrl, axiosInstance),
|
|
87401
87407
|
chunkedUploads: new ChunkedUploadsApi(configuration, baseUrl, axiosInstance),
|
|
87402
87408
|
users: new UsersApi(configuration, baseUrl, axiosInstance),
|
|
87409
|
+
groups: new GroupsApi(configuration, baseUrl, axiosInstance),
|
|
87410
|
+
permissions: new PermissionsApi(configuration, baseUrl, axiosInstance),
|
|
87403
87411
|
baseUrl,
|
|
87404
87412
|
token: token2,
|
|
87405
87413
|
tokenSource: resolved.source
|
|
@@ -89008,10 +89016,437 @@ async function handleTemplatesShow(idOrKey, opts) {
|
|
|
89008
89016
|
}
|
|
89009
89017
|
}
|
|
89010
89018
|
|
|
89019
|
+
// apps/cli/cli/handlers/user-handlers.ts
|
|
89020
|
+
var USER_COLUMNS = [
|
|
89021
|
+
{ header: "ID", key: "id" },
|
|
89022
|
+
{ header: "Email", key: "email_address" },
|
|
89023
|
+
{ header: "Name", key: "display_name" },
|
|
89024
|
+
{ header: "Role", key: "role" },
|
|
89025
|
+
{ header: "External", key: "external_id", maxWidth: 24 }
|
|
89026
|
+
];
|
|
89027
|
+
function matchesQuery(user, q) {
|
|
89028
|
+
const needle = q.toLowerCase();
|
|
89029
|
+
const haystack = [
|
|
89030
|
+
user.email_address,
|
|
89031
|
+
user.first_name,
|
|
89032
|
+
user.last_name,
|
|
89033
|
+
user.display_name,
|
|
89034
|
+
user.external_id
|
|
89035
|
+
].filter(Boolean).join(" ").toLowerCase();
|
|
89036
|
+
return haystack.includes(needle);
|
|
89037
|
+
}
|
|
89038
|
+
async function handleUsersList(opts) {
|
|
89039
|
+
const out = output(opts);
|
|
89040
|
+
const ctx = await buildApiContext(out);
|
|
89041
|
+
try {
|
|
89042
|
+
const res = await ctx.users.apiV1UsersGet(
|
|
89043
|
+
void 0,
|
|
89044
|
+
void 0,
|
|
89045
|
+
opts.limit ? Number(opts.limit) : void 0
|
|
89046
|
+
);
|
|
89047
|
+
let items = res.data ?? [];
|
|
89048
|
+
if (opts.q) items = items.filter((u) => matchesQuery(u, opts.q));
|
|
89049
|
+
if (items.length === 50 && !opts.q) {
|
|
89050
|
+
out.note("server may have capped result at 50 (see corex#460) \u2014 refine with --q to filter");
|
|
89051
|
+
}
|
|
89052
|
+
out.list(items, USER_COLUMNS);
|
|
89053
|
+
} catch (err) {
|
|
89054
|
+
out.fail(err);
|
|
89055
|
+
}
|
|
89056
|
+
}
|
|
89057
|
+
async function handleUsersShow(idOrEmail, opts) {
|
|
89058
|
+
const out = output(opts);
|
|
89059
|
+
const ctx = await buildApiContext(out);
|
|
89060
|
+
const looksLikeEmail = idOrEmail.includes("@");
|
|
89061
|
+
let found;
|
|
89062
|
+
try {
|
|
89063
|
+
if (!looksLikeEmail) {
|
|
89064
|
+
const res2 = await ctx.users.apiV1UsersIdGet(idOrEmail);
|
|
89065
|
+
out.show(res2.data, { title: `User ${idOrEmail}` });
|
|
89066
|
+
return;
|
|
89067
|
+
}
|
|
89068
|
+
const res = await ctx.users.apiV1UsersGet();
|
|
89069
|
+
const items = res.data ?? [];
|
|
89070
|
+
found = items.find((u) => (u.email_address || "").toLowerCase() === idOrEmail.toLowerCase());
|
|
89071
|
+
} catch (err) {
|
|
89072
|
+
out.fail(err);
|
|
89073
|
+
return;
|
|
89074
|
+
}
|
|
89075
|
+
if (!found) {
|
|
89076
|
+
out.fail(
|
|
89077
|
+
new Error(
|
|
89078
|
+
`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.`
|
|
89079
|
+
)
|
|
89080
|
+
);
|
|
89081
|
+
}
|
|
89082
|
+
out.show(found, { title: `User ${idOrEmail}` });
|
|
89083
|
+
}
|
|
89084
|
+
|
|
89085
|
+
// apps/cli/cli/handlers/group-handlers.ts
|
|
89086
|
+
var GROUP_COLUMNS = [
|
|
89087
|
+
{ header: "ID", key: "id" },
|
|
89088
|
+
{ header: "Name", key: "name" },
|
|
89089
|
+
{ header: "Description", key: "description", maxWidth: 40 },
|
|
89090
|
+
{ header: "External", key: "external_id", maxWidth: 24 },
|
|
89091
|
+
{ header: "Updated", key: "updated_at" }
|
|
89092
|
+
];
|
|
89093
|
+
async function handleGroupsList(opts) {
|
|
89094
|
+
const out = output(opts);
|
|
89095
|
+
const ctx = await buildApiContext(out);
|
|
89096
|
+
try {
|
|
89097
|
+
const res = await ctx.groups.apiV1GroupsGet(
|
|
89098
|
+
void 0,
|
|
89099
|
+
void 0,
|
|
89100
|
+
opts.limit ? Number(opts.limit) : void 0,
|
|
89101
|
+
opts.page ? Number(opts.page) : void 0
|
|
89102
|
+
);
|
|
89103
|
+
let items = res.data ?? [];
|
|
89104
|
+
if (opts.q) {
|
|
89105
|
+
const needle = opts.q.toLowerCase();
|
|
89106
|
+
items = items.filter(
|
|
89107
|
+
(g) => (g.name || "").toLowerCase().includes(needle) || (g.description || "").toLowerCase().includes(needle)
|
|
89108
|
+
);
|
|
89109
|
+
}
|
|
89110
|
+
out.list(items, GROUP_COLUMNS);
|
|
89111
|
+
} catch (err) {
|
|
89112
|
+
out.fail(err);
|
|
89113
|
+
}
|
|
89114
|
+
}
|
|
89115
|
+
async function handleGroupsShow(id, opts) {
|
|
89116
|
+
const out = output(opts);
|
|
89117
|
+
const ctx = await buildApiContext(out);
|
|
89118
|
+
try {
|
|
89119
|
+
const res = await ctx.groups.apiV1GroupsIdGet(id);
|
|
89120
|
+
out.show(res.data, { title: `Group ${id}` });
|
|
89121
|
+
} catch (err) {
|
|
89122
|
+
out.fail(err);
|
|
89123
|
+
}
|
|
89124
|
+
}
|
|
89125
|
+
async function handleGroupsCreate(opts) {
|
|
89126
|
+
const out = output(opts);
|
|
89127
|
+
const ctx = await buildApiContext(out);
|
|
89128
|
+
const mutation = { name: opts.name };
|
|
89129
|
+
if (opts.description !== void 0) mutation.description = opts.description;
|
|
89130
|
+
if (opts.externalId !== void 0) mutation.external_id = opts.externalId;
|
|
89131
|
+
try {
|
|
89132
|
+
const res = await ctx.groups.apiV1GroupsPost(
|
|
89133
|
+
ApiV1GroupsPostContentTypeEnum.ApplicationJson,
|
|
89134
|
+
mutation
|
|
89135
|
+
);
|
|
89136
|
+
out.ok(`Group created (id=${res.data.id})`, res.data);
|
|
89137
|
+
} catch (err) {
|
|
89138
|
+
out.fail(err);
|
|
89139
|
+
}
|
|
89140
|
+
}
|
|
89141
|
+
async function handleGroupsUpdate(id, opts) {
|
|
89142
|
+
const out = output(opts);
|
|
89143
|
+
const ctx = await buildApiContext(out);
|
|
89144
|
+
if (opts.name === void 0 && opts.description === void 0 && opts.externalId === void 0) {
|
|
89145
|
+
out.fail(new Error("No fields to update"));
|
|
89146
|
+
}
|
|
89147
|
+
let nameValue = opts.name;
|
|
89148
|
+
if (nameValue === void 0) {
|
|
89149
|
+
try {
|
|
89150
|
+
const cur = await ctx.groups.apiV1GroupsIdGet(id);
|
|
89151
|
+
nameValue = cur.data.name;
|
|
89152
|
+
} catch (err) {
|
|
89153
|
+
out.fail(err);
|
|
89154
|
+
return;
|
|
89155
|
+
}
|
|
89156
|
+
}
|
|
89157
|
+
const mutation = { name: nameValue };
|
|
89158
|
+
if (opts.description !== void 0) mutation.description = opts.description;
|
|
89159
|
+
if (opts.externalId !== void 0) mutation.external_id = opts.externalId;
|
|
89160
|
+
try {
|
|
89161
|
+
const res = await ctx.groups.apiV1GroupsIdPatch(
|
|
89162
|
+
id,
|
|
89163
|
+
ApiV1GroupsIdPatchContentTypeEnum.ApplicationJson,
|
|
89164
|
+
mutation
|
|
89165
|
+
);
|
|
89166
|
+
out.ok(`Group ${id} updated`, res.data);
|
|
89167
|
+
} catch (err) {
|
|
89168
|
+
out.fail(err);
|
|
89169
|
+
}
|
|
89170
|
+
}
|
|
89171
|
+
async function handleGroupsDelete(id, opts) {
|
|
89172
|
+
const out = output(opts);
|
|
89173
|
+
const ctx = await buildApiContext(out);
|
|
89174
|
+
if (!opts.yes && !opts.json) {
|
|
89175
|
+
const { confirm } = await lib_default.prompt([
|
|
89176
|
+
{
|
|
89177
|
+
type: "confirm",
|
|
89178
|
+
name: "confirm",
|
|
89179
|
+
message: `Delete group ${id}? This removes all its members and permissions.`,
|
|
89180
|
+
default: false
|
|
89181
|
+
}
|
|
89182
|
+
]);
|
|
89183
|
+
if (!confirm) {
|
|
89184
|
+
out.note("Aborted");
|
|
89185
|
+
return;
|
|
89186
|
+
}
|
|
89187
|
+
} else if (!opts.yes && opts.json) {
|
|
89188
|
+
out.fail(new Error("Refusing to delete without --yes in --json mode"));
|
|
89189
|
+
}
|
|
89190
|
+
try {
|
|
89191
|
+
await ctx.groups.apiV1GroupsIdDelete(id);
|
|
89192
|
+
out.ok(`Group ${id} deleted`);
|
|
89193
|
+
} catch (err) {
|
|
89194
|
+
out.fail(err);
|
|
89195
|
+
}
|
|
89196
|
+
}
|
|
89197
|
+
|
|
89198
|
+
// apps/cli/cli/handlers/group-member-handlers.ts
|
|
89199
|
+
var MEMBER_COLUMNS = [
|
|
89200
|
+
{ header: "Membership ID", key: "id" },
|
|
89201
|
+
{ header: "Member ID", key: "member_id" },
|
|
89202
|
+
{ header: "Type", key: "member_type" },
|
|
89203
|
+
{ header: "External", key: "user_external_id", maxWidth: 24 },
|
|
89204
|
+
{ header: "Role", key: "role" }
|
|
89205
|
+
];
|
|
89206
|
+
function membersBase(groupId) {
|
|
89207
|
+
return `/api/v1/groups/${encodeURIComponent(groupId)}/members`;
|
|
89208
|
+
}
|
|
89209
|
+
async function handleGroupMembersList(opts) {
|
|
89210
|
+
const out = output(opts);
|
|
89211
|
+
const ctx = await buildApiContext(out);
|
|
89212
|
+
try {
|
|
89213
|
+
const res = await ctx.axios.get(membersBase(opts.group));
|
|
89214
|
+
out.list(res.data ?? [], MEMBER_COLUMNS);
|
|
89215
|
+
} catch (err) {
|
|
89216
|
+
out.fail(err);
|
|
89217
|
+
}
|
|
89218
|
+
}
|
|
89219
|
+
async function resolveExternalId(ctx, out, opts) {
|
|
89220
|
+
if (opts.userExternalId) return opts.userExternalId;
|
|
89221
|
+
if (opts.userId) {
|
|
89222
|
+
let ext2;
|
|
89223
|
+
try {
|
|
89224
|
+
const res = await ctx.users.apiV1UsersIdGet(opts.userId);
|
|
89225
|
+
ext2 = res.data?.external_id;
|
|
89226
|
+
} catch (err) {
|
|
89227
|
+
out.fail(err);
|
|
89228
|
+
return "";
|
|
89229
|
+
}
|
|
89230
|
+
if (!ext2) {
|
|
89231
|
+
out.fail(
|
|
89232
|
+
new Error(
|
|
89233
|
+
`User ${opts.userId} has no external_id \u2014 server requires it to create a membership.`
|
|
89234
|
+
)
|
|
89235
|
+
);
|
|
89236
|
+
}
|
|
89237
|
+
return ext2;
|
|
89238
|
+
}
|
|
89239
|
+
if (opts.userEmail) {
|
|
89240
|
+
let found;
|
|
89241
|
+
try {
|
|
89242
|
+
const res = await ctx.users.apiV1UsersGet();
|
|
89243
|
+
const items = res.data ?? [];
|
|
89244
|
+
found = items.find(
|
|
89245
|
+
(u) => (u.email_address || "").toLowerCase() === opts.userEmail.toLowerCase()
|
|
89246
|
+
);
|
|
89247
|
+
} catch (err) {
|
|
89248
|
+
out.fail(err);
|
|
89249
|
+
return "";
|
|
89250
|
+
}
|
|
89251
|
+
if (!found) {
|
|
89252
|
+
out.fail(
|
|
89253
|
+
new Error(
|
|
89254
|
+
`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.`
|
|
89255
|
+
)
|
|
89256
|
+
);
|
|
89257
|
+
}
|
|
89258
|
+
if (!found.external_id) {
|
|
89259
|
+
out.fail(
|
|
89260
|
+
new Error(
|
|
89261
|
+
`User '${opts.userEmail}' has no external_id \u2014 server requires it to create a membership.`
|
|
89262
|
+
)
|
|
89263
|
+
);
|
|
89264
|
+
}
|
|
89265
|
+
return found.external_id;
|
|
89266
|
+
}
|
|
89267
|
+
out.fail(
|
|
89268
|
+
new Error("Pass one of --user-external-id, --user-id, or --user-email to identify the user.")
|
|
89269
|
+
);
|
|
89270
|
+
return "";
|
|
89271
|
+
}
|
|
89272
|
+
async function handleGroupMembersAdd(opts) {
|
|
89273
|
+
const out = output(opts);
|
|
89274
|
+
const ctx = await buildApiContext(out);
|
|
89275
|
+
const externalId = await resolveExternalId(ctx, out, opts);
|
|
89276
|
+
try {
|
|
89277
|
+
const res = await ctx.axios.post(membersBase(opts.group), { user_external_id: externalId });
|
|
89278
|
+
out.ok(`Membership created (id=${res.data.id})`, res.data);
|
|
89279
|
+
} catch (err) {
|
|
89280
|
+
out.fail(err);
|
|
89281
|
+
}
|
|
89282
|
+
}
|
|
89283
|
+
async function handleGroupMembersRemove(membershipId, opts) {
|
|
89284
|
+
const out = output(opts);
|
|
89285
|
+
const ctx = await buildApiContext(out);
|
|
89286
|
+
if (!opts.yes && !opts.json) {
|
|
89287
|
+
const { confirm } = await lib_default.prompt([
|
|
89288
|
+
{
|
|
89289
|
+
type: "confirm",
|
|
89290
|
+
name: "confirm",
|
|
89291
|
+
message: `Remove membership ${membershipId} from group ${opts.group}?`,
|
|
89292
|
+
default: false
|
|
89293
|
+
}
|
|
89294
|
+
]);
|
|
89295
|
+
if (!confirm) {
|
|
89296
|
+
out.note("Aborted");
|
|
89297
|
+
return;
|
|
89298
|
+
}
|
|
89299
|
+
} else if (!opts.yes && opts.json) {
|
|
89300
|
+
out.fail(new Error("Refusing to remove without --yes in --json mode"));
|
|
89301
|
+
}
|
|
89302
|
+
try {
|
|
89303
|
+
await ctx.axios.delete(`${membersBase(opts.group)}/${encodeURIComponent(membershipId)}`);
|
|
89304
|
+
out.ok(`Membership ${membershipId} removed`);
|
|
89305
|
+
} catch (err) {
|
|
89306
|
+
out.fail(err);
|
|
89307
|
+
}
|
|
89308
|
+
}
|
|
89309
|
+
|
|
89310
|
+
// apps/cli/cli/handlers/permission-handlers.ts
|
|
89311
|
+
var PERM_COLUMNS = [
|
|
89312
|
+
{ header: "ID", key: "id" },
|
|
89313
|
+
{ header: "Actor", key: "actor_type", format: (v, row) => `${v}:${row.actor?.display_name ?? row.actor_id?.substring(0, 8)}` },
|
|
89314
|
+
{ header: "Level", key: "level" },
|
|
89315
|
+
{ header: "On", key: "permissible_type", format: (v) => v?.replace(/^Documents::/, "") ?? "" },
|
|
89316
|
+
{ header: "Resource ID", key: "permissible_id" },
|
|
89317
|
+
{ header: "Updated", key: "updated_at" }
|
|
89318
|
+
];
|
|
89319
|
+
var VALID_LEVELS = /* @__PURE__ */ new Set(["read", "write", "admin", "owner", "forbidden"]);
|
|
89320
|
+
var PERMISSIBLE_ALIASES = {
|
|
89321
|
+
workspace: "Documents::Workspace",
|
|
89322
|
+
folder: "Documents::Folder",
|
|
89323
|
+
file: "Documents::File",
|
|
89324
|
+
share_link: "Documents::ShareLink"
|
|
89325
|
+
};
|
|
89326
|
+
var ACTOR_ALIASES = {
|
|
89327
|
+
user: "User",
|
|
89328
|
+
group: "Group",
|
|
89329
|
+
contact: "Contact"
|
|
89330
|
+
};
|
|
89331
|
+
function normalizePermissibleType(input, out) {
|
|
89332
|
+
const lower = input.toLowerCase();
|
|
89333
|
+
if (PERMISSIBLE_ALIASES[lower]) return PERMISSIBLE_ALIASES[lower];
|
|
89334
|
+
if (input.startsWith("Documents::")) return input;
|
|
89335
|
+
out.fail(
|
|
89336
|
+
new Error(
|
|
89337
|
+
`Invalid --on-type: '${input}'. Use workspace/folder/file/share_link or a fully-qualified Documents::\u2026 class name.`
|
|
89338
|
+
)
|
|
89339
|
+
);
|
|
89340
|
+
return "";
|
|
89341
|
+
}
|
|
89342
|
+
function normalizeActorType(input, out) {
|
|
89343
|
+
const lower = input.toLowerCase();
|
|
89344
|
+
if (ACTOR_ALIASES[lower]) return ACTOR_ALIASES[lower];
|
|
89345
|
+
if (["User", "Group", "Contact"].includes(input)) return input;
|
|
89346
|
+
out.fail(new Error(`Invalid --to-type: '${input}'. Use user/group/contact.`));
|
|
89347
|
+
return "";
|
|
89348
|
+
}
|
|
89349
|
+
async function handlePermissionsList(opts) {
|
|
89350
|
+
const out = output(opts);
|
|
89351
|
+
const ctx = await buildApiContext(out);
|
|
89352
|
+
if (!opts.on) {
|
|
89353
|
+
out.fail(
|
|
89354
|
+
new Error(
|
|
89355
|
+
"--on=<resource-id> is required (the server rejects listing without permissible_id/type)."
|
|
89356
|
+
)
|
|
89357
|
+
);
|
|
89358
|
+
}
|
|
89359
|
+
const onType = opts.onType ? normalizePermissibleType(opts.onType, out) : "Documents::Workspace";
|
|
89360
|
+
try {
|
|
89361
|
+
const res = await ctx.axios.get("/api/v1/permissions", {
|
|
89362
|
+
params: {
|
|
89363
|
+
permissible_id: opts.on,
|
|
89364
|
+
permissible_type: onType
|
|
89365
|
+
}
|
|
89366
|
+
});
|
|
89367
|
+
let items = res.data ?? [];
|
|
89368
|
+
if (opts.to) items = items.filter((p) => p.actor_id === opts.to);
|
|
89369
|
+
if (opts.toType) {
|
|
89370
|
+
const norm = normalizeActorType(opts.toType, out);
|
|
89371
|
+
items = items.filter((p) => p.actor_type === norm);
|
|
89372
|
+
}
|
|
89373
|
+
if (opts.level) items = items.filter((p) => p.level === opts.level);
|
|
89374
|
+
out.list(items, PERM_COLUMNS);
|
|
89375
|
+
} catch (err) {
|
|
89376
|
+
out.fail(err);
|
|
89377
|
+
}
|
|
89378
|
+
}
|
|
89379
|
+
async function handlePermissionsGrant(opts) {
|
|
89380
|
+
const out = output(opts);
|
|
89381
|
+
const ctx = await buildApiContext(out);
|
|
89382
|
+
if (!VALID_LEVELS.has(opts.level)) {
|
|
89383
|
+
out.fail(
|
|
89384
|
+
new Error(
|
|
89385
|
+
`Invalid --level: '${opts.level}'. Expected one of: ${[...VALID_LEVELS].join(", ")}`
|
|
89386
|
+
)
|
|
89387
|
+
);
|
|
89388
|
+
}
|
|
89389
|
+
const actorType = normalizeActorType(opts.toType, out);
|
|
89390
|
+
const permissibleType = normalizePermissibleType(opts.onType, out);
|
|
89391
|
+
try {
|
|
89392
|
+
const body = {
|
|
89393
|
+
actor_id: opts.to,
|
|
89394
|
+
actor_type: actorType,
|
|
89395
|
+
permissible_id: opts.on,
|
|
89396
|
+
permissible_type: permissibleType,
|
|
89397
|
+
level: opts.level
|
|
89398
|
+
};
|
|
89399
|
+
if (opts.temporary) body.temporary = true;
|
|
89400
|
+
const res = await ctx.axios.post("/api/v1/permissions", body);
|
|
89401
|
+
out.ok(
|
|
89402
|
+
`Permission granted (${actorType} \u2192 ${opts.level} on ${permissibleType})`,
|
|
89403
|
+
res.data
|
|
89404
|
+
);
|
|
89405
|
+
} catch (err) {
|
|
89406
|
+
out.fail(err);
|
|
89407
|
+
}
|
|
89408
|
+
}
|
|
89409
|
+
async function handlePermissionsRevoke(id, opts) {
|
|
89410
|
+
const out = output(opts);
|
|
89411
|
+
const ctx = await buildApiContext(out);
|
|
89412
|
+
if (!opts.yes && !opts.json) {
|
|
89413
|
+
const { confirm } = await lib_default.prompt([
|
|
89414
|
+
{
|
|
89415
|
+
type: "confirm",
|
|
89416
|
+
name: "confirm",
|
|
89417
|
+
message: `Revoke permission ${id}?`,
|
|
89418
|
+
default: false
|
|
89419
|
+
}
|
|
89420
|
+
]);
|
|
89421
|
+
if (!confirm) {
|
|
89422
|
+
out.note("Aborted");
|
|
89423
|
+
return;
|
|
89424
|
+
}
|
|
89425
|
+
} else if (!opts.yes && opts.json) {
|
|
89426
|
+
out.fail(new Error("Refusing to revoke without --yes in --json mode"));
|
|
89427
|
+
}
|
|
89428
|
+
try {
|
|
89429
|
+
await ctx.permissions.apiV1PermissionsIdDelete(id);
|
|
89430
|
+
out.ok(`Permission ${id} revoked`);
|
|
89431
|
+
} catch (err) {
|
|
89432
|
+
out.fail(err);
|
|
89433
|
+
}
|
|
89434
|
+
}
|
|
89435
|
+
async function handlePermissionsShow(id, opts) {
|
|
89436
|
+
const out = output(opts);
|
|
89437
|
+
const ctx = await buildApiContext(out);
|
|
89438
|
+
try {
|
|
89439
|
+
const res = await ctx.permissions.apiV1PermissionsIdGet(id);
|
|
89440
|
+
out.show(res.data, { title: `Permission ${id}` });
|
|
89441
|
+
} catch (err) {
|
|
89442
|
+
out.fail(err);
|
|
89443
|
+
}
|
|
89444
|
+
}
|
|
89445
|
+
|
|
89011
89446
|
// apps/cli/cli.ts
|
|
89012
89447
|
var getVersion = () => {
|
|
89013
89448
|
try {
|
|
89014
|
-
if (true) return "1.
|
|
89449
|
+
if (true) return "1.6.0";
|
|
89015
89450
|
} catch {
|
|
89016
89451
|
}
|
|
89017
89452
|
for (const candidate of [
|
|
@@ -89241,6 +89676,52 @@ templates.command("ls").description("List active templates").action(async (optio
|
|
|
89241
89676
|
templates.command("show <id-or-key>").description("Show a template (full default_parts + page_settings)").action(async (idOrKey, options) => {
|
|
89242
89677
|
await handleTemplatesShow(idOrKey, withGlobals(options));
|
|
89243
89678
|
});
|
|
89679
|
+
var users = program2.command("users").description("Look up users (read-only)");
|
|
89680
|
+
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) => {
|
|
89681
|
+
await handleUsersList(withGlobals(options));
|
|
89682
|
+
});
|
|
89683
|
+
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) => {
|
|
89684
|
+
await handleUsersShow(id, withGlobals(options));
|
|
89685
|
+
});
|
|
89686
|
+
var groups = program2.command("groups").description("Manage groups and members");
|
|
89687
|
+
groups.command("ls").description("List groups").option("--q <string>", "Filter by name/description (client-side)").option("--limit <n>").option("--page <n>").action(async (options) => {
|
|
89688
|
+
await handleGroupsList(withGlobals(options));
|
|
89689
|
+
});
|
|
89690
|
+
groups.command("show <id>").description("Show a group by id").action(async (id, options) => {
|
|
89691
|
+
await handleGroupsShow(id, withGlobals(options));
|
|
89692
|
+
});
|
|
89693
|
+
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) => {
|
|
89694
|
+
await handleGroupsCreate(withGlobals(options));
|
|
89695
|
+
});
|
|
89696
|
+
groups.command("update <id>").description("Update group fields").option("--name <name>").option("--description <text>").option("--external-id <id>").action(async (id, options) => {
|
|
89697
|
+
await handleGroupsUpdate(id, withGlobals(options));
|
|
89698
|
+
});
|
|
89699
|
+
groups.command("delete <id>").description("Delete a group (cascades to memberships and permissions)").option("--yes", "Skip the interactive confirmation").action(async (id, options) => {
|
|
89700
|
+
await handleGroupsDelete(id, withGlobals(options));
|
|
89701
|
+
});
|
|
89702
|
+
var members = groups.command("members").description("Manage the membership of a group");
|
|
89703
|
+
members.command("ls").description("List members of a group").requiredOption("--group <id>", "Parent group id").action(async (options) => {
|
|
89704
|
+
await handleGroupMembersList(withGlobals(options));
|
|
89705
|
+
});
|
|
89706
|
+
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) => {
|
|
89707
|
+
await handleGroupMembersAdd(withGlobals(options));
|
|
89708
|
+
});
|
|
89709
|
+
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) => {
|
|
89710
|
+
await handleGroupMembersRemove(id, withGlobals(options));
|
|
89711
|
+
});
|
|
89712
|
+
var permissions = program2.command("permissions").description("Manage ACL: grant / list / revoke permissions on documents resources");
|
|
89713
|
+
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) => {
|
|
89714
|
+
await handlePermissionsList(withGlobals(options));
|
|
89715
|
+
});
|
|
89716
|
+
permissions.command("show <id>").description("Show a permission by id").action(async (id, options) => {
|
|
89717
|
+
await handlePermissionsShow(id, withGlobals(options));
|
|
89718
|
+
});
|
|
89719
|
+
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) => {
|
|
89720
|
+
await handlePermissionsGrant(withGlobals(options));
|
|
89721
|
+
});
|
|
89722
|
+
permissions.command("revoke <id>").description("Revoke a permission by id").option("--yes", "Skip the interactive confirmation").action(async (id, options) => {
|
|
89723
|
+
await handlePermissionsRevoke(id, withGlobals(options));
|
|
89724
|
+
});
|
|
89244
89725
|
program2.parse();
|
|
89245
89726
|
/*! Bundled license information:
|
|
89246
89727
|
|
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.
|