whale-igniter 1.3.4 → 1.3.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/commands/createComponent.js +4 -2
- package/dist/commands/createLanding.js +98 -62
- package/dist/commands/ignite.js +3 -1
- package/dist/commands/themes.js +20 -0
- package/dist/index.js +9 -0
- package/dist/themes/registry.js +49 -0
- package/dist/themes/types.js +1 -0
- package/dist/ui/splash.js +36 -0
- package/dist/version.js +1 -1
- package/package.json +2 -1
- package/themes/atlas.json +34 -0
- package/themes/aurora.json +34 -0
- package/themes/harbor.json +34 -0
|
@@ -32,6 +32,8 @@ const CATEGORY_TEMPLATES = {
|
|
|
32
32
|
*/
|
|
33
33
|
export async function createComponentCommand(name, options = {}) {
|
|
34
34
|
const target = resolveTarget();
|
|
35
|
+
const shouldRegister = options.register !== false && !options.noRegister;
|
|
36
|
+
const shouldSync = options.sync !== false && !options.noSync;
|
|
35
37
|
if (!name || !/^[A-Z][A-Za-z0-9]*$/.test(name)) {
|
|
36
38
|
console.log(ui.fail("Component name must be PascalCase (e.g. `Button`, `UserCard`)."));
|
|
37
39
|
process.exitCode = 1;
|
|
@@ -82,7 +84,7 @@ export async function createComponentCommand(name, options = {}) {
|
|
|
82
84
|
if (variants && variants.length > 0)
|
|
83
85
|
console.log(` ${ui.kv("variants", variants.join(", "), { keyWidth: 8 })}`);
|
|
84
86
|
// Auto-register in the catalog unless explicitly skipped.
|
|
85
|
-
if (
|
|
87
|
+
if (shouldRegister) {
|
|
86
88
|
await upsertComponent(target, {
|
|
87
89
|
name,
|
|
88
90
|
category,
|
|
@@ -92,7 +94,7 @@ export async function createComponentCommand(name, options = {}) {
|
|
|
92
94
|
});
|
|
93
95
|
console.log(ui.muted(` registered in intelligence/components.json`));
|
|
94
96
|
}
|
|
95
|
-
if (
|
|
97
|
+
if (shouldSync && shouldRegister) {
|
|
96
98
|
await generateWiki(target);
|
|
97
99
|
console.log(ui.muted(`${ui.glyph.check} AI context updated`));
|
|
98
100
|
}
|
|
@@ -5,12 +5,15 @@ import { loadConfig } from "../utils/config.js";
|
|
|
5
5
|
import { upsertComponent } from "../utils/components.js";
|
|
6
6
|
import { generateWiki } from "../generators/wikiGenerator.js";
|
|
7
7
|
import { ui } from "../ui/index.js";
|
|
8
|
+
import { loadUiTheme, DEFAULT_UI_THEME } from "../themes/registry.js";
|
|
8
9
|
const DEFAULT_SECTIONS = ["hero", "features", "proof", "contact"];
|
|
9
10
|
const VALID_SECTIONS = new Set(DEFAULT_SECTIONS);
|
|
10
11
|
export async function createLandingCommand(options = {}) {
|
|
11
12
|
const target = resolveTarget();
|
|
12
13
|
const config = await loadConfig(target);
|
|
13
14
|
const sections = parseSections(options.sections);
|
|
15
|
+
const shouldRegister = options.register !== false && !options.noRegister;
|
|
16
|
+
const shouldSync = options.sync !== false && !options.noSync;
|
|
14
17
|
if (sections.length === 0) {
|
|
15
18
|
console.log(ui.fail("No valid sections selected. Use hero,features,proof,contact."));
|
|
16
19
|
process.exitCode = 1;
|
|
@@ -35,24 +38,27 @@ export async function createLandingCommand(options = {}) {
|
|
|
35
38
|
console.log(ui.warn(`Project stack is ${ui.code(config.stack)}; generating portable HTML/CSS anyway.`));
|
|
36
39
|
}
|
|
37
40
|
await fs.ensureDir(path.dirname(htmlAbs));
|
|
38
|
-
const grid = config.foundations?.grid ?? 8;
|
|
39
|
-
const controlRadius = config.foundations?.radius?.control ?? 2;
|
|
40
|
-
const containerRadius = config.foundations?.radius?.container ?? 4;
|
|
41
41
|
const projectName = config.projectName ?? "Whale Landing";
|
|
42
|
+
// Resolve theme: CLI flag → config.theme → DEFAULT_UI_THEME
|
|
43
|
+
const themeName = options.theme ?? config.theme ?? DEFAULT_UI_THEME;
|
|
44
|
+
const theme = await loadUiTheme(themeName);
|
|
45
|
+
if (!theme) {
|
|
46
|
+
console.log(ui.fail(`Unknown theme '${themeName}'. Run \`whale themes list\`.`));
|
|
47
|
+
process.exitCode = 1;
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
42
50
|
await fs.writeFile(htmlAbs, renderHtml(projectName, sections), "utf8");
|
|
43
|
-
await fs.writeFile(cssAbs, renderCss({
|
|
44
|
-
grid,
|
|
45
|
-
controlRadius,
|
|
46
|
-
containerRadius,
|
|
51
|
+
await fs.writeFile(cssAbs, renderCss(theme, {
|
|
47
52
|
includeProof: sections.includes("proof"),
|
|
48
53
|
includeContact: sections.includes("contact")
|
|
49
54
|
}), "utf8");
|
|
50
55
|
console.log(ui.ok(`Created ${ui.path(htmlRel)}`));
|
|
51
56
|
console.log(ui.ok(`Created ${ui.path(cssRel)}`));
|
|
52
57
|
console.log(` ${ui.kv("sections", sections.join(", "), { keyWidth: 8 })}`);
|
|
53
|
-
console.log(` ${ui.kv("
|
|
54
|
-
console.log(` ${ui.kv("
|
|
55
|
-
|
|
58
|
+
console.log(` ${ui.kv("theme", theme.label, { keyWidth: 8 })}`);
|
|
59
|
+
console.log(` ${ui.kv("grid", `${theme.structural.grid}px`, { keyWidth: 8 })}`);
|
|
60
|
+
console.log(` ${ui.kv("radius", `${theme.structural.radiusControl}px controls, ${theme.structural.radiusContainer}px containers`, { keyWidth: 8 })}`);
|
|
61
|
+
if (shouldRegister) {
|
|
56
62
|
for (const section of sections) {
|
|
57
63
|
await upsertComponent(target, {
|
|
58
64
|
name: sectionName(section),
|
|
@@ -66,7 +72,7 @@ export async function createLandingCommand(options = {}) {
|
|
|
66
72
|
}
|
|
67
73
|
console.log(ui.muted(` registered ${sections.length} section(s) in intelligence/components.json`));
|
|
68
74
|
}
|
|
69
|
-
if (
|
|
75
|
+
if (shouldSync && shouldRegister) {
|
|
70
76
|
await generateWiki(target);
|
|
71
77
|
console.log(ui.muted(`${ui.glyph.check} AI context updated`));
|
|
72
78
|
}
|
|
@@ -214,17 +220,45 @@ function renderSection(section, title, sections) {
|
|
|
214
220
|
</section>`;
|
|
215
221
|
}
|
|
216
222
|
}
|
|
217
|
-
function renderCss(
|
|
218
|
-
const {
|
|
223
|
+
function renderCss(theme, opts) {
|
|
224
|
+
const { includeProof, includeContact } = opts;
|
|
225
|
+
const { identity, typography, structural, interaction } = theme;
|
|
226
|
+
const g = structural.grid;
|
|
227
|
+
const focusStyles = interaction.focus === "ring"
|
|
228
|
+
? `box-shadow: 0 0 0 3px var(--color-accent);
|
|
229
|
+
outline: none;`
|
|
230
|
+
: `outline: var(--border-width) solid var(--color-accent);
|
|
231
|
+
outline-offset: ${g / 2}px;`;
|
|
219
232
|
return `:root {
|
|
220
|
-
|
|
221
|
-
--color-bg:
|
|
222
|
-
--color-
|
|
223
|
-
--color-
|
|
224
|
-
--color-
|
|
225
|
-
--color-
|
|
226
|
-
--color-accent
|
|
227
|
-
--color-
|
|
233
|
+
/* Identity */
|
|
234
|
+
--color-bg: ${identity.bg};
|
|
235
|
+
--color-surface: ${identity.surface};
|
|
236
|
+
--color-text: ${identity.text};
|
|
237
|
+
--color-muted: ${identity.muted};
|
|
238
|
+
--color-border: ${identity.border};
|
|
239
|
+
--color-accent: ${identity.accent};
|
|
240
|
+
--color-accent-strong: ${identity.accentStrong};
|
|
241
|
+
--color-on-accent: ${identity.onAccent};
|
|
242
|
+
|
|
243
|
+
/* Typography */
|
|
244
|
+
--font-display: ${typography.display};
|
|
245
|
+
--font-body: ${typography.body};
|
|
246
|
+
--font-mono: ${typography.mono};
|
|
247
|
+
|
|
248
|
+
/* Structure */
|
|
249
|
+
--space-grid: ${structural.grid}px;
|
|
250
|
+
--radius-control: ${structural.radiusControl}px;
|
|
251
|
+
--radius-container: ${structural.radiusContainer}px;
|
|
252
|
+
--border-width: ${structural.borderWidth}px;
|
|
253
|
+
--shadow: ${structural.shadow};
|
|
254
|
+
|
|
255
|
+
/* Motion */
|
|
256
|
+
--motion-duration: ${interaction.duration}ms;
|
|
257
|
+
--motion-ease: ${interaction.easing};
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
@media (prefers-reduced-motion: reduce) {
|
|
261
|
+
* { transition: none !important; }
|
|
228
262
|
}
|
|
229
263
|
|
|
230
264
|
* {
|
|
@@ -233,7 +267,7 @@ function renderCss(args) {
|
|
|
233
267
|
|
|
234
268
|
body {
|
|
235
269
|
margin: 0;
|
|
236
|
-
font-family:
|
|
270
|
+
font-family: var(--font-body);
|
|
237
271
|
color: var(--color-text);
|
|
238
272
|
background: var(--color-bg);
|
|
239
273
|
}
|
|
@@ -243,17 +277,17 @@ a {
|
|
|
243
277
|
}
|
|
244
278
|
|
|
245
279
|
.section {
|
|
246
|
-
padding:
|
|
280
|
+
padding: calc(var(--space-grid) * 8) calc(var(--space-grid) * 3);
|
|
247
281
|
}
|
|
248
282
|
|
|
249
283
|
.section__inner {
|
|
250
284
|
width: 100%;
|
|
251
|
-
max-width:
|
|
285
|
+
max-width: calc(var(--space-grid) * 140);
|
|
252
286
|
margin: 0 auto;
|
|
253
287
|
}
|
|
254
288
|
|
|
255
289
|
.hero {
|
|
256
|
-
min-height:
|
|
290
|
+
min-height: calc(var(--space-grid) * 80);
|
|
257
291
|
display: grid;
|
|
258
292
|
align-items: center;
|
|
259
293
|
}
|
|
@@ -262,13 +296,13 @@ a {
|
|
|
262
296
|
.contact__grid,
|
|
263
297
|
.proof__grid {
|
|
264
298
|
display: grid;
|
|
265
|
-
grid-template-columns: minmax(0, 1.2fr) minmax(
|
|
266
|
-
gap:
|
|
299
|
+
grid-template-columns: minmax(0, 1.2fr) minmax(calc(var(--space-grid) * 36), 0.8fr);
|
|
300
|
+
gap: calc(var(--space-grid) * 6);
|
|
267
301
|
align-items: center;
|
|
268
302
|
}
|
|
269
303
|
|
|
270
304
|
.eyebrow {
|
|
271
|
-
margin: 0 0
|
|
305
|
+
margin: 0 0 calc(var(--space-grid) * 2);
|
|
272
306
|
color: var(--color-accent-strong);
|
|
273
307
|
font-size: 0.875rem;
|
|
274
308
|
font-weight: 700;
|
|
@@ -283,27 +317,29 @@ p {
|
|
|
283
317
|
}
|
|
284
318
|
|
|
285
319
|
h1 {
|
|
286
|
-
max-width:
|
|
287
|
-
margin-bottom:
|
|
320
|
+
max-width: calc(var(--space-grid) * 88);
|
|
321
|
+
margin-bottom: calc(var(--space-grid) * 3);
|
|
322
|
+
font-family: var(--font-display);
|
|
288
323
|
font-size: clamp(2.5rem, 6vw, 4.5rem);
|
|
289
324
|
line-height: 1;
|
|
290
325
|
}
|
|
291
326
|
|
|
292
327
|
h2 {
|
|
293
|
-
max-width:
|
|
294
|
-
margin-bottom:
|
|
328
|
+
max-width: calc(var(--space-grid) * 80);
|
|
329
|
+
margin-bottom: calc(var(--space-grid) * 4);
|
|
330
|
+
font-family: var(--font-display);
|
|
295
331
|
font-size: clamp(2rem, 4vw, 3rem);
|
|
296
332
|
line-height: 1.1;
|
|
297
333
|
}
|
|
298
334
|
|
|
299
335
|
h3 {
|
|
300
|
-
margin-bottom:
|
|
336
|
+
margin-bottom: var(--space-grid);
|
|
301
337
|
font-size: 1.125rem;
|
|
302
338
|
}
|
|
303
339
|
|
|
304
340
|
.lede {
|
|
305
|
-
max-width:
|
|
306
|
-
margin-bottom:
|
|
341
|
+
max-width: calc(var(--space-grid) * 72);
|
|
342
|
+
margin-bottom: calc(var(--space-grid) * 4);
|
|
307
343
|
color: var(--color-muted);
|
|
308
344
|
font-size: 1.125rem;
|
|
309
345
|
line-height: 1.6;
|
|
@@ -311,18 +347,19 @@ h3 {
|
|
|
311
347
|
|
|
312
348
|
.button {
|
|
313
349
|
display: inline-flex;
|
|
314
|
-
min-height:
|
|
350
|
+
min-height: calc(var(--space-grid) * 6);
|
|
315
351
|
align-items: center;
|
|
316
352
|
justify-content: center;
|
|
317
|
-
padding:
|
|
353
|
+
padding: var(--space-grid) calc(var(--space-grid) * 3);
|
|
318
354
|
border: 0;
|
|
319
|
-
border-radius:
|
|
355
|
+
border-radius: var(--radius-control);
|
|
320
356
|
background: var(--color-accent);
|
|
321
|
-
color: var(--color-
|
|
357
|
+
color: var(--color-on-accent);
|
|
322
358
|
font: inherit;
|
|
323
359
|
font-weight: 700;
|
|
324
360
|
text-decoration: none;
|
|
325
361
|
cursor: pointer;
|
|
362
|
+
transition: background var(--motion-duration) var(--motion-ease);
|
|
326
363
|
}
|
|
327
364
|
|
|
328
365
|
.button:hover {
|
|
@@ -332,24 +369,23 @@ h3 {
|
|
|
332
369
|
.button:focus-visible,
|
|
333
370
|
input:focus-visible,
|
|
334
371
|
textarea:focus-visible {
|
|
335
|
-
|
|
336
|
-
outline-offset: ${grid / 2}px;
|
|
372
|
+
${focusStyles}
|
|
337
373
|
}
|
|
338
374
|
|
|
339
375
|
.hero__panel,
|
|
340
376
|
.card,
|
|
341
377
|
blockquote,
|
|
342
378
|
.contact-form {
|
|
343
|
-
border:
|
|
344
|
-
border-radius:
|
|
379
|
+
border: var(--border-width) solid var(--color-border);
|
|
380
|
+
border-radius: var(--radius-container);
|
|
345
381
|
background: var(--color-surface);
|
|
346
|
-
box-shadow:
|
|
382
|
+
box-shadow: var(--shadow);
|
|
347
383
|
}
|
|
348
384
|
|
|
349
385
|
.hero__panel {
|
|
350
386
|
display: grid;
|
|
351
|
-
gap:
|
|
352
|
-
padding:
|
|
387
|
+
gap: calc(var(--space-grid) * 2);
|
|
388
|
+
padding: calc(var(--space-grid) * 4);
|
|
353
389
|
}
|
|
354
390
|
|
|
355
391
|
.hero__panel strong {
|
|
@@ -359,15 +395,15 @@ blockquote,
|
|
|
359
395
|
.feature-grid {
|
|
360
396
|
display: grid;
|
|
361
397
|
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
362
|
-
gap:
|
|
398
|
+
gap: calc(var(--space-grid) * 3);
|
|
363
399
|
}
|
|
364
400
|
|
|
365
401
|
.card {
|
|
366
|
-
padding:
|
|
402
|
+
padding: calc(var(--space-grid) * 3);
|
|
367
403
|
}
|
|
368
|
-
${includeProof ? proofCss(
|
|
369
|
-
${includeContact ? contactCss(
|
|
370
|
-
@media (max-width:
|
|
404
|
+
${includeProof ? proofCss() : ""}
|
|
405
|
+
${includeContact ? contactCss() : ""}
|
|
406
|
+
@media (max-width: calc(var(--space-grid) * 96)) {
|
|
371
407
|
.hero__grid,
|
|
372
408
|
.contact__grid,
|
|
373
409
|
.proof__grid,
|
|
@@ -376,12 +412,12 @@ ${includeContact ? contactCss(grid) : ""}
|
|
|
376
412
|
}
|
|
377
413
|
|
|
378
414
|
.section {
|
|
379
|
-
padding:
|
|
415
|
+
padding: calc(var(--space-grid) * 6) calc(var(--space-grid) * 2);
|
|
380
416
|
}
|
|
381
417
|
}
|
|
382
418
|
`;
|
|
383
419
|
}
|
|
384
|
-
function proofCss(
|
|
420
|
+
function proofCss() {
|
|
385
421
|
return `
|
|
386
422
|
.proof {
|
|
387
423
|
background: var(--color-surface);
|
|
@@ -389,11 +425,11 @@ function proofCss(grid) {
|
|
|
389
425
|
|
|
390
426
|
blockquote {
|
|
391
427
|
margin: 0;
|
|
392
|
-
padding:
|
|
428
|
+
padding: calc(var(--space-grid) * 3);
|
|
393
429
|
}
|
|
394
430
|
|
|
395
431
|
blockquote p {
|
|
396
|
-
margin-bottom:
|
|
432
|
+
margin-bottom: calc(var(--space-grid) * 2);
|
|
397
433
|
font-size: 1.25rem;
|
|
398
434
|
line-height: 1.5;
|
|
399
435
|
}
|
|
@@ -404,26 +440,26 @@ cite {
|
|
|
404
440
|
}
|
|
405
441
|
`;
|
|
406
442
|
}
|
|
407
|
-
function contactCss(
|
|
443
|
+
function contactCss() {
|
|
408
444
|
return `
|
|
409
445
|
.contact-form {
|
|
410
446
|
display: grid;
|
|
411
|
-
gap:
|
|
412
|
-
padding:
|
|
447
|
+
gap: calc(var(--space-grid) * 2);
|
|
448
|
+
padding: calc(var(--space-grid) * 3);
|
|
413
449
|
}
|
|
414
450
|
|
|
415
451
|
label {
|
|
416
452
|
display: grid;
|
|
417
|
-
gap:
|
|
453
|
+
gap: var(--space-grid);
|
|
418
454
|
font-weight: 700;
|
|
419
455
|
}
|
|
420
456
|
|
|
421
457
|
input,
|
|
422
458
|
textarea {
|
|
423
459
|
width: 100%;
|
|
424
|
-
padding:
|
|
425
|
-
border:
|
|
426
|
-
border-radius:
|
|
460
|
+
padding: var(--space-grid) calc(var(--space-grid) * 2);
|
|
461
|
+
border: var(--border-width) solid var(--color-border);
|
|
462
|
+
border-radius: var(--radius-control);
|
|
427
463
|
font: inherit;
|
|
428
464
|
}
|
|
429
465
|
`;
|
package/dist/commands/ignite.js
CHANGED
|
@@ -10,6 +10,7 @@ import { getAiAvailability } from "../utils/aiAvailability.js";
|
|
|
10
10
|
import { mapWizardAnswers, suggestUiCategories } from "../utils/wizardMapping.js";
|
|
11
11
|
import { ui } from "../ui/index.js";
|
|
12
12
|
import { PACKAGE_VERSION } from "../version.js";
|
|
13
|
+
import { splash } from "../ui/splash.js";
|
|
13
14
|
const OPINIONATED_PACKS = ["lighthouse", "forge", "scribe"];
|
|
14
15
|
const MINIMAL_PACKS = ["lighthouse"];
|
|
15
16
|
export async function igniteCommand(projectName = "whale-project", options = {}) {
|
|
@@ -19,7 +20,8 @@ export async function igniteCommand(projectName = "whale-project", options = {})
|
|
|
19
20
|
? "minimal"
|
|
20
21
|
: "opinionated";
|
|
21
22
|
console.log();
|
|
22
|
-
console.log(
|
|
23
|
+
console.log(splash());
|
|
24
|
+
console.log(ui.muted(`ignite • ${mode}`));
|
|
23
25
|
console.log();
|
|
24
26
|
// ---- Resolve config based on mode -----------------------------------------
|
|
25
27
|
let config;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ui } from "../ui/index.js";
|
|
2
|
+
import { listUiThemes } from "../themes/registry.js";
|
|
3
|
+
export async function themesListCommand() {
|
|
4
|
+
const themes = await listUiThemes();
|
|
5
|
+
if (themes.length === 0) {
|
|
6
|
+
console.log(ui.warn("No UI themes found."));
|
|
7
|
+
return;
|
|
8
|
+
}
|
|
9
|
+
for (const theme of themes) {
|
|
10
|
+
console.log();
|
|
11
|
+
console.log(ui.section(theme.label));
|
|
12
|
+
console.log(ui.muted(theme.description));
|
|
13
|
+
console.log(` ${ui.kv("accent", theme.identity.accent, { keyWidth: 10 })}`);
|
|
14
|
+
console.log(` ${ui.kv("radius", `${theme.structural.radiusControl}px / ${theme.structural.radiusContainer}px`, { keyWidth: 10 })}`);
|
|
15
|
+
console.log(` ${ui.kv("mode", theme.identity.mode, { keyWidth: 10 })}`);
|
|
16
|
+
console.log(` ${ui.kv("slug", theme.name, { keyWidth: 10 })}`);
|
|
17
|
+
}
|
|
18
|
+
console.log();
|
|
19
|
+
console.log(ui.muted(`Use --theme <slug> with \`whale create landing\` to apply a theme.`));
|
|
20
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -15,6 +15,7 @@ import { adoptReviewCommand, adoptStatusCommand } from "./commands/adoptReview.j
|
|
|
15
15
|
import { insightsCommand, insightsDriftReviewCommand } from "./commands/insights.js";
|
|
16
16
|
import { createComponentCommand } from "./commands/createComponent.js";
|
|
17
17
|
import { createLandingCommand } from "./commands/createLanding.js";
|
|
18
|
+
import { themesListCommand } from "./commands/themes.js";
|
|
18
19
|
import { updateTokensCommand } from "./commands/updateTokens.js";
|
|
19
20
|
import { seleneDescribeCommand, seleneAuditCommand, seleneSuggestCommand, seleneApplyCommand, seleneStatusCommand, seleneCacheClearCommand } from "./commands/selene.js";
|
|
20
21
|
import { mcpServeCommand, mcpConfigCommand } from "./commands/mcp.js";
|
|
@@ -182,6 +183,7 @@ create
|
|
|
182
183
|
.option("--force", "Overwrite generated files if they already exist")
|
|
183
184
|
.option("--no-register", "Don't add generated sections to intelligence/components.json")
|
|
184
185
|
.option("--no-sync", "Don't regenerate CLAUDE.md / wiki after creating")
|
|
186
|
+
.option("--theme <name>", "Visual theme for the generated UI (see `whale themes list`)")
|
|
185
187
|
.action((opts) => createLandingCommand(opts));
|
|
186
188
|
const adopt = program
|
|
187
189
|
.command("adopt [target]")
|
|
@@ -294,4 +296,11 @@ program
|
|
|
294
296
|
.description("Show what changed in intelligence stores since a git ref.")
|
|
295
297
|
.option("--since <ref>", "Git ref to compare against (default: HEAD~1)", "HEAD~1")
|
|
296
298
|
.action((target, opts) => changesCommand({ since: opts.since, target }));
|
|
299
|
+
const themes = program
|
|
300
|
+
.command("themes")
|
|
301
|
+
.description("Browse local UI themes for generated output.");
|
|
302
|
+
themes
|
|
303
|
+
.command("list")
|
|
304
|
+
.description("List available UI themes.")
|
|
305
|
+
.action(() => themesListCommand());
|
|
297
306
|
program.parse();
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "fs-extra";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
5
|
+
const __dirname = path.dirname(__filename);
|
|
6
|
+
// Resolve themes/ relative to compiled output (dist/themes/ → ../../themes)
|
|
7
|
+
const THEMES_DIR = path.resolve(__dirname, "../../themes");
|
|
8
|
+
export const DEFAULT_UI_THEME = "atlas";
|
|
9
|
+
function isValidUiTheme(obj) {
|
|
10
|
+
if (!obj || typeof obj !== "object")
|
|
11
|
+
return false;
|
|
12
|
+
const t = obj;
|
|
13
|
+
return (typeof t.name === "string" &&
|
|
14
|
+
typeof t.label === "string" &&
|
|
15
|
+
typeof t.description === "string" &&
|
|
16
|
+
typeof t.identity === "object" && t.identity !== null &&
|
|
17
|
+
typeof t.typography === "object" && t.typography !== null &&
|
|
18
|
+
typeof t.structural === "object" && t.structural !== null &&
|
|
19
|
+
typeof t.interaction === "object" && t.interaction !== null);
|
|
20
|
+
}
|
|
21
|
+
export async function listUiThemes() {
|
|
22
|
+
const files = await fs.readdir(THEMES_DIR).catch(() => []);
|
|
23
|
+
const themes = [];
|
|
24
|
+
for (const file of files) {
|
|
25
|
+
if (!file.endsWith(".json"))
|
|
26
|
+
continue;
|
|
27
|
+
try {
|
|
28
|
+
const raw = await fs.readJson(path.join(THEMES_DIR, file));
|
|
29
|
+
if (isValidUiTheme(raw))
|
|
30
|
+
themes.push(raw);
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// skip malformed packs
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
return themes.sort((a, b) => a.label.localeCompare(b.label));
|
|
37
|
+
}
|
|
38
|
+
export async function loadUiTheme(name) {
|
|
39
|
+
const filePath = path.join(THEMES_DIR, `${name}.json`);
|
|
40
|
+
if (!(await fs.pathExists(filePath)))
|
|
41
|
+
return null;
|
|
42
|
+
try {
|
|
43
|
+
const raw = await fs.readJson(filePath);
|
|
44
|
+
return isValidUiTheme(raw) ? raw : null;
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return null;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* splash.ts — capability-gated welcome banner for `whale ignite`.
|
|
3
|
+
*
|
|
4
|
+
* Three tiers:
|
|
5
|
+
* 1. Full (color + unicode) — wordmark with accent color + glyphs
|
|
6
|
+
* 2. Unicode, no color — same layout, no color
|
|
7
|
+
* 3. Plain — single ASCII line, no glyphs
|
|
8
|
+
*
|
|
9
|
+
* Rules: no chalk imports, no direct env var reads, no "premium".
|
|
10
|
+
* Uses `capabilities()` from capabilities.js and `ui` atoms.
|
|
11
|
+
*/
|
|
12
|
+
import { capabilities } from "./capabilities.js";
|
|
13
|
+
import { accent, muted, emphasis } from "./atoms.js";
|
|
14
|
+
import { PACKAGE_VERSION } from "../version.js";
|
|
15
|
+
const TAGLINE = "map the project before the agent moves";
|
|
16
|
+
export function splash() {
|
|
17
|
+
const { color, unicode } = capabilities();
|
|
18
|
+
const isPlain = !unicode;
|
|
19
|
+
// Tier 3 — plain ASCII, single line
|
|
20
|
+
if (isPlain) {
|
|
21
|
+
return `WHALE IGNITER v${PACKAGE_VERSION} — ${TAGLINE}`;
|
|
22
|
+
}
|
|
23
|
+
// Tier 1 & 2 — unicode layout (color applied via atom functions when available)
|
|
24
|
+
const whale = "◆ ~";
|
|
25
|
+
const wordmarkWhale = color ? accent("WHALE") : "WHALE";
|
|
26
|
+
const wordmarkIgniter = color ? emphasis("IGNITER") : "IGNITER";
|
|
27
|
+
const version = color ? muted(`v${PACKAGE_VERSION}`) : `v${PACKAGE_VERSION}`;
|
|
28
|
+
const tag = color ? muted(TAGLINE) : TAGLINE;
|
|
29
|
+
const rule = "─".repeat(36);
|
|
30
|
+
return [
|
|
31
|
+
`${whale}`,
|
|
32
|
+
`${wordmarkWhale} ${wordmarkIgniter} ${version}`,
|
|
33
|
+
tag,
|
|
34
|
+
rule
|
|
35
|
+
].join("\n");
|
|
36
|
+
}
|
package/dist/version.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export const PACKAGE_VERSION = "1.3.
|
|
1
|
+
export const PACKAGE_VERSION = "1.3.6";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "whale-igniter",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.6",
|
|
4
4
|
"description": "CLI-first operational intelligence. Bootstraps and adopts AI-readable project context so agents like Claude Code, Codex and Cursor understand your design system from the first commit. Includes an MCP server, a file watcher, deterministic insights, and an opt-in AI bridge (Selene).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"dist",
|
|
12
12
|
"brand",
|
|
13
13
|
"packs",
|
|
14
|
+
"themes",
|
|
14
15
|
"ui-references",
|
|
15
16
|
"README.md",
|
|
16
17
|
"LICENSE",
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "atlas",
|
|
3
|
+
"label": "Atlas",
|
|
4
|
+
"description": "Enterprise minimal: dense, neutral, hairline borders.",
|
|
5
|
+
"identity": {
|
|
6
|
+
"mode": "light",
|
|
7
|
+
"bg": "#FFFFFF",
|
|
8
|
+
"surface": "#FFFFFF",
|
|
9
|
+
"text": "#0F172A",
|
|
10
|
+
"muted": "#475569",
|
|
11
|
+
"border": "#E2E8F0",
|
|
12
|
+
"accent": "#334155",
|
|
13
|
+
"accentStrong": "#0F172A",
|
|
14
|
+
"onAccent": "#FFFFFF"
|
|
15
|
+
},
|
|
16
|
+
"typography": {
|
|
17
|
+
"display": "Inter, ui-sans-serif, system-ui, -apple-system, sans-serif",
|
|
18
|
+
"body": "Inter, ui-sans-serif, system-ui, -apple-system, sans-serif",
|
|
19
|
+
"mono": "ui-monospace, SFMono-Regular, monospace",
|
|
20
|
+
"scaleRatio": 1.2
|
|
21
|
+
},
|
|
22
|
+
"structural": {
|
|
23
|
+
"grid": 8,
|
|
24
|
+
"radiusControl": 2,
|
|
25
|
+
"radiusContainer": 4,
|
|
26
|
+
"borderWidth": 1,
|
|
27
|
+
"shadow": "none"
|
|
28
|
+
},
|
|
29
|
+
"interaction": {
|
|
30
|
+
"duration": 120,
|
|
31
|
+
"easing": "cubic-bezier(0.4, 0, 0.2, 1)",
|
|
32
|
+
"focus": "ring"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "aurora",
|
|
3
|
+
"label": "Aurora",
|
|
4
|
+
"description": "Modern SaaS: soft, airy, friendly, generous whitespace.",
|
|
5
|
+
"identity": {
|
|
6
|
+
"mode": "light",
|
|
7
|
+
"bg": "#FBFAFF",
|
|
8
|
+
"surface": "#FFFFFF",
|
|
9
|
+
"text": "#26215C",
|
|
10
|
+
"muted": "#534AB7",
|
|
11
|
+
"border": "#E4DFFA",
|
|
12
|
+
"accent": "#6D5AE6",
|
|
13
|
+
"accentStrong": "#534AB7",
|
|
14
|
+
"onAccent": "#FFFFFF"
|
|
15
|
+
},
|
|
16
|
+
"typography": {
|
|
17
|
+
"display": "ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif",
|
|
18
|
+
"body": "ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif",
|
|
19
|
+
"mono": "ui-monospace, SFMono-Regular, monospace",
|
|
20
|
+
"scaleRatio": 1.25
|
|
21
|
+
},
|
|
22
|
+
"structural": {
|
|
23
|
+
"grid": 8,
|
|
24
|
+
"radiusControl": 6,
|
|
25
|
+
"radiusContainer": 8,
|
|
26
|
+
"borderWidth": 1,
|
|
27
|
+
"shadow": "0 8px 24px rgba(109, 90, 230, 0.12)"
|
|
28
|
+
},
|
|
29
|
+
"interaction": {
|
|
30
|
+
"duration": 200,
|
|
31
|
+
"easing": "cubic-bezier(0.16, 1, 0.3, 1)",
|
|
32
|
+
"focus": "ring"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "harbor",
|
|
3
|
+
"label": "Harbor",
|
|
4
|
+
"description": "The Whale brand: confident, editorial, royal blue on paper.",
|
|
5
|
+
"identity": {
|
|
6
|
+
"mode": "light",
|
|
7
|
+
"bg": "#F2ECE1",
|
|
8
|
+
"surface": "#FFFFFF",
|
|
9
|
+
"text": "#003087",
|
|
10
|
+
"muted": "#3A4D7A",
|
|
11
|
+
"border": "#C9D6F2",
|
|
12
|
+
"accent": "#0034D3",
|
|
13
|
+
"accentStrong": "#003087",
|
|
14
|
+
"onAccent": "#F2ECE1"
|
|
15
|
+
},
|
|
16
|
+
"typography": {
|
|
17
|
+
"display": "\"Special Elite\", ui-monospace, SFMono-Regular, monospace",
|
|
18
|
+
"body": "ui-sans-serif, system-ui, -apple-system, \"Segoe UI\", sans-serif",
|
|
19
|
+
"mono": "\"JetBrains Mono\", ui-monospace, monospace",
|
|
20
|
+
"scaleRatio": 1.25
|
|
21
|
+
},
|
|
22
|
+
"structural": {
|
|
23
|
+
"grid": 8,
|
|
24
|
+
"radiusControl": 2,
|
|
25
|
+
"radiusContainer": 4,
|
|
26
|
+
"borderWidth": 1,
|
|
27
|
+
"shadow": "0 8px 24px rgba(0, 48, 135, 0.10)"
|
|
28
|
+
},
|
|
29
|
+
"interaction": {
|
|
30
|
+
"duration": 160,
|
|
31
|
+
"easing": "cubic-bezier(0.2, 0, 0, 1)",
|
|
32
|
+
"focus": "outline"
|
|
33
|
+
}
|
|
34
|
+
}
|