@clonecommand/cloud 0.0.2 → 0.0.4

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/README.md CHANGED
@@ -4,6 +4,13 @@ The official SDK for CloneCommand managed services.
4
4
 
5
5
  This library simplifies integration with ephemeral preview environments and provides access to CloneCommand Cloud services like Login Proxy, Storage, and Email.
6
6
 
7
+ ### 🤖 Agent Ready
8
+ This package is designed to be easily consumed by AI agents and LLMs. It features:
9
+ - **Full TypeScript Definitions**: All methods, arguments, and return types are strictly typed.
10
+ - **Rich JSDoc**: Comprehensive comments and examples to help agents understand the intent behind every function.
11
+ - **Clean API Surface**: A logical, singleton-based structure that is easy to navigate.
12
+
13
+
7
14
  ## Documentation
8
15
 
9
16
  Full documentation is available in the [`docs/`](./docs/README.md) directory.
@@ -1,69 +1,76 @@
1
- import { CloneCommandCloud } from '../index.js';
2
- export interface EmailConfig {
3
- id: string;
4
- projectId: string;
5
- domainId: string;
6
- isEnabled: boolean;
7
- sesIdentityArn?: string;
8
- sesVerificationStatus?: string;
9
- dkimTokens?: string;
10
- }
11
- export interface InboundEmail {
12
- id: string;
13
- projectId: string;
14
- sender: string;
15
- recipient: string;
16
- subject: string;
17
- snippet: string;
18
- bucketPath: string;
19
- receivedAt: string;
20
- isRead: boolean;
21
- hasAttachments: boolean;
22
- rawSize: number;
23
- threadId?: string;
24
- messageId?: string;
25
- inReplyTo?: string;
26
- references?: string;
27
- }
28
- export interface SendEmailOptions {
29
- from?: string;
30
- to: string | string[];
31
- subject: string;
32
- html?: string;
33
- text?: string;
34
- }
1
+ import { CloneCommandCloud } from "..";
2
+ import { EmailConfig, InboundEmail, SendEmailOptions } from "./types";
3
+ /**
4
+ * Client for interacting with CloneCommand Email services.
5
+ */
35
6
  export declare class EmailClient {
36
7
  private ccc;
37
8
  constructor(ccc: CloneCommandCloud);
38
9
  /**
39
- * Get email configuration for the project.
10
+ * Retrieves the email configuration for the current project.
11
+ *
12
+ * @returns A promise resolving to the EmailConfig or null if not found.
13
+ * @throws Error if projectId is missing or the API returns an error.
40
14
  */
41
- getConfig(projectId?: string): Promise<EmailConfig | null>;
15
+ getConfig(): Promise<EmailConfig | null>;
42
16
  /**
43
- * Enable email service for a domain.
17
+ * Enables the email service for a specific domain.
18
+ *
19
+ * @param domainId - The ID of the domain to enable email for.
20
+ * @returns A promise resolving to the updated EmailConfig.
21
+ * @throws Error if activation fails.
44
22
  */
45
- enable(domainId: string, projectId?: string): Promise<EmailConfig>;
23
+ enable(domainId: string): Promise<EmailConfig>;
46
24
  /**
47
- * Send an email.
25
+ * Sends an email message.
26
+ *
27
+ * @param options - The email content and recipient details.
28
+ * @returns A promise resolving to the Message-ID of the sent email.
29
+ * @throws Error if sending fails.
30
+ *
31
+ * @example
32
+ * await ccc.email.send({
33
+ * to: 'user@example.com',
34
+ * subject: 'Hello',
35
+ * text: 'This is a test'
36
+ * });
48
37
  */
49
- send(options: SendEmailOptions, projectId?: string): Promise<string>;
38
+ send(options: SendEmailOptions): Promise<string>;
50
39
  /**
51
- * Get inbound emails.
40
+ * Retrieves a list of inbound emails received by the project.
41
+ *
42
+ * @param options - Filtering and pagination options (limit, before).
43
+ * @returns A promise resolving to an array of InboundEmail previews.
52
44
  */
53
45
  getInbound(options?: {
54
46
  limit?: number;
55
47
  before?: Date;
56
- }, projectId?: string): Promise<InboundEmail[]>;
48
+ }): Promise<InboundEmail[]>;
57
49
  /**
58
- * Get a specific email by ID.
50
+ * Retrieves the full details of a specific inbound email, including the body.
51
+ *
52
+ * @param emailId - The unique ID of the email.
53
+ * @returns A promise resolving to the full InboundEmail object, or null if not found.
59
54
  */
60
55
  get(emailId: string): Promise<InboundEmail | null>;
61
56
  /**
62
- * Mark an email as read.
57
+ * Marks an inbound email as read.
58
+ *
59
+ * @param emailId - The unique ID of the email.
60
+ * @returns A promise resolving to true if successful.
63
61
  */
64
62
  markAsRead(emailId: string): Promise<boolean>;
65
63
  /**
66
- * Delete an email.
64
+ * Deletes an inbound email permanently.
65
+ *
66
+ * @param emailId - The unique ID of the email.
67
+ * @returns A promise resolving to true if successful.
67
68
  */
68
69
  delete(emailId: string): Promise<boolean>;
70
+ /**
71
+ * Refreshes the email verification status for the current project.
72
+ *
73
+ * @returns A promise resolving to the status string.
74
+ */
75
+ refreshEmailVerificationStatus(): Promise<string>;
69
76
  }
@@ -1,15 +1,21 @@
1
+ /**
2
+ * Client for interacting with CloneCommand Email services.
3
+ */
1
4
  export class EmailClient {
2
5
  ccc;
3
6
  constructor(ccc) {
4
7
  this.ccc = ccc;
5
8
  }
6
9
  /**
7
- * Get email configuration for the project.
10
+ * Retrieves the email configuration for the current project.
11
+ *
12
+ * @returns A promise resolving to the EmailConfig or null if not found.
13
+ * @throws Error if projectId is missing or the API returns an error.
8
14
  */
9
- async getConfig(projectId) {
10
- const pid = projectId || this.ccc.projectId;
15
+ async getConfig() {
16
+ const pid = this.ccc.projectId;
11
17
  if (!pid)
12
- throw new Error("projectId is required");
18
+ throw new Error("projectId could not be resolved. Ensure CC_PROJECT_TOKEN is set.");
13
19
  const query = `
14
20
  query EmailConfig($projectId: ID!) {
15
21
  emailConfig(projectId: $projectId) {
@@ -35,12 +41,16 @@ export class EmailClient {
35
41
  return result.data.emailConfig;
36
42
  }
37
43
  /**
38
- * Enable email service for a domain.
44
+ * Enables the email service for a specific domain.
45
+ *
46
+ * @param domainId - The ID of the domain to enable email for.
47
+ * @returns A promise resolving to the updated EmailConfig.
48
+ * @throws Error if activation fails.
39
49
  */
40
- async enable(domainId, projectId) {
41
- const pid = projectId || this.ccc.projectId;
50
+ async enable(domainId) {
51
+ const pid = this.ccc.projectId;
42
52
  if (!pid)
43
- throw new Error("projectId is required");
53
+ throw new Error("projectId could not be resolved. Ensure CC_PROJECT_TOKEN is set.");
44
54
  const query = `
45
55
  mutation EnableEmail($projectId: ID!, $domainId: ID!) {
46
56
  enableEmail(projectId: $projectId, domainId: $domainId) {
@@ -66,12 +76,23 @@ export class EmailClient {
66
76
  return result.data.enableEmail;
67
77
  }
68
78
  /**
69
- * Send an email.
79
+ * Sends an email message.
80
+ *
81
+ * @param options - The email content and recipient details.
82
+ * @returns A promise resolving to the Message-ID of the sent email.
83
+ * @throws Error if sending fails.
84
+ *
85
+ * @example
86
+ * await ccc.email.send({
87
+ * to: 'user@example.com',
88
+ * subject: 'Hello',
89
+ * text: 'This is a test'
90
+ * });
70
91
  */
71
- async send(options, projectId) {
72
- const pid = projectId || this.ccc.projectId;
92
+ async send(options) {
93
+ const pid = this.ccc.projectId;
73
94
  if (!pid)
74
- throw new Error("projectId is required");
95
+ throw new Error("projectId could not be resolved. Ensure CC_PROJECT_TOKEN is set.");
75
96
  const query = `
76
97
  mutation SendEmail($projectId: ID!, $email: SendEmailInput!) {
77
98
  sendEmail(projectId: $projectId, email: $email)
@@ -99,12 +120,15 @@ export class EmailClient {
99
120
  return result.data.sendEmail;
100
121
  }
101
122
  /**
102
- * Get inbound emails.
123
+ * Retrieves a list of inbound emails received by the project.
124
+ *
125
+ * @param options - Filtering and pagination options (limit, before).
126
+ * @returns A promise resolving to an array of InboundEmail previews.
103
127
  */
104
- async getInbound(options = {}, projectId) {
105
- const pid = projectId || this.ccc.projectId;
128
+ async getInbound(options = {}) {
129
+ const pid = this.ccc.projectId;
106
130
  if (!pid)
107
- throw new Error("projectId is required");
131
+ throw new Error("projectId could not be resolved. Ensure CC_PROJECT_TOKEN is set.");
108
132
  const query = `
109
133
  query InboundEmails($projectId: ID!, $limit: Int, $before: DateTime) {
110
134
  inboundEmails(projectId: $projectId, limit: $limit, before: $before) {
@@ -137,7 +161,10 @@ export class EmailClient {
137
161
  return result.data.inboundEmails;
138
162
  }
139
163
  /**
140
- * Get a specific email by ID.
164
+ * Retrieves the full details of a specific inbound email, including the body.
165
+ *
166
+ * @param emailId - The unique ID of the email.
167
+ * @returns A promise resolving to the full InboundEmail object, or null if not found.
141
168
  */
142
169
  async get(emailId) {
143
170
  const query = `
@@ -169,7 +196,10 @@ export class EmailClient {
169
196
  return result.data.email;
170
197
  }
171
198
  /**
172
- * Mark an email as read.
199
+ * Marks an inbound email as read.
200
+ *
201
+ * @param emailId - The unique ID of the email.
202
+ * @returns A promise resolving to true if successful.
173
203
  */
174
204
  async markAsRead(emailId) {
175
205
  const query = `
@@ -190,7 +220,10 @@ export class EmailClient {
190
220
  return result.data.markEmailAsRead;
191
221
  }
192
222
  /**
193
- * Delete an email.
223
+ * Deletes an inbound email permanently.
224
+ *
225
+ * @param emailId - The unique ID of the email.
226
+ * @returns A promise resolving to true if successful.
194
227
  */
195
228
  async delete(emailId) {
196
229
  const query = `
@@ -210,4 +243,25 @@ export class EmailClient {
210
243
  throw new Error(result.errors[0].message);
211
244
  return result.data.deleteEmail;
212
245
  }
246
+ /**
247
+ * Refreshes the email verification status for the current project.
248
+ *
249
+ * @returns A promise resolving to the status string.
250
+ */
251
+ async refreshEmailVerificationStatus() {
252
+ const pid = this.ccc.projectId;
253
+ if (!pid)
254
+ throw new Error("projectId could not be resolved. Ensure CC_PROJECT_TOKEN is set.");
255
+ const status = await this.ccc.request('/graphql', {
256
+ method: 'POST',
257
+ headers: { 'Content-Type': 'application/json' },
258
+ body: JSON.stringify({
259
+ query: `mutation RefreshVerification($projectId: ID!) { refreshEmailVerificationStatus(projectId: $projectId) }`,
260
+ variables: { projectId: pid }
261
+ })
262
+ });
263
+ if (status.errors)
264
+ throw new Error(status.errors[0].message);
265
+ return status.data.refreshEmailVerificationStatus;
266
+ }
213
267
  }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Configuration for the email service.
3
+ */
4
+ export interface EmailConfig {
5
+ /** Unique ID for the email configuration. */
6
+ id: string;
7
+ /** Project ID this configuration belongs to. */
8
+ projectId: string;
9
+ /** The domain ID associated with this email service. */
10
+ domainId: string;
11
+ /** Whether the email service is currently enabled. */
12
+ isEnabled: boolean;
13
+ /** The Amazon SES Identity ARN. */
14
+ sesIdentityArn?: string;
15
+ /** Current verification status in SES. */
16
+ sesVerificationStatus?: string;
17
+ /** DKIM verification tokens as a JSON string. */
18
+ dkimTokens?: string;
19
+ }
20
+ /**
21
+ * Represents an inbound email received by the system.
22
+ */
23
+ export interface InboundEmail {
24
+ /** Unique ID of the email. */
25
+ id: string;
26
+ /** Project ID this email belongs to. */
27
+ projectId: string;
28
+ /** The email address of the sender. */
29
+ sender: string;
30
+ /** The email address of the recipient. */
31
+ recipient: string;
32
+ /** The subject line of the email. */
33
+ subject: string;
34
+ /** A short snippet/preview of the email content. */
35
+ snippet: string;
36
+ /** The full HTML or text body of the email (only available in detailed view). */
37
+ body?: string;
38
+ /** The path to the raw email file in storage. */
39
+ bucketPath: string;
40
+ /** ISO timestamp when the email was received. */
41
+ receivedAt: string;
42
+ /** Whether the email has been marked as read. */
43
+ isRead: boolean;
44
+ /** Whether the email contains attachments. */
45
+ hasAttachments: boolean;
46
+ /** Total size of the raw email in bytes. */
47
+ rawSize: number;
48
+ /** Thread ID for grouping related emails. */
49
+ threadId?: string;
50
+ /** Standard Message-ID header. */
51
+ messageId?: string;
52
+ /** In-Reply-To header. */
53
+ inReplyTo?: string;
54
+ /** References header. */
55
+ references?: string;
56
+ }
57
+ /**
58
+ * Options for sending an email.
59
+ */
60
+ export interface SendEmailOptions {
61
+ /**
62
+ * The sender address. Must be a verified domain.
63
+ * Use "Name <email@domain.com>" or just "email@domain.com".
64
+ */
65
+ from?: string;
66
+ /** The recipient(s). Can be a string or an array of strings. */
67
+ to: string | string[];
68
+ /** The subject line. */
69
+ subject: string;
70
+ /** The HTML body of the email. */
71
+ html?: string;
72
+ /** The plain text body of the email. */
73
+ text?: string;
74
+ }
@@ -0,0 +1 @@
1
+ export {};
package/dist/index.d.ts CHANGED
@@ -1,79 +1,100 @@
1
- export interface CCCConfig {
2
- projectId?: string;
3
- baseUrl?: string;
4
- }
5
- export interface StorageFile {
6
- id: string;
7
- projectId: string;
8
- fileId: string;
9
- originalFilename?: string;
10
- contentType: string;
11
- sizeBytes?: number;
12
- extension?: string;
13
- publicUrl: string;
14
- createdAt: string;
15
- }
16
1
  import { EmailClient } from './email/index.js';
2
+ import { CCCConfig, StorageFile } from './types.js';
3
+ /**
4
+ * The main client for interacting with CloneCommand managed services.
5
+ * This class provides access to storage, email, and authentication features.
6
+ */
17
7
  export declare class CloneCommandCloud {
18
8
  private config;
9
+ private _email;
10
+ /**
11
+ * Initializes the SDK with configuration options.
12
+ *
13
+ * @param config - The configuration object.
14
+ */
19
15
  init(config: CCCConfig): void;
16
+ /**
17
+ * Returns the current project ID.
18
+ * Resolution order:
19
+ * 1. Manual config via `init()`
20
+ * 2. `CC_PROJECT_ID` environment variable
21
+ * 3. Extracted from the `CC_PROJECT_TOKEN` JWT.
22
+ */
20
23
  get projectId(): string | undefined;
24
+ /**
25
+ * The base URL for API requests.
26
+ */
21
27
  get baseUrl(): string;
28
+ /**
29
+ * The service token used for authentication.
30
+ * Resolution order:
31
+ * 1. Manual config via `init()`
32
+ * 2. `CC_PROJECT_TOKEN` environment variable.
33
+ */
22
34
  get serviceToken(): string | undefined;
23
35
  /**
24
36
  * Internal helper to make authenticated requests to the CloneCommand API.
25
- * This is used by server-side methods (e.g., image uploads).
37
+ *
38
+ * @param path - The API path (e.g., '/graphql').
39
+ * @param options - Standard RequestInit options.
40
+ * @returns A promise resolving to the JSON response.
41
+ * @throws Error if the request fails or returns a non-OK status.
42
+ */
43
+ request<T = any>(path: string, options?: RequestInit): Promise<T>;
44
+ /**
45
+ * Login and authentication utilities.
26
46
  */
27
- request(path: string, options?: RequestInit): Promise<any>;
28
47
  get login(): {
29
48
  /**
30
49
  * Wraps a standard OAuth authorization URL with the CloneCommand Proxy.
31
50
  * This allows ephemeral preview environments (*.clonecommand.app) to handle
32
51
  * OAuth redirects without constant provider configuration changes.
33
52
  *
34
- * @param originalUrl The standard OAuth authorization URL
35
- * @returns The proxied URL to redirect the user to
53
+ * @param originalUrl - The standard OAuth authorization URL.
54
+ * @returns The proxied URL to redirect the user to.
55
+ * @throws Error if projectId is not configured.
36
56
  */
37
57
  proxy: (originalUrl: string) => string;
38
58
  };
59
+ /**
60
+ * Storage and file management utilities.
61
+ */
39
62
  get storage(): {
40
63
  /**
41
- * Import a file from a URL and upload it to CloneCommand Storage.
64
+ * Imports a file from a URL and uploads it to CloneCommand Storage.
42
65
  *
43
- * @param url The URL of the file to import
44
- * @param contentType Optional content type (auto-detected if not provided)
45
- * @returns StorageFile object with publicUrl
66
+ * @param url - The URL of the file to import.
67
+ * @param contentType - Optional content type (auto-detected if not provided).
68
+ * @returns A promise resolving to the StorageFile record.
46
69
  *
47
70
  * @example
48
71
  * const file = await ccc.storage.importFileFromUrl('https://example.com/image.png');
49
- * console.log(file.publicUrl); // https://media.clonecommand.com/:projectId/:fileId/original.png
50
72
  */
51
73
  importFileFromUrl: (url: string, contentType?: string) => Promise<StorageFile>;
52
74
  /**
53
- * Import a file from a Buffer and upload it to CloneCommand Storage.
54
- *
55
- * @param buffer The file buffer
56
- * @param contentType The content type of the file
57
- * @returns StorageFile object with publicUrl
75
+ * Uploads a file from a memory buffer.
58
76
  *
59
- * @example
60
- * const buffer = fs.readFileSync('image.png');
61
- * const file = await ccc.storage.importFileFromBuffer(buffer, 'image/png');
77
+ * @param buffer - The file contents as a Buffer.
78
+ * @param contentType - The MIME type of the file.
79
+ * @param filename - Optional filename.
80
+ * @returns A promise resolving to the StorageFile record.
62
81
  */
63
82
  importFileFromBuffer: (buffer: Buffer, contentType: string, filename?: string) => Promise<StorageFile>;
64
83
  /**
65
- * Get the public URL for a file.
66
- *
67
- * @param fileId The file ID
68
- * @param projectSlug Optional project slug (uses projectId from config if not provided)
69
- * @returns The public URL
84
+ * Generates a public URL for a given file ID.
70
85
  *
71
- * @example
72
- * const url = ccc.storage.getFileUrl('abc-123-def', 'my-project');
73
- * // => https://my-project.clonecommand.media/abc-123-def/original
86
+ * @param fileId - The unique file ID.
87
+ * @param projectSlug - Optional project slug (automatically resolved from projectId if not provided).
88
+ * @returns The public URL string.
74
89
  */
75
90
  getFileUrl: (fileId: string, projectSlug?: string) => string;
76
91
  };
92
+ /**
93
+ * Access to email services.
94
+ */
77
95
  get email(): EmailClient;
78
96
  }
97
+ /**
98
+ * Singleton instance of the CloneCommandCloud client.
99
+ */
79
100
  export declare const ccc: CloneCommandCloud;
package/dist/index.js CHANGED
@@ -1,12 +1,29 @@
1
1
  import { EmailClient } from './email/index.js';
2
+ /**
3
+ * The main client for interacting with CloneCommand managed services.
4
+ * This class provides access to storage, email, and authentication features.
5
+ */
2
6
  export class CloneCommandCloud {
3
7
  config = {};
8
+ _email = null;
9
+ /**
10
+ * Initializes the SDK with configuration options.
11
+ *
12
+ * @param config - The configuration object.
13
+ */
4
14
  init(config) {
5
15
  this.config = {
6
16
  ...this.config,
7
17
  ...config,
8
18
  };
9
19
  }
20
+ /**
21
+ * Returns the current project ID.
22
+ * Resolution order:
23
+ * 1. Manual config via `init()`
24
+ * 2. `CC_PROJECT_ID` environment variable
25
+ * 3. Extracted from the `CC_PROJECT_TOKEN` JWT.
26
+ */
10
27
  get projectId() {
11
28
  if (this.config.projectId)
12
29
  return this.config.projectId;
@@ -30,15 +47,28 @@ export class CloneCommandCloud {
30
47
  }
31
48
  return undefined;
32
49
  }
50
+ /**
51
+ * The base URL for API requests.
52
+ */
33
53
  get baseUrl() {
34
54
  return this.config.baseUrl || 'https://graph.clonecommand.com';
35
55
  }
56
+ /**
57
+ * The service token used for authentication.
58
+ * Resolution order:
59
+ * 1. Manual config via `init()`
60
+ * 2. `CC_PROJECT_TOKEN` environment variable.
61
+ */
36
62
  get serviceToken() {
37
- return typeof process !== 'undefined' ? process.env.CC_PROJECT_TOKEN : undefined;
63
+ return this.config.serviceToken || (typeof process !== 'undefined' ? process.env.CC_PROJECT_TOKEN : undefined);
38
64
  }
39
65
  /**
40
66
  * Internal helper to make authenticated requests to the CloneCommand API.
41
- * This is used by server-side methods (e.g., image uploads).
67
+ *
68
+ * @param path - The API path (e.g., '/graphql').
69
+ * @param options - Standard RequestInit options.
70
+ * @returns A promise resolving to the JSON response.
71
+ * @throws Error if the request fails or returns a non-OK status.
42
72
  */
43
73
  async request(path, options = {}) {
44
74
  const token = this.serviceToken;
@@ -56,6 +86,9 @@ export class CloneCommandCloud {
56
86
  }
57
87
  return response.json();
58
88
  }
89
+ /**
90
+ * Login and authentication utilities.
91
+ */
59
92
  get login() {
60
93
  return {
61
94
  /**
@@ -63,43 +96,40 @@ export class CloneCommandCloud {
63
96
  * This allows ephemeral preview environments (*.clonecommand.app) to handle
64
97
  * OAuth redirects without constant provider configuration changes.
65
98
  *
66
- * @param originalUrl The standard OAuth authorization URL
67
- * @returns The proxied URL to redirect the user to
99
+ * @param originalUrl - The standard OAuth authorization URL.
100
+ * @returns The proxied URL to redirect the user to.
101
+ * @throws Error if projectId is not configured.
68
102
  */
69
103
  proxy: (originalUrl) => {
70
104
  const pid = this.projectId;
71
105
  if (!pid) {
72
- throw new Error("CloneCommand: projectId is required. Provide it in ccc.init({ projectId }) " +
73
- "or set the CC_PROJECT_ID environment variable.");
106
+ throw new Error("CloneCommand: projectId is required but could not be resolved from CC_PROJECT_TOKEN or environment. " +
107
+ "Provide it in ccc.init({ projectId }) or set the CC_PROJECT_ID environment variable.");
74
108
  }
75
109
  const encodedUrl = encodeURIComponent(originalUrl);
76
110
  return `${this.baseUrl}/login/proxy/start?projectId=${pid}&url=${encodedUrl}`;
77
111
  }
78
112
  };
79
113
  }
114
+ /**
115
+ * Storage and file management utilities.
116
+ */
80
117
  get storage() {
81
118
  return {
82
119
  /**
83
- * Import a file from a URL and upload it to CloneCommand Storage.
120
+ * Imports a file from a URL and uploads it to CloneCommand Storage.
84
121
  *
85
- * @param url The URL of the file to import
86
- * @param contentType Optional content type (auto-detected if not provided)
87
- * @returns StorageFile object with publicUrl
122
+ * @param url - The URL of the file to import.
123
+ * @param contentType - Optional content type (auto-detected if not provided).
124
+ * @returns A promise resolving to the StorageFile record.
88
125
  *
89
126
  * @example
90
127
  * const file = await ccc.storage.importFileFromUrl('https://example.com/image.png');
91
- * console.log(file.publicUrl); // https://media.clonecommand.com/:projectId/:fileId/original.png
92
128
  */
93
129
  importFileFromUrl: async (url, contentType) => {
94
- const token = this.serviceToken;
95
130
  const pid = this.projectId;
96
- if (!token) {
97
- throw new Error("CloneCommand Storage: CC_PROJECT_TOKEN is required. " +
98
- "Ensure your service is deployed via CloneCommand or use 'clones run' for local development.");
99
- }
100
131
  if (!pid) {
101
- throw new Error("CloneCommand Storage: projectId is required. " +
102
- "Set CC_PROJECT_ID environment variable or call ccc.init({ projectId }).");
132
+ throw new Error("CloneCommand Storage: projectId could not be resolved. Ensure CC_PROJECT_TOKEN is set.");
103
133
  }
104
134
  const query = `
105
135
  mutation UploadFileFromUrl($projectId: String!, $url: String!, $contentType: String) {
@@ -118,9 +148,7 @@ export class CloneCommandCloud {
118
148
  `;
119
149
  const result = await this.request('/graphql', {
120
150
  method: 'POST',
121
- headers: {
122
- 'Content-Type': 'application/json',
123
- },
151
+ headers: { 'Content-Type': 'application/json' },
124
152
  body: JSON.stringify({
125
153
  query,
126
154
  variables: { projectId: pid, url, contentType },
@@ -132,24 +160,17 @@ export class CloneCommandCloud {
132
160
  return result.data.uploadFileFromUrl;
133
161
  },
134
162
  /**
135
- * Import a file from a Buffer and upload it to CloneCommand Storage.
163
+ * Uploads a file from a memory buffer.
136
164
  *
137
- * @param buffer The file buffer
138
- * @param contentType The content type of the file
139
- * @returns StorageFile object with publicUrl
140
- *
141
- * @example
142
- * const buffer = fs.readFileSync('image.png');
143
- * const file = await ccc.storage.importFileFromBuffer(buffer, 'image/png');
165
+ * @param buffer - The file contents as a Buffer.
166
+ * @param contentType - The MIME type of the file.
167
+ * @param filename - Optional filename.
168
+ * @returns A promise resolving to the StorageFile record.
144
169
  */
145
170
  importFileFromBuffer: async (buffer, contentType, filename = 'file') => {
146
- const token = this.serviceToken;
147
171
  const pid = this.projectId;
148
- if (!token) {
149
- throw new Error("CloneCommand Storage: CC_PROJECT_TOKEN is required.");
150
- }
151
172
  if (!pid) {
152
- throw new Error("CloneCommand Storage: projectId is required.");
173
+ throw new Error("CloneCommand Storage: projectId could not be resolved. Ensure CC_PROJECT_TOKEN is set.");
153
174
  }
154
175
  const query = `
155
176
  mutation UploadFileFromBuffer($projectId: String!, $buffer: String!, $contentType: String!, $filename: String!) {
@@ -185,31 +206,33 @@ export class CloneCommandCloud {
185
206
  return result.data.uploadFileFromBuffer;
186
207
  },
187
208
  /**
188
- * Get the public URL for a file.
209
+ * Generates a public URL for a given file ID.
189
210
  *
190
- * @param fileId The file ID
191
- * @param projectSlug Optional project slug (uses projectId from config if not provided)
192
- * @returns The public URL
193
- *
194
- * @example
195
- * const url = ccc.storage.getFileUrl('abc-123-def', 'my-project');
196
- * // => https://my-project.clonecommand.media/abc-123-def/original
211
+ * @param fileId - The unique file ID.
212
+ * @param projectSlug - Optional project slug (automatically resolved from projectId if not provided).
213
+ * @returns The public URL string.
197
214
  */
198
215
  getFileUrl: (fileId, projectSlug) => {
199
216
  const pid = this.projectId;
200
217
  const slug = projectSlug || pid;
201
218
  if (!slug) {
202
- throw new Error("CloneCommand Storage: projectId or projectSlug is required. " +
203
- "Set CC_PROJECT_ID environment variable or call ccc.init({ projectId }).");
219
+ throw new Error("CloneCommand Storage: projectId or projectSlug could not be resolved.");
204
220
  }
205
- // Note: This returns the default projectslug.clonecommand.media URL
206
- // Custom domains are handled server-side
207
221
  return `https://${slug}.clonecommand.media/${fileId}/original`;
208
222
  },
209
223
  };
210
224
  }
225
+ /**
226
+ * Access to email services.
227
+ */
211
228
  get email() {
212
- return new EmailClient(this);
229
+ if (!this._email) {
230
+ this._email = new EmailClient(this);
231
+ }
232
+ return this._email;
213
233
  }
214
234
  }
235
+ /**
236
+ * Singleton instance of the CloneCommandCloud client.
237
+ */
215
238
  export const ccc = new CloneCommandCloud();
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Configuration for the CloneCommand Cloud SDK.
3
+ */
4
+ export interface CCCConfig {
5
+ /**
6
+ * The unique identifier for your project.
7
+ * Can also be set via the `CC_PROJECT_ID` environment variable.
8
+ */
9
+ projectId?: string;
10
+ /**
11
+ * The base URL for the CloneCommand API.
12
+ * Defaults to `https://graph.clonecommand.com`.
13
+ */
14
+ baseUrl?: string;
15
+ /**
16
+ * The service token for authentication.
17
+ * Can also be set via the `CC_PROJECT_TOKEN` environment variable.
18
+ */
19
+ serviceToken?: string;
20
+ }
21
+ /**
22
+ * Represents a file stored in CloneCommand Storage.
23
+ */
24
+ export interface StorageFile {
25
+ /** Unique ID of the storage record. */
26
+ id: string;
27
+ /** Project ID this file belongs to. */
28
+ projectId: string;
29
+ /** The underlying storage provider's file ID. */
30
+ fileId: string;
31
+ /** Original filename if available. */
32
+ originalFilename?: string;
33
+ /** MIME type of the file. */
34
+ contentType: string;
35
+ /** Size of the file in bytes. */
36
+ sizeBytes?: number;
37
+ /** File extension. */
38
+ extension?: string;
39
+ /** Publicly accessible URL for the file. */
40
+ publicUrl: string;
41
+ /** ISO timestamp when the file was uploaded. */
42
+ createdAt: string;
43
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
package/docs/README.md ADDED
@@ -0,0 +1,23 @@
1
+ # CloneCommand Cloud SDK Documentation
2
+
3
+ Welcome to the `@clonecommand/cloud` SDK documentation.
4
+
5
+ ## Modules
6
+
7
+ - [**🔐 Login**](./login.md): OAuth Proxy service for preview environments.
8
+ - [**📦 Storage**](./storage.md): Managed file hosting with CDN and custom domains.
9
+ - [**📧 Email**](./email.md): Managed email sending and receiving with inbound processing.
10
+
11
+ ## Quick Start
12
+
13
+ ```typescript
14
+ import { ccc } from '@clonecommand/cloud';
15
+
16
+ // 1. Initialize (Optional in managed environments)
17
+ ccc.init({ projectId: 'your-project-id' });
18
+
19
+ // 2. Use services
20
+ const loginUrl = ccc.login.proxy('https://google.com/oauth...');
21
+ const file = await ccc.storage.importFileFromUrl('...');
22
+ const config = await ccc.email.getConfig();
23
+ ```
package/docs/email.md ADDED
@@ -0,0 +1,65 @@
1
+ # 📧 CloneCommand Email Service
2
+
3
+ Managed email sending and receiving with inbound processing and domain verification.
4
+
5
+ ## Features
6
+
7
+ - **Send Emails**: Send transactional emails via Amazon SES (managed backend).
8
+ - **Inbound Processing**: Receive emails, store them in GCS, and access them via API.
9
+ - **Domain Verification**: Automates DNS verification for custom domains.
10
+
11
+ ## Usage
12
+
13
+ ### Getting Configuration
14
+
15
+ Check if email is enabled for the current project.
16
+
17
+ ```typescript
18
+ import { ccc } from '@clonecommand/cloud';
19
+
20
+ const config = await ccc.email.getConfig();
21
+
22
+ if (config?.isEnabled) {
23
+ console.log('Email is active!');
24
+ }
25
+ ```
26
+
27
+ ### Sending Emails
28
+
29
+ Send simple text or HTML emails.
30
+
31
+ ```typescript
32
+ const messageId = await ccc.email.send({
33
+ to: 'user@example.com',
34
+ subject: 'Welcome!',
35
+ html: '<h1>Welcome to our platform</h1>',
36
+ text: 'Welcome to our platform'
37
+ });
38
+ ```
39
+
40
+ ### Retrieving Inbound Emails
41
+
42
+ Fetch the latest emails received by the project's inbound address.
43
+
44
+ ```typescript
45
+ const emails = await ccc.email.getInbound({ limit: 5 });
46
+
47
+ for (const email of emails) {
48
+ console.log(`Received from: ${email.sender}`);
49
+ console.log(`Subject: ${email.subject}`);
50
+
51
+ // Fetch full details including body
52
+ const fullEmail = await ccc.email.get(email.id);
53
+ console.log(fullEmail.body);
54
+ }
55
+ ```
56
+
57
+ ## Agent Capabilities
58
+
59
+ The email service is designed to be easily used by AI agents to build automated workflows:
60
+ - **Auto-Reply Bots**: Poll `getInbound()` and `send()` replies.
61
+ - **Verification flows**: Trigger emails and read the verification codes from the inbox.
62
+
63
+ ## Authentication
64
+
65
+ Requires the `CC_PROJECT_TOKEN` environment variable, automatically injected in CloneCommand deployments.
package/docs/login.md ADDED
@@ -0,0 +1,54 @@
1
+ # 🔐 CloneCommand Login Service (OAuth Proxy)
2
+
3
+ The **CloneCommand Login Service** acts as a trusted intermediary for OAuth flows, solving the "Redirect URI Paradox" in preview environments.
4
+
5
+ ## Why is this needed?
6
+
7
+ OAuth providers (Google, GitHub, LinkedIn, etc.) require whitelisting specific Redirect URIs. This is problematic for:
8
+ 1. **Preview Environments**: Each pull request creates a unique URL (e.g., `pr-123.clonecommand.app`). You can't whitelist infinite dynamic URLs.
9
+ 2. **Local Development**: Managing `localhost` vs production URLs often leads to config drift.
10
+
11
+ ## How it works
12
+
13
+ Instead of redirecting users directly to the OAuth provider, you redirect them to the **CloneCommand Proxy**. The proxy handles the redirection to the provider using a single, stable callback URL (`https://graph.clonecommand.com/login/proxy/callback`), and then redirects the user back to your specific environment.
14
+
15
+ ## Usage
16
+
17
+ ### 1. Wrap your Authorization URL
18
+
19
+ ```typescript
20
+ import { ccc } from '@clonecommand/cloud';
21
+
22
+ const originalUrl = 'https://github.com/login/oauth/authorize?client_id=...';
23
+ const proxyUrl = ccc.login.proxy(originalUrl);
24
+
25
+ window.location.href = proxyUrl;
26
+ ```
27
+
28
+ ### 2. Configure your OAuth Provider
29
+
30
+ Whitelist **only** this URL in your provider's developer console:
31
+
32
+ `https://graph.clonecommand.com/login/proxy/callback`
33
+
34
+ ### 3. Handle the Callback
35
+
36
+ When exchanging the `code` for an access token on your backend, you **must** use the proxy callback URL as the `redirect_uri`, regardless of where your app is actually running.
37
+
38
+ ```typescript
39
+ // Backend code to exchange code for token
40
+ const response = await fetch('https://github.com/login/oauth/access_token', {
41
+ method: 'POST',
42
+ body: JSON.stringify({
43
+ client_id: '...',
44
+ client_secret: '...',
45
+ code: req.query.code,
46
+ redirect_uri: 'https://graph.clonecommand.com/login/proxy/callback' // <--- CRITICAL
47
+ })
48
+ });
49
+ ```
50
+
51
+ ## Agent Notes
52
+
53
+ - **Stability**: This feature ensures that authentication flows work immediately in any new environment an agent creates.
54
+ - **Zero Config**: No need to ask the user to update their OAuth provider settings when deploying new branches.
@@ -0,0 +1,40 @@
1
+ # 📦 CloneCommand Storage Service
2
+
3
+ Managed file storage with global CDN, automatic SSL, and custom domain support.
4
+
5
+ ## Usage
6
+
7
+ ### Uploading Files
8
+
9
+ The `importFileFromUrl` method allows you to upload assets from external sources (e.g., social media avatars, public APIs) directly to your project's storage bucket.
10
+
11
+ ```typescript
12
+ import { ccc } from '@clonecommand/cloud';
13
+
14
+ const file = await ccc.storage.importFileFromUrl('https://example.com/image.png');
15
+
16
+ console.log(file);
17
+ // {
18
+ // id: "...",
19
+ // fileId: "xyz-123",
20
+ // publicUrl: "https://my-project.clonecommand.media/xyz-123/original.png",
21
+ // ...
22
+ // }
23
+ ```
24
+
25
+ ### Retrieving File URLs
26
+
27
+ Generate the public CDN URL for a file using its `fileId`.
28
+
29
+ ```typescript
30
+ const url = ccc.storage.getFileUrl('xyz-123');
31
+ // -> https://my-project.clonecommand.media/xyz-123/original
32
+ ```
33
+
34
+ ## Custom Domains
35
+
36
+ If your project has a custom media domain configured (e.g., `cdn.myapp.com`), `getFileUrl` and `importFileFromUrl` will automatically return URLs using that domain.
37
+
38
+ ## Authentication
39
+
40
+ Storage operations require the `CC_PROJECT_TOKEN` environment variable, which is automatically injected in CloneCommand deployments. For local development, use `clones run`.
package/package.json CHANGED
@@ -1,13 +1,24 @@
1
1
  {
2
2
  "name": "@clonecommand/cloud",
3
- "version": "0.0.2",
3
+ "version": "0.0.4",
4
4
  "type": "module",
5
5
  "description": "The official SDK for CloneCommand managed services",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ },
13
+ "./email": {
14
+ "types": "./dist/email/index.d.ts",
15
+ "import": "./dist/email/index.js"
16
+ }
17
+ },
8
18
  "files": [
9
19
  "dist",
10
- "README.md"
20
+ "README.md",
21
+ "docs"
11
22
  ],
12
23
  "publishConfig": {
13
24
  "access": "public"