@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.
Files changed (74) hide show
  1. package/bin/papercrane.js +170 -251
  2. package/components/command-listener.tsx +52 -0
  3. package/components/dashboard-grid.tsx +61 -0
  4. package/components/error-reporter.tsx +112 -0
  5. package/components/theme-provider.tsx +10 -0
  6. package/components/theme-switcher.tsx +54 -0
  7. package/components/theme-toggle.tsx +21 -0
  8. package/components/ui/accordion.tsx +66 -0
  9. package/components/ui/alert-dialog.tsx +157 -0
  10. package/components/ui/alert.tsx +66 -0
  11. package/components/ui/aspect-ratio.tsx +11 -0
  12. package/components/ui/avatar.tsx +53 -0
  13. package/components/ui/badge.tsx +46 -0
  14. package/components/ui/breadcrumb.tsx +109 -0
  15. package/components/ui/button-group.tsx +83 -0
  16. package/components/ui/button.tsx +60 -0
  17. package/components/ui/calendar.tsx +216 -0
  18. package/components/ui/card.tsx +92 -0
  19. package/components/ui/carousel.tsx +241 -0
  20. package/components/ui/chart.tsx +357 -0
  21. package/components/ui/checkbox.tsx +32 -0
  22. package/components/ui/collapsible.tsx +33 -0
  23. package/components/ui/command.tsx +184 -0
  24. package/components/ui/context-menu.tsx +252 -0
  25. package/components/ui/dialog.tsx +143 -0
  26. package/components/ui/drawer.tsx +135 -0
  27. package/components/ui/dropdown-menu.tsx +257 -0
  28. package/components/ui/empty.tsx +104 -0
  29. package/components/ui/field.tsx +248 -0
  30. package/components/ui/form.tsx +167 -0
  31. package/components/ui/hover-card.tsx +44 -0
  32. package/components/ui/input-group.tsx +170 -0
  33. package/components/ui/input-otp.tsx +77 -0
  34. package/components/ui/input.tsx +21 -0
  35. package/components/ui/item.tsx +193 -0
  36. package/components/ui/kbd.tsx +28 -0
  37. package/components/ui/label.tsx +24 -0
  38. package/components/ui/menubar.tsx +276 -0
  39. package/components/ui/navigation-menu.tsx +168 -0
  40. package/components/ui/pagination.tsx +127 -0
  41. package/components/ui/popover.tsx +48 -0
  42. package/components/ui/progress.tsx +31 -0
  43. package/components/ui/radio-group.tsx +45 -0
  44. package/components/ui/resizable.tsx +56 -0
  45. package/components/ui/scroll-area.tsx +58 -0
  46. package/components/ui/select.tsx +187 -0
  47. package/components/ui/separator.tsx +28 -0
  48. package/components/ui/sheet.tsx +139 -0
  49. package/components/ui/sidebar.tsx +726 -0
  50. package/components/ui/skeleton.tsx +13 -0
  51. package/components/ui/slider.tsx +63 -0
  52. package/components/ui/sonner.tsx +40 -0
  53. package/components/ui/spinner.tsx +16 -0
  54. package/components/ui/switch.tsx +31 -0
  55. package/components/ui/table.tsx +116 -0
  56. package/components/ui/tabs.tsx +66 -0
  57. package/components/ui/textarea.tsx +18 -0
  58. package/components/ui/toggle-group.tsx +83 -0
  59. package/components/ui/toggle.tsx +47 -0
  60. package/components/ui/tooltip.tsx +61 -0
  61. package/lib/dev-server.js +395 -0
  62. package/lib/environment-client.js +50 -12
  63. package/package.json +65 -4
  64. package/public/themes/blue.css +69 -0
  65. package/public/themes/default.css +70 -0
  66. package/public/themes/green.css +69 -0
  67. package/public/themes/orange.css +69 -0
  68. package/public/themes/red.css +69 -0
  69. package/public/themes/rose.css +69 -0
  70. package/public/themes/violet.css +69 -0
  71. package/public/themes/yellow.css +69 -0
  72. package/runtime-hooks/use-mobile.ts +19 -0
  73. package/runtime-lib/papercrane.ts +49 -0
  74. package/runtime-lib/utils.ts +6 -0
@@ -0,0 +1,395 @@
1
+ import path from 'path';
2
+ import fs from 'fs/promises';
3
+ import fsSync from 'fs';
4
+ import os from 'os';
5
+ import { fileURLToPath } from 'url';
6
+ import { execSync } from 'child_process';
7
+
8
+ import { createRequire } from 'module';
9
+ const require = createRequire(import.meta.url);
10
+
11
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
+ const cliRoot = path.resolve(__dirname, '..');
13
+ const cliVersion = require(path.join(cliRoot, 'package.json')).version;
14
+
15
+ // Local dev: running from source (e.g. tools/papercrane-cli/).
16
+ // Published: running from inside node_modules/@papercraneai/cli/.
17
+ const isLocalDev = !cliRoot.includes('node_modules');
18
+
19
+ // ---------------------------------------------------------------------------
20
+ // Templates
21
+ // ---------------------------------------------------------------------------
22
+
23
+ const BT = '`'; // backtick character for use in templates
24
+
25
+ const LAYOUT_TEMPLATE = `import type { Metadata } from "next";
26
+ import { Geist, Geist_Mono } from "next/font/google";
27
+ import { ThemeProvider } from "@/components/theme-provider";
28
+ import { ErrorReporter } from "@/components/error-reporter";
29
+ import { CommandListener } from "@/components/command-listener";
30
+ import "./globals.css";
31
+
32
+ const geistSans = Geist({
33
+ variable: "--font-geist-sans",
34
+ subsets: ["latin"],
35
+ });
36
+
37
+ const geistMono = Geist_Mono({
38
+ variable: "--font-geist-mono",
39
+ subsets: ["latin"],
40
+ });
41
+
42
+ export const metadata: Metadata = {
43
+ title: "Dashboard",
44
+ description: "Local dashboard preview",
45
+ };
46
+
47
+ const themeInitScript = ${BT}
48
+ (function() {
49
+ try {
50
+ var params = new URLSearchParams(window.location.search);
51
+ var theme = params.get('theme');
52
+ var color = params.get('color');
53
+ if (theme === 'dark') {
54
+ document.documentElement.classList.add('dark');
55
+ localStorage.setItem('theme', 'dark');
56
+ } else if (theme === 'light') {
57
+ document.documentElement.classList.remove('dark');
58
+ localStorage.setItem('theme', 'light');
59
+ }
60
+ if (color && color !== 'default') {
61
+ var link = document.createElement('link');
62
+ link.id = 'color-theme-stylesheet';
63
+ link.rel = 'stylesheet';
64
+ link.href = '/themes/' + color + '.css';
65
+ document.head.appendChild(link);
66
+ }
67
+ } catch (e) {}
68
+ })();
69
+ ${BT};
70
+
71
+ export default function RootLayout({
72
+ children,
73
+ }: Readonly<{
74
+ children: React.ReactNode;
75
+ }>) {
76
+ return (
77
+ <html lang="en" suppressHydrationWarning>
78
+ <head>
79
+ <script dangerouslySetInnerHTML={{ __html: themeInitScript }} />
80
+ </head>
81
+ <body
82
+ className={${BT}\${geistSans.variable} \${geistMono.variable} antialiased${BT}}
83
+ >
84
+ <ThemeProvider
85
+ attribute="class"
86
+ defaultTheme="system"
87
+ enableSystem
88
+ disableTransitionOnChange
89
+ >
90
+ <ErrorReporter />
91
+ <CommandListener />
92
+ {children}
93
+ </ThemeProvider>
94
+ </body>
95
+ </html>
96
+ );
97
+ }
98
+ `;
99
+
100
+ const GLOBALS_CSS_TEMPLATE = `@import "tailwindcss";
101
+ @import "tw-animate-css";
102
+
103
+ @source "../node_modules/@papercraneai/cli/components";
104
+
105
+ @custom-variant dark (&:is(.dark *));
106
+
107
+ @theme inline {
108
+ --color-background: var(--background);
109
+ --color-foreground: var(--foreground);
110
+ --font-sans: var(--font-geist-sans);
111
+ --font-mono: var(--font-geist-mono);
112
+ --color-sidebar-ring: var(--sidebar-ring);
113
+ --color-sidebar-border: var(--sidebar-border);
114
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
115
+ --color-sidebar-accent: var(--sidebar-accent);
116
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
117
+ --color-sidebar-primary: var(--sidebar-primary);
118
+ --color-sidebar-foreground: var(--sidebar-foreground);
119
+ --color-sidebar: var(--sidebar);
120
+ --color-chart-5: var(--chart-5);
121
+ --color-chart-4: var(--chart-4);
122
+ --color-chart-3: var(--chart-3);
123
+ --color-chart-2: var(--chart-2);
124
+ --color-chart-1: var(--chart-1);
125
+ --color-ring: var(--ring);
126
+ --color-input: var(--input);
127
+ --color-border: var(--border);
128
+ --color-destructive: var(--destructive);
129
+ --color-accent-foreground: var(--accent-foreground);
130
+ --color-accent: var(--accent);
131
+ --color-muted-foreground: var(--muted-foreground);
132
+ --color-muted: var(--muted);
133
+ --color-secondary-foreground: var(--secondary-foreground);
134
+ --color-secondary: var(--secondary);
135
+ --color-primary-foreground: var(--primary-foreground);
136
+ --color-primary: var(--primary);
137
+ --color-popover-foreground: var(--popover-foreground);
138
+ --color-popover: var(--popover);
139
+ --color-card-foreground: var(--card-foreground);
140
+ --color-card: var(--card);
141
+ --radius-sm: calc(var(--radius) - 4px);
142
+ --radius-md: calc(var(--radius) - 2px);
143
+ --radius-lg: var(--radius);
144
+ --radius-xl: calc(var(--radius) + 4px);
145
+ }
146
+
147
+ :root {
148
+ --radius: 0.65rem;
149
+ --background: oklch(1 0 0);
150
+ --foreground: oklch(0.145 0 0);
151
+ --card: oklch(1 0 0);
152
+ --card-foreground: oklch(0.145 0 0);
153
+ --popover: oklch(1 0 0);
154
+ --popover-foreground: oklch(0.145 0 0);
155
+ --primary: oklch(0.205 0 0);
156
+ --primary-foreground: oklch(0.985 0 0);
157
+ --secondary: oklch(0.97 0 0);
158
+ --secondary-foreground: oklch(0.205 0 0);
159
+ --muted: oklch(0.97 0 0);
160
+ --muted-foreground: oklch(0.556 0 0);
161
+ --accent: oklch(0.97 0 0);
162
+ --accent-foreground: oklch(0.205 0 0);
163
+ --destructive: oklch(0.577 0.245 27.325);
164
+ --border: oklch(0.922 0 0);
165
+ --input: oklch(0.922 0 0);
166
+ --ring: oklch(0.708 0 0);
167
+ --chart-1: oklch(0.646 0.222 41.116);
168
+ --chart-2: oklch(0.6 0.118 184.704);
169
+ --chart-3: oklch(0.398 0.07 227.392);
170
+ --chart-4: oklch(0.828 0.189 84.429);
171
+ --chart-5: oklch(0.769 0.188 70.08);
172
+ --sidebar: oklch(0.985 0 0);
173
+ --sidebar-foreground: oklch(0.145 0 0);
174
+ --sidebar-primary: oklch(0.205 0 0);
175
+ --sidebar-primary-foreground: oklch(0.985 0 0);
176
+ --sidebar-accent: oklch(0.97 0 0);
177
+ --sidebar-accent-foreground: oklch(0.205 0 0);
178
+ --sidebar-border: oklch(0.922 0 0);
179
+ --sidebar-ring: oklch(0.708 0 0);
180
+ }
181
+
182
+ .dark {
183
+ --background: oklch(0.145 0 0);
184
+ --foreground: oklch(0.985 0 0);
185
+ --card: oklch(0.205 0 0);
186
+ --card-foreground: oklch(0.985 0 0);
187
+ --popover: oklch(0.205 0 0);
188
+ --popover-foreground: oklch(0.985 0 0);
189
+ --primary: oklch(0.922 0 0);
190
+ --primary-foreground: oklch(0.205 0 0);
191
+ --secondary: oklch(0.269 0 0);
192
+ --secondary-foreground: oklch(0.985 0 0);
193
+ --muted: oklch(0.269 0 0);
194
+ --muted-foreground: oklch(0.708 0 0);
195
+ --accent: oklch(0.269 0 0);
196
+ --accent-foreground: oklch(0.985 0 0);
197
+ --destructive: oklch(0.704 0.191 22.216);
198
+ --border: oklch(1 0 0 / 10%);
199
+ --input: oklch(1 0 0 / 15%);
200
+ --ring: oklch(0.556 0 0);
201
+ --chart-1: oklch(0.488 0.243 264.376);
202
+ --chart-2: oklch(0.696 0.17 162.48);
203
+ --chart-3: oklch(0.769 0.188 70.08);
204
+ --chart-4: oklch(0.627 0.265 303.9);
205
+ --chart-5: oklch(0.645 0.246 16.439);
206
+ --sidebar: oklch(0.205 0 0);
207
+ --sidebar-foreground: oklch(0.985 0 0);
208
+ --sidebar-primary: oklch(0.488 0.243 264.376);
209
+ --sidebar-primary-foreground: oklch(0.985 0 0);
210
+ --sidebar-accent: oklch(0.269 0 0);
211
+ --sidebar-accent-foreground: oklch(0.985 0 0);
212
+ --sidebar-border: oklch(1 0 0 / 10%);
213
+ --sidebar-ring: oklch(0.556 0 0);
214
+ }
215
+
216
+ @layer base {
217
+ * {
218
+ @apply border-border outline-ring/50;
219
+ }
220
+ body {
221
+ @apply bg-background text-foreground;
222
+ }
223
+ }
224
+ `;
225
+
226
+ const POSTCSS_CONFIG = `const config = {
227
+ plugins: {
228
+ "@tailwindcss/postcss": {},
229
+ },
230
+ };
231
+ export default config;
232
+ `;
233
+
234
+ // ---------------------------------------------------------------------------
235
+ // Public API
236
+ // ---------------------------------------------------------------------------
237
+
238
+ const SCAFFOLDING_FILES = [
239
+ 'app/layout.tsx',
240
+ 'app/globals.css',
241
+ 'tsconfig.json',
242
+ 'postcss.config.mjs',
243
+ 'next.config.mjs',
244
+ '.npmrc',
245
+ ];
246
+
247
+ /**
248
+ * Delete scaffolding files so generateScaffolding will recreate them.
249
+ * Does not touch package.json, node_modules, or dashboard files in app/.
250
+ */
251
+ export async function resetScaffolding(workspaceDir) {
252
+ for (const file of SCAFFOLDING_FILES) {
253
+ try { await fs.unlink(path.join(workspaceDir, file)); } catch {}
254
+ }
255
+ }
256
+
257
+ /**
258
+ * Generate the project structure inside the workspace.
259
+ * Creates package.json, tsconfig.json, postcss.config.mjs, app/layout.tsx, app/globals.css.
260
+ * Runs npm install if node_modules doesn't exist.
261
+ */
262
+ export async function generateScaffolding(workspaceDir) {
263
+ const appDir = path.join(workspaceDir, 'app');
264
+ await fs.mkdir(appDir, { recursive: true });
265
+
266
+ // layout.tsx and globals.css in app/
267
+ const layoutPath = path.join(appDir, 'layout.tsx');
268
+ const globalsPath = path.join(appDir, 'globals.css');
269
+
270
+ try { await fs.access(layoutPath); } catch {
271
+ await fs.writeFile(layoutPath, LAYOUT_TEMPLATE, 'utf-8');
272
+ }
273
+
274
+ try { await fs.access(globalsPath); } catch {
275
+ await fs.writeFile(globalsPath, GLOBALS_CSS_TEMPLATE, 'utf-8');
276
+ }
277
+
278
+ // package.json — @papercraneai/cli brings in next, react, and all dashboard deps
279
+ const pkgPath = path.join(workspaceDir, 'package.json');
280
+ try { await fs.access(pkgPath); } catch {
281
+ const pkg = {
282
+ private: true,
283
+ dependencies: {
284
+ "@papercraneai/cli": isLocalDev ? `file:${cliRoot}` : cliVersion,
285
+ },
286
+ };
287
+ await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2), 'utf-8');
288
+ }
289
+
290
+ // .npmrc
291
+ const npmrcPath = path.join(workspaceDir, '.npmrc');
292
+ try { await fs.access(npmrcPath); } catch {
293
+ await fs.writeFile(npmrcPath, [
294
+ 'legacy-peer-deps=true', // react-simple-maps peer dep compatibility
295
+ 'install-links=true', // ensures file: deps get transitive deps hoisted
296
+ ].join('\n') + '\n', 'utf-8');
297
+ }
298
+
299
+ // tsconfig.json — path aliases resolve through node_modules/@papercraneai/cli
300
+ const tsconfigPath = path.join(workspaceDir, 'tsconfig.json');
301
+ const tsconfig = {
302
+ compilerOptions: {
303
+ target: "ES2017",
304
+ lib: ["dom", "dom.iterable", "esnext"],
305
+ allowJs: true,
306
+ skipLibCheck: true,
307
+ strict: false,
308
+ noEmit: true,
309
+ incremental: true,
310
+ module: "esnext",
311
+ esModuleInterop: true,
312
+ moduleResolution: "bundler",
313
+ resolveJsonModule: true,
314
+ isolatedModules: true,
315
+ jsx: "react-jsx",
316
+ plugins: [{ name: "next" }],
317
+ paths: {
318
+ "@/components/*": ["./node_modules/@papercraneai/cli/components/*"],
319
+ "@/lib/*": ["./node_modules/@papercraneai/cli/runtime-lib/*"],
320
+ "@/hooks/*": ["./node_modules/@papercraneai/cli/runtime-hooks/*"],
321
+ },
322
+ },
323
+ include: ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.mts"],
324
+ exclude: ["node_modules"],
325
+ };
326
+ await fs.writeFile(tsconfigPath, JSON.stringify(tsconfig, null, 2), 'utf-8');
327
+
328
+ // postcss.config.mjs — Tailwind compilation
329
+ await fs.writeFile(path.join(workspaceDir, 'postcss.config.mjs'), POSTCSS_CONFIG, 'utf-8');
330
+
331
+ // next.config.mjs
332
+ const nextConfigPath = path.join(workspaceDir, 'next.config.mjs');
333
+ const transpileLine = isLocalDev ? ` transpilePackages: ['@papercraneai/cli'],\n` : '';
334
+ const nextConfig = `/** @type {import('next').NextConfig} */
335
+ const nextConfig = {
336
+ ${transpileLine} turbopack: {
337
+ root: ${JSON.stringify(workspaceDir)},
338
+ },
339
+ };
340
+ export default nextConfig;
341
+ `;
342
+ await fs.writeFile(nextConfigPath, nextConfig, 'utf-8');
343
+
344
+ // npm install — only if node_modules doesn't exist yet
345
+ const nmDir = path.join(workspaceDir, 'node_modules');
346
+ if (!fsSync.existsSync(nmDir)) {
347
+ console.log('Installing dependencies...');
348
+ execSync('npm install', { cwd: workspaceDir, stdio: 'inherit' });
349
+ }
350
+ }
351
+
352
+
353
+ /**
354
+ * Start the Next.js dev server.
355
+ *
356
+ * The workspace IS the Next.js project root. Structure:
357
+ * workspaces/{id}/
358
+ * package.json ← depends on @papercraneai/cli
359
+ * node_modules/ ← installed via npm install
360
+ * app/ ← layout.tsx, globals.css, dashboard routes
361
+ * tsconfig.json ← @/ path aliases via node_modules/@papercraneai/cli
362
+ * postcss.config.mjs
363
+ */
364
+ export async function startDevServer(workspaceDir, port) {
365
+ const next = (await import('next')).default;
366
+ const express = (await import('express')).default;
367
+
368
+ const projectDir = workspaceDir;
369
+
370
+ const nextApp = next({
371
+ dev: true,
372
+ dir: projectDir,
373
+ });
374
+
375
+ await nextApp.prepare();
376
+ const handle = nextApp.getRequestHandler();
377
+
378
+ const server = express();
379
+
380
+ // Serve theme CSS from CLI package
381
+ server.use('/themes', express.static(path.join(cliRoot, 'public', 'themes')));
382
+
383
+ // Everything else -> Next.js
384
+ server.all('/{*path}', (req, res) => handle(req, res));
385
+
386
+ return new Promise((resolve) => {
387
+ server.listen(port, () => resolve());
388
+ });
389
+ }
390
+
391
+ // ---------------------------------------------------------------------------
392
+ // Internal helpers
393
+ // ---------------------------------------------------------------------------
394
+
395
+
@@ -212,15 +212,21 @@ export async function pullWorkspace(workspaceId, onProgress = null) {
212
212
  if (onProgress) onProgress(filePath, i + 1, filePaths.length);
213
213
 
214
214
  try {
215
+ // Skip binary files — the API can't serve them correctly
216
+ if (isBinaryFile(filePath)) continue;
217
+
215
218
  const { content } = await readFile(workspaceId, filePath);
216
- const localFilePath = join(localPath, filePath);
219
+
220
+ // Remap cloud paths into dashboards/app/
221
+ const localRelPath = remapCloudToLocal(filePath);
222
+ const localFilePath = join(localPath, localRelPath);
217
223
 
218
224
  // Ensure directory exists
219
225
  await mkdir(dirname(localFilePath), { recursive: true });
220
226
 
221
227
  // Write file
222
228
  await fsWriteFile(localFilePath, content, 'utf-8');
223
- downloaded.push(filePath);
229
+ downloaded.push(localRelPath);
224
230
  } catch (error) {
225
231
  // Skip files that can't be read (e.g., binary files)
226
232
  console.warn(`Skipping ${filePath}: ${error.message}`);
@@ -230,6 +236,27 @@ export async function pullWorkspace(workspaceId, onProgress = null) {
230
236
  return { localPath, files: downloaded };
231
237
  }
232
238
 
239
+ /**
240
+ * Remap cloud workspace path to local path.
241
+ * The cloud workspace root IS the Next.js app directory,
242
+ * so all files go into app/.
243
+ * Cloud: ga4-dashboard/page.tsx → Local: app/ga4-dashboard/page.tsx
244
+ */
245
+ function remapCloudToLocal(cloudPath) {
246
+ return 'app/' + cloudPath;
247
+ }
248
+
249
+ /**
250
+ * Remap local path back to cloud workspace path.
251
+ * Local: app/ga4-dashboard/page.tsx → Cloud: ga4-dashboard/page.tsx
252
+ */
253
+ function remapLocalToCloud(localPath) {
254
+ if (localPath.startsWith('app/')) {
255
+ return localPath.slice('app/'.length);
256
+ }
257
+ return localPath;
258
+ }
259
+
233
260
  // Binary file extensions to skip during push
234
261
  const BINARY_EXTENSIONS = new Set([
235
262
  'ico', 'png', 'jpg', 'jpeg', 'gif', 'webp', 'svg', 'bmp',
@@ -262,8 +289,10 @@ async function collectLocalFiles(dir, prefix = '') {
262
289
  try {
263
290
  const entries = await readdir(dir, { withFileTypes: true });
264
291
  for (const entry of entries) {
265
- // Skip hidden files and common non-source directories
266
- if (entry.name.startsWith('.') || entry.name === 'node_modules') continue;
292
+ // Skip hidden files, infrastructure files, and non-source directories
293
+ if (entry.name.startsWith('.') || entry.name === 'node_modules' ||
294
+ entry.name === 'tsconfig.json' || entry.name === 'postcss.config.mjs' ||
295
+ entry.name === 'next-env.d.ts') continue;
267
296
 
268
297
  const itemPath = prefix ? `${prefix}/${entry.name}` : entry.name;
269
298
  const fullPath = join(dir, entry.name);
@@ -293,24 +322,32 @@ export async function pushWorkspace(workspaceId, onProgress = null, pathFilter =
293
322
  const { readFile: fsReadFile } = await import('fs/promises');
294
323
 
295
324
  const localPath = getLocalWorkspacePath(workspaceId);
325
+ const appPath = join(localPath, 'app');
296
326
 
297
- // Collect local files
298
- let filePaths = await collectLocalFiles(localPath);
327
+ // Collect local files from dashboards/app/ directory
328
+ let filePaths = await collectLocalFiles(appPath, 'app');
299
329
 
300
330
  // Apply path filter if specified
301
331
  if (pathFilter) {
302
- const normalizedFilter = pathFilter.replace(/^\/+|\/+$/g, ''); // Remove leading/trailing slashes
303
- filePaths = filePaths.filter(p => p.startsWith(normalizedFilter + '/') || p === normalizedFilter);
332
+ const normalizedFilter = pathFilter.replace(/^\/+|\/+$/g, '');
333
+ // Allow filtering by dashboard name (e.g., "sales-overview")
334
+ // filePaths are like "app/sales-overview/page.tsx"
335
+ // cloudPaths are like "sales-overview/page.tsx"
336
+ filePaths = filePaths.filter(p => {
337
+ const cloudPath = remapLocalToCloud(p);
338
+ return cloudPath.startsWith(normalizedFilter + '/') ||
339
+ cloudPath === normalizedFilter;
340
+ });
304
341
  }
305
342
 
306
343
  if (filePaths.length === 0) {
307
344
  const msg = pathFilter
308
- ? `No files found matching '${pathFilter}' in ${localPath}`
309
- : `No files found in ${localPath}. Run 'papercrane pull' first.`;
345
+ ? `No files found matching '${pathFilter}' in ${appPath}`
346
+ : `No files found in ${appPath}. Run 'papercrane pull' first.`;
310
347
  throw new Error(msg);
311
348
  }
312
349
 
313
- // Upload each file
350
+ // Upload each file, remapping local paths to cloud paths
314
351
  const uploaded = [];
315
352
  for (let i = 0; i < filePaths.length; i++) {
316
353
  const filePath = filePaths[i];
@@ -319,7 +356,8 @@ export async function pushWorkspace(workspaceId, onProgress = null, pathFilter =
319
356
  const localFilePath = join(localPath, filePath);
320
357
  const content = await fsReadFile(localFilePath, 'utf-8');
321
358
 
322
- await writeFile(workspaceId, filePath, content);
359
+ const cloudPath = remapLocalToCloud(filePath);
360
+ await writeFile(workspaceId, cloudPath, content);
323
361
  uploaded.push(filePath);
324
362
  }
325
363
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papercraneai/cli",
3
- "version": "1.5.6",
3
+ "version": "1.6.0-beta.1",
4
4
  "description": "CLI tool for managing OAuth credentials for LLM integrations",
5
5
  "main": "index.js",
6
6
  "type": "module",
@@ -9,7 +9,11 @@
9
9
  },
10
10
  "files": [
11
11
  "bin",
12
- "lib"
12
+ "lib",
13
+ "components",
14
+ "runtime-lib",
15
+ "runtime-hooks",
16
+ "public"
13
17
  ],
14
18
  "publishConfig": {
15
19
  "access": "public"
@@ -26,11 +30,68 @@
26
30
  "author": "",
27
31
  "license": "MIT",
28
32
  "dependencies": {
29
- "commander": "^12.0.0",
33
+ "@hookform/resolvers": "^5.2.2",
34
+ "@radix-ui/react-accordion": "^1.2.12",
35
+ "@radix-ui/react-alert-dialog": "^1.1.15",
36
+ "@radix-ui/react-aspect-ratio": "^1.1.8",
37
+ "@radix-ui/react-avatar": "^1.1.11",
38
+ "@radix-ui/react-checkbox": "^1.3.3",
39
+ "@radix-ui/react-collapsible": "^1.1.12",
40
+ "@radix-ui/react-context-menu": "^2.2.16",
41
+ "@radix-ui/react-dialog": "^1.1.15",
42
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
43
+ "@radix-ui/react-hover-card": "^1.1.15",
44
+ "@radix-ui/react-label": "^2.1.8",
45
+ "@radix-ui/react-menubar": "^1.1.16",
46
+ "@radix-ui/react-navigation-menu": "^1.2.14",
47
+ "@radix-ui/react-popover": "^1.1.15",
48
+ "@radix-ui/react-progress": "^1.1.8",
49
+ "@radix-ui/react-radio-group": "^1.3.8",
50
+ "@radix-ui/react-scroll-area": "^1.2.10",
51
+ "@radix-ui/react-select": "^2.2.6",
52
+ "@radix-ui/react-separator": "^1.1.8",
53
+ "@radix-ui/react-slider": "^1.3.6",
54
+ "@radix-ui/react-slot": "^1.2.4",
55
+ "@radix-ui/react-switch": "^1.2.6",
56
+ "@radix-ui/react-tabs": "^1.1.13",
57
+ "@radix-ui/react-toggle": "^1.1.10",
58
+ "@radix-ui/react-toggle-group": "^1.1.11",
59
+ "@radix-ui/react-tooltip": "^1.2.8",
60
+ "@tailwindcss/postcss": "^4",
61
+ "@tanstack/react-table": "^8.21.3",
62
+ "@types/node": "^20",
63
+ "@types/react": "^19",
64
+ "@types/react-dom": "^19",
30
65
  "axios": "^1.6.0",
31
66
  "chalk": "^4.1.2",
67
+ "class-variance-authority": "^0.7.1",
68
+ "clsx": "^2.1.1",
69
+ "cmdk": "^1.1.1",
70
+ "commander": "^12.0.0",
71
+ "date-fns": "^4.1.0",
72
+ "embla-carousel-react": "^8.6.0",
73
+ "express": "^5.1.0",
32
74
  "https-proxy-agent": "^7.0.4",
75
+ "input-otp": "^1.4.2",
33
76
  "inquirer": "^8.2.6",
34
- "open": "^8.4.2"
77
+ "lucide-react": "^0.559.0",
78
+ "next": "^16.1.7",
79
+ "next-themes": "^0.4.6",
80
+ "open": "^8.4.2",
81
+ "react": "19.2.1",
82
+ "react-day-picker": "^9.12.0",
83
+ "react-dom": "19.2.1",
84
+ "react-hook-form": "^7.68.0",
85
+ "react-resizable-panels": "^3.0.6",
86
+ "react-simple-maps": "^3.0.0",
87
+ "recharts": "^2.15.4",
88
+ "sonner": "^2.0.7",
89
+ "tailwind-merge": "^3.4.0",
90
+ "tailwindcss": "^4",
91
+ "topojson-client": "^3.1.0",
92
+ "tw-animate-css": "^1.4.0",
93
+ "typescript": "^5",
94
+ "vaul": "^1.1.2",
95
+ "zod": "^4.1.13"
35
96
  }
36
97
  }
@@ -0,0 +1,69 @@
1
+ /* Blue Theme - Paste OKLCH values from shadcn themes page */
2
+ :root {
3
+ --radius: 0.65rem;
4
+ --background: oklch(1 0 0);
5
+ --foreground: oklch(0.141 0.005 285.823);
6
+ --card: oklch(1 0 0);
7
+ --card-foreground: oklch(0.141 0.005 285.823);
8
+ --popover: oklch(1 0 0);
9
+ --popover-foreground: oklch(0.141 0.005 285.823);
10
+ --primary: oklch(0.488 0.243 264.376);
11
+ --primary-foreground: oklch(0.97 0.014 254.604);
12
+ --secondary: oklch(0.967 0.001 286.375);
13
+ --secondary-foreground: oklch(0.21 0.006 285.885);
14
+ --muted: oklch(0.967 0.001 286.375);
15
+ --muted-foreground: oklch(0.552 0.016 285.938);
16
+ --accent: oklch(0.967 0.001 286.375);
17
+ --accent-foreground: oklch(0.21 0.006 285.885);
18
+ --destructive: oklch(0.577 0.245 27.325);
19
+ --border: oklch(0.92 0.004 286.32);
20
+ --input: oklch(0.92 0.004 286.32);
21
+ --ring: oklch(0.708 0 0);
22
+ --chart-1: oklch(0.809 0.105 251.813);
23
+ --chart-2: oklch(0.623 0.214 259.815);
24
+ --chart-3: oklch(0.546 0.245 262.881);
25
+ --chart-4: oklch(0.488 0.243 264.376);
26
+ --chart-5: oklch(0.424 0.199 265.638);
27
+ --sidebar: oklch(0.985 0 0);
28
+ --sidebar-foreground: oklch(0.141 0.005 285.823);
29
+ --sidebar-primary: oklch(0.546 0.245 262.881);
30
+ --sidebar-primary-foreground: oklch(0.97 0.014 254.604);
31
+ --sidebar-accent: oklch(0.967 0.001 286.375);
32
+ --sidebar-accent-foreground: oklch(0.21 0.006 285.885);
33
+ --sidebar-border: oklch(0.92 0.004 286.32);
34
+ --sidebar-ring: oklch(0.708 0 0);
35
+ }
36
+
37
+ .dark {
38
+ --background: oklch(0.141 0.005 285.823);
39
+ --foreground: oklch(0.985 0 0);
40
+ --card: oklch(0.21 0.006 285.885);
41
+ --card-foreground: oklch(0.985 0 0);
42
+ --popover: oklch(0.21 0.006 285.885);
43
+ --popover-foreground: oklch(0.985 0 0);
44
+ --primary: oklch(0.488 0.243 264.376);
45
+ --primary-foreground: oklch(0.97 0.014 254.604);
46
+ --secondary: oklch(0.274 0.006 286.033);
47
+ --secondary-foreground: oklch(0.985 0 0);
48
+ --muted: oklch(0.274 0.006 286.033);
49
+ --muted-foreground: oklch(0.705 0.015 286.067);
50
+ --accent: oklch(0.274 0.006 286.033);
51
+ --accent-foreground: oklch(0.985 0 0);
52
+ --destructive: oklch(0.704 0.191 22.216);
53
+ --border: oklch(1 0 0 / 10%);
54
+ --input: oklch(1 0 0 / 15%);
55
+ --ring: oklch(0.556 0 0);
56
+ --chart-1: oklch(0.809 0.105 251.813);
57
+ --chart-2: oklch(0.623 0.214 259.815);
58
+ --chart-3: oklch(0.546 0.245 262.881);
59
+ --chart-4: oklch(0.488 0.243 264.376);
60
+ --chart-5: oklch(0.424 0.199 265.638);
61
+ --sidebar: oklch(0.21 0.006 285.885);
62
+ --sidebar-foreground: oklch(0.985 0 0);
63
+ --sidebar-primary: oklch(0.623 0.214 259.815);
64
+ --sidebar-primary-foreground: oklch(0.97 0.014 254.604);
65
+ --sidebar-accent: oklch(0.274 0.006 286.033);
66
+ --sidebar-accent-foreground: oklch(0.985 0 0);
67
+ --sidebar-border: oklch(1 0 0 / 10%);
68
+ --sidebar-ring: oklch(0.439 0 0);
69
+ }