@taskmagic/apps-skillplate 0.0.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/README.md +24 -0
- package/package.json +54 -0
- package/src/index.d.ts +1 -0
- package/src/index.js +73 -0
- package/src/index.js.map +1 -0
- package/src/lib/actions/create-order.d.ts +11 -0
- package/src/lib/actions/create-order.js +93 -0
- package/src/lib/actions/create-order.js.map +1 -0
- package/src/lib/actions/create-user.d.ts +6 -0
- package/src/lib/actions/create-user.js +46 -0
- package/src/lib/actions/create-user.js.map +1 -0
- package/src/lib/actions/enroll-user.d.ts +5 -0
- package/src/lib/actions/enroll-user.js +32 -0
- package/src/lib/actions/enroll-user.js.map +1 -0
- package/src/lib/actions/find-users.d.ts +5 -0
- package/src/lib/actions/find-users.js +71 -0
- package/src/lib/actions/find-users.js.map +1 -0
- package/src/lib/actions/get-user.d.ts +3 -0
- package/src/lib/actions/get-user.js +27 -0
- package/src/lib/actions/get-user.js.map +1 -0
- package/src/lib/actions/list-courses.d.ts +1 -0
- package/src/lib/actions/list-courses.js +24 -0
- package/src/lib/actions/list-courses.js.map +1 -0
- package/src/lib/common.d.ts +49 -0
- package/src/lib/common.js +327 -0
- package/src/lib/common.js.map +1 -0
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Skillplate
|
|
2
|
+
|
|
3
|
+
TaskMagic piece for [Skillplate](https://skillplate.com/) — enrol learners in courses, create
|
|
4
|
+
users, and record orders that create-and-enrol in a single call.
|
|
5
|
+
|
|
6
|
+
Built for the App Playground request [TM-859](https://linear.app/taskmagic-dev/issue/TM-859).
|
|
7
|
+
|
|
8
|
+
## API
|
|
9
|
+
|
|
10
|
+
Base URL: `https://api.skillplate.com/v1/external`
|
|
11
|
+
Auth: `Authorization: Bearer <Personal Access Token>` (Skillplate dashboard → Settings → API Tokens)
|
|
12
|
+
|
|
13
|
+
Skillplate's API reference at `skillplate.com/docs/api` is only reachable while signed in to a
|
|
14
|
+
Skillplate account, so it cannot be linked as a public citation. The endpoint list, field names
|
|
15
|
+
and constraints used here were supplied by the vendor's own documentation, relayed by a
|
|
16
|
+
Skillplate customer, and recorded verbatim on
|
|
17
|
+
[TM-860](https://linear.app/taskmagic-dev/issue/TM-860) — that ticket is the citation for this
|
|
18
|
+
piece. Every route was additionally probe-verified against the live API before implementation
|
|
19
|
+
(a bogus token returns `401 invalid_token` on a real route and `404` on an invented one).
|
|
20
|
+
|
|
21
|
+
## Vocabulary
|
|
22
|
+
|
|
23
|
+
A **course** is a **product** in the API. The piece says "Course" to users and sends
|
|
24
|
+
`product_id` / `product_ids` on the wire.
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@taskmagic/apps-skillplate",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"dependencies": {
|
|
5
|
+
"@anthropic-ai/sdk": "0.33.1",
|
|
6
|
+
"@sinclair/typebox": "0.34.11",
|
|
7
|
+
"axios": "1.17.0",
|
|
8
|
+
"axios-retry": "4.4.1",
|
|
9
|
+
"deepmerge-ts": "7.1.0",
|
|
10
|
+
"mime-types": "2.1.35",
|
|
11
|
+
"nanoid": "3.3.8",
|
|
12
|
+
"openai": "4.67.1",
|
|
13
|
+
"replicate": "0.34.1",
|
|
14
|
+
"semver": "7.6.0",
|
|
15
|
+
"zod": "3.25.76",
|
|
16
|
+
"@taskmagic/pieces-common": "0.4.5",
|
|
17
|
+
"@taskmagic/pieces-framework": "0.7.46",
|
|
18
|
+
"@taskmagic/shared": "0.10.171",
|
|
19
|
+
"tslib": "1.14.1"
|
|
20
|
+
},
|
|
21
|
+
"overrides": {
|
|
22
|
+
"cross-spawn": "7.0.6",
|
|
23
|
+
"elliptic": "^6.6.1",
|
|
24
|
+
"fast-xml-parser": "^4.4.1",
|
|
25
|
+
"protobufjs": "^7.5.5",
|
|
26
|
+
"tmp": "^0.2.4",
|
|
27
|
+
"koa": "^2.16.4",
|
|
28
|
+
"picomatch": "^4.0.4",
|
|
29
|
+
"langsmith": "^0.6.0",
|
|
30
|
+
"serialize-javascript": "^6.0.2",
|
|
31
|
+
"websocket-driver": "^0.7.5",
|
|
32
|
+
"@grpc/grpc-js": "^1.13.5",
|
|
33
|
+
"immutable": "^5.1.8",
|
|
34
|
+
"postcss": "^8.5.18",
|
|
35
|
+
"socket.io-parser": "^4.2.7",
|
|
36
|
+
"shell-quote": "^1.9.0",
|
|
37
|
+
"linkify-it": "^5.0.2",
|
|
38
|
+
"piscina": "^4.9.3",
|
|
39
|
+
"svgo": "^3.3.4",
|
|
40
|
+
"form-data": "4.0.5",
|
|
41
|
+
"@tryfabric/martian": {
|
|
42
|
+
"@notionhq/client": "2.2.11"
|
|
43
|
+
},
|
|
44
|
+
"vite": {
|
|
45
|
+
"rollup": "npm:@rollup/wasm-node@^4.61.1"
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"resolutions": {
|
|
49
|
+
"rollup": "npm:@rollup/wasm-node@^4.61.1"
|
|
50
|
+
},
|
|
51
|
+
"types": "./src/index.d.ts",
|
|
52
|
+
"main": "./src/index.js",
|
|
53
|
+
"type": "commonjs"
|
|
54
|
+
}
|
package/src/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const skillplate: import("@taskmagic/pieces-framework").Piece<import("@taskmagic/pieces-framework").SecretTextProperty<true>>;
|
package/src/index.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.skillplate = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const pieces_common_1 = require("@taskmagic/pieces-common");
|
|
6
|
+
const pieces_framework_1 = require("@taskmagic/pieces-framework");
|
|
7
|
+
const shared_1 = require("@taskmagic/shared");
|
|
8
|
+
const create_order_1 = require("./lib/actions/create-order");
|
|
9
|
+
const create_user_1 = require("./lib/actions/create-user");
|
|
10
|
+
const enroll_user_1 = require("./lib/actions/enroll-user");
|
|
11
|
+
const find_users_1 = require("./lib/actions/find-users");
|
|
12
|
+
const get_user_1 = require("./lib/actions/get-user");
|
|
13
|
+
const list_courses_1 = require("./lib/actions/list-courses");
|
|
14
|
+
const common_1 = require("./lib/common");
|
|
15
|
+
/*
|
|
16
|
+
* Skillplate — course platform. Base https://api.skillplate.com/v1/external, Bearer Personal
|
|
17
|
+
* Access Token. See lib/common.ts for the citation and the probe results behind every route.
|
|
18
|
+
*
|
|
19
|
+
* Actions only for 0.0.1. Skillplate advertises webhooks publicly, but the relayed spec covers
|
|
20
|
+
* only the endpoints above — it documents no webhook subscription API, and no sort or cursor
|
|
21
|
+
* vocabulary for GET /users or GET /orders that a polling trigger could rely on to detect "new"
|
|
22
|
+
* records. Shipping a trigger would mean inventing that vocabulary, so the trigger gap is
|
|
23
|
+
* reported to the caller as a follow-up instead. The requested automation does not need one:
|
|
24
|
+
* the trigger in that flow is Shopify's, and Create Order is the Skillplate half.
|
|
25
|
+
*/
|
|
26
|
+
exports.skillplate = (0, pieces_framework_1.createPiece)({
|
|
27
|
+
displayName: 'Skillplate',
|
|
28
|
+
description: 'Course platform — enroll learners in courses, create users, and record orders that create and enroll a customer in a single call',
|
|
29
|
+
auth: common_1.skillplateAuth,
|
|
30
|
+
minimumSupportedRelease: '0.30.0',
|
|
31
|
+
logoUrl: 'https://apps-logos.sfo3.digitaloceanspaces.com/pieces/requests/skillplate-7Zv3yK1fx3QfvjCk4YDoT.png',
|
|
32
|
+
categories: [shared_1.PieceCategory.CONTENT_AND_FILES],
|
|
33
|
+
authors: ['taskmagic'],
|
|
34
|
+
ai: {
|
|
35
|
+
description: 'Manage Skillplate course enrolments: enroll an existing user in one or more courses, create users, look a user up by email to get their id, list the courses (products) in the account, and record an order that matches or creates the customer by email and enrolls them in one call — the usual way to grant course access after a purchase in an ecommerce store such as Shopify.',
|
|
36
|
+
keywords: [
|
|
37
|
+
'skillplate',
|
|
38
|
+
'course',
|
|
39
|
+
'courses',
|
|
40
|
+
'enroll',
|
|
41
|
+
'enrolment',
|
|
42
|
+
'enrollment',
|
|
43
|
+
'lms',
|
|
44
|
+
'learning',
|
|
45
|
+
'student',
|
|
46
|
+
'learner',
|
|
47
|
+
'user',
|
|
48
|
+
'order',
|
|
49
|
+
'product',
|
|
50
|
+
'grant access',
|
|
51
|
+
'online school',
|
|
52
|
+
],
|
|
53
|
+
},
|
|
54
|
+
actions: [
|
|
55
|
+
create_order_1.createOrder,
|
|
56
|
+
enroll_user_1.enrollUser,
|
|
57
|
+
create_user_1.createUser,
|
|
58
|
+
find_users_1.findUsers,
|
|
59
|
+
get_user_1.getUser,
|
|
60
|
+
list_courses_1.listCourses,
|
|
61
|
+
(0, pieces_common_1.createCustomApiCallAction)({
|
|
62
|
+
baseUrl: () => common_1.SKILLPLATE_BASE_URL,
|
|
63
|
+
auth: common_1.skillplateAuth,
|
|
64
|
+
// Trimmed to match skillplateRequest. Without this a token pasted with trailing
|
|
65
|
+
// whitespace works for the six typed actions and 401s here, on the same connection.
|
|
66
|
+
authMapping: (auth) => tslib_1.__awaiter(void 0, void 0, void 0, function* () {
|
|
67
|
+
return { Authorization: `Bearer ${auth.trim()}` };
|
|
68
|
+
}),
|
|
69
|
+
}),
|
|
70
|
+
],
|
|
71
|
+
triggers: [],
|
|
72
|
+
});
|
|
73
|
+
//# sourceMappingURL=index.js.map
|
package/src/index.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../../../packages/pieces/community/skillplate/src/index.ts"],"names":[],"mappings":";;;;AAAA,4DAAqE;AACrE,kEAA0D;AAC1D,8CAAkD;AAClD,6DAAyD;AACzD,2DAAuD;AACvD,2DAAuD;AACvD,yDAAqD;AACrD,qDAAiD;AACjD,6DAAyD;AACzD,yCAIsB;AAEtB;;;;;;;;;;GAUG;AACU,QAAA,UAAU,GAAG,IAAA,8BAAW,EAAC;IACpC,WAAW,EAAE,YAAY;IACzB,WAAW,EACT,kIAAkI;IACpI,IAAI,EAAE,uBAAc;IACpB,uBAAuB,EAAE,QAAQ;IACjC,OAAO,EACL,qGAAqG;IACvG,UAAU,EAAE,CAAC,sBAAa,CAAC,iBAAiB,CAAC;IAC7C,OAAO,EAAE,CAAC,WAAW,CAAC;IACtB,EAAE,EAAE;QACF,WAAW,EACT,uXAAuX;QACzX,QAAQ,EAAE;YACR,YAAY;YACZ,QAAQ;YACR,SAAS;YACT,QAAQ;YACR,WAAW;YACX,YAAY;YACZ,KAAK;YACL,UAAU;YACV,SAAS;YACT,SAAS;YACT,MAAM;YACN,OAAO;YACP,SAAS;YACT,cAAc;YACd,eAAe;SAChB;KACF;IACD,OAAO,EAAE;QACP,0BAAW;QACX,wBAAU;QACV,wBAAU;QACV,sBAAS;QACT,kBAAO;QACP,0BAAW;QACX,IAAA,yCAAyB,EAAC;YACxB,OAAO,EAAE,GAAG,EAAE,CAAC,4BAAmB;YAClC,IAAI,EAAE,uBAAc;YACpB,gFAAgF;YAChF,oFAAoF;YACpF,WAAW,EAAE,CAAO,IAAI,EAAE,EAAE;gBAC1B,OAAO,EAAE,aAAa,EAAE,UAAW,IAAuB,CAAC,IAAI,EAAE,EAAE,EAAE,CAAC;YACxE,CAAC,CAAA;SACF,CAAC;KACH;IACD,QAAQ,EAAE,EAAE;CACb,CAAC,CAAC"}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const createOrder: import("@taskmagic/pieces-framework").IAction<import("@taskmagic/pieces-framework").SecretTextProperty<true>, {
|
|
2
|
+
email: import("@taskmagic/pieces-framework").ShortTextProperty<true>;
|
|
3
|
+
firstName: import("@taskmagic/pieces-framework").ShortTextProperty<false>;
|
|
4
|
+
lastName: import("@taskmagic/pieces-framework").ShortTextProperty<false>;
|
|
5
|
+
courses: import("@taskmagic/pieces-framework").MultiSelectDropdownProperty<string, false>;
|
|
6
|
+
courseIds: import("@taskmagic/pieces-framework").ShortTextProperty<false>;
|
|
7
|
+
amount: import("@taskmagic/pieces-framework").NumberProperty<false>;
|
|
8
|
+
status: import("@taskmagic/pieces-framework").ShortTextProperty<false>;
|
|
9
|
+
country: import("@taskmagic/pieces-framework").ShortTextProperty<false>;
|
|
10
|
+
userId: import("@taskmagic/pieces-framework").ShortTextProperty<false>;
|
|
11
|
+
}>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createOrder = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const pieces_framework_1 = require("@taskmagic/pieces-framework");
|
|
6
|
+
const pieces_common_1 = require("@taskmagic/pieces-common");
|
|
7
|
+
const common_1 = require("../common");
|
|
8
|
+
// POST /orders — citation: https://linear.app/taskmagic-dev/issue/TM-860
|
|
9
|
+
//
|
|
10
|
+
// This is the one-call path the vendor recommends for exactly the use case this piece was
|
|
11
|
+
// requested for (a new store order should enrol the buyer): with `user_id` omitted, Skillplate
|
|
12
|
+
// looks the user up by email or creates them in the background, then enrols them in the
|
|
13
|
+
// products on the order. That makes it correct for both brand-new and existing customers
|
|
14
|
+
// without the flow having to branch.
|
|
15
|
+
exports.createOrder = (0, pieces_framework_1.createAction)({
|
|
16
|
+
name: 'create_order',
|
|
17
|
+
auth: common_1.skillplateAuth,
|
|
18
|
+
displayName: 'Create Order (Enroll New or Existing Customer)',
|
|
19
|
+
description: 'Record an order and enroll the customer in its courses. Leave User ID empty and Skillplate will match the person by email or create them, so this works for brand-new and existing customers alike.',
|
|
20
|
+
props: {
|
|
21
|
+
email: pieces_framework_1.Property.ShortText({
|
|
22
|
+
displayName: 'Email',
|
|
23
|
+
description: "The customer's email address. Skillplate matches an existing user on this, or creates one when there is no match.",
|
|
24
|
+
required: true,
|
|
25
|
+
}),
|
|
26
|
+
firstName: pieces_framework_1.Property.ShortText({
|
|
27
|
+
displayName: 'First Name',
|
|
28
|
+
required: false,
|
|
29
|
+
}),
|
|
30
|
+
lastName: pieces_framework_1.Property.ShortText({
|
|
31
|
+
displayName: 'Last Name',
|
|
32
|
+
required: false,
|
|
33
|
+
}),
|
|
34
|
+
courses: common_1.coursesDropdown,
|
|
35
|
+
courseIds: common_1.courseIdsOverride,
|
|
36
|
+
amount: pieces_framework_1.Property.Number({
|
|
37
|
+
displayName: 'Amount',
|
|
38
|
+
description: 'The amount recorded against each course on this order. Defaults to 0, which is what you want when the purchase was already handled by your store.',
|
|
39
|
+
required: false,
|
|
40
|
+
defaultValue: 0,
|
|
41
|
+
}),
|
|
42
|
+
status: pieces_framework_1.Property.ShortText({
|
|
43
|
+
displayName: 'Status',
|
|
44
|
+
description: 'The order status. `completed` is the value that enrols the customer — leave it as-is unless you are deliberately recording an order that should not grant access yet.',
|
|
45
|
+
required: false,
|
|
46
|
+
defaultValue: 'completed',
|
|
47
|
+
}),
|
|
48
|
+
country: pieces_framework_1.Property.ShortText({
|
|
49
|
+
displayName: 'Country',
|
|
50
|
+
description: 'Two-letter ISO country code for the customer, e.g. `IT` or `US`.',
|
|
51
|
+
required: false,
|
|
52
|
+
}),
|
|
53
|
+
userId: pieces_framework_1.Property.ShortText({
|
|
54
|
+
displayName: 'User ID',
|
|
55
|
+
description: 'Optional. Pass a known Skillplate user id to attach the order to that exact user. Leave empty to let Skillplate match by email or create the user — that is the usual choice.',
|
|
56
|
+
required: false,
|
|
57
|
+
}),
|
|
58
|
+
},
|
|
59
|
+
run(_a) {
|
|
60
|
+
return tslib_1.__awaiter(this, arguments, void 0, function* ({ auth, propsValue }) {
|
|
61
|
+
var _b, _c;
|
|
62
|
+
const productIds = (0, common_1.resolveCourseIds)(propsValue.courses, propsValue.courseIds);
|
|
63
|
+
// `amount` is optional in the UI, but sending `undefined` would drop the key from the line
|
|
64
|
+
// item entirely; the documented shape always carries it.
|
|
65
|
+
const amount = (_b = propsValue.amount) !== null && _b !== void 0 ? _b : 0;
|
|
66
|
+
const body = {
|
|
67
|
+
email: propsValue.email.trim(),
|
|
68
|
+
status: ((_c = propsValue.status) === null || _c === void 0 ? void 0 : _c.trim()) || 'completed',
|
|
69
|
+
line_items: productIds.map((productId) => ({
|
|
70
|
+
product_id: productId,
|
|
71
|
+
amount,
|
|
72
|
+
})),
|
|
73
|
+
};
|
|
74
|
+
if (propsValue.firstName) {
|
|
75
|
+
body['first_name'] = propsValue.firstName;
|
|
76
|
+
}
|
|
77
|
+
if (propsValue.lastName) {
|
|
78
|
+
body['last_name'] = propsValue.lastName;
|
|
79
|
+
}
|
|
80
|
+
if (propsValue.country) {
|
|
81
|
+
body['country'] = propsValue.country.trim();
|
|
82
|
+
}
|
|
83
|
+
// Only send user_id when the user actually supplied one. Sending an empty string would
|
|
84
|
+
// stop Skillplate from doing the match-or-create that makes this action work for new
|
|
85
|
+
// customers, which is the entire reason to prefer it over Enroll User.
|
|
86
|
+
if (propsValue.userId && propsValue.userId.trim().length > 0) {
|
|
87
|
+
body['user_id'] = propsValue.userId.trim();
|
|
88
|
+
}
|
|
89
|
+
return (0, common_1.skillplateRequest)(auth, pieces_common_1.HttpMethod.POST, '/orders', { body });
|
|
90
|
+
});
|
|
91
|
+
},
|
|
92
|
+
});
|
|
93
|
+
//# sourceMappingURL=create-order.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-order.js","sourceRoot":"","sources":["../../../../../../../../packages/pieces/community/skillplate/src/lib/actions/create-order.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare const createUser: import("@taskmagic/pieces-framework").IAction<import("@taskmagic/pieces-framework").SecretTextProperty<true>, {
|
|
2
|
+
email: import("@taskmagic/pieces-framework").ShortTextProperty<true>;
|
|
3
|
+
firstName: import("@taskmagic/pieces-framework").ShortTextProperty<true>;
|
|
4
|
+
lastName: import("@taskmagic/pieces-framework").ShortTextProperty<true>;
|
|
5
|
+
password: import("@taskmagic/pieces-framework").ShortTextProperty<true>;
|
|
6
|
+
}>;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createUser = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const pieces_framework_1 = require("@taskmagic/pieces-framework");
|
|
6
|
+
const pieces_common_1 = require("@taskmagic/pieces-common");
|
|
7
|
+
const common_1 = require("../common");
|
|
8
|
+
// POST /users — citation: https://linear.app/taskmagic-dev/issue/TM-860
|
|
9
|
+
exports.createUser = (0, pieces_framework_1.createAction)({
|
|
10
|
+
name: 'create_user',
|
|
11
|
+
auth: common_1.skillplateAuth,
|
|
12
|
+
displayName: 'Create User',
|
|
13
|
+
description: 'Create a Skillplate user without enrolling them in anything. To create and enroll in one step, use Create Order instead.',
|
|
14
|
+
props: {
|
|
15
|
+
email: pieces_framework_1.Property.ShortText({
|
|
16
|
+
displayName: 'Email',
|
|
17
|
+
required: true,
|
|
18
|
+
}),
|
|
19
|
+
firstName: pieces_framework_1.Property.ShortText({
|
|
20
|
+
displayName: 'First Name',
|
|
21
|
+
required: true,
|
|
22
|
+
}),
|
|
23
|
+
lastName: pieces_framework_1.Property.ShortText({
|
|
24
|
+
displayName: 'Last Name',
|
|
25
|
+
required: true,
|
|
26
|
+
}),
|
|
27
|
+
password: pieces_framework_1.Property.ShortText({
|
|
28
|
+
displayName: 'Password',
|
|
29
|
+
description: "The new user's password. Generate one per user rather than typing a shared value here — anyone with it can sign in as that learner, and this field is stored with the flow. Create Order needs no password at all, so prefer it when you just want to grant course access.",
|
|
30
|
+
required: true,
|
|
31
|
+
}),
|
|
32
|
+
},
|
|
33
|
+
run(_a) {
|
|
34
|
+
return tslib_1.__awaiter(this, arguments, void 0, function* ({ auth, propsValue }) {
|
|
35
|
+
return (0, common_1.skillplateRequest)(auth, pieces_common_1.HttpMethod.POST, '/users', {
|
|
36
|
+
body: {
|
|
37
|
+
email: propsValue.email.trim(),
|
|
38
|
+
first_name: propsValue.firstName,
|
|
39
|
+
last_name: propsValue.lastName,
|
|
40
|
+
password: propsValue.password,
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
});
|
|
44
|
+
},
|
|
45
|
+
});
|
|
46
|
+
//# sourceMappingURL=create-user.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"create-user.js","sourceRoot":"","sources":["../../../../../../../../packages/pieces/community/skillplate/src/lib/actions/create-user.ts"],"names":[],"mappings":";;;;AAAA,kEAAqE;AACrE,4DAAsD;AACtD,sCAImB;AAEnB,wEAAwE;AAC3D,QAAA,UAAU,GAAG,IAAA,+BAAY,EAAC;IACrC,IAAI,EAAE,aAAa;IACnB,IAAI,EAAE,uBAAc;IACpB,WAAW,EAAE,aAAa;IAC1B,WAAW,EACT,0HAA0H;IAC5H,KAAK,EAAE;QACL,KAAK,EAAE,2BAAQ,CAAC,SAAS,CAAC;YACxB,WAAW,EAAE,OAAO;YACpB,QAAQ,EAAE,IAAI;SACf,CAAC;QACF,SAAS,EAAE,2BAAQ,CAAC,SAAS,CAAC;YAC5B,WAAW,EAAE,YAAY;YACzB,QAAQ,EAAE,IAAI;SACf,CAAC;QACF,QAAQ,EAAE,2BAAQ,CAAC,SAAS,CAAC;YAC3B,WAAW,EAAE,WAAW;YACxB,QAAQ,EAAE,IAAI;SACf,CAAC;QACF,QAAQ,EAAE,2BAAQ,CAAC,SAAS,CAAC;YAC3B,WAAW,EAAE,UAAU;YACvB,WAAW,EACT,4QAA4Q;YAC9Q,QAAQ,EAAE,IAAI;SACf,CAAC;KACH;IACK,GAAG;qEAAC,EAAE,IAAI,EAAE,UAAU,EAAE;YAC5B,OAAO,IAAA,0BAAiB,EACtB,IAAsB,EACtB,0BAAU,CAAC,IAAI,EACf,QAAQ,EACR;gBACE,IAAI,EAAE;oBACJ,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE;oBAC9B,UAAU,EAAE,UAAU,CAAC,SAAS;oBAChC,SAAS,EAAE,UAAU,CAAC,QAAQ;oBAC9B,QAAQ,EAAE,UAAU,CAAC,QAAQ;iBAC9B;aACF,CACF,CAAC;QACJ,CAAC;KAAA;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const enrollUser: import("@taskmagic/pieces-framework").IAction<import("@taskmagic/pieces-framework").SecretTextProperty<true>, {
|
|
2
|
+
userId: import("@taskmagic/pieces-framework").ShortTextProperty<true>;
|
|
3
|
+
courses: import("@taskmagic/pieces-framework").MultiSelectDropdownProperty<string, false>;
|
|
4
|
+
courseIds: import("@taskmagic/pieces-framework").ShortTextProperty<false>;
|
|
5
|
+
}>;
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.enrollUser = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const pieces_framework_1 = require("@taskmagic/pieces-framework");
|
|
6
|
+
const pieces_common_1 = require("@taskmagic/pieces-common");
|
|
7
|
+
const common_1 = require("../common");
|
|
8
|
+
// POST /users/{id}/enroll — citation: https://linear.app/taskmagic-dev/issue/TM-860
|
|
9
|
+
// `product_ids` MUST be an array of strings; a bare string or a missing array returns
|
|
10
|
+
// 422 validation_failed.
|
|
11
|
+
exports.enrollUser = (0, pieces_framework_1.createAction)({
|
|
12
|
+
name: 'enroll_user',
|
|
13
|
+
auth: common_1.skillplateAuth,
|
|
14
|
+
displayName: 'Enroll User In Courses',
|
|
15
|
+
description: 'Enroll an existing Skillplate user in one or more courses. Use Create Order instead if the learner may not exist yet.',
|
|
16
|
+
props: {
|
|
17
|
+
userId: pieces_framework_1.Property.ShortText({
|
|
18
|
+
displayName: 'User ID',
|
|
19
|
+
description: 'The Skillplate user to enroll. This is a hashed string such as `uKq2ZbT9rVn4LpAe`, not a number — use Find Users to look one up by email.',
|
|
20
|
+
required: true,
|
|
21
|
+
}),
|
|
22
|
+
courses: common_1.coursesDropdown,
|
|
23
|
+
courseIds: common_1.courseIdsOverride,
|
|
24
|
+
},
|
|
25
|
+
run(_a) {
|
|
26
|
+
return tslib_1.__awaiter(this, arguments, void 0, function* ({ auth, propsValue }) {
|
|
27
|
+
const productIds = (0, common_1.resolveCourseIds)(propsValue.courses, propsValue.courseIds);
|
|
28
|
+
return (0, common_1.skillplateRequest)(auth, pieces_common_1.HttpMethod.POST, `/users/${encodeURIComponent(propsValue.userId.trim())}/enroll`, { body: { product_ids: productIds } });
|
|
29
|
+
});
|
|
30
|
+
},
|
|
31
|
+
});
|
|
32
|
+
//# sourceMappingURL=enroll-user.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"enroll-user.js","sourceRoot":"","sources":["../../../../../../../../packages/pieces/community/skillplate/src/lib/actions/enroll-user.ts"],"names":[],"mappings":";;;;AAAA,kEAAqE;AACrE,4DAAsD;AACtD,sCAOmB;AAEnB,oFAAoF;AACpF,sFAAsF;AACtF,yBAAyB;AACZ,QAAA,UAAU,GAAG,IAAA,+BAAY,EAAC;IACrC,IAAI,EAAE,aAAa;IACnB,IAAI,EAAE,uBAAc;IACpB,WAAW,EAAE,wBAAwB;IACrC,WAAW,EACT,uHAAuH;IACzH,KAAK,EAAE;QACL,MAAM,EAAE,2BAAQ,CAAC,SAAS,CAAC;YACzB,WAAW,EAAE,SAAS;YACtB,WAAW,EACT,2IAA2I;YAC7I,QAAQ,EAAE,IAAI;SACf,CAAC;QACF,OAAO,EAAE,wBAAe;QACxB,SAAS,EAAE,0BAAiB;KAC7B;IACK,GAAG;qEAAC,EAAE,IAAI,EAAE,UAAU,EAAE;YAC5B,MAAM,UAAU,GAAG,IAAA,yBAAgB,EAAC,UAAU,CAAC,OAAO,EAAE,UAAU,CAAC,SAAS,CAAC,CAAC;YAC9E,OAAO,IAAA,0BAAiB,EACtB,IAAsB,EACtB,0BAAU,CAAC,IAAI,EACf,UAAU,kBAAkB,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,SAAS,EAC/D,EAAE,IAAI,EAAE,EAAE,WAAW,EAAE,UAAU,EAAE,EAAE,CACtC,CAAC;QACJ,CAAC;KAAA;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const findUsers: import("@taskmagic/pieces-framework").IAction<import("@taskmagic/pieces-framework").SecretTextProperty<true>, {
|
|
2
|
+
email: import("@taskmagic/pieces-framework").ShortTextProperty<false>;
|
|
3
|
+
search: import("@taskmagic/pieces-framework").ShortTextProperty<false>;
|
|
4
|
+
limit: import("@taskmagic/pieces-framework").NumberProperty<false>;
|
|
5
|
+
}>;
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.findUsers = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const pieces_framework_1 = require("@taskmagic/pieces-framework");
|
|
6
|
+
const common_1 = require("../common");
|
|
7
|
+
// GET /users — citation: https://linear.app/taskmagic-dev/issue/TM-860
|
|
8
|
+
//
|
|
9
|
+
// The relayed spec names the resource but not its query-parameter vocabulary, so Search and
|
|
10
|
+
// Limit are passed through only when the user fills them in, and the description says plainly
|
|
11
|
+
// that Skillplate may ignore them. The Email match is then applied by TaskMagic so that "find
|
|
12
|
+
// the user with this address" is reliable either way — this action's main job is turning an
|
|
13
|
+
// email into the hashed id that Enroll User In Courses needs.
|
|
14
|
+
//
|
|
15
|
+
// The email match pages through the full list rather than inspecting only the first response
|
|
16
|
+
// (Codex P2 on PR #1735): a user further down the list would otherwise come back `found: false`,
|
|
17
|
+
// and a flow branching on that would create a duplicate account for someone who already exists.
|
|
18
|
+
exports.findUsers = (0, pieces_framework_1.createAction)({
|
|
19
|
+
name: 'find_users',
|
|
20
|
+
auth: common_1.skillplateAuth,
|
|
21
|
+
displayName: 'Find Users',
|
|
22
|
+
description: 'List Skillplate users, optionally narrowing to one email address. Use this to turn an email into the user id that Enroll User In Courses needs.',
|
|
23
|
+
props: {
|
|
24
|
+
email: pieces_framework_1.Property.ShortText({
|
|
25
|
+
displayName: 'Email',
|
|
26
|
+
description: 'Optional. Return only the user with this exact email address (case-insensitive). Skillplate is searched page by page until the address is found, so a match is not missed just because the user is further down the list. Leave empty to list users.',
|
|
27
|
+
required: false,
|
|
28
|
+
}),
|
|
29
|
+
search: pieces_framework_1.Property.ShortText({
|
|
30
|
+
displayName: 'Search',
|
|
31
|
+
description: 'Optional. Passed to Skillplate as a `search` query parameter. Skillplate may ignore it — the Email match above is applied by TaskMagic and always works.',
|
|
32
|
+
required: false,
|
|
33
|
+
}),
|
|
34
|
+
limit: pieces_framework_1.Property.Number({
|
|
35
|
+
displayName: 'Limit',
|
|
36
|
+
description: 'Optional. Passed to Skillplate as a `limit` query parameter to cap how many users are fetched per page. Leave empty when looking a user up by email — a small limit makes the lookup do more round trips, and if Skillplate returns no pagination details it can stop the search before the user is reached.',
|
|
37
|
+
required: false,
|
|
38
|
+
}),
|
|
39
|
+
},
|
|
40
|
+
run(_a) {
|
|
41
|
+
return tslib_1.__awaiter(this, arguments, void 0, function* ({ auth, propsValue }) {
|
|
42
|
+
var _b;
|
|
43
|
+
const query = {};
|
|
44
|
+
if (propsValue.search) {
|
|
45
|
+
query['search'] = propsValue.search;
|
|
46
|
+
}
|
|
47
|
+
if (propsValue.limit !== undefined && propsValue.limit !== null) {
|
|
48
|
+
query['limit'] = String(propsValue.limit);
|
|
49
|
+
}
|
|
50
|
+
const wanted = (_b = propsValue.email) === null || _b === void 0 ? void 0 : _b.trim().toLowerCase();
|
|
51
|
+
const matches = (user) => { var _a; return ((_a = user.email) === null || _a === void 0 ? void 0 : _a.trim().toLowerCase()) === wanted; };
|
|
52
|
+
const { users, complete } = yield (0, common_1.listUsersPaged)(auth, query,
|
|
53
|
+
// Stop as soon as the wanted address turns up; without an email this collects the list.
|
|
54
|
+
wanted ? (soFar) => soFar.some(matches) : undefined);
|
|
55
|
+
const matched = wanted ? users.filter(matches) : users;
|
|
56
|
+
// `complete` is false only when the page walk hit its safety cap with more data left. Saying
|
|
57
|
+
// "found: false" then would assert something this action did not actually establish, so the
|
|
58
|
+
// result reports the search as incomplete and lets the flow author decide.
|
|
59
|
+
if (wanted && matched.length === 0 && !complete) {
|
|
60
|
+
throw new Error(`Searched ${users.length} Skillplate users without finding ${propsValue.email}, and there are still more pages. Narrow the search with the Search field, or raise Limit so each page returns more users, then try again — treating this as "no such user" could create a duplicate account for someone who already exists.`);
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
found: matched.length > 0,
|
|
64
|
+
count: matched.length,
|
|
65
|
+
users: matched,
|
|
66
|
+
searched: users.length,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
},
|
|
70
|
+
});
|
|
71
|
+
//# sourceMappingURL=find-users.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"find-users.js","sourceRoot":"","sources":["../../../../../../../../packages/pieces/community/skillplate/src/lib/actions/find-users.ts"],"names":[],"mappings":";;;;AAAA,kEAAqE;AAErE,sCAKmB;AAEnB,uEAAuE;AACvE,EAAE;AACF,4FAA4F;AAC5F,8FAA8F;AAC9F,8FAA8F;AAC9F,4FAA4F;AAC5F,8DAA8D;AAC9D,EAAE;AACF,6FAA6F;AAC7F,iGAAiG;AACjG,gGAAgG;AACnF,QAAA,SAAS,GAAG,IAAA,+BAAY,EAAC;IACpC,IAAI,EAAE,YAAY;IAClB,IAAI,EAAE,uBAAc;IACpB,WAAW,EAAE,YAAY;IACzB,WAAW,EACT,iJAAiJ;IACnJ,KAAK,EAAE;QACL,KAAK,EAAE,2BAAQ,CAAC,SAAS,CAAC;YACxB,WAAW,EAAE,OAAO;YACpB,WAAW,EACT,sPAAsP;YACxP,QAAQ,EAAE,KAAK;SAChB,CAAC;QACF,MAAM,EAAE,2BAAQ,CAAC,SAAS,CAAC;YACzB,WAAW,EAAE,QAAQ;YACrB,WAAW,EACT,0JAA0J;YAC5J,QAAQ,EAAE,KAAK;SAChB,CAAC;QACF,KAAK,EAAE,2BAAQ,CAAC,MAAM,CAAC;YACrB,WAAW,EAAE,OAAO;YACpB,WAAW,EACT,8SAA8S;YAChT,QAAQ,EAAE,KAAK;SAChB,CAAC;KACH;IACK,GAAG;qEAAC,EAAE,IAAI,EAAE,UAAU,EAAE;;YAC5B,MAAM,KAAK,GAAgB,EAAE,CAAC;YAC9B,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;gBACtB,KAAK,CAAC,QAAQ,CAAC,GAAG,UAAU,CAAC,MAAM,CAAC;YACtC,CAAC;YACD,IAAI,UAAU,CAAC,KAAK,KAAK,SAAS,IAAI,UAAU,CAAC,KAAK,KAAK,IAAI,EAAE,CAAC;gBAChE,KAAK,CAAC,OAAO,CAAC,GAAG,MAAM,CAAC,UAAU,CAAC,KAAK,CAAC,CAAC;YAC5C,CAAC;YAED,MAAM,MAAM,GAAG,MAAA,UAAU,CAAC,KAAK,0CAAE,IAAI,GAAG,WAAW,EAAE,CAAC;YACtD,MAAM,OAAO,GAAG,CAAC,IAAoB,EAAE,EAAE,WACvC,OAAA,CAAA,MAAA,IAAI,CAAC,KAAK,0CAAE,IAAI,GAAG,WAAW,EAAE,MAAK,MAAM,CAAA,EAAA,CAAC;YAE9C,MAAM,EAAE,KAAK,EAAE,QAAQ,EAAE,GAAG,MAAM,IAAA,uBAAc,EAC9C,IAAsB,EACtB,KAAK;YACL,wFAAwF;YACxF,MAAM,CAAC,CAAC,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,SAAS,CACpD,CAAC;YAEF,MAAM,OAAO,GAAG,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC;YAEvD,6FAA6F;YAC7F,4FAA4F;YAC5F,2EAA2E;YAC3E,IAAI,MAAM,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC;gBAChD,MAAM,IAAI,KAAK,CACb,YAAY,KAAK,CAAC,MAAM,qCAAqC,UAAU,CAAC,KAAK,8OAA8O,CAC5T,CAAC;YACJ,CAAC;YAED,OAAO;gBACL,KAAK,EAAE,OAAO,CAAC,MAAM,GAAG,CAAC;gBACzB,KAAK,EAAE,OAAO,CAAC,MAAM;gBACrB,KAAK,EAAE,OAAO;gBACd,QAAQ,EAAE,KAAK,CAAC,MAAM;aACvB,CAAC;QACJ,CAAC;KAAA;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.getUser = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const pieces_framework_1 = require("@taskmagic/pieces-framework");
|
|
6
|
+
const pieces_common_1 = require("@taskmagic/pieces-common");
|
|
7
|
+
const common_1 = require("../common");
|
|
8
|
+
// GET /users/{id} — citation: https://linear.app/taskmagic-dev/issue/TM-860
|
|
9
|
+
exports.getUser = (0, pieces_framework_1.createAction)({
|
|
10
|
+
name: 'get_user',
|
|
11
|
+
auth: common_1.skillplateAuth,
|
|
12
|
+
displayName: 'Get User',
|
|
13
|
+
description: 'Fetch a single Skillplate user by id.',
|
|
14
|
+
props: {
|
|
15
|
+
userId: pieces_framework_1.Property.ShortText({
|
|
16
|
+
displayName: 'User ID',
|
|
17
|
+
description: 'The Skillplate user id — a hashed string such as `uKq2ZbT9rVn4LpAe`, not a number.',
|
|
18
|
+
required: true,
|
|
19
|
+
}),
|
|
20
|
+
},
|
|
21
|
+
run(_a) {
|
|
22
|
+
return tslib_1.__awaiter(this, arguments, void 0, function* ({ auth, propsValue }) {
|
|
23
|
+
return (0, common_1.skillplateRequest)(auth, pieces_common_1.HttpMethod.GET, `/users/${encodeURIComponent(propsValue.userId.trim())}`);
|
|
24
|
+
});
|
|
25
|
+
},
|
|
26
|
+
});
|
|
27
|
+
//# sourceMappingURL=get-user.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"get-user.js","sourceRoot":"","sources":["../../../../../../../../packages/pieces/community/skillplate/src/lib/actions/get-user.ts"],"names":[],"mappings":";;;;AAAA,kEAAqE;AACrE,4DAAsD;AACtD,sCAImB;AAEnB,4EAA4E;AAC/D,QAAA,OAAO,GAAG,IAAA,+BAAY,EAAC;IAClC,IAAI,EAAE,UAAU;IAChB,IAAI,EAAE,uBAAc;IACpB,WAAW,EAAE,UAAU;IACvB,WAAW,EAAE,uCAAuC;IACpD,KAAK,EAAE;QACL,MAAM,EAAE,2BAAQ,CAAC,SAAS,CAAC;YACzB,WAAW,EAAE,SAAS;YACtB,WAAW,EACT,oFAAoF;YACtF,QAAQ,EAAE,IAAI;SACf,CAAC;KACH;IACK,GAAG;qEAAC,EAAE,IAAI,EAAE,UAAU,EAAE;YAC5B,OAAO,IAAA,0BAAiB,EACtB,IAAsB,EACtB,0BAAU,CAAC,GAAG,EACd,UAAU,kBAAkB,CAAC,UAAU,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,EAAE,CACzD,CAAC;QACJ,CAAC;KAAA;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const listCourses: import("@taskmagic/pieces-framework").IAction<import("@taskmagic/pieces-framework").SecretTextProperty<true>, {}>;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.listCourses = void 0;
|
|
4
|
+
const tslib_1 = require("tslib");
|
|
5
|
+
const pieces_framework_1 = require("@taskmagic/pieces-framework");
|
|
6
|
+
const common_1 = require("../common");
|
|
7
|
+
// GET /products — citation: https://linear.app/taskmagic-dev/issue/TM-860
|
|
8
|
+
//
|
|
9
|
+
// A course is a product in Skillplate's API. This action exists mainly so a user can discover
|
|
10
|
+
// the hashed course ids the enrolment actions need without leaving the flow builder.
|
|
11
|
+
exports.listCourses = (0, pieces_framework_1.createAction)({
|
|
12
|
+
name: 'list_courses',
|
|
13
|
+
auth: common_1.skillplateAuth,
|
|
14
|
+
displayName: 'List Courses',
|
|
15
|
+
description: 'List the courses (products) in your Skillplate account, with the ids the enrolment actions need.',
|
|
16
|
+
props: {},
|
|
17
|
+
run(_a) {
|
|
18
|
+
return tslib_1.__awaiter(this, arguments, void 0, function* ({ auth }) {
|
|
19
|
+
const products = yield (0, common_1.listProducts)(auth);
|
|
20
|
+
return { count: products.length, courses: products };
|
|
21
|
+
});
|
|
22
|
+
},
|
|
23
|
+
});
|
|
24
|
+
//# sourceMappingURL=list-courses.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"list-courses.js","sourceRoot":"","sources":["../../../../../../../../packages/pieces/community/skillplate/src/lib/actions/list-courses.ts"],"names":[],"mappings":";;;;AAAA,kEAA2D;AAC3D,sCAImB;AAEnB,0EAA0E;AAC1E,EAAE;AACF,8FAA8F;AAC9F,qFAAqF;AACxE,QAAA,WAAW,GAAG,IAAA,+BAAY,EAAC;IACtC,IAAI,EAAE,cAAc;IACpB,IAAI,EAAE,uBAAc;IACpB,WAAW,EAAE,cAAc;IAC3B,WAAW,EACT,kGAAkG;IACpG,KAAK,EAAE,EAAE;IACH,GAAG;qEAAC,EAAE,IAAI,EAAE;YAChB,MAAM,QAAQ,GAAG,MAAM,IAAA,qBAAY,EAAC,IAAsB,CAAC,CAAC;YAC5D,OAAO,EAAE,KAAK,EAAE,QAAQ,CAAC,MAAM,EAAE,OAAO,EAAE,QAAQ,EAAE,CAAC;QACvD,CAAC;KAAA;CACF,CAAC,CAAC"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { HttpMethod, QueryParams } from '@taskmagic/pieces-common';
|
|
2
|
+
export declare const SKILLPLATE_BASE_URL = "https://api.skillplate.com/v1/external";
|
|
3
|
+
/** PieceAuth.SecretText resolves to the raw Personal Access Token string. */
|
|
4
|
+
export type SkillplateAuth = string;
|
|
5
|
+
export declare class SkillplateApiError extends Error {
|
|
6
|
+
/** The provider HTTP status this was raised for, when there was one. */
|
|
7
|
+
readonly status?: number | undefined;
|
|
8
|
+
constructor(message: string,
|
|
9
|
+
/** The provider HTTP status this was raised for, when there was one. */
|
|
10
|
+
status?: number | undefined);
|
|
11
|
+
}
|
|
12
|
+
export declare const skillplateAuth: import("@taskmagic/pieces-framework").SecretTextProperty<true>;
|
|
13
|
+
export declare function skillplateRequest<T = unknown>(auth: SkillplateAuth, method: HttpMethod, path: string, options?: {
|
|
14
|
+
body?: Record<string, unknown>;
|
|
15
|
+
query?: QueryParams;
|
|
16
|
+
}): Promise<T>;
|
|
17
|
+
export type SkillplateUser = {
|
|
18
|
+
id: string;
|
|
19
|
+
email?: string;
|
|
20
|
+
first_name?: string;
|
|
21
|
+
last_name?: string;
|
|
22
|
+
};
|
|
23
|
+
export type SkillplateProduct = {
|
|
24
|
+
id: string;
|
|
25
|
+
name?: string;
|
|
26
|
+
title?: string;
|
|
27
|
+
};
|
|
28
|
+
export declare function listProducts(auth: SkillplateAuth): Promise<SkillplateProduct[]>;
|
|
29
|
+
/**
|
|
30
|
+
* Walk every page of `GET /users`, stopping early once `stopWhen` is satisfied.
|
|
31
|
+
*
|
|
32
|
+
* An email lookup that only inspects the first page reports `found: false` for a user who
|
|
33
|
+
* genuinely exists further down the list — a false negative that a flow would branch on. That
|
|
34
|
+
* was raised on this PR (Codex P2) and it is the reason this pages at all.
|
|
35
|
+
*/
|
|
36
|
+
export declare function listUsersPaged(auth: SkillplateAuth, query?: QueryParams, stopWhen?: (soFar: SkillplateUser[]) => boolean): Promise<{
|
|
37
|
+
users: SkillplateUser[];
|
|
38
|
+
complete: boolean;
|
|
39
|
+
}>;
|
|
40
|
+
export declare const coursesDropdown: import("@taskmagic/pieces-framework").MultiSelectDropdownProperty<string, false>;
|
|
41
|
+
export declare const courseIdsOverride: import("@taskmagic/pieces-framework").ShortTextProperty<false>;
|
|
42
|
+
/**
|
|
43
|
+
* Skillplate requires `product_ids` to be an **array of strings**. The requester reported that a
|
|
44
|
+
* singular string, or the array structure missing entirely, returns `422 validation_failed` — so
|
|
45
|
+
* this always produces an array, and refuses to send an empty one rather than letting Skillplate
|
|
46
|
+
* reject a request we could see was incomplete.
|
|
47
|
+
*/
|
|
48
|
+
export declare function resolveCourseIds(courses: string[] | undefined, override: string | undefined): string[];
|
|
49
|
+
export declare const SAMPLE_USER: SkillplateUser;
|
|
@@ -0,0 +1,327 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SAMPLE_USER = exports.courseIdsOverride = exports.coursesDropdown = exports.skillplateAuth = exports.SkillplateApiError = exports.SKILLPLATE_BASE_URL = void 0;
|
|
4
|
+
exports.skillplateRequest = skillplateRequest;
|
|
5
|
+
exports.listProducts = listProducts;
|
|
6
|
+
exports.listUsersPaged = listUsersPaged;
|
|
7
|
+
exports.resolveCourseIds = resolveCourseIds;
|
|
8
|
+
const tslib_1 = require("tslib");
|
|
9
|
+
const pieces_common_1 = require("@taskmagic/pieces-common");
|
|
10
|
+
const pieces_framework_1 = require("@taskmagic/pieces-framework");
|
|
11
|
+
/*
|
|
12
|
+
* Docs: https://skillplate.com/docs/api
|
|
13
|
+
*
|
|
14
|
+
* That page is a client-rendered route that redirects to the homepage when signed out, so the
|
|
15
|
+
* reference is readable only by signed-in Skillplate customers and cannot serve as a public
|
|
16
|
+
* citation. The endpoints, field names and constraints below come from the vendor's own
|
|
17
|
+
* documentation as relayed by a Skillplate customer and recorded verbatim on
|
|
18
|
+
* https://linear.app/taskmagic-dev/issue/TM-860 — that ticket is the citation for every route
|
|
19
|
+
* in this piece.
|
|
20
|
+
*
|
|
21
|
+
* Each route was independently probe-verified against the live API before being implemented.
|
|
22
|
+
* The probe discriminates cleanly, because Skillplate routes before it authenticates:
|
|
23
|
+
* real route + bogus token -> 401 {"error":{"code":"invalid_token", ...}}
|
|
24
|
+
* invented route + bogus token -> 404 {"message":"The requested Resource was not found."}
|
|
25
|
+
* Confirmed 401 (real): GET/POST /users, POST /users/{id}/enroll, GET/POST /orders,
|
|
26
|
+
* GET /products, GET /users/{id}. Confirmed 404 (controls): /definitely-not-a-route and
|
|
27
|
+
* /users/{id}/definitely-not-an-action, including the nested form — so /users/{id}/enroll is a
|
|
28
|
+
* genuinely registered sub-route, not a catch-all.
|
|
29
|
+
*/
|
|
30
|
+
exports.SKILLPLATE_BASE_URL = 'https://api.skillplate.com/v1/external';
|
|
31
|
+
class SkillplateApiError extends Error {
|
|
32
|
+
constructor(message,
|
|
33
|
+
/** The provider HTTP status this was raised for, when there was one. */
|
|
34
|
+
status) {
|
|
35
|
+
super(message);
|
|
36
|
+
this.status = status;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
exports.SkillplateApiError = SkillplateApiError;
|
|
40
|
+
/** Only these statuses mean "this credential is wrong". */
|
|
41
|
+
function isAuthStatus(status) {
|
|
42
|
+
return status === 401 || status === 403;
|
|
43
|
+
}
|
|
44
|
+
exports.skillplateAuth = pieces_framework_1.PieceAuth.SecretText({
|
|
45
|
+
displayName: 'Personal Access Token',
|
|
46
|
+
description: `To connect your Skillplate account:
|
|
47
|
+
|
|
48
|
+
1. Sign in to Skillplate and open **Settings → API Tokens** in the dashboard.
|
|
49
|
+
2. Create a new Personal Access Token and copy it.
|
|
50
|
+
3. Paste the token below.
|
|
51
|
+
|
|
52
|
+
Skillplate sends this token as \`Authorization: Bearer <token>\` on every request. If the API
|
|
53
|
+
rejects it with *"Invalid token format. Expected Personal Access Token"*, the value pasted is
|
|
54
|
+
not a Personal Access Token — generate one from Settings → API Tokens rather than reusing a
|
|
55
|
+
token from anywhere else in Skillplate.`,
|
|
56
|
+
required: true,
|
|
57
|
+
// Validate at connect time so a bad token is reported where the user can act on it, rather
|
|
58
|
+
// than surfacing later as an unexplained empty Course list on a step.
|
|
59
|
+
validate: (_a) => tslib_1.__awaiter(void 0, [_a], void 0, function* ({ auth }) {
|
|
60
|
+
try {
|
|
61
|
+
yield skillplateRequest(auth, pieces_common_1.HttpMethod.GET, '/users', {
|
|
62
|
+
query: { limit: '1' },
|
|
63
|
+
});
|
|
64
|
+
return { valid: true };
|
|
65
|
+
}
|
|
66
|
+
catch (e) {
|
|
67
|
+
// Only auth failures invalidate the connection. A 404, a 422, a rate limit or a
|
|
68
|
+
// provider-side 5xx must never tell a user to rotate a token that is actually fine, so
|
|
69
|
+
// anything else is re-thrown and surfaces as its own error rather than as "bad key".
|
|
70
|
+
if (e instanceof SkillplateApiError && isAuthStatus(e.status)) {
|
|
71
|
+
return { valid: false, error: e.message };
|
|
72
|
+
}
|
|
73
|
+
throw e;
|
|
74
|
+
}
|
|
75
|
+
}),
|
|
76
|
+
});
|
|
77
|
+
function skillplateRequest(auth, method, path, options) {
|
|
78
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
79
|
+
return skillplateRequestUrl(auth, method, `${exports.SKILLPLATE_BASE_URL}${path}`, options);
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
function skillplateRequestUrl(auth, method, url, options) {
|
|
83
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
84
|
+
var _a, _b;
|
|
85
|
+
try {
|
|
86
|
+
const response = yield pieces_common_1.httpClient.sendRequest({
|
|
87
|
+
method,
|
|
88
|
+
url,
|
|
89
|
+
headers: {
|
|
90
|
+
'Content-Type': 'application/json',
|
|
91
|
+
Accept: 'application/json',
|
|
92
|
+
Authorization: `Bearer ${auth.trim()}`,
|
|
93
|
+
},
|
|
94
|
+
// httpClient defaults to no timeout (0). Bound every call so a stalled request cannot
|
|
95
|
+
// hang an action indefinitely. axios timeout is in ms.
|
|
96
|
+
timeout: 30000,
|
|
97
|
+
queryParams: options === null || options === void 0 ? void 0 : options.query,
|
|
98
|
+
body: options === null || options === void 0 ? void 0 : options.body,
|
|
99
|
+
});
|
|
100
|
+
return response.body;
|
|
101
|
+
}
|
|
102
|
+
catch (e) {
|
|
103
|
+
if (e instanceof SkillplateApiError) {
|
|
104
|
+
throw e;
|
|
105
|
+
}
|
|
106
|
+
const status = (_a = e === null || e === void 0 ? void 0 : e.response) === null || _a === void 0 ? void 0 : _a.status;
|
|
107
|
+
const body = (_b = e === null || e === void 0 ? void 0 : e.response) === null || _b === void 0 ? void 0 : _b.body;
|
|
108
|
+
if (status === 401) {
|
|
109
|
+
throw new SkillplateApiError(`Skillplate rejected the token (HTTP 401). Reconnect with a Personal Access Token from Settings → API Tokens. ${describeError(body)}`.trim(), 401);
|
|
110
|
+
}
|
|
111
|
+
if (status === 403) {
|
|
112
|
+
throw new SkillplateApiError(`Skillplate refused this request (HTTP 403). The token is valid but not allowed to do this — check the token's permissions in Settings → API Tokens. ${describeError(body)}`.trim(), 403);
|
|
113
|
+
}
|
|
114
|
+
if (status === 404) {
|
|
115
|
+
throw new SkillplateApiError(`Skillplate returned 404 for ${safePath(url)}. Check the user id and course (product) id on this step — Skillplate ids are hashed strings such as \`aB3xK9mQ2pR7sT1v\`, not numbers. ${describeError(body)}`.trim(), 404);
|
|
116
|
+
}
|
|
117
|
+
if (status === 422) {
|
|
118
|
+
// The requester specifically flagged 422 as the failure mode when `product_ids` is sent
|
|
119
|
+
// as a bare string instead of an array, so relay Skillplate's own field errors rather
|
|
120
|
+
// than a generic message — they name the offending field.
|
|
121
|
+
throw new SkillplateApiError(`Skillplate rejected the request body (HTTP 422 validation_failed). ${describeError(body)}`.trim(), 422);
|
|
122
|
+
}
|
|
123
|
+
if (status === 429) {
|
|
124
|
+
throw new SkillplateApiError('Skillplate rate limit hit (HTTP 429). Slow the flow down and retry.', 429);
|
|
125
|
+
}
|
|
126
|
+
throw e;
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/** The path only, so a query string can never carry a value into an error message. */
|
|
131
|
+
function safePath(url) {
|
|
132
|
+
try {
|
|
133
|
+
return new URL(url).pathname;
|
|
134
|
+
}
|
|
135
|
+
catch (_a) {
|
|
136
|
+
return url;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/** Surface Skillplate's own error text, which names the specific field on a 422. */
|
|
140
|
+
function describeError(body) {
|
|
141
|
+
var _a, _b;
|
|
142
|
+
const record = body;
|
|
143
|
+
const message = (_b = (_a = record === null || record === void 0 ? void 0 : record.error) === null || _a === void 0 ? void 0 : _a.message) !== null && _b !== void 0 ? _b : record === null || record === void 0 ? void 0 : record.message;
|
|
144
|
+
const errors = record === null || record === void 0 ? void 0 : record.errors;
|
|
145
|
+
const details = errors && typeof errors === 'object' && Object.keys(errors).length > 0
|
|
146
|
+
? ` Details: ${JSON.stringify(errors)}`
|
|
147
|
+
: '';
|
|
148
|
+
return message ? `Skillplate said: ${message}.${details}` : '';
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Skillplate's list responses are not documented envelope-by-envelope, and this piece was built
|
|
152
|
+
* without an account to observe them. Tolerate the common shapes instead of assuming one: the
|
|
153
|
+
* failure mode of guessing wrong is `.map` on a non-array, which the builder renders as a bare
|
|
154
|
+
* "Throws an error" on the dropdown — indistinguishable from an auth failure, and the exact
|
|
155
|
+
* complaint MyMarky was reported for (TM-603).
|
|
156
|
+
*/
|
|
157
|
+
function extractList(body) {
|
|
158
|
+
if (Array.isArray(body)) {
|
|
159
|
+
return body;
|
|
160
|
+
}
|
|
161
|
+
const record = body;
|
|
162
|
+
for (const key of ['data', 'items', 'results', 'users', 'products']) {
|
|
163
|
+
const value = record === null || record === void 0 ? void 0 : record[key];
|
|
164
|
+
if (Array.isArray(value)) {
|
|
165
|
+
return value;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return null;
|
|
169
|
+
}
|
|
170
|
+
function listProducts(auth) {
|
|
171
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
172
|
+
const body = yield skillplateRequest(auth, pieces_common_1.HttpMethod.GET, '/products');
|
|
173
|
+
const list = extractList(body);
|
|
174
|
+
if (list === null) {
|
|
175
|
+
throw new SkillplateApiError('Skillplate did not return a list of courses (products). If this account has no courses yet, create one in Skillplate first.');
|
|
176
|
+
}
|
|
177
|
+
return list;
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
/*
|
|
181
|
+
* Skillplate runs on Apiato/Laravel (the API root answers "Welcome to Apiato"), whose paginated
|
|
182
|
+
* responses carry the next page as `links.next` / `next_page_url`. This reads those standard
|
|
183
|
+
* keys if they are present and returns undefined otherwise — it does not invent a Skillplate
|
|
184
|
+
* page parameter.
|
|
185
|
+
*
|
|
186
|
+
* It only follows a link back to Skillplate's own host. A `next` pointing anywhere else would
|
|
187
|
+
* send the user's Personal Access Token to that host, which is the same credential-egress
|
|
188
|
+
* shape Codex flagged on this PR for the shared Custom API Call helper. Cheap to rule out here.
|
|
189
|
+
*/
|
|
190
|
+
function extractNextUrl(body) {
|
|
191
|
+
var _a;
|
|
192
|
+
const record = body;
|
|
193
|
+
for (const candidate of [
|
|
194
|
+
(_a = record === null || record === void 0 ? void 0 : record.links) === null || _a === void 0 ? void 0 : _a.next,
|
|
195
|
+
record === null || record === void 0 ? void 0 : record.next_page_url,
|
|
196
|
+
record === null || record === void 0 ? void 0 : record.next,
|
|
197
|
+
]) {
|
|
198
|
+
if (typeof candidate === 'string' &&
|
|
199
|
+
candidate.startsWith(`${exports.SKILLPLATE_BASE_URL}/`)) {
|
|
200
|
+
return candidate;
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
return undefined;
|
|
204
|
+
}
|
|
205
|
+
/** Hard stop so a malformed or cyclic `next` chain cannot spin an action forever. */
|
|
206
|
+
const MAX_USER_PAGES = 20;
|
|
207
|
+
/**
|
|
208
|
+
* Walk every page of `GET /users`, stopping early once `stopWhen` is satisfied.
|
|
209
|
+
*
|
|
210
|
+
* An email lookup that only inspects the first page reports `found: false` for a user who
|
|
211
|
+
* genuinely exists further down the list — a false negative that a flow would branch on. That
|
|
212
|
+
* was raised on this PR (Codex P2) and it is the reason this pages at all.
|
|
213
|
+
*/
|
|
214
|
+
function listUsersPaged(auth, query, stopWhen) {
|
|
215
|
+
return tslib_1.__awaiter(this, void 0, void 0, function* () {
|
|
216
|
+
const collected = [];
|
|
217
|
+
let body = yield skillplateRequest(auth, pieces_common_1.HttpMethod.GET, '/users', {
|
|
218
|
+
query,
|
|
219
|
+
});
|
|
220
|
+
for (let page = 0; page < MAX_USER_PAGES; page++) {
|
|
221
|
+
const list = extractList(body);
|
|
222
|
+
if (list === null) {
|
|
223
|
+
throw new SkillplateApiError('Skillplate did not return a list of users.');
|
|
224
|
+
}
|
|
225
|
+
collected.push(...list);
|
|
226
|
+
if (stopWhen === null || stopWhen === void 0 ? void 0 : stopWhen(collected)) {
|
|
227
|
+
return { users: collected, complete: true };
|
|
228
|
+
}
|
|
229
|
+
const nextUrl = extractNextUrl(body);
|
|
230
|
+
if (!nextUrl) {
|
|
231
|
+
return { users: collected, complete: true };
|
|
232
|
+
}
|
|
233
|
+
body = yield skillplateRequestUrl(auth, pieces_common_1.HttpMethod.GET, nextUrl);
|
|
234
|
+
}
|
|
235
|
+
// Ran out of pages before running out of data.
|
|
236
|
+
return { users: collected, complete: false };
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
function connectFirst() {
|
|
240
|
+
return {
|
|
241
|
+
disabled: true,
|
|
242
|
+
placeholder: 'Connect your Skillplate account first',
|
|
243
|
+
options: [],
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function productLabel(product) {
|
|
247
|
+
var _a, _b;
|
|
248
|
+
return (_b = (_a = product.name) !== null && _a !== void 0 ? _a : product.title) !== null && _b !== void 0 ? _b : product.id;
|
|
249
|
+
}
|
|
250
|
+
/*
|
|
251
|
+
* Courses are OPTIONAL here and paired with `courseIdsOverride` below.
|
|
252
|
+
*
|
|
253
|
+
* This piece was built from a relayed spec with no Skillplate account behind it, so the shape of
|
|
254
|
+
* `GET /products` is inferred rather than observed. A required dropdown that cannot populate is a
|
|
255
|
+
* dead end — a `Property.MultiSelectDropdown` accepts no typed value, so the user cannot proceed
|
|
256
|
+
* at all. MyMarky shipped exactly that and a tester reported "there is a dropdown but no items to
|
|
257
|
+
* select, and when trying to type something, it cancels out" (TM-603). Pairing the list with a
|
|
258
|
+
* paste-an-id field means a user who knows their course id is never blocked by our inference.
|
|
259
|
+
*/
|
|
260
|
+
exports.coursesDropdown = pieces_framework_1.Property.MultiSelectDropdown({
|
|
261
|
+
displayName: 'Courses',
|
|
262
|
+
description: 'The Skillplate courses to enrol into. A course is a "product" in Skillplate\'s API. If this list is empty or will not load, leave it blank and use Course IDs below.',
|
|
263
|
+
required: false,
|
|
264
|
+
refreshers: [],
|
|
265
|
+
options: (_a) => tslib_1.__awaiter(void 0, [_a], void 0, function* ({ auth }) {
|
|
266
|
+
if (!auth) {
|
|
267
|
+
return connectFirst();
|
|
268
|
+
}
|
|
269
|
+
try {
|
|
270
|
+
const products = yield listProducts(auth);
|
|
271
|
+
if (products.length === 0) {
|
|
272
|
+
return {
|
|
273
|
+
disabled: true,
|
|
274
|
+
placeholder: 'No courses in this Skillplate account yet — create one in Skillplate, then reopen this list.',
|
|
275
|
+
options: [],
|
|
276
|
+
};
|
|
277
|
+
}
|
|
278
|
+
return {
|
|
279
|
+
options: products.map((product) => ({
|
|
280
|
+
label: productLabel(product),
|
|
281
|
+
value: product.id,
|
|
282
|
+
})),
|
|
283
|
+
};
|
|
284
|
+
}
|
|
285
|
+
catch (e) {
|
|
286
|
+
// An unhandled throw renders as the framework's bare "Throws an error", which names no
|
|
287
|
+
// cause and leaves the user nothing to act on.
|
|
288
|
+
return {
|
|
289
|
+
disabled: true,
|
|
290
|
+
placeholder: e instanceof SkillplateApiError
|
|
291
|
+
? e.message
|
|
292
|
+
: 'Could not load your courses from Skillplate. Check the token on this connection.',
|
|
293
|
+
options: [],
|
|
294
|
+
};
|
|
295
|
+
}
|
|
296
|
+
}),
|
|
297
|
+
});
|
|
298
|
+
exports.courseIdsOverride = pieces_framework_1.Property.ShortText({
|
|
299
|
+
displayName: 'Course IDs',
|
|
300
|
+
description: 'Optional. Comma-separated Skillplate course (product) ids — use this when the Courses list above is empty or will not load. These are added to anything picked above. An id is a hashed string, not a number: open the course settings in Skillplate and copy the `product_id` from the URL, e.g. `?product_id=aB3xK9mQ2pR7sT1v`.',
|
|
301
|
+
required: false,
|
|
302
|
+
});
|
|
303
|
+
/**
|
|
304
|
+
* Skillplate requires `product_ids` to be an **array of strings**. The requester reported that a
|
|
305
|
+
* singular string, or the array structure missing entirely, returns `422 validation_failed` — so
|
|
306
|
+
* this always produces an array, and refuses to send an empty one rather than letting Skillplate
|
|
307
|
+
* reject a request we could see was incomplete.
|
|
308
|
+
*/
|
|
309
|
+
function resolveCourseIds(courses, override) {
|
|
310
|
+
const fromDropdown = (courses !== null && courses !== void 0 ? courses : []).map((id) => String(id).trim());
|
|
311
|
+
const fromOverride = (override !== null && override !== void 0 ? override : '')
|
|
312
|
+
.split(',')
|
|
313
|
+
.map((id) => id.trim())
|
|
314
|
+
.filter((id) => id.length > 0);
|
|
315
|
+
const merged = [...new Set([...fromDropdown, ...fromOverride])].filter((id) => id.length > 0);
|
|
316
|
+
if (merged.length === 0) {
|
|
317
|
+
throw new SkillplateApiError('This action needs at least one course: pick one from the Courses list, or paste an id into Course IDs. If the list is empty, Skillplate returned no courses for this token.');
|
|
318
|
+
}
|
|
319
|
+
return merged;
|
|
320
|
+
}
|
|
321
|
+
exports.SAMPLE_USER = {
|
|
322
|
+
id: 'uKq2ZbT9rVn4LpAe',
|
|
323
|
+
email: 'learner@example.com',
|
|
324
|
+
first_name: 'Ada',
|
|
325
|
+
last_name: 'Lovelace',
|
|
326
|
+
};
|
|
327
|
+
//# sourceMappingURL=common.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"common.js","sourceRoot":"","sources":["../../../../../../../packages/pieces/community/skillplate/src/lib/common.ts"],"names":[],"mappings":"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"}
|