@pstdio/sdk 0.1.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/api/actions.d.ts +12 -0
- package/dist/api/agents.d.ts +1 -0
- package/dist/api/index.d.ts +9 -0
- package/dist/api/index.js +0 -0
- package/dist/api/projects.d.ts +1 -0
- package/dist/api/sessions.d.ts +1 -0
- package/dist/api/statuses.d.ts +1 -0
- package/dist/api/tags.d.ts +1 -0
- package/dist/api/templates.d.ts +1 -0
- package/dist/api/tickets.d.ts +23 -0
- package/dist/api/workspaces.d.ts +1 -0
- package/dist/client/actions.d.ts +8 -0
- package/dist/client/agents.d.ts +13 -0
- package/dist/client/client.d.ts +24 -0
- package/dist/client/index.d.ts +12 -0
- package/dist/client/index.js +215 -0
- package/dist/client/projects.d.ts +13 -0
- package/dist/client/request.d.ts +16 -0
- package/dist/client/sessions.d.ts +15 -0
- package/dist/client/skills.d.ts +8 -0
- package/dist/client/statuses.d.ts +22 -0
- package/dist/client/tags.d.ts +13 -0
- package/dist/client/templates.d.ts +11 -0
- package/dist/client/tickets.d.ts +17 -0
- package/dist/client/workspaces.d.ts +12 -0
- package/dist/hooks/attempt.d.ts +14 -0
- package/dist/hooks/base.d.ts +17 -0
- package/dist/hooks/entities.d.ts +8 -0
- package/dist/hooks/index.d.ts +5 -0
- package/dist/hooks/index.js +0 -0
- package/dist/hooks/session.d.ts +13 -0
- package/dist/hooks/ticket.d.ts +23 -0
- package/dist/hooks/worktree.d.ts +52 -0
- package/dist/plugins/define-plugin.d.ts +2 -0
- package/dist/plugins/helpers/context.d.ts +24 -0
- package/dist/plugins/helpers/create-attempt.d.ts +5 -0
- package/dist/plugins/helpers/create-session.d.ts +20 -0
- package/dist/plugins/helpers/create-workspace.d.ts +5 -0
- package/dist/plugins/helpers/find-ticket-by-ref.d.ts +6 -0
- package/dist/plugins/helpers/find-workspace-by-ref.d.ts +5 -0
- package/dist/plugins/helpers/followup-session.d.ts +22 -0
- package/dist/plugins/helpers/get-attempts-for-ticket.d.ts +17 -0
- package/dist/plugins/helpers/index.d.ts +15 -0
- package/dist/plugins/helpers/remove-all-worktrees-for-ticket.d.ts +2 -0
- package/dist/plugins/helpers/run-command.d.ts +9 -0
- package/dist/plugins/helpers/set-ticket-status.d.ts +7 -0
- package/dist/plugins/helpers/set-workspace-attempt-status.d.ts +7 -0
- package/dist/plugins/helpers/ticket-pull.d.ts +18 -0
- package/dist/plugins/helpers/update-ticket-when-all-attempts-match.d.ts +7 -0
- package/dist/plugins/helpers/workspaces-for-ticket.d.ts +17 -0
- package/dist/plugins/helpers/worktree-bootstrap.d.ts +8 -0
- package/dist/plugins/hooks.d.ts +42 -0
- package/dist/plugins/index.d.ts +5 -0
- package/dist/plugins/index.js +464 -0
- package/dist/plugins/types.d.ts +89 -0
- package/dist/prompts/index.d.ts +1 -0
- package/dist/prompts/index.js +6 -0
- package/dist/prompts/render-prompt.d.ts +1 -0
- package/dist/resources/agent.d.ts +1 -0
- package/dist/resources/file.d.ts +1 -0
- package/dist/resources/index.d.ts +11 -0
- package/dist/resources/index.js +0 -0
- package/dist/resources/project.d.ts +1 -0
- package/dist/resources/session.d.ts +1 -0
- package/dist/resources/skill.d.ts +1 -0
- package/dist/resources/status.d.ts +1 -0
- package/dist/resources/tag.d.ts +1 -0
- package/dist/resources/template.d.ts +1 -0
- package/dist/resources/ticket.d.ts +1 -0
- package/dist/resources/workspace.d.ts +1 -0
- package/package.json +61 -0
|
@@ -0,0 +1,464 @@
|
|
|
1
|
+
// src/prompts/render-prompt.ts
|
|
2
|
+
import Mustache from "mustache";
|
|
3
|
+
var renderPrompt = (template, data) => Mustache.render(template, data);
|
|
4
|
+
// src/plugins/define-plugin.ts
|
|
5
|
+
var assertActionTriggers = (plugin) => {
|
|
6
|
+
for (const action of plugin.actions ?? []) {
|
|
7
|
+
if (typeof action.trigger !== "function") {
|
|
8
|
+
throw new Error(`Action "${action.key}" is missing trigger(ctx)`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
var definePlugin = (plugin) => {
|
|
13
|
+
assertActionTriggers(plugin);
|
|
14
|
+
return plugin;
|
|
15
|
+
};
|
|
16
|
+
// src/plugins/helpers/context.ts
|
|
17
|
+
var firstMatch = (values, ref, readName) => {
|
|
18
|
+
if (ref) {
|
|
19
|
+
const byIdMatch = values.find((value) => value.id === ref);
|
|
20
|
+
if (byIdMatch)
|
|
21
|
+
return byIdMatch;
|
|
22
|
+
return values.find((value) => readName(value) === ref) ?? null;
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
25
|
+
};
|
|
26
|
+
var readTicketLike = (value) => {
|
|
27
|
+
if (!value || typeof value !== "object")
|
|
28
|
+
return null;
|
|
29
|
+
const ticket = value;
|
|
30
|
+
if (typeof ticket.id !== "string" || typeof ticket.shorthand !== "string") {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
return ticket;
|
|
34
|
+
};
|
|
35
|
+
var readWorkspaceLike = (value) => {
|
|
36
|
+
if (!value || typeof value !== "object")
|
|
37
|
+
return null;
|
|
38
|
+
const workspace = value;
|
|
39
|
+
if (typeof workspace.id !== "string" || typeof workspace.workspace_shorthand !== "string") {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
return workspace;
|
|
43
|
+
};
|
|
44
|
+
var matchesRef = (ref, id, name) => !ref || ref === id || ref === name;
|
|
45
|
+
var readTicketFromContext = (ctx, ref) => {
|
|
46
|
+
const actionTarget = "targetType" in ctx && ctx.targetType === "ticket" ? readTicketLike(ctx.target) : null;
|
|
47
|
+
if (actionTarget && matchesRef(ref, actionTarget.id, actionTarget.shorthand)) {
|
|
48
|
+
return actionTarget;
|
|
49
|
+
}
|
|
50
|
+
const hookTicket = readTicketLike(ctx.ticket);
|
|
51
|
+
if (hookTicket && matchesRef(ref, hookTicket.id, hookTicket.shorthand)) {
|
|
52
|
+
return hookTicket;
|
|
53
|
+
}
|
|
54
|
+
return null;
|
|
55
|
+
};
|
|
56
|
+
var readWorkspaceFromContext = (ctx, ref) => {
|
|
57
|
+
const actionTarget = "targetType" in ctx && ctx.targetType === "workspace" ? readWorkspaceLike(ctx.target) : null;
|
|
58
|
+
if (actionTarget && matchesRef(ref, actionTarget.id, actionTarget.workspace_shorthand)) {
|
|
59
|
+
return actionTarget;
|
|
60
|
+
}
|
|
61
|
+
const hookWorkspace = readWorkspaceLike(ctx.workspace);
|
|
62
|
+
if (hookWorkspace && matchesRef(ref, hookWorkspace.id, hookWorkspace.workspace_shorthand)) {
|
|
63
|
+
return hookWorkspace;
|
|
64
|
+
}
|
|
65
|
+
return null;
|
|
66
|
+
};
|
|
67
|
+
var resolveSessionIdFromContext = (ctx) => {
|
|
68
|
+
const actionTarget = "targetType" in ctx && ctx.targetType === "session" ? ctx.target.id : null;
|
|
69
|
+
if (actionTarget)
|
|
70
|
+
return actionTarget;
|
|
71
|
+
const originalSessionId = ctx.originalSessionId;
|
|
72
|
+
if (typeof originalSessionId === "string" && originalSessionId.length > 0) {
|
|
73
|
+
return originalSessionId;
|
|
74
|
+
}
|
|
75
|
+
const sessionId = ctx.sessionId;
|
|
76
|
+
if (typeof sessionId === "string" && sessionId.length > 0) {
|
|
77
|
+
return sessionId;
|
|
78
|
+
}
|
|
79
|
+
return null;
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// src/plugins/helpers/find-ticket-by-ref.ts
|
|
83
|
+
var findTicketByRef = async (ctx, input) => {
|
|
84
|
+
const contextualTicket = readTicketFromContext(ctx, input.ticketId);
|
|
85
|
+
if (contextualTicket)
|
|
86
|
+
return contextualTicket;
|
|
87
|
+
const tickets = await ctx.client.tickets.list(ctx.projectId);
|
|
88
|
+
return firstMatch(tickets, input.ticketId, (ticket) => ticket.shorthand);
|
|
89
|
+
};
|
|
90
|
+
var resolveTicketShorthand = async (ctx, input) => {
|
|
91
|
+
if (!input.ticketId)
|
|
92
|
+
return null;
|
|
93
|
+
const ticket = await findTicketByRef(ctx, input);
|
|
94
|
+
return ticket?.shorthand ?? null;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// src/plugins/helpers/create-attempt.ts
|
|
98
|
+
var createAttempt = async (ctx, input) => {
|
|
99
|
+
const ticketId = (await findTicketByRef(ctx, input))?.id;
|
|
100
|
+
if (!ticketId)
|
|
101
|
+
return null;
|
|
102
|
+
const { ticketId: _ticketId, ...attemptInput } = input;
|
|
103
|
+
return ctx.client.tickets.createAttempt(ticketId, {
|
|
104
|
+
...attemptInput,
|
|
105
|
+
start_session: true
|
|
106
|
+
});
|
|
107
|
+
};
|
|
108
|
+
// src/plugins/helpers/create-session.ts
|
|
109
|
+
var createSession = async (ctx, input) => ctx.client.sessions.create({
|
|
110
|
+
project_id: ctx.projectId,
|
|
111
|
+
...input
|
|
112
|
+
});
|
|
113
|
+
// src/plugins/helpers/create-workspace.ts
|
|
114
|
+
var createWorkspace = async (ctx, input) => {
|
|
115
|
+
const ticketId = (await findTicketByRef(ctx, input))?.id;
|
|
116
|
+
if (!ticketId)
|
|
117
|
+
return null;
|
|
118
|
+
const { ticketId: _ticketId, ...attemptInput } = input;
|
|
119
|
+
return ctx.client.tickets.createAttempt(ticketId, {
|
|
120
|
+
...attemptInput,
|
|
121
|
+
start_session: false
|
|
122
|
+
});
|
|
123
|
+
};
|
|
124
|
+
// src/plugins/helpers/find-workspace-by-ref.ts
|
|
125
|
+
var findWorkspaceByRef = async (ctx, input) => {
|
|
126
|
+
const contextualWorkspace = readWorkspaceFromContext(ctx, input.workspaceId);
|
|
127
|
+
if (contextualWorkspace)
|
|
128
|
+
return contextualWorkspace;
|
|
129
|
+
const workspaces = await ctx.client.workspaces.list(ctx.projectId);
|
|
130
|
+
return firstMatch(workspaces, input.workspaceId, (workspace) => workspace.workspace_shorthand);
|
|
131
|
+
};
|
|
132
|
+
// src/plugins/helpers/followup-session.ts
|
|
133
|
+
var followupSession = async (ctx, input) => {
|
|
134
|
+
const sessionId = input.sessionId ?? resolveSessionIdFromContext(ctx);
|
|
135
|
+
if (!sessionId) {
|
|
136
|
+
throw new Error("followupSession requires sessionId or hook/action session context");
|
|
137
|
+
}
|
|
138
|
+
const { sessionId: _sessionId, ...followupInput } = input;
|
|
139
|
+
return ctx.client.sessions.followUp(sessionId, followupInput);
|
|
140
|
+
};
|
|
141
|
+
// src/plugins/helpers/workspaces-for-ticket.ts
|
|
142
|
+
var workspacesForTicket = async (ctx, input) => {
|
|
143
|
+
const ticketShorthand = await resolveTicketShorthand(ctx, input);
|
|
144
|
+
if (!ticketShorthand)
|
|
145
|
+
return [];
|
|
146
|
+
const workspaces = await ctx.client.workspaces.list(ctx.projectId);
|
|
147
|
+
return workspaces.filter((workspace) => workspace.ticket_shorthand === ticketShorthand);
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
// src/plugins/helpers/get-attempts-for-ticket.ts
|
|
151
|
+
var getAttemptsForTicket = async (ctx, input) => {
|
|
152
|
+
return workspacesForTicket(ctx, input);
|
|
153
|
+
};
|
|
154
|
+
// src/plugins/helpers/remove-all-worktrees-for-ticket.ts
|
|
155
|
+
var removeAllWorktreesForTicket = async (ctx, input) => {
|
|
156
|
+
const ticketShorthand = await resolveTicketShorthand(ctx, input);
|
|
157
|
+
if (!ticketShorthand)
|
|
158
|
+
return 0;
|
|
159
|
+
const workspaces = await ctx.client.workspaces.list(ctx.projectId);
|
|
160
|
+
const ticketWorkspaces = workspaces.filter((workspace) => workspace.ticket_shorthand === ticketShorthand && workspace.worktree_path);
|
|
161
|
+
let removed = 0;
|
|
162
|
+
for (const workspace of ticketWorkspaces) {
|
|
163
|
+
try {
|
|
164
|
+
const result = await ctx.client.workspaces.removeWorktree(workspace.id);
|
|
165
|
+
if (result.removed) {
|
|
166
|
+
removed++;
|
|
167
|
+
}
|
|
168
|
+
} catch {}
|
|
169
|
+
}
|
|
170
|
+
return removed;
|
|
171
|
+
};
|
|
172
|
+
// src/plugins/helpers/run-command.ts
|
|
173
|
+
import { spawn as nodeSpawn } from "node:child_process";
|
|
174
|
+
var runCommand = async (cwd, command, options = {}) => {
|
|
175
|
+
const [cmd, ...args] = command;
|
|
176
|
+
const stdio = options.quiet ? "ignore" : "pipe";
|
|
177
|
+
return new Promise((resolve) => {
|
|
178
|
+
const proc = nodeSpawn(cmd, args, { cwd, stdio: ["ignore", stdio, stdio] });
|
|
179
|
+
const stdout = [];
|
|
180
|
+
const stderr = [];
|
|
181
|
+
proc.stdout?.on("data", (chunk) => stdout.push(chunk.toString()));
|
|
182
|
+
proc.stderr?.on("data", (chunk) => stderr.push(chunk.toString()));
|
|
183
|
+
proc.on("close", (code) => {
|
|
184
|
+
resolve({
|
|
185
|
+
exitCode: code ?? 1,
|
|
186
|
+
stdout: stdout.join("").trim(),
|
|
187
|
+
stderr: stderr.join("").trim()
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
});
|
|
191
|
+
};
|
|
192
|
+
// src/plugins/helpers/set-ticket-status.ts
|
|
193
|
+
var setTicketStatus = async (ctx, input) => {
|
|
194
|
+
const [ticket, statuses] = await Promise.all([
|
|
195
|
+
findTicketByRef(ctx, { ticketId: input.ticket }),
|
|
196
|
+
ctx.client.statuses.list(ctx.projectId)
|
|
197
|
+
]);
|
|
198
|
+
if (!ticket)
|
|
199
|
+
return false;
|
|
200
|
+
const status = statuses.find((candidate) => candidate.name === input.status);
|
|
201
|
+
if (!status)
|
|
202
|
+
return false;
|
|
203
|
+
await ctx.client.tickets.update(ticket.id, { status_id: status.id });
|
|
204
|
+
return true;
|
|
205
|
+
};
|
|
206
|
+
// src/plugins/helpers/set-workspace-attempt-status.ts
|
|
207
|
+
var setWorkspaceAttemptStatus = async (ctx, input) => {
|
|
208
|
+
const workspace = await findWorkspaceByRef(ctx, input);
|
|
209
|
+
const workspaceId = workspace?.id;
|
|
210
|
+
if (!workspaceId)
|
|
211
|
+
return false;
|
|
212
|
+
await ctx.client.workspaces.updateAttemptStatus(workspaceId, {
|
|
213
|
+
status: input.statusName,
|
|
214
|
+
session_id: input.sessionId
|
|
215
|
+
});
|
|
216
|
+
return true;
|
|
217
|
+
};
|
|
218
|
+
// src/plugins/helpers/ticket-pull.ts
|
|
219
|
+
import { existsSync, mkdirSync, statSync, writeFileSync } from "node:fs";
|
|
220
|
+
import { dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
221
|
+
var TICKETS_DIR = join(".pstdio", "tickets");
|
|
222
|
+
var TICKET_FILES_DIR = "files";
|
|
223
|
+
var escapeYamlScalar = (value) => value.replace(/\\/g, "\\\\").replace(/"/g, "\\\"").replace(/\n/g, "\\n");
|
|
224
|
+
var buildTicketFrontmatter = (fields) => {
|
|
225
|
+
const lines = ["---"];
|
|
226
|
+
const q = (value) => `"${escapeYamlScalar(value)}"`;
|
|
227
|
+
lines.push(`ticket_id: ${q(fields.shorthand)}`);
|
|
228
|
+
if (fields.user_prompt)
|
|
229
|
+
lines.push(`user_prompt: ${q(fields.user_prompt)}`);
|
|
230
|
+
lines.push(`created: ${q(fields.created_at)}`);
|
|
231
|
+
if (fields.draft !== null)
|
|
232
|
+
lines.push(`draft: ${fields.draft}`);
|
|
233
|
+
if (fields.status_name)
|
|
234
|
+
lines.push(`status: ${q(fields.status_name)}`);
|
|
235
|
+
if (fields.parent_id)
|
|
236
|
+
lines.push(`parent_id: ${q(fields.parent_id)}`);
|
|
237
|
+
if (fields.depends_on)
|
|
238
|
+
lines.push(`depends_on: ${q(fields.depends_on)}`);
|
|
239
|
+
if (fields.parallelizable)
|
|
240
|
+
lines.push(`parallelizable: ${q(fields.parallelizable)}`);
|
|
241
|
+
if (fields.blocked_reason)
|
|
242
|
+
lines.push(`blocked_reason: ${q(fields.blocked_reason)}`);
|
|
243
|
+
if (fields.tag_names.length > 0)
|
|
244
|
+
lines.push(`tags: [${fields.tag_names.map(q).join(", ")}]`);
|
|
245
|
+
lines.push("---");
|
|
246
|
+
return lines.join(`
|
|
247
|
+
`);
|
|
248
|
+
};
|
|
249
|
+
var stripFrontmatter = (content) => {
|
|
250
|
+
if (!content.startsWith("---"))
|
|
251
|
+
return content;
|
|
252
|
+
const closingIndex = content.indexOf("---", 3);
|
|
253
|
+
if (closingIndex === -1)
|
|
254
|
+
return content;
|
|
255
|
+
return content.slice(closingIndex + 3);
|
|
256
|
+
};
|
|
257
|
+
var applyFrontmatter = (frontmatter, content) => {
|
|
258
|
+
const body = stripFrontmatter(content).replace(/^\n+/, "");
|
|
259
|
+
if (!body)
|
|
260
|
+
return frontmatter;
|
|
261
|
+
return `${frontmatter}
|
|
262
|
+
|
|
263
|
+
${body}`;
|
|
264
|
+
};
|
|
265
|
+
var toRelativeFilePath = (baseDir, absolutePath) => relative(baseDir, absolutePath).split("\\").join("/");
|
|
266
|
+
var resolveTicketDir = (rootPath, shorthand) => {
|
|
267
|
+
const exactDir = join(rootPath, TICKETS_DIR, shorthand);
|
|
268
|
+
if (!existsSync(exactDir))
|
|
269
|
+
return null;
|
|
270
|
+
if (!statSync(exactDir).isDirectory()) {
|
|
271
|
+
throw new Error(`Invalid ticket path for ${shorthand}: .pstdio/tickets/${shorthand} is not a directory.`);
|
|
272
|
+
}
|
|
273
|
+
return exactDir;
|
|
274
|
+
};
|
|
275
|
+
var writeTicketFile = (rootPath, shorthand, content, overwrite = true) => {
|
|
276
|
+
const existingDir = resolveTicketDir(rootPath, shorthand);
|
|
277
|
+
const dir = existingDir ?? join(rootPath, TICKETS_DIR, shorthand);
|
|
278
|
+
const filePath = join(dir, "ticket.md");
|
|
279
|
+
if (!overwrite && existsSync(filePath)) {
|
|
280
|
+
throw new Error(`Local file already exists: ${toRelativeFilePath(rootPath, filePath)}. Use force to overwrite.`);
|
|
281
|
+
}
|
|
282
|
+
mkdirSync(dir, { recursive: true });
|
|
283
|
+
writeFileSync(filePath, content);
|
|
284
|
+
return filePath;
|
|
285
|
+
};
|
|
286
|
+
var resolveTicketAttachmentPath = (rootPath, shorthand, fileName) => {
|
|
287
|
+
const ticketDir = resolveTicketDir(rootPath, shorthand);
|
|
288
|
+
if (!ticketDir)
|
|
289
|
+
throw new Error(`Ticket directory not found for ${shorthand}`);
|
|
290
|
+
const filesDir = join(ticketDir, TICKET_FILES_DIR);
|
|
291
|
+
const targetPath = resolve(filesDir, fileName);
|
|
292
|
+
const rel = relative(filesDir, targetPath);
|
|
293
|
+
if (isAbsolute(rel) || rel.startsWith("..")) {
|
|
294
|
+
throw new Error(`Ticket file path resolves outside ticket files directory: ${fileName}`);
|
|
295
|
+
}
|
|
296
|
+
return targetPath;
|
|
297
|
+
};
|
|
298
|
+
var writeTicketAttachment = (rootPath, shorthand, fileName, content, overwrite = false) => {
|
|
299
|
+
const filePath = resolveTicketAttachmentPath(rootPath, shorthand, fileName);
|
|
300
|
+
if (!overwrite && existsSync(filePath)) {
|
|
301
|
+
throw new Error(`Local file already exists: ${toRelativeFilePath(rootPath, filePath)}. Use force to overwrite.`);
|
|
302
|
+
}
|
|
303
|
+
mkdirSync(dirname(filePath), { recursive: true });
|
|
304
|
+
writeFileSync(filePath, content);
|
|
305
|
+
return filePath;
|
|
306
|
+
};
|
|
307
|
+
var isNotFoundError = (error) => typeof error === "object" && error !== null && ("status" in error) && error.status === 404;
|
|
308
|
+
var isTicketShorthand = (value) => /^[A-Za-z]+-\d+$/.test(value);
|
|
309
|
+
var resolveParentFrontmatterValue = async (ctx, parentId) => {
|
|
310
|
+
if (!parentId)
|
|
311
|
+
return null;
|
|
312
|
+
if (isTicketShorthand(parentId))
|
|
313
|
+
return parentId;
|
|
314
|
+
try {
|
|
315
|
+
const parentTicket = await ctx.client.tickets.get(parentId);
|
|
316
|
+
return parentTicket.shorthand || parentId;
|
|
317
|
+
} catch (error) {
|
|
318
|
+
if (isNotFoundError(error))
|
|
319
|
+
return parentId;
|
|
320
|
+
throw error;
|
|
321
|
+
}
|
|
322
|
+
};
|
|
323
|
+
var resolveTicketByShorthand = async (ctx, ticketShorthand) => {
|
|
324
|
+
const published = await ctx.client.tickets.list(ctx.projectId, { shorthand: ticketShorthand });
|
|
325
|
+
if (published.length > 0)
|
|
326
|
+
return published[0];
|
|
327
|
+
const drafts = await ctx.client.tickets.list(ctx.projectId, { shorthand: ticketShorthand, draft: true });
|
|
328
|
+
return drafts[0];
|
|
329
|
+
};
|
|
330
|
+
var resolveTicketById = async (ctx, ticketId) => {
|
|
331
|
+
try {
|
|
332
|
+
const detail = await ctx.client.tickets.get(ticketId);
|
|
333
|
+
const byShorthand = await resolveTicketByShorthand(ctx, detail.shorthand);
|
|
334
|
+
if (byShorthand)
|
|
335
|
+
return byShorthand;
|
|
336
|
+
return {
|
|
337
|
+
...detail,
|
|
338
|
+
status_name: null,
|
|
339
|
+
tag_ids: [],
|
|
340
|
+
tag_names: []
|
|
341
|
+
};
|
|
342
|
+
} catch (error) {
|
|
343
|
+
if (isNotFoundError(error))
|
|
344
|
+
return null;
|
|
345
|
+
throw error;
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
var resolveTicketByRef = async (ctx, ticketId) => {
|
|
349
|
+
const byId = await resolveTicketById(ctx, ticketId);
|
|
350
|
+
if (byId)
|
|
351
|
+
return byId;
|
|
352
|
+
return resolveTicketByShorthand(ctx, ticketId);
|
|
353
|
+
};
|
|
354
|
+
var pullSingleTicket = async (ctx, rootPath, ticketListItem, force, log) => {
|
|
355
|
+
const ticket = await ctx.client.tickets.get(ticketListItem.id);
|
|
356
|
+
const parentId = await resolveParentFrontmatterValue(ctx, ticket.parent_id);
|
|
357
|
+
const frontmatter = buildTicketFrontmatter({
|
|
358
|
+
shorthand: ticketListItem.shorthand,
|
|
359
|
+
created_at: ticket.created_at,
|
|
360
|
+
draft: ticket.draft,
|
|
361
|
+
status_name: ticketListItem.status_name,
|
|
362
|
+
parent_id: parentId,
|
|
363
|
+
user_prompt: ticket.user_prompt ?? null,
|
|
364
|
+
depends_on: ticket.depends_on,
|
|
365
|
+
parallelizable: ticket.parallelizable,
|
|
366
|
+
blocked_reason: ticket.blocked_reason,
|
|
367
|
+
tag_names: ticketListItem.tag_names ?? []
|
|
368
|
+
});
|
|
369
|
+
const content = applyFrontmatter(frontmatter, ticket.content ?? "");
|
|
370
|
+
const filePath = writeTicketFile(rootPath, ticketListItem.shorthand, content, force);
|
|
371
|
+
const ticketDir = filePath.replace(/\/ticket\.md$/, "").replace(`${rootPath}/`, "");
|
|
372
|
+
const files = await ctx.client.tickets.listFiles(ticket.id);
|
|
373
|
+
const attachments = files.filter((file) => file.id !== ticket.file_id);
|
|
374
|
+
for (const file of attachments) {
|
|
375
|
+
const fileContent = await ctx.client.tickets.getFileContent(ticket.id, file.id);
|
|
376
|
+
writeTicketAttachment(rootPath, ticketListItem.shorthand, file.file_name, fileContent, force);
|
|
377
|
+
}
|
|
378
|
+
log(`Pulled ticket ${ticketListItem.shorthand} to ${ticketDir}`);
|
|
379
|
+
if (attachments.length > 0)
|
|
380
|
+
log(`Downloaded ${attachments.length} ticket files`);
|
|
381
|
+
return { shorthand: ticketListItem.shorthand, downloadedFileCount: attachments.length };
|
|
382
|
+
};
|
|
383
|
+
var pullTickets = async (ctx, input) => {
|
|
384
|
+
const force = input.force ?? false;
|
|
385
|
+
const log = input.log ?? (() => {});
|
|
386
|
+
if (input.ticketId) {
|
|
387
|
+
const ticket = await resolveTicketByRef(ctx, input.ticketId);
|
|
388
|
+
if (!ticket)
|
|
389
|
+
throw new Error(`Ticket not found: ${input.ticketId}`);
|
|
390
|
+
const result = await pullSingleTicket(ctx, input.rootPath, ticket, force, log);
|
|
391
|
+
return { pulledTicketShorthands: [result.shorthand], downloadedFileCount: result.downloadedFileCount };
|
|
392
|
+
}
|
|
393
|
+
const listedTickets = await ctx.client.tickets.list(ctx.projectId, { archived: false });
|
|
394
|
+
const tickets = listedTickets.filter((ticket) => !ticket.archived);
|
|
395
|
+
if (tickets.length === 0) {
|
|
396
|
+
log("No tickets to pull.");
|
|
397
|
+
return { pulledTicketShorthands: [], downloadedFileCount: 0 };
|
|
398
|
+
}
|
|
399
|
+
const pulledTicketShorthands = [];
|
|
400
|
+
let downloadedFileCount = 0;
|
|
401
|
+
for (const ticket of tickets) {
|
|
402
|
+
const result = await pullSingleTicket(ctx, input.rootPath, ticket, force, log);
|
|
403
|
+
pulledTicketShorthands.push(result.shorthand);
|
|
404
|
+
downloadedFileCount += result.downloadedFileCount;
|
|
405
|
+
}
|
|
406
|
+
log(`Pulled ${tickets.length} tickets`);
|
|
407
|
+
return { pulledTicketShorthands, downloadedFileCount };
|
|
408
|
+
};
|
|
409
|
+
// src/plugins/helpers/update-ticket-when-all-attempts-match.ts
|
|
410
|
+
var updateTicketWhenAllAttemptsMatch = async (ctx, input) => {
|
|
411
|
+
const ticket = await findTicketByRef(ctx, input);
|
|
412
|
+
const ticketId = ticket?.id;
|
|
413
|
+
if (!ticketId)
|
|
414
|
+
return false;
|
|
415
|
+
const result = await ctx.client.tickets.updateWhenAttemptStatus(ticketId, {
|
|
416
|
+
all_attempts_status: input.allAttemptsStatus,
|
|
417
|
+
set_status: input.setStatus
|
|
418
|
+
});
|
|
419
|
+
return result.updated;
|
|
420
|
+
};
|
|
421
|
+
// src/plugins/helpers/worktree-bootstrap.ts
|
|
422
|
+
import { cpSync, existsSync as existsSync2, mkdirSync as mkdirSync2 } from "node:fs";
|
|
423
|
+
import { join as join2 } from "node:path";
|
|
424
|
+
var AGENT_DIRS = [".claude", ".opencode", ".agents"];
|
|
425
|
+
var bootstrapWorktree = async (ctx, input) => {
|
|
426
|
+
const { repoPath, worktreePath, ticketId } = input;
|
|
427
|
+
const repoConfig = join2(repoPath, ".pstdio", "config.json");
|
|
428
|
+
const worktreeConfigDir = join2(worktreePath, ".pstdio");
|
|
429
|
+
const worktreeConfig = join2(worktreeConfigDir, "config.json");
|
|
430
|
+
if (existsSync2(repoConfig)) {
|
|
431
|
+
mkdirSync2(worktreeConfigDir, { recursive: true });
|
|
432
|
+
cpSync(repoConfig, worktreeConfig);
|
|
433
|
+
}
|
|
434
|
+
for (const agentDir of AGENT_DIRS) {
|
|
435
|
+
const fromDir = join2(repoPath, agentDir);
|
|
436
|
+
const toDir = join2(worktreePath, agentDir);
|
|
437
|
+
if (!existsSync2(fromDir))
|
|
438
|
+
continue;
|
|
439
|
+
mkdirSync2(toDir, { recursive: true });
|
|
440
|
+
cpSync(fromDir, toDir, { recursive: true });
|
|
441
|
+
}
|
|
442
|
+
if (!ticketId)
|
|
443
|
+
return;
|
|
444
|
+
await pullTickets(ctx, { rootPath: worktreePath, ticketId });
|
|
445
|
+
};
|
|
446
|
+
export {
|
|
447
|
+
workspacesForTicket,
|
|
448
|
+
updateTicketWhenAllAttemptsMatch,
|
|
449
|
+
setWorkspaceAttemptStatus,
|
|
450
|
+
setTicketStatus,
|
|
451
|
+
runCommand,
|
|
452
|
+
renderPrompt,
|
|
453
|
+
removeAllWorktreesForTicket,
|
|
454
|
+
pullTickets,
|
|
455
|
+
getAttemptsForTicket,
|
|
456
|
+
followupSession,
|
|
457
|
+
findWorkspaceByRef,
|
|
458
|
+
findTicketByRef,
|
|
459
|
+
definePlugin,
|
|
460
|
+
createWorkspace,
|
|
461
|
+
createSession,
|
|
462
|
+
createAttempt,
|
|
463
|
+
bootstrapWorktree
|
|
464
|
+
};
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { PstdioClient } from "../client/client";
|
|
2
|
+
import type { Session } from "../resources/session";
|
|
3
|
+
import type { TicketListItem } from "../resources/ticket";
|
|
4
|
+
import type { WorkspaceListItem } from "../resources/workspace";
|
|
5
|
+
import type { PluginHooks } from "./hooks";
|
|
6
|
+
export type TargetType = "ticket" | "workspace" | "session";
|
|
7
|
+
export type ActionPlacement = "primary" | "secondary" | "overflow";
|
|
8
|
+
type ActionParamBase = {
|
|
9
|
+
key: string;
|
|
10
|
+
label: string;
|
|
11
|
+
description?: string;
|
|
12
|
+
required?: boolean;
|
|
13
|
+
defaultValue?: string;
|
|
14
|
+
};
|
|
15
|
+
export type TextActionParam = ActionParamBase & {
|
|
16
|
+
type: "text";
|
|
17
|
+
};
|
|
18
|
+
export type LongTextActionParam = ActionParamBase & {
|
|
19
|
+
type: "longtext";
|
|
20
|
+
};
|
|
21
|
+
export type SelectActionParam = ActionParamBase & {
|
|
22
|
+
type: "select";
|
|
23
|
+
options: {
|
|
24
|
+
value: string;
|
|
25
|
+
label: string;
|
|
26
|
+
}[];
|
|
27
|
+
};
|
|
28
|
+
export type TemplateSelectActionParam = ActionParamBase & {
|
|
29
|
+
type: "template-select";
|
|
30
|
+
templateType: string;
|
|
31
|
+
};
|
|
32
|
+
export type AgentActionParam = ActionParamBase & {
|
|
33
|
+
type: "agent";
|
|
34
|
+
};
|
|
35
|
+
export type RepoActionParam = ActionParamBase & {
|
|
36
|
+
type: "repo";
|
|
37
|
+
};
|
|
38
|
+
export type ActionParamDef = TextActionParam | LongTextActionParam | SelectActionParam | TemplateSelectActionParam | AgentActionParam | RepoActionParam;
|
|
39
|
+
export type AgentParamValue = {
|
|
40
|
+
agent: string;
|
|
41
|
+
model: string;
|
|
42
|
+
};
|
|
43
|
+
export type RepoParamValue = {
|
|
44
|
+
repo: string;
|
|
45
|
+
branch: string;
|
|
46
|
+
};
|
|
47
|
+
export type ActionParamValue = string | AgentParamValue | RepoParamValue;
|
|
48
|
+
export type ActionTargetMap = {
|
|
49
|
+
ticket: TicketListItem;
|
|
50
|
+
workspace: WorkspaceListItem;
|
|
51
|
+
session: Session;
|
|
52
|
+
};
|
|
53
|
+
type ActionTriggerContextBase = {
|
|
54
|
+
client: PstdioClient;
|
|
55
|
+
projectId: string;
|
|
56
|
+
prompts: Record<string, string>;
|
|
57
|
+
params: Record<string, ActionParamValue>;
|
|
58
|
+
};
|
|
59
|
+
export type ActionTriggerContext<TTargetType extends TargetType = TargetType> = ActionTriggerContextBase & (TTargetType extends TargetType ? {
|
|
60
|
+
targetType: TTargetType;
|
|
61
|
+
targetId: string;
|
|
62
|
+
target: ActionTargetMap[TTargetType];
|
|
63
|
+
} : never);
|
|
64
|
+
export type ActionInput = {
|
|
65
|
+
[K in TargetType]: {
|
|
66
|
+
key: string;
|
|
67
|
+
label: string;
|
|
68
|
+
targetType: K;
|
|
69
|
+
placement: ActionPlacement;
|
|
70
|
+
params?: ActionParamDef[];
|
|
71
|
+
trigger: (ctx: ActionTriggerContext<K>) => void | Promise<void>;
|
|
72
|
+
};
|
|
73
|
+
}[TargetType];
|
|
74
|
+
export type ActionDescriptor = {
|
|
75
|
+
key: string;
|
|
76
|
+
label: string;
|
|
77
|
+
targetType: TargetType;
|
|
78
|
+
placement: ActionPlacement;
|
|
79
|
+
params?: ActionParamDef[];
|
|
80
|
+
};
|
|
81
|
+
export type ActionDefinition = ActionDescriptor & {
|
|
82
|
+
trigger: (ctx: ActionTriggerContext) => void | Promise<void>;
|
|
83
|
+
};
|
|
84
|
+
export type PluginDefinition = {
|
|
85
|
+
key?: string;
|
|
86
|
+
actions?: ActionInput[];
|
|
87
|
+
hooks?: PluginHooks;
|
|
88
|
+
};
|
|
89
|
+
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { renderPrompt } from "./render-prompt";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const renderPrompt: (template: string, data: Record<string, unknown>) => string;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { AgentAvailabilityType, AgentConfig, AgentInfo, AgentModel } from "pstdio-api-contracts";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { FileRecord } from "pstdio-api-contracts";
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type { Repo } from "pstdio-api-contracts";
|
|
2
|
+
export type { AgentAvailabilityType, AgentConfig, AgentInfo, AgentModel } from "./agent";
|
|
3
|
+
export type { FileRecord } from "./file";
|
|
4
|
+
export type { Project } from "./project";
|
|
5
|
+
export type { Session, SessionStatus } from "./session";
|
|
6
|
+
export type { Skill, SkillWithContent } from "./skill";
|
|
7
|
+
export type { AttemptStatus, Status } from "./status";
|
|
8
|
+
export type { Tag, TagOption } from "./tag";
|
|
9
|
+
export type { Template, TemplateType, TemplateWithContent } from "./template";
|
|
10
|
+
export type { Ticket, TicketDetail, TicketFile, TicketListItem } from "./ticket";
|
|
11
|
+
export type { Workspace, WorkspaceListItem } from "./workspace";
|
|
File without changes
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { Project } from "pstdio-api-contracts";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { Session, SessionStatus } from "pstdio-api-contracts";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { Skill, SkillWithContent } from "pstdio-api-contracts";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { AttemptStatus, Status } from "pstdio-api-contracts";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { Tag, TagOption } from "pstdio-api-contracts";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { Template, TemplateType, TemplateWithContent } from "pstdio-api-contracts";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { FileRecord as TicketFile, Ticket, TicketDetail, TicketListItem } from "pstdio-api-contracts";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export type { Workspace, WorkspaceListItem } from "pstdio-api-contracts";
|
package/package.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@pstdio/sdk",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/pufflyai/prompt-studio"
|
|
7
|
+
},
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"dist"
|
|
13
|
+
],
|
|
14
|
+
"type": "module",
|
|
15
|
+
"exports": {
|
|
16
|
+
"./resources": {
|
|
17
|
+
"import": "./dist/resources/index.js",
|
|
18
|
+
"types": "./dist/resources/index.d.ts"
|
|
19
|
+
},
|
|
20
|
+
"./api": {
|
|
21
|
+
"import": "./dist/api/index.js",
|
|
22
|
+
"types": "./dist/api/index.d.ts"
|
|
23
|
+
},
|
|
24
|
+
"./client": {
|
|
25
|
+
"import": "./dist/client/index.js",
|
|
26
|
+
"types": "./dist/client/index.d.ts"
|
|
27
|
+
},
|
|
28
|
+
"./plugins": {
|
|
29
|
+
"import": "./dist/plugins/index.js",
|
|
30
|
+
"types": "./dist/plugins/index.d.ts"
|
|
31
|
+
},
|
|
32
|
+
"./prompts": {
|
|
33
|
+
"import": "./dist/prompts/index.js",
|
|
34
|
+
"types": "./dist/prompts/index.d.ts"
|
|
35
|
+
},
|
|
36
|
+
"./hooks": {
|
|
37
|
+
"import": "./dist/hooks/index.js",
|
|
38
|
+
"types": "./dist/hooks/index.d.ts"
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"build": "rm -rf ./dist && bun run build:js && bun run build:types",
|
|
43
|
+
"build:js": "bun build ./src/api/index.ts ./src/client/index.ts ./src/plugins/index.ts ./src/prompts/index.ts ./src/hooks/index.ts ./src/resources/index.ts --outdir ./dist --root ./src --target node --format esm --packages external",
|
|
44
|
+
"build:types": "tsc --project ./tsconfig.build.json",
|
|
45
|
+
"prepack": "bun run build",
|
|
46
|
+
"typecheck": "tsc --noEmit",
|
|
47
|
+
"lint": "bun run typecheck && biome check .",
|
|
48
|
+
"format": "biome check --write .",
|
|
49
|
+
"test": "bun test --silent",
|
|
50
|
+
"test:coverage": "bun test --coverage"
|
|
51
|
+
},
|
|
52
|
+
"dependencies": {
|
|
53
|
+
"mustache": "^4.2.0"
|
|
54
|
+
},
|
|
55
|
+
"devDependencies": {
|
|
56
|
+
"@types/bun": "latest",
|
|
57
|
+
"@types/mustache": "^4.2.6",
|
|
58
|
+
"pstdio-api-contracts": "workspace:*",
|
|
59
|
+
"typescript": "^5.9.3"
|
|
60
|
+
}
|
|
61
|
+
}
|