termi-kids 0.2.3 → 0.2.4

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 (2) hide show
  1. package/dist/surfaces/home.js +14 -164
  2. package/package.json +1 -1
@@ -12,9 +12,6 @@ import * as p from '@clack/prompts';
12
12
  import { atomicWriteFileSync, termiHome } from '../config/paths.js';
13
13
  import { saveSettings } from '../config/settings.js';
14
14
  import { consumeGuardReadyNotice, guardFetchState, guardProgressBar, } from '../safety/guarddownload.js';
15
- import { nameIsOkay } from '../safety/prefilter.js';
16
- import { scaffolds, scaffoldById } from '../projects/scaffolds/index.js';
17
- import { suggestProjectNames } from '../setup/wizard.js';
18
15
  import { BADGES, celebrate, confetti } from '../ui/celebrate.js';
19
16
  import { mascot } from '../ui/mascot.js';
20
17
  import { style } from '../ui/theme.js';
@@ -99,174 +96,27 @@ export function recapFromTermiMd(text) {
99
96
  }
100
97
  return null;
101
98
  }
102
- async function loadProjectModules() {
99
+ async function loadStore() {
103
100
  try {
104
- const store = await import('../projects/store.js');
105
- const create = await import('../projects/create.js');
106
- return { store, create };
101
+ return await import('../projects/store.js');
107
102
  }
108
103
  catch {
109
104
  return null;
110
105
  }
111
106
  }
112
- const CUSTOM_NAME = '__custom__';
113
- async function pickProjectName(themeLabel) {
114
- const suggestions = suggestProjectNames(themeLabel);
115
- const pick = await p.select({
116
- message: 'Pick a name.',
117
- options: [
118
- ...suggestions.map((name) => ({ value: name, label: name })),
119
- { value: CUSTOM_NAME, label: 'Type my own name' },
120
- ],
121
- });
122
- if (p.isCancel(pick)) {
123
- return null;
124
- }
125
- if (pick !== CUSTOM_NAME) {
126
- return pick;
127
- }
128
- const typed = await p.text({
129
- message: 'What is its name?',
130
- validate: (value) => {
131
- const trimmed = (value ?? '').trim();
132
- if (trimmed.length === 0)
133
- return 'It needs a name.';
134
- if (!nameIsOkay(trimmed))
135
- return 'That name will not work. Try another one.';
136
- return undefined;
137
- },
138
- });
139
- if (p.isCancel(typed)) {
140
- return null;
141
- }
142
- return typed.trim();
143
- }
144
- async function remixFlow(store, create) {
145
- const metas = store.listProjects();
146
- const sourceSlug = await p.select({
147
- message: 'Which project do you want to remix?',
148
- options: metas.map((m) => ({ value: m.slug, label: m.prettyName })),
149
- });
150
- if (p.isCancel(sourceSlug)) {
151
- return null;
152
- }
153
- const source = store.openProject(sourceSlug);
154
- if (source === null) {
155
- p.log.warn('I could not open that one.');
156
- return null;
157
- }
158
- for (;;) {
159
- const name = await p.text({
160
- message: 'Name your remix.',
161
- validate: (value) => ((value ?? '').trim().length > 0 ? undefined : 'It needs a name.'),
162
- });
163
- if (p.isCancel(name)) {
164
- return null;
165
- }
166
- const trimmed = name.trim();
167
- if (create.slugifyName(trimmed).collision) {
168
- p.log.warn('That name is taken. Try another one.');
169
- continue;
170
- }
171
- const made = create.createProject(source.meta.scaffoldId, source.meta.themeId, trimmed);
172
- for (const file of source.listKidFiles()) {
173
- const content = source.readFile(file.relPath);
174
- if (content !== null) {
175
- made.project.writeFile(file.relPath, content);
176
- }
177
- }
178
- await awardBadge('remixer');
179
- return made.project;
180
- }
181
- }
182
107
  /**
183
- * The full new-project flow: scaffold, theme, name (with collision help),
184
- * remix option, badges. Returns the created project, or null on cancel.
108
+ * New project entry: always Build a game (blank shell + idea list).
109
+ * Multi-scaffold stock starters are no longer on the kid path.
185
110
  */
186
111
  export async function runNewProject(settings) {
187
- void settings;
188
- const mods = await loadProjectModules();
189
- if (mods === null) {
190
- p.log.warn('Project tools are not ready yet.');
191
- return null;
192
- }
193
- const { store, create } = mods;
194
- const existing = store.listProjects();
195
- const options = scaffolds.map((s) => ({
196
- value: s.id,
197
- label: `${s.emoji} ${s.label}`,
198
- hint: s.ageNote,
199
- }));
200
- if (existing.length > 0) {
201
- options.push({
202
- value: '__remix__',
203
- label: 'Remix one of your projects',
204
- hint: 'Copy a project and make it new.',
205
- });
206
- }
207
- const pick = await p.select({ message: 'What do you want to make?', options });
208
- if (p.isCancel(pick)) {
209
- return null;
210
- }
211
- if (pick === '__remix__') {
212
- return remixFlow(store, create);
213
- }
214
- const scaffold = scaffoldById(pick);
215
- if (scaffold === undefined) {
216
- return null;
217
- }
218
- const themeId = await p.select({
219
- message: 'Pick a style.',
220
- options: scaffold.themes.map((t) => ({ value: t.id, label: `${t.emoji} ${t.label}` })),
221
- });
222
- if (p.isCancel(themeId)) {
223
- return null;
224
- }
225
- const theme = scaffold.themes.find((t) => t.id === themeId);
226
- if (theme === undefined) {
227
- return null;
112
+ try {
113
+ const build = await import('./buildGame.js');
114
+ return await build.runBuildGame(settings);
228
115
  }
229
- let prettyName = await pickProjectName(theme.label);
230
- if (prettyName === null) {
116
+ catch {
117
+ p.log.warn('Build a game is taking a nap. Try again soon.');
231
118
  return null;
232
119
  }
233
- const slugInfo = create.slugifyName(prettyName);
234
- if (slugInfo.collision) {
235
- const what = await p.select({
236
- message: 'You already have a project with that name.',
237
- options: [
238
- { value: 'open', label: 'Open it' },
239
- { value: 'suffix', label: `Call this one ${prettyName} 2` },
240
- { value: 'rename', label: 'Pick a new name' },
241
- ],
242
- });
243
- if (p.isCancel(what)) {
244
- return null;
245
- }
246
- if (what === 'open') {
247
- return store.openProject(slugInfo.slug);
248
- }
249
- if (what === 'suffix') {
250
- prettyName = `${prettyName} 2`;
251
- }
252
- else {
253
- const again = await pickProjectName(theme.label);
254
- if (again === null) {
255
- return null;
256
- }
257
- prettyName = again;
258
- }
259
- }
260
- const made = create.createProject(scaffold.id, theme.id, prettyName);
261
- await awardBadge('first-project');
262
- if (store.listProjects().length >= 5) {
263
- await awardBadge('five-projects');
264
- }
265
- const starters = made.starterPrompts.slice(0, 2);
266
- if (starters.length > 0) {
267
- p.note(starters.map((line) => `- ${line}`).join('\n'), 'Try saying');
268
- }
269
- return made.project;
270
120
  }
271
121
  /**
272
122
  * Opens the chat for a project and keeps going while the kid asks for
@@ -445,12 +295,12 @@ export async function showHome(settings) {
445
295
  }
446
296
  }
447
297
  async function openPicked(settings) {
448
- const mods = await loadProjectModules();
449
- if (mods === null) {
298
+ const store = await loadStore();
299
+ if (store === null) {
450
300
  p.log.warn('Project tools are not ready yet.');
451
301
  return;
452
302
  }
453
- const metas = mods.store.listProjects();
303
+ const metas = store.listProjects();
454
304
  if (metas.length === 0) {
455
305
  p.log.info('No games yet. Pick "Build a game"!');
456
306
  return;
@@ -460,14 +310,14 @@ async function openPicked(settings) {
460
310
  ? settings.lastProjectSlug
461
311
  : first?.slug;
462
312
  const pick = await p.select({
463
- message: 'Pick a project.',
313
+ message: 'Pick a game.',
464
314
  options: metas.map((m) => ({ value: m.slug, label: m.prettyName })),
465
315
  ...(initial !== undefined ? { initialValue: initial } : {}),
466
316
  });
467
317
  if (p.isCancel(pick)) {
468
318
  return;
469
319
  }
470
- const project = mods.store.openProject(pick);
320
+ const project = store.openProject(pick);
471
321
  if (project === null) {
472
322
  p.log.warn('I could not open that one.');
473
323
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "termi-kids",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Termi is a friendly coding buddy for kids. Build browser games with AI prompts, Learn AI lessons, and strong local safety rails.",
5
5
  "keywords": [
6
6
  "kids",