kaddidlehopper 0.1.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 (183) hide show
  1. package/CONTEXT.md +139 -0
  2. package/README.md +47 -0
  3. package/add-ons/ai/README.md +34 -0
  4. package/add-ons/ai/assets/_dot_env.local.append +13 -0
  5. package/add-ons/ai/assets/src/components/AIAssistant.tsx +149 -0
  6. package/add-ons/ai/assets/src/lib/ai-hook.ts +21 -0
  7. package/add-ons/ai/assets/src/lib/weather-tools.ts +30 -0
  8. package/add-ons/ai/assets/src/routes/api.chat.ts +94 -0
  9. package/add-ons/ai/assets/src/routes/chat.css +175 -0
  10. package/add-ons/ai/assets/src/routes/chat.tsx +141 -0
  11. package/add-ons/ai/info.json +27 -0
  12. package/add-ons/ai/package.json +17 -0
  13. package/add-ons/ai/small-logo.svg +8 -0
  14. package/dist/cli.js +251 -0
  15. package/dist/index.js +33 -0
  16. package/dist/types/cli.d.ts +8 -0
  17. package/dist/types/index.d.ts +2 -0
  18. package/dist/types/types.d.ts +14 -0
  19. package/dist/types.js +1 -0
  20. package/examples/blog/README.md +60 -0
  21. package/examples/blog/assets/content/posts/beach.md +12 -0
  22. package/examples/blog/assets/content/posts/jungle.md.ejs +12 -0
  23. package/examples/blog/assets/content/posts/mountains.md.ejs +12 -0
  24. package/examples/blog/assets/content/posts/snorkeling.md.ejs +12 -0
  25. package/examples/blog/assets/content/posts/waterfall.md.ejs +12 -0
  26. package/examples/blog/assets/content-collections.ts +30 -0
  27. package/examples/blog/assets/public/beach.jpg +0 -0
  28. package/examples/blog/assets/public/jungle.jpg +0 -0
  29. package/examples/blog/assets/public/mountains.jpg +0 -0
  30. package/examples/blog/assets/public/snorkeling.jpg +0 -0
  31. package/examples/blog/assets/public/waterfall.jpg +0 -0
  32. package/examples/blog/assets/src/components/Header.tsx +52 -0
  33. package/examples/blog/assets/src/components/VacayAssistant.tsx +205 -0
  34. package/examples/blog/assets/src/components/blog-posts.tsx +78 -0
  35. package/examples/blog/assets/src/components/ui/card.tsx +92 -0
  36. package/examples/blog/assets/src/lib/blog-ai-hook.ts +25 -0
  37. package/examples/blog/assets/src/lib/blog-tools.ts +111 -0
  38. package/examples/blog/assets/src/lib/utils.ts +6 -0
  39. package/examples/blog/assets/src/routes/__root.tsx +57 -0
  40. package/examples/blog/assets/src/routes/api.blog-chat.ts +117 -0
  41. package/examples/blog/assets/src/routes/category.$category.tsx +19 -0
  42. package/examples/blog/assets/src/routes/index.tsx +19 -0
  43. package/examples/blog/assets/src/routes/posts.$slug.tsx +63 -0
  44. package/examples/blog/assets/src/styles.css +138 -0
  45. package/examples/blog/info.json +43 -0
  46. package/examples/blog/package.json +23 -0
  47. package/examples/events/README.md +110 -0
  48. package/examples/events/assets/content/speakers/andre-costa.md +22 -0
  49. package/examples/events/assets/content/speakers/hans-mueller.md.ejs +22 -0
  50. package/examples/events/assets/content/speakers/isabella-martinez.md.ejs +22 -0
  51. package/examples/events/assets/content/speakers/kenji-nakamura.md.ejs +22 -0
  52. package/examples/events/assets/content/speakers/marie-dubois.md.ejs +20 -0
  53. package/examples/events/assets/content/speakers/priya-sharma.md.ejs +22 -0
  54. package/examples/events/assets/content/talks/croissant-lamination-secrets.md +39 -0
  55. package/examples/events/assets/content/talks/french-macaron-mastery.md.ejs +39 -0
  56. package/examples/events/assets/content/talks/neapolitan-pizza-tradition-meets-innovation.md.ejs +39 -0
  57. package/examples/events/assets/content/talks/savory-breads-of-the-mediterranean.md.ejs +39 -0
  58. package/examples/events/assets/content/talks/sourdough-from-starter-to-masterpiece.md.ejs +36 -0
  59. package/examples/events/assets/content/talks/the-art-of-the-perfect-tart.md.ejs +32 -0
  60. package/examples/events/assets/content/talks/the-science-of-sugar.md.ejs +39 -0
  61. package/examples/events/assets/content/talks/umami-in-pastry-east-meets-west.md.ejs +39 -0
  62. package/examples/events/assets/content-collections.ts +56 -0
  63. package/examples/events/assets/public/background-1.jpg +0 -0
  64. package/examples/events/assets/public/background-2.jpg +0 -0
  65. package/examples/events/assets/public/background-3.jpg +0 -0
  66. package/examples/events/assets/public/background-4.jpg +0 -0
  67. package/examples/events/assets/public/conference-logo.png +0 -0
  68. package/examples/events/assets/public/favicon.ico +0 -0
  69. package/examples/events/assets/public/speakers/andre-costa.jpg +0 -0
  70. package/examples/events/assets/public/speakers/hans-mueller.jpg +0 -0
  71. package/examples/events/assets/public/speakers/isabella-martinez.jpg +0 -0
  72. package/examples/events/assets/public/speakers/kenji-nakamura.jpg +0 -0
  73. package/examples/events/assets/public/speakers/marie-dubois.jpg +0 -0
  74. package/examples/events/assets/public/speakers/priya-sharma.jpg +0 -0
  75. package/examples/events/assets/public/talks/croissant-lamination-secrets.jpg +0 -0
  76. package/examples/events/assets/public/talks/french-macaron-mastery.jpg +0 -0
  77. package/examples/events/assets/public/talks/neapolitan-pizza-tradition-meets-innovation.jpg +0 -0
  78. package/examples/events/assets/public/talks/savory-breads-of-the-mediterranean.jpg +0 -0
  79. package/examples/events/assets/public/talks/sourdough-from-starter-to-masterpiece.jpg +0 -0
  80. package/examples/events/assets/public/talks/the-art-of-the-perfect-tart.jpg +0 -0
  81. package/examples/events/assets/public/talks/the-science-of-sugar.jpg +0 -0
  82. package/examples/events/assets/public/talks/umami-in-pastry-east-meets-west.jpg +0 -0
  83. package/examples/events/assets/public/tanstack-circle-logo.png +0 -0
  84. package/examples/events/assets/public/tanstack-word-logo-white.svg +1 -0
  85. package/examples/events/assets/src/components/Header.tsx +59 -0
  86. package/examples/events/assets/src/components/HeaderNav.tsx +67 -0
  87. package/examples/events/assets/src/components/HeroCarousel.tsx +61 -0
  88. package/examples/events/assets/src/components/RemyAssistant.tsx +207 -0
  89. package/examples/events/assets/src/components/SpeakerCard.tsx +67 -0
  90. package/examples/events/assets/src/components/TalkCard.tsx +77 -0
  91. package/examples/events/assets/src/components/ui/card.tsx +92 -0
  92. package/examples/events/assets/src/lib/conference-ai-hook.ts +26 -0
  93. package/examples/events/assets/src/lib/conference-tools.ts +210 -0
  94. package/examples/events/assets/src/lib/model-selection.ts +1 -0
  95. package/examples/events/assets/src/lib/utils.ts +6 -0
  96. package/examples/events/assets/src/routes/__root.tsx +70 -0
  97. package/examples/events/assets/src/routes/api.remy-chat.ts +119 -0
  98. package/examples/events/assets/src/routes/index.tsx +192 -0
  99. package/examples/events/assets/src/routes/schedule.index.tsx +274 -0
  100. package/examples/events/assets/src/routes/speakers.$slug.tsx +122 -0
  101. package/examples/events/assets/src/routes/speakers.index.tsx +40 -0
  102. package/examples/events/assets/src/routes/talks.$slug.tsx +116 -0
  103. package/examples/events/assets/src/routes/talks.index.tsx +40 -0
  104. package/examples/events/assets/src/styles.css +182 -0
  105. package/examples/events/info.json +74 -0
  106. package/examples/events/package.json +23 -0
  107. package/examples/marketing/README.md +60 -0
  108. package/examples/marketing/assets/public/logo.png +0 -0
  109. package/examples/marketing/assets/public/motorcycle-adventure.jpg +0 -0
  110. package/examples/marketing/assets/public/motorcycle-cruiser.jpg +0 -0
  111. package/examples/marketing/assets/public/motorcycle-scooter.jpg +0 -0
  112. package/examples/marketing/assets/public/motorcycle-sport.jpg +0 -0
  113. package/examples/marketing/assets/public/motorcycle-supersport.jpg +0 -0
  114. package/examples/marketing/assets/src/components/Header.tsx +36 -0
  115. package/examples/marketing/assets/src/components/MotorcycleAIAssistant.tsx +162 -0
  116. package/examples/marketing/assets/src/components/MotorcycleRecommendation.tsx +53 -0
  117. package/examples/marketing/assets/src/data/motorcycles.ts.ejs +77 -0
  118. package/examples/marketing/assets/src/lib/motorcycle-ai-hook.ts +24 -0
  119. package/examples/marketing/assets/src/lib/motorcycle-tools.ts +42 -0
  120. package/examples/marketing/assets/src/routes/__root.tsx +57 -0
  121. package/examples/marketing/assets/src/routes/api.motorcycle-chat.ts +78 -0
  122. package/examples/marketing/assets/src/routes/index.tsx +72 -0
  123. package/examples/marketing/assets/src/routes/motorcycles/$motorcycleId.tsx +56 -0
  124. package/examples/marketing/assets/src/store/motorcycle-assistant.ts +3 -0
  125. package/examples/marketing/assets/src/styles.css +212 -0
  126. package/examples/marketing/info.json +38 -0
  127. package/examples/marketing/package.json +14 -0
  128. package/examples/resume/README.md +82 -0
  129. package/examples/resume/assets/content/education/code-school.md +17 -0
  130. package/examples/resume/assets/content/jobs/freelance.md.ejs +13 -0
  131. package/examples/resume/assets/content/jobs/initech-junior.md +20 -0
  132. package/examples/resume/assets/content/jobs/initech-lead.md.ejs +29 -0
  133. package/examples/resume/assets/content/jobs/initrode-senior.md.ejs +28 -0
  134. package/examples/resume/assets/content-collections.ts +36 -0
  135. package/examples/resume/assets/public/headshot-on-white.jpg +0 -0
  136. package/examples/resume/assets/src/components/Header.tsx +33 -0
  137. package/examples/resume/assets/src/components/ResumeAssistant.tsx +193 -0
  138. package/examples/resume/assets/src/components/ui/badge.tsx +46 -0
  139. package/examples/resume/assets/src/components/ui/card.tsx +92 -0
  140. package/examples/resume/assets/src/components/ui/checkbox.tsx +30 -0
  141. package/examples/resume/assets/src/components/ui/hover-card.tsx +44 -0
  142. package/examples/resume/assets/src/components/ui/separator.tsx +26 -0
  143. package/examples/resume/assets/src/lib/resume-ai-hook.ts +21 -0
  144. package/examples/resume/assets/src/lib/resume-tools.ts +165 -0
  145. package/examples/resume/assets/src/lib/utils.ts +6 -0
  146. package/examples/resume/assets/src/routes/api.resume-chat.ts +110 -0
  147. package/examples/resume/assets/src/routes/index.tsx +220 -0
  148. package/examples/resume/assets/src/styles.css +138 -0
  149. package/examples/resume/info.json +25 -0
  150. package/examples/resume/package.json +26 -0
  151. package/package.json +39 -0
  152. package/project/base/_dot_claude/skills/content-collections/SKILL.md +505 -0
  153. package/project/base/_dot_claude/skills/netlify-blobs/SKILL.md +410 -0
  154. package/project/base/_dot_claude/skills/netlify-db/SKILL.md +424 -0
  155. package/project/base/_dot_claude/skills/netlify-debugging/SKILL.md +419 -0
  156. package/project/base/_dot_claude/skills/netlify-forms/SKILL.md +243 -0
  157. package/project/base/_dot_claude/skills/netlify-functions/SKILL.md +372 -0
  158. package/project/base/_dot_claude/skills/tanstack-start-api-routes/SKILL.md +421 -0
  159. package/project/base/_dot_claude/skills/tanstack-start-loaders/SKILL.md +426 -0
  160. package/project/base/_dot_claude/skills/tanstack-start-project-setup/SKILL.md +493 -0
  161. package/project/base/_dot_claude/skills/tanstack-start-routes/SKILL.md +430 -0
  162. package/project/base/_dot_claude/skills/tanstack-start-server-functions/SKILL.md +445 -0
  163. package/project/base/_dot_claude/skills/tanstack-start-typesafe-routing/SKILL.md +494 -0
  164. package/project/base/_dot_gitignore +8 -0
  165. package/project/base/netlify.toml +7 -0
  166. package/project/base/package.json +33 -0
  167. package/project/base/public/favicon.ico +0 -0
  168. package/project/base/public/tanstack-circle-logo.png +0 -0
  169. package/project/base/public/tanstack-word-logo-white.svg +1 -0
  170. package/project/base/src/components/Header.tsx +17 -0
  171. package/project/base/src/components/HeaderNav.tsx.ejs +179 -0
  172. package/project/base/src/router.tsx +15 -0
  173. package/project/base/src/routes/__root.tsx +57 -0
  174. package/project/base/src/routes/index.tsx +48 -0
  175. package/project/base/src/styles.css +15 -0
  176. package/project/base/tsconfig.json +28 -0
  177. package/project/base/vite.config.ts.ejs +25 -0
  178. package/project/packages.json +22 -0
  179. package/scripts/check-outdated-packages.js +421 -0
  180. package/src/cli.ts +343 -0
  181. package/src/index.ts +49 -0
  182. package/src/types.ts +15 -0
  183. package/tsconfig.json +17 -0
@@ -0,0 +1,175 @@
1
+ @import "tailwindcss";
2
+ @import "highlight.js/styles/github-dark.css";
3
+ @source "../../../../node_modules/streamdown/dist/*.js";
4
+
5
+ /* Custom scrollbar styles */
6
+ ::-webkit-scrollbar {
7
+ width: 8px;
8
+ }
9
+
10
+ ::-webkit-scrollbar-track {
11
+ background: transparent;
12
+ }
13
+
14
+ ::-webkit-scrollbar-thumb {
15
+ background-color: rgba(156, 163, 175, 0.5);
16
+ border-radius: 4px;
17
+ }
18
+
19
+ ::-webkit-scrollbar-thumb:hover {
20
+ background-color: rgba(156, 163, 175, 0.7);
21
+ }
22
+
23
+ /* Smooth transitions for dark mode */
24
+ html {
25
+ transition: background-color 0.3s ease;
26
+ }
27
+
28
+ /* Markdown content styles */
29
+ .prose {
30
+ max-width: none;
31
+ color: #e5e7eb; /* text-gray-200 */
32
+ }
33
+
34
+ .prose code {
35
+ color: #e5e7eb;
36
+ background-color: rgba(31, 41, 55, 0.5);
37
+ padding: 0.2em 0.4em;
38
+ border-radius: 0.375rem;
39
+ font-size: 0.875em;
40
+ }
41
+
42
+ .prose pre {
43
+ background-color: rgba(31, 41, 55, 0.5);
44
+ border-radius: 0.5rem;
45
+ padding: 1rem;
46
+ margin: 1.25em 0;
47
+ overflow-x: auto;
48
+ }
49
+
50
+ .prose pre code {
51
+ background-color: transparent;
52
+ padding: 0;
53
+ border-radius: 0;
54
+ color: inherit;
55
+ }
56
+
57
+ .prose h1,
58
+ .prose h2,
59
+ .prose h3,
60
+ .prose h4 {
61
+ color: #f9fafb; /* text-gray-50 */
62
+ }
63
+
64
+ .prose ul,
65
+ .prose ol {
66
+ margin-top: 1.25em;
67
+ margin-bottom: 1.25em;
68
+ padding-left: 1.625em;
69
+ }
70
+
71
+ .prose li {
72
+ margin-top: 0.5em;
73
+ margin-bottom: 0.5em;
74
+ }
75
+
76
+ .prose blockquote {
77
+ border-left-color: #f97316; /* orange-500 */
78
+ background-color: rgba(249, 115, 22, 0.1);
79
+ padding: 1em;
80
+ margin: 1.25em 0;
81
+ border-radius: 0.5rem;
82
+ }
83
+
84
+ .prose hr {
85
+ border-color: rgba(249, 115, 22, 0.2);
86
+ margin: 2em 0;
87
+ }
88
+
89
+ .prose a {
90
+ color: #f97316; /* orange-500 */
91
+ text-decoration: underline;
92
+ text-decoration-thickness: 0.1em;
93
+ text-underline-offset: 0.2em;
94
+ }
95
+
96
+ .prose a:hover {
97
+ color: #fb923c; /* orange-400 */
98
+ }
99
+
100
+ .prose table {
101
+ width: 100%;
102
+ border-collapse: collapse;
103
+ margin: 1.25em 0;
104
+ }
105
+
106
+ .prose th,
107
+ .prose td {
108
+ padding: 0.75em;
109
+ border: 1px solid rgba(249, 115, 22, 0.2);
110
+ }
111
+
112
+ .prose th {
113
+ background-color: rgba(249, 115, 22, 0.1);
114
+ font-weight: 600;
115
+ }
116
+
117
+ /* Message transition animations */
118
+ .message-enter {
119
+ opacity: 0;
120
+ transform: translateY(10px);
121
+ }
122
+
123
+ .message-enter-active {
124
+ opacity: 1;
125
+ transform: translateY(0);
126
+ transition:
127
+ opacity 300ms,
128
+ transform 300ms;
129
+ }
130
+
131
+ .message-exit {
132
+ opacity: 1;
133
+ }
134
+
135
+ .message-exit-active {
136
+ opacity: 0;
137
+ transition: opacity 300ms;
138
+ }
139
+
140
+ /* Heading styles */
141
+ .prose h1 {
142
+ font-size: 2em;
143
+ margin-bottom: 0.5em;
144
+ }
145
+
146
+ .prose h2 {
147
+ font-size: 1.5em;
148
+ margin-top: 1em;
149
+ margin-bottom: 0.5em;
150
+ }
151
+
152
+ .prose h3 {
153
+ font-size: 1.25em;
154
+ margin-top: 1em;
155
+ margin-bottom: 0.5em;
156
+ }
157
+
158
+ .prose ul {
159
+ list-style-type: disc;
160
+ padding-left: 1.5em;
161
+ }
162
+
163
+ .prose ol {
164
+ list-style-type: decimal;
165
+ padding-left: 1.5em;
166
+ }
167
+
168
+ .prose strong {
169
+ color: #f9fafb; /* text-gray-50 */
170
+ font-weight: 600;
171
+ }
172
+
173
+ .prose em {
174
+ font-style: italic;
175
+ }
@@ -0,0 +1,141 @@
1
+ import { useEffect, useRef, useState } from 'react'
2
+ import { createFileRoute } from '@tanstack/react-router'
3
+ import { Send, Square } from 'lucide-react'
4
+ import { Streamdown } from 'streamdown'
5
+
6
+ import { useAIChat } from '@/lib/ai-hook'
7
+ import type { ChatMessages } from '@/lib/ai-hook'
8
+
9
+ import './chat.css'
10
+
11
+ function Messages({ messages }: { messages: ChatMessages }) {
12
+ const messagesContainerRef = useRef<HTMLDivElement>(null)
13
+
14
+ useEffect(() => {
15
+ if (messagesContainerRef.current) {
16
+ messagesContainerRef.current.scrollTop =
17
+ messagesContainerRef.current.scrollHeight
18
+ }
19
+ }, [messages])
20
+
21
+ if (!messages.length) {
22
+ return null
23
+ }
24
+
25
+ return (
26
+ <div
27
+ ref={messagesContainerRef}
28
+ className="flex-1 overflow-y-auto pb-4 min-h-0"
29
+ >
30
+ <div className="max-w-3xl mx-auto w-full px-4">
31
+ {messages.map((message) => (
32
+ <div
33
+ key={message.id}
34
+ className={`p-4 ${
35
+ message.role === 'assistant'
36
+ ? 'bg-linear-to-r from-orange-500/5 to-red-600/5'
37
+ : 'bg-transparent'
38
+ }`}
39
+ >
40
+ <div className="flex items-start gap-4 max-w-3xl mx-auto w-full">
41
+ <div className={`w-8 h-8 rounded-lg flex items-center justify-center text-sm font-medium text-white flex-shrink-0 ${
42
+ message.role === 'assistant'
43
+ ? 'bg-linear-to-r from-orange-500 to-red-600'
44
+ : 'bg-gray-700'
45
+ }`}>
46
+ {message.role === 'assistant' ? 'AI' : 'Y'}
47
+ </div>
48
+ <div className="flex-1 min-w-0">
49
+ {message.parts.map((part, index) => {
50
+ if (part.type === 'text' && part.content) {
51
+ return (
52
+ <div
53
+ className="flex-1 min-w-0 prose dark:prose-invert max-w-none prose-sm"
54
+ key={index}
55
+ >
56
+ <Streamdown>{part.content}</Streamdown>
57
+ </div>
58
+ )
59
+ }
60
+ return null
61
+ })}
62
+ </div>
63
+ </div>
64
+ </div>
65
+ ))}
66
+ </div>
67
+ </div>
68
+ )
69
+ }
70
+
71
+ function ChatPage() {
72
+ const [input, setInput] = useState('')
73
+ const { messages, sendMessage, isLoading, stop } = useAIChat()
74
+
75
+ return (
76
+ <div className="relative flex h-[calc(100vh-80px)] bg-gray-900">
77
+ <div className="flex-1 flex flex-col min-h-0">
78
+ {messages.length === 0 && (
79
+ <div className="flex-1 flex items-center justify-center px-4">
80
+ <div className="text-center max-w-3xl mx-auto w-full">
81
+ <h1 className="text-4xl font-bold mb-4 text-white">
82
+ Weather Chat
83
+ </h1>
84
+ <p className="text-gray-400 mb-6">
85
+ Ask me about the weather! Try "What's the weather in Paris?"
86
+ </p>
87
+ </div>
88
+ </div>
89
+ )}
90
+ <Messages messages={messages} />
91
+
92
+ <div className="sticky bottom-0 left-0 right-0 bg-gray-900/80 backdrop-blur-sm border-t border-orange-500/10 z-10">
93
+ <div className="max-w-3xl mx-auto w-full px-4 py-3">
94
+ {isLoading && (
95
+ <div className="flex items-center justify-center mb-3">
96
+ <button
97
+ onClick={stop}
98
+ className="px-4 py-2 bg-red-600 hover:bg-red-700 text-white rounded-lg text-sm font-medium transition-colors flex items-center gap-2"
99
+ >
100
+ <Square className="w-4 h-4 fill-current" />
101
+ Stop
102
+ </button>
103
+ </div>
104
+ )}
105
+ <form
106
+ onSubmit={(e) => {
107
+ e.preventDefault()
108
+ if (input.trim()) {
109
+ sendMessage(input)
110
+ setInput('')
111
+ }
112
+ }}
113
+ >
114
+ <div className="relative max-w-xl mx-auto flex items-center gap-2">
115
+ <input
116
+ type="text"
117
+ value={input}
118
+ onChange={(e) => setInput(e.target.value)}
119
+ placeholder="Ask about the weather..."
120
+ className="w-full rounded-lg border border-orange-500/20 bg-gray-800/50 px-4 py-3 text-sm text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-orange-500/50"
121
+ disabled={isLoading}
122
+ />
123
+ <button
124
+ type="submit"
125
+ disabled={!input.trim() || isLoading}
126
+ className="p-3 bg-orange-500 hover:bg-orange-600 disabled:bg-gray-600 text-white rounded-lg transition-colors"
127
+ >
128
+ <Send className="w-4 h-4" />
129
+ </button>
130
+ </div>
131
+ </form>
132
+ </div>
133
+ </div>
134
+ </div>
135
+ </div>
136
+ )
137
+ }
138
+
139
+ export const Route = createFileRoute('/chat')({
140
+ component: ChatPage,
141
+ })
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "AI Chat",
3
+ "phase": "add-on",
4
+ "description": "Add AI chat capabilities with multi-vendor support (OpenAI, Anthropic, Gemini, Ollama). Includes a chat page and pop-up assistant.",
5
+ "link": "https://tanstack.com/ai",
6
+ "modes": ["file-router"],
7
+ "type": "add-on",
8
+ "priority": 20,
9
+ "routes": [
10
+ {
11
+ "icon": "MessagesSquare",
12
+ "url": "/chat",
13
+ "name": "Chat",
14
+ "path": "src/routes/chat.tsx",
15
+ "jsName": "ChatPage"
16
+ }
17
+ ],
18
+ "integrations": [
19
+ {
20
+ "type": "header-user",
21
+ "path": "src/components/AIAssistant.tsx",
22
+ "jsName": "AIAssistant"
23
+ }
24
+ ],
25
+ "dependsOn": [],
26
+ "variables": []
27
+ }
@@ -0,0 +1,17 @@
1
+ {
2
+ "dependencies": {
3
+ "@tanstack/ai": "0.2.2",
4
+ "@tanstack/ai-anthropic": "0.2.0",
5
+ "@tanstack/ai-client": "0.2.2",
6
+ "@tanstack/ai-gemini": "0.3.2",
7
+ "@tanstack/ai-ollama": "0.3.0",
8
+ "@tanstack/ai-openai": "0.3.0",
9
+ "@tanstack/ai-react": "0.2.2",
10
+ "@tanstack/react-store": "^0.8.0",
11
+ "@tanstack/store": "^0.8.0",
12
+ "highlight.js": "^11.11.1",
13
+ "streamdown": "^2.1.0",
14
+ "lucide-react": "^0.563.0",
15
+ "zod": "^4.3.5"
16
+ }
17
+ }
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
2
+ <path d="M12 8V4H8"/>
3
+ <rect width="16" height="12" x="4" y="8" rx="2"/>
4
+ <path d="M2 14h2"/>
5
+ <path d="M20 14h2"/>
6
+ <path d="M15 13v2"/>
7
+ <path d="M9 13v2"/>
8
+ </svg>
package/dist/cli.js ADDED
@@ -0,0 +1,251 @@
1
+ import { resolve, basename, join } from 'node:path';
2
+ import { unlink } from 'node:fs/promises';
3
+ import { Command, InvalidArgumentError } from 'commander';
4
+ import chalk from 'chalk';
5
+ import validatePackageName from 'validate-npm-package-name';
6
+ import { SUPPORTED_PACKAGE_MANAGERS, createApp, createDefaultEnvironment, finalizeAddOns, getAllAddOns, getFrameworkByName, getPackageManager, DEFAULT_PACKAGE_MANAGER, populateAddOnOptionsDefaults, } from '@tanstack/cta-engine';
7
+ // Utility functions
8
+ function sanitizePackageName(name) {
9
+ return name
10
+ .toLowerCase()
11
+ .replace(/\s+/g, '-')
12
+ .replace(/_/g, '-')
13
+ .replace(/[^a-z0-9-]/g, '')
14
+ .replace(/^[^a-z]+/, '')
15
+ .replace(/-+/g, '-')
16
+ .replace(/-$/, '');
17
+ }
18
+ function getCurrentDirectoryName() {
19
+ return basename(process.cwd());
20
+ }
21
+ function validateProjectName(name) {
22
+ const { validForNewPackages, validForOldPackages, errors, warnings } = validatePackageName(name);
23
+ const error = errors?.[0] || warnings?.[0];
24
+ return {
25
+ valid: validForNewPackages && validForOldPackages,
26
+ error: error?.replace(/name/g, 'Project name') ||
27
+ 'Project name does not meet npm package naming requirements',
28
+ };
29
+ }
30
+ // Create environment with UI functions for console output
31
+ function createEnvironment(appName) {
32
+ const env = createDefaultEnvironment();
33
+ env.appName = appName;
34
+ env.intro = (message) => console.log(chalk.bold.cyan(message));
35
+ env.outro = (message) => console.log(chalk.bold.green(message));
36
+ env.info = (title, message) => {
37
+ if (title && message) {
38
+ console.log(chalk.blue(`${title}: ${message}`));
39
+ }
40
+ else if (title) {
41
+ console.log(chalk.blue(title));
42
+ }
43
+ };
44
+ env.error = (title, message) => {
45
+ if (title && message) {
46
+ console.error(chalk.red(`${title}: ${message}`));
47
+ }
48
+ else if (title) {
49
+ console.error(chalk.red(title));
50
+ }
51
+ };
52
+ env.warn = (title, message) => {
53
+ if (title && message) {
54
+ console.warn(chalk.yellow(`${title}: ${message}`));
55
+ }
56
+ else if (title) {
57
+ console.warn(chalk.yellow(title));
58
+ }
59
+ };
60
+ env.spinner = () => ({
61
+ start: (message) => console.log(chalk.gray(`⟳ ${message}`)),
62
+ stop: (message) => console.log(chalk.green(`✓ ${message}`)),
63
+ });
64
+ return env;
65
+ }
66
+ export function cli({ name, appName, defaultFramework, forcedMode, forcedAddOns = [], }) {
67
+ const environment = createEnvironment(appName);
68
+ const program = new Command();
69
+ const defaultMode = forcedMode || 'file-router';
70
+ program.name(name).description(`CLI to create a new ${appName} application`);
71
+ program.argument('[project-name]', 'name of the project');
72
+ program
73
+ .option('--no-install', 'skip installing dependencies')
74
+ .option(`--package-manager <${SUPPORTED_PACKAGE_MANAGERS.join('|')}>`, `Explicitly tell the CLI to use this package manager`, (value) => {
75
+ if (!SUPPORTED_PACKAGE_MANAGERS.includes(value)) {
76
+ throw new InvalidArgumentError(`Invalid package manager: ${value}. The following are allowed: ${SUPPORTED_PACKAGE_MANAGERS.join(', ')}`);
77
+ }
78
+ return value;
79
+ })
80
+ .option('--add-ons [...add-ons]', 'pick from a list of available add-ons (comma separated list)', (value) => {
81
+ let addOns = !!value;
82
+ if (typeof value === 'string') {
83
+ addOns = value.split(',').map((addon) => addon.trim());
84
+ }
85
+ return addOns;
86
+ })
87
+ .option('--list-add-ons', 'list all available add-ons', false)
88
+ .option('--list-addons-json', 'list all available add-ons as JSON', false)
89
+ .option('--addon-details <addon-id>', 'show detailed information about a specific add-on')
90
+ .option('--no-git', 'do not create a git repository')
91
+ .option('--target-dir <path>', 'the target directory for the application root')
92
+ .option('-f, --force', 'force project creation even if the target directory is not empty', false)
93
+ .option('--bare-bones', 'create minimal scaffolding for LLM modification', false);
94
+ program.action(async (projectName, options) => {
95
+ const framework = getFrameworkByName(defaultFramework);
96
+ if (!framework) {
97
+ console.error(`Framework '${defaultFramework}' not found`);
98
+ process.exit(1);
99
+ }
100
+ // Handle --list-addons-json
101
+ if (options.listAddonsJson) {
102
+ const addOns = await getAllAddOns(framework, defaultMode);
103
+ const serialized = addOns
104
+ .filter((a) => !forcedAddOns.includes(a.id))
105
+ .map((addon) => ({
106
+ id: addon.id,
107
+ name: addon.name,
108
+ description: addon.description,
109
+ type: addon.type,
110
+ options: addon.options,
111
+ }));
112
+ console.log(JSON.stringify(serialized, null, 2));
113
+ return;
114
+ }
115
+ // Handle --list-add-ons (text format)
116
+ if (options.listAddOns) {
117
+ const addOns = await getAllAddOns(framework, defaultMode);
118
+ let hasConfigurableAddOns = false;
119
+ for (const addOn of addOns.filter((a) => !forcedAddOns.includes(a.id))) {
120
+ const hasOptions = addOn.options && Object.keys(addOn.options).length > 0;
121
+ const optionMarker = hasOptions ? '*' : ' ';
122
+ if (hasOptions)
123
+ hasConfigurableAddOns = true;
124
+ console.log(`${optionMarker} ${chalk.bold(addOn.id)}: ${addOn.description}`);
125
+ }
126
+ if (hasConfigurableAddOns) {
127
+ console.log('\n* = has configuration options');
128
+ }
129
+ return;
130
+ }
131
+ // Handle --addon-details
132
+ if (options.addonDetails) {
133
+ const addOns = await getAllAddOns(framework, defaultMode);
134
+ const addOn = addOns.find((a) => a.id === options.addonDetails);
135
+ if (!addOn) {
136
+ console.error(`Add-on '${options.addonDetails}' not found`);
137
+ process.exit(1);
138
+ }
139
+ console.log(`${chalk.bold.cyan('Add-on Details:')} ${chalk.bold(addOn.name)}`);
140
+ console.log(`${chalk.bold('ID:')} ${addOn.id}`);
141
+ console.log(`${chalk.bold('Description:')} ${addOn.description}`);
142
+ console.log(`${chalk.bold('Type:')} ${addOn.type}`);
143
+ console.log(`${chalk.bold('Phase:')} ${addOn.phase}`);
144
+ console.log(`${chalk.bold('Supported Modes:')} ${addOn.modes.join(', ')}`);
145
+ if (addOn.dependsOn && addOn.dependsOn.length > 0) {
146
+ console.log(`${chalk.bold('Dependencies:')} ${addOn.dependsOn.join(', ')}`);
147
+ }
148
+ if (addOn.options && Object.keys(addOn.options).length > 0) {
149
+ console.log(`\n${chalk.bold.yellow('Configuration Options:')}`);
150
+ for (const [optionName, option] of Object.entries(addOn.options)) {
151
+ if (option && typeof option === 'object' && 'type' in option) {
152
+ const opt = option;
153
+ console.log(` ${chalk.bold(optionName)}:`);
154
+ console.log(` Label: ${opt.label}`);
155
+ if (opt.description) {
156
+ console.log(` Description: ${opt.description}`);
157
+ }
158
+ console.log(` Type: ${opt.type}`);
159
+ console.log(` Default: ${opt.default}`);
160
+ if (opt.type === 'select' && opt.options) {
161
+ console.log(` Available values:`);
162
+ for (const choice of opt.options) {
163
+ console.log(` - ${choice.value}: ${choice.label}`);
164
+ }
165
+ }
166
+ }
167
+ }
168
+ }
169
+ else {
170
+ console.log(`\n${chalk.gray('No configuration options available')}`);
171
+ }
172
+ if (addOn.routes && addOn.routes.length > 0) {
173
+ console.log(`\n${chalk.bold.green('Routes:')}`);
174
+ for (const route of addOn.routes) {
175
+ console.log(` ${chalk.bold(route.url)} (${route.name})`);
176
+ console.log(` File: ${route.path}`);
177
+ }
178
+ }
179
+ return;
180
+ }
181
+ // Handle project creation
182
+ if (!projectName) {
183
+ console.error('Project name is required');
184
+ program.help();
185
+ return;
186
+ }
187
+ let resolvedProjectName = projectName;
188
+ let targetDir;
189
+ // Handle "." as project name - use current directory
190
+ if (projectName === '.') {
191
+ resolvedProjectName = sanitizePackageName(getCurrentDirectoryName());
192
+ targetDir = resolve(process.cwd());
193
+ }
194
+ else {
195
+ targetDir = options.targetDir
196
+ ? resolve(options.targetDir)
197
+ : resolve(process.cwd(), projectName);
198
+ }
199
+ const { valid, error } = validateProjectName(resolvedProjectName);
200
+ if (!valid) {
201
+ console.error(error);
202
+ process.exit(1);
203
+ }
204
+ // Determine selected add-ons
205
+ const selectedAddOns = new Set([...forcedAddOns]);
206
+ if (options.addOns && Array.isArray(options.addOns)) {
207
+ for (const a of options.addOns) {
208
+ selectedAddOns.add(a);
209
+ }
210
+ }
211
+ const chosenAddOns = await finalizeAddOns(framework, defaultMode, Array.from(selectedAddOns));
212
+ const finalOptions = {
213
+ projectName: resolvedProjectName,
214
+ targetDir,
215
+ framework,
216
+ mode: defaultMode,
217
+ typescript: true,
218
+ tailwind: true,
219
+ packageManager: options.packageManager ||
220
+ getPackageManager() ||
221
+ DEFAULT_PACKAGE_MANAGER,
222
+ git: options.git !== false,
223
+ install: options.install !== false,
224
+ chosenAddOns,
225
+ addOnOptions: {
226
+ ...populateAddOnOptionsDefaults(chosenAddOns),
227
+ project: { bareBones: options.bareBones ?? false },
228
+ },
229
+ };
230
+ environment.intro(`Creating a new ${appName} app in ${resolvedProjectName}...`);
231
+ await createApp(environment, finalOptions);
232
+ // Delete files specified in bareBones.deleteFiles for each add-on when in bare-bones mode
233
+ if (options.bareBones) {
234
+ for (const addOn of chosenAddOns) {
235
+ const addOnWithBareBones = addOn;
236
+ if (addOnWithBareBones.bareBones?.deleteFiles) {
237
+ for (const file of addOnWithBareBones.bareBones.deleteFiles) {
238
+ const filePath = join(targetDir, file);
239
+ try {
240
+ await unlink(filePath);
241
+ }
242
+ catch {
243
+ // File may not exist, ignore errors
244
+ }
245
+ }
246
+ }
247
+ }
248
+ }
249
+ });
250
+ program.parse();
251
+ }
package/dist/index.js ADDED
@@ -0,0 +1,33 @@
1
+ #!/usr/bin/env node
2
+ import { dirname, join } from 'node:path';
3
+ import { fileURLToPath } from 'node:url';
4
+ import { registerFramework, scanAddOnDirectories, scanProjectDirectory, } from '@tanstack/cta-engine';
5
+ import { cli } from './cli.js';
6
+ const projectDirectory = join(dirname(dirname(fileURLToPath(import.meta.url))), 'project');
7
+ const addOns = scanAddOnDirectories([
8
+ join(dirname(dirname(fileURLToPath(import.meta.url))), 'add-ons'),
9
+ join(dirname(dirname(fileURLToPath(import.meta.url))), 'examples'),
10
+ ]);
11
+ const { files, basePackageJSON, optionalPackages } = scanProjectDirectory(projectDirectory, join(dirname(dirname(fileURLToPath(import.meta.url))), 'project', 'base'));
12
+ registerFramework({
13
+ id: 'netlify-start',
14
+ name: 'Netlify TanStack Start',
15
+ description: 'TanStack Start applications for Netlify deployment',
16
+ version: '0.1.0',
17
+ base: files,
18
+ addOns,
19
+ basePackageJSON,
20
+ optionalPackages,
21
+ supportedModes: {
22
+ 'file-router': {
23
+ displayName: 'File Router',
24
+ description: 'File-based routing with TanStack Start',
25
+ forceTypescript: true,
26
+ },
27
+ },
28
+ });
29
+ cli({
30
+ name: 'netlify-cta',
31
+ appName: 'Netlify TanStack Start',
32
+ defaultFramework: 'Netlify TanStack Start',
33
+ });
@@ -0,0 +1,8 @@
1
+ export interface CliConfig {
2
+ name: string;
3
+ appName: string;
4
+ defaultFramework: string;
5
+ forcedMode?: string;
6
+ forcedAddOns?: Array<string>;
7
+ }
8
+ export declare function cli({ name, appName, defaultFramework, forcedMode, forcedAddOns, }: CliConfig): void;
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ export {};
@@ -0,0 +1,14 @@
1
+ import type { PackageManager } from '@tanstack/cta-engine';
2
+ export interface CliOptions {
3
+ projectName?: string;
4
+ packageManager?: PackageManager;
5
+ git?: boolean;
6
+ addOns?: Array<string> | boolean;
7
+ listAddOns?: boolean;
8
+ listAddonsJson?: boolean;
9
+ addonDetails?: string;
10
+ targetDir?: string;
11
+ install?: boolean;
12
+ force?: boolean;
13
+ bareBones?: boolean;
14
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};