@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.
- package/dist/cli/index.js +49 -39
- 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:
|
|
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 =
|
|
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:
|
|
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"}
|
|
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
|
-
/**
|
|
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: "
|
|
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: "
|
|
368
|
-
{ value: "
|
|
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
|
-
|
|
371
|
-
|
|
372
|
-
|
|
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
|
-
/**
|
|
476
|
-
|
|
477
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
-
|
|
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:
|
|
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.
|
|
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": {
|