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