runline 0.12.0 → 0.14.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.
@@ -0,0 +1,179 @@
1
+ import * as t from "typebox";
2
+ import { enumSchema, ISSUE_PRIORITY, ISSUE_STATUS, ISSUE_VIEW_GROUP, ISSUE_VIEW_LAYOUT, ISSUE_VIEW_SORT, ISSUE_VIEW_VISIBILITY, pathSegment, request, } from "./shared.js";
3
+ const viewFields = {
4
+ name: t.String({ minLength: 1, maxLength: 120, description: "View name" }),
5
+ visibility: t.Optional(t.Literal("organization")),
6
+ layout: t.Optional(enumSchema("View layout", ISSUE_VIEW_LAYOUT)),
7
+ projectId: t.Optional(t.String({ minLength: 1 })),
8
+ rootIssueId: t.Optional(t.String({ minLength: 1 })),
9
+ statuses: t.Optional(t.Array(enumSchema("Issue status", ISSUE_STATUS), { maxItems: 5 })),
10
+ priorities: t.Optional(t.Array(enumSchema("Issue priority", ISSUE_PRIORITY), { maxItems: 5 })),
11
+ assigneeUserIds: t.Optional(t.Array(t.String({ minLength: 1 }), { maxItems: 20 })),
12
+ labels: t.Optional(t.Array(t.String({ minLength: 1, maxLength: 64 }), { maxItems: 20 })),
13
+ startAfter: t.Optional(t.String()),
14
+ dueBefore: t.Optional(t.String()),
15
+ blocked: t.Optional(t.Boolean()),
16
+ groupBy: t.Optional(enumSchema("View grouping", ISSUE_VIEW_GROUP)),
17
+ sortBy: t.Optional(t.Array(enumSchema("View sort", ISSUE_VIEW_SORT), {
18
+ minItems: 1,
19
+ maxItems: 3,
20
+ })),
21
+ };
22
+ export function registerIssueViewActions(rl) {
23
+ const listFields = {
24
+ visibility: t.Optional(enumSchema("View visibility", ISSUE_VIEW_VISIBILITY)),
25
+ cursor: t.Optional(t.String({ minLength: 1, description: "Next-page cursor" })),
26
+ limit: t.Optional(t.Integer({ minimum: 1, maximum: 100 })),
27
+ };
28
+ rl.registerAction("issueView.list", {
29
+ access: "read",
30
+ description: "List the first page of personal and organization Issue Views.",
31
+ inputSchema: t.Object(listFields),
32
+ async execute(input, ctx) {
33
+ const body = await request(ctx, `/v1/issue-views?${listParams(input)}`);
34
+ return body.views;
35
+ },
36
+ });
37
+ rl.registerAction("issueView.listPage", {
38
+ access: "read",
39
+ description: "List a cursor-paginated page of Issue Views.",
40
+ inputSchema: t.Object(listFields),
41
+ async execute(input, ctx) {
42
+ return request(ctx, `/v1/issue-views?${listParams(input)}`);
43
+ },
44
+ });
45
+ rl.registerAction("issueView.get", {
46
+ access: "read",
47
+ description: "Get an Issue View by ID.",
48
+ inputSchema: t.Object({ id: t.String() }),
49
+ async execute(input, ctx) {
50
+ const { id } = input;
51
+ const body = await request(ctx, `/v1/issue-views/${pathSegment(id)}`);
52
+ return body.view;
53
+ },
54
+ });
55
+ rl.registerAction("issueView.issues", {
56
+ access: "read",
57
+ description: "Execute the first page of a saved Issue View.",
58
+ inputSchema: t.Object({
59
+ id: t.String({ minLength: 1 }),
60
+ limit: t.Optional(t.Integer({ minimum: 1, maximum: 100 })),
61
+ }),
62
+ async execute(input, ctx) {
63
+ const { id, ...pagination } = input;
64
+ const body = await request(ctx, withQuery(`/v1/issue-views/${pathSegment(id)}/issues`, listParams(pagination)));
65
+ return body.issues;
66
+ },
67
+ });
68
+ rl.registerAction("issueView.issuesPage", {
69
+ access: "read",
70
+ description: "Execute a cursor-paginated page of a saved Issue View.",
71
+ inputSchema: t.Object({
72
+ id: t.String({ minLength: 1 }),
73
+ cursor: t.Optional(t.String({ minLength: 1 })),
74
+ limit: t.Optional(t.Integer({ minimum: 1, maximum: 100 })),
75
+ }),
76
+ async execute(input, ctx) {
77
+ const { id, ...pagination } = input;
78
+ return request(ctx, withQuery(`/v1/issue-views/${pathSegment(id)}/issues`, listParams(pagination)));
79
+ },
80
+ });
81
+ rl.registerAction("issueView.create", {
82
+ access: "write",
83
+ description: "Create a bounded saved Issue query. Views never own Issue membership.",
84
+ inputSchema: t.Object(viewFields),
85
+ async execute(input, ctx) {
86
+ const body = await request(ctx, "/v1/issue-views", {
87
+ method: "POST",
88
+ body: JSON.stringify(toViewBody(input)),
89
+ });
90
+ return body.view;
91
+ },
92
+ });
93
+ rl.registerAction("issueView.update", {
94
+ access: "write",
95
+ description: "Patch a saved Issue View. Null filter values remove that filter without replacing unrelated filters.",
96
+ inputSchema: t.Object({
97
+ id: t.String({ minLength: 1 }),
98
+ name: t.Optional(t.String({ minLength: 1, maxLength: 120 })),
99
+ visibility: t.Optional(t.Literal("organization")),
100
+ layout: t.Optional(enumSchema("View layout", ISSUE_VIEW_LAYOUT)),
101
+ projectId: optionalNullableString(),
102
+ rootIssueId: optionalNullableString(),
103
+ statuses: optionalNullableArray(enumSchema("Issue status", ISSUE_STATUS), 5),
104
+ priorities: optionalNullableArray(enumSchema("Issue priority", ISSUE_PRIORITY), 5),
105
+ assigneeUserIds: optionalNullableArray(t.String({ minLength: 1 }), 20),
106
+ labels: optionalNullableArray(t.String({ minLength: 1, maxLength: 64 }), 20),
107
+ startAfter: optionalNullableString(),
108
+ dueBefore: optionalNullableString(),
109
+ blocked: t.Optional(t.Union([t.Boolean(), t.Null()])),
110
+ groupBy: t.Optional(t.Union([enumSchema("View grouping", ISSUE_VIEW_GROUP), t.Null()])),
111
+ sortBy: t.Optional(t.Array(enumSchema("View sort", ISSUE_VIEW_SORT), {
112
+ minItems: 1,
113
+ maxItems: 3,
114
+ })),
115
+ }, { minProperties: 2 }),
116
+ async execute(input, ctx) {
117
+ const { id, ...fields } = input;
118
+ const body = await request(ctx, `/v1/issue-views/${pathSegment(id)}`, { method: "PATCH", body: JSON.stringify(toViewBody(fields, false)) });
119
+ return body.view;
120
+ },
121
+ });
122
+ rl.registerAction("issueView.delete", {
123
+ access: "write",
124
+ description: "Delete a saved Issue View without changing any Issues.",
125
+ inputSchema: t.Object({ id: t.String() }),
126
+ async execute(input, ctx) {
127
+ const { id } = input;
128
+ await request(ctx, `/v1/issue-views/${pathSegment(id)}`, {
129
+ method: "DELETE",
130
+ });
131
+ return { deleted: true };
132
+ },
133
+ });
134
+ }
135
+ function listParams(input) {
136
+ const params = new URLSearchParams();
137
+ for (const [key, value] of Object.entries((input ?? {}))) {
138
+ if (value !== undefined)
139
+ params.set(key, String(value));
140
+ }
141
+ return params;
142
+ }
143
+ function withQuery(path, params) {
144
+ const query = params.toString();
145
+ return query ? `${path}?${query}` : path;
146
+ }
147
+ function optionalNullableString() {
148
+ return t.Optional(t.Union([t.String({ minLength: 1 }), t.Null()]));
149
+ }
150
+ function optionalNullableArray(item, maxItems) {
151
+ return t.Optional(t.Union([t.Array(item, { maxItems }), t.Null()]));
152
+ }
153
+ function toViewBody(input, applyDefaults = true) {
154
+ const { projectId, rootIssueId, statuses, priorities, assigneeUserIds, labels, startAfter, dueBefore, blocked, ...view } = input;
155
+ const filters = Object.fromEntries(Object.entries({
156
+ projectId,
157
+ rootIssueId,
158
+ statuses,
159
+ priorities,
160
+ assigneeUserIds,
161
+ labels,
162
+ startAfter,
163
+ dueBefore,
164
+ blocked,
165
+ }).filter(([, value]) => value !== undefined));
166
+ return {
167
+ ...view,
168
+ ...(applyDefaults
169
+ ? {
170
+ visibility: view.visibility ?? "organization",
171
+ layout: view.layout ?? "list",
172
+ }
173
+ : {}),
174
+ ...(Object.keys(filters).length > 0 ? { filters } : {}),
175
+ ...(applyDefaults && view.sortBy === undefined
176
+ ? { sortBy: ["updatedAt"] }
177
+ : {}),
178
+ };
179
+ }
@@ -1,54 +1,86 @@
1
1
  import * as t from "typebox";
2
- import { enumSchema, ISSUE_KIND, ISSUE_PRIORITY, ISSUE_SEVERITY, ISSUE_SOURCE, ISSUE_STATUS, request, } from "./shared.js";
2
+ import { enumSchema, ISSUE_PRIORITY, ISSUE_SOURCE, ISSUE_STATUS, pathSegment, request, } from "./shared.js";
3
+ const issueListFields = {
4
+ status: t.Optional(enumSchema("Issue status", ISSUE_STATUS)),
5
+ projectId: t.Optional(t.String({ minLength: 1 })),
6
+ parentIssueId: t.Optional(t.String({ minLength: 1 })),
7
+ assigneeUserId: t.Optional(t.String({ minLength: 1 })),
8
+ source: t.Optional(enumSchema("Issue source", ISSUE_SOURCE)),
9
+ cursor: t.Optional(t.String({ minLength: 1, description: "Next-page cursor" })),
10
+ limit: t.Optional(t.Integer({
11
+ minimum: 1,
12
+ maximum: 100,
13
+ description: "Max results, default 50",
14
+ })),
15
+ };
16
+ function issueListParams(input) {
17
+ const params = new URLSearchParams();
18
+ for (const [key, value] of Object.entries((input ?? {}))) {
19
+ if (value !== undefined)
20
+ params.set(key, String(value));
21
+ }
22
+ return params;
23
+ }
24
+ function withQuery(path, params) {
25
+ const query = params.toString();
26
+ return query ? `${path}?${query}` : path;
27
+ }
3
28
  export function registerIssueActions(rl) {
4
29
  rl.registerAction("issue.list", {
5
30
  access: "read",
6
- description: "List Shift Labs issues for the API key's organization.",
7
- inputSchema: t.Object({
8
- status: t.Optional(enumSchema("Issue status", ISSUE_STATUS)),
9
- assigneeUserId: t.Optional(t.String()),
10
- source: t.Optional(enumSchema("Issue source", ISSUE_SOURCE)),
11
- limit: t.Optional(t.Number({ description: "Max results, default 50" })),
12
- }),
31
+ description: "List the first page of Shift Labs Issues for the API key's organization.",
32
+ inputSchema: t.Object(issueListFields),
13
33
  async execute(input, ctx) {
14
- const fields = (input ?? {});
15
- const params = new URLSearchParams();
16
- for (const key of ["status", "assigneeUserId", "source", "limit"]) {
17
- const value = fields[key];
18
- if (value !== undefined)
19
- params.set(key, String(value));
20
- }
21
- const body = await request(ctx, `/v1/issues?${params}`);
34
+ const body = await request(ctx, `/v1/issues?${issueListParams(input)}`);
22
35
  return body.issues;
23
36
  },
24
37
  });
38
+ rl.registerAction("issue.listPage", {
39
+ access: "read",
40
+ description: "List a cursor-paginated page of Shift Labs Issues.",
41
+ inputSchema: t.Object(issueListFields),
42
+ async execute(input, ctx) {
43
+ return request(ctx, `/v1/issues?${issueListParams(input)}`);
44
+ },
45
+ });
25
46
  rl.registerAction("issue.get", {
26
47
  access: "read",
27
- description: "Get a Shift Labs issue by ID.",
28
- inputSchema: t.Object({ id: t.String({ description: "Issue ID" }) }),
48
+ description: "Get a Shift Labs Issue by ID.",
49
+ inputSchema: t.Object({
50
+ id: t.String({ minLength: 1, description: "Issue ID" }),
51
+ }),
29
52
  async execute(input, ctx) {
30
53
  const { id } = input;
31
- const body = await request(ctx, `/v1/issues/${encodeURIComponent(id)}`);
54
+ const body = await request(ctx, `/v1/issues/${pathSegment(id)}`);
32
55
  return body.issue;
33
56
  },
34
57
  });
35
58
  rl.registerAction("issue.create", {
36
59
  access: "write",
37
- description: "Create a Shift Labs issue. The issue starts in triage.",
60
+ description: "Create an Issue. Leave projectId empty for inbox work; parentIssueId requires a Project.",
38
61
  inputSchema: t.Object({
39
- title: t.String({ description: "Issue title" }),
40
- description: t.Optional(t.String({ description: "Issue description" })),
41
- kind: t.Optional(enumSchema("Issue kind", ISSUE_KIND)),
62
+ title: t.String({
63
+ minLength: 1,
64
+ maxLength: 160,
65
+ description: "Issue title",
66
+ }),
67
+ description: t.Optional(t.String({ maxLength: 20_000, description: "Issue description" })),
68
+ projectId: t.Optional(t.String({ minLength: 1 })),
69
+ parentIssueId: t.Optional(t.String({ minLength: 1 })),
70
+ sortOrder: t.Optional(t.Integer({ minimum: 0 })),
71
+ status: t.Optional(enumSchema("Issue status", ISSUE_STATUS)),
42
72
  priority: t.Optional(enumSchema("Issue priority", ISSUE_PRIORITY)),
43
- severity: t.Optional(enumSchema("Issue severity", ISSUE_SEVERITY)),
44
73
  source: t.Optional(enumSchema("Issue source", ISSUE_SOURCE)),
45
- deploymentId: t.Optional(t.String()),
46
- workspaceId: t.Optional(t.String()),
47
- sessionId: t.Optional(t.String()),
48
- traceId: t.Optional(t.String()),
49
- fingerprint: t.Optional(t.String()),
50
- labels: t.Optional(t.Array(t.String())),
51
- metadata: t.Optional(t.Object({}, { description: "Issue metadata" })),
74
+ assigneeUserId: t.Optional(t.String({ minLength: 1 })),
75
+ startAt: t.Optional(t.String({ description: "ISO-8601 start timestamp" })),
76
+ dueAt: t.Optional(t.String({ description: "ISO-8601 due timestamp" })),
77
+ deploymentId: t.Optional(t.String({ minLength: 1 })),
78
+ workspaceId: t.Optional(t.String({ minLength: 1 })),
79
+ sessionId: t.Optional(t.String({ minLength: 1 })),
80
+ traceId: t.Optional(t.String({ minLength: 1 })),
81
+ fingerprint: t.Optional(t.String({ minLength: 1, maxLength: 512 })),
82
+ labels: t.Optional(t.Array(t.String({ minLength: 1, maxLength: 64 }))),
83
+ metadata: t.Optional(t.Record(t.String(), t.Unknown(), { description: "Issue metadata" })),
52
84
  }),
53
85
  async execute(input, ctx) {
54
86
  const body = await request(ctx, "/v1/issues", {
@@ -58,17 +90,103 @@ export function registerIssueActions(rl) {
58
90
  return body.issue;
59
91
  },
60
92
  });
93
+ rl.registerAction("issue.update", {
94
+ access: "write",
95
+ description: "Update an Issue, including Project assignment, one-level parentage, ordering, status, dates, or assignee.",
96
+ inputSchema: t.Object({
97
+ id: t.String({ minLength: 1, description: "Issue ID" }),
98
+ title: t.Optional(t.String({ minLength: 1, maxLength: 160 })),
99
+ description: t.Optional(t.String({ maxLength: 20_000 })),
100
+ projectId: t.Optional(t.Union([t.String({ minLength: 1 }), t.Null()])),
101
+ parentIssueId: t.Optional(t.Union([t.String({ minLength: 1 }), t.Null()])),
102
+ sortOrder: t.Optional(t.Integer({ minimum: 0 })),
103
+ status: t.Optional(enumSchema("Issue status", ISSUE_STATUS)),
104
+ priority: t.Optional(enumSchema("Issue priority", ISSUE_PRIORITY)),
105
+ assigneeUserId: t.Optional(t.Union([t.String({ minLength: 1 }), t.Null()])),
106
+ startAt: t.Optional(t.Union([t.String(), t.Null()])),
107
+ dueAt: t.Optional(t.Union([t.String(), t.Null()])),
108
+ labels: t.Optional(t.Array(t.String({ minLength: 1, maxLength: 64 }))),
109
+ archived: t.Optional(t.Boolean()),
110
+ }, { minProperties: 2 }),
111
+ async execute(input, ctx) {
112
+ const { id, ...patch } = input;
113
+ const body = await request(ctx, `/v1/issues/${pathSegment(id)}`, { method: "PATCH", body: JSON.stringify(patch) });
114
+ return body.issue;
115
+ },
116
+ });
61
117
  rl.registerAction("issue.comment", {
62
118
  access: "write",
63
- description: "Add a comment to a Shift Labs issue.",
119
+ description: "Add a comment to a Shift Labs Issue.",
64
120
  inputSchema: t.Object({
65
- id: t.String({ description: "Issue ID" }),
66
- body: t.String({ description: "Comment body" }),
121
+ id: t.String({ minLength: 1, description: "Issue ID" }),
122
+ body: t.String({
123
+ minLength: 1,
124
+ maxLength: 20_000,
125
+ description: "Comment body",
126
+ }),
67
127
  }),
68
128
  async execute(input, ctx) {
69
129
  const { id, body } = input;
70
- const response = await request(ctx, `/v1/issues/${encodeURIComponent(id)}/comments`, { method: "POST", body: JSON.stringify({ body }) });
130
+ const response = await request(ctx, `/v1/issues/${pathSegment(id)}/comments`, { method: "POST", body: JSON.stringify({ body }) });
71
131
  return response.event;
72
132
  },
73
133
  });
134
+ rl.registerAction("issue.dependency.list", {
135
+ access: "read",
136
+ description: "List the first page of blocking and blocked-by links.",
137
+ inputSchema: t.Object({
138
+ id: t.String({ minLength: 1, description: "Issue ID" }),
139
+ limit: t.Optional(t.Integer({ minimum: 1, maximum: 100 })),
140
+ }),
141
+ async execute(input, ctx) {
142
+ const { id, ...pagination } = input;
143
+ const body = await request(ctx, withQuery(`/v1/issues/${pathSegment(id)}/dependencies`, issueListParams(pagination)));
144
+ return body.dependencies;
145
+ },
146
+ });
147
+ rl.registerAction("issue.dependency.listPage", {
148
+ access: "read",
149
+ description: "List a cursor-paginated page of Issue dependencies.",
150
+ inputSchema: t.Object({
151
+ id: t.String({ minLength: 1, description: "Issue ID" }),
152
+ cursor: t.Optional(t.String({ minLength: 1 })),
153
+ limit: t.Optional(t.Integer({ minimum: 1, maximum: 100 })),
154
+ }),
155
+ async execute(input, ctx) {
156
+ const { id, ...pagination } = input;
157
+ return request(ctx, withQuery(`/v1/issues/${pathSegment(id)}/dependencies`, issueListParams(pagination)));
158
+ },
159
+ });
160
+ rl.registerAction("issue.dependency.add", {
161
+ access: "write",
162
+ description: "Declare that one Issue blocks another Issue.",
163
+ inputSchema: t.Object({
164
+ blockedIssueId: t.String({
165
+ minLength: 1,
166
+ description: "Issue that is blocked",
167
+ }),
168
+ blockingIssueId: t.String({
169
+ minLength: 1,
170
+ description: "Issue that blocks it",
171
+ }),
172
+ }),
173
+ async execute(input, ctx) {
174
+ const { blockedIssueId, blockingIssueId } = input;
175
+ const body = await request(ctx, `/v1/issues/${pathSegment(blockedIssueId)}/dependencies`, { method: "POST", body: JSON.stringify({ blockingIssueId }) });
176
+ return body.dependency;
177
+ },
178
+ });
179
+ rl.registerAction("issue.dependency.remove", {
180
+ access: "write",
181
+ description: "Remove an Issue dependency link.",
182
+ inputSchema: t.Object({
183
+ issueId: t.String({ minLength: 1 }),
184
+ dependencyId: t.String({ minLength: 1 }),
185
+ }),
186
+ async execute(input, ctx) {
187
+ const { issueId, dependencyId } = input;
188
+ await request(ctx, `/v1/issues/${pathSegment(issueId)}/dependencies/${pathSegment(dependencyId)}`, { method: "DELETE" });
189
+ return { removed: true };
190
+ },
191
+ });
74
192
  }
@@ -0,0 +1,93 @@
1
+ import * as t from "typebox";
2
+ import { enumSchema, PROJECT_STATUS, pathSegment, request } from "./shared.js";
3
+ const projectListFields = {
4
+ status: t.Optional(enumSchema("Project status", PROJECT_STATUS)),
5
+ leadUserId: t.Optional(t.String({ minLength: 1 })),
6
+ includeArchived: t.Optional(t.Boolean()),
7
+ cursor: t.Optional(t.String({ minLength: 1, description: "Next-page cursor" })),
8
+ limit: t.Optional(t.Integer({ minimum: 1, maximum: 100 })),
9
+ };
10
+ function listParams(input) {
11
+ const params = new URLSearchParams();
12
+ for (const [key, value] of Object.entries((input ?? {}))) {
13
+ if (value !== undefined)
14
+ params.set(key, String(value));
15
+ }
16
+ return params;
17
+ }
18
+ export function registerProjectActions(rl) {
19
+ rl.registerAction("project.list", {
20
+ access: "read",
21
+ description: "List the first page of concrete Shift Labs Projects.",
22
+ inputSchema: t.Object(projectListFields),
23
+ async execute(input, ctx) {
24
+ const body = await request(ctx, `/v1/projects?${listParams(input)}`);
25
+ return body.projects;
26
+ },
27
+ });
28
+ rl.registerAction("project.listPage", {
29
+ access: "read",
30
+ description: "List a cursor-paginated page of concrete Projects.",
31
+ inputSchema: t.Object(projectListFields),
32
+ async execute(input, ctx) {
33
+ return request(ctx, `/v1/projects?${listParams(input)}`);
34
+ },
35
+ });
36
+ rl.registerAction("project.get", {
37
+ access: "read",
38
+ description: "Get a Project and its derived Issue progress.",
39
+ inputSchema: t.Object({
40
+ id: t.String({ minLength: 1, description: "Project ID" }),
41
+ }),
42
+ async execute(input, ctx) {
43
+ const { id } = input;
44
+ const [project, progress] = await Promise.all([
45
+ request(ctx, `/v1/projects/${pathSegment(id)}`),
46
+ request(ctx, `/v1/projects/${pathSegment(id)}/progress`),
47
+ ]);
48
+ return { project: project.project, progress: progress.progress };
49
+ },
50
+ });
51
+ rl.registerAction("project.create", {
52
+ access: "write",
53
+ description: "Create a concrete Project container.",
54
+ inputSchema: t.Object({
55
+ name: t.String({
56
+ minLength: 1,
57
+ maxLength: 160,
58
+ description: "Project name",
59
+ }),
60
+ description: t.Optional(t.String({ maxLength: 20_000 })),
61
+ status: t.Optional(enumSchema("Project status", PROJECT_STATUS)),
62
+ leadUserId: t.Optional(t.String({ minLength: 1 })),
63
+ startAt: t.Optional(t.String({ description: "ISO-8601 start timestamp" })),
64
+ targetAt: t.Optional(t.String({ description: "ISO-8601 target timestamp" })),
65
+ }),
66
+ async execute(input, ctx) {
67
+ const body = await request(ctx, "/v1/projects", {
68
+ method: "POST",
69
+ body: JSON.stringify(input),
70
+ });
71
+ return body.project;
72
+ },
73
+ });
74
+ rl.registerAction("project.update", {
75
+ access: "write",
76
+ description: "Update Project lifecycle, ownership, dates, or content.",
77
+ inputSchema: t.Object({
78
+ id: t.String({ minLength: 1, description: "Project ID" }),
79
+ name: t.Optional(t.String({ minLength: 1, maxLength: 160 })),
80
+ description: t.Optional(t.String({ maxLength: 20_000 })),
81
+ status: t.Optional(enumSchema("Project status", PROJECT_STATUS)),
82
+ leadUserId: t.Optional(t.Union([t.String({ minLength: 1 }), t.Null()])),
83
+ startAt: t.Optional(t.Union([t.String(), t.Null()])),
84
+ targetAt: t.Optional(t.Union([t.String(), t.Null()])),
85
+ archived: t.Optional(t.Boolean()),
86
+ }, { minProperties: 2 }),
87
+ async execute(input, ctx) {
88
+ const { id, ...patch } = input;
89
+ const body = await request(ctx, `/v1/projects/${pathSegment(id)}`, { method: "PATCH", body: JSON.stringify(patch) });
90
+ return body.project;
91
+ },
92
+ });
93
+ }
@@ -31,18 +31,17 @@ export function pageRenderUrl(organizationId, slug) {
31
31
  return new URL(`/pages/${pathSegment(organizationId)}/${pathSegment(slug)}`, baseUrl()).toString();
32
32
  }
33
33
  export const ISSUE_STATUS = [
34
- "triage",
35
- "open",
34
+ "backlog",
35
+ "todo",
36
36
  "in_progress",
37
- "resolved",
38
- "closed",
37
+ "done",
38
+ "canceled",
39
39
  ];
40
- export const ISSUE_KIND = [
41
- "bug",
42
- "support",
43
- "feature_request",
44
- "incident",
45
- "task",
40
+ export const PROJECT_STATUS = [
41
+ "planned",
42
+ "active",
43
+ "completed",
44
+ "canceled",
46
45
  ];
47
46
  export const ISSUE_PRIORITY = [
48
47
  "none",
@@ -51,7 +50,6 @@ export const ISSUE_PRIORITY = [
51
50
  "high",
52
51
  "urgent",
53
52
  ];
54
- export const ISSUE_SEVERITY = ["info", "warning", "error", "critical"];
55
53
  export const ISSUE_SOURCE = [
56
54
  "user",
57
55
  "agent",
@@ -59,8 +57,25 @@ export const ISSUE_SOURCE = [
59
57
  "api",
60
58
  "integration",
61
59
  ];
60
+ export const ISSUE_VIEW_VISIBILITY = ["personal", "organization"];
61
+ export const ISSUE_VIEW_LAYOUT = ["list", "board", "timeline"];
62
+ export const ISSUE_VIEW_GROUP = [
63
+ "status",
64
+ "assignee",
65
+ "priority",
66
+ "project",
67
+ ];
68
+ export const ISSUE_VIEW_SORT = [
69
+ "manual",
70
+ "priority",
71
+ "startAt",
72
+ "dueAt",
73
+ "updatedAt",
74
+ ];
62
75
  export const PAGE_STATUS = ["draft", "published", "archived"];
63
76
  export const PAGE_VISIBILITY = ["org", "invited"];
77
+ export const TRANSCRIPTION_LANGUAGE = ["auto", "en", "he"];
78
+ export const TRANSCRIPT_FORMAT = ["txt", "srt", "vtt", "json"];
64
79
  export function enumDescription(name, values) {
65
80
  return `${name}: ${values.join(" | ")}`;
66
81
  }