projects-plus-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/index.mjs ADDED
@@ -0,0 +1,759 @@
1
+ // src/errors/index.ts
2
+ var ApiError = class _ApiError extends Error {
3
+ constructor(message, status, code, details) {
4
+ super(message);
5
+ this.status = status;
6
+ this.code = code;
7
+ this.details = details;
8
+ this.name = "ApiError";
9
+ Object.setPrototypeOf(this, _ApiError.prototype);
10
+ }
11
+ };
12
+ var AuthenticationError = class _AuthenticationError extends ApiError {
13
+ constructor(message = "Invalid or missing API key") {
14
+ super(message, 401, "AUTHENTICATION_ERROR");
15
+ this.name = "AuthenticationError";
16
+ Object.setPrototypeOf(this, _AuthenticationError.prototype);
17
+ }
18
+ };
19
+ var RateLimitError = class _RateLimitError extends ApiError {
20
+ constructor(message = "Rate limit exceeded", retryAfter) {
21
+ super(message, 429, "RATE_LIMIT_ERROR");
22
+ this.retryAfter = retryAfter;
23
+ this.name = "RateLimitError";
24
+ Object.setPrototypeOf(this, _RateLimitError.prototype);
25
+ }
26
+ };
27
+ var NotFoundError = class _NotFoundError extends ApiError {
28
+ constructor(message = "Resource not found") {
29
+ super(message, 404, "NOT_FOUND_ERROR");
30
+ this.name = "NotFoundError";
31
+ Object.setPrototypeOf(this, _NotFoundError.prototype);
32
+ }
33
+ };
34
+ var ValidationError = class _ValidationError extends ApiError {
35
+ constructor(message = "Invalid request", details) {
36
+ super(message, 400, "VALIDATION_ERROR", details);
37
+ this.name = "ValidationError";
38
+ Object.setPrototypeOf(this, _ValidationError.prototype);
39
+ }
40
+ };
41
+ var ServerError = class _ServerError extends ApiError {
42
+ constructor(message = "Server error") {
43
+ super(message, 500, "SERVER_ERROR");
44
+ this.name = "ServerError";
45
+ Object.setPrototypeOf(this, _ServerError.prototype);
46
+ }
47
+ };
48
+ var TimeoutError = class _TimeoutError extends ApiError {
49
+ constructor(message = "Request timed out") {
50
+ super(message, 408, "TIMEOUT_ERROR");
51
+ this.name = "TimeoutError";
52
+ Object.setPrototypeOf(this, _TimeoutError.prototype);
53
+ }
54
+ };
55
+ var NetworkError = class _NetworkError extends ApiError {
56
+ constructor(message = "Network error") {
57
+ super(message, 0, "NETWORK_ERROR");
58
+ this.name = "NetworkError";
59
+ Object.setPrototypeOf(this, _NetworkError.prototype);
60
+ }
61
+ };
62
+ function createApiError(status, message, details) {
63
+ switch (status) {
64
+ case 401:
65
+ return new AuthenticationError(message);
66
+ case 404:
67
+ return new NotFoundError(message);
68
+ case 429:
69
+ return new RateLimitError(message);
70
+ case 400:
71
+ return new ValidationError(message, details);
72
+ case 408:
73
+ return new TimeoutError(message);
74
+ default:
75
+ if (status >= 500) {
76
+ return new ServerError(message);
77
+ }
78
+ return new ApiError(message, status, void 0, details);
79
+ }
80
+ }
81
+
82
+ // src/utils/http.ts
83
+ var HttpClient = class {
84
+ config;
85
+ constructor(config) {
86
+ this.config = config;
87
+ }
88
+ /**
89
+ * Make an HTTP request with automatic retries
90
+ */
91
+ async request(path, options = {}) {
92
+ const { method = "GET", body, params, headers = {}, skipRetry = false } = options;
93
+ const url = this.buildUrl(path, params);
94
+ const requestInit = {
95
+ method,
96
+ headers: {
97
+ "Content-Type": "application/json",
98
+ "X-API-Key": this.config.apiKey,
99
+ ...headers
100
+ }
101
+ };
102
+ if (body !== void 0) {
103
+ requestInit.body = JSON.stringify(body);
104
+ }
105
+ const maxAttempts = skipRetry ? 1 : this.config.retries;
106
+ let lastError = null;
107
+ for (let attempt = 1; attempt <= maxAttempts; attempt++) {
108
+ try {
109
+ const response = await this.fetchWithTimeout(url, requestInit);
110
+ return await this.handleResponse(response);
111
+ } catch (error) {
112
+ lastError = error;
113
+ if (error instanceof AuthenticationError || error instanceof ApiError && error.status < 500) {
114
+ throw error;
115
+ }
116
+ if (attempt < maxAttempts) {
117
+ const delay = this.calculateRetryDelay(attempt, error);
118
+ await this.sleep(delay);
119
+ }
120
+ }
121
+ }
122
+ throw lastError ?? new NetworkError("Request failed after retries");
123
+ }
124
+ /**
125
+ * Build URL with query parameters
126
+ */
127
+ buildUrl(path, params) {
128
+ const url = new URL(path, this.config.baseUrl);
129
+ if (params) {
130
+ Object.entries(params).forEach(([key, value]) => {
131
+ if (value !== void 0) {
132
+ url.searchParams.append(key, String(value));
133
+ }
134
+ });
135
+ }
136
+ return url.toString();
137
+ }
138
+ /**
139
+ * Fetch with timeout
140
+ */
141
+ async fetchWithTimeout(url, init) {
142
+ const controller = new AbortController();
143
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
144
+ try {
145
+ const response = await fetch(url, {
146
+ ...init,
147
+ signal: controller.signal
148
+ });
149
+ return response;
150
+ } catch (error) {
151
+ if (error instanceof Error && error.name === "AbortError") {
152
+ throw new TimeoutError(`Request timed out after ${this.config.timeout}ms`);
153
+ }
154
+ throw new NetworkError(
155
+ error instanceof Error ? error.message : "Network error"
156
+ );
157
+ } finally {
158
+ clearTimeout(timeoutId);
159
+ }
160
+ }
161
+ /**
162
+ * Handle response and parse JSON
163
+ */
164
+ async handleResponse(response) {
165
+ let data;
166
+ const contentType = response.headers.get("content-type");
167
+ if (contentType?.includes("application/json")) {
168
+ try {
169
+ data = await response.json();
170
+ } catch {
171
+ data = null;
172
+ }
173
+ } else {
174
+ data = await response.text();
175
+ }
176
+ if (!response.ok) {
177
+ const message = this.extractErrorMessage(data) ?? response.statusText;
178
+ if (response.status === 429) {
179
+ const retryAfter = parseInt(
180
+ response.headers.get("Retry-After") ?? "60",
181
+ 10
182
+ );
183
+ throw new RateLimitError(message, retryAfter);
184
+ }
185
+ throw createApiError(response.status, message, data);
186
+ }
187
+ return data;
188
+ }
189
+ /**
190
+ * Extract error message from response data
191
+ */
192
+ extractErrorMessage(data) {
193
+ if (typeof data === "string") return data;
194
+ if (typeof data === "object" && data !== null) {
195
+ const obj = data;
196
+ if (typeof obj["error"] === "string") return obj["error"];
197
+ if (typeof obj["message"] === "string") return obj["message"];
198
+ }
199
+ return void 0;
200
+ }
201
+ /**
202
+ * Calculate retry delay with exponential backoff
203
+ */
204
+ calculateRetryDelay(attempt, error) {
205
+ if (error instanceof RateLimitError && error.retryAfter) {
206
+ return error.retryAfter * 1e3;
207
+ }
208
+ return this.config.retryDelay * Math.pow(2, attempt - 1);
209
+ }
210
+ /**
211
+ * Sleep for the specified duration
212
+ */
213
+ sleep(ms) {
214
+ return new Promise((resolve) => setTimeout(resolve, ms));
215
+ }
216
+ };
217
+
218
+ // src/resources/context.ts
219
+ var ContextResource = class {
220
+ constructor(http) {
221
+ this.http = http;
222
+ }
223
+ /**
224
+ * Get theme context including all projects and tasks
225
+ *
226
+ * @example
227
+ * ```ts
228
+ * const context = await pp.context.get();
229
+ * console.log(context.theme.name);
230
+ * console.log(context.projects);
231
+ * ```
232
+ */
233
+ async get() {
234
+ const response = await this.http.request("/context");
235
+ return response.context;
236
+ }
237
+ };
238
+
239
+ // src/resources/sessions.ts
240
+ var SessionsResource = class {
241
+ constructor(http) {
242
+ this.http = http;
243
+ }
244
+ /**
245
+ * Start a new AI development session
246
+ *
247
+ * @param params - Session parameters
248
+ * @returns The created session
249
+ *
250
+ * @example
251
+ * ```ts
252
+ * const session = await pp.sessions.start({ ai: 'claude' });
253
+ * console.log(session.id);
254
+ * ```
255
+ */
256
+ async start(params) {
257
+ const response = await this.http.request(
258
+ "/sessions/start",
259
+ {
260
+ method: "POST",
261
+ body: params
262
+ }
263
+ );
264
+ return response.session;
265
+ }
266
+ /**
267
+ * End the current AI development session
268
+ *
269
+ * @param params - Optional handoff note
270
+ * @returns The ended session
271
+ *
272
+ * @example
273
+ * ```ts
274
+ * const session = await pp.sessions.end({
275
+ * handoffNote: 'Completed tasks X, Y, Z. Remaining: A, B.'
276
+ * });
277
+ * ```
278
+ */
279
+ async end(params) {
280
+ const response = await this.http.request(
281
+ "/sessions/end",
282
+ {
283
+ method: "POST",
284
+ body: params ?? {}
285
+ }
286
+ );
287
+ return response.session;
288
+ }
289
+ /**
290
+ * Get the current active session if any
291
+ *
292
+ * @returns The current session or null
293
+ */
294
+ async current() {
295
+ try {
296
+ const response = await this.http.request(
297
+ "/sessions/current"
298
+ );
299
+ return response.session;
300
+ } catch {
301
+ return null;
302
+ }
303
+ }
304
+ };
305
+
306
+ // src/resources/tasks.ts
307
+ var TasksResource = class {
308
+ constructor(http) {
309
+ this.http = http;
310
+ }
311
+ /**
312
+ * List tasks with optional filters
313
+ *
314
+ * @param params - Filter parameters
315
+ * @returns List of tasks
316
+ *
317
+ * @example
318
+ * ```ts
319
+ * // Get all active tasks
320
+ * const tasks = await pp.tasks.list({ status: 'active' });
321
+ *
322
+ * // Get tasks for a specific project
323
+ * const projectTasks = await pp.tasks.list({
324
+ * projectId: 'proj-123',
325
+ * status: 'active'
326
+ * });
327
+ * ```
328
+ */
329
+ async list(params) {
330
+ const response = await this.http.request("/tasks", {
331
+ params
332
+ });
333
+ return response.tasks;
334
+ }
335
+ /**
336
+ * Get a specific task
337
+ *
338
+ * @param taskId - Task ID
339
+ * @param params - Additional parameters (projectId required)
340
+ * @returns The task
341
+ *
342
+ * @example
343
+ * ```ts
344
+ * const task = await pp.tasks.get('task-123', {
345
+ * projectId: 'proj-123'
346
+ * });
347
+ * ```
348
+ */
349
+ async get(taskId, params) {
350
+ const response = await this.http.request(
351
+ `/tasks/${taskId}`,
352
+ {
353
+ params: {
354
+ projectId: params.projectId
355
+ }
356
+ }
357
+ );
358
+ return response.task;
359
+ }
360
+ /**
361
+ * Mark a task as completed (archived)
362
+ *
363
+ * @param taskId - Task ID
364
+ * @param params - Additional parameters (projectId required)
365
+ * @returns The updated task
366
+ *
367
+ * @example
368
+ * ```ts
369
+ * await pp.tasks.complete('task-123', {
370
+ * projectId: 'proj-123'
371
+ * });
372
+ * ```
373
+ */
374
+ async complete(taskId, params) {
375
+ const response = await this.http.request(
376
+ `/tasks/${taskId}/complete`,
377
+ {
378
+ method: "POST",
379
+ body: params
380
+ }
381
+ );
382
+ return response.task;
383
+ }
384
+ /**
385
+ * Update task status
386
+ *
387
+ * @param taskId - Task ID
388
+ * @param params - Status and projectId
389
+ * @returns The updated task
390
+ *
391
+ * @example
392
+ * ```ts
393
+ * await pp.tasks.updateStatus('task-123', {
394
+ * projectId: 'proj-123',
395
+ * status: 'paused'
396
+ * });
397
+ * ```
398
+ */
399
+ async updateStatus(taskId, params) {
400
+ const response = await this.http.request(
401
+ `/tasks/${taskId}/status`,
402
+ {
403
+ method: "PATCH",
404
+ body: params
405
+ }
406
+ );
407
+ return response.task;
408
+ }
409
+ };
410
+
411
+ // src/resources/projects.ts
412
+ var ProjectsResource = class {
413
+ constructor(http) {
414
+ this.http = http;
415
+ }
416
+ /**
417
+ * List all projects
418
+ *
419
+ * @param params - Filter parameters
420
+ * @returns List of projects
421
+ *
422
+ * @example
423
+ * ```ts
424
+ * const projects = await pp.projects.list();
425
+ *
426
+ * // Filter by status
427
+ * const activeProjects = await pp.projects.list({ status: 'active' });
428
+ * ```
429
+ */
430
+ async list(params) {
431
+ const response = await this.http.request("/projects", {
432
+ params
433
+ });
434
+ return response.projects;
435
+ }
436
+ /**
437
+ * Get a specific project
438
+ *
439
+ * @param projectId - Project ID
440
+ * @returns The project
441
+ *
442
+ * @example
443
+ * ```ts
444
+ * const project = await pp.projects.get('proj-123');
445
+ * ```
446
+ */
447
+ async get(projectId) {
448
+ const response = await this.http.request(
449
+ `/projects/${projectId}`
450
+ );
451
+ return response.project;
452
+ }
453
+ /**
454
+ * Get tasks for a specific project
455
+ *
456
+ * @param projectId - Project ID
457
+ * @param params - Filter parameters
458
+ * @returns List of tasks
459
+ *
460
+ * @example
461
+ * ```ts
462
+ * const tasks = await pp.projects.getTasks('proj-123');
463
+ *
464
+ * // Filter by status
465
+ * const activeTasks = await pp.projects.getTasks('proj-123', {
466
+ * status: 'active'
467
+ * });
468
+ * ```
469
+ */
470
+ async getTasks(projectId, params) {
471
+ const response = await this.http.request(
472
+ `/projects/${projectId}/tasks`,
473
+ {
474
+ params
475
+ }
476
+ );
477
+ return response.tasks;
478
+ }
479
+ };
480
+
481
+ // src/resources/comments.ts
482
+ var CommentsResource = class {
483
+ constructor(http) {
484
+ this.http = http;
485
+ }
486
+ /**
487
+ * List comments for a task
488
+ *
489
+ * @param params - Task identifiers
490
+ * @returns List of comments
491
+ *
492
+ * @example
493
+ * ```ts
494
+ * const comments = await pp.comments.list({
495
+ * projectId: 'proj-123',
496
+ * taskId: 'task-123'
497
+ * });
498
+ * ```
499
+ */
500
+ async list(params) {
501
+ const response = await this.http.request(
502
+ "/comments",
503
+ {
504
+ params: {
505
+ projectId: params.projectId,
506
+ taskId: params.taskId
507
+ }
508
+ }
509
+ );
510
+ return response.comments;
511
+ }
512
+ /**
513
+ * Create a new comment on a task
514
+ *
515
+ * @param params - Comment data
516
+ * @returns The created comment
517
+ *
518
+ * @example
519
+ * ```ts
520
+ * await pp.comments.create({
521
+ * projectId: 'proj-123',
522
+ * taskId: 'task-123',
523
+ * content: '## 実装完了\n\n### 変更内容\n- file1.ts\n- file2.ts',
524
+ * aiName: 'Claude'
525
+ * });
526
+ * ```
527
+ */
528
+ async create(params) {
529
+ const response = await this.http.request(
530
+ "/comments",
531
+ {
532
+ method: "POST",
533
+ body: params
534
+ }
535
+ );
536
+ return response.comment;
537
+ }
538
+ /**
539
+ * Delete a comment
540
+ *
541
+ * @param commentId - Comment ID
542
+ * @param params - Task identifiers
543
+ *
544
+ * @example
545
+ * ```ts
546
+ * await pp.comments.delete('comment-123', {
547
+ * projectId: 'proj-123',
548
+ * taskId: 'task-123'
549
+ * });
550
+ * ```
551
+ */
552
+ async delete(commentId, params) {
553
+ await this.http.request(`/comments/${commentId}`, {
554
+ method: "DELETE",
555
+ params: {
556
+ projectId: params.projectId,
557
+ taskId: params.taskId
558
+ }
559
+ });
560
+ }
561
+ };
562
+
563
+ // src/workflow.ts
564
+ var Workflow = class {
565
+ client;
566
+ ai;
567
+ currentSession = null;
568
+ currentContext = null;
569
+ constructor(client, ai) {
570
+ this.client = client;
571
+ this.ai = ai;
572
+ }
573
+ /**
574
+ * Start session and get context in one call
575
+ *
576
+ * @returns Session and context
577
+ */
578
+ async start() {
579
+ const session = await this.client.sessions.start({ ai: this.ai });
580
+ const context = await this.client.context.get();
581
+ this.currentSession = session;
582
+ this.currentContext = context;
583
+ return { session, context };
584
+ }
585
+ /**
586
+ * Report progress with auto-formatted markdown comment
587
+ *
588
+ * @param taskId - Task ID
589
+ * @param params - Progress details
590
+ */
591
+ async reportProgress(taskId, params) {
592
+ const content = this.formatProgressComment(params);
593
+ await this.client.comments.create({
594
+ projectId: params.projectId,
595
+ taskId,
596
+ content,
597
+ aiName: this.ai.charAt(0).toUpperCase() + this.ai.slice(1)
598
+ // Capitalize
599
+ });
600
+ }
601
+ /**
602
+ * Complete a task and refresh context
603
+ *
604
+ * @param taskId - Task ID
605
+ * @param params - Project ID
606
+ * @returns Updated context
607
+ */
608
+ async completeAndRefresh(taskId, params) {
609
+ await this.client.tasks.complete(taskId, params);
610
+ const context = await this.client.context.get();
611
+ this.currentContext = context;
612
+ return context;
613
+ }
614
+ /**
615
+ * Refresh context without completing a task
616
+ *
617
+ * @returns Updated context
618
+ */
619
+ async refresh() {
620
+ const context = await this.client.context.get();
621
+ this.currentContext = context;
622
+ return context;
623
+ }
624
+ /**
625
+ * End the session with optional handoff note
626
+ *
627
+ * @param handoffNote - Summary for next session
628
+ * @returns Ended session
629
+ */
630
+ async end(handoffNote) {
631
+ const session = await this.client.sessions.end({ handoffNote });
632
+ this.currentSession = session;
633
+ return session;
634
+ }
635
+ /**
636
+ * Get the current session
637
+ */
638
+ getSession() {
639
+ return this.currentSession;
640
+ }
641
+ /**
642
+ * Get the current context
643
+ */
644
+ getContext() {
645
+ return this.currentContext;
646
+ }
647
+ /**
648
+ * Format a progress comment from params
649
+ */
650
+ formatProgressComment(params) {
651
+ const lines = ["## \u5B9F\u88C5\u5B8C\u4E86", ""];
652
+ if (params.changes && params.changes.length > 0) {
653
+ lines.push("### \u5909\u66F4\u5185\u5BB9");
654
+ for (const change of params.changes) {
655
+ lines.push(`- \`${change}\``);
656
+ }
657
+ lines.push("");
658
+ }
659
+ if (params.testResults) {
660
+ lines.push("### \u30C6\u30B9\u30C8\u7D50\u679C");
661
+ const { passed, failed, skipped } = params.testResults;
662
+ if (passed !== void 0) {
663
+ lines.push(`- \u2705 \u6210\u529F: ${passed}`);
664
+ }
665
+ if (failed !== void 0) {
666
+ lines.push(`- \u274C \u5931\u6557: ${failed}`);
667
+ }
668
+ if (skipped !== void 0) {
669
+ lines.push(`- \u23ED\uFE0F \u30B9\u30AD\u30C3\u30D7: ${skipped}`);
670
+ }
671
+ lines.push("");
672
+ }
673
+ if (params.note) {
674
+ lines.push("### \u5099\u8003");
675
+ lines.push(params.note);
676
+ lines.push("");
677
+ }
678
+ lines.push("---");
679
+ lines.push(`*\u{1F916} Generated by ${this.ai.charAt(0).toUpperCase() + this.ai.slice(1)} via Projects Plus SDK*`);
680
+ return lines.join("\n");
681
+ }
682
+ };
683
+
684
+ // src/client.ts
685
+ var DEFAULT_BASE_URL = "https://us-central1-projects-plus-app.cloudfunctions.net/api/v1";
686
+ var DEFAULT_TIMEOUT = 3e4;
687
+ var DEFAULT_RETRIES = 3;
688
+ var DEFAULT_RETRY_DELAY = 1e3;
689
+ var ProjectsPlus = class {
690
+ http;
691
+ /** Context resource for getting theme overview */
692
+ context;
693
+ /** Sessions resource for managing AI development sessions */
694
+ sessions;
695
+ /** Tasks resource for managing tasks */
696
+ tasks;
697
+ /** Projects resource for managing projects */
698
+ projects;
699
+ /** Comments resource for managing task comments */
700
+ comments;
701
+ /**
702
+ * Create a new Projects Plus client
703
+ *
704
+ * @param config - Client configuration
705
+ */
706
+ constructor(config) {
707
+ if (!config.apiKey) {
708
+ throw new Error("API key is required");
709
+ }
710
+ this.http = new HttpClient({
711
+ apiKey: config.apiKey,
712
+ baseUrl: config.baseUrl ?? DEFAULT_BASE_URL,
713
+ timeout: config.timeout ?? DEFAULT_TIMEOUT,
714
+ retries: config.retries ?? DEFAULT_RETRIES,
715
+ retryDelay: config.retryDelay ?? DEFAULT_RETRY_DELAY
716
+ });
717
+ this.context = new ContextResource(this.http);
718
+ this.sessions = new SessionsResource(this.http);
719
+ this.tasks = new TasksResource(this.http);
720
+ this.projects = new ProjectsResource(this.http);
721
+ this.comments = new CommentsResource(this.http);
722
+ }
723
+ /**
724
+ * Create a workflow helper for common development patterns
725
+ *
726
+ * @param params - Workflow parameters
727
+ * @returns Workflow instance
728
+ *
729
+ * @example
730
+ * ```ts
731
+ * const workflow = pp.workflow({ ai: 'claude' });
732
+ *
733
+ * // Start session and get context in one call
734
+ * const { session, context } = await workflow.start();
735
+ *
736
+ * // Report progress with auto-formatted markdown
737
+ * await workflow.reportProgress('task-id', {
738
+ * projectId: 'proj-id',
739
+ * changes: ['file1.ts', 'file2.ts'],
740
+ * testResults: { passed: 10, failed: 0 },
741
+ * });
742
+ *
743
+ * // Complete task and refresh context
744
+ * const newContext = await workflow.completeAndRefresh('task-id', {
745
+ * projectId: 'proj-id'
746
+ * });
747
+ *
748
+ * // End session
749
+ * await workflow.end('Summary...');
750
+ * ```
751
+ */
752
+ workflow(params) {
753
+ return new Workflow(this, params.ai);
754
+ }
755
+ };
756
+
757
+ export { ApiError, AuthenticationError, NetworkError, NotFoundError, ProjectsPlus, RateLimitError, ServerError, TimeoutError, ValidationError, Workflow, createApiError };
758
+ //# sourceMappingURL=index.mjs.map
759
+ //# sourceMappingURL=index.mjs.map