@studiometa/productive-mcp 0.6.4 → 0.8.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/errors.d.ts +41 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/handlers/bookings.d.ts +1 -1
- package/dist/handlers/bookings.d.ts.map +1 -1
- package/dist/handlers/comments.d.ts +1 -1
- package/dist/handlers/comments.d.ts.map +1 -1
- package/dist/handlers/companies.d.ts +1 -1
- package/dist/handlers/companies.d.ts.map +1 -1
- package/dist/handlers/deals.d.ts +1 -1
- package/dist/handlers/deals.d.ts.map +1 -1
- package/dist/handlers/help.d.ts +13 -0
- package/dist/handlers/help.d.ts.map +1 -0
- package/dist/handlers/index.d.ts.map +1 -1
- package/dist/handlers/people.d.ts +1 -1
- package/dist/handlers/people.d.ts.map +1 -1
- package/dist/handlers/projects.d.ts +1 -1
- package/dist/handlers/projects.d.ts.map +1 -1
- package/dist/handlers/reports.d.ts +21 -0
- package/dist/handlers/reports.d.ts.map +1 -0
- package/dist/handlers/services.d.ts +1 -1
- package/dist/handlers/services.d.ts.map +1 -1
- package/dist/handlers/tasks.d.ts.map +1 -1
- package/dist/handlers/time.d.ts +1 -1
- package/dist/handlers/time.d.ts.map +1 -1
- package/dist/handlers/timers.d.ts.map +1 -1
- package/dist/handlers/types.d.ts +1 -0
- package/dist/handlers/types.d.ts.map +1 -1
- package/dist/handlers/utils.d.ts +12 -1
- package/dist/handlers/utils.d.ts.map +1 -1
- package/dist/handlers.js +1 -1
- package/dist/http.d.ts +1 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +4 -3
- package/dist/http.js.map +1 -1
- package/dist/index-D743zTS1.js +1177 -0
- package/dist/index-D743zTS1.js.map +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/instructions.d.ts +11 -0
- package/dist/instructions.d.ts.map +1 -0
- package/dist/schema.d.ts +218 -0
- package/dist/schema.d.ts.map +1 -0
- package/dist/server.js +1 -1
- package/dist/stdio.js +1 -1
- package/dist/tools.d.ts +6 -0
- package/dist/tools.d.ts.map +1 -1
- package/dist/tools.js +61 -5
- package/dist/tools.js.map +1 -1
- package/dist/version-i2-GF6d1.js +21 -0
- package/dist/version-i2-GF6d1.js.map +1 -0
- package/package.json +13 -3
- package/skills/SKILL.md +330 -0
- package/dist/index-CmTDkz-y.js +0 -480
- package/dist/index-CmTDkz-y.js.map +0 -1
- package/dist/version-BRw90xAB.js +0 -5
- package/dist/version-BRw90xAB.js.map +0 -1
|
@@ -0,0 +1,1177 @@
|
|
|
1
|
+
import { formatBooking as formatBooking$1, formatListResponse as formatListResponse$1, formatDeal as formatDeal$1, formatTimer as formatTimer$1, formatComment as formatComment$1, formatCompany as formatCompany$1, formatPerson as formatPerson$1, formatService as formatService$1, formatTask as formatTask$1, formatTimeEntry as formatTimeEntry$1, formatProject as formatProject$1, ProductiveApi } from "@studiometa/productive-cli";
|
|
2
|
+
class UserInputError extends Error {
|
|
3
|
+
hints;
|
|
4
|
+
constructor(message, hints) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "UserInputError";
|
|
7
|
+
this.hints = hints;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Format error message with hints for LLM consumption
|
|
11
|
+
*/
|
|
12
|
+
toFormattedMessage() {
|
|
13
|
+
let msg = `**Input Error:** ${this.message}`;
|
|
14
|
+
if (this.hints && this.hints.length > 0) {
|
|
15
|
+
msg += "\n\n**Hints:**\n" + this.hints.map((h) => `- ${h}`).join("\n");
|
|
16
|
+
}
|
|
17
|
+
return msg;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
const ErrorMessages = {
|
|
21
|
+
// Required field errors
|
|
22
|
+
missingId: (action) => new UserInputError(`id is required for ${action} action`, [
|
|
23
|
+
`Use action="list" first to find the resource ID`,
|
|
24
|
+
`Then use action="${action}" with the id parameter`
|
|
25
|
+
]),
|
|
26
|
+
missingRequiredFields: (resource, fields) => new UserInputError(
|
|
27
|
+
`${fields.join(", ")} ${fields.length === 1 ? "is" : "are"} required for creating ${resource}`,
|
|
28
|
+
[
|
|
29
|
+
`Provide all required fields: ${fields.join(", ")}`,
|
|
30
|
+
`Use action="help" for detailed documentation on ${resource}`
|
|
31
|
+
]
|
|
32
|
+
),
|
|
33
|
+
// Invalid action errors
|
|
34
|
+
invalidAction: (action, resource, validActions) => new UserInputError(`Invalid action "${action}" for ${resource}`, [
|
|
35
|
+
`Valid actions are: ${validActions.join(", ")}`,
|
|
36
|
+
`Use action="help" with resource="${resource}" for detailed documentation`
|
|
37
|
+
]),
|
|
38
|
+
// Unknown resource errors
|
|
39
|
+
unknownResource: (resource, validResources) => new UserInputError(`Unknown resource: ${resource}`, [
|
|
40
|
+
`Valid resources are: ${validResources.join(", ")}`,
|
|
41
|
+
`Use action="help" without a resource for an overview of all resources`
|
|
42
|
+
]),
|
|
43
|
+
// Report-specific errors
|
|
44
|
+
missingReportType: () => new UserInputError("report_type is required for reports", [
|
|
45
|
+
'Specify report_type parameter (e.g., "time_reports", "project_reports")',
|
|
46
|
+
'Use action="help" with resource="reports" for available report types'
|
|
47
|
+
]),
|
|
48
|
+
invalidReportType: (reportType, validTypes) => new UserInputError(`Invalid report_type: ${reportType}`, [
|
|
49
|
+
`Valid report types are: ${validTypes.join(", ")}`,
|
|
50
|
+
'Use action="help" with resource="reports" for detailed documentation'
|
|
51
|
+
]),
|
|
52
|
+
// Timer-specific errors
|
|
53
|
+
missingServiceForTimer: () => new UserInputError("service_id is required to start a timer", [
|
|
54
|
+
'First find a service using resource="services" action="list"',
|
|
55
|
+
"Then start the timer with the service_id"
|
|
56
|
+
]),
|
|
57
|
+
// People-specific errors
|
|
58
|
+
noUserIdConfigured: () => new UserInputError("User ID not configured", [
|
|
59
|
+
'The "me" action requires a user ID to be configured',
|
|
60
|
+
'Use action="list" to find people, or configure the user ID'
|
|
61
|
+
]),
|
|
62
|
+
// Comment-specific errors
|
|
63
|
+
missingCommentTarget: () => new UserInputError("A target is required for creating a comment", [
|
|
64
|
+
"Provide one of: task_id, deal_id, or company_id",
|
|
65
|
+
'Find targets using resource="tasks", "deals", or "companies" with action="list"'
|
|
66
|
+
]),
|
|
67
|
+
// Booking-specific errors
|
|
68
|
+
missingBookingTarget: () => new UserInputError("A service or event is required for creating a booking", [
|
|
69
|
+
"Provide either: service_id or event_id",
|
|
70
|
+
'Find services using resource="services" with action="list"'
|
|
71
|
+
]),
|
|
72
|
+
// API errors
|
|
73
|
+
apiError: (statusCode, message) => {
|
|
74
|
+
const hints = [];
|
|
75
|
+
if (statusCode === 401) {
|
|
76
|
+
hints.push("Check that your API token is valid and not expired");
|
|
77
|
+
hints.push("Verify the organization ID is correct");
|
|
78
|
+
} else if (statusCode === 403) {
|
|
79
|
+
hints.push("You may not have permission to access this resource");
|
|
80
|
+
hints.push("Check your API token permissions");
|
|
81
|
+
} else if (statusCode === 404) {
|
|
82
|
+
hints.push("The resource may not exist or you may not have access");
|
|
83
|
+
hints.push("Verify the resource ID is correct");
|
|
84
|
+
hints.push('Use action="list" to find valid resource IDs');
|
|
85
|
+
} else if (statusCode === 422) {
|
|
86
|
+
hints.push("The request data may be invalid");
|
|
87
|
+
hints.push("Check the field values and types");
|
|
88
|
+
hints.push('Use action="help" for field documentation');
|
|
89
|
+
} else if (statusCode >= 500) {
|
|
90
|
+
hints.push("This is a server error - try again later");
|
|
91
|
+
}
|
|
92
|
+
return new UserInputError(`API error (${statusCode}): ${message}`, hints);
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
function isUserInputError(error) {
|
|
96
|
+
return error instanceof UserInputError;
|
|
97
|
+
}
|
|
98
|
+
const MCP_FORMAT_OPTIONS = {
|
|
99
|
+
includeRelationshipIds: false,
|
|
100
|
+
includeTimestamps: false,
|
|
101
|
+
stripHtml: true
|
|
102
|
+
};
|
|
103
|
+
function compactify(obj, fieldsToRemove) {
|
|
104
|
+
const result = { ...obj };
|
|
105
|
+
for (const field of fieldsToRemove) {
|
|
106
|
+
delete result[field];
|
|
107
|
+
}
|
|
108
|
+
return result;
|
|
109
|
+
}
|
|
110
|
+
function formatTimeEntry(entry, options) {
|
|
111
|
+
const result = formatTimeEntry$1(entry, MCP_FORMAT_OPTIONS);
|
|
112
|
+
if (options?.compact) {
|
|
113
|
+
return compactify(result, ["note", "billable_time", "approved"]);
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
function formatProject(project, options) {
|
|
118
|
+
const result = formatProject$1(project, MCP_FORMAT_OPTIONS);
|
|
119
|
+
if (options?.compact) {
|
|
120
|
+
return compactify(result, ["budget"]);
|
|
121
|
+
}
|
|
122
|
+
return result;
|
|
123
|
+
}
|
|
124
|
+
function formatTask(task, options) {
|
|
125
|
+
const result = formatTask$1(task, { ...MCP_FORMAT_OPTIONS, included: options?.included });
|
|
126
|
+
if (options?.compact) {
|
|
127
|
+
return compactify(result, [
|
|
128
|
+
"description",
|
|
129
|
+
"initial_estimate",
|
|
130
|
+
"worked_time",
|
|
131
|
+
"remaining_time",
|
|
132
|
+
"project",
|
|
133
|
+
// Keep project_name but remove nested object
|
|
134
|
+
"company"
|
|
135
|
+
// Keep company name inline if needed
|
|
136
|
+
]);
|
|
137
|
+
}
|
|
138
|
+
return result;
|
|
139
|
+
}
|
|
140
|
+
function formatPerson(person, options) {
|
|
141
|
+
const result = formatPerson$1(person, MCP_FORMAT_OPTIONS);
|
|
142
|
+
if (options?.compact) {
|
|
143
|
+
return compactify(result, ["title", "first_name", "last_name"]);
|
|
144
|
+
}
|
|
145
|
+
return result;
|
|
146
|
+
}
|
|
147
|
+
function formatService(service, options) {
|
|
148
|
+
const result = formatService$1(service, MCP_FORMAT_OPTIONS);
|
|
149
|
+
if (options?.compact) {
|
|
150
|
+
return compactify(result, ["budgeted_time", "worked_time"]);
|
|
151
|
+
}
|
|
152
|
+
return result;
|
|
153
|
+
}
|
|
154
|
+
function formatCompany(company, options) {
|
|
155
|
+
const result = formatCompany$1(company, MCP_FORMAT_OPTIONS);
|
|
156
|
+
if (options?.compact) {
|
|
157
|
+
return compactify(result, ["billing_name", "domain", "due_days"]);
|
|
158
|
+
}
|
|
159
|
+
return result;
|
|
160
|
+
}
|
|
161
|
+
function formatComment(comment, options) {
|
|
162
|
+
const result = formatComment$1(comment, { ...MCP_FORMAT_OPTIONS, included: options?.included });
|
|
163
|
+
return result;
|
|
164
|
+
}
|
|
165
|
+
function formatTimer(timer, _options) {
|
|
166
|
+
const result = formatTimer$1(timer, MCP_FORMAT_OPTIONS);
|
|
167
|
+
return result;
|
|
168
|
+
}
|
|
169
|
+
function formatDeal(deal, options) {
|
|
170
|
+
const result = formatDeal$1(deal, { ...MCP_FORMAT_OPTIONS, included: options?.included });
|
|
171
|
+
if (options?.compact) {
|
|
172
|
+
return compactify(result, ["won_at", "lost_at"]);
|
|
173
|
+
}
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
function formatBooking(booking, options) {
|
|
177
|
+
const result = formatBooking$1(booking, { ...MCP_FORMAT_OPTIONS, included: options?.included });
|
|
178
|
+
if (options?.compact) {
|
|
179
|
+
return compactify(result, ["approved_at", "rejected_at", "rejected_reason"]);
|
|
180
|
+
}
|
|
181
|
+
return result;
|
|
182
|
+
}
|
|
183
|
+
function formatListResponse(data, formatter, meta, options) {
|
|
184
|
+
const wrappedFormatter = (item, _cliOptions) => {
|
|
185
|
+
return formatter(item, options);
|
|
186
|
+
};
|
|
187
|
+
const result = formatListResponse$1(data, wrappedFormatter, meta, {
|
|
188
|
+
...MCP_FORMAT_OPTIONS,
|
|
189
|
+
included: options?.included
|
|
190
|
+
});
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
function jsonResult(data) {
|
|
194
|
+
return {
|
|
195
|
+
content: [{ type: "text", text: JSON.stringify(data, null, 2) }]
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
function errorResult(message) {
|
|
199
|
+
return {
|
|
200
|
+
content: [{ type: "text", text: `**Error:** ${message}` }],
|
|
201
|
+
isError: true
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
function inputErrorResult(error) {
|
|
205
|
+
return {
|
|
206
|
+
content: [{ type: "text", text: error.toFormattedMessage() }],
|
|
207
|
+
isError: true
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function formatError(error) {
|
|
211
|
+
if (isUserInputError(error)) {
|
|
212
|
+
return inputErrorResult(error);
|
|
213
|
+
}
|
|
214
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
215
|
+
return errorResult(message);
|
|
216
|
+
}
|
|
217
|
+
function toStringFilter(filter) {
|
|
218
|
+
if (!filter) return void 0;
|
|
219
|
+
const result = {};
|
|
220
|
+
for (const [key, value] of Object.entries(filter)) {
|
|
221
|
+
if (value !== void 0 && value !== null) {
|
|
222
|
+
result[key] = String(value);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
return Object.keys(result).length > 0 ? result : void 0;
|
|
226
|
+
}
|
|
227
|
+
const DEFAULT_BOOKING_INCLUDE = ["person", "service"];
|
|
228
|
+
const VALID_ACTIONS$a = ["list", "get", "create", "update"];
|
|
229
|
+
async function handleBookings(action, args, ctx) {
|
|
230
|
+
const { api, formatOptions, filter, page, perPage, include: userInclude } = ctx;
|
|
231
|
+
const { id, person_id, service_id, event_id, started_on, ended_on, time, note } = args;
|
|
232
|
+
const include = userInclude?.length ? [.../* @__PURE__ */ new Set([...DEFAULT_BOOKING_INCLUDE, ...userInclude])] : DEFAULT_BOOKING_INCLUDE;
|
|
233
|
+
if (action === "get") {
|
|
234
|
+
if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
|
|
235
|
+
const result = await api.getBooking(id, { include });
|
|
236
|
+
return jsonResult(formatBooking(result.data, { ...formatOptions, included: result.included }));
|
|
237
|
+
}
|
|
238
|
+
if (action === "create") {
|
|
239
|
+
if (!person_id || !started_on || !ended_on) {
|
|
240
|
+
return inputErrorResult(
|
|
241
|
+
ErrorMessages.missingRequiredFields("booking", ["person_id", "started_on", "ended_on"])
|
|
242
|
+
);
|
|
243
|
+
}
|
|
244
|
+
if (!service_id && !event_id) {
|
|
245
|
+
return inputErrorResult(ErrorMessages.missingBookingTarget());
|
|
246
|
+
}
|
|
247
|
+
const result = await api.createBooking({
|
|
248
|
+
person_id,
|
|
249
|
+
service_id,
|
|
250
|
+
event_id,
|
|
251
|
+
started_on,
|
|
252
|
+
ended_on,
|
|
253
|
+
time,
|
|
254
|
+
note
|
|
255
|
+
});
|
|
256
|
+
return jsonResult({ success: true, ...formatBooking(result.data, formatOptions) });
|
|
257
|
+
}
|
|
258
|
+
if (action === "update") {
|
|
259
|
+
if (!id) return inputErrorResult(ErrorMessages.missingId("update"));
|
|
260
|
+
const updateData = {};
|
|
261
|
+
if (started_on !== void 0) updateData.started_on = started_on;
|
|
262
|
+
if (ended_on !== void 0) updateData.ended_on = ended_on;
|
|
263
|
+
if (time !== void 0) updateData.time = time;
|
|
264
|
+
if (note !== void 0) updateData.note = note;
|
|
265
|
+
const result = await api.updateBooking(id, updateData);
|
|
266
|
+
return jsonResult({ success: true, ...formatBooking(result.data, formatOptions) });
|
|
267
|
+
}
|
|
268
|
+
if (action === "list") {
|
|
269
|
+
const result = await api.getBookings({ filter, page, perPage, include });
|
|
270
|
+
return jsonResult(
|
|
271
|
+
formatListResponse(result.data, formatBooking, result.meta, {
|
|
272
|
+
...formatOptions,
|
|
273
|
+
included: result.included
|
|
274
|
+
})
|
|
275
|
+
);
|
|
276
|
+
}
|
|
277
|
+
return inputErrorResult(ErrorMessages.invalidAction(action, "bookings", VALID_ACTIONS$a));
|
|
278
|
+
}
|
|
279
|
+
const DEFAULT_COMMENT_INCLUDE = ["creator"];
|
|
280
|
+
const VALID_ACTIONS$9 = ["list", "get", "create", "update"];
|
|
281
|
+
async function handleComments(action, args, ctx) {
|
|
282
|
+
const { api, formatOptions, filter, page, perPage, include: userInclude } = ctx;
|
|
283
|
+
const { id, body, task_id, deal_id, company_id } = args;
|
|
284
|
+
const include = userInclude?.length ? [.../* @__PURE__ */ new Set([...DEFAULT_COMMENT_INCLUDE, ...userInclude])] : DEFAULT_COMMENT_INCLUDE;
|
|
285
|
+
if (action === "get") {
|
|
286
|
+
if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
|
|
287
|
+
const result = await api.getComment(id, { include });
|
|
288
|
+
return jsonResult(formatComment(result.data, { ...formatOptions, included: result.included }));
|
|
289
|
+
}
|
|
290
|
+
if (action === "create") {
|
|
291
|
+
if (!body) return inputErrorResult(ErrorMessages.missingRequiredFields("comment", ["body"]));
|
|
292
|
+
if (!task_id && !deal_id && !company_id) {
|
|
293
|
+
return inputErrorResult(ErrorMessages.missingCommentTarget());
|
|
294
|
+
}
|
|
295
|
+
const result = await api.createComment({
|
|
296
|
+
body,
|
|
297
|
+
task_id,
|
|
298
|
+
deal_id,
|
|
299
|
+
company_id
|
|
300
|
+
});
|
|
301
|
+
return jsonResult({ success: true, ...formatComment(result.data, formatOptions) });
|
|
302
|
+
}
|
|
303
|
+
if (action === "update") {
|
|
304
|
+
if (!id) return inputErrorResult(ErrorMessages.missingId("update"));
|
|
305
|
+
if (!body)
|
|
306
|
+
return inputErrorResult(ErrorMessages.missingRequiredFields("comment update", ["body"]));
|
|
307
|
+
const result = await api.updateComment(id, { body });
|
|
308
|
+
return jsonResult({ success: true, ...formatComment(result.data, formatOptions) });
|
|
309
|
+
}
|
|
310
|
+
if (action === "list") {
|
|
311
|
+
const result = await api.getComments({ filter, page, perPage, include });
|
|
312
|
+
return jsonResult(
|
|
313
|
+
formatListResponse(result.data, formatComment, result.meta, {
|
|
314
|
+
...formatOptions,
|
|
315
|
+
included: result.included
|
|
316
|
+
})
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
return inputErrorResult(ErrorMessages.invalidAction(action, "comments", VALID_ACTIONS$9));
|
|
320
|
+
}
|
|
321
|
+
const VALID_ACTIONS$8 = ["list", "get", "create", "update"];
|
|
322
|
+
async function handleCompanies(action, args, ctx) {
|
|
323
|
+
const { api, formatOptions, filter, page, perPage } = ctx;
|
|
324
|
+
const { id, name } = args;
|
|
325
|
+
if (action === "get") {
|
|
326
|
+
if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
|
|
327
|
+
const result = await api.getCompany(id);
|
|
328
|
+
return jsonResult(formatCompany(result.data, formatOptions));
|
|
329
|
+
}
|
|
330
|
+
if (action === "create") {
|
|
331
|
+
if (!name) return inputErrorResult(ErrorMessages.missingRequiredFields("company", ["name"]));
|
|
332
|
+
const result = await api.createCompany({ name });
|
|
333
|
+
return jsonResult({ success: true, ...formatCompany(result.data, formatOptions) });
|
|
334
|
+
}
|
|
335
|
+
if (action === "update") {
|
|
336
|
+
if (!id) return inputErrorResult(ErrorMessages.missingId("update"));
|
|
337
|
+
const updateData = {};
|
|
338
|
+
if (name !== void 0) updateData.name = name;
|
|
339
|
+
const result = await api.updateCompany(id, updateData);
|
|
340
|
+
return jsonResult({ success: true, ...formatCompany(result.data, formatOptions) });
|
|
341
|
+
}
|
|
342
|
+
if (action === "list") {
|
|
343
|
+
const result = await api.getCompanies({ filter, page, perPage });
|
|
344
|
+
return jsonResult(formatListResponse(result.data, formatCompany, result.meta, formatOptions));
|
|
345
|
+
}
|
|
346
|
+
return inputErrorResult(ErrorMessages.invalidAction(action, "companies", VALID_ACTIONS$8));
|
|
347
|
+
}
|
|
348
|
+
const DEFAULT_DEAL_INCLUDE_GET = ["company", "deal_status", "responsible"];
|
|
349
|
+
const DEFAULT_DEAL_INCLUDE_LIST = ["company", "deal_status"];
|
|
350
|
+
const VALID_ACTIONS$7 = ["list", "get", "create", "update"];
|
|
351
|
+
async function handleDeals(action, args, ctx) {
|
|
352
|
+
const { api, formatOptions, filter, page, perPage, include: userInclude } = ctx;
|
|
353
|
+
const { id, name, company_id } = args;
|
|
354
|
+
if (action === "get") {
|
|
355
|
+
if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
|
|
356
|
+
const include = userInclude?.length ? [.../* @__PURE__ */ new Set([...DEFAULT_DEAL_INCLUDE_GET, ...userInclude])] : DEFAULT_DEAL_INCLUDE_GET;
|
|
357
|
+
const result = await api.getDeal(id, { include });
|
|
358
|
+
return jsonResult(formatDeal(result.data, { ...formatOptions, included: result.included }));
|
|
359
|
+
}
|
|
360
|
+
if (action === "create") {
|
|
361
|
+
if (!name || !company_id) {
|
|
362
|
+
return inputErrorResult(ErrorMessages.missingRequiredFields("deal", ["name", "company_id"]));
|
|
363
|
+
}
|
|
364
|
+
const result = await api.createDeal({ name, company_id });
|
|
365
|
+
return jsonResult({ success: true, ...formatDeal(result.data, formatOptions) });
|
|
366
|
+
}
|
|
367
|
+
if (action === "update") {
|
|
368
|
+
if (!id) return inputErrorResult(ErrorMessages.missingId("update"));
|
|
369
|
+
const updateData = {};
|
|
370
|
+
if (name !== void 0) updateData.name = name;
|
|
371
|
+
const result = await api.updateDeal(id, updateData);
|
|
372
|
+
return jsonResult({ success: true, ...formatDeal(result.data, formatOptions) });
|
|
373
|
+
}
|
|
374
|
+
if (action === "list") {
|
|
375
|
+
const include = userInclude?.length ? [.../* @__PURE__ */ new Set([...DEFAULT_DEAL_INCLUDE_LIST, ...userInclude])] : DEFAULT_DEAL_INCLUDE_LIST;
|
|
376
|
+
const result = await api.getDeals({
|
|
377
|
+
filter,
|
|
378
|
+
page,
|
|
379
|
+
perPage,
|
|
380
|
+
include
|
|
381
|
+
});
|
|
382
|
+
return jsonResult(
|
|
383
|
+
formatListResponse(result.data, formatDeal, result.meta, {
|
|
384
|
+
...formatOptions,
|
|
385
|
+
included: result.included
|
|
386
|
+
})
|
|
387
|
+
);
|
|
388
|
+
}
|
|
389
|
+
return inputErrorResult(ErrorMessages.invalidAction(action, "deals", VALID_ACTIONS$7));
|
|
390
|
+
}
|
|
391
|
+
const RESOURCE_HELP = {
|
|
392
|
+
projects: {
|
|
393
|
+
description: "Manage projects in Productive.io",
|
|
394
|
+
actions: {
|
|
395
|
+
list: "List all projects with optional filters",
|
|
396
|
+
get: "Get a single project by ID with full details"
|
|
397
|
+
},
|
|
398
|
+
filters: {
|
|
399
|
+
query: "Text search on project name",
|
|
400
|
+
project_type_id: "Filter by project type",
|
|
401
|
+
company_id: "Filter by company",
|
|
402
|
+
archived: "Filter by archived status (true/false)"
|
|
403
|
+
},
|
|
404
|
+
fields: {
|
|
405
|
+
id: "Unique project identifier",
|
|
406
|
+
name: "Project name",
|
|
407
|
+
project_number: "Project reference number",
|
|
408
|
+
archived: "Whether the project is archived",
|
|
409
|
+
budget: "Project budget amount"
|
|
410
|
+
},
|
|
411
|
+
examples: [
|
|
412
|
+
{
|
|
413
|
+
description: "Search projects by name",
|
|
414
|
+
params: { resource: "projects", action: "list", query: "website" }
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
description: "List active projects",
|
|
418
|
+
params: { resource: "projects", action: "list", filter: { archived: "false" } }
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
description: "Get project details",
|
|
422
|
+
params: { resource: "projects", action: "get", id: "12345" }
|
|
423
|
+
}
|
|
424
|
+
]
|
|
425
|
+
},
|
|
426
|
+
tasks: {
|
|
427
|
+
description: "Manage tasks within projects",
|
|
428
|
+
actions: {
|
|
429
|
+
list: "List tasks with optional filters",
|
|
430
|
+
get: "Get a single task by ID with full details (description, comments, etc.)",
|
|
431
|
+
create: "Create a new task (requires title, project_id, task_list_id)",
|
|
432
|
+
update: "Update an existing task"
|
|
433
|
+
},
|
|
434
|
+
filters: {
|
|
435
|
+
query: "Text search on task title",
|
|
436
|
+
project_id: "Filter by project",
|
|
437
|
+
assignee_id: "Filter by assigned person",
|
|
438
|
+
status: "Filter by status (open, closed, all)",
|
|
439
|
+
task_list_id: "Filter by task list"
|
|
440
|
+
},
|
|
441
|
+
includes: [
|
|
442
|
+
"project",
|
|
443
|
+
"project.company",
|
|
444
|
+
"assignee",
|
|
445
|
+
"workflow_status",
|
|
446
|
+
"comments",
|
|
447
|
+
"attachments",
|
|
448
|
+
"subtasks"
|
|
449
|
+
],
|
|
450
|
+
fields: {
|
|
451
|
+
id: "Unique task identifier",
|
|
452
|
+
title: "Task title",
|
|
453
|
+
description: "Full task description (HTML)",
|
|
454
|
+
number: "Task number within project",
|
|
455
|
+
due_date: "Due date (YYYY-MM-DD)",
|
|
456
|
+
initial_estimate: "Estimated time in minutes",
|
|
457
|
+
worked_time: "Logged time in minutes",
|
|
458
|
+
remaining_time: "Remaining time in minutes",
|
|
459
|
+
closed: "Whether the task is closed"
|
|
460
|
+
},
|
|
461
|
+
examples: [
|
|
462
|
+
{
|
|
463
|
+
description: "Search tasks by title",
|
|
464
|
+
params: { resource: "tasks", action: "list", query: "bug fix" }
|
|
465
|
+
},
|
|
466
|
+
{
|
|
467
|
+
description: "List open tasks for a project",
|
|
468
|
+
params: {
|
|
469
|
+
resource: "tasks",
|
|
470
|
+
action: "list",
|
|
471
|
+
filter: { project_id: "12345", status: "open" }
|
|
472
|
+
}
|
|
473
|
+
},
|
|
474
|
+
{
|
|
475
|
+
description: "Get task with comments",
|
|
476
|
+
params: {
|
|
477
|
+
resource: "tasks",
|
|
478
|
+
action: "get",
|
|
479
|
+
id: "67890",
|
|
480
|
+
include: ["comments", "assignee"]
|
|
481
|
+
}
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
description: "Create a task",
|
|
485
|
+
params: {
|
|
486
|
+
resource: "tasks",
|
|
487
|
+
action: "create",
|
|
488
|
+
title: "New task",
|
|
489
|
+
project_id: "12345",
|
|
490
|
+
task_list_id: "111"
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
]
|
|
494
|
+
},
|
|
495
|
+
time: {
|
|
496
|
+
description: "Track time entries against services/tasks",
|
|
497
|
+
actions: {
|
|
498
|
+
list: "List time entries with optional filters",
|
|
499
|
+
get: "Get a single time entry by ID",
|
|
500
|
+
create: "Create a new time entry (requires person_id, service_id, date, time)",
|
|
501
|
+
update: "Update an existing time entry"
|
|
502
|
+
},
|
|
503
|
+
filters: {
|
|
504
|
+
person_id: "Filter by person",
|
|
505
|
+
service_id: "Filter by service",
|
|
506
|
+
project_id: "Filter by project",
|
|
507
|
+
after: "Filter entries after date (YYYY-MM-DD)",
|
|
508
|
+
before: "Filter entries before date (YYYY-MM-DD)"
|
|
509
|
+
},
|
|
510
|
+
fields: {
|
|
511
|
+
id: "Unique time entry identifier",
|
|
512
|
+
date: "Date of the entry (YYYY-MM-DD)",
|
|
513
|
+
time: "Time in minutes",
|
|
514
|
+
note: "Description of work done",
|
|
515
|
+
billable_time: "Billable time in minutes",
|
|
516
|
+
approved: "Whether the entry is approved"
|
|
517
|
+
},
|
|
518
|
+
examples: [
|
|
519
|
+
{
|
|
520
|
+
description: "List my time entries this week",
|
|
521
|
+
params: {
|
|
522
|
+
resource: "time",
|
|
523
|
+
action: "list",
|
|
524
|
+
filter: { person_id: "me", after: "2024-01-15", before: "2024-01-21" }
|
|
525
|
+
}
|
|
526
|
+
},
|
|
527
|
+
{
|
|
528
|
+
description: "Log 2 hours",
|
|
529
|
+
params: {
|
|
530
|
+
resource: "time",
|
|
531
|
+
action: "create",
|
|
532
|
+
service_id: "12345",
|
|
533
|
+
date: "2024-01-16",
|
|
534
|
+
time: 120,
|
|
535
|
+
note: "Development work"
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
]
|
|
539
|
+
},
|
|
540
|
+
services: {
|
|
541
|
+
description: "Budget line items within projects",
|
|
542
|
+
actions: {
|
|
543
|
+
list: "List services with optional filters",
|
|
544
|
+
get: "Get a single service by ID"
|
|
545
|
+
},
|
|
546
|
+
filters: {
|
|
547
|
+
project_id: "Filter by project",
|
|
548
|
+
deal_id: "Filter by deal"
|
|
549
|
+
},
|
|
550
|
+
fields: {
|
|
551
|
+
id: "Unique service identifier",
|
|
552
|
+
name: "Service name",
|
|
553
|
+
budgeted_time: "Budgeted time in minutes",
|
|
554
|
+
worked_time: "Logged time in minutes"
|
|
555
|
+
},
|
|
556
|
+
examples: [
|
|
557
|
+
{
|
|
558
|
+
description: "List services for a project",
|
|
559
|
+
params: { resource: "services", action: "list", filter: { project_id: "12345" } }
|
|
560
|
+
}
|
|
561
|
+
]
|
|
562
|
+
},
|
|
563
|
+
people: {
|
|
564
|
+
description: "Team members and contacts",
|
|
565
|
+
actions: {
|
|
566
|
+
list: "List people with optional filters",
|
|
567
|
+
get: "Get a single person by ID",
|
|
568
|
+
me: "Get the currently authenticated user"
|
|
569
|
+
},
|
|
570
|
+
filters: {
|
|
571
|
+
query: "Text search on name or email",
|
|
572
|
+
status: "Filter by status (active, inactive)"
|
|
573
|
+
},
|
|
574
|
+
fields: {
|
|
575
|
+
id: "Unique person identifier",
|
|
576
|
+
name: "Full name",
|
|
577
|
+
first_name: "First name",
|
|
578
|
+
last_name: "Last name",
|
|
579
|
+
email: "Email address",
|
|
580
|
+
title: "Job title",
|
|
581
|
+
active: "Whether the person is active"
|
|
582
|
+
},
|
|
583
|
+
examples: [
|
|
584
|
+
{ description: "Get current user", params: { resource: "people", action: "me" } },
|
|
585
|
+
{
|
|
586
|
+
description: "Search people by name",
|
|
587
|
+
params: { resource: "people", action: "list", query: "john" }
|
|
588
|
+
},
|
|
589
|
+
{
|
|
590
|
+
description: "List active team members",
|
|
591
|
+
params: { resource: "people", action: "list", filter: { status: "active" } }
|
|
592
|
+
}
|
|
593
|
+
]
|
|
594
|
+
},
|
|
595
|
+
companies: {
|
|
596
|
+
description: "Client companies and organizations",
|
|
597
|
+
actions: {
|
|
598
|
+
list: "List companies with optional filters",
|
|
599
|
+
get: "Get a single company by ID",
|
|
600
|
+
create: "Create a new company (requires name)",
|
|
601
|
+
update: "Update an existing company"
|
|
602
|
+
},
|
|
603
|
+
filters: {
|
|
604
|
+
query: "Text search on company name",
|
|
605
|
+
archived: "Filter by archived status (true/false)"
|
|
606
|
+
},
|
|
607
|
+
fields: {
|
|
608
|
+
id: "Unique company identifier",
|
|
609
|
+
name: "Company name",
|
|
610
|
+
billing_name: "Legal/billing name",
|
|
611
|
+
domain: "Website domain",
|
|
612
|
+
vat: "VAT number"
|
|
613
|
+
},
|
|
614
|
+
examples: [
|
|
615
|
+
{
|
|
616
|
+
description: "Search companies",
|
|
617
|
+
params: { resource: "companies", action: "list", query: "acme" }
|
|
618
|
+
},
|
|
619
|
+
{
|
|
620
|
+
description: "List active companies",
|
|
621
|
+
params: { resource: "companies", action: "list", filter: { archived: "false" } }
|
|
622
|
+
}
|
|
623
|
+
]
|
|
624
|
+
},
|
|
625
|
+
comments: {
|
|
626
|
+
description: "Comments on tasks, deals, and other resources",
|
|
627
|
+
actions: {
|
|
628
|
+
list: "List comments with optional filters",
|
|
629
|
+
get: "Get a single comment by ID",
|
|
630
|
+
create: "Create a new comment (requires body and one of: task_id, deal_id, company_id)",
|
|
631
|
+
update: "Update an existing comment"
|
|
632
|
+
},
|
|
633
|
+
filters: {
|
|
634
|
+
task_id: "Filter by task",
|
|
635
|
+
deal_id: "Filter by deal"
|
|
636
|
+
},
|
|
637
|
+
includes: ["creator", "task", "deal"],
|
|
638
|
+
fields: {
|
|
639
|
+
id: "Unique comment identifier",
|
|
640
|
+
body: "Comment text (may contain HTML)",
|
|
641
|
+
creator: "Person who created the comment"
|
|
642
|
+
},
|
|
643
|
+
examples: [
|
|
644
|
+
{
|
|
645
|
+
description: "List comments on a task",
|
|
646
|
+
params: { resource: "comments", action: "list", filter: { task_id: "12345" } }
|
|
647
|
+
},
|
|
648
|
+
{
|
|
649
|
+
description: "Add a comment",
|
|
650
|
+
params: { resource: "comments", action: "create", task_id: "12345", body: "Looking good!" }
|
|
651
|
+
}
|
|
652
|
+
]
|
|
653
|
+
},
|
|
654
|
+
timers: {
|
|
655
|
+
description: "Active time tracking timers",
|
|
656
|
+
actions: {
|
|
657
|
+
list: "List active timers",
|
|
658
|
+
get: "Get a single timer by ID",
|
|
659
|
+
start: "Start a new timer (requires service_id or time_entry_id)",
|
|
660
|
+
stop: "Stop an active timer by ID"
|
|
661
|
+
},
|
|
662
|
+
fields: {
|
|
663
|
+
id: "Unique timer identifier",
|
|
664
|
+
started_at: "When the timer started (ISO 8601)",
|
|
665
|
+
total_time: "Elapsed time in seconds"
|
|
666
|
+
},
|
|
667
|
+
examples: [
|
|
668
|
+
{ description: "List active timers", params: { resource: "timers", action: "list" } },
|
|
669
|
+
{
|
|
670
|
+
description: "Start timer on service",
|
|
671
|
+
params: { resource: "timers", action: "start", service_id: "12345" }
|
|
672
|
+
},
|
|
673
|
+
{ description: "Stop timer", params: { resource: "timers", action: "stop", id: "67890" } }
|
|
674
|
+
]
|
|
675
|
+
},
|
|
676
|
+
deals: {
|
|
677
|
+
description: "Sales deals and opportunities",
|
|
678
|
+
actions: {
|
|
679
|
+
list: "List deals with optional filters",
|
|
680
|
+
get: "Get a single deal by ID",
|
|
681
|
+
create: "Create a new deal (requires name, company_id)",
|
|
682
|
+
update: "Update an existing deal"
|
|
683
|
+
},
|
|
684
|
+
filters: {
|
|
685
|
+
query: "Text search on deal name",
|
|
686
|
+
company_id: "Filter by company",
|
|
687
|
+
deal_status_id: "Filter by status"
|
|
688
|
+
},
|
|
689
|
+
includes: ["company", "deal_status", "responsible", "project"],
|
|
690
|
+
fields: {
|
|
691
|
+
id: "Unique deal identifier",
|
|
692
|
+
name: "Deal name",
|
|
693
|
+
number: "Deal number",
|
|
694
|
+
date: "Deal date",
|
|
695
|
+
status: "Current status (from deal_status)"
|
|
696
|
+
},
|
|
697
|
+
examples: [
|
|
698
|
+
{
|
|
699
|
+
description: "Search deals",
|
|
700
|
+
params: { resource: "deals", action: "list", query: "website redesign" }
|
|
701
|
+
},
|
|
702
|
+
{
|
|
703
|
+
description: "List deals for a company",
|
|
704
|
+
params: { resource: "deals", action: "list", filter: { company_id: "12345" } }
|
|
705
|
+
}
|
|
706
|
+
]
|
|
707
|
+
},
|
|
708
|
+
bookings: {
|
|
709
|
+
description: "Resource scheduling and capacity planning",
|
|
710
|
+
actions: {
|
|
711
|
+
list: "List bookings with optional filters",
|
|
712
|
+
get: "Get a single booking by ID",
|
|
713
|
+
create: "Create a new booking (requires person_id, started_on, ended_on, and service_id or event_id)",
|
|
714
|
+
update: "Update an existing booking"
|
|
715
|
+
},
|
|
716
|
+
filters: {
|
|
717
|
+
person_id: "Filter by person",
|
|
718
|
+
service_id: "Filter by service",
|
|
719
|
+
after: "Filter bookings after date (YYYY-MM-DD)",
|
|
720
|
+
before: "Filter bookings before date (YYYY-MM-DD)"
|
|
721
|
+
},
|
|
722
|
+
includes: ["person", "service", "event"],
|
|
723
|
+
fields: {
|
|
724
|
+
id: "Unique booking identifier",
|
|
725
|
+
started_on: "Start date (YYYY-MM-DD)",
|
|
726
|
+
ended_on: "End date (YYYY-MM-DD)",
|
|
727
|
+
time: "Time per day in minutes",
|
|
728
|
+
total_time: "Total booked time in minutes",
|
|
729
|
+
note: "Booking note"
|
|
730
|
+
},
|
|
731
|
+
examples: [
|
|
732
|
+
{
|
|
733
|
+
description: "List my bookings",
|
|
734
|
+
params: { resource: "bookings", action: "list", filter: { person_id: "me" } }
|
|
735
|
+
}
|
|
736
|
+
]
|
|
737
|
+
},
|
|
738
|
+
reports: {
|
|
739
|
+
description: "Generate various reports (time, budget, project, etc.)",
|
|
740
|
+
actions: {
|
|
741
|
+
get: "Generate a report (requires report_type)"
|
|
742
|
+
},
|
|
743
|
+
filters: {
|
|
744
|
+
person_id: "Filter by person",
|
|
745
|
+
project_id: "Filter by project",
|
|
746
|
+
company_id: "Filter by company",
|
|
747
|
+
after: "Filter from date (YYYY-MM-DD)",
|
|
748
|
+
before: "Filter to date (YYYY-MM-DD)"
|
|
749
|
+
},
|
|
750
|
+
fields: {
|
|
751
|
+
report_type: "Type of report: time_reports, project_reports, budget_reports, person_reports, invoice_reports, payment_reports, service_reports, task_reports, company_reports, deal_reports, timesheet_reports",
|
|
752
|
+
group: "Grouping dimension (varies by report type)",
|
|
753
|
+
from: "Start date for date range",
|
|
754
|
+
to: "End date for date range"
|
|
755
|
+
},
|
|
756
|
+
examples: [
|
|
757
|
+
{
|
|
758
|
+
description: "Time report by person",
|
|
759
|
+
params: {
|
|
760
|
+
resource: "reports",
|
|
761
|
+
action: "get",
|
|
762
|
+
report_type: "time_reports",
|
|
763
|
+
group: "person",
|
|
764
|
+
from: "2024-01-01",
|
|
765
|
+
to: "2024-01-31"
|
|
766
|
+
}
|
|
767
|
+
},
|
|
768
|
+
{
|
|
769
|
+
description: "Project budget report",
|
|
770
|
+
params: {
|
|
771
|
+
resource: "reports",
|
|
772
|
+
action: "get",
|
|
773
|
+
report_type: "budget_reports",
|
|
774
|
+
filter: { project_id: "12345" }
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
]
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
function handleHelp(resource) {
|
|
781
|
+
const help = RESOURCE_HELP[resource];
|
|
782
|
+
if (!help) {
|
|
783
|
+
return jsonResult({
|
|
784
|
+
error: `Unknown resource: ${resource}`,
|
|
785
|
+
available_resources: Object.keys(RESOURCE_HELP)
|
|
786
|
+
});
|
|
787
|
+
}
|
|
788
|
+
return jsonResult({
|
|
789
|
+
resource,
|
|
790
|
+
...help
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
function handleHelpOverview() {
|
|
794
|
+
const overview = Object.entries(RESOURCE_HELP).map(([resource, help]) => ({
|
|
795
|
+
resource,
|
|
796
|
+
description: help.description,
|
|
797
|
+
actions: Object.keys(help.actions)
|
|
798
|
+
}));
|
|
799
|
+
return jsonResult({
|
|
800
|
+
message: 'Use action="help" with a specific resource for detailed documentation',
|
|
801
|
+
resources: overview
|
|
802
|
+
});
|
|
803
|
+
}
|
|
804
|
+
const VALID_ACTIONS$6 = ["list", "get", "me"];
|
|
805
|
+
async function handlePeople(action, args, ctx, credentials) {
|
|
806
|
+
const { api, formatOptions, filter, page, perPage } = ctx;
|
|
807
|
+
const { id } = args;
|
|
808
|
+
if (action === "get") {
|
|
809
|
+
if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
|
|
810
|
+
const result = await api.getPerson(id);
|
|
811
|
+
return jsonResult(formatPerson(result.data, formatOptions));
|
|
812
|
+
}
|
|
813
|
+
if (action === "me") {
|
|
814
|
+
if (credentials.userId) {
|
|
815
|
+
const result = await api.getPerson(credentials.userId);
|
|
816
|
+
return jsonResult(formatPerson(result.data, formatOptions));
|
|
817
|
+
}
|
|
818
|
+
return jsonResult({
|
|
819
|
+
message: "User ID not configured. Set userId in credentials to use this action.",
|
|
820
|
+
hint: 'Use action="list" to find people, or configure the user ID in your credentials.',
|
|
821
|
+
organizationId: credentials.organizationId
|
|
822
|
+
});
|
|
823
|
+
}
|
|
824
|
+
if (action === "list") {
|
|
825
|
+
const result = await api.getPeople({ filter, page, perPage });
|
|
826
|
+
return jsonResult(formatListResponse(result.data, formatPerson, result.meta, formatOptions));
|
|
827
|
+
}
|
|
828
|
+
return inputErrorResult(ErrorMessages.invalidAction(action, "people", VALID_ACTIONS$6));
|
|
829
|
+
}
|
|
830
|
+
const VALID_ACTIONS$5 = ["list", "get"];
|
|
831
|
+
async function handleProjects(action, args, ctx) {
|
|
832
|
+
const { api, formatOptions, filter, page, perPage } = ctx;
|
|
833
|
+
const { id } = args;
|
|
834
|
+
if (action === "get") {
|
|
835
|
+
if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
|
|
836
|
+
const result = await api.getProject(id);
|
|
837
|
+
return jsonResult(formatProject(result.data, formatOptions));
|
|
838
|
+
}
|
|
839
|
+
if (action === "list") {
|
|
840
|
+
const result = await api.getProjects({ filter, page, perPage });
|
|
841
|
+
return jsonResult(formatListResponse(result.data, formatProject, result.meta, formatOptions));
|
|
842
|
+
}
|
|
843
|
+
return inputErrorResult(ErrorMessages.invalidAction(action, "projects", VALID_ACTIONS$5));
|
|
844
|
+
}
|
|
845
|
+
function formatReportData(data) {
|
|
846
|
+
return data.map((item) => {
|
|
847
|
+
const record = item;
|
|
848
|
+
return {
|
|
849
|
+
id: record.id,
|
|
850
|
+
type: record.type,
|
|
851
|
+
...record.attributes
|
|
852
|
+
};
|
|
853
|
+
});
|
|
854
|
+
}
|
|
855
|
+
const VALID_REPORT_TYPES = [
|
|
856
|
+
"time_reports",
|
|
857
|
+
"project_reports",
|
|
858
|
+
"budget_reports",
|
|
859
|
+
"person_reports",
|
|
860
|
+
"invoice_reports",
|
|
861
|
+
"payment_reports",
|
|
862
|
+
"service_reports",
|
|
863
|
+
"task_reports",
|
|
864
|
+
"company_reports",
|
|
865
|
+
"deal_reports",
|
|
866
|
+
"timesheet_reports"
|
|
867
|
+
];
|
|
868
|
+
const VALID_ACTIONS$4 = ["get"];
|
|
869
|
+
async function handleReports(action, args, ctx) {
|
|
870
|
+
const { api, filter, page, perPage } = ctx;
|
|
871
|
+
const { report_type, group, from, to, person_id, project_id, company_id, deal_id, status } = args;
|
|
872
|
+
if (action !== "get") {
|
|
873
|
+
return inputErrorResult(ErrorMessages.invalidAction(action, "reports", VALID_ACTIONS$4));
|
|
874
|
+
}
|
|
875
|
+
if (!report_type) {
|
|
876
|
+
return inputErrorResult(ErrorMessages.missingReportType());
|
|
877
|
+
}
|
|
878
|
+
if (!VALID_REPORT_TYPES.includes(report_type)) {
|
|
879
|
+
return inputErrorResult(ErrorMessages.invalidReportType(report_type, VALID_REPORT_TYPES));
|
|
880
|
+
}
|
|
881
|
+
const reportFilter = { ...filter };
|
|
882
|
+
if (from) {
|
|
883
|
+
if (report_type === "invoice_reports") {
|
|
884
|
+
reportFilter.invoice_date_after = from;
|
|
885
|
+
} else if (report_type === "payment_reports" || report_type === "deal_reports") {
|
|
886
|
+
reportFilter.date_after = from;
|
|
887
|
+
} else {
|
|
888
|
+
reportFilter.after = from;
|
|
889
|
+
}
|
|
890
|
+
}
|
|
891
|
+
if (to) {
|
|
892
|
+
if (report_type === "invoice_reports") {
|
|
893
|
+
reportFilter.invoice_date_before = to;
|
|
894
|
+
} else if (report_type === "payment_reports" || report_type === "deal_reports") {
|
|
895
|
+
reportFilter.date_before = to;
|
|
896
|
+
} else {
|
|
897
|
+
reportFilter.before = to;
|
|
898
|
+
}
|
|
899
|
+
}
|
|
900
|
+
if (person_id) {
|
|
901
|
+
if (report_type === "task_reports") {
|
|
902
|
+
reportFilter.assignee_id = person_id;
|
|
903
|
+
} else {
|
|
904
|
+
reportFilter.person_id = person_id;
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
if (project_id) reportFilter.project_id = project_id;
|
|
908
|
+
if (company_id) reportFilter.company_id = company_id;
|
|
909
|
+
if (deal_id) {
|
|
910
|
+
if (report_type === "deal_reports") {
|
|
911
|
+
reportFilter.deal_status_id = deal_id;
|
|
912
|
+
} else {
|
|
913
|
+
reportFilter.deal_id = deal_id;
|
|
914
|
+
}
|
|
915
|
+
}
|
|
916
|
+
if (status) {
|
|
917
|
+
if (report_type === "deal_reports") {
|
|
918
|
+
reportFilter.deal_status_id = status;
|
|
919
|
+
} else {
|
|
920
|
+
reportFilter.status = status;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
let effectiveGroup = group;
|
|
924
|
+
if (!effectiveGroup) {
|
|
925
|
+
const defaultGroups = {
|
|
926
|
+
time_reports: "person",
|
|
927
|
+
project_reports: "project",
|
|
928
|
+
budget_reports: "deal",
|
|
929
|
+
person_reports: "person",
|
|
930
|
+
invoice_reports: "invoice",
|
|
931
|
+
payment_reports: "payment",
|
|
932
|
+
service_reports: "service",
|
|
933
|
+
task_reports: "task",
|
|
934
|
+
company_reports: "company",
|
|
935
|
+
deal_reports: "deal"
|
|
936
|
+
};
|
|
937
|
+
effectiveGroup = defaultGroups[report_type];
|
|
938
|
+
}
|
|
939
|
+
const includeMap = {
|
|
940
|
+
project_reports: ["project"],
|
|
941
|
+
budget_reports: ["deal"],
|
|
942
|
+
person_reports: ["person"],
|
|
943
|
+
invoice_reports: ["invoice"],
|
|
944
|
+
payment_reports: ["payment"],
|
|
945
|
+
service_reports: ["service"],
|
|
946
|
+
task_reports: ["task"],
|
|
947
|
+
company_reports: ["company"],
|
|
948
|
+
deal_reports: ["deal"],
|
|
949
|
+
timesheet_reports: ["person"]
|
|
950
|
+
};
|
|
951
|
+
const include = includeMap[report_type];
|
|
952
|
+
const result = await api.getReports(report_type, {
|
|
953
|
+
page,
|
|
954
|
+
perPage,
|
|
955
|
+
filter: reportFilter,
|
|
956
|
+
group: effectiveGroup,
|
|
957
|
+
include
|
|
958
|
+
});
|
|
959
|
+
const formattedData = formatReportData(result.data);
|
|
960
|
+
return jsonResult({
|
|
961
|
+
data: formattedData,
|
|
962
|
+
meta: result.meta
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
const VALID_ACTIONS$3 = ["list"];
|
|
966
|
+
async function handleServices(action, _args, ctx) {
|
|
967
|
+
const { api, formatOptions, filter, page, perPage } = ctx;
|
|
968
|
+
if (action === "list") {
|
|
969
|
+
const result = await api.getServices({ filter, page, perPage });
|
|
970
|
+
return jsonResult(formatListResponse(result.data, formatService, result.meta, formatOptions));
|
|
971
|
+
}
|
|
972
|
+
return inputErrorResult(ErrorMessages.invalidAction(action, "services", VALID_ACTIONS$3));
|
|
973
|
+
}
|
|
974
|
+
const DEFAULT_TASK_INCLUDE = ["project", "project.company"];
|
|
975
|
+
const VALID_ACTIONS$2 = ["list", "get", "create", "update"];
|
|
976
|
+
async function handleTasks(action, args, ctx) {
|
|
977
|
+
const { api, formatOptions, filter, page, perPage, include: userInclude } = ctx;
|
|
978
|
+
const { id, title, project_id, task_list_id, description, assignee_id } = args;
|
|
979
|
+
const include = userInclude?.length ? [.../* @__PURE__ */ new Set([...DEFAULT_TASK_INCLUDE, ...userInclude])] : DEFAULT_TASK_INCLUDE;
|
|
980
|
+
if (action === "get") {
|
|
981
|
+
if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
|
|
982
|
+
const result = await api.getTask(id, { include });
|
|
983
|
+
return jsonResult(formatTask(result.data, { ...formatOptions, included: result.included }));
|
|
984
|
+
}
|
|
985
|
+
if (action === "create") {
|
|
986
|
+
if (!title || !project_id || !task_list_id) {
|
|
987
|
+
return inputErrorResult(
|
|
988
|
+
ErrorMessages.missingRequiredFields("task", ["title", "project_id", "task_list_id"])
|
|
989
|
+
);
|
|
990
|
+
}
|
|
991
|
+
const result = await api.createTask({
|
|
992
|
+
title,
|
|
993
|
+
project_id,
|
|
994
|
+
task_list_id,
|
|
995
|
+
assignee_id,
|
|
996
|
+
description
|
|
997
|
+
});
|
|
998
|
+
return jsonResult({ success: true, ...formatTask(result.data, formatOptions) });
|
|
999
|
+
}
|
|
1000
|
+
if (action === "update") {
|
|
1001
|
+
if (!id) return inputErrorResult(ErrorMessages.missingId("update"));
|
|
1002
|
+
const updateData = {};
|
|
1003
|
+
if (title !== void 0) updateData.title = title;
|
|
1004
|
+
if (description !== void 0) updateData.description = description;
|
|
1005
|
+
if (assignee_id !== void 0) updateData.assignee_id = assignee_id;
|
|
1006
|
+
const result = await api.updateTask(id, updateData);
|
|
1007
|
+
return jsonResult({ success: true, ...formatTask(result.data, formatOptions) });
|
|
1008
|
+
}
|
|
1009
|
+
if (action === "list") {
|
|
1010
|
+
const result = await api.getTasks({ filter, page, perPage, include });
|
|
1011
|
+
return jsonResult(
|
|
1012
|
+
formatListResponse(result.data, formatTask, result.meta, {
|
|
1013
|
+
...formatOptions,
|
|
1014
|
+
included: result.included
|
|
1015
|
+
})
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
return inputErrorResult(ErrorMessages.invalidAction(action, "tasks", VALID_ACTIONS$2));
|
|
1019
|
+
}
|
|
1020
|
+
const VALID_ACTIONS$1 = ["list", "get", "create", "update"];
|
|
1021
|
+
async function handleTime(action, args, ctx) {
|
|
1022
|
+
const { api, formatOptions, filter, page, perPage } = ctx;
|
|
1023
|
+
const { id, person_id, service_id, task_id, time, date, note } = args;
|
|
1024
|
+
if (action === "get") {
|
|
1025
|
+
if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
|
|
1026
|
+
const result = await api.getTimeEntry(id);
|
|
1027
|
+
return jsonResult(formatTimeEntry(result.data, formatOptions));
|
|
1028
|
+
}
|
|
1029
|
+
if (action === "create") {
|
|
1030
|
+
if (!person_id || !service_id || !time || !date) {
|
|
1031
|
+
return inputErrorResult(
|
|
1032
|
+
ErrorMessages.missingRequiredFields("time entry", [
|
|
1033
|
+
"person_id",
|
|
1034
|
+
"service_id",
|
|
1035
|
+
"time",
|
|
1036
|
+
"date"
|
|
1037
|
+
])
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
const result = await api.createTimeEntry({
|
|
1041
|
+
person_id,
|
|
1042
|
+
service_id,
|
|
1043
|
+
time,
|
|
1044
|
+
date,
|
|
1045
|
+
note,
|
|
1046
|
+
task_id
|
|
1047
|
+
});
|
|
1048
|
+
return jsonResult({ success: true, ...formatTimeEntry(result.data, formatOptions) });
|
|
1049
|
+
}
|
|
1050
|
+
if (action === "update") {
|
|
1051
|
+
if (!id) return inputErrorResult(ErrorMessages.missingId("update"));
|
|
1052
|
+
const updateData = {};
|
|
1053
|
+
if (time !== void 0) updateData.time = time;
|
|
1054
|
+
if (date !== void 0) updateData.date = date;
|
|
1055
|
+
if (note !== void 0) updateData.note = note;
|
|
1056
|
+
const result = await api.updateTimeEntry(id, updateData);
|
|
1057
|
+
return jsonResult({ success: true, ...formatTimeEntry(result.data, formatOptions) });
|
|
1058
|
+
}
|
|
1059
|
+
if (action === "list") {
|
|
1060
|
+
const result = await api.getTimeEntries({ filter, page, perPage });
|
|
1061
|
+
return jsonResult(formatListResponse(result.data, formatTimeEntry, result.meta, formatOptions));
|
|
1062
|
+
}
|
|
1063
|
+
return inputErrorResult(ErrorMessages.invalidAction(action, "time", VALID_ACTIONS$1));
|
|
1064
|
+
}
|
|
1065
|
+
const VALID_ACTIONS = ["list", "get", "start", "stop"];
|
|
1066
|
+
async function handleTimers(action, args, ctx) {
|
|
1067
|
+
const { api, formatOptions, filter, page, perPage, include } = ctx;
|
|
1068
|
+
const { id, service_id, time_entry_id } = args;
|
|
1069
|
+
if (action === "get") {
|
|
1070
|
+
if (!id) return inputErrorResult(ErrorMessages.missingId("get"));
|
|
1071
|
+
const result = await api.getTimer(id, { include });
|
|
1072
|
+
return jsonResult(formatTimer(result.data));
|
|
1073
|
+
}
|
|
1074
|
+
if (action === "start" || action === "create") {
|
|
1075
|
+
if (!service_id && !time_entry_id) {
|
|
1076
|
+
return inputErrorResult(ErrorMessages.missingServiceForTimer());
|
|
1077
|
+
}
|
|
1078
|
+
const result = await api.startTimer({ service_id, time_entry_id });
|
|
1079
|
+
return jsonResult({ success: true, ...formatTimer(result.data) });
|
|
1080
|
+
}
|
|
1081
|
+
if (action === "stop") {
|
|
1082
|
+
if (!id) return inputErrorResult(ErrorMessages.missingId("stop"));
|
|
1083
|
+
const result = await api.stopTimer(id);
|
|
1084
|
+
return jsonResult({ success: true, ...formatTimer(result.data) });
|
|
1085
|
+
}
|
|
1086
|
+
if (action === "list") {
|
|
1087
|
+
const result = await api.getTimers({ filter, page, perPage, include });
|
|
1088
|
+
return jsonResult(formatListResponse(result.data, formatTimer, result.meta, formatOptions));
|
|
1089
|
+
}
|
|
1090
|
+
return inputErrorResult(ErrorMessages.invalidAction(action, "timers", VALID_ACTIONS));
|
|
1091
|
+
}
|
|
1092
|
+
const VALID_RESOURCES = [
|
|
1093
|
+
"projects",
|
|
1094
|
+
"time",
|
|
1095
|
+
"tasks",
|
|
1096
|
+
"services",
|
|
1097
|
+
"people",
|
|
1098
|
+
"companies",
|
|
1099
|
+
"comments",
|
|
1100
|
+
"timers",
|
|
1101
|
+
"deals",
|
|
1102
|
+
"bookings",
|
|
1103
|
+
"reports"
|
|
1104
|
+
];
|
|
1105
|
+
const DEFAULT_PER_PAGE = 20;
|
|
1106
|
+
async function executeToolWithCredentials(name, args, credentials) {
|
|
1107
|
+
const api = new ProductiveApi({
|
|
1108
|
+
token: credentials.apiToken,
|
|
1109
|
+
"org-id": credentials.organizationId,
|
|
1110
|
+
"user-id": credentials.userId
|
|
1111
|
+
});
|
|
1112
|
+
if (name !== "productive") {
|
|
1113
|
+
return errorResult(`Unknown tool: ${name}`);
|
|
1114
|
+
}
|
|
1115
|
+
const { resource, action, filter, page, per_page, compact, include, query, ...restArgs } = args;
|
|
1116
|
+
const isCompact = compact ?? action !== "get";
|
|
1117
|
+
const formatOptions = { compact: isCompact };
|
|
1118
|
+
let stringFilter = toStringFilter(filter);
|
|
1119
|
+
const perPage = per_page ?? DEFAULT_PER_PAGE;
|
|
1120
|
+
if (query) {
|
|
1121
|
+
stringFilter = { ...stringFilter, query };
|
|
1122
|
+
}
|
|
1123
|
+
const ctx = {
|
|
1124
|
+
api,
|
|
1125
|
+
formatOptions,
|
|
1126
|
+
filter: stringFilter,
|
|
1127
|
+
page,
|
|
1128
|
+
perPage,
|
|
1129
|
+
include
|
|
1130
|
+
};
|
|
1131
|
+
try {
|
|
1132
|
+
if (action === "help") {
|
|
1133
|
+
return resource ? handleHelp(resource) : handleHelpOverview();
|
|
1134
|
+
}
|
|
1135
|
+
switch (resource) {
|
|
1136
|
+
case "projects":
|
|
1137
|
+
return await handleProjects(action, restArgs, ctx);
|
|
1138
|
+
case "time":
|
|
1139
|
+
return await handleTime(action, restArgs, ctx);
|
|
1140
|
+
case "tasks":
|
|
1141
|
+
return await handleTasks(action, restArgs, ctx);
|
|
1142
|
+
case "services":
|
|
1143
|
+
return await handleServices(action, restArgs, ctx);
|
|
1144
|
+
case "people":
|
|
1145
|
+
return await handlePeople(action, restArgs, ctx, credentials);
|
|
1146
|
+
case "companies":
|
|
1147
|
+
return await handleCompanies(action, restArgs, ctx);
|
|
1148
|
+
case "comments":
|
|
1149
|
+
return await handleComments(action, restArgs, ctx);
|
|
1150
|
+
case "timers":
|
|
1151
|
+
return await handleTimers(action, restArgs, ctx);
|
|
1152
|
+
case "deals":
|
|
1153
|
+
return await handleDeals(action, restArgs, ctx);
|
|
1154
|
+
case "bookings":
|
|
1155
|
+
return await handleBookings(action, restArgs, ctx);
|
|
1156
|
+
case "reports":
|
|
1157
|
+
return await handleReports(action, restArgs, ctx);
|
|
1158
|
+
default:
|
|
1159
|
+
return inputErrorResult(ErrorMessages.unknownResource(resource, VALID_RESOURCES));
|
|
1160
|
+
}
|
|
1161
|
+
} catch (error) {
|
|
1162
|
+
if (isUserInputError(error)) {
|
|
1163
|
+
return formatError(error);
|
|
1164
|
+
}
|
|
1165
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1166
|
+
const statusMatch = message.match(/(\d{3})/);
|
|
1167
|
+
if (statusMatch) {
|
|
1168
|
+
const statusCode = Number.parseInt(statusMatch[1], 10);
|
|
1169
|
+
return inputErrorResult(ErrorMessages.apiError(statusCode, message));
|
|
1170
|
+
}
|
|
1171
|
+
return errorResult(message);
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
export {
|
|
1175
|
+
executeToolWithCredentials as e
|
|
1176
|
+
};
|
|
1177
|
+
//# sourceMappingURL=index-D743zTS1.js.map
|