@papercraneai/cli 1.5.6 → 1.6.0-beta.1
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/bin/papercrane.js +170 -251
- package/components/command-listener.tsx +52 -0
- package/components/dashboard-grid.tsx +61 -0
- package/components/error-reporter.tsx +112 -0
- package/components/theme-provider.tsx +10 -0
- package/components/theme-switcher.tsx +54 -0
- package/components/theme-toggle.tsx +21 -0
- package/components/ui/accordion.tsx +66 -0
- package/components/ui/alert-dialog.tsx +157 -0
- package/components/ui/alert.tsx +66 -0
- package/components/ui/aspect-ratio.tsx +11 -0
- package/components/ui/avatar.tsx +53 -0
- package/components/ui/badge.tsx +46 -0
- package/components/ui/breadcrumb.tsx +109 -0
- package/components/ui/button-group.tsx +83 -0
- package/components/ui/button.tsx +60 -0
- package/components/ui/calendar.tsx +216 -0
- package/components/ui/card.tsx +92 -0
- package/components/ui/carousel.tsx +241 -0
- package/components/ui/chart.tsx +357 -0
- package/components/ui/checkbox.tsx +32 -0
- package/components/ui/collapsible.tsx +33 -0
- package/components/ui/command.tsx +184 -0
- package/components/ui/context-menu.tsx +252 -0
- package/components/ui/dialog.tsx +143 -0
- package/components/ui/drawer.tsx +135 -0
- package/components/ui/dropdown-menu.tsx +257 -0
- package/components/ui/empty.tsx +104 -0
- package/components/ui/field.tsx +248 -0
- package/components/ui/form.tsx +167 -0
- package/components/ui/hover-card.tsx +44 -0
- package/components/ui/input-group.tsx +170 -0
- package/components/ui/input-otp.tsx +77 -0
- package/components/ui/input.tsx +21 -0
- package/components/ui/item.tsx +193 -0
- package/components/ui/kbd.tsx +28 -0
- package/components/ui/label.tsx +24 -0
- package/components/ui/menubar.tsx +276 -0
- package/components/ui/navigation-menu.tsx +168 -0
- package/components/ui/pagination.tsx +127 -0
- package/components/ui/popover.tsx +48 -0
- package/components/ui/progress.tsx +31 -0
- package/components/ui/radio-group.tsx +45 -0
- package/components/ui/resizable.tsx +56 -0
- package/components/ui/scroll-area.tsx +58 -0
- package/components/ui/select.tsx +187 -0
- package/components/ui/separator.tsx +28 -0
- package/components/ui/sheet.tsx +139 -0
- package/components/ui/sidebar.tsx +726 -0
- package/components/ui/skeleton.tsx +13 -0
- package/components/ui/slider.tsx +63 -0
- package/components/ui/sonner.tsx +40 -0
- package/components/ui/spinner.tsx +16 -0
- package/components/ui/switch.tsx +31 -0
- package/components/ui/table.tsx +116 -0
- package/components/ui/tabs.tsx +66 -0
- package/components/ui/textarea.tsx +18 -0
- package/components/ui/toggle-group.tsx +83 -0
- package/components/ui/toggle.tsx +47 -0
- package/components/ui/tooltip.tsx +61 -0
- package/lib/dev-server.js +395 -0
- package/lib/environment-client.js +50 -12
- package/package.json +65 -4
- package/public/themes/blue.css +69 -0
- package/public/themes/default.css +70 -0
- package/public/themes/green.css +69 -0
- package/public/themes/orange.css +69 -0
- package/public/themes/red.css +69 -0
- package/public/themes/rose.css +69 -0
- package/public/themes/violet.css +69 -0
- package/public/themes/yellow.css +69 -0
- package/runtime-hooks/use-mobile.ts +19 -0
- package/runtime-lib/papercrane.ts +49 -0
- package/runtime-lib/utils.ts +6 -0
package/bin/papercrane.js
CHANGED
|
@@ -4,12 +4,81 @@ import { Command } from 'commander';
|
|
|
4
4
|
import chalk from 'chalk';
|
|
5
5
|
import readline from 'readline';
|
|
6
6
|
import fs from 'fs/promises';
|
|
7
|
+
import path from 'path';
|
|
8
|
+
import os from 'os';
|
|
7
9
|
import http from 'http';
|
|
8
10
|
import { http as httpClient } from '../lib/axios-client.js';
|
|
9
11
|
import { setApiKey, clearConfig, isLoggedIn, setDefaultWorkspace, getDefaultWorkspace, setOrgId, getOrgId } from '../lib/config.js';
|
|
10
12
|
import { validateApiKey } from '../lib/cloud-client.js';
|
|
11
13
|
import { listFunctions, getFunction, runFunction, formatDescribe, formatDescribeRoot, formatFlat, formatResult, formatUnconnected } from '../lib/function-client.js';
|
|
12
|
-
import { listWorkspaces, resolveWorkspaceId,
|
|
14
|
+
import { listWorkspaces, resolveWorkspaceId, getLocalWorkspacePath, pullWorkspace, pushWorkspace } from '../lib/environment-client.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Check login and workspace setup. Exits with helpful message if not ready.
|
|
18
|
+
*/
|
|
19
|
+
async function requireWorkspace() {
|
|
20
|
+
const loggedIn = await isLoggedIn();
|
|
21
|
+
if (!loggedIn) {
|
|
22
|
+
console.error(chalk.red('Not logged in. Run: papercrane login'));
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
const defaultWs = await getDefaultWorkspace();
|
|
26
|
+
if (!defaultWs) {
|
|
27
|
+
console.error(chalk.red('No workspace selected.'));
|
|
28
|
+
console.error(chalk.dim('\nRun these commands first:'));
|
|
29
|
+
console.error(chalk.dim(' papercrane workspaces list'));
|
|
30
|
+
console.error(chalk.dim(' papercrane workspaces use <id>'));
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
return defaultWs;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* After login, list workspaces and prompt user to select one.
|
|
38
|
+
*/
|
|
39
|
+
async function showPostLoginHelp() {
|
|
40
|
+
try {
|
|
41
|
+
const workspaces = await listWorkspaces();
|
|
42
|
+
if (!workspaces || workspaces.length === 0) {
|
|
43
|
+
console.log(chalk.dim('No workspaces found. Create one at https://papercrane.ai\n'));
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (workspaces.length === 1) {
|
|
48
|
+
// Auto-select the only workspace
|
|
49
|
+
const ws = workspaces[0];
|
|
50
|
+
await setDefaultWorkspace(ws.id);
|
|
51
|
+
|
|
52
|
+
const workspaceDir = getLocalWorkspacePath(ws.id);
|
|
53
|
+
const { generateScaffolding } = await import('../lib/dev-server.js');
|
|
54
|
+
await generateScaffolding(workspaceDir);
|
|
55
|
+
|
|
56
|
+
const currentLink = path.join(os.homedir(), '.papercrane', 'current');
|
|
57
|
+
try {
|
|
58
|
+
const existing = await fs.readlink(currentLink);
|
|
59
|
+
if (existing !== workspaceDir) {
|
|
60
|
+
await fs.unlink(currentLink);
|
|
61
|
+
await fs.symlink(workspaceDir, currentLink);
|
|
62
|
+
}
|
|
63
|
+
} catch {
|
|
64
|
+
try { await fs.unlink(currentLink); } catch {}
|
|
65
|
+
await fs.symlink(workspaceDir, currentLink);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
console.log(chalk.green(`\n✓ Workspace set to ${ws.id}${ws.name ? ` (${ws.name})` : ''}`));
|
|
69
|
+
console.log(chalk.dim(` Dashboards: ${workspaceDir}/app/\n`));
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
console.log(chalk.bold('\nAvailable workspaces:\n'));
|
|
74
|
+
for (const ws of workspaces) {
|
|
75
|
+
console.log(` ${chalk.bold(ws.id)} ${ws.name || '(unnamed)'}`);
|
|
76
|
+
}
|
|
77
|
+
console.log(chalk.dim('\nNext step: papercrane workspaces use <id>\n'));
|
|
78
|
+
} catch {
|
|
79
|
+
// Non-fatal — workspace listing might fail
|
|
80
|
+
}
|
|
81
|
+
}
|
|
13
82
|
|
|
14
83
|
/**
|
|
15
84
|
* Test if browser can actually open URLs by starting a local server and
|
|
@@ -239,7 +308,8 @@ program
|
|
|
239
308
|
} catch {
|
|
240
309
|
// Non-fatal — org ID is a convenience, not required
|
|
241
310
|
}
|
|
242
|
-
console.log(chalk.green('✓ Successfully logged in
|
|
311
|
+
console.log(chalk.green('✓ Successfully logged in'));
|
|
312
|
+
await showPostLoginHelp();
|
|
243
313
|
} else {
|
|
244
314
|
console.log(chalk.yellow('⚠️ API key saved, but validation failed.'));
|
|
245
315
|
console.log(chalk.yellow(' The key may be invalid or the service may be unavailable.\n'));
|
|
@@ -267,7 +337,8 @@ program
|
|
|
267
337
|
if (data.orgId) await setOrgId(data.orgId);
|
|
268
338
|
await clearPendingSession();
|
|
269
339
|
console.log(chalk.green(`✓ Logged in as ${data.email}`));
|
|
270
|
-
console.log(chalk.dim(` API key saved to ~/.papercrane/config.json
|
|
340
|
+
console.log(chalk.dim(` API key saved to ~/.papercrane/config.json`));
|
|
341
|
+
await showPostLoginHelp();
|
|
271
342
|
return;
|
|
272
343
|
}
|
|
273
344
|
}
|
|
@@ -284,7 +355,8 @@ program
|
|
|
284
355
|
if (result.orgId) await setOrgId(result.orgId);
|
|
285
356
|
|
|
286
357
|
console.log(chalk.green(`✓ Logged in as ${result.email}`));
|
|
287
|
-
console.log(chalk.dim(` API key saved to ~/.papercrane/config.json
|
|
358
|
+
console.log(chalk.dim(` API key saved to ~/.papercrane/config.json`));
|
|
359
|
+
await showPostLoginHelp();
|
|
288
360
|
} catch (error) {
|
|
289
361
|
if (error.message.includes('timed out')) {
|
|
290
362
|
console.error(chalk.red('\nAuthentication timed out. Please try again.'));
|
|
@@ -386,7 +458,7 @@ program
|
|
|
386
458
|
|
|
387
459
|
program
|
|
388
460
|
.command('add [integration]')
|
|
389
|
-
.description('
|
|
461
|
+
.description('Connect a new integration')
|
|
390
462
|
.action(async (integration) => {
|
|
391
463
|
try {
|
|
392
464
|
if (integration) {
|
|
@@ -458,19 +530,22 @@ program
|
|
|
458
530
|
|
|
459
531
|
program
|
|
460
532
|
.command('dashboard-guide')
|
|
461
|
-
.description('Show how to build dashboards
|
|
533
|
+
.description('Show how to build dashboards')
|
|
462
534
|
.action(async () => {
|
|
463
|
-
console.log(chalk.bold('\n📊 Building Dashboards
|
|
464
|
-
console.log(chalk.dim('
|
|
535
|
+
console.log(chalk.bold('\n📊 Building Dashboards with Papercrane\n'));
|
|
536
|
+
console.log(chalk.dim('Each dashboard is a folder with two files: page.tsx (UI) and action.ts (data).\n'));
|
|
465
537
|
|
|
466
538
|
console.log(chalk.bold('FILE STRUCTURE'));
|
|
467
539
|
console.log(chalk.dim('─'.repeat(60)));
|
|
468
540
|
console.log(`
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
541
|
+
~/.papercrane/current/app/
|
|
542
|
+
├── layout.tsx # Auto-generated by CLI
|
|
543
|
+
├── globals.css # Auto-generated by CLI
|
|
544
|
+
└── my-dashboard/ # Your dashboard
|
|
545
|
+
├── action.ts # Server-side data fetching
|
|
546
|
+
└── page.tsx # Client component with charts
|
|
547
|
+
|
|
548
|
+
Set active workspace: papercrane workspaces use <id>
|
|
474
549
|
`);
|
|
475
550
|
|
|
476
551
|
console.log(chalk.bold('1. action.ts - Server Action Pattern'));
|
|
@@ -577,15 +652,18 @@ Use these Tailwind classes for theme-aware colors:
|
|
|
577
652
|
console.log(chalk.bold('QUICK START'));
|
|
578
653
|
console.log(chalk.dim('─'.repeat(60)));
|
|
579
654
|
console.log(`
|
|
580
|
-
1. ${chalk.cyan('papercrane')}
|
|
581
|
-
2. ${chalk.cyan('papercrane
|
|
582
|
-
3. ${chalk.cyan('papercrane
|
|
655
|
+
1. ${chalk.cyan('papercrane login')} # Authenticate with Papercrane
|
|
656
|
+
2. ${chalk.cyan('papercrane workspaces list')} # See available workspaces
|
|
657
|
+
3. ${chalk.cyan('papercrane workspaces use <id>')} # Set active workspace
|
|
583
658
|
4. ${chalk.cyan('papercrane pull')} # Pull workspace files locally
|
|
584
|
-
5.
|
|
585
|
-
6. ${chalk.cyan('papercrane
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
${chalk.dim('
|
|
659
|
+
5. ${chalk.cyan('papercrane dev')} # Start local dev server with HMR
|
|
660
|
+
6. Create dashboards in ${chalk.cyan('~/.papercrane/current/app/')}
|
|
661
|
+
7. ${chalk.cyan('papercrane push')} # Push to cloud, get shareable URLs
|
|
662
|
+
|
|
663
|
+
${chalk.dim('Explore APIs:')}
|
|
664
|
+
${chalk.cyan('papercrane')} # List connected integrations
|
|
665
|
+
${chalk.cyan('papercrane describe <path>')} # Explore endpoints
|
|
666
|
+
${chalk.cyan('papercrane call <path> \'{}\'')} # Test data fetching
|
|
589
667
|
`);
|
|
590
668
|
console.log();
|
|
591
669
|
});
|
|
@@ -634,7 +712,8 @@ workspacesCmd
|
|
|
634
712
|
workspacesCmd
|
|
635
713
|
.command('use <id>')
|
|
636
714
|
.description('Set the default workspace')
|
|
637
|
-
.
|
|
715
|
+
.option('--reset-scaffolding', 'Regenerate scaffolding files (layout.tsx, globals.css, configs)')
|
|
716
|
+
.action(async (id, options) => {
|
|
638
717
|
try {
|
|
639
718
|
const loggedIn = await isLoggedIn();
|
|
640
719
|
if (!loggedIn) {
|
|
@@ -649,206 +728,78 @@ workspacesCmd
|
|
|
649
728
|
}
|
|
650
729
|
|
|
651
730
|
await setDefaultWorkspace(wsId);
|
|
652
|
-
console.log(chalk.green(`✓ Default workspace set to ${wsId}\n`));
|
|
653
|
-
} catch (error) {
|
|
654
|
-
console.error(chalk.red('Error:'), error.message);
|
|
655
|
-
process.exit(1);
|
|
656
|
-
}
|
|
657
|
-
});
|
|
658
|
-
|
|
659
|
-
// ============================================================================
|
|
660
|
-
// FILE COMMANDS
|
|
661
|
-
// ============================================================================
|
|
662
731
|
|
|
663
|
-
|
|
732
|
+
// Scaffold the workspace and create ~/.papercrane/current symlink
|
|
733
|
+
const { generateScaffolding, resetScaffolding } = await import('../lib/dev-server.js');
|
|
734
|
+
const workspaceDir = getLocalWorkspacePath(wsId);
|
|
664
735
|
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
.action(async (path, options) => {
|
|
670
|
-
try {
|
|
671
|
-
const loggedIn = await isLoggedIn();
|
|
672
|
-
if (!loggedIn) {
|
|
673
|
-
console.error(chalk.red('Error: Not logged in. Run: papercrane login'));
|
|
674
|
-
process.exit(1);
|
|
736
|
+
if (options.resetScaffolding) {
|
|
737
|
+
await resetScaffolding(workspaceDir);
|
|
738
|
+
await fs.rm(path.join(workspaceDir, '.next'), { recursive: true, force: true });
|
|
739
|
+
console.log(chalk.dim('Scaffolding regenerated.'));
|
|
675
740
|
}
|
|
741
|
+
await generateScaffolding(workspaceDir);
|
|
676
742
|
|
|
677
|
-
const
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
});
|
|
688
|
-
|
|
689
|
-
filesCmd
|
|
690
|
-
.command('read <path>')
|
|
691
|
-
.description('Read file contents')
|
|
692
|
-
.option('-w, --workspace <id>', 'Workspace ID (uses default if not specified)')
|
|
693
|
-
.option('--raw', 'Output raw content without formatting')
|
|
694
|
-
.action(async (path, options) => {
|
|
695
|
-
try {
|
|
696
|
-
const loggedIn = await isLoggedIn();
|
|
697
|
-
if (!loggedIn) {
|
|
698
|
-
console.error(chalk.red('Error: Not logged in. Run: papercrane login'));
|
|
699
|
-
process.exit(1);
|
|
743
|
+
const currentLink = path.join(os.homedir(), '.papercrane', 'current');
|
|
744
|
+
try {
|
|
745
|
+
const existing = await fs.readlink(currentLink);
|
|
746
|
+
if (existing !== workspaceDir) {
|
|
747
|
+
await fs.unlink(currentLink);
|
|
748
|
+
await fs.symlink(workspaceDir, currentLink);
|
|
749
|
+
}
|
|
750
|
+
} catch {
|
|
751
|
+
try { await fs.unlink(currentLink); } catch {}
|
|
752
|
+
await fs.symlink(workspaceDir, currentLink);
|
|
700
753
|
}
|
|
701
754
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
if (options.raw) {
|
|
706
|
-
process.stdout.write(result.content);
|
|
707
|
-
} else {
|
|
708
|
-
console.log(chalk.dim(`# ${result.path} (${result.size} bytes) — use this path for write/edit/delete\n`));
|
|
709
|
-
console.log(result.content);
|
|
710
|
-
}
|
|
755
|
+
console.log(chalk.green(`✓ Default workspace set to ${wsId}`));
|
|
756
|
+
console.log(chalk.dim(` ${currentLink} → ${workspaceDir}`));
|
|
757
|
+
console.log(chalk.dim(` Dashboards: ${workspaceDir}/app/\n`));
|
|
711
758
|
} catch (error) {
|
|
712
759
|
console.error(chalk.red('Error:'), error.message);
|
|
713
760
|
process.exit(1);
|
|
714
761
|
}
|
|
715
762
|
});
|
|
716
763
|
|
|
717
|
-
filesCmd
|
|
718
|
-
.command('write <path> [content]')
|
|
719
|
-
.description('Write content to a file. Path is relative to workspace root (same paths as files list/read).\n\nExamples:\n papercrane files write app/page.tsx --json \'"use client";\\nexport default function() {}"\'\n papercrane files write app/action.ts -f ./local-file.ts')
|
|
720
|
-
.option('-w, --workspace <id>', 'Workspace ID (uses default if not specified)')
|
|
721
|
-
.option('-f, --file <localPath>', 'Read content from a local file')
|
|
722
|
-
.option('--json <string>', 'Content as a JSON-encoded string (recommended for code)')
|
|
723
|
-
.option('--stdin', 'Read content from stdin')
|
|
724
|
-
.action(async (path, contentArg, options) => {
|
|
725
|
-
try {
|
|
726
|
-
const loggedIn = await isLoggedIn();
|
|
727
|
-
if (!loggedIn) {
|
|
728
|
-
console.error(chalk.red('Error: Not logged in. Run: papercrane login'));
|
|
729
|
-
process.exit(1);
|
|
730
|
-
}
|
|
731
|
-
|
|
732
|
-
let content;
|
|
733
|
-
|
|
734
|
-
if (options.json) {
|
|
735
|
-
// Parse JSON-encoded string
|
|
736
|
-
try {
|
|
737
|
-
content = JSON.parse(options.json);
|
|
738
|
-
if (typeof content !== 'string') {
|
|
739
|
-
console.error(chalk.red('Error: --json must be a JSON string, e.g., \'"hello\\nworld"\''));
|
|
740
|
-
process.exit(1);
|
|
741
|
-
}
|
|
742
|
-
} catch (e) {
|
|
743
|
-
console.error(chalk.red('Error: Invalid JSON string'));
|
|
744
|
-
console.error(chalk.dim(' Expected format: \'"line1\\nline2"\''));
|
|
745
|
-
process.exit(1);
|
|
746
|
-
}
|
|
747
|
-
} else if (options.file) {
|
|
748
|
-
// Read from local file
|
|
749
|
-
content = await fs.readFile(options.file, 'utf-8');
|
|
750
|
-
} else if (options.stdin) {
|
|
751
|
-
// Read from stdin
|
|
752
|
-
content = await readStdin();
|
|
753
|
-
} else if (contentArg) {
|
|
754
|
-
// Use positional argument
|
|
755
|
-
content = contentArg;
|
|
756
|
-
} else {
|
|
757
|
-
console.error(chalk.red('Error: Content required. Provide --json, --file, --stdin, or as argument'));
|
|
758
|
-
process.exit(1);
|
|
759
|
-
}
|
|
760
|
-
|
|
761
|
-
const wsId = await resolveWorkspaceId(options.workspace);
|
|
762
|
-
const result = await writeFile(wsId, path, content);
|
|
763
764
|
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
console.error(chalk.red('Error: 404 — workspace sandbox may be stopped or unreachable'));
|
|
768
|
-
console.error(chalk.dim(` Path: ${path}`));
|
|
769
|
-
console.error(chalk.dim(' Check workspace status: papercrane workspaces list'));
|
|
770
|
-
} else {
|
|
771
|
-
console.error(chalk.red('Error:'), error.response?.data?.error || error.message);
|
|
772
|
-
}
|
|
773
|
-
process.exit(1);
|
|
774
|
-
}
|
|
775
|
-
});
|
|
765
|
+
// ============================================================================
|
|
766
|
+
// DEV COMMAND
|
|
767
|
+
// ============================================================================
|
|
776
768
|
|
|
777
|
-
|
|
778
|
-
.command('
|
|
779
|
-
.description('
|
|
780
|
-
.option('-w, --workspace <id>', 'Workspace ID
|
|
781
|
-
.option('--
|
|
782
|
-
.option('--
|
|
783
|
-
.option('--
|
|
784
|
-
.
|
|
785
|
-
.option('--stdin', 'Read parameters as JSON from stdin')
|
|
786
|
-
.action(async (path, options) => {
|
|
769
|
+
program
|
|
770
|
+
.command('dev')
|
|
771
|
+
.description('Start local dev server for dashboard development with HMR')
|
|
772
|
+
.option('-w, --workspace <id>', 'Workspace ID')
|
|
773
|
+
.option('-p, --port <port>', 'Port (default: 3100)', '3100')
|
|
774
|
+
.option('--clear-cache', 'Clear build cache before starting (use after structural changes like editing globals.css or adding dependencies)')
|
|
775
|
+
.option('--reset-scaffolding', 'Regenerate scaffolding files (layout.tsx, globals.css, configs) and clear build cache')
|
|
776
|
+
.action(async (options) => {
|
|
787
777
|
try {
|
|
788
|
-
const
|
|
789
|
-
if (!loggedIn) {
|
|
790
|
-
console.error(chalk.red('Error: Not logged in. Run: papercrane login'));
|
|
791
|
-
process.exit(1);
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
let oldString, newString, replaceAll = false;
|
|
795
|
-
|
|
796
|
-
if (options.stdin) {
|
|
797
|
-
// Read JSON from stdin
|
|
798
|
-
const input = await readStdin();
|
|
799
|
-
const params = JSON.parse(input);
|
|
800
|
-
oldString = params.old_string;
|
|
801
|
-
newString = params.new_string;
|
|
802
|
-
replaceAll = params.replace_all || false;
|
|
803
|
-
} else if (options.params) {
|
|
804
|
-
// Parse JSON params
|
|
805
|
-
const params = JSON.parse(options.params);
|
|
806
|
-
oldString = params.old_string;
|
|
807
|
-
newString = params.new_string;
|
|
808
|
-
replaceAll = params.replace_all || false;
|
|
809
|
-
} else if (options.old !== undefined && options.new !== undefined) {
|
|
810
|
-
// Use --old and --new flags
|
|
811
|
-
oldString = options.old;
|
|
812
|
-
newString = options.new;
|
|
813
|
-
replaceAll = options.replaceAll || false;
|
|
814
|
-
} else {
|
|
815
|
-
console.error(chalk.red('Error: Must provide --old and --new, or --params, or --stdin'));
|
|
816
|
-
process.exit(1);
|
|
817
|
-
}
|
|
778
|
+
const { generateScaffolding, resetScaffolding, startDevServer } = await import('../lib/dev-server.js');
|
|
818
779
|
|
|
819
|
-
const wsId =
|
|
820
|
-
const
|
|
780
|
+
const wsId = options.workspace ? parseInt(options.workspace, 10) : await requireWorkspace();
|
|
781
|
+
const workspaceDir = getLocalWorkspacePath(wsId);
|
|
821
782
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
console.error(chalk.red('Error:'), error.response.data.error);
|
|
826
|
-
if (error.response.data.occurrences) {
|
|
827
|
-
console.error(chalk.dim(` Found ${error.response.data.occurrences} occurrences. Use --replace-all to replace all.`));
|
|
828
|
-
}
|
|
829
|
-
} else {
|
|
830
|
-
console.error(chalk.red('Error:'), error.message);
|
|
783
|
+
if (options.resetScaffolding) {
|
|
784
|
+
await resetScaffolding(workspaceDir);
|
|
785
|
+
console.log(chalk.dim('Scaffolding regenerated.'));
|
|
831
786
|
}
|
|
832
|
-
process.exit(1);
|
|
833
|
-
}
|
|
834
|
-
});
|
|
835
787
|
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
.action(async (path, options) => {
|
|
841
|
-
try {
|
|
842
|
-
const loggedIn = await isLoggedIn();
|
|
843
|
-
if (!loggedIn) {
|
|
844
|
-
console.error(chalk.red('Error: Not logged in. Run: papercrane login'));
|
|
845
|
-
process.exit(1);
|
|
788
|
+
if (options.clearCache || options.resetScaffolding) {
|
|
789
|
+
const nextDir = path.join(workspaceDir, '.next');
|
|
790
|
+
await fs.rm(nextDir, { recursive: true, force: true });
|
|
791
|
+
console.log(chalk.dim('Build cache cleared.'));
|
|
846
792
|
}
|
|
847
793
|
|
|
848
|
-
|
|
849
|
-
|
|
794
|
+
await generateScaffolding(workspaceDir);
|
|
795
|
+
|
|
796
|
+
const port = parseInt(options.port, 10);
|
|
797
|
+
const dashboardDir = workspaceDir + '/app';
|
|
798
|
+
console.log(chalk.green(`\nDev server starting on http://localhost:${port}`));
|
|
799
|
+
console.log(chalk.dim(`Dashboard dir: ${dashboardDir}`));
|
|
800
|
+
console.log(chalk.dim('Create dashboard folders with page.tsx + action.ts\n'));
|
|
850
801
|
|
|
851
|
-
|
|
802
|
+
await startDevServer(workspaceDir, port);
|
|
852
803
|
} catch (error) {
|
|
853
804
|
console.error(chalk.red('Error:'), error.message);
|
|
854
805
|
process.exit(1);
|
|
@@ -865,13 +816,7 @@ program
|
|
|
865
816
|
.option('-w, --workspace <id>', 'Workspace ID (uses default if not specified)')
|
|
866
817
|
.action(async (options) => {
|
|
867
818
|
try {
|
|
868
|
-
const
|
|
869
|
-
if (!loggedIn) {
|
|
870
|
-
console.error(chalk.red('Error: Not logged in. Run: papercrane login'));
|
|
871
|
-
process.exit(1);
|
|
872
|
-
}
|
|
873
|
-
|
|
874
|
-
const wsId = await resolveWorkspaceId(options.workspace);
|
|
819
|
+
const wsId = options.workspace ? parseInt(options.workspace, 10) : await requireWorkspace();
|
|
875
820
|
|
|
876
821
|
console.log(chalk.cyan(`Pulling workspace ${wsId}...`));
|
|
877
822
|
|
|
@@ -879,9 +824,15 @@ program
|
|
|
879
824
|
process.stdout.write(`\r ${chalk.dim(`[${current}/${total}]`)} ${file}`);
|
|
880
825
|
});
|
|
881
826
|
|
|
827
|
+
// Set up dev server infrastructure after pull
|
|
828
|
+
const { generateScaffolding } = await import('../lib/dev-server.js');
|
|
829
|
+
await generateScaffolding(localPath);
|
|
830
|
+
|
|
882
831
|
console.log('\n');
|
|
883
832
|
console.log(chalk.green(`✓ Pulled ${files.length} files to ${localPath}\n`));
|
|
884
|
-
console.log(chalk.dim('
|
|
833
|
+
console.log(chalk.dim('Dashboards are in: ' + localPath + '/app/'));
|
|
834
|
+
console.log(chalk.dim('Start local dev server: papercrane dev'));
|
|
835
|
+
console.log(chalk.dim('Push changes back: papercrane push'));
|
|
885
836
|
} catch (error) {
|
|
886
837
|
console.error(chalk.red('Error:'), error.message);
|
|
887
838
|
process.exit(1);
|
|
@@ -890,18 +841,12 @@ program
|
|
|
890
841
|
|
|
891
842
|
program
|
|
892
843
|
.command('push')
|
|
893
|
-
.description('Push
|
|
844
|
+
.description('Push dashboards and resources to cloud for sharing')
|
|
894
845
|
.option('-w, --workspace <id>', 'Workspace ID (uses default if not specified)')
|
|
895
|
-
.option('-p, --path <path>', 'Only push files under this path (e.g., "
|
|
846
|
+
.option('-p, --path <path>', 'Only push files under this path (e.g., "ga4-dashboard")')
|
|
896
847
|
.action(async (options) => {
|
|
897
848
|
try {
|
|
898
|
-
const
|
|
899
|
-
if (!loggedIn) {
|
|
900
|
-
console.error(chalk.red('Error: Not logged in. Run: papercrane login'));
|
|
901
|
-
process.exit(1);
|
|
902
|
-
}
|
|
903
|
-
|
|
904
|
-
const wsId = await resolveWorkspaceId(options.workspace);
|
|
849
|
+
const wsId = options.workspace ? parseInt(options.workspace, 10) : await requireWorkspace();
|
|
905
850
|
const localPath = getLocalWorkspacePath(wsId);
|
|
906
851
|
const pathFilter = options.path;
|
|
907
852
|
|
|
@@ -912,19 +857,20 @@ program
|
|
|
912
857
|
}, pathFilter);
|
|
913
858
|
|
|
914
859
|
console.log('\n');
|
|
915
|
-
console.log(chalk.green(`✓ Pushed ${files.length} files
|
|
860
|
+
console.log(chalk.green(`✓ Pushed ${files.length} files to cloud`));
|
|
916
861
|
|
|
917
|
-
// Print
|
|
862
|
+
// Print shareable URLs
|
|
918
863
|
const { getApiBaseUrl, getOrgId: getStoredOrgId } = await import('../lib/config.js');
|
|
919
864
|
const baseUrl = await getApiBaseUrl();
|
|
920
865
|
const orgId = await getStoredOrgId();
|
|
921
866
|
|
|
922
|
-
|
|
923
|
-
|
|
867
|
+
const dashboards = [...new Set(files
|
|
868
|
+
.filter(f => f.startsWith('app/'))
|
|
869
|
+
.map(f => f.split('/')[1])
|
|
870
|
+
.filter(d => d && !d.includes('.')))];
|
|
924
871
|
|
|
925
872
|
if (dashboards.length > 0) {
|
|
926
873
|
console.log('');
|
|
927
|
-
console.log(chalk.dim('Preview:'));
|
|
928
874
|
const pathPrefix = orgId ? `/org/${orgId}` : '';
|
|
929
875
|
for (const dashboard of dashboards) {
|
|
930
876
|
console.log(chalk.cyan(` ${baseUrl}${pathPrefix}/environments/${wsId}/${dashboard}`));
|
|
@@ -948,29 +894,6 @@ function readStdin() {
|
|
|
948
894
|
});
|
|
949
895
|
}
|
|
950
896
|
|
|
951
|
-
// Helper to print file tree recursively
|
|
952
|
-
function printFileTree(tree, indent = '') {
|
|
953
|
-
if (!tree || !Array.isArray(tree)) return;
|
|
954
|
-
|
|
955
|
-
// Sort: folders first, then files
|
|
956
|
-
const sorted = [...tree].sort((a, b) => {
|
|
957
|
-
if (a.type === 'folder' && b.type !== 'folder') return -1;
|
|
958
|
-
if (a.type !== 'folder' && b.type === 'folder') return 1;
|
|
959
|
-
return a.name.localeCompare(b.name);
|
|
960
|
-
});
|
|
961
|
-
|
|
962
|
-
for (const item of sorted) {
|
|
963
|
-
if (item.type === 'folder') {
|
|
964
|
-
console.log(`${indent}${chalk.blue(item.name + '/')}`);
|
|
965
|
-
if (item.children) {
|
|
966
|
-
printFileTree(item.children, indent + ' ');
|
|
967
|
-
}
|
|
968
|
-
} else {
|
|
969
|
-
console.log(`${indent}${item.name}`);
|
|
970
|
-
}
|
|
971
|
-
}
|
|
972
|
-
}
|
|
973
|
-
|
|
974
897
|
// Default action: show connected modules when no command is given
|
|
975
898
|
program.action(async (_, cmd) => {
|
|
976
899
|
// Reject unknown commands instead of silently falling through
|
|
@@ -986,13 +909,9 @@ program.action(async (_, cmd) => {
|
|
|
986
909
|
console.error(' papercrane dashboard-guide Show how to build dashboards');
|
|
987
910
|
console.error(' papercrane workspaces list List workspaces');
|
|
988
911
|
console.error(' papercrane workspaces use <id> Set default workspace');
|
|
989
|
-
console.error(' papercrane
|
|
990
|
-
console.error(' papercrane files read <path> Read file contents');
|
|
991
|
-
console.error(' papercrane files write <path> Write file contents');
|
|
992
|
-
console.error(' papercrane files edit <path> Edit file with find/replace');
|
|
993
|
-
console.error(' papercrane files delete <path> Delete a file');
|
|
912
|
+
console.error(' papercrane dev Start local dev server with HMR');
|
|
994
913
|
console.error(' papercrane pull Pull workspace files locally for editing');
|
|
995
|
-
console.error(' papercrane push Push
|
|
914
|
+
console.error(' papercrane push Push to cloud, get shareable URLs');
|
|
996
915
|
console.error(' papercrane login Log in with API key');
|
|
997
916
|
console.error(' papercrane logout Log out');
|
|
998
917
|
process.exit(1);
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use client"
|
|
2
|
+
|
|
3
|
+
import { useEffect, useRef } from "react"
|
|
4
|
+
|
|
5
|
+
interface CommandHandler {
|
|
6
|
+
handleCommand: (command: { action: string; value?: string }) => void
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function CommandListener() {
|
|
10
|
+
const handlerRef = useRef<CommandHandler | null>(null)
|
|
11
|
+
|
|
12
|
+
// Try to load the plugin and set up the command handler
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
let cancelled = false
|
|
15
|
+
|
|
16
|
+
async function init() {
|
|
17
|
+
try {
|
|
18
|
+
const pkg = "@papercrane/" + "dashboard-grid/plugin"
|
|
19
|
+
const mod = await import(/* turbopackIgnore: true */ pkg)
|
|
20
|
+
if (!cancelled && mod.createCommandHandler) {
|
|
21
|
+
handlerRef.current = mod.createCommandHandler()
|
|
22
|
+
}
|
|
23
|
+
} catch {
|
|
24
|
+
// Plugin not installed — commands are silently ignored
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
init()
|
|
29
|
+
return () => { cancelled = true }
|
|
30
|
+
}, [])
|
|
31
|
+
|
|
32
|
+
useEffect(() => {
|
|
33
|
+
// Only listen when embedded in an iframe
|
|
34
|
+
if (typeof window === "undefined" || window === window.parent) {
|
|
35
|
+
return
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const handleMessage = (event: MessageEvent) => {
|
|
39
|
+
const data = event.data
|
|
40
|
+
if (!data || typeof data !== "object" || data.type !== "papercrane-command") {
|
|
41
|
+
return
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
handlerRef.current?.handleCommand(data.command)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
window.addEventListener("message", handleMessage)
|
|
48
|
+
return () => window.removeEventListener("message", handleMessage)
|
|
49
|
+
}, [])
|
|
50
|
+
|
|
51
|
+
return null
|
|
52
|
+
}
|