@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
|
@@ -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
|
-
|
|
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(
|
|
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
|
|
266
|
-
if (entry.name.startsWith('.') || entry.name === 'node_modules'
|
|
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(
|
|
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, '');
|
|
303
|
-
|
|
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 ${
|
|
309
|
-
: `No files found in ${
|
|
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
|
-
|
|
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.
|
|
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
|
-
"
|
|
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
|
-
"
|
|
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
|
+
}
|