@ryuxdsgn/design-intelligence 2.8.0 → 2.8.1

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/cli/index.js +49 -39
  2. package/package.json +1 -1
package/dist/cli/index.js CHANGED
@@ -133,9 +133,10 @@ async function runInstall(flags) {
133
133
  return;
134
134
  }
135
135
  p.intro(pc.bgCyan(pc.black(" RYUX ")));
136
+ p.log.message(KEY_LEGEND);
136
137
  const detected = agentsAt(scope, false);
137
138
  const a = await p.multiselect({
138
- message: `Which agents do you use?${keys("↑/↓ move", "Space select", "a all", "Enter continue", "Ctrl+C cancel")}`,
139
+ message: "Which agents do you use? Pick any (a selects all).",
139
140
  options: [
140
141
  ...AGENT_TARGETS.map((x) => ({ value: x.id, label: x.label, hint: scope.global ? `~/${x.globalDir}` : x.dir })),
141
142
  { value: AGENTS_MD_INLINE.id, label: AGENTS_MD_INLINE.label, hint: "AGENTS.md" },
@@ -145,11 +146,8 @@ async function runInstall(flags) {
145
146
  });
146
147
  if (p.isCancel(a))
147
148
  return cancel();
148
- const m = await p.confirm({ message: `Show how to connect the ryux MCP?${keys(YES_NO, "Enter continue")}`, initialValue: false });
149
- if (p.isCancel(m))
150
- return cancel();
151
149
  agents = a;
152
- showMcp = Boolean(m);
150
+ showMcp = false;
153
151
  }
154
152
  if (scope.global && agents.includes(AGENTS_MD_INLINE.id)) {
155
153
  console.log(pc.yellow("agents-md is project-only; skipped for --global."));
@@ -170,7 +168,7 @@ async function runInstall(flags) {
170
168
  if (nonInteractive)
171
169
  return;
172
170
  if (!scope.global) {
173
- const s = await p.confirm({ message: `Set up project context now? Every question can be skipped.${keys(YES_NO, "Enter continue")}`, initialValue: true });
171
+ const s = await p.confirm({ message: "Set up project context now? Every question can be skipped.", initialValue: true });
174
172
  if (p.isCancel(s))
175
173
  return cancel();
176
174
  if (s && !(await setupInteractive(scope.root)))
@@ -216,7 +214,7 @@ async function runRemove(flags) {
216
214
  return;
217
215
  }
218
216
  if (!flags.yes) {
219
- const ok = await p.confirm({ message: `Remove RYUX from ${scope.global ? "your home directory" : "this project"}?${keys(YES_NO, "Enter confirm")}` });
217
+ const ok = await p.confirm({ message: `Remove RYUX from ${scope.global ? "your home directory" : "this project"}?` });
220
218
  if (p.isCancel(ok) || !ok) {
221
219
  console.log("Cancelled.");
222
220
  return;
@@ -313,11 +311,11 @@ function detectContext(root) {
313
311
  ];
314
312
  return { name, system: candidates.filter((c) => existsSync(join(root, c))) };
315
313
  }
316
- /** A choice whose value is empty means "not decided": the field stays unknown. */
314
+ /** Every question also offers "Write my own" and "Skip, leave unknown"; a skipped field stays unknown. */
317
315
  const CONTEXT_FIELDS = [
318
316
  {
319
317
  label: "Product", hint: "(what it does, in one sentence:)",
320
- ask: { flag: "product", group: "Project", question: "What kind of product is it?", options: [
318
+ ask: { flag: "product", group: "Project", question: "What kind of product is it?", example: "Ticketing for local events", options: [
321
319
  { value: "Online store", label: "Online store" },
322
320
  { value: "Payments or finance", label: "Payments or finance" },
323
321
  { value: "Marketplace", label: "Marketplace" },
@@ -329,18 +327,17 @@ const CONTEXT_FIELDS = [
329
327
  },
330
328
  {
331
329
  label: "Current work", hint: "",
332
- ask: { flag: "work", group: "Project", question: "What are you building right now?", options: [
330
+ ask: { flag: "work", group: "Project", question: "What are you building right now?", example: "The checkout flow", options: [
333
331
  { value: "A new product", label: "A new product" },
334
332
  { value: "A new feature", label: "A new feature" },
335
333
  { value: "A new screen", label: "A new screen" },
336
334
  { value: "An existing flow", label: "An existing flow", hint: "changing something that already exists" },
337
335
  { value: "A redesign", label: "A redesign" },
338
- { value: "", label: "Not decided yet", hint: "stays unknown" },
339
336
  ] },
340
337
  },
341
338
  {
342
339
  label: "Audience", hint: "",
343
- ask: { flag: "audience", group: "Project", question: "Who is it for?", options: [
340
+ ask: { flag: "audience", group: "Project", question: "Who is it for?", example: "Freelancers who send invoices", options: [
344
341
  { value: "Consumers", label: "Consumers", hint: "people using it for themselves" },
345
342
  { value: "Small business owners", label: "Small business owners" },
346
343
  { value: "Professionals at work", label: "Professionals at work" },
@@ -350,11 +347,10 @@ const CONTEXT_FIELDS = [
350
347
  },
351
348
  {
352
349
  label: "Market and locale", hint: "(for example Indonesia, id-ID, Rupiah; or global, en-US, USD)",
353
- ask: { flag: "market", group: "Project", question: "Where is it used? This sets language and currency.", options: [
350
+ ask: { flag: "market", group: "Project", question: "Where is it used? This sets language and currency.", example: "Malaysia (ms-MY, MYR)", options: [
354
351
  { value: "Global, several markets", label: "Global, several markets" },
355
352
  { value: "Indonesia (id-ID, IDR)", label: "Indonesia", hint: "id-ID, Rupiah" },
356
353
  { value: "United States (en-US, USD)", label: "United States", hint: "en-US, US dollar" },
357
- { value: "", label: "Not decided yet", hint: "stays unknown" },
358
354
  ] },
359
355
  },
360
356
  { label: "Brand and design system", hint: "" },
@@ -362,19 +358,20 @@ const CONTEXT_FIELDS = [
362
358
  { label: "Evidence sources", hint: "RYUX MCP (not available yet), Figma files, reference URLs:" },
363
359
  {
364
360
  label: "Constraints", hint: "platforms, accessibility target (for example WCAG 2.2 AA), what must not change:",
365
- ask: { flag: "constraints", group: "Project", question: "What must RYUX keep or support?", multi: true, options: [
361
+ ask: { flag: "constraints", group: "Project", question: "Where does it run?", example: "Tablet app for shop staff", options: [
366
362
  { value: "Mobile app", label: "Mobile app" },
367
- { value: "Web", label: "Web" },
368
- { value: "Responsive web", label: "Responsive web", hint: "one site for phone and desktop" },
363
+ { value: "Responsive website", label: "Website", hint: "phone and desktop" },
364
+ { value: "Desktop website", label: "Desktop website" },
369
365
  { value: "Web and mobile app", label: "Web and mobile app" },
370
- { value: "Existing design system", label: "Existing design system" },
371
- { value: "Existing brand identity", label: "Existing brand identity" },
372
- { value: "Accessibility, WCAG 2.2 AA", label: "Accessibility", hint: "WCAG 2.2 AA" },
373
- ] },
366
+ ], then: { question: "What must stay as it is? Pick any.", example: "Our current logo and colours", multi: true, options: [
367
+ { value: "Existing design system", label: "Existing design system" },
368
+ { value: "Existing brand identity", label: "Existing brand identity" },
369
+ { value: "Accessibility (WCAG 2.2 AA)", label: "Accessibility", hint: "WCAG 2.2 AA" },
370
+ ] } },
374
371
  },
375
372
  {
376
373
  label: "Design intent", hint: "(what users should understand, feel, and do)",
377
- ask: { flag: "intent", group: "Design", question: "What should people get from it?", options: [
374
+ ask: { flag: "intent", group: "Design", question: "What should people get from it?", example: "Know when their order arrives", options: [
378
375
  { value: "Understand their status at a glance", label: "Understand their status at a glance" },
379
376
  { value: "Finish a task quickly", label: "Finish a task quickly" },
380
377
  { value: "Feel safe and in control", label: "Feel safe and in control" },
@@ -383,7 +380,7 @@ const CONTEXT_FIELDS = [
383
380
  },
384
381
  {
385
382
  label: "UX direction", hint: "",
386
- ask: { flag: "ux", group: "Design", question: "How should the experience help users?", options: [
383
+ ask: { flag: "ux", group: "Design", question: "How should the experience help users?", example: "Works one-handed at a shop counter", options: [
387
384
  { value: "Fast for people who use it often", label: "Fast for people who use it often" },
388
385
  { value: "Clear for people using it for the first time", label: "Clear for people using it for the first time" },
389
386
  { value: "Easy to scan and understand", label: "Easy to scan and understand" },
@@ -392,7 +389,7 @@ const CONTEXT_FIELDS = [
392
389
  },
393
390
  {
394
391
  label: "UI direction", hint: "(character, for example calm, trustworthy, restrained)",
395
- ask: { flag: "ui", group: "Design", question: "What character should it have? A character, not colors or fonts. Pick up to 3.", multi: true, max: 3, options: [
392
+ ask: { flag: "ui", group: "Design", question: "What character should it have? A character, not colors or fonts. Pick up to 3.", example: "Warm, precise", multi: true, max: 3, options: [
396
393
  { value: "Calm", label: "Calm" },
397
394
  { value: "Trustworthy", label: "Trustworthy" },
398
395
  { value: "Friendly", label: "Friendly" },
@@ -405,7 +402,7 @@ const CONTEXT_FIELDS = [
405
402
  },
406
403
  {
407
404
  label: "Motion direction", hint: "(feel, what motion communicates, what to avoid)",
408
- ask: { flag: "motion", group: "Design", question: "How much motion?", options: [
405
+ ask: { flag: "motion", group: "Design", question: "How much motion?", example: "Only to confirm a payment went through", options: [
409
406
  { value: "No motion unless necessary", label: "No motion unless necessary" },
410
407
  { value: "Subtle, only to show change", label: "Subtle, only to show change" },
411
408
  { value: "Expressive, part of the brand", label: "Expressive, part of the brand" },
@@ -472,19 +469,17 @@ async function writeContext(root, answers) {
472
469
  await upsertBlock(path, body, CONTEXT_MARKS);
473
470
  return body;
474
471
  }
475
- /** A dim second line under a prompt's question: which keys move, select, and what Enter does here. */
476
- function keys(...parts) {
477
- return `\n${pc.gray("│")} ${pc.dim(parts.join(" · "))}`;
478
- }
479
- const YES_NO = "←/→ or y/n choose";
480
- const TEXT_KEYS = "Type, then Enter save (empty skips)";
472
+ /** The text box hint: an example answer, and that an empty Enter skips. */
473
+ const typed = (example) => `e.g. ${example} · empty Enter skips`;
474
+ /** Shown once at the start, not under every question (it stayed on screen after each answer). */
475
+ const KEY_LEGEND = pc.dim("↑/↓ move · Space select · Enter continue · Ctrl+C cancel");
481
476
  const OWN = "\u0000own";
482
477
  const KEEP = "\u0000keep";
483
478
  const SKIP = "\u0000skip";
484
479
  /** One select: Keep (when set), the choices, Write my own, Skip. Returns "" for skip, null on cancel. */
485
- async function askOne(question, options, current) {
480
+ async function askOne(question, options, current, example) {
486
481
  const v = await p.select({
487
- message: `${question}${keys("↑/↓ move", "Enter choose and go on")}`,
482
+ message: question,
488
483
  options: [
489
484
  ...(current ? [{ value: KEEP, label: `Keep: ${current}` }] : []),
490
485
  ...options.map((o) => ({ value: o.value || SKIP, label: o.label, hint: o.hint })),
@@ -498,19 +493,19 @@ async function askOne(question, options, current) {
498
493
  if (v === KEEP || v === SKIP)
499
494
  return "";
500
495
  if (v === OWN) {
501
- const t = await p.text({ message: `${question}${keys(TEXT_KEYS)}`, placeholder: "Enter to skip" });
496
+ const t = await p.text({ message: question, placeholder: typed(example) });
502
497
  return p.isCancel(t) ? null : (t ?? "").trim();
503
498
  }
504
499
  return v;
505
500
  }
506
501
  /** A multiselect: pick any, or none to skip. Returns "" for skip, null on cancel. */
507
- async function askMany(question, options, current, max) {
502
+ async function askMany(question, options, current, example, max) {
508
503
  const parts = current ? current.split(",").map((x) => x.trim()).filter(Boolean) : [];
509
504
  const known = options.map((o) => o.value);
510
505
  const custom = parts.filter((x) => !known.includes(x)).join(", ");
511
506
  for (;;) {
512
507
  const v = await p.multiselect({
513
- message: `${question}${keys("↑/↓ move", "Space select", "Enter go on (nothing selected skips)")}`,
508
+ message: question,
514
509
  options: [
515
510
  ...(custom ? [{ value: KEEP, label: `Keep: ${custom}` }] : []),
516
511
  ...options.map((o) => ({ value: o.value, label: o.label, hint: o.hint })),
@@ -526,7 +521,7 @@ async function askMany(question, options, current, max) {
526
521
  if (picked.includes(KEEP))
527
522
  values.push(custom);
528
523
  if (picked.includes(OWN)) {
529
- const t = await p.text({ message: `${question}${keys(TEXT_KEYS)}`, placeholder: "Enter to skip" });
524
+ const t = await p.text({ message: question, placeholder: typed(example) });
530
525
  if (p.isCancel(t))
531
526
  return null;
532
527
  if (t?.trim())
@@ -551,12 +546,26 @@ async function askContext(root) {
551
546
  p.log.step(group === "Project" ? "Project setup" : "Design setup");
552
547
  }
553
548
  const current = fieldValue(body, f);
554
- const v = ask.multi ? await askMany(ask.question, ask.options, current, ask.max) : await askOne(ask.question, ask.options, current);
549
+ if (ask.then) {
550
+ // Two questions, one field: split what is stored between them, and keep a part that is skipped.
551
+ const parts = current.split(",").map((x) => x.trim()).filter(Boolean);
552
+ const first = parts.find((x) => ask.options.some((o) => o.value === x)) ?? "";
553
+ const rest = parts.filter((x) => x !== first).join(", ");
554
+ const a = await askOne(ask.question, ask.options, first, ask.example);
555
+ if (a === null)
556
+ return null;
557
+ const b = ask.then.multi ? await askMany(ask.then.question, ask.then.options, rest, ask.then.example) : await askOne(ask.then.question, ask.then.options, rest, ask.then.example);
558
+ if (b === null)
559
+ return null;
560
+ answers[f.label] = [a || first, b || rest].filter(Boolean).join(", ");
561
+ continue;
562
+ }
563
+ const v = ask.multi ? await askMany(ask.question, ask.options, current, ask.example, ask.max) : await askOne(ask.question, ask.options, current, ask.example);
555
564
  if (v === null)
556
565
  return null;
557
566
  answers[f.label] = v;
558
567
  if (f.label === "Product" && v) {
559
- const t = await p.text({ message: `In one sentence, what does it do?${keys(TEXT_KEYS)}`, placeholder: "Enter to skip" });
568
+ const t = await p.text({ message: "In one sentence, what does it do?", placeholder: typed("A wallet for paying at shops by QR and sending money to friends") });
560
569
  if (p.isCancel(t))
561
570
  return null;
562
571
  if (t?.trim())
@@ -599,6 +608,7 @@ async function runSetup(flags) {
599
608
  return;
600
609
  }
601
610
  p.intro(pc.bgCyan(pc.black(" RYUX setup ")));
611
+ p.log.message(KEY_LEGEND);
602
612
  if (!(await setupInteractive(root)))
603
613
  return cancel();
604
614
  p.outro(`RYUX is ready. Next: ${CLI_CMD} check`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ryuxdsgn/design-intelligence",
3
- "version": "2.8.0",
3
+ "version": "2.8.1",
4
4
  "description": "RYUX: design intelligence for AI agents and designers. One skill, five entry points: Analyze, Design, Build, Critique, and QA, for Claude Code, Codex, Cursor, Gemini CLI, OpenCode, Cline, Copilot, and more.",
5
5
  "type": "module",
6
6
  "bin": {