runline 0.14.0 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/plugins/_shared/shiftCloud.js +65 -0
- package/dist/plugins/shiftCrm/src/access.js +45 -0
- package/dist/plugins/shiftCrm/src/accounts.js +81 -0
- package/dist/plugins/shiftCrm/src/activities.js +61 -0
- package/dist/plugins/shiftCrm/src/imports.js +101 -0
- package/dist/plugins/shiftCrm/src/index.js +28 -0
- package/dist/plugins/shiftCrm/src/opportunities.js +145 -0
- package/dist/plugins/shiftCrm/src/people.js +117 -0
- package/dist/plugins/shiftCrm/src/records.js +80 -0
- package/dist/plugins/shiftCrm/src/shared.js +122 -0
- package/dist/plugins/shiftCrm/src/tasks.js +79 -0
- package/dist/plugins/shiftLabs/src/issue-views.js +52 -31
- package/dist/plugins/shiftLabs/src/issues.js +61 -51
- package/dist/plugins/shiftLabs/src/pages.js +60 -34
- package/dist/plugins/shiftLabs/src/projects.js +28 -19
- package/dist/plugins/shiftLabs/src/shared.js +2 -35
- package/package.json +1 -1
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import * as t from "typebox";
|
|
2
|
-
import { enumSchema, ISSUE_PRIORITY, ISSUE_SOURCE, ISSUE_STATUS, pathSegment, request, } from "./shared.js";
|
|
2
|
+
import { cursorSchema, enumSchema, ISSUE_PRIORITY, ISSUE_SOURCE, ISSUE_STATUS, idSchema, pathSegment, request, STRICT_OBJECT, STRICT_UPDATE_OBJECT, timestampSchema, } from "./shared.js";
|
|
3
3
|
const issueListFields = {
|
|
4
4
|
status: t.Optional(enumSchema("Issue status", ISSUE_STATUS)),
|
|
5
|
-
projectId: t.Optional(
|
|
6
|
-
parentIssueId: t.Optional(
|
|
7
|
-
assigneeUserId: t.Optional(
|
|
5
|
+
projectId: t.Optional(idSchema("Project ID")),
|
|
6
|
+
parentIssueId: t.Optional(idSchema("Parent Issue ID")),
|
|
7
|
+
assigneeUserId: t.Optional(idSchema("Assignee user ID")),
|
|
8
8
|
source: t.Optional(enumSchema("Issue source", ISSUE_SOURCE)),
|
|
9
|
-
|
|
9
|
+
includeArchived: t.Optional(t.Boolean()),
|
|
10
|
+
cursor: t.Optional(cursorSchema()),
|
|
10
11
|
limit: t.Optional(t.Integer({
|
|
11
12
|
minimum: 1,
|
|
12
13
|
maximum: 100,
|
|
@@ -29,7 +30,7 @@ export function registerIssueActions(rl) {
|
|
|
29
30
|
rl.registerAction("issue.list", {
|
|
30
31
|
access: "read",
|
|
31
32
|
description: "List the first page of Shift Labs Issues for the API key's organization.",
|
|
32
|
-
inputSchema: t.Object(issueListFields),
|
|
33
|
+
inputSchema: t.Object(issueListFields, STRICT_OBJECT),
|
|
33
34
|
async execute(input, ctx) {
|
|
34
35
|
const body = await request(ctx, `/v1/issues?${issueListParams(input)}`);
|
|
35
36
|
return body.issues;
|
|
@@ -38,7 +39,7 @@ export function registerIssueActions(rl) {
|
|
|
38
39
|
rl.registerAction("issue.listPage", {
|
|
39
40
|
access: "read",
|
|
40
41
|
description: "List a cursor-paginated page of Shift Labs Issues.",
|
|
41
|
-
inputSchema: t.Object(issueListFields),
|
|
42
|
+
inputSchema: t.Object(issueListFields, STRICT_OBJECT),
|
|
42
43
|
async execute(input, ctx) {
|
|
43
44
|
return request(ctx, `/v1/issues?${issueListParams(input)}`);
|
|
44
45
|
},
|
|
@@ -46,9 +47,7 @@ export function registerIssueActions(rl) {
|
|
|
46
47
|
rl.registerAction("issue.get", {
|
|
47
48
|
access: "read",
|
|
48
49
|
description: "Get a Shift Labs Issue by ID.",
|
|
49
|
-
inputSchema: t.Object({
|
|
50
|
-
id: t.String({ minLength: 1, description: "Issue ID" }),
|
|
51
|
-
}),
|
|
50
|
+
inputSchema: t.Object({ id: idSchema("Issue ID") }, STRICT_OBJECT),
|
|
52
51
|
async execute(input, ctx) {
|
|
53
52
|
const { id } = input;
|
|
54
53
|
const body = await request(ctx, `/v1/issues/${pathSegment(id)}`);
|
|
@@ -62,30 +61,37 @@ export function registerIssueActions(rl) {
|
|
|
62
61
|
title: t.String({
|
|
63
62
|
minLength: 1,
|
|
64
63
|
maxLength: 160,
|
|
64
|
+
pattern: "\\S",
|
|
65
65
|
description: "Issue title",
|
|
66
66
|
}),
|
|
67
67
|
description: t.Optional(t.String({ maxLength: 20_000, description: "Issue description" })),
|
|
68
|
-
projectId: t.Optional(
|
|
69
|
-
parentIssueId: t.Optional(
|
|
68
|
+
projectId: t.Optional(idSchema("Project ID")),
|
|
69
|
+
parentIssueId: t.Optional(idSchema("Parent Issue ID")),
|
|
70
70
|
sortOrder: t.Optional(t.Integer({ minimum: 0 })),
|
|
71
71
|
status: t.Optional(enumSchema("Issue status", ISSUE_STATUS)),
|
|
72
72
|
priority: t.Optional(enumSchema("Issue priority", ISSUE_PRIORITY)),
|
|
73
73
|
source: t.Optional(enumSchema("Issue source", ISSUE_SOURCE)),
|
|
74
|
-
assigneeUserId: t.Optional(
|
|
75
|
-
startAt: t.Optional(
|
|
76
|
-
dueAt: t.Optional(
|
|
77
|
-
deploymentId: t.Optional(
|
|
78
|
-
workspaceId: t.Optional(
|
|
79
|
-
sessionId: t.Optional(
|
|
80
|
-
traceId: t.Optional(
|
|
81
|
-
fingerprint: t.Optional(t.String({ minLength: 1, maxLength: 512 })),
|
|
82
|
-
labels: t.Optional(t.Array(t.String({ minLength: 1, maxLength: 64 }))),
|
|
74
|
+
assigneeUserId: t.Optional(idSchema("Assignee user ID")),
|
|
75
|
+
startAt: t.Optional(timestampSchema("ISO-8601 start timestamp")),
|
|
76
|
+
dueAt: t.Optional(timestampSchema("ISO-8601 due timestamp")),
|
|
77
|
+
deploymentId: t.Optional(idSchema("Deployment ID")),
|
|
78
|
+
workspaceId: t.Optional(idSchema("Workspace ID")),
|
|
79
|
+
sessionId: t.Optional(idSchema("Session ID")),
|
|
80
|
+
traceId: t.Optional(idSchema("Trace ID")),
|
|
81
|
+
fingerprint: t.Optional(t.String({ minLength: 1, maxLength: 512, pattern: "\\S" })),
|
|
82
|
+
labels: t.Optional(t.Array(t.String({ minLength: 1, maxLength: 64, pattern: "\\S" }))),
|
|
83
83
|
metadata: t.Optional(t.Record(t.String(), t.Unknown(), { description: "Issue metadata" })),
|
|
84
|
-
}),
|
|
84
|
+
}, STRICT_OBJECT),
|
|
85
85
|
async execute(input, ctx) {
|
|
86
|
+
const fields = input;
|
|
87
|
+
if (fields.parentIssueId !== undefined &&
|
|
88
|
+
fields.projectId === undefined) {
|
|
89
|
+
throw new Error("projectId is required when parentIssueId is set");
|
|
90
|
+
}
|
|
91
|
+
assertDateOrder(fields.startAt, fields.dueAt, "Issue dueAt must not precede startAt");
|
|
86
92
|
const body = await request(ctx, "/v1/issues", {
|
|
87
93
|
method: "POST",
|
|
88
|
-
body: JSON.stringify(
|
|
94
|
+
body: JSON.stringify(fields),
|
|
89
95
|
});
|
|
90
96
|
return body.issue;
|
|
91
97
|
},
|
|
@@ -94,22 +100,23 @@ export function registerIssueActions(rl) {
|
|
|
94
100
|
access: "write",
|
|
95
101
|
description: "Update an Issue, including Project assignment, one-level parentage, ordering, status, dates, or assignee.",
|
|
96
102
|
inputSchema: t.Object({
|
|
97
|
-
id:
|
|
98
|
-
title: t.Optional(t.String({ minLength: 1, maxLength: 160 })),
|
|
103
|
+
id: idSchema("Issue ID"),
|
|
104
|
+
title: t.Optional(t.String({ minLength: 1, maxLength: 160, pattern: "\\S" })),
|
|
99
105
|
description: t.Optional(t.String({ maxLength: 20_000 })),
|
|
100
|
-
projectId: t.Optional(t.Union([
|
|
101
|
-
parentIssueId: t.Optional(t.Union([
|
|
106
|
+
projectId: t.Optional(t.Union([idSchema("Project ID"), t.Null()])),
|
|
107
|
+
parentIssueId: t.Optional(t.Union([idSchema("Parent Issue ID"), t.Null()])),
|
|
102
108
|
sortOrder: t.Optional(t.Integer({ minimum: 0 })),
|
|
103
109
|
status: t.Optional(enumSchema("Issue status", ISSUE_STATUS)),
|
|
104
110
|
priority: t.Optional(enumSchema("Issue priority", ISSUE_PRIORITY)),
|
|
105
|
-
assigneeUserId: t.Optional(t.Union([
|
|
106
|
-
startAt: t.Optional(t.Union([
|
|
107
|
-
dueAt: t.Optional(t.Union([
|
|
108
|
-
labels: t.Optional(t.Array(t.String({ minLength: 1, maxLength: 64 }))),
|
|
111
|
+
assigneeUserId: t.Optional(t.Union([idSchema("Assignee user ID"), t.Null()])),
|
|
112
|
+
startAt: t.Optional(t.Union([timestampSchema("ISO-8601 start timestamp"), t.Null()])),
|
|
113
|
+
dueAt: t.Optional(t.Union([timestampSchema("ISO-8601 due timestamp"), t.Null()])),
|
|
114
|
+
labels: t.Optional(t.Array(t.String({ minLength: 1, maxLength: 64, pattern: "\\S" }))),
|
|
109
115
|
archived: t.Optional(t.Boolean()),
|
|
110
|
-
},
|
|
116
|
+
}, STRICT_UPDATE_OBJECT),
|
|
111
117
|
async execute(input, ctx) {
|
|
112
118
|
const { id, ...patch } = input;
|
|
119
|
+
assertDateOrder(patch.startAt, patch.dueAt, "Issue dueAt must not precede startAt");
|
|
113
120
|
const body = await request(ctx, `/v1/issues/${pathSegment(id)}`, { method: "PATCH", body: JSON.stringify(patch) });
|
|
114
121
|
return body.issue;
|
|
115
122
|
},
|
|
@@ -118,13 +125,14 @@ export function registerIssueActions(rl) {
|
|
|
118
125
|
access: "write",
|
|
119
126
|
description: "Add a comment to a Shift Labs Issue.",
|
|
120
127
|
inputSchema: t.Object({
|
|
121
|
-
id:
|
|
128
|
+
id: idSchema("Issue ID"),
|
|
122
129
|
body: t.String({
|
|
123
130
|
minLength: 1,
|
|
124
131
|
maxLength: 20_000,
|
|
132
|
+
pattern: "\\S",
|
|
125
133
|
description: "Comment body",
|
|
126
134
|
}),
|
|
127
|
-
}),
|
|
135
|
+
}, STRICT_OBJECT),
|
|
128
136
|
async execute(input, ctx) {
|
|
129
137
|
const { id, body } = input;
|
|
130
138
|
const response = await request(ctx, `/v1/issues/${pathSegment(id)}/comments`, { method: "POST", body: JSON.stringify({ body }) });
|
|
@@ -135,9 +143,9 @@ export function registerIssueActions(rl) {
|
|
|
135
143
|
access: "read",
|
|
136
144
|
description: "List the first page of blocking and blocked-by links.",
|
|
137
145
|
inputSchema: t.Object({
|
|
138
|
-
id:
|
|
146
|
+
id: idSchema("Issue ID"),
|
|
139
147
|
limit: t.Optional(t.Integer({ minimum: 1, maximum: 100 })),
|
|
140
|
-
}),
|
|
148
|
+
}, STRICT_OBJECT),
|
|
141
149
|
async execute(input, ctx) {
|
|
142
150
|
const { id, ...pagination } = input;
|
|
143
151
|
const body = await request(ctx, withQuery(`/v1/issues/${pathSegment(id)}/dependencies`, issueListParams(pagination)));
|
|
@@ -148,10 +156,10 @@ export function registerIssueActions(rl) {
|
|
|
148
156
|
access: "read",
|
|
149
157
|
description: "List a cursor-paginated page of Issue dependencies.",
|
|
150
158
|
inputSchema: t.Object({
|
|
151
|
-
id:
|
|
152
|
-
cursor: t.Optional(
|
|
159
|
+
id: idSchema("Issue ID"),
|
|
160
|
+
cursor: t.Optional(cursorSchema()),
|
|
153
161
|
limit: t.Optional(t.Integer({ minimum: 1, maximum: 100 })),
|
|
154
|
-
}),
|
|
162
|
+
}, STRICT_OBJECT),
|
|
155
163
|
async execute(input, ctx) {
|
|
156
164
|
const { id, ...pagination } = input;
|
|
157
165
|
return request(ctx, withQuery(`/v1/issues/${pathSegment(id)}/dependencies`, issueListParams(pagination)));
|
|
@@ -161,17 +169,14 @@ export function registerIssueActions(rl) {
|
|
|
161
169
|
access: "write",
|
|
162
170
|
description: "Declare that one Issue blocks another Issue.",
|
|
163
171
|
inputSchema: t.Object({
|
|
164
|
-
blockedIssueId:
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
}),
|
|
168
|
-
blockingIssueId: t.String({
|
|
169
|
-
minLength: 1,
|
|
170
|
-
description: "Issue that blocks it",
|
|
171
|
-
}),
|
|
172
|
-
}),
|
|
172
|
+
blockedIssueId: idSchema("Issue that is blocked"),
|
|
173
|
+
blockingIssueId: idSchema("Issue that blocks it"),
|
|
174
|
+
}, STRICT_OBJECT),
|
|
173
175
|
async execute(input, ctx) {
|
|
174
176
|
const { blockedIssueId, blockingIssueId } = input;
|
|
177
|
+
if (blockedIssueId === blockingIssueId) {
|
|
178
|
+
throw new Error("An Issue cannot block itself");
|
|
179
|
+
}
|
|
175
180
|
const body = await request(ctx, `/v1/issues/${pathSegment(blockedIssueId)}/dependencies`, { method: "POST", body: JSON.stringify({ blockingIssueId }) });
|
|
176
181
|
return body.dependency;
|
|
177
182
|
},
|
|
@@ -180,9 +185,9 @@ export function registerIssueActions(rl) {
|
|
|
180
185
|
access: "write",
|
|
181
186
|
description: "Remove an Issue dependency link.",
|
|
182
187
|
inputSchema: t.Object({
|
|
183
|
-
issueId:
|
|
184
|
-
dependencyId:
|
|
185
|
-
}),
|
|
188
|
+
issueId: idSchema("Issue ID"),
|
|
189
|
+
dependencyId: idSchema("Dependency ID"),
|
|
190
|
+
}, STRICT_OBJECT),
|
|
186
191
|
async execute(input, ctx) {
|
|
187
192
|
const { issueId, dependencyId } = input;
|
|
188
193
|
await request(ctx, `/v1/issues/${pathSegment(issueId)}/dependencies/${pathSegment(dependencyId)}`, { method: "DELETE" });
|
|
@@ -190,3 +195,8 @@ export function registerIssueActions(rl) {
|
|
|
190
195
|
},
|
|
191
196
|
});
|
|
192
197
|
}
|
|
198
|
+
function assertDateOrder(start, end, message) {
|
|
199
|
+
if (typeof start === "string" && typeof end === "string" && start > end) {
|
|
200
|
+
throw new Error(message);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
@@ -1,13 +1,23 @@
|
|
|
1
1
|
import * as t from "typebox";
|
|
2
|
-
import { enumSchema, PAGE_STATUS, PAGE_VISIBILITY, pageRenderUrl, request, } from "./shared.js";
|
|
2
|
+
import { enumSchema, idSchema, PAGE_STATUS, PAGE_VISIBILITY, pageRenderUrl, pathSegment, request, STRICT_OBJECT, timestampSchema, } from "./shared.js";
|
|
3
|
+
const PageSlug = t.String({
|
|
4
|
+
minLength: 1,
|
|
5
|
+
maxLength: 120,
|
|
6
|
+
pattern: "^[a-z0-9]+(?:-[a-z0-9]+)*$",
|
|
7
|
+
description: "Lowercase kebab-case page slug",
|
|
8
|
+
});
|
|
3
9
|
export function registerPageActions(rl) {
|
|
4
10
|
rl.registerAction("page.list", {
|
|
5
11
|
access: "read",
|
|
6
12
|
description: "List Shift Labs pages for the API key's organization.",
|
|
7
13
|
inputSchema: t.Object({
|
|
8
14
|
status: t.Optional(enumSchema("Page status", PAGE_STATUS)),
|
|
9
|
-
limit: t.Optional(t.
|
|
10
|
-
|
|
15
|
+
limit: t.Optional(t.Integer({
|
|
16
|
+
minimum: 1,
|
|
17
|
+
maximum: 100,
|
|
18
|
+
description: "Max results, default 50",
|
|
19
|
+
})),
|
|
20
|
+
}, STRICT_OBJECT),
|
|
11
21
|
async execute(input, ctx) {
|
|
12
22
|
const fields = (input ?? {});
|
|
13
23
|
const params = new URLSearchParams();
|
|
@@ -22,10 +32,10 @@ export function registerPageActions(rl) {
|
|
|
22
32
|
rl.registerAction("page.get", {
|
|
23
33
|
access: "read",
|
|
24
34
|
description: "Get a Shift Labs page by ID.",
|
|
25
|
-
inputSchema: t.Object({ id:
|
|
35
|
+
inputSchema: t.Object({ id: idSchema("Page ID") }, STRICT_OBJECT),
|
|
26
36
|
async execute(input, ctx) {
|
|
27
37
|
const { id } = input;
|
|
28
|
-
const body = await request(ctx, `/v1/pages/${
|
|
38
|
+
const body = await request(ctx, `/v1/pages/${pathSegment(id)}`);
|
|
29
39
|
return body.page;
|
|
30
40
|
},
|
|
31
41
|
});
|
|
@@ -33,11 +43,19 @@ export function registerPageActions(rl) {
|
|
|
33
43
|
access: "write",
|
|
34
44
|
description: "Create a draft hosted HTML page. Agents can publish it with page.publish.",
|
|
35
45
|
inputSchema: t.Object({
|
|
36
|
-
slug:
|
|
37
|
-
title: t.String({
|
|
38
|
-
|
|
46
|
+
slug: PageSlug,
|
|
47
|
+
title: t.String({
|
|
48
|
+
minLength: 1,
|
|
49
|
+
maxLength: 160,
|
|
50
|
+
pattern: "\\S",
|
|
51
|
+
description: "Page title",
|
|
52
|
+
}),
|
|
53
|
+
html: t.Optional(t.String({
|
|
54
|
+
maxLength: 1_000_000,
|
|
55
|
+
description: "Hosted HTML content",
|
|
56
|
+
})),
|
|
39
57
|
visibility: t.Optional(enumSchema("Page visibility", PAGE_VISIBILITY)),
|
|
40
|
-
}),
|
|
58
|
+
}, STRICT_OBJECT),
|
|
41
59
|
async execute(input, ctx) {
|
|
42
60
|
const fields = input;
|
|
43
61
|
const body = await request(ctx, "/v1/pages", {
|
|
@@ -55,15 +73,23 @@ export function registerPageActions(rl) {
|
|
|
55
73
|
access: "write",
|
|
56
74
|
description: "Update a hosted HTML page's slug, title, visibility, or HTML.",
|
|
57
75
|
inputSchema: t.Object({
|
|
58
|
-
id:
|
|
59
|
-
slug: t.Optional(
|
|
60
|
-
title: t.Optional(t.String({
|
|
76
|
+
id: idSchema("Page ID"),
|
|
77
|
+
slug: t.Optional(PageSlug),
|
|
78
|
+
title: t.Optional(t.String({
|
|
79
|
+
minLength: 1,
|
|
80
|
+
maxLength: 160,
|
|
81
|
+
pattern: "\\S",
|
|
82
|
+
description: "Page title",
|
|
83
|
+
})),
|
|
61
84
|
visibility: t.Optional(enumSchema("Page visibility", PAGE_VISIBILITY)),
|
|
62
|
-
html: t.Optional(t.String({
|
|
63
|
-
|
|
85
|
+
html: t.Optional(t.String({
|
|
86
|
+
maxLength: 1_000_000,
|
|
87
|
+
description: "Hosted HTML content",
|
|
88
|
+
})),
|
|
89
|
+
}, STRICT_OBJECT),
|
|
64
90
|
async execute(input, ctx) {
|
|
65
91
|
const { id, ...fields } = input;
|
|
66
|
-
const body = await request(ctx, `/v1/pages/${
|
|
92
|
+
const body = await request(ctx, `/v1/pages/${pathSegment(String(id))}`, {
|
|
67
93
|
method: "PATCH",
|
|
68
94
|
body: JSON.stringify(fields),
|
|
69
95
|
});
|
|
@@ -73,30 +99,30 @@ export function registerPageActions(rl) {
|
|
|
73
99
|
rl.registerAction("page.publish", {
|
|
74
100
|
access: "write",
|
|
75
101
|
description: "Publish a Shift Labs hosted HTML page.",
|
|
76
|
-
inputSchema: t.Object({ id:
|
|
102
|
+
inputSchema: t.Object({ id: idSchema("Page ID") }, STRICT_OBJECT),
|
|
77
103
|
async execute(input, ctx) {
|
|
78
104
|
const { id } = input;
|
|
79
|
-
const body = await request(ctx, `/v1/pages/${
|
|
105
|
+
const body = await request(ctx, `/v1/pages/${pathSegment(id)}/publish`, { method: "POST" });
|
|
80
106
|
return body.page;
|
|
81
107
|
},
|
|
82
108
|
});
|
|
83
109
|
rl.registerAction("page.archive", {
|
|
84
110
|
access: "write",
|
|
85
111
|
description: "Archive a Shift Labs page.",
|
|
86
|
-
inputSchema: t.Object({ id:
|
|
112
|
+
inputSchema: t.Object({ id: idSchema("Page ID") }, STRICT_OBJECT),
|
|
87
113
|
async execute(input, ctx) {
|
|
88
114
|
const { id } = input;
|
|
89
|
-
const body = await request(ctx, `/v1/pages/${
|
|
115
|
+
const body = await request(ctx, `/v1/pages/${pathSegment(id)}/archive`, { method: "POST" });
|
|
90
116
|
return body.page;
|
|
91
117
|
},
|
|
92
118
|
});
|
|
93
119
|
rl.registerAction("page.shares", {
|
|
94
120
|
access: "read",
|
|
95
121
|
description: "List shares for a Shift Labs page.",
|
|
96
|
-
inputSchema: t.Object({ pageId:
|
|
122
|
+
inputSchema: t.Object({ pageId: idSchema("Page ID") }, STRICT_OBJECT),
|
|
97
123
|
async execute(input, ctx) {
|
|
98
124
|
const { pageId } = input;
|
|
99
|
-
const body = await request(ctx, `/v1/pages/${
|
|
125
|
+
const body = await request(ctx, `/v1/pages/${pathSegment(pageId)}/shares`);
|
|
100
126
|
return body.shares;
|
|
101
127
|
},
|
|
102
128
|
});
|
|
@@ -104,13 +130,17 @@ export function registerPageActions(rl) {
|
|
|
104
130
|
access: "write",
|
|
105
131
|
description: "Create a viewer share for a Shift Labs page.",
|
|
106
132
|
inputSchema: t.Object({
|
|
107
|
-
pageId:
|
|
108
|
-
email: t.String({
|
|
109
|
-
|
|
110
|
-
|
|
133
|
+
pageId: idSchema("Page ID"),
|
|
134
|
+
email: t.String({
|
|
135
|
+
minLength: 1,
|
|
136
|
+
format: "email",
|
|
137
|
+
description: "Viewer email address",
|
|
138
|
+
}),
|
|
139
|
+
expiresAt: t.Optional(timestampSchema("Optional ISO-8601 expiration")),
|
|
140
|
+
}, STRICT_OBJECT),
|
|
111
141
|
async execute(input, ctx) {
|
|
112
142
|
const { pageId, ...fields } = input;
|
|
113
|
-
const body = await request(ctx, `/v1/pages/${
|
|
143
|
+
const body = await request(ctx, `/v1/pages/${pathSegment(String(pageId))}/shares`, {
|
|
114
144
|
method: "POST",
|
|
115
145
|
body: JSON.stringify(fields),
|
|
116
146
|
});
|
|
@@ -120,12 +150,10 @@ export function registerPageActions(rl) {
|
|
|
120
150
|
rl.registerAction("page.revokeShare", {
|
|
121
151
|
access: "write",
|
|
122
152
|
description: "Revoke a Shift Labs page share.",
|
|
123
|
-
inputSchema: t.Object({
|
|
124
|
-
shareId: t.String({ description: "Page share ID" }),
|
|
125
|
-
}),
|
|
153
|
+
inputSchema: t.Object({ shareId: idSchema("Page share ID") }, STRICT_OBJECT),
|
|
126
154
|
async execute(input, ctx) {
|
|
127
155
|
const { shareId } = input;
|
|
128
|
-
await request(ctx, `/v1/pages/shares/${
|
|
156
|
+
await request(ctx, `/v1/pages/shares/${pathSegment(shareId)}`, {
|
|
129
157
|
method: "DELETE",
|
|
130
158
|
});
|
|
131
159
|
return { success: true };
|
|
@@ -134,12 +162,10 @@ export function registerPageActions(rl) {
|
|
|
134
162
|
rl.registerAction("page.renderUrl", {
|
|
135
163
|
access: "read",
|
|
136
164
|
description: "Return the authenticated render URL for a page.",
|
|
137
|
-
inputSchema: t.Object({
|
|
138
|
-
pageId: t.String({ description: "Page ID" }),
|
|
139
|
-
}),
|
|
165
|
+
inputSchema: t.Object({ pageId: idSchema("Page ID") }, STRICT_OBJECT),
|
|
140
166
|
async execute(input, ctx) {
|
|
141
167
|
const { pageId } = input;
|
|
142
|
-
const body = await request(ctx, `/v1/pages/${
|
|
168
|
+
const body = await request(ctx, `/v1/pages/${pathSegment(pageId)}`);
|
|
143
169
|
return {
|
|
144
170
|
url: pageRenderUrl(body.page.organizationId, body.page.slug),
|
|
145
171
|
};
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import * as t from "typebox";
|
|
2
|
-
import { enumSchema, PROJECT_STATUS, pathSegment, request } from "./shared.js";
|
|
2
|
+
import { cursorSchema, enumSchema, idSchema, PROJECT_STATUS, pathSegment, request, STRICT_OBJECT, STRICT_UPDATE_OBJECT, timestampSchema, } from "./shared.js";
|
|
3
3
|
const projectListFields = {
|
|
4
4
|
status: t.Optional(enumSchema("Project status", PROJECT_STATUS)),
|
|
5
|
-
leadUserId: t.Optional(
|
|
5
|
+
leadUserId: t.Optional(idSchema("Lead user ID")),
|
|
6
6
|
includeArchived: t.Optional(t.Boolean()),
|
|
7
|
-
cursor: t.Optional(
|
|
7
|
+
cursor: t.Optional(cursorSchema()),
|
|
8
8
|
limit: t.Optional(t.Integer({ minimum: 1, maximum: 100 })),
|
|
9
9
|
};
|
|
10
10
|
function listParams(input) {
|
|
@@ -19,7 +19,7 @@ export function registerProjectActions(rl) {
|
|
|
19
19
|
rl.registerAction("project.list", {
|
|
20
20
|
access: "read",
|
|
21
21
|
description: "List the first page of concrete Shift Labs Projects.",
|
|
22
|
-
inputSchema: t.Object(projectListFields),
|
|
22
|
+
inputSchema: t.Object(projectListFields, STRICT_OBJECT),
|
|
23
23
|
async execute(input, ctx) {
|
|
24
24
|
const body = await request(ctx, `/v1/projects?${listParams(input)}`);
|
|
25
25
|
return body.projects;
|
|
@@ -28,7 +28,7 @@ export function registerProjectActions(rl) {
|
|
|
28
28
|
rl.registerAction("project.listPage", {
|
|
29
29
|
access: "read",
|
|
30
30
|
description: "List a cursor-paginated page of concrete Projects.",
|
|
31
|
-
inputSchema: t.Object(projectListFields),
|
|
31
|
+
inputSchema: t.Object(projectListFields, STRICT_OBJECT),
|
|
32
32
|
async execute(input, ctx) {
|
|
33
33
|
return request(ctx, `/v1/projects?${listParams(input)}`);
|
|
34
34
|
},
|
|
@@ -36,9 +36,7 @@ export function registerProjectActions(rl) {
|
|
|
36
36
|
rl.registerAction("project.get", {
|
|
37
37
|
access: "read",
|
|
38
38
|
description: "Get a Project and its derived Issue progress.",
|
|
39
|
-
inputSchema: t.Object({
|
|
40
|
-
id: t.String({ minLength: 1, description: "Project ID" }),
|
|
41
|
-
}),
|
|
39
|
+
inputSchema: t.Object({ id: idSchema("Project ID") }, STRICT_OBJECT),
|
|
42
40
|
async execute(input, ctx) {
|
|
43
41
|
const { id } = input;
|
|
44
42
|
const [project, progress] = await Promise.all([
|
|
@@ -55,18 +53,21 @@ export function registerProjectActions(rl) {
|
|
|
55
53
|
name: t.String({
|
|
56
54
|
minLength: 1,
|
|
57
55
|
maxLength: 160,
|
|
56
|
+
pattern: "\\S",
|
|
58
57
|
description: "Project name",
|
|
59
58
|
}),
|
|
60
59
|
description: t.Optional(t.String({ maxLength: 20_000 })),
|
|
61
60
|
status: t.Optional(enumSchema("Project status", PROJECT_STATUS)),
|
|
62
|
-
leadUserId: t.Optional(
|
|
63
|
-
startAt: t.Optional(
|
|
64
|
-
targetAt: t.Optional(
|
|
65
|
-
}),
|
|
61
|
+
leadUserId: t.Optional(idSchema("Lead user ID")),
|
|
62
|
+
startAt: t.Optional(timestampSchema("ISO-8601 start timestamp")),
|
|
63
|
+
targetAt: t.Optional(timestampSchema("ISO-8601 target timestamp")),
|
|
64
|
+
}, STRICT_OBJECT),
|
|
66
65
|
async execute(input, ctx) {
|
|
66
|
+
const fields = input;
|
|
67
|
+
assertDateOrder(fields.startAt, fields.targetAt);
|
|
67
68
|
const body = await request(ctx, "/v1/projects", {
|
|
68
69
|
method: "POST",
|
|
69
|
-
body: JSON.stringify(
|
|
70
|
+
body: JSON.stringify(fields),
|
|
70
71
|
});
|
|
71
72
|
return body.project;
|
|
72
73
|
},
|
|
@@ -75,19 +76,27 @@ export function registerProjectActions(rl) {
|
|
|
75
76
|
access: "write",
|
|
76
77
|
description: "Update Project lifecycle, ownership, dates, or content.",
|
|
77
78
|
inputSchema: t.Object({
|
|
78
|
-
id:
|
|
79
|
-
name: t.Optional(t.String({ minLength: 1, maxLength: 160 })),
|
|
79
|
+
id: idSchema("Project ID"),
|
|
80
|
+
name: t.Optional(t.String({ minLength: 1, maxLength: 160, pattern: "\\S" })),
|
|
80
81
|
description: t.Optional(t.String({ maxLength: 20_000 })),
|
|
81
82
|
status: t.Optional(enumSchema("Project status", PROJECT_STATUS)),
|
|
82
|
-
leadUserId: t.Optional(t.Union([
|
|
83
|
-
startAt: t.Optional(t.Union([
|
|
84
|
-
targetAt: t.Optional(t.Union([
|
|
83
|
+
leadUserId: t.Optional(t.Union([idSchema("Lead user ID"), t.Null()])),
|
|
84
|
+
startAt: t.Optional(t.Union([timestampSchema("ISO-8601 start timestamp"), t.Null()])),
|
|
85
|
+
targetAt: t.Optional(t.Union([timestampSchema("ISO-8601 target timestamp"), t.Null()])),
|
|
85
86
|
archived: t.Optional(t.Boolean()),
|
|
86
|
-
},
|
|
87
|
+
}, STRICT_UPDATE_OBJECT),
|
|
87
88
|
async execute(input, ctx) {
|
|
88
89
|
const { id, ...patch } = input;
|
|
90
|
+
assertDateOrder(patch.startAt, patch.targetAt);
|
|
89
91
|
const body = await request(ctx, `/v1/projects/${pathSegment(id)}`, { method: "PATCH", body: JSON.stringify(patch) });
|
|
90
92
|
return body.project;
|
|
91
93
|
},
|
|
92
94
|
});
|
|
93
95
|
}
|
|
96
|
+
function assertDateOrder(startAt, targetAt) {
|
|
97
|
+
if (typeof startAt === "string" &&
|
|
98
|
+
typeof targetAt === "string" &&
|
|
99
|
+
startAt > targetAt) {
|
|
100
|
+
throw new Error("Project targetAt must not precede startAt");
|
|
101
|
+
}
|
|
102
|
+
}
|
|
@@ -1,32 +1,5 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
export function baseUrl() {
|
|
4
|
-
return `${SHIFT_LABS_API_URL}/`;
|
|
5
|
-
}
|
|
6
|
-
export async function request(ctx, path, init = {}) {
|
|
7
|
-
const headers = new Headers(init.headers);
|
|
8
|
-
const apiKey = ctx.connection.config.apiKey;
|
|
9
|
-
if (typeof apiKey !== "string" || !apiKey) {
|
|
10
|
-
throw new Error("Shift Labs apiKey is required");
|
|
11
|
-
}
|
|
12
|
-
headers.set("authorization", `Bearer ${apiKey}`);
|
|
13
|
-
if (init.body && !headers.has("content-type")) {
|
|
14
|
-
headers.set("content-type", "application/json");
|
|
15
|
-
}
|
|
16
|
-
const response = await fetch(new URL(path, baseUrl()), {
|
|
17
|
-
...init,
|
|
18
|
-
headers,
|
|
19
|
-
});
|
|
20
|
-
if (!response.ok) {
|
|
21
|
-
throw new Error(`Shift Labs API error ${response.status}: ${await response.text()}`);
|
|
22
|
-
}
|
|
23
|
-
if (response.status === 204)
|
|
24
|
-
return undefined;
|
|
25
|
-
return (await response.json());
|
|
26
|
-
}
|
|
27
|
-
export function pathSegment(value) {
|
|
28
|
-
return encodeURIComponent(value);
|
|
29
|
-
}
|
|
1
|
+
import { baseUrl, pathSegment } from "../../_shared/shiftCloud.js";
|
|
2
|
+
export { baseUrl, cursorSchema, enumDescription, enumSchema, idSchema, pathSegment, request, STRICT_OBJECT, STRICT_UPDATE_OBJECT, timestampSchema, } from "../../_shared/shiftCloud.js";
|
|
30
3
|
export function pageRenderUrl(organizationId, slug) {
|
|
31
4
|
return new URL(`/pages/${pathSegment(organizationId)}/${pathSegment(slug)}`, baseUrl()).toString();
|
|
32
5
|
}
|
|
@@ -76,9 +49,3 @@ export const PAGE_STATUS = ["draft", "published", "archived"];
|
|
|
76
49
|
export const PAGE_VISIBILITY = ["org", "invited"];
|
|
77
50
|
export const TRANSCRIPTION_LANGUAGE = ["auto", "en", "he"];
|
|
78
51
|
export const TRANSCRIPT_FORMAT = ["txt", "srt", "vtt", "json"];
|
|
79
|
-
export function enumDescription(name, values) {
|
|
80
|
-
return `${name}: ${values.join(" | ")}`;
|
|
81
|
-
}
|
|
82
|
-
export function enumSchema(name, values) {
|
|
83
|
-
return t.Union(values.map((value) => t.Literal(value)), { description: enumDescription(name, values) });
|
|
84
|
-
}
|