sunpeak 0.6.7 → 0.7.10

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.
Files changed (47) hide show
  1. package/README.md +3 -3
  2. package/bin/commands/build.mjs +22 -5
  3. package/bin/commands/deploy.mjs +125 -0
  4. package/bin/commands/login.mjs +217 -0
  5. package/bin/commands/logout.mjs +87 -0
  6. package/bin/commands/pull.mjs +254 -0
  7. package/bin/commands/push.mjs +352 -0
  8. package/bin/sunpeak.js +101 -2
  9. package/dist/mcp/entry.cjs +2 -2
  10. package/dist/mcp/entry.cjs.map +1 -1
  11. package/dist/mcp/entry.js +2 -2
  12. package/dist/mcp/entry.js.map +1 -1
  13. package/dist/mcp/index.cjs +1 -1
  14. package/dist/mcp/index.js +1 -1
  15. package/dist/{server-CQGbJWbk.cjs → server-BOYwNazb.cjs} +25 -26
  16. package/dist/{server-CQGbJWbk.cjs.map → server-BOYwNazb.cjs.map} +1 -1
  17. package/dist/{server-DGCvp1RA.js → server-C6vMGV6H.js} +25 -26
  18. package/dist/{server-DGCvp1RA.js.map → server-C6vMGV6H.js.map} +1 -1
  19. package/package.json +1 -1
  20. package/template/.sunpeak/dev.tsx +8 -10
  21. package/template/README.md +4 -4
  22. package/template/dist/albums.json +15 -0
  23. package/template/dist/carousel.json +15 -0
  24. package/template/dist/counter.json +10 -0
  25. package/template/dist/map.json +19 -0
  26. package/template/index.html +1 -1
  27. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Button.js +3 -3
  28. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_SegmentedControl.js +1 -1
  29. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Select.js +16 -16
  30. package/template/node_modules/.vite/deps/@openai_apps-sdk-ui_components_Textarea.js +3 -3
  31. package/template/node_modules/.vite/deps/_metadata.json +32 -32
  32. package/template/node_modules/.vite/deps/{chunk-DQAZDQU3.js → chunk-LR7NKCX5.js} +8 -8
  33. package/template/node_modules/.vite/vitest/da39a3ee5e6b4b0d3255bfef95601890afd80709/results.json +1 -1
  34. package/template/src/resources/albums-resource.json +12 -0
  35. package/template/src/resources/carousel-resource.json +12 -0
  36. package/template/src/resources/counter-resource.json +9 -0
  37. package/template/src/resources/map-resource.json +13 -0
  38. package/template/src/simulations/albums-simulation.ts +3 -15
  39. package/template/src/simulations/carousel-simulation.ts +3 -15
  40. package/template/src/simulations/counter-simulation.ts +3 -15
  41. package/template/src/simulations/map-simulation.ts +5 -28
  42. package/template/src/simulations/widget-config.ts +0 -42
  43. /package/template/dist/{chatgpt/albums.js → albums.js} +0 -0
  44. /package/template/dist/{chatgpt/carousel.js → carousel.js} +0 -0
  45. /package/template/dist/{chatgpt/counter.js → counter.js} +0 -0
  46. /package/template/dist/{chatgpt/map.js → map.js} +0 -0
  47. /package/template/node_modules/.vite/deps/{chunk-DQAZDQU3.js.map → chunk-LR7NKCX5.js.map} +0 -0
@@ -0,0 +1,254 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, mkdirSync, writeFileSync, readFileSync } from 'fs';
3
+ import { join } from 'path';
4
+ import { homedir } from 'os';
5
+
6
+ const SUNPEAK_API_URL = process.env.SUNPEAK_API_URL || 'https://app.sunpeak.ai';
7
+ const CREDENTIALS_DIR = join(homedir(), '.sunpeak');
8
+ const CREDENTIALS_FILE = join(CREDENTIALS_DIR, 'credentials.json');
9
+
10
+ /**
11
+ * Load credentials from disk
12
+ */
13
+ function loadCredentials() {
14
+ if (!existsSync(CREDENTIALS_FILE)) {
15
+ return null;
16
+ }
17
+ try {
18
+ return JSON.parse(readFileSync(CREDENTIALS_FILE, 'utf-8'));
19
+ } catch {
20
+ return null;
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Lookup resources by tag and repository
26
+ * @returns {Promise<Array>} Array of matching resources
27
+ */
28
+ async function lookupResources(tag, repository, accessToken, name = null) {
29
+ const params = new URLSearchParams({ tag, repository });
30
+ if (name) {
31
+ params.set('name', name);
32
+ }
33
+ const response = await fetch(`${SUNPEAK_API_URL}/api/v1/resources/lookup?${params}`, {
34
+ headers: {
35
+ Authorization: `Bearer ${accessToken}`,
36
+ },
37
+ });
38
+
39
+ if (!response.ok) {
40
+ const data = await response.json().catch(() => ({}));
41
+ throw new Error(data.message || data.error || `HTTP ${response.status}`);
42
+ }
43
+
44
+ const data = await response.json();
45
+ return data.resources;
46
+ }
47
+
48
+ /**
49
+ * Download the JS file for a resource
50
+ */
51
+ async function downloadJsFile(resource) {
52
+ if (!resource.js_file?.url) {
53
+ throw new Error('Resource has no JS file attached');
54
+ }
55
+
56
+ // The URL is a pre-signed S3 URL, no additional auth needed
57
+ const response = await fetch(resource.js_file.url);
58
+
59
+ if (!response.ok) {
60
+ throw new Error(`Failed to download JS file: HTTP ${response.status}`);
61
+ }
62
+
63
+ return response.text();
64
+ }
65
+
66
+ /**
67
+ * Main pull command
68
+ * @param {string} projectRoot - Project root directory
69
+ * @param {Object} options - Command options
70
+ * @param {string} options.repository - Repository name in owner/repo format (required)
71
+ * @param {string} options.tag - Tag name to pull (required)
72
+ * @param {string} options.name - Resource name to filter by (optional)
73
+ * @param {string} options.output - Output directory (optional, defaults to current directory)
74
+ */
75
+ export async function pull(projectRoot = process.cwd(), options = {}) {
76
+ // Handle help flag
77
+ if (options.help) {
78
+ console.log(`
79
+ sunpeak pull - Pull resources from the Sunpeak repository
80
+
81
+ Usage:
82
+ sunpeak pull -r <owner/repo> -t <tag> [options]
83
+
84
+ Options:
85
+ -r, --repository <owner/repo> Repository name (required)
86
+ -t, --tag <name> Tag name to pull (required)
87
+ -n, --name <name> Resource name to filter by (optional)
88
+ -o, --output <path> Output directory (defaults to current directory)
89
+ -h, --help Show this help message
90
+
91
+ Examples:
92
+ sunpeak pull -r myorg/my-app -t prod Pull all resources tagged "prod"
93
+ sunpeak pull -r myorg/my-app -t prod -n counter Pull only the "counter" resource
94
+ sunpeak pull -r myorg/my-app -t v1.0.0 Pull a specific version
95
+ `);
96
+ return;
97
+ }
98
+
99
+ // Check credentials
100
+ const credentials = loadCredentials();
101
+ if (!credentials?.access_token) {
102
+ console.error('Error: Not logged in. Run "sunpeak login" first.');
103
+ process.exit(1);
104
+ }
105
+
106
+ // Require repository
107
+ if (!options.repository) {
108
+ console.error('Error: Repository is required. Use --repository or -r to specify a repository.');
109
+ console.error('Example: sunpeak pull -r myorg/my-app -t prod');
110
+ process.exit(1);
111
+ }
112
+
113
+ // Require tag
114
+ if (!options.tag) {
115
+ console.error('Error: Tag is required. Use --tag or -t to specify a tag.');
116
+ console.error('Example: sunpeak pull -r myorg/my-app -t prod');
117
+ process.exit(1);
118
+ }
119
+
120
+ const repository = options.repository;
121
+
122
+ const nameFilter = options.name ? ` with name "${options.name}"` : '';
123
+ console.log(`Pulling resources from repository "${repository}" with tag "${options.tag}"${nameFilter}...`);
124
+ console.log();
125
+
126
+ try {
127
+ // Lookup resources
128
+ const resources = await lookupResources(options.tag, repository, credentials.access_token, options.name);
129
+
130
+ if (!resources || resources.length === 0) {
131
+ console.error('Error: No resources found matching the criteria.');
132
+ process.exit(1);
133
+ }
134
+
135
+ console.log(`Found ${resources.length} resource(s):\n`);
136
+
137
+ // Determine output directory
138
+ const outputDir = options.output || projectRoot;
139
+
140
+ // Create output directory if it doesn't exist
141
+ if (!existsSync(outputDir)) {
142
+ mkdirSync(outputDir, { recursive: true });
143
+ }
144
+
145
+ // Process each resource
146
+ for (const resource of resources) {
147
+ console.log(`Resource: ${resource.name}`);
148
+ console.log(` Title: ${resource.title}`);
149
+ console.log(` URI: ${resource.uri}`);
150
+ console.log(` Tags: ${resource.tags?.join(', ') || 'none'}`);
151
+ console.log(` Created: ${resource.created_at}`);
152
+
153
+ if (!resource.js_file) {
154
+ console.log(` ⚠ Skipping: No JS file attached.\n`);
155
+ continue;
156
+ }
157
+
158
+ // Download the JS file
159
+ console.log(` Downloading JS file...`);
160
+ const jsContent = await downloadJsFile(resource);
161
+
162
+ const outputFile = join(outputDir, `${resource.name}.js`);
163
+ const metaFile = join(outputDir, `${resource.name}.json`);
164
+
165
+ // Write the JS file
166
+ writeFileSync(outputFile, jsContent);
167
+ console.log(` ✓ Saved ${resource.name}.js`);
168
+
169
+ // Write metadata JSON
170
+ const meta = {
171
+ uri: resource.uri,
172
+ name: resource.name,
173
+ title: resource.title,
174
+ description: resource.description,
175
+ mimeType: resource.mime_type,
176
+ _meta: {
177
+ 'openai/widgetDomain': resource.widget_domain,
178
+ 'openai/widgetCSP': {
179
+ connect_domains: resource.widget_csp_connect_domains || [],
180
+ resource_domains: resource.widget_csp_resource_domains || [],
181
+ },
182
+ },
183
+ };
184
+ writeFileSync(metaFile, JSON.stringify(meta, null, 2));
185
+ console.log(` ✓ Saved ${resource.name}.json\n`);
186
+ }
187
+
188
+ console.log(`✓ Successfully pulled ${resources.length} resource(s) to ${outputDir}`);
189
+ } catch (error) {
190
+ console.error(`Error: ${error.message}`);
191
+ process.exit(1);
192
+ }
193
+ }
194
+
195
+ /**
196
+ * Parse command line arguments
197
+ */
198
+ function parseArgs(args) {
199
+ const options = {};
200
+ let i = 0;
201
+
202
+ while (i < args.length) {
203
+ const arg = args[i];
204
+
205
+ if (arg === '--repository' || arg === '-r') {
206
+ options.repository = args[++i];
207
+ } else if (arg === '--tag' || arg === '-t') {
208
+ options.tag = args[++i];
209
+ } else if (arg === '--name' || arg === '-n') {
210
+ options.name = args[++i];
211
+ } else if (arg === '--output' || arg === '-o') {
212
+ options.output = args[++i];
213
+ } else if (arg === '--help' || arg === '-h') {
214
+ console.log(`
215
+ sunpeak pull - Pull resources from the Sunpeak repository
216
+
217
+ Usage:
218
+ sunpeak pull -r <owner/repo> -t <tag> [options]
219
+
220
+ Options:
221
+ -r, --repository <owner/repo> Repository name (required)
222
+ -t, --tag <name> Tag name to pull (required)
223
+ -n, --name <name> Resource name to filter by (optional)
224
+ -o, --output <path> Output directory (defaults to current directory)
225
+ -h, --help Show this help message
226
+
227
+ Examples:
228
+ sunpeak pull -r myorg/my-app -t prod Pull all resources tagged "prod"
229
+ sunpeak pull -r myorg/my-app -t prod -n counter Pull only the "counter" resource
230
+ sunpeak pull -r myorg/my-app -t v1.0.0 Pull a specific version
231
+ `);
232
+ process.exit(0);
233
+ } else if (!arg.startsWith('-')) {
234
+ // Positional argument - treat as tag
235
+ if (!options.tag) {
236
+ options.tag = arg;
237
+ }
238
+ }
239
+
240
+ i++;
241
+ }
242
+
243
+ return options;
244
+ }
245
+
246
+ // Allow running directly
247
+ if (import.meta.url === `file://${process.argv[1]}`) {
248
+ const args = process.argv.slice(2);
249
+ const options = parseArgs(args);
250
+ pull(process.cwd(), options).catch((error) => {
251
+ console.error('Error:', error.message);
252
+ process.exit(1);
253
+ });
254
+ }
@@ -0,0 +1,352 @@
1
+ #!/usr/bin/env node
2
+ import { existsSync, readFileSync, readdirSync } from 'fs';
3
+ import { join, dirname, basename } from 'path';
4
+ import { homedir } from 'os';
5
+ import { execSync } from 'child_process';
6
+
7
+ const SUNPEAK_API_URL = process.env.SUNPEAK_API_URL || 'https://app.sunpeak.ai';
8
+ const CREDENTIALS_DIR = join(homedir(), '.sunpeak');
9
+ const CREDENTIALS_FILE = join(CREDENTIALS_DIR, 'credentials.json');
10
+
11
+ /**
12
+ * Load credentials from disk
13
+ */
14
+ function loadCredentials() {
15
+ if (!existsSync(CREDENTIALS_FILE)) {
16
+ return null;
17
+ }
18
+ try {
19
+ return JSON.parse(readFileSync(CREDENTIALS_FILE, 'utf-8'));
20
+ } catch {
21
+ return null;
22
+ }
23
+ }
24
+
25
+ /**
26
+ * Get the current git repository name in owner/repo format
27
+ */
28
+ function getGitRepoName() {
29
+ try {
30
+ // Try to get the remote URL first
31
+ const remoteUrl = execSync('git remote get-url origin 2>/dev/null', { encoding: 'utf-8' }).trim();
32
+ if (remoteUrl) {
33
+ // Extract owner/repo from URL
34
+ // Handles: https://github.com/owner/repo.git, git@github.com:owner/repo.git
35
+ const match = remoteUrl.match(/[/:]([^/:]+\/[^/]+?)(?:\.git)?$/);
36
+ if (match) {
37
+ return match[1];
38
+ }
39
+ }
40
+ } catch {
41
+ // No remote
42
+ }
43
+
44
+ return null;
45
+ }
46
+
47
+ /**
48
+ * Find all resources in a directory
49
+ * Returns array of { name, jsPath, metaPath, meta }
50
+ */
51
+ export function findResources(distDir) {
52
+ if (!existsSync(distDir)) {
53
+ return [];
54
+ }
55
+
56
+ const files = readdirSync(distDir);
57
+ const jsFiles = files.filter((f) => f.endsWith('.js'));
58
+ const jsonFiles = new Set(files.filter((f) => f.endsWith('.json')));
59
+
60
+ // Only include .js files that have a matching .json file
61
+ return jsFiles
62
+ .filter((jsFile) => {
63
+ const name = jsFile.replace('.js', '');
64
+ return jsonFiles.has(`${name}.json`);
65
+ })
66
+ .map((jsFile) => {
67
+ const name = jsFile.replace('.js', '');
68
+ const jsPath = join(distDir, jsFile);
69
+ const metaPath = join(distDir, `${name}.json`);
70
+
71
+ let meta = null;
72
+ try {
73
+ meta = JSON.parse(readFileSync(metaPath, 'utf-8'));
74
+ } catch {
75
+ console.warn(`Warning: Could not parse ${name}.json`);
76
+ }
77
+
78
+ return { name, jsPath, metaPath, meta };
79
+ });
80
+ }
81
+
82
+ /**
83
+ * Build a resource from a specific JS file path
84
+ * Returns { name, jsPath, metaPath, meta }
85
+ */
86
+ function buildResourceFromFile(jsPath) {
87
+ if (!existsSync(jsPath)) {
88
+ console.error(`Error: File not found: ${jsPath}`);
89
+ process.exit(1);
90
+ }
91
+
92
+ // Extract name from filename (remove .js extension)
93
+ const fileName = basename(jsPath);
94
+ const name = fileName.replace('.js', '');
95
+
96
+ // Look for .json in the same directory
97
+ const dir = dirname(jsPath);
98
+ const metaPath = join(dir, `${name}.json`);
99
+
100
+ let meta = null;
101
+ if (existsSync(metaPath)) {
102
+ try {
103
+ meta = JSON.parse(readFileSync(metaPath, 'utf-8'));
104
+ } catch {
105
+ console.warn(`Warning: Could not parse ${name}.json`);
106
+ }
107
+ }
108
+
109
+ return { name, jsPath, metaPath, meta };
110
+ }
111
+
112
+ /**
113
+ * Push a single resource to the API
114
+ */
115
+ async function pushResource(resource, repository, tags, accessToken) {
116
+ if (!resource.meta?.uri) {
117
+ throw new Error('Resource is missing URI. Run "sunpeak build" to generate URIs.');
118
+ }
119
+
120
+ const jsContent = readFileSync(resource.jsPath);
121
+ const jsBlob = new Blob([jsContent], { type: 'application/javascript' });
122
+
123
+ // Build form data
124
+ const formData = new FormData();
125
+ formData.append('repository', repository);
126
+ formData.append('js_file', jsBlob, `${resource.name}.js`);
127
+
128
+ // Add metadata fields
129
+ if (resource.meta) {
130
+ formData.append('name', resource.meta.name || resource.name);
131
+ formData.append('title', resource.meta.title || resource.name);
132
+ if (resource.meta.description) {
133
+ formData.append('description', resource.meta.description);
134
+ }
135
+ formData.append('mime_type', resource.meta.mimeType || 'text/html+skybridge');
136
+ formData.append('uri', resource.meta.uri);
137
+
138
+ // Handle OpenAI widget metadata
139
+ if (resource.meta._meta) {
140
+ if (resource.meta._meta['openai/widgetDomain']) {
141
+ formData.append('widget_domain', resource.meta._meta['openai/widgetDomain']);
142
+ }
143
+ if (resource.meta._meta['openai/widgetCSP']) {
144
+ const csp = resource.meta._meta['openai/widgetCSP'];
145
+ if (csp.connect_domains) {
146
+ csp.connect_domains.forEach((domain) => {
147
+ formData.append('widget_csp_connect_domains[]', domain);
148
+ });
149
+ }
150
+ if (csp.resource_domains) {
151
+ csp.resource_domains.forEach((domain) => {
152
+ formData.append('widget_csp_resource_domains[]', domain);
153
+ });
154
+ }
155
+ }
156
+ }
157
+ } else {
158
+ // Fallback metadata
159
+ formData.append('name', resource.name);
160
+ formData.append('title', resource.name);
161
+ formData.append('mime_type', 'text/html+skybridge');
162
+ }
163
+
164
+ // Add tags if provided
165
+ if (tags && tags.length > 0) {
166
+ tags.forEach((tag) => {
167
+ formData.append('tags[]', tag);
168
+ });
169
+ }
170
+
171
+ const response = await fetch(`${SUNPEAK_API_URL}/api/v1/resources`, {
172
+ method: 'POST',
173
+ headers: {
174
+ Authorization: `Bearer ${accessToken}`,
175
+ },
176
+ body: formData,
177
+ });
178
+
179
+ if (!response.ok) {
180
+ const data = await response.json().catch(() => ({}));
181
+ let errorMessage = data.message || data.error || `HTTP ${response.status}`;
182
+ if (data.errors && Array.isArray(data.errors) && data.errors.length > 0) {
183
+ errorMessage += ': ' + data.errors.join(', ');
184
+ }
185
+ throw new Error(errorMessage);
186
+ }
187
+
188
+ return response.json();
189
+ }
190
+
191
+ /**
192
+ * Main push command
193
+ * @param {string} projectRoot - Project root directory
194
+ * @param {Object} options - Command options
195
+ * @param {string} options.repository - Repository name (optional, defaults to git repo name)
196
+ * @param {string} options.file - Path to a specific resource JS file (optional)
197
+ * @param {string[]} options.tags - Tags to assign to the pushed resources (optional)
198
+ */
199
+ export async function push(projectRoot = process.cwd(), options = {}) {
200
+ // Handle help flag
201
+ if (options.help) {
202
+ console.log(`
203
+ sunpeak push - Push resources to the Sunpeak repository
204
+
205
+ Usage:
206
+ sunpeak push [file] [options]
207
+
208
+ Options:
209
+ -r, --repository <owner/repo> Repository name (defaults to git remote origin)
210
+ -t, --tag <name> Tag to assign (can be specified multiple times)
211
+ -h, --help Show this help message
212
+
213
+ Arguments:
214
+ file Optional JS file to push (e.g., dist/carousel.js)
215
+ If not provided, pushes all resources from dist/
216
+
217
+ Examples:
218
+ sunpeak push Push all resources from dist/
219
+ sunpeak push dist/carousel.js Push a single resource
220
+ sunpeak push -r myorg/my-app Push to "myorg/my-app" repository
221
+ sunpeak push -t v1.0.0 Push with a version tag
222
+ sunpeak push -t v1.0.0 -t latest Push with multiple tags
223
+ `);
224
+ return;
225
+ }
226
+
227
+ // Check credentials
228
+ const credentials = loadCredentials();
229
+ if (!credentials?.access_token) {
230
+ console.error('Error: Not logged in. Run "sunpeak login" first.');
231
+ process.exit(1);
232
+ }
233
+
234
+ // Determine repository name (owner/repo format)
235
+ const repository = options.repository || getGitRepoName();
236
+ if (!repository) {
237
+ console.error('Error: Could not determine repository name.');
238
+ console.error('Please provide a repository name: sunpeak push --repository <owner/repo>');
239
+ console.error('Or run this command from within a git repository with a remote origin.');
240
+ process.exit(1);
241
+ }
242
+
243
+ // Find resources - either a specific file or all from dist directory
244
+ let resources;
245
+ if (options.file) {
246
+ // Push a single specific resource
247
+ resources = [buildResourceFromFile(options.file)];
248
+ } else {
249
+ // Default: find all resources in dist directory
250
+ const distDir = join(projectRoot, 'dist');
251
+ if (!existsSync(distDir)) {
252
+ console.error(`Error: dist/ directory not found`);
253
+ console.error('Run "sunpeak build" first to build your resources.');
254
+ process.exit(1);
255
+ }
256
+
257
+ resources = findResources(distDir);
258
+ if (resources.length === 0) {
259
+ console.error(`Error: No resources found in dist/`);
260
+ console.error('Run "sunpeak build" first to build your resources.');
261
+ process.exit(1);
262
+ }
263
+ }
264
+
265
+ console.log(`Pushing ${resources.length} resource(s) to repository "${repository}"...`);
266
+ if (options.tags && options.tags.length > 0) {
267
+ console.log(`Tags: ${options.tags.join(', ')}`);
268
+ }
269
+ console.log();
270
+
271
+ // Push each resource
272
+ let successCount = 0;
273
+ for (const resource of resources) {
274
+ try {
275
+ const result = await pushResource(resource, repository, options.tags, credentials.access_token);
276
+ console.log(`✓ Pushed ${resource.name} (id: ${result.id})`);
277
+ if (result.tags?.length > 0) {
278
+ console.log(` Tags: ${result.tags.join(', ')}`);
279
+ }
280
+ successCount++;
281
+ } catch (error) {
282
+ console.error(`✗ Failed to push ${resource.name}: ${error.message}`);
283
+ }
284
+ }
285
+
286
+ console.log();
287
+ if (successCount === resources.length) {
288
+ console.log(`✓ Successfully pushed ${successCount} resource(s).`);
289
+ } else {
290
+ console.log(`Pushed ${successCount}/${resources.length} resource(s).`);
291
+ process.exit(1);
292
+ }
293
+ }
294
+
295
+ /**
296
+ * Parse command line arguments
297
+ */
298
+ function parseArgs(args) {
299
+ const options = { tags: [] };
300
+ let i = 0;
301
+
302
+ while (i < args.length) {
303
+ const arg = args[i];
304
+
305
+ if (arg === '--repository' || arg === '-r') {
306
+ options.repository = args[++i];
307
+ } else if (arg === '--tag' || arg === '-t') {
308
+ options.tags.push(args[++i]);
309
+ } else if (arg === '--help' || arg === '-h') {
310
+ console.log(`
311
+ sunpeak push - Push resources to the Sunpeak repository
312
+
313
+ Usage:
314
+ sunpeak push [file] [options]
315
+
316
+ Options:
317
+ -r, --repository <owner/repo> Repository name (defaults to git remote origin)
318
+ -t, --tag <name> Tag to assign (can be specified multiple times)
319
+ -h, --help Show this help message
320
+
321
+ Arguments:
322
+ file Optional JS file to push (e.g., dist/carousel.js)
323
+ If not provided, pushes all resources from dist/
324
+
325
+ Examples:
326
+ sunpeak push Push all resources from dist/
327
+ sunpeak push dist/carousel.js Push a single resource
328
+ sunpeak push -r myorg/my-app Push to "myorg/my-app" repository
329
+ sunpeak push -t v1.0.0 Push with a version tag
330
+ sunpeak push -t v1.0.0 -t latest Push with multiple tags
331
+ `);
332
+ process.exit(0);
333
+ } else if (!arg.startsWith('-')) {
334
+ // Positional argument - treat as file path
335
+ options.file = arg;
336
+ }
337
+
338
+ i++;
339
+ }
340
+
341
+ return options;
342
+ }
343
+
344
+ // Allow running directly
345
+ if (import.meta.url === `file://${process.argv[1]}`) {
346
+ const args = process.argv.slice(2);
347
+ const options = parseArgs(args);
348
+ push(process.cwd(), options).catch((error) => {
349
+ console.error('Error:', error.message);
350
+ process.exit(1);
351
+ });
352
+ }