@papercraneai/cli 1.7.1 → 1.8.0-beta.0

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 CHANGED
@@ -51,7 +51,23 @@ async function showPostLoginHelp() {
51
51
 
52
52
  const workspaceDir = getLocalWorkspacePath(ws.id);
53
53
  const { generateScaffolding } = await import('../lib/dev-server.js');
54
- await generateScaffolding(workspaceDir);
54
+
55
+ // Check if we can reuse the pre-scaffolded default workspace
56
+ let didSymlink = false;
57
+ try {
58
+ await fs.access(workspaceDir);
59
+ // Workspace dir already exists — normal path
60
+ } catch {
61
+ if (await isDefaultScaffoldAvailable()) {
62
+ await fs.mkdir(path.dirname(workspaceDir), { recursive: true });
63
+ await fs.symlink(DEFAULT_SCAFFOLD_DIR, workspaceDir);
64
+ didSymlink = true;
65
+ }
66
+ }
67
+
68
+ if (!didSymlink) {
69
+ await generateScaffolding(workspaceDir);
70
+ }
55
71
 
56
72
  const currentLink = path.join(os.homedir(), '.papercrane', 'current');
57
73
  try {
@@ -132,6 +148,39 @@ import { createRequire } from 'module';
132
148
  const require = createRequire(import.meta.url);
133
149
  const { version } = require('../package.json');
134
150
 
151
+ const DEFAULT_SCAFFOLD_DIR = path.join(os.homedir(), '.papercrane', 'workspaces', 'default');
152
+
153
+ /**
154
+ * Check if the pre-scaffolded default workspace is available for symlink reuse.
155
+ * Returns true only if default/ has been scaffolded AND no other workspace already
156
+ * symlinks to it.
157
+ */
158
+ async function isDefaultScaffoldAvailable() {
159
+ try {
160
+ await fs.access(path.join(DEFAULT_SCAFFOLD_DIR, 'package.json'));
161
+ } catch {
162
+ return false;
163
+ }
164
+ // Check if any existing workspace already claims default/
165
+ const workspacesDir = path.join(os.homedir(), '.papercrane', 'workspaces');
166
+ try {
167
+ const entries = await fs.readdir(workspacesDir);
168
+ for (const entry of entries) {
169
+ if (entry === 'default') continue;
170
+ const entryPath = path.join(workspacesDir, entry);
171
+ try {
172
+ const target = await fs.readlink(entryPath);
173
+ if (target === DEFAULT_SCAFFOLD_DIR) return false;
174
+ } catch {
175
+ // Not a symlink — ignore
176
+ }
177
+ }
178
+ } catch {
179
+ // workspaces dir doesn't exist yet — that's fine
180
+ }
181
+ return true;
182
+ }
183
+
135
184
  /**
136
185
  * Run the browser-based auth flow (init session, open browser or print URL, poll for result).
137
186
  * @param {Object} options
@@ -387,6 +436,45 @@ program
387
436
  }
388
437
  });
389
438
 
439
+ // ============================================================================
440
+ // SCAFFOLD COMMAND
441
+ // ============================================================================
442
+
443
+ program
444
+ .command('scaffold')
445
+ .description('Pre-scaffold a workspace directory (no login required)')
446
+ .option('--dir <path>', 'Directory to scaffold', DEFAULT_SCAFFOLD_DIR)
447
+ .option('--npm-install', 'Run npm install after scaffolding')
448
+ .action(async (options) => {
449
+ try {
450
+ const dir = options.dir;
451
+ console.log(chalk.cyan(`Scaffolding workspace in ${dir}...`));
452
+
453
+ const { generateScaffolding } = await import('../lib/dev-server.js');
454
+ await generateScaffolding(dir, { skipInstall: !options.npmInstall });
455
+
456
+ // Set ~/.papercrane/current symlink → scaffolded dir
457
+ const currentLink = path.join(os.homedir(), '.papercrane', 'current');
458
+ try {
459
+ const existing = await fs.readlink(currentLink);
460
+ if (existing !== dir) {
461
+ await fs.unlink(currentLink);
462
+ await fs.symlink(dir, currentLink);
463
+ }
464
+ } catch {
465
+ try { await fs.unlink(currentLink); } catch {}
466
+ await fs.symlink(dir, currentLink);
467
+ }
468
+
469
+ console.log(chalk.green(`\n✓ Scaffolded workspace`));
470
+ console.log(chalk.dim(` ${currentLink} → ${dir}`));
471
+ console.log(chalk.dim(` Dashboards: ${dir}/app/\n`));
472
+ } catch (error) {
473
+ console.error(chalk.red('Error:'), error.message);
474
+ process.exit(1);
475
+ }
476
+ });
477
+
390
478
  // ============================================================================
391
479
  // FUNCTION COMMANDS
392
480
  // ============================================================================
@@ -755,7 +843,6 @@ workspacesCmd
755
843
 
756
844
  await setDefaultWorkspace(wsId);
757
845
 
758
- // Scaffold the workspace and create ~/.papercrane/current symlink
759
846
  const { generateScaffolding, resetScaffolding } = await import('../lib/dev-server.js');
760
847
  const workspaceDir = getLocalWorkspacePath(wsId);
761
848
 
@@ -764,8 +851,27 @@ workspacesCmd
764
851
  await fs.rm(path.join(workspaceDir, '.next'), { recursive: true, force: true });
765
852
  console.log(chalk.dim('Scaffolding regenerated.'));
766
853
  }
767
- await generateScaffolding(workspaceDir);
768
854
 
855
+ // Check if we can reuse the pre-scaffolded default workspace
856
+ let didSymlink = false;
857
+ try {
858
+ await fs.access(workspaceDir);
859
+ // Workspace dir already exists — normal path
860
+ } catch {
861
+ // Workspace dir doesn't exist yet — check for pre-scaffolded default
862
+ if (await isDefaultScaffoldAvailable()) {
863
+ await fs.mkdir(path.dirname(workspaceDir), { recursive: true });
864
+ await fs.symlink(DEFAULT_SCAFFOLD_DIR, workspaceDir);
865
+ didSymlink = true;
866
+ console.log(chalk.dim('Reusing pre-scaffolded workspace (symlink).'));
867
+ }
868
+ }
869
+
870
+ if (!didSymlink) {
871
+ await generateScaffolding(workspaceDir);
872
+ }
873
+
874
+ // Update ~/.papercrane/current symlink
769
875
  const currentLink = path.join(os.homedir(), '.papercrane', 'current');
770
876
  try {
771
877
  const existing = await fs.readlink(currentLink);
@@ -795,16 +901,33 @@ workspacesCmd
795
901
  program
796
902
  .command('dev')
797
903
  .description('Start local dev server for dashboard development with HMR')
798
- .option('-w, --workspace <id>', 'Workspace ID')
904
+ .option('-w, --workspace <id>', 'Workspace ID (resolves to workspaces/<id>/ instead of current)')
799
905
  .option('-p, --port <port>', 'Port (default: 3100)', '3100')
906
+ .option('--npm-install', 'Force run npm install before starting')
800
907
  .option('--clear-cache', 'Clear build cache before starting (use after structural changes like editing globals.css or adding dependencies)')
801
908
  .option('--reset-scaffolding', 'Regenerate scaffolding files (layout.tsx, globals.css, configs) and clear build cache')
802
909
  .action(async (options) => {
803
910
  try {
804
911
  const { generateScaffolding, resetScaffolding, startDevServer } = await import('../lib/dev-server.js');
912
+ const { execSync } = await import('child_process');
805
913
 
806
- const wsId = options.workspace ? parseInt(options.workspace, 10) : await requireWorkspace();
807
- const workspaceDir = getLocalWorkspacePath(wsId);
914
+ let workspaceDir;
915
+ if (options.workspace) {
916
+ workspaceDir = getLocalWorkspacePath(parseInt(options.workspace, 10));
917
+ } else {
918
+ // Resolve from ~/.papercrane/current symlink (no login required)
919
+ const currentLink = path.join(os.homedir(), '.papercrane', 'current');
920
+ try {
921
+ await fs.readlink(currentLink);
922
+ workspaceDir = currentLink;
923
+ } catch {
924
+ console.error(chalk.red('No workspace set up.'));
925
+ console.error(chalk.dim('\nRun one of:'));
926
+ console.error(chalk.dim(' papercrane scaffold # Pre-scaffold without login'));
927
+ console.error(chalk.dim(' papercrane workspaces use <id> # Set active workspace'));
928
+ process.exit(1);
929
+ }
930
+ }
808
931
 
809
932
  if (options.resetScaffolding) {
810
933
  await resetScaffolding(workspaceDir);
@@ -817,7 +940,21 @@ program
817
940
  console.log(chalk.dim('Build cache cleared.'));
818
941
  }
819
942
 
820
- await generateScaffolding(workspaceDir);
943
+ await generateScaffolding(workspaceDir, { skipInstall: true });
944
+
945
+ // npm install — dev server won't work without node_modules
946
+ const nmDir = path.join(workspaceDir, 'node_modules');
947
+ if (options.npmInstall) {
948
+ console.log(chalk.dim('Running npm install...'));
949
+ execSync('npm install --prefer-offline', { cwd: workspaceDir, stdio: 'inherit' });
950
+ } else {
951
+ try {
952
+ await fs.access(nmDir);
953
+ } catch {
954
+ console.log(chalk.dim('node_modules not found, running npm install...'));
955
+ execSync('npm install --prefer-offline', { cwd: workspaceDir, stdio: 'inherit' });
956
+ }
957
+ }
821
958
 
822
959
  const port = parseInt(options.port, 10);
823
960
  const dashboardDir = workspaceDir + '/app';
@@ -933,6 +1070,7 @@ program.action(async (_, cmd) => {
933
1070
  console.error(' papercrane call <path> [json] Call an endpoint');
934
1071
  console.error(' papercrane add Add a new integration');
935
1072
  console.error(' papercrane dashboard-guide Show how to build dashboards');
1073
+ console.error(' papercrane scaffold Pre-scaffold workspace (no login required)');
936
1074
  console.error(' papercrane workspaces list List workspaces');
937
1075
  console.error(' papercrane workspaces use <id> Set default workspace');
938
1076
  console.error(' papercrane dev Start local dev server with HMR');
package/lib/dev-server.js CHANGED
@@ -259,7 +259,7 @@ export async function resetScaffolding(workspaceDir) {
259
259
  * Creates package.json, tsconfig.json, postcss.config.mjs, app/layout.tsx, app/globals.css.
260
260
  * Runs npm install if node_modules doesn't exist.
261
261
  */
262
- export async function generateScaffolding(workspaceDir) {
262
+ export async function generateScaffolding(workspaceDir, { skipInstall = false } = {}) {
263
263
  const appDir = path.join(workspaceDir, 'app');
264
264
  await fs.mkdir(appDir, { recursive: true });
265
265
 
@@ -346,10 +346,12 @@ export default nextConfig;
346
346
  await fs.writeFile(nextConfigPath, nextConfig, 'utf-8');
347
347
 
348
348
  // npm install — only if node_modules doesn't exist yet
349
- const nmDir = path.join(workspaceDir, 'node_modules');
350
- if (!fsSync.existsSync(nmDir)) {
351
- console.log('Installing dependencies...');
352
- execSync('npm install --prefer-offline', { cwd: workspaceDir, stdio: 'inherit' });
349
+ if (!skipInstall) {
350
+ const nmDir = path.join(workspaceDir, 'node_modules');
351
+ if (!fsSync.existsSync(nmDir)) {
352
+ console.log('Installing dependencies...');
353
+ execSync('npm install --prefer-offline', { cwd: workspaceDir, stdio: 'inherit' });
354
+ }
353
355
  }
354
356
  }
355
357
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@papercraneai/cli",
3
- "version": "1.7.1",
3
+ "version": "1.8.0-beta.0",
4
4
  "description": "CLI tool for managing OAuth credentials for LLM integrations",
5
5
  "main": "index.js",
6
6
  "type": "module",