ai-forge-cli 0.4.2 → 0.4.6

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 (84) hide show
  1. package/dist/add-component-AQPCXQ4O.js +120 -0
  2. package/dist/{add-feature-2AR4DP7P.js → add-feature-VEY62Y5M.js} +37 -8
  3. package/dist/add-hook-OE4BKE6B.js +103 -0
  4. package/dist/{add-integration-QXB63S3V.js → add-integration-NJ56UXSY.js} +97 -27
  5. package/dist/add-layout-2KQPPTJX.js +104 -0
  6. package/dist/add-page-GIC2ZXJI.js +81 -0
  7. package/dist/add-util-T5JXAV4G.js +73 -0
  8. package/dist/{chunk-PIFX2L5H.js → chunk-MBF6K2AC.js} +1 -0
  9. package/dist/chunk-YMJTSRWM.js +49 -0
  10. package/dist/index.js +8 -3
  11. package/dist/{init-OYJP5QCZ.js → init-C4FFZDSP.js} +1 -1
  12. package/dist/templates/component/Component.tsx.hbs +11 -0
  13. package/dist/templates/feature/routes/$id.tsx.hbs +3 -13
  14. package/dist/templates/feature/routes/index.tsx.hbs +19 -9
  15. package/dist/templates/feature/src/components/Card.tsx.hbs +21 -0
  16. package/dist/templates/feature/src/components/Detail.tsx.hbs +28 -0
  17. package/dist/templates/feature/src/components/Form.tsx.hbs +46 -0
  18. package/dist/templates/feature/src/components/List.tsx.hbs +26 -0
  19. package/dist/templates/feature/src/components/index.ts.hbs +4 -4
  20. package/dist/templates/hook/feature-hook.ts.hbs +4 -0
  21. package/dist/templates/hook/global-hook.ts.hbs +11 -0
  22. package/dist/templates/init/claude.md.hbs +37 -11
  23. package/dist/templates/integration/auth/src/components/auth/AuthGuard.tsx.hbs +31 -0
  24. package/dist/templates/integration/auth/src/components/auth/LoginForm.tsx.hbs +83 -0
  25. package/dist/templates/integration/auth/src/components/auth/SignupForm.tsx.hbs +102 -0
  26. package/dist/templates/integration/auth/src/components/auth/UserMenu.tsx.hbs +36 -0
  27. package/dist/templates/integration/auth/src/components/auth/index.ts.hbs +4 -0
  28. package/dist/templates/integration/auth/src/routes/login.tsx.hbs +14 -0
  29. package/dist/templates/integration/auth/src/routes/signup.tsx.hbs +14 -0
  30. package/dist/templates/integration/storage/src/components/storage/FilePreview.tsx.hbs +18 -0
  31. package/dist/templates/integration/storage/src/components/storage/FileUpload.tsx.hbs +49 -0
  32. package/dist/templates/integration/storage/src/components/storage/index.ts.hbs +2 -0
  33. package/dist/templates/layout/auth/_layout.tsx.hbs +15 -0
  34. package/dist/templates/layout/auth/index.tsx.hbs +5 -0
  35. package/dist/templates/layout/base/_layout.tsx.hbs +14 -0
  36. package/dist/templates/layout/base/index.tsx.hbs +13 -0
  37. package/dist/templates/layout/dashboard/_layout.tsx.hbs +20 -0
  38. package/dist/templates/layout/dashboard/components/Header.tsx.hbs +12 -0
  39. package/dist/templates/layout/dashboard/components/Sidebar.tsx.hbs +39 -0
  40. package/dist/templates/layout/dashboard/components/index.ts.hbs +2 -0
  41. package/dist/templates/layout/dashboard/index.tsx.hbs +15 -0
  42. package/dist/templates/layout/marketing/_layout.tsx.hbs +39 -0
  43. package/dist/templates/layout/marketing/index.tsx.hbs +16 -0
  44. package/dist/templates/page/components/Content.tsx.hbs +8 -0
  45. package/dist/templates/page/components/index.ts.hbs +1 -0
  46. package/dist/templates/page/route.tsx.hbs +10 -0
  47. package/dist/templates/util/util.ts.hbs +7 -0
  48. package/package.json +1 -1
  49. package/templates/component/Component.tsx.hbs +11 -0
  50. package/templates/feature/routes/$id.tsx.hbs +3 -13
  51. package/templates/feature/routes/index.tsx.hbs +19 -9
  52. package/templates/feature/src/components/Card.tsx.hbs +21 -0
  53. package/templates/feature/src/components/Detail.tsx.hbs +28 -0
  54. package/templates/feature/src/components/Form.tsx.hbs +46 -0
  55. package/templates/feature/src/components/List.tsx.hbs +26 -0
  56. package/templates/feature/src/components/index.ts.hbs +4 -4
  57. package/templates/hook/feature-hook.ts.hbs +4 -0
  58. package/templates/hook/global-hook.ts.hbs +11 -0
  59. package/templates/init/claude.md.hbs +37 -11
  60. package/templates/integration/auth/src/components/auth/AuthGuard.tsx.hbs +31 -0
  61. package/templates/integration/auth/src/components/auth/LoginForm.tsx.hbs +83 -0
  62. package/templates/integration/auth/src/components/auth/SignupForm.tsx.hbs +102 -0
  63. package/templates/integration/auth/src/components/auth/UserMenu.tsx.hbs +36 -0
  64. package/templates/integration/auth/src/components/auth/index.ts.hbs +4 -0
  65. package/templates/integration/auth/src/routes/login.tsx.hbs +14 -0
  66. package/templates/integration/auth/src/routes/signup.tsx.hbs +14 -0
  67. package/templates/integration/storage/src/components/storage/FilePreview.tsx.hbs +18 -0
  68. package/templates/integration/storage/src/components/storage/FileUpload.tsx.hbs +49 -0
  69. package/templates/integration/storage/src/components/storage/index.ts.hbs +2 -0
  70. package/templates/layout/auth/_layout.tsx.hbs +15 -0
  71. package/templates/layout/auth/index.tsx.hbs +5 -0
  72. package/templates/layout/base/_layout.tsx.hbs +14 -0
  73. package/templates/layout/base/index.tsx.hbs +13 -0
  74. package/templates/layout/dashboard/_layout.tsx.hbs +20 -0
  75. package/templates/layout/dashboard/components/Header.tsx.hbs +12 -0
  76. package/templates/layout/dashboard/components/Sidebar.tsx.hbs +39 -0
  77. package/templates/layout/dashboard/components/index.ts.hbs +2 -0
  78. package/templates/layout/dashboard/index.tsx.hbs +15 -0
  79. package/templates/layout/marketing/_layout.tsx.hbs +39 -0
  80. package/templates/layout/marketing/index.tsx.hbs +16 -0
  81. package/templates/page/components/Content.tsx.hbs +8 -0
  82. package/templates/page/components/index.ts.hbs +1 -0
  83. package/templates/page/route.tsx.hbs +10 -0
  84. package/templates/util/util.ts.hbs +7 -0
@@ -0,0 +1,81 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ kebabCase,
4
+ pascalCase,
5
+ renderTemplate
6
+ } from "./chunk-MBF6K2AC.js";
7
+ import {
8
+ fileExists,
9
+ logger,
10
+ readFile,
11
+ writeFile
12
+ } from "./chunk-HL4K5AHI.js";
13
+
14
+ // src/commands/add-page.ts
15
+ import { defineCommand } from "citty";
16
+ import { join } from "path";
17
+ var add_page_default = defineCommand({
18
+ meta: {
19
+ name: "add:page",
20
+ description: "Create a non-feature page (about, pricing, settings)"
21
+ },
22
+ args: {
23
+ name: {
24
+ type: "positional",
25
+ description: "Page name or path (e.g., 'about' or 'settings/profile')",
26
+ required: true
27
+ }
28
+ },
29
+ async run({ args }) {
30
+ const cwd = process.cwd();
31
+ const rawName = args.name;
32
+ const name = kebabCase(rawName);
33
+ const routePath = name.includes("/") ? name.replace(/\//g, ".") : name;
34
+ const componentFolder = name.includes("/") ? name.split("/")[0] : name;
35
+ const componentName = pascalCase(name.replace(/\//g, "-"));
36
+ const routeFile = join(cwd, "src/routes", `${routePath}.tsx`);
37
+ if (await fileExists(routeFile)) {
38
+ logger.error(`Page "${name}" already exists at ${routeFile}`);
39
+ process.exit(1);
40
+ }
41
+ logger.blank();
42
+ const templateData = {
43
+ name,
44
+ routePath,
45
+ componentFolder,
46
+ componentName
47
+ };
48
+ const routeContent = renderTemplate("page/route.tsx.hbs", templateData);
49
+ await writeFile(routeFile, routeContent);
50
+ logger.success(`Created src/routes/${routePath}.tsx`);
51
+ const contentPath = join(cwd, "src/components", componentFolder, `${componentName}Content.tsx`);
52
+ const contentTemplate = renderTemplate("page/components/Content.tsx.hbs", templateData);
53
+ await writeFile(contentPath, contentTemplate);
54
+ logger.success(`Created src/components/${componentFolder}/${componentName}Content.tsx`);
55
+ const indexPath = join(cwd, "src/components", componentFolder, "index.ts");
56
+ const exportLine = `export { ${componentName}Content } from "./${componentName}Content";`;
57
+ if (await fileExists(indexPath)) {
58
+ const existing = await readFile(indexPath);
59
+ if (!existing.includes(exportLine)) {
60
+ await writeFile(indexPath, existing.trim() + "\n" + exportLine + "\n");
61
+ logger.success(`Updated src/components/${componentFolder}/index.ts`);
62
+ }
63
+ } else {
64
+ const indexContent = renderTemplate("page/components/index.ts.hbs", templateData);
65
+ await writeFile(indexPath, indexContent);
66
+ logger.success(`Created src/components/${componentFolder}/index.ts`);
67
+ }
68
+ logger.blank();
69
+ logger.log(` Page "${name}" created!`);
70
+ logger.blank();
71
+ logger.log(" Files:");
72
+ logger.log(` - src/routes/${routePath}.tsx`);
73
+ logger.log(` - src/components/${componentFolder}/${componentName}Content.tsx`);
74
+ logger.blank();
75
+ logger.log(` Visit: http://localhost:3000/${name.replace(/\./g, "/")}`);
76
+ logger.blank();
77
+ }
78
+ });
79
+ export {
80
+ add_page_default as default
81
+ };
@@ -0,0 +1,73 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ camelCase,
4
+ kebabCase,
5
+ renderTemplate
6
+ } from "./chunk-MBF6K2AC.js";
7
+ import {
8
+ fileExists,
9
+ logger,
10
+ readFile,
11
+ writeFile
12
+ } from "./chunk-HL4K5AHI.js";
13
+
14
+ // src/commands/add-util.ts
15
+ import { defineCommand } from "citty";
16
+ import { join } from "path";
17
+ var add_util_default = defineCommand({
18
+ meta: {
19
+ name: "add:util",
20
+ description: "Create a new utility function in src/lib"
21
+ },
22
+ args: {
23
+ name: {
24
+ type: "positional",
25
+ description: "Utility name (will be camelCased)",
26
+ required: true
27
+ }
28
+ },
29
+ async run({ args }) {
30
+ const cwd = process.cwd();
31
+ const rawName = args.name;
32
+ const utilName = camelCase(rawName);
33
+ const fileName = kebabCase(rawName);
34
+ const libDir = join(cwd, "src/lib");
35
+ const utilPath = join(libDir, `${fileName}.ts`);
36
+ const indexPath = join(libDir, "index.ts");
37
+ if (await fileExists(utilPath)) {
38
+ logger.error(`Utility "${utilName}" already exists at src/lib/${fileName}.ts`);
39
+ process.exit(1);
40
+ }
41
+ logger.blank();
42
+ const templateData = {
43
+ utilName,
44
+ fileName
45
+ };
46
+ const content = renderTemplate("util/util.ts.hbs", templateData);
47
+ await writeFile(utilPath, content);
48
+ logger.success(`Created src/lib/${fileName}.ts`);
49
+ const exportLine = `export { ${utilName} } from "./${fileName}";`;
50
+ if (await fileExists(indexPath)) {
51
+ const existing = await readFile(indexPath);
52
+ if (!existing.includes(exportLine)) {
53
+ await writeFile(indexPath, existing.trim() + "\n" + exportLine + "\n");
54
+ logger.success("Updated src/lib/index.ts");
55
+ }
56
+ } else {
57
+ await writeFile(indexPath, exportLine + "\n");
58
+ logger.success("Created src/lib/index.ts");
59
+ }
60
+ logger.blank();
61
+ logger.log(` Utility "${utilName}" created!`);
62
+ logger.blank();
63
+ logger.log(" Location:");
64
+ logger.log(` src/lib/${fileName}.ts`);
65
+ logger.blank();
66
+ logger.log(" Import:");
67
+ logger.log(` import { ${utilName} } from "~/lib";`);
68
+ logger.blank();
69
+ }
70
+ });
71
+ export {
72
+ add_util_default as default
73
+ };
@@ -41,6 +41,7 @@ function renderTemplate(templatePath, data) {
41
41
 
42
42
  export {
43
43
  camelCase,
44
+ pascalCase,
44
45
  kebabCase,
45
46
  renderTemplate
46
47
  };
@@ -0,0 +1,49 @@
1
+ #!/usr/bin/env node
2
+ import {
3
+ logger
4
+ } from "./chunk-HL4K5AHI.js";
5
+
6
+ // src/utils/shadcn.ts
7
+ import { spawn } from "child_process";
8
+ import { existsSync } from "fs";
9
+ import { join } from "path";
10
+ var SHADCN_DEPS = {
11
+ feature: ["button", "input", "label"],
12
+ auth: ["button", "input", "label"],
13
+ storage: ["button"],
14
+ dashboard: ["button", "avatar", "dropdown-menu", "sheet"],
15
+ marketing: ["button"]
16
+ };
17
+ function runCommand(cmd, args, cwd) {
18
+ return new Promise((resolve, reject) => {
19
+ const proc = spawn(cmd, args, { cwd, stdio: "pipe" });
20
+ proc.on("close", (code) => {
21
+ if (code === 0) resolve();
22
+ else reject(new Error(`Command failed with code ${code}`));
23
+ });
24
+ proc.on("error", reject);
25
+ });
26
+ }
27
+ async function ensureShadcnComponents(cwd, type) {
28
+ const required = SHADCN_DEPS[type];
29
+ if (!required || required.length === 0) return;
30
+ const uiDir = join(cwd, "src/components/ui");
31
+ const missing = required.filter(
32
+ (component) => !existsSync(join(uiDir, `${component}.tsx`))
33
+ );
34
+ if (missing.length === 0) {
35
+ return;
36
+ }
37
+ const step = logger.step(`Installing shadcn components: ${missing.join(", ")}`);
38
+ try {
39
+ await runCommand("npx", ["shadcn@latest", "add", ...missing, "--yes"], cwd);
40
+ step.succeed("shadcn components installed");
41
+ } catch {
42
+ step.fail("Could not auto-install shadcn components");
43
+ logger.warn(`Run manually: npx shadcn@latest add ${missing.join(" ")}`);
44
+ }
45
+ }
46
+
47
+ export {
48
+ ensureShadcnComponents
49
+ };
package/dist/index.js CHANGED
@@ -19,9 +19,14 @@ var main = defineCommand({
19
19
  }
20
20
  },
21
21
  subCommands: {
22
- init: () => import("./init-OYJP5QCZ.js").then((m) => m.default),
23
- "add:feature": () => import("./add-feature-2AR4DP7P.js").then((m) => m.default),
24
- "add:integration": () => import("./add-integration-QXB63S3V.js").then((m) => m.default),
22
+ init: () => import("./init-C4FFZDSP.js").then((m) => m.default),
23
+ "add:feature": () => import("./add-feature-VEY62Y5M.js").then((m) => m.default),
24
+ "add:integration": () => import("./add-integration-NJ56UXSY.js").then((m) => m.default),
25
+ "add:page": () => import("./add-page-GIC2ZXJI.js").then((m) => m.default),
26
+ "add:layout": () => import("./add-layout-2KQPPTJX.js").then((m) => m.default),
27
+ "add:component": () => import("./add-component-AQPCXQ4O.js").then((m) => m.default),
28
+ "add:hook": () => import("./add-hook-OE4BKE6B.js").then((m) => m.default),
29
+ "add:util": () => import("./add-util-T5JXAV4G.js").then((m) => m.default),
25
30
  check: () => import("./check-YMGJNKME.js").then((m) => m.default),
26
31
  version: () => import("./version-VO3LHLDO.js").then((m) => m.default)
27
32
  },
@@ -2,7 +2,7 @@
2
2
  import {
3
3
  kebabCase,
4
4
  renderTemplate
5
- } from "./chunk-PIFX2L5H.js";
5
+ } from "./chunk-MBF6K2AC.js";
6
6
  import {
7
7
  ensureDir,
8
8
  fileExists,
@@ -0,0 +1,11 @@
1
+ interface {{componentName}}Props {
2
+ className?: string;
3
+ }
4
+
5
+ export function {{componentName}}({ className }: {{componentName}}Props) {
6
+ return (
7
+ <div className={className}>
8
+ {/* {{componentName}} content */}
9
+ </div>
10
+ );
11
+ }
@@ -1,5 +1,5 @@
1
1
  import { createFileRoute } from "@tanstack/react-router";
2
- import { use{{pascalCase name}} } from "~/features/{{camelCase name}}";
2
+ import { {{pascalCase name}}Detail } from "~/features/{{camelCase name}}";
3
3
  import type { Id } from "@convex/_generated/dataModel";
4
4
 
5
5
  export const Route = createFileRoute("/{{kebabCase name}}/$id")({
@@ -8,20 +8,10 @@ export const Route = createFileRoute("/{{kebabCase name}}/$id")({
8
8
 
9
9
  function {{pascalCase name}}DetailRoute() {
10
10
  const { id } = Route.useParams();
11
- const { item, isLoading } = use{{pascalCase name}}(id as Id<"{{camelCase name}}">);
12
-
13
- if (isLoading) {
14
- return <div>Loading...</div>;
15
- }
16
-
17
- if (!item) {
18
- return <div>Not found</div>;
19
- }
20
11
 
21
12
  return (
22
- <div>
23
- <h1>{{pascalCase name}} Detail</h1>
24
- {/* Build your UI here */}
13
+ <div className="container py-8">
14
+ <{{pascalCase name}}Detail id={id as Id<"{{camelCase name}}">} />
25
15
  </div>
26
16
  );
27
17
  }
@@ -1,21 +1,31 @@
1
1
  import { createFileRoute } from "@tanstack/react-router";
2
- import { use{{pascalCase name}}List } from "~/features/{{camelCase name}}";
2
+ import { {{pascalCase name}}List, {{pascalCase name}}Form } from "~/features/{{camelCase name}}";
3
+ import { useState } from "react";
4
+ import { Button } from "~/components/ui/button";
3
5
 
4
6
  export const Route = createFileRoute("/{{kebabCase name}}/")({
5
7
  component: {{pascalCase name}}IndexRoute,
6
8
  });
7
9
 
8
10
  function {{pascalCase name}}IndexRoute() {
9
- const { items, isLoading } = use{{pascalCase name}}List();
10
-
11
- if (isLoading) {
12
- return <div>Loading...</div>;
13
- }
11
+ const [showForm, setShowForm] = useState(false);
14
12
 
15
13
  return (
16
- <div>
17
- <h1>{{pascalCase name}}</h1>
18
- {/* Build your UI here */}
14
+ <div className="container py-8 space-y-6">
15
+ <div className="flex items-center justify-between">
16
+ <h1 className="text-3xl font-bold">{{pascalCase name}}</h1>
17
+ <Button onClick={() => setShowForm(!showForm)}>
18
+ {showForm ? "Cancel" : "Create New"}
19
+ </Button>
20
+ </div>
21
+
22
+ {showForm && (
23
+ <div className="rounded-lg border p-4">
24
+ <{{pascalCase name}}Form onSuccess={() => setShowForm(false)} />
25
+ </div>
26
+ )}
27
+
28
+ <{{pascalCase name}}List />
19
29
  </div>
20
30
  );
21
31
  }
@@ -0,0 +1,21 @@
1
+ import { Link } from "@tanstack/react-router";
2
+ import type { Doc } from "@convex/_generated/dataModel";
3
+
4
+ interface {{pascalCase name}}CardProps {
5
+ {{camelCase name}}: Doc<"{{camelCase name}}">;
6
+ }
7
+
8
+ export function {{pascalCase name}}Card({ {{camelCase name}} }: {{pascalCase name}}CardProps) {
9
+ return (
10
+ <Link
11
+ to="/{{kebabCase name}}/$id"
12
+ params=\{{ id: {{camelCase name}}._id }}
13
+ className="block rounded-lg border p-4 hover:border-foreground transition-colors"
14
+ >
15
+ <h3 className="font-medium">{{pascalCase name}}</h3>
16
+ <p className="text-sm text-muted-foreground">
17
+ Created {new Date({{camelCase name}}.createdAt).toLocaleDateString()}
18
+ </p>
19
+ </Link>
20
+ );
21
+ }
@@ -0,0 +1,28 @@
1
+ import { use{{pascalCase name}} } from "../hooks";
2
+ import type { Id } from "@convex/_generated/dataModel";
3
+
4
+ interface {{pascalCase name}}DetailProps {
5
+ id: Id<"{{camelCase name}}">;
6
+ }
7
+
8
+ export function {{pascalCase name}}Detail({ id }: {{pascalCase name}}DetailProps) {
9
+ const { item, isLoading } = use{{pascalCase name}}(id);
10
+
11
+ if (isLoading) {
12
+ return <div className="animate-pulse">Loading...</div>;
13
+ }
14
+
15
+ if (!item) {
16
+ return <div>Not found</div>;
17
+ }
18
+
19
+ return (
20
+ <div className="space-y-4">
21
+ <h1 className="text-2xl font-bold">{{pascalCase name}} Details</h1>
22
+ {/* Customize your detail view here */}
23
+ <pre className="rounded bg-muted p-4 text-sm overflow-auto">
24
+ {JSON.stringify(item, null, 2)}
25
+ </pre>
26
+ </div>
27
+ );
28
+ }
@@ -0,0 +1,46 @@
1
+ import { useState } from "react";
2
+ import { use{{pascalCase name}}Mutations } from "../hooks";
3
+ import { Button } from "~/components/ui/button";
4
+ import { Input } from "~/components/ui/input";
5
+ import { Label } from "~/components/ui/label";
6
+
7
+ interface {{pascalCase name}}FormProps {
8
+ onSuccess?: () => void;
9
+ }
10
+
11
+ export function {{pascalCase name}}Form({ onSuccess }: {{pascalCase name}}FormProps) {
12
+ const { create } = use{{pascalCase name}}Mutations();
13
+ const [isLoading, setIsLoading] = useState(false);
14
+
15
+ const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
16
+ e.preventDefault();
17
+ setIsLoading(true);
18
+
19
+ const formData = new FormData(e.currentTarget);
20
+
21
+ try {
22
+ await create({
23
+ // Add your fields here from formData
24
+ // Example: name: formData.get("name") as string,
25
+ });
26
+ e.currentTarget.reset();
27
+ onSuccess?.();
28
+ } finally {
29
+ setIsLoading(false);
30
+ }
31
+ };
32
+
33
+ return (
34
+ <form onSubmit={handleSubmit} className="space-y-4">
35
+ {/* Add your form fields here */}
36
+ <div className="space-y-2">
37
+ <Label htmlFor="name">Name</Label>
38
+ <Input id="name" name="name" required />
39
+ </div>
40
+
41
+ <Button type="submit" disabled={isLoading}>
42
+ {isLoading ? "Creating..." : "Create {{pascalCase name}}"}
43
+ </Button>
44
+ </form>
45
+ );
46
+ }
@@ -0,0 +1,26 @@
1
+ import { use{{pascalCase name}}List } from "../hooks";
2
+ import { {{pascalCase name}}Card } from "./{{pascalCase name}}Card";
3
+
4
+ export function {{pascalCase name}}List() {
5
+ const { items, isLoading } = use{{pascalCase name}}List();
6
+
7
+ if (isLoading) {
8
+ return <div className="animate-pulse">Loading...</div>;
9
+ }
10
+
11
+ if (items.length === 0) {
12
+ return (
13
+ <div className="text-center py-12 text-muted-foreground">
14
+ No {{name}} yet. Create your first one!
15
+ </div>
16
+ );
17
+ }
18
+
19
+ return (
20
+ <div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
21
+ {items.map((item) => (
22
+ <{{pascalCase name}}Card key={item._id} {{camelCase name}}={item} />
23
+ ))}
24
+ </div>
25
+ );
26
+ }
@@ -1,4 +1,4 @@
1
- // Export components as you create them
2
- // export { {{pascalCase name}}Card } from "./{{pascalCase name}}Card";
3
- // export { {{pascalCase name}}List } from "./{{pascalCase name}}List";
4
- // export { {{pascalCase name}}Form } from "./{{pascalCase name}}Form";
1
+ export { {{pascalCase name}}List } from "./{{pascalCase name}}List";
2
+ export { {{pascalCase name}}Card } from "./{{pascalCase name}}Card";
3
+ export { {{pascalCase name}}Form } from "./{{pascalCase name}}Form";
4
+ export { {{pascalCase name}}Detail } from "./{{pascalCase name}}Detail";
@@ -0,0 +1,4 @@
1
+ export function {{hookName}}() {
2
+ // Add your hook logic here
3
+ return {};
4
+ }
@@ -0,0 +1,11 @@
1
+ import { useState, useCallback } from "react";
2
+
3
+ export function {{hookName}}() {
4
+ const [state, setState] = useState<unknown>(null);
5
+
6
+ const update = useCallback((value: unknown) => {
7
+ setState(value);
8
+ }, []);
9
+
10
+ return { state, update };
11
+ }
@@ -25,12 +25,35 @@ The ONLY route you may create directly is `src/routes/index.tsx` (homepage).
25
25
  pnpm dev # Start dev server
26
26
  npx convex dev # Start Convex backend
27
27
  pnpm lint # Check with Biome
28
- forge add:feature <name> # Scaffold a new feature
29
- forge add:integration auth # Add Convex Auth
30
- forge add:integration storage # Add Convex file storage
31
28
  forge check # Validate architecture (run before finishing)
32
29
  ```
33
30
 
31
+ ## Forge CLI Commands
32
+
33
+ ```bash
34
+ # Features (vertical slice architecture)
35
+ forge add:feature <name> # Scaffold full-stack feature (components, hooks, routes, Convex backend)
36
+
37
+ # Pages & Layouts
38
+ forge add:page <name> # Create a non-feature page (about, pricing, settings/profile)
39
+ forge add:layout <name> # Create a layout with preset (--preset dashboard|auth|marketing)
40
+
41
+ # Components, Hooks & Utilities
42
+ forge add:component <name> # Create component in src/components/ui/
43
+ forge add:component <name> --feature <feature> # Add component to a feature
44
+ forge add:component <name> --page <page> # Add component to a page folder
45
+ forge add:component <name> --layout <layout> # Add component to a layout folder
46
+
47
+ forge add:hook <name> # Create hook in src/hooks/
48
+ forge add:hook <name> --feature <feature> # Append hook to feature's hooks.ts
49
+
50
+ forge add:util <name> # Create utility in src/lib/
51
+
52
+ # Integrations
53
+ forge add:integration auth # Add Convex Auth with password flow + UI
54
+ forge add:integration storage # Add Convex file storage + UI components
55
+ ```
56
+
34
57
  ## Integrations
35
58
 
36
59
  For infrastructure (not features), use `forge add:integration`:
@@ -44,9 +67,10 @@ These create files in `convex/` and `src/lib/` - NOT in features.
44
67
  ```
45
68
  src/routes/ → Thin route files (import from features, no logic)
46
69
  src/features/ → Feature code (components/, hooks.ts)
47
- src/components/ui/ → shadcn primitives only
48
- src/components/ → Truly shared components (Header, Footer, Logo)
49
- src/lib/ Utilities + integration hooks (auth.ts, storage.ts)
70
+ src/components/ui/ → shadcn primitives + global custom components
71
+ src/components/ → Page and layout specific components
72
+ src/hooks/ Global hooks (created via forge add:hook)
73
+ src/lib/ → Utilities (cn.ts, auth.ts, storage.ts)
50
74
  convex/features/ → Backend (mirrors src/features/)
51
75
  convex/lib/ → Shared backend utilities (storage.ts)
52
76
  convex/auth.ts → Auth configuration (from forge add:integration auth)
@@ -54,12 +78,12 @@ convex/auth.ts → Auth configuration (from forge add:integration auth)
54
78
 
55
79
  ## Where Components Go
56
80
 
57
- - `src/components/ui/*` → shadcn primitives (Button, Card, Dialog)
58
- - `src/components/*.tsx`Truly shared components used across multiple pages
59
- - `src/features/<name>/components/`Page-specific components
81
+ - `src/components/ui/*` → shadcn primitives (Button, Card, Dialog) + global custom components
82
+ - `src/components/<page>/`Page-specific components (created via `forge add:page` or `forge add:component --page`)
83
+ - `src/components/<layout>/`Layout components (Sidebar, Header - created via `forge add:layout`)
84
+ - `src/features/<name>/components/` → Feature-specific components
60
85
 
61
- **IMPORTANT:** NO subdirectories in `src/components/` except `ui/`.
62
- If you need 3+ components for a page, create a feature with `forge add:feature`.
86
+ Use `forge add:component` with appropriate flags to scaffold components in the correct location.
63
87
 
64
88
  ## Rules
65
89
 
@@ -71,4 +95,6 @@ If you need 3+ components for a page, create a feature with `forge add:feature`.
71
95
 
72
96
  - **Data/Mutations**: Use hooks from `src/features/<name>/hooks.ts`
73
97
  - **UI Components**: Import from `~/components/ui/*` (shadcn)
98
+ - **Global Hooks**: Import from `~/hooks`
99
+ - **Utilities**: Import from `~/lib`
74
100
  - **Styling**: Tailwind classes only
@@ -0,0 +1,31 @@
1
+ "use client";
2
+
3
+ import { useConvexAuth } from "convex/react";
4
+ import { useNavigate } from "@tanstack/react-router";
5
+ import { useEffect } from "react";
6
+
7
+ interface AuthGuardProps {
8
+ children: React.ReactNode;
9
+ fallback?: React.ReactNode;
10
+ }
11
+
12
+ export function AuthGuard({ children, fallback }: AuthGuardProps) {
13
+ const { isAuthenticated, isLoading } = useConvexAuth();
14
+ const navigate = useNavigate();
15
+
16
+ useEffect(() => {
17
+ if (!isLoading && !isAuthenticated) {
18
+ navigate({ to: "/login" });
19
+ }
20
+ }, [isAuthenticated, isLoading, navigate]);
21
+
22
+ if (isLoading) {
23
+ return fallback ?? <div className="flex h-screen items-center justify-center">Loading...</div>;
24
+ }
25
+
26
+ if (!isAuthenticated) {
27
+ return null;
28
+ }
29
+
30
+ return <>{children}</>;
31
+ }