create-githat-app 1.0.4 → 1.0.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.
package/dist/cli.js CHANGED
@@ -11,7 +11,7 @@ import gradient from "gradient-string";
11
11
  import chalk from "chalk";
12
12
 
13
13
  // src/constants.ts
14
- var VERSION = "1.0.4";
14
+ var VERSION = "1.0.6";
15
15
  var DEFAULT_API_URL = "https://api.githat.io";
16
16
  var DASHBOARD_URL = "https://githat.io/dashboard/apps";
17
17
  var BRAND_COLORS = ["#7c3aed", "#6366f1", "#8b5cf6"];
@@ -406,15 +406,77 @@ function openBrowser(url) {
406
406
  } catch {
407
407
  }
408
408
  }
409
+ function sleep(ms) {
410
+ return new Promise((resolve4) => setTimeout(resolve4, ms));
411
+ }
412
+ async function deviceAuthFlow() {
413
+ const spinner2 = p5.spinner();
414
+ try {
415
+ spinner2.start("Requesting device code...");
416
+ const codeRes = await fetch(`${DEFAULT_API_URL}/auth/device/code`, {
417
+ method: "POST",
418
+ headers: { "Content-Type": "application/json" },
419
+ body: JSON.stringify({ client_name: "create-githat-app" })
420
+ });
421
+ if (!codeRes.ok) {
422
+ spinner2.stop("Failed to get device code");
423
+ return null;
424
+ }
425
+ const codeData = await codeRes.json();
426
+ spinner2.stop("Device code generated");
427
+ p5.note(
428
+ `Code: ${codeData.user_code}
429
+
430
+ Opening browser to complete sign-in...
431
+ If it doesn't open, visit: ${codeData.verification_uri_complete}`,
432
+ "Authorize Device"
433
+ );
434
+ openBrowser(codeData.verification_uri_complete);
435
+ spinner2.start("Waiting for browser authorization...");
436
+ const expiresAt = Date.now() + codeData.expires_in * 1e3;
437
+ const pollInterval = (codeData.interval || 5) * 1e3;
438
+ while (Date.now() < expiresAt) {
439
+ await sleep(pollInterval);
440
+ const tokenRes = await fetch(`${DEFAULT_API_URL}/auth/device/token`, {
441
+ method: "POST",
442
+ headers: { "Content-Type": "application/json" },
443
+ body: JSON.stringify({ device_code: codeData.device_code })
444
+ });
445
+ const tokenData = await tokenRes.json();
446
+ if (tokenData.error === "authorization_pending") {
447
+ continue;
448
+ }
449
+ if (tokenData.error === "expired_token") {
450
+ spinner2.stop("Device code expired");
451
+ p5.log.error("Authorization timed out. Please try again.");
452
+ return null;
453
+ }
454
+ if (tokenData.publishable_key) {
455
+ spinner2.stop("Authorized!");
456
+ p5.log.success(`Connected to ${tokenData.app_name || "your app"} (${tokenData.org_name || "your org"})`);
457
+ return tokenData.publishable_key;
458
+ }
459
+ spinner2.stop("Authorization failed");
460
+ return null;
461
+ }
462
+ spinner2.stop("Timed out");
463
+ p5.log.error("Authorization timed out. Please try again.");
464
+ return null;
465
+ } catch (err) {
466
+ spinner2.stop("Connection error");
467
+ p5.log.error("Failed to connect to GitHat API. Check your internet connection.");
468
+ return null;
469
+ }
470
+ }
409
471
  async function promptGitHat(existingKey) {
410
472
  let publishableKey = existingKey || "";
411
473
  if (!publishableKey) {
412
474
  const connectChoice = await p5.select({
413
475
  message: "Connect to GitHat",
414
476
  options: [
415
- { value: "browser", label: "Sign in with browser", hint: "opens githat.io \u2014 recommended" },
416
- { value: "paste", label: "I have a key", hint: "paste your pk_live_... key" },
417
- { value: "skip", label: "Skip for now", hint: "add key to .env later" }
477
+ { value: "skip", label: "Skip for now", hint: "auth works on localhost \u2014 add key later" },
478
+ { value: "browser", label: "Sign in with browser", hint: "opens githat.io to authorize" },
479
+ { value: "paste", label: "I have a key", hint: "paste your pk_live_... key" }
418
480
  ]
419
481
  });
420
482
  if (p5.isCancel(connectChoice)) {
@@ -422,19 +484,10 @@ async function promptGitHat(existingKey) {
422
484
  process.exit(0);
423
485
  }
424
486
  if (connectChoice === "browser") {
425
- p5.log.step("Opening githat.io in your browser...");
426
- openBrowser("https://githat.io/sign-up");
427
- p5.log.info("Sign up (or sign in), then go to Dashboard \u2192 Apps to copy your key.");
428
- const pastedKey = await p5.text({
429
- message: "Paste your publishable key",
430
- placeholder: "pk_live_...",
431
- validate: validatePublishableKey
432
- });
433
- if (p5.isCancel(pastedKey)) {
434
- p5.cancel("Setup cancelled.");
435
- process.exit(0);
487
+ const key = await deviceAuthFlow();
488
+ if (key) {
489
+ publishableKey = key;
436
490
  }
437
- publishableKey = pastedKey || "";
438
491
  } else if (connectChoice === "paste") {
439
492
  const pastedKey = await p5.text({
440
493
  message: "Publishable key",
@@ -446,9 +499,6 @@ async function promptGitHat(existingKey) {
446
499
  process.exit(0);
447
500
  }
448
501
  publishableKey = pastedKey || "";
449
- } else if (connectChoice === "skip") {
450
- p5.log.info("Auth works on localhost without a key (CORS bypass for development).");
451
- p5.log.info("Sign up at githat.io \u2014 a publishable key is auto-created for you.");
452
502
  }
453
503
  }
454
504
  const authFeatures = await p5.multiselect({
@@ -528,13 +578,15 @@ async function promptFinalize() {
528
578
  function toDisplayName(projectName) {
529
579
  return projectName.replace(/[-_]/g, " ").replace(/\b\w/g, (c) => c.toUpperCase());
530
580
  }
531
- function getDefaults(projectName, publishableKey, typescript) {
581
+ function getDefaults(projectName, publishableKey, typescript, fullstack, backendFramework) {
532
582
  const displayName = toDisplayName(projectName);
583
+ const projectType = fullstack ? "fullstack" : "frontend";
533
584
  return {
534
585
  projectName,
535
586
  businessName: displayName,
536
587
  description: `${displayName} \u2014 Built with GitHat`,
537
- projectType: "frontend",
588
+ projectType,
589
+ backendFramework: fullstack ? backendFramework || "hono" : void 0,
538
590
  framework: "nextjs",
539
591
  typescript: typescript ?? true,
540
592
  packageManager: detectPackageManager(),
@@ -544,7 +596,7 @@ function getDefaults(projectName, publishableKey, typescript) {
544
596
  databaseChoice: "none",
545
597
  useTailwind: true,
546
598
  includeDashboard: true,
547
- includeGithatFolder: true,
599
+ includeGithatFolder: projectType === "frontend",
548
600
  initGit: true,
549
601
  installDeps: true
550
602
  };
@@ -552,7 +604,7 @@ function getDefaults(projectName, publishableKey, typescript) {
552
604
  async function runPrompts(args) {
553
605
  if (args.yes && args.initialName) {
554
606
  p8.log.info("Using defaults (--yes flag)");
555
- return getDefaults(args.initialName, args.publishableKey, args.typescript);
607
+ return getDefaults(args.initialName, args.publishableKey, args.typescript, args.fullstack, args.backendFramework);
556
608
  }
557
609
  p8.intro("Let's set up your GitHat app");
558
610
  sectionHeader("Project");
@@ -725,14 +777,14 @@ function createSpinner(text4) {
725
777
  return ora({ text: text4, color: "magenta" });
726
778
  }
727
779
  async function withSpinner(text4, fn, successText) {
728
- const spinner = createSpinner(text4);
729
- spinner.start();
780
+ const spinner2 = createSpinner(text4);
781
+ spinner2.start();
730
782
  try {
731
783
  const result = await fn();
732
- spinner.succeed(successText || text4);
784
+ spinner2.succeed(successText || text4);
733
785
  return result;
734
786
  } catch (err) {
735
- spinner.fail(text4);
787
+ spinner2.fail(text4);
736
788
  throw err;
737
789
  }
738
790
  }
@@ -1436,7 +1488,7 @@ skillsCommand.action(() => {
1436
1488
  // src/cli.ts
1437
1489
  var program = new Command7();
1438
1490
  program.name("githat").description("GitHat CLI - Scaffold apps and manage skills").version(VERSION);
1439
- program.command("create [project-name]", { isDefault: true }).description("Scaffold a new GitHat app").option("--key <key>", "GitHat publishable key (pk_live_...)").option("--ts", "Use TypeScript (default)").option("--js", "Use JavaScript").option("-y, --yes", "Skip prompts and use defaults").action(async (projectName, opts) => {
1491
+ program.command("create [project-name]", { isDefault: true }).description("Scaffold a new GitHat app").option("--key <key>", "GitHat publishable key (pk_live_...)").option("--ts", "Use TypeScript (default)").option("--js", "Use JavaScript").option("--fullstack", "Create fullstack project (Turborepo)").option("--backend <framework>", "Backend framework (hono, express, fastify)").option("-y, --yes", "Skip prompts and use defaults").action(async (projectName, opts) => {
1440
1492
  try {
1441
1493
  displayBanner();
1442
1494
  const typescript = opts.js ? false : opts.ts ? true : void 0;
@@ -1448,7 +1500,9 @@ program.command("create [project-name]", { isDefault: true }).description("Scaff
1448
1500
  initialName: projectName,
1449
1501
  publishableKey: opts.key,
1450
1502
  typescript,
1451
- yes: opts.yes
1503
+ yes: opts.yes,
1504
+ fullstack: opts.fullstack,
1505
+ backendFramework: opts.backend
1452
1506
  });
1453
1507
  const context = answersToContext(answers);
1454
1508
  await scaffold(context, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-githat-app",
3
- "version": "1.0.4",
3
+ "version": "1.0.6",
4
4
  "description": "GitHat CLI — scaffold apps and manage the skills marketplace",
5
5
  "type": "module",
6
6
  "bin": {
@@ -1,11 +1,39 @@
1
1
  {{#if includeForgotPassword}}
2
+ 'use client';
3
+
4
+ import { Suspense } from 'react';
5
+ import { useSearchParams } from 'next/navigation';
2
6
  import { ResetPasswordForm } from '@githat/nextjs';
3
7
 
4
- export default function ResetPasswordPage() {
8
+ function ResetPasswordContent() {
9
+ const searchParams = useSearchParams();
10
+ const token = searchParams.get('token');
11
+
12
+ if (!token) {
13
+ return (
14
+ <main {{#if useTailwind}}className="flex items-center justify-center min-h-screen bg-[#09090b]"{{else}}style=\{{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: '#09090b' }}{{/if}}>
15
+ <div {{#if useTailwind}}className="text-center"{{else}}style=\{{ textAlign: 'center' }}{{/if}}>
16
+ <h1 {{#if useTailwind}}className="text-xl font-semibold text-white mb-2"{{else}}style=\{{ fontSize: '1.25rem', fontWeight: 600, color: '#fafafa', marginBottom: '0.5rem' }}{{/if}}>Invalid reset link</h1>
17
+ <p {{#if useTailwind}}className="text-zinc-400"{{else}}style=\{{ color: '#a1a1aa' }}{{/if}}>
18
+ <a href="/forgot-password" {{#if useTailwind}}className="text-violet-500 hover:underline"{{else}}style=\{{ color: '#7c3aed' }}{{/if}}>Request a new one</a>
19
+ </p>
20
+ </div>
21
+ </main>
22
+ );
23
+ }
24
+
5
25
  return (
6
26
  <main {{#if useTailwind}}className="flex items-center justify-center min-h-screen bg-[#09090b]"{{else}}style=\{{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: '#09090b' }}{{/if}}>
7
- <ResetPasswordForm signInUrl="/sign-in" />
27
+ <ResetPasswordForm token={token} signInUrl="/sign-in" />
8
28
  </main>
9
29
  );
10
30
  }
31
+
32
+ export default function ResetPasswordPage() {
33
+ return (
34
+ <Suspense fallback={<div {{#if useTailwind}}className="flex items-center justify-center min-h-screen bg-[#09090b] text-zinc-400"{{else}}style=\{{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: '#09090b', color: '#a1a1aa' }}{{/if}}>Loading...</div>}>
35
+ <ResetPasswordContent />
36
+ </Suspense>
37
+ );
38
+ }
11
39
  {{/if}}
@@ -1,9 +1,15 @@
1
1
  {{#if includeDashboard}}
2
- import { withAuth } from '@githat/nextjs/server';
2
+ import { redirect } from 'next/navigation';
3
+ import { cookies } from 'next/headers';
4
+
5
+ export default async function DashboardLayout({ children }{{#if typescript}}: { children: React.ReactNode }{{/if}}) {
6
+ const cookieStore = await cookies();
7
+ const token = cookieStore.get('githat_access')?.value;
8
+
9
+ if (!token) {
10
+ redirect('/sign-in');
11
+ }
3
12
 
4
- async function DashboardLayout({ children }{{#if typescript}}: { children: React.ReactNode }{{/if}}) {
5
13
  return <>{children}</>;
6
14
  }
7
-
8
- export default withAuth(DashboardLayout);
9
15
  {{/if}}
@@ -1,9 +1,9 @@
1
1
  {{#if includeDashboard}}
2
2
  'use client';
3
- import { useGitHat, UserButton } from '@githat/nextjs';
3
+ import { useAuth, UserButton } from '@githat/nextjs';
4
4
 
5
5
  export default function DashboardPage() {
6
- const { user, org } = useGitHat();
6
+ const { user, org } = useAuth();
7
7
 
8
8
  return (
9
9
  <div {{#if useTailwind}}className="p-8"{{else}}style=\{{ padding: '2rem' }}{{/if}}>
@@ -11,5 +11,5 @@
11
11
  "devDependencies": {
12
12
  "turbo": "^2.3.0"
13
13
  },
14
- "packageManager": "{{packageManager}}@*"
14
+ "packageManager": "{{packageManager}}@10.9.2"
15
15
  }
@@ -1,13 +1,13 @@
1
1
  {{#if includeForgotPassword}}
2
2
  'use client';
3
3
 
4
- import { useState } from 'react';
4
+ import { Suspense, useState } from 'react';
5
5
  import { useSearchParams, useRouter } from 'next/navigation';
6
6
  {{#if includeGithatFolder}}
7
7
  import { authApi } from '../../../githat/api/auth{{#unless typescript}}.js{{/unless}}';
8
8
  {{/if}}
9
9
 
10
- export default function ResetPasswordPage() {
10
+ function ResetPasswordContent() {
11
11
  const searchParams = useSearchParams();
12
12
  const router = useRouter();
13
13
  const token = searchParams.get('token');
@@ -95,4 +95,12 @@ export default function ResetPasswordPage() {
95
95
  </main>
96
96
  );
97
97
  }
98
+
99
+ export default function ResetPasswordPage() {
100
+ return (
101
+ <Suspense fallback={<div {{#if useTailwind}}className="flex items-center justify-center min-h-screen bg-[#09090b] text-zinc-400"{{else}}style=\{{ display: 'flex', alignItems: 'center', justifyContent: 'center', minHeight: '100vh', background: '#09090b', color: '#a1a1aa' }}{{/if}}>Loading...</div>}>
102
+ <ResetPasswordContent />
103
+ </Suspense>
104
+ );
105
+ }
98
106
  {{/if}}