@percepta/create 3.4.2 → 3.5.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.
Files changed (42) hide show
  1. package/README.md +13 -0
  2. package/dist/index.js +135 -106
  3. package/dist/index.js.map +1 -1
  4. package/dist/{init-CtCp7Tv2.js → init-sI9aIrkU.js} +2 -2
  5. package/dist/init-sI9aIrkU.js.map +1 -0
  6. package/dist/{upstream-D-LH_1z4.js → upstream-gUHLWSR1.js} +2 -2
  7. package/dist/upstream-gUHLWSR1.js.map +1 -0
  8. package/package.json +1 -1
  9. package/template-versions.json +1 -0
  10. package/templates/monorepo/README.md +8 -5
  11. package/templates/monorepo/package.json.template +1 -0
  12. package/templates/webapp/.claude/commands/upstream.md +1 -1
  13. package/templates/webapp/AGENTS.md +1 -1
  14. package/templates/webapp/agent-skills/access-control.md +24 -1
  15. package/templates/webapp/agent-skills/inngest.md +5 -5
  16. package/templates/webapp/agent-skills/langfuse.md +4 -4
  17. package/templates/webapp/agent-skills/llm.md +1 -1
  18. package/templates/webapp/drizzle.config.ts +1 -1
  19. package/templates/webapp/package.json.template +10 -17
  20. package/templates/webapp/src/app/api/inngest/route.ts +12 -22
  21. package/templates/webapp/src/drizzle/db.ts +1 -2
  22. package/templates/webapp/src/instrumentation.ts +2 -63
  23. package/templates/webapp/src/server/trpc.ts +6 -18
  24. package/templates/webapp/src/services/AuthContextService.ts +7 -59
  25. package/templates/webapp/src/services/inngest/InngestService.ts +14 -62
  26. package/templates/webapp/src/services/langfuse/LangfuseService.ts +9 -77
  27. package/templates/webapp/src/services/llm/LLMService.ts +10 -88
  28. package/templates/webapp/src/services/logger/AppLogger.ts +3 -48
  29. package/templates/webapp/src/utils/syncInngestApp.ts +4 -56
  30. package/dist/init-CtCp7Tv2.js.map +0 -1
  31. package/dist/upstream-D-LH_1z4.js.map +0 -1
  32. package/templates/webapp/scripts/generate-migrations.ts +0 -28
  33. package/templates/webapp/scripts/migrate.ts +0 -21
  34. package/templates/webapp/scripts/setup-database.ts +0 -78
  35. package/templates/webapp/scripts/setup-readonly-user.ts +0 -193
  36. package/templates/webapp/src/drizzle/__tests__/migrationSql.test.ts +0 -24
  37. package/templates/webapp/src/drizzle/migrationSql.ts +0 -8
  38. package/templates/webapp/src/drizzle/searchPath.test.ts +0 -21
  39. package/templates/webapp/src/drizzle/searchPath.ts +0 -16
  40. package/templates/webapp/src/drizzle/ssl.ts +0 -5
  41. package/templates/webapp/src/services/inngest/InngestFunctionCollection.ts +0 -5
  42. package/templates/webapp/src/services/llm/LlmProviderService.ts +0 -85
package/README.md CHANGED
@@ -26,6 +26,7 @@ The bare command above is the canonical UX. The flags below exist for tests and
26
26
  ## Subcommands
27
27
 
28
28
  - `create` (default) — scaffold a new Mosaic package
29
+ - `add` — add a webapp or library to the current monorepo
29
30
  - `status` — show template sync status for the current app
30
31
  - `sync` — generate downstream sync context (template → app)
31
32
  - `upstream` — generate upstream context (app → template)
@@ -38,6 +39,18 @@ The bare command above is the canonical UX. The flags below exist for tests and
38
39
  - **Outside a monorepo** — you're asked "Initialize with a webapp?" (Y/n, default Y), then for the repo name. Picking the webapp option also asks for the webapp name and scaffolds it inside `packages/<webapp-name>/`. Declining gives you an empty monorepo.
39
40
  - **Inside a monorepo** — pick `Webapp` (default) or `Library` to add a new package under the workspace pattern.
40
41
 
42
+ Generated monorepos include a root `.mosaic-workspace.json` and a pinned
43
+ `pnpm mosaic` script. Prefer the workspace-owned command when adding packages:
44
+
45
+ ```bash
46
+ pnpm mosaic add webapp my-app
47
+ pnpm mosaic add library my-lib
48
+ ```
49
+
50
+ That command uses the create package version and template compatibility versions
51
+ the monorepo was created with, so a newly added app does not silently drift to a
52
+ newer scaffold.
53
+
41
54
  ## Happy-path: zero-friction webapp
42
55
 
43
56
  When you scaffold a webapp (the default flow), `create` automatically runs:
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { program } from "commander";
3
- import { execFile, execSync, spawn } from "node:child_process";
3
+ import { execSync, spawn } from "node:child_process";
4
4
  import path from "node:path";
5
5
  import chalk from "chalk";
6
6
  import fs from "fs-extra";
@@ -10,7 +10,6 @@ import { parse } from "yaml";
10
10
  import { randomBytes } from "node:crypto";
11
11
  import inquirer from "inquirer";
12
12
  import validateNpmPackageName from "validate-npm-package-name";
13
- import { promisify } from "node:util";
14
13
  //#region src/utils/case-converters.ts
15
14
  /** Lowercase, hyphenated, npm-package-name-safe form: "My Cool App" → "my-cool-app". */
16
15
  function toKebabCase(str) {
@@ -192,6 +191,24 @@ function resolveMosaicTemplatePath(options) {
192
191
  throw new Error("Mosaic repo path required. Use --mosaic-template-path or set MOSAIC_TEMPLATE_PATH.");
193
192
  }
194
193
  //#endregion
194
+ //#region src/utils/package-metadata.ts
195
+ const FALLBACK_METADATA = {
196
+ name: "@percepta/create",
197
+ version: "0.0.0"
198
+ };
199
+ function readCreatePackageMetadata() {
200
+ const currentDir = path.dirname(fileURLToPath(import.meta.url));
201
+ const candidates = [path.resolve(currentDir, "../package.json"), path.resolve(currentDir, "../../package.json")];
202
+ for (const packageJsonPath of candidates) try {
203
+ const pkg = fs.readJsonSync(packageJsonPath);
204
+ if (typeof pkg.name === "string" && typeof pkg.version === "string") return {
205
+ name: pkg.name,
206
+ version: pkg.version
207
+ };
208
+ } catch {}
209
+ return FALLBACK_METADATA;
210
+ }
211
+ //#endregion
195
212
  //#region src/utils/validate.ts
196
213
  function validateProjectName(name) {
197
214
  const result = validateNpmPackageName(name);
@@ -352,7 +369,9 @@ const PLACEHOLDERS = {
352
369
  __APP_NAME_UPPER__: "nameUpper",
353
370
  __APP_NAME_SNAKE__: "nameSnake",
354
371
  __REPO_NAME__: "repoName",
355
- __REPO_NAME_SNAKE__: "repoNameSnake"
372
+ __REPO_NAME_SNAKE__: "repoNameSnake",
373
+ __CREATE_PACKAGE__: "createPackage",
374
+ __CREATE_VERSION__: "createVersion"
356
375
  };
357
376
  const SKIP_DIRS = new Set([
358
377
  "node_modules",
@@ -450,77 +469,6 @@ async function replacePlaceholders(targetDir, config) {
450
469
  return stats;
451
470
  }
452
471
  //#endregion
453
- //#region src/utils/resolve-percepta-versions.ts
454
- const execFileAsync = promisify(execFile);
455
- const DEPENDENCY_SECTIONS = [
456
- "dependencies",
457
- "devDependencies",
458
- "optionalDependencies",
459
- "peerDependencies"
460
- ];
461
- function getPerceptaPackages(pkg) {
462
- const names = /* @__PURE__ */ new Set();
463
- for (const section of DEPENDENCY_SECTIONS) {
464
- const deps = pkg[section];
465
- if (!deps) continue;
466
- for (const name of Object.keys(deps)) if (name.startsWith("@percepta/")) names.add(name);
467
- }
468
- return [...names].sort();
469
- }
470
- async function npmViewDistTagLatest(packageName, cwd) {
471
- const { stdout } = await execFileAsync("npm", [
472
- "view",
473
- packageName,
474
- "dist-tags.latest",
475
- "--silent"
476
- ], {
477
- cwd,
478
- encoding: "utf8",
479
- timeout: 5e3
480
- });
481
- const version = stdout.trim();
482
- return version.length > 0 ? version : null;
483
- }
484
- async function resolvePerceptaVersionsInPackageJson(packageJsonPath, lookupLatest = npmViewDistTagLatest) {
485
- const cwd = path.dirname(packageJsonPath);
486
- const pkg = await fs.readJson(packageJsonPath);
487
- const packageNames = getPerceptaPackages(pkg);
488
- const resolved = {};
489
- const failed = [];
490
- const results = await Promise.all(packageNames.map(async (packageName) => {
491
- try {
492
- return {
493
- packageName,
494
- latest: await lookupLatest(packageName, cwd)
495
- };
496
- } catch {
497
- return {
498
- packageName,
499
- latest: null
500
- };
501
- }
502
- }));
503
- for (const { packageName, latest } of results) {
504
- if (!latest) {
505
- failed.push(packageName);
506
- continue;
507
- }
508
- resolved[packageName] = latest;
509
- for (const section of DEPENDENCY_SECTIONS) {
510
- const deps = pkg[section];
511
- if (deps?.[packageName]) deps[packageName] = latest;
512
- }
513
- }
514
- if (Object.keys(resolved).length > 0) {
515
- await fs.writeJson(packageJsonPath, pkg, { spaces: 2 });
516
- await fs.appendFile(packageJsonPath, "\n");
517
- }
518
- return {
519
- resolved,
520
- failed
521
- };
522
- }
523
- //#endregion
524
472
  //#region src/utils/template-versions.ts
525
473
  const FALLBACK_TEMPLATE_VERSION = "1.0.0";
526
474
  function readTemplateVersions() {
@@ -536,6 +484,44 @@ function getTemplateVersion(templateType) {
536
484
  return readTemplateVersions()[templateType] ?? FALLBACK_TEMPLATE_VERSION;
537
485
  }
538
486
  //#endregion
487
+ //#region src/utils/workspace-manifest.ts
488
+ const WORKSPACE_MANIFEST_FILENAME = ".mosaic-workspace.json";
489
+ function getWorkspaceManifestPath(rootDir) {
490
+ return path.join(rootDir, WORKSPACE_MANIFEST_FILENAME);
491
+ }
492
+ function createWorkspaceManifest(createdAt = (/* @__PURE__ */ new Date()).toISOString()) {
493
+ const createPackage = readCreatePackageMetadata();
494
+ return {
495
+ schemaVersion: 1,
496
+ createPackage: createPackage.name,
497
+ createVersion: createPackage.version,
498
+ monorepoTemplateVersion: getTemplateVersion("monorepo"),
499
+ compatibleTemplates: {
500
+ webapp: getTemplateVersion("webapp"),
501
+ library: getTemplateVersion("library")
502
+ },
503
+ createdAt
504
+ };
505
+ }
506
+ async function readWorkspaceManifest(rootDir) {
507
+ const manifestPath = getWorkspaceManifestPath(rootDir);
508
+ if (!await fs.pathExists(manifestPath)) return null;
509
+ const content = await fs.readFile(manifestPath, "utf-8");
510
+ try {
511
+ return JSON.parse(content);
512
+ } catch (error) {
513
+ throw new Error(`Invalid JSON in ${WORKSPACE_MANIFEST_FILENAME}: ${error.message}`);
514
+ }
515
+ }
516
+ async function writeWorkspaceManifest(rootDir, manifest) {
517
+ const manifestPath = getWorkspaceManifestPath(rootDir);
518
+ await fs.writeJson(manifestPath, manifest, { spaces: 2 });
519
+ await fs.appendFile(manifestPath, "\n");
520
+ }
521
+ function getCompatibleTemplateVersion(manifest, templateType) {
522
+ return manifest?.compatibleTemplates[templateType] ?? getTemplateVersion(templateType);
523
+ }
524
+ //#endregion
539
525
  //#region src/commands/create.ts
540
526
  const PACKAGE_MANAGER = "pnpm";
541
527
  /** Paths in copy-paste shell commands (POSIX-style). */
@@ -696,19 +682,20 @@ async function autoRunWebapp(packageDir, monorepoRoot) {
696
682
  await closed;
697
683
  return true;
698
684
  }
699
- async function writeMosaicFiles(packageDir, config, projectType) {
685
+ async function writeMosaicFiles(packageDir, config, projectType, templateVersion = getTemplateVersion(projectType), templateCommit = "npm") {
700
686
  await writeManifest(packageDir, {
701
687
  templateType: projectType,
702
- templateVersion: getTemplateVersion(projectType),
703
- templateCommit: "npm",
688
+ templateVersion,
689
+ templateCommit,
704
690
  createdAt: (/* @__PURE__ */ new Date()).toISOString(),
705
691
  placeholders: derivePlaceholders(config.name, config.title, config.repoName),
706
- source: { templatePath: `packages/create-mosaic-module/templates/${projectType}` }
692
+ source: { templatePath: `packages/blueberry/templates/${projectType}` }
707
693
  });
708
694
  const notesPath = path.join(packageDir, "mosaic-template-notes.md");
709
695
  await fs.writeFile(notesPath, `# Mosaic Divergence Notes\n\nDocument intentional differences from the ${projectType} template here.\nClaude reads this file during sync to preserve your customizations.\n\n## Intentional Divergences\n\n_None yet — freshly created from template._\n`);
710
696
  }
711
697
  function buildAppConfig(name, title = toTitleCase(name), repoName = name) {
698
+ const createPackage = readCreatePackageMetadata();
712
699
  return {
713
700
  name,
714
701
  title,
@@ -716,7 +703,9 @@ function buildAppConfig(name, title = toTitleCase(name), repoName = name) {
716
703
  nameUpper: name.toUpperCase(),
717
704
  nameSnake: toSnakeCase(name),
718
705
  repoName,
719
- repoNameSnake: toSnakeCase(repoName)
706
+ repoNameSnake: toSnakeCase(repoName),
707
+ createPackage: createPackage.name,
708
+ createVersion: createPackage.version
720
709
  };
721
710
  }
722
711
  /** Copy the monorepo template into `targetDir` and replace its placeholders. */
@@ -746,7 +735,7 @@ async function scaffoldMonorepo(targetDir, config) {
746
735
  * webapp-only post-copy steps (.env.local, workflow relocation) when applicable.
747
736
  */
748
737
  async function addPackageToMonorepo(args) {
749
- const { packageDir, monorepoRoot, projectType, config } = args;
738
+ const { packageDir, monorepoRoot, projectType, config, templateVersion, templateCommit } = args;
750
739
  const copySpinner = ora("Copying package template...").start();
751
740
  try {
752
741
  await copyTemplate(packageDir, projectType);
@@ -765,27 +754,12 @@ async function addPackageToMonorepo(args) {
765
754
  console.error(error);
766
755
  process.exit(1);
767
756
  }
768
- await writeMosaicFiles(packageDir, config, projectType);
757
+ await writeMosaicFiles(packageDir, config, projectType, templateVersion, templateCommit);
769
758
  if (projectType === "webapp") {
770
- await resolvePerceptaPackageVersions(packageDir);
771
759
  await generateEnvLocal(packageDir);
772
760
  await relocateWorkflowsToRoot(packageDir, monorepoRoot, config.name);
773
761
  }
774
762
  }
775
- async function resolvePerceptaPackageVersions(packageDir) {
776
- const packageJsonPath = path.join(packageDir, "package.json");
777
- if (!await fs.pathExists(packageJsonPath)) return;
778
- const spinner = ora("Resolving latest @percepta/* versions...").start();
779
- try {
780
- const result = await resolvePerceptaVersionsInPackageJson(packageJsonPath);
781
- const count = Object.keys(result.resolved).length;
782
- if (result.failed.length > 0) spinner.warn(`Resolved ${count} @percepta/* versions; kept existing ranges for ${result.failed.join(", ")}`);
783
- else spinner.succeed(`Resolved ${count} @percepta/* versions`);
784
- } catch (error) {
785
- spinner.warn("Could not resolve latest @percepta/* versions; kept template ranges");
786
- console.log(chalk.dim(error.message));
787
- }
788
- }
789
763
  /** Initialize a git repo at `targetDir` with an initial commit. Best-effort. */
790
764
  function initGitRepo(targetDir) {
791
765
  const gitSpinner = ora("Initializing git repository...").start();
@@ -858,6 +832,23 @@ function requireNpmTokenForWebappInstall(projectType, installDeps) {
858
832
  console.error(chalk.dim(" Or pass --skip-install to scaffold without running install."));
859
833
  process.exit(1);
860
834
  }
835
+ function ensureWorkspaceCreateVersionCompatible(workspaceManifest, projectType) {
836
+ if (!workspaceManifest || projectType === "monorepo") return;
837
+ const createPackage = readCreatePackageMetadata();
838
+ if (workspaceManifest.createPackage === createPackage.name && workspaceManifest.createVersion === createPackage.version) return;
839
+ console.log();
840
+ console.error(chalk.red(`Error: This workspace is pinned to ${workspaceManifest.createPackage}@${workspaceManifest.createVersion}, but you are running ${createPackage.name}@${createPackage.version}.`));
841
+ console.error();
842
+ console.error(chalk.dim(" Run the workspace-owned command so new packages match this monorepo:"));
843
+ console.error(chalk.cyan(` pnpm mosaic add ${projectType} <name>`));
844
+ console.error();
845
+ console.error(chalk.dim(" To adopt newer templates, upgrade the workspace manifest first."));
846
+ process.exit(1);
847
+ }
848
+ function getTemplateCommitSource(workspaceManifest) {
849
+ if (!workspaceManifest) return "npm";
850
+ return `${workspaceManifest.createPackage}@${workspaceManifest.createVersion}`;
851
+ }
861
852
  async function createProject(options) {
862
853
  const cwd = await resolveCreateCwd(options.cwd);
863
854
  if (options.type !== void 0 && !isValidProjectType(options.type)) {
@@ -868,6 +859,10 @@ async function createProject(options) {
868
859
  console.log(chalk.bold("Creating a new Mosaic package..."));
869
860
  console.log();
870
861
  const monorepoContext = await detectMonorepo(cwd);
862
+ if (options.addOnly && !monorepoContext.found) {
863
+ console.error(chalk.red("Error: 'create add' must be run inside an existing pnpm monorepo."));
864
+ process.exit(1);
865
+ }
871
866
  if (options.type === "monorepo" && monorepoContext.found) {
872
867
  console.error(chalk.red(`Error: Already inside a monorepo at ${monorepoContext.rootDir}. Choose 'webapp' or 'library' to add a package, or run from outside the monorepo.`));
873
868
  process.exit(1);
@@ -875,6 +870,14 @@ async function createProject(options) {
875
870
  if (monorepoContext.found) console.log(chalk.dim(" Detected monorepo at"), chalk.cyan(monorepoContext.rootDir));
876
871
  else console.log(chalk.dim(" No monorepo detected. A new monorepo will be created."));
877
872
  console.log();
873
+ const workspaceManifest = monorepoContext.found ? await readWorkspaceManifest(monorepoContext.rootDir) : null;
874
+ if (workspaceManifest) {
875
+ console.log(chalk.dim(" Workspace create version:"), chalk.cyan(`${workspaceManifest.createPackage}@${workspaceManifest.createVersion}`));
876
+ console.log();
877
+ } else if (monorepoContext.found) {
878
+ console.log(chalk.yellow("!"), "No .mosaic-workspace.json found; using this CLI's bundled templates.");
879
+ console.log();
880
+ }
878
881
  const projectName = options.name;
879
882
  const repoName = options.repoName;
880
883
  if (options.yes && !projectName) {
@@ -898,6 +901,7 @@ async function createProject(options) {
898
901
  let answers;
899
902
  if (options.yes) {
900
903
  const projectType = options.type || "webapp";
904
+ ensureWorkspaceCreateVersionCompatible(workspaceManifest, projectType);
901
905
  requireNpmTokenForWebappInstall(projectType, !options.skipInstall);
902
906
  const kebabName = toKebabCase(projectName);
903
907
  const kebabRepoName = repoName ? toKebabCase(repoName) : kebabName;
@@ -918,7 +922,10 @@ async function createProject(options) {
918
922
  skipInstall: options.skipInstall,
919
923
  monorepoContext,
920
924
  cwd,
921
- beforeNamePrompt: (projectType) => requireNpmTokenForWebappInstall(projectType, !options.skipInstall)
925
+ beforeNamePrompt: (projectType) => {
926
+ ensureWorkspaceCreateVersionCompatible(workspaceManifest, projectType);
927
+ requireNpmTokenForWebappInstall(projectType, !options.skipInstall);
928
+ }
922
929
  });
923
930
  if (monorepoContext.found && monorepoContext.packageDir && !answers.directory) answers.directory = path.join(monorepoContext.packageDir, answers.name);
924
931
  }
@@ -946,7 +953,9 @@ async function createProject(options) {
946
953
  packageDir,
947
954
  monorepoRoot,
948
955
  projectType: answers.projectType,
949
- config
956
+ config,
957
+ templateVersion: getCompatibleTemplateVersion(workspaceManifest, answers.projectType),
958
+ templateCommit: getTemplateCommitSource(workspaceManifest)
950
959
  });
951
960
  await warnIfMissingRootNpmrc(monorepoRoot);
952
961
  const installSucceeded = await installAtMonorepoRoot(monorepoRoot, answers.installDeps);
@@ -981,11 +990,15 @@ async function createProject(options) {
981
990
  }
982
991
  }
983
992
  await scaffoldMonorepo(monorepoRoot, monorepoConfig);
993
+ const newWorkspaceManifest = createWorkspaceManifest();
994
+ await writeWorkspaceManifest(monorepoRoot, newWorkspaceManifest);
984
995
  if (packageDir && answers.projectType !== "monorepo") await addPackageToMonorepo({
985
996
  packageDir,
986
997
  monorepoRoot,
987
998
  projectType: answers.projectType,
988
- config
999
+ config,
1000
+ templateVersion: getCompatibleTemplateVersion(newWorkspaceManifest, answers.projectType),
1001
+ templateCommit: getTemplateCommitSource(newWorkspaceManifest)
989
1002
  });
990
1003
  initGitRepo(monorepoRoot);
991
1004
  const installSucceeded = await installAtMonorepoRoot(monorepoRoot, answers.installDeps);
@@ -1081,7 +1094,7 @@ function printNextStepsNew(answers, targetDir) {
1081
1094
  const repoRel = shPath(relativePath);
1082
1095
  if (repoRel !== ".") console.log(chalk.dim(` ${step++}.`), `cd ${repoRel}`);
1083
1096
  if (!answers.installDeps) console.log(chalk.dim(` ${step++}.`), `${pm} install`);
1084
- console.log(chalk.dim(` ${step++}.`), `Add a package: ${chalk.cyan(`npx @percepta/create --type webapp <name>`)}`);
1097
+ console.log(chalk.dim(` ${step++}.`), `Add a package: ${chalk.cyan(`pnpm mosaic add webapp <name>`)}`);
1085
1098
  break;
1086
1099
  }
1087
1100
  case "webapp":
@@ -1140,11 +1153,27 @@ function printNextStepsExisting(answers, packageDir) {
1140
1153
  }
1141
1154
  //#endregion
1142
1155
  //#region src/index.ts
1143
- program.name("create").description("Scaffold and manage Mosaic packages").version({
1144
- name: "@percepta/create",
1145
- version: "1.0.0"
1146
- }.version);
1156
+ const packageJson = readCreatePackageMetadata();
1157
+ program.name("create").description("Scaffold and manage Mosaic packages").version(packageJson.version);
1147
1158
  program.command("create", { isDefault: true }).description("Scaffold a new Mosaic package").option("-t, --type <type>", "Package type: monorepo, webapp, or library").option("--name <name>", "Package/app name").option("--repo-name <name>", "Repository name when creating a new monorepo").option("--cwd <dir>", "Run create as if started from this directory").option("--skip-install", "Skip dependency installation (also skips the auto-run setup + dev + browser)", false).option("-y, --yes", "Skip all prompts and use defaults", false).action(createProject);
1159
+ program.command("add").description("Add a Mosaic package to the current monorepo").argument("[typeOrName]", "Package type (webapp/library) or package name").argument("[name]", "Package/app name").option("-t, --type <type>", "Package type: webapp or library").option("--name <name>", "Package/app name").option("--skip-install", "Skip dependency installation (also skips the auto-run setup + dev + browser)", false).option("-y, --yes", "Skip all prompts and use defaults", false).action(async (typeOrName, name, options) => {
1160
+ let projectType = options.type;
1161
+ let projectName = options.name;
1162
+ if (typeOrName === "webapp" || typeOrName === "library") {
1163
+ projectType = projectType ?? typeOrName;
1164
+ projectName = projectName ?? name;
1165
+ } else if (typeOrName === "monorepo") program.error("error: 'add' only supports webapp or library packages");
1166
+ else if (typeOrName) {
1167
+ if (name) program.error(`error: unknown package type "${typeOrName}". Expected webapp or library.`);
1168
+ projectName = projectName ?? typeOrName;
1169
+ }
1170
+ await createProject({
1171
+ ...options,
1172
+ type: projectType,
1173
+ name: projectName,
1174
+ addOnly: true
1175
+ });
1176
+ });
1148
1177
  program.command("status").description("Show template sync status for current app").option("--mosaic-template-path <path>", "Path to local mosaic repo checkout").action(async (options) => {
1149
1178
  const { statusCommand } = await import("./status-CKe4aKso.js");
1150
1179
  await statusCommand(options);
@@ -1154,11 +1183,11 @@ program.command("sync").description("Generate downstream sync context (template
1154
1183
  await syncCommand(options);
1155
1184
  });
1156
1185
  program.command("upstream").description("Generate upstream context (app → template)").option("--mosaic-template-path <path>", "Path to local mosaic repo checkout").option("--files <patterns...>", "Specific files to propose upstream").action(async (options) => {
1157
- const { upstreamCommand } = await import("./upstream-D-LH_1z4.js");
1186
+ const { upstreamCommand } = await import("./upstream-gUHLWSR1.js");
1158
1187
  await upstreamCommand(options);
1159
1188
  });
1160
1189
  program.command("init").description("Add .mosaic-template.json to an existing app").option("-t, --type <type>", "Template type (e.g., webapp, library)").option("--template-version <version>", "Template version to set").action(async (options) => {
1161
- const { initCommand } = await import("./init-CtCp7Tv2.js");
1190
+ const { initCommand } = await import("./init-sI9aIrkU.js");
1162
1191
  await initCommand(options);
1163
1192
  });
1164
1193
  program.parse();