@askewly/design 0.6.3 → 0.7.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.
package/dist/data.js CHANGED
@@ -1,14 +1,14 @@
1
- // M110 앞단 — 레포 자산 조회 통합 (`data recall`).
1
+ // M110 앞단 — 레포 자산 조회 통합. 정식 이름 `find`, `data recall` 은 영구 별칭(cs724).
2
2
  // design-intake.md §5단계와 askewly-design SKILL §2.7 이 약속해 온 `askewly data recall`
3
3
  // 이 유령 명령이었다(이 기기 어디에도 `data` 커맨드가 없었다) — 그 약속을 실재하게 만든다.
4
4
  //
5
5
  // 번들된 두 축(terms · recipes) + knowledge 축을 한 번에 훑는다. methodology/ 는
6
6
  // build-data.mjs 가 애초에 번들하지 않으므로(knowledge/*.md 만 읽는다) 여기서도 다루지
7
7
  // 않는다 — 새 데이터 로더를 만들지 않고 기존 load.ts 만 재사용한다는 이 step 의 원칙.
8
- // methodology 를 포함한 5축 통합 회수가 필요하면 `python ~/projects/second-brain/bin/data.py recall`
9
- // 로 보완한다(design-intake.md 가 그 경계를 함께 적는다).
8
+ // 여러 축을 넓게 찾는 일은 `askda recall` 소관이다(design-intake.md 가 그 경계를 함께 적는다).
10
9
  import { loadKnowledge, loadRecipes, loadTerms, searchKnowledge, searchTerms, } from "./load.js";
11
- export const RECALL_AXES = ["terms", "recipes", "knowledge"];
10
+ import { assetUrl, loadAssets, searchAssets } from "./assets.js";
11
+ export const RECALL_AXES = ["terms", "recipes", "assets", "knowledge"];
12
12
  function summarize(text, max = 120) {
13
13
  if (!text)
14
14
  return "";
@@ -46,6 +46,7 @@ function termHit(term) {
46
46
  id: term.id,
47
47
  path: `docs/ui-vocabulary/terms.yml#${term.id}`,
48
48
  summary: summarize(term.one_liner ?? term.en?.name ?? term.id),
49
+ next: `terms show ${term.id}`,
49
50
  };
50
51
  }
51
52
  function recipeHit(recipe) {
@@ -54,35 +55,53 @@ function recipeHit(recipe) {
54
55
  id: recipe.id,
55
56
  path: recipe.source_path,
56
57
  summary: summarize(`${recipe.name ? `${recipe.name} (${recipe.pattern_group ?? "-"})` : recipe.id}${recipe.status ? ` [${recipe.status}]` : ""}`),
58
+ next: `recipes show ${recipe.id}`,
57
59
  status: recipe.status,
58
60
  };
59
61
  }
62
+ function assetHit(asset) {
63
+ return {
64
+ axis: "assets",
65
+ id: asset.id,
66
+ path: assetUrl(asset),
67
+ summary: summarize(`${asset.definition} [${asset.install ? "installable" : "recipe only"}]`),
68
+ next: `assets show ${asset.id}`,
69
+ };
70
+ }
60
71
  function knowledgeHit(doc) {
61
72
  return {
62
73
  axis: "knowledge",
63
74
  id: doc.id,
64
75
  path: doc.source_path,
65
76
  summary: summarize(doc.description),
77
+ next: `knowledge show ${doc.id}`,
66
78
  };
67
79
  }
68
80
  /**
69
- * Search terms + recipes + knowledge in one pass and return path + one-line
81
+ * Search terms + recipes + assets + knowledge in one pass and return path + one-line
70
82
  * summary per hit. This is an index, not the source text — the caller must
71
83
  * still open `path` and read the body before judging.
72
84
  */
73
85
  export function recall(query, limit) {
74
86
  const terms = searchTerms(loadTerms(), query).map(termHit);
75
87
  const recipes = searchRecipes(loadRecipes(), query).map(recipeHit);
88
+ // Most assets share an id with a recipe; keep one row so duplicates don't eat the --limit window.
89
+ const recipeIds = new Set(recipes.map((hit) => hit.id));
90
+ const assets = searchAssets(loadAssets(), query)
91
+ .filter((asset) => !recipeIds.has(asset.id))
92
+ .map(assetHit);
76
93
  const knowledge = searchKnowledge(loadKnowledge(), query).map(knowledgeHit);
77
94
  // Interleave by axis (round-robin) so a query that happens to match many
78
95
  // terms doesn't crowd recipes/knowledge out of a small --limit window.
79
96
  const merged = [];
80
- const maxLen = Math.max(terms.length, recipes.length, knowledge.length);
97
+ const maxLen = Math.max(terms.length, recipes.length, assets.length, knowledge.length);
81
98
  for (let i = 0; i < maxLen; i++) {
82
99
  if (terms[i])
83
100
  merged.push(terms[i]);
84
101
  if (recipes[i])
85
102
  merged.push(recipes[i]);
103
+ if (assets[i])
104
+ merged.push(assets[i]);
86
105
  if (knowledge[i])
87
106
  merged.push(knowledge[i]);
88
107
  }
package/dist/index.js CHANGED
@@ -2,8 +2,8 @@
2
2
  import { readFileSync } from "node:fs";
3
3
  import path from "node:path";
4
4
  import { Command } from "commander";
5
- import { addRecipe, initProject, InjectError } from "./inject.js";
6
- import { runKickstart, KickstartError } from "./kickstart.js";
5
+ import { addFromRegistry, addRecipe, defaultSrcRoot, initProject, InjectError } from "./inject.js";
6
+ import { aliasStep, detectPathAlias, REGISTRY_BASE, runKickstart, KickstartError } from "./kickstart.js";
7
7
  import { DEFAULT_TYPOGRAPHY_THRESHOLD } from "./typography.js";
8
8
  import { MASCOT_POSES, renderMascot } from "./mascot.js";
9
9
  import { verifyDir } from "./verify.js";
@@ -11,10 +11,13 @@ import { loadKnowledge, loadRecipes, loadTerms, loadTokens, loadTokensCss, searc
11
11
  import { recall, RECALL_AXES } from "./data.js";
12
12
  import { assetUrl, buildPrompt, loadAssets, searchAssets, suggestAssets } from "./assets.js";
13
13
  import { getSite, listSites, siteNextStep, SiteError } from "./site.js";
14
+ import { localRepo, LocalToolError, siteFind, siteResolve } from "./local.js";
14
15
  const pkg = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
16
+ // Set only by the local `askde` shim; npm and npx users never see the repo-only commands.
17
+ const repo = localRepo();
15
18
  const program = new Command();
16
19
  program
17
- .name("askewly-design")
20
+ .name(repo ? "askde" : "askewly-design")
18
21
  .description("Query the Askewly Design system: UI vocabulary terms, design tokens, and component recipes.")
19
22
  .version(pkg.version);
20
23
  function fail(message) {
@@ -143,16 +146,62 @@ program
143
146
  program
144
147
  .command("add")
145
148
  .argument("<recipe>", "recipe id, e.g. button")
146
- .option("--out <dir>", "output directory for the component file", ".")
149
+ .option("--out <dir>", "directory for the component file (default: src/components, or . for --excerpt)")
147
150
  .option("--tokens <file>", "path to the project's tokens.css", "tokens.css")
148
- .option("--force", "overwrite an existing file")
149
- .description("inject a recipe's Code excerpt into the project")
150
- .action((id, options) => {
151
+ .option("--registry <baseUrl>", "registry base URL", REGISTRY_BASE)
152
+ .option("--excerpt", "write only the recipe's Code excerpt (offline; the excerpt is a usage example)")
153
+ .option("--force", "overwrite existing files")
154
+ .description("add the published component (registry item + its libs and dependencies) to the project")
155
+ .action(async (id, options) => {
151
156
  const recipe = loadRecipes().find((r) => r.id === id);
157
+ // 1. 완성본 — 사이트가 공개한 registry 항목 그대로 (F6, 0.6.4)
158
+ let fallbackReason = null;
159
+ if (!options.excerpt) {
160
+ try {
161
+ const full = await addFromRegistry(id, {
162
+ registryBase: options.registry,
163
+ projectDir: process.cwd(),
164
+ componentDir: options.out ? path.resolve(options.out) : undefined,
165
+ force: Boolean(options.force),
166
+ tokensCssPath: path.resolve(options.tokens),
167
+ });
168
+ if (full) {
169
+ console.log(`added the full component from ${full.url}`);
170
+ for (const file of full.written)
171
+ console.log(`wrote ${file}`);
172
+ for (const file of full.unchanged)
173
+ console.log(`unchanged ${file} (already identical)`);
174
+ for (const file of full.conflicts)
175
+ console.log(`⚠ kept ${file} - differs from the registry; not overwritten (use --force to replace)`);
176
+ const steps = [];
177
+ const srcRel = path.basename(defaultSrcRoot(process.cwd())) === "src" ? "./src" : ".";
178
+ const alias = aliasStep(detectPathAlias(process.cwd()), srcRel);
179
+ if (alias)
180
+ steps.push(alias);
181
+ steps.push(`npm i ${full.npmDeps.join(" ")}`);
182
+ if (full.missingCssVars.length > 0) {
183
+ steps.push(`define the CSS variables it needs (missing in ${options.tokens} — run \`askewly-design init\` first): ${full.missingCssVars.join(", ")}`);
184
+ }
185
+ console.log("\nNext steps:");
186
+ for (const [index, step] of steps.entries())
187
+ console.log(` ${index + 1}. ${step}`);
188
+ return;
189
+ }
190
+ fallbackReason = `no published component for "${id}" in ${options.registry}/r/`;
191
+ }
192
+ catch (error) {
193
+ if (!(error instanceof KickstartError))
194
+ throw error;
195
+ fallbackReason = error.message;
196
+ }
197
+ }
198
+ // 2. 폴백 — recipe 의 Code 발췌 (사용 예시라 그대로는 안 돌 수 있다)
152
199
  if (!recipe)
153
- fail(`unknown recipe "${id}" — try: askewly-design recipes list`);
200
+ fail(fallbackReason ? `unknown recipe "${id}" (${fallbackReason}) — try: askewly-design recipes list` : `unknown recipe "${id}" — try: askewly-design recipes list`);
201
+ if (fallbackReason)
202
+ console.log(`⚠ ${fallbackReason} — writing the recipe's Code excerpt instead. It is a usage example, not the finished component; wire it up yourself.`);
154
203
  try {
155
- const result = addRecipe(recipe, path.resolve(options.out), Boolean(options.force), path.resolve(options.tokens));
204
+ const result = addRecipe(recipe, path.resolve(options.out ?? "."), Boolean(options.force), path.resolve(options.tokens));
156
205
  console.log(`wrote ${result.file}`);
157
206
  for (const lib of result.libs) {
158
207
  if (lib.status === "written")
@@ -342,37 +391,40 @@ program
342
391
  const color = options.color && Boolean(process.stdout.isTTY);
343
392
  console.log(renderMascot(pose, { color }));
344
393
  });
345
- const data = program
346
- .command("data")
347
- .description("query bundled repo assets across axes (terms, recipes, knowledge) in one pass");
348
- data
349
- .command("recall")
350
- .argument("<query>", "text to match across terms / recipes / knowledge")
351
- .option("--limit <n>", "max results", "5")
352
- .option("--json", "machine-readable output")
353
- .description("search terms + recipes + knowledge in one pass — an index of paths, not the source text")
354
- .action((query, options) => {
355
- const limit = Number(options.limit) || 5;
356
- const hits = recall(query, limit);
357
- if (options.json) {
358
- console.log(JSON.stringify({
359
- note: "index only — read `path` before judging",
360
- axes: RECALL_AXES,
361
- query,
362
- hits,
363
- }, null, 2));
364
- return;
365
- }
366
- console.log("색인이지 본문이 아니다 — 후보 경로를 받은 뒤 판단은 본문을 읽고 한다.");
367
- if (hits.length === 0) {
368
- console.log(`no matches for "${query}" across axes: ${RECALL_AXES.join(", ")} (methodology/ is not bundled here — try: python ~/projects/second-brain/bin/data.py recall "${query}")`);
369
- return;
370
- }
371
- for (const hit of hits) {
372
- console.log(`[${hit.axis}] ${hit.id} — ${hit.path}`);
373
- console.log(` ${hit.summary}`);
374
- }
375
- });
394
+ function addFindCommand(parent, name) {
395
+ parent
396
+ .command(name)
397
+ .argument("<query>", "text to match across terms / recipes / assets / knowledge")
398
+ .option("--limit <n>", "max results", "5")
399
+ .option("--json", "machine-readable output")
400
+ .description("search terms + recipes + assets + knowledge in one pass — an index of paths, not the source text")
401
+ .action((query, options) => {
402
+ const limit = Number(options.limit) || 5;
403
+ const hits = recall(query, limit);
404
+ if (options.json) {
405
+ console.log(JSON.stringify({
406
+ note: "index only — read `path` before judging",
407
+ axes: RECALL_AXES,
408
+ query,
409
+ hits,
410
+ }, null, 2));
411
+ return;
412
+ }
413
+ console.log("색인이지 본문이 아니다 — 후보 경로를 받은 뒤 판단은 본문을 읽고 한다.");
414
+ if (hits.length === 0) {
415
+ console.log(`no matches for "${query}" across axes: ${RECALL_AXES.join(", ")} (methodology/ is not bundled here)`);
416
+ return;
417
+ }
418
+ for (const hit of hits) {
419
+ console.log(`[${hit.axis}] ${hit.id} — ${hit.path}`);
420
+ console.log(` ${hit.summary}`);
421
+ console.log(` next: ${hit.next}`);
422
+ }
423
+ });
424
+ }
425
+ addFindCommand(program, "find");
426
+ // Permanent alias: 0.5.1+ users and published docs call `data recall`.
427
+ addFindCommand(program.command("data").description("alias of `find`, kept for existing callers"), "recall");
376
428
  // step-11 (M141): 자산별 md(공개 URL https://ui.askewly.com/llms/assets/<id>.md) 를 번들에서 오프라인으로 읽는다.
377
429
  const assetsCmd = program.command("assets").description("registry + recipe assets (one md per asset: definition, usage rules, install command)");
378
430
  assetsCmd
@@ -460,4 +512,40 @@ ${siteNextStep(target)}`);
460
512
  throw error;
461
513
  }
462
514
  });
463
- program.parse();
515
+ if (repo) {
516
+ siteCmd
517
+ .command("find")
518
+ .argument("<query>", "element or site, e.g. menu")
519
+ .option("--limit <n>", "max results", "5")
520
+ .description("local: search approved recreated-site modules in this repo")
521
+ .action((query, options) => {
522
+ const limit = Number(options.limit);
523
+ if (!Number.isInteger(limit) || limit < 1)
524
+ fail("--limit must be a positive integer");
525
+ try {
526
+ process.exitCode = siteFind(repo, query, limit);
527
+ }
528
+ catch (error) {
529
+ if (error instanceof LocalToolError)
530
+ fail(error.message);
531
+ throw error;
532
+ }
533
+ });
534
+ siteCmd
535
+ .command("resolve")
536
+ .argument("<reference>", "copied site pointer or URL")
537
+ .option("--file <path>", "save this manifest file under tmp/site-references after verifying its hash")
538
+ .option("--local", "read the same revision from the local snapshot")
539
+ .description("local: verify a copied site pointer and optionally fetch one pinned source file")
540
+ .action((reference, options) => {
541
+ try {
542
+ process.exitCode = siteResolve(repo, reference, options);
543
+ }
544
+ catch (error) {
545
+ if (error instanceof LocalToolError)
546
+ fail(error.message);
547
+ throw error;
548
+ }
549
+ });
550
+ }
551
+ await program.parseAsync();
package/dist/inject.js CHANGED
@@ -1,5 +1,6 @@
1
1
  import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
2
2
  import path from "node:path";
3
+ import { fetchRegistryItem, missingRequiredVars, transplantBlock } from "./kickstart.js";
3
4
  export class InjectError extends Error {
4
5
  }
5
6
  function readBundled(relative) {
@@ -75,6 +76,30 @@ export function extractSectionNotes(recipe, title) {
75
76
  export function defaultSrcRoot(projectDir) {
76
77
  return existsSync(path.join(projectDir, "src")) ? path.join(projectDir, "src") : projectDir;
77
78
  }
79
+ /**
80
+ * Writes the published component — the same item `npx shadcn add <url>` would
81
+ * install: its files, its registry:lib files, and registryDependencies resolved
82
+ * recursively (bare names from shadcn, URLs from ui.askewly.com). Returns null
83
+ * when the registry has no item for this id, so the caller can fall back to the
84
+ * recipe excerpt and say so (F6 — the excerpt alone does not typecheck).
85
+ */
86
+ export async function addFromRegistry(id, opts) {
87
+ const registryBase = opts.registryBase.replace(/\/$/, "");
88
+ const item = await fetchRegistryItem(id, registryBase);
89
+ if (!item)
90
+ return null;
91
+ const result = await transplantBlock(item, opts.projectDir, { keepExisting: !opts.force, componentDir: opts.componentDir });
92
+ const css = opts.tokensCssPath && existsSync(opts.tokensCssPath) ? readFileSync(opts.tokensCssPath, "utf8") : "";
93
+ return {
94
+ url: `${registryBase}/r/${id}.json`,
95
+ written: result.written,
96
+ unchanged: result.unchanged,
97
+ conflicts: result.conflicts,
98
+ npmDeps: [...result.npmDeps].sort(),
99
+ requiredCssVars: result.requiredCssVars,
100
+ missingCssVars: missingRequiredVars(css, result.requiredCssVars),
101
+ };
102
+ }
78
103
  /**
79
104
  * Writes the recipe's Code excerpt to <outDir>/<id>.tsx and reports which
80
105
  * tokens_used entries are not covered by an existing tokens.css in outDir's
package/dist/kickstart.js CHANGED
@@ -7,7 +7,7 @@ import { verifyDir } from "./verify.js";
7
7
  const BUILTIN_MODULES = new Set(builtinModules);
8
8
  export class KickstartError extends Error {
9
9
  }
10
- const REGISTRY_BASE = "https://ui.askewly.com";
10
+ export const REGISTRY_BASE = "https://ui.askewly.com";
11
11
  const SHADCN_BASE = "https://ui.shadcn.com/r/styles/new-york-v4";
12
12
  const BRIEF_QUESTIONS = [
13
13
  {
@@ -516,21 +516,45 @@ export async function fetchBlock(blockName, registryBase) {
516
516
  }
517
517
  return block;
518
518
  }
519
- export async function transplantBlock(block, targetDir) {
519
+ /** `add` 용 — 티어를 가리지 않는다. 공개 항목이 없으면(404) null, 그 밖의 실패는 던진다. */
520
+ export async function fetchRegistryItem(name, registryBase) {
521
+ const url = `${registryBase}/r/${name}.json`;
522
+ let res;
523
+ try {
524
+ res = await fetch(url);
525
+ }
526
+ catch (error) {
527
+ throw new KickstartError(`fetch failed: ${url} — ${error.message}`);
528
+ }
529
+ if (res.status === 404)
530
+ return null;
531
+ if (!res.ok)
532
+ throw new KickstartError(`registry fetch ${res.status}: ${url}`);
533
+ return (await res.json());
534
+ }
535
+ export async function transplantBlock(block, targetDir, opts = {}) {
520
536
  const srcRoot = existsSync(path.join(targetDir, "src")) ? path.join(targetDir, "src") : targetDir;
521
537
  const written = [];
538
+ const unchanged = [];
539
+ const conflicts = [];
522
540
  const npmDeps = new Set(["clsx", "tailwind-merge"]);
523
541
  const visited = new Set();
524
542
  const requiredVars = new Set();
525
- function write(file) {
526
- const dest = destFor(file, srcRoot);
543
+ function write(file, top = false) {
544
+ const dest = top && opts.componentDir && !file.target ? path.join(opts.componentDir, path.basename(file.path)) : destFor(file, srcRoot);
527
545
  const content = rewriteImports(file.content);
546
+ // The file is the authority on what must be installed, written or not.
547
+ for (const pkg of importedPackages(content))
548
+ npmDeps.add(pkg);
549
+ if (opts.keepExisting && existsSync(dest)) {
550
+ // 사용자가 고친 파일을 조용히 날리지 않는다 — 같으면 건너뛰고 다르면 알린다.
551
+ const same = readFileSync(dest, "utf8").replace(/\r\n/g, "\n") === content.replace(/\r\n/g, "\n");
552
+ (same ? unchanged : conflicts).push(dest);
553
+ return;
554
+ }
528
555
  mkdirSync(path.dirname(dest), { recursive: true });
529
556
  writeFileSync(dest, content);
530
557
  written.push(dest);
531
- // The file we just wrote is the authority on what must be installed.
532
- for (const pkg of importedPackages(content))
533
- npmDeps.add(pkg);
534
558
  }
535
559
  async function resolve(item) {
536
560
  // Required CSS vars are collected transitively, not just off the top-level
@@ -556,7 +580,7 @@ export async function transplantBlock(block, targetDir) {
556
580
  }
557
581
  await resolve(block);
558
582
  for (const file of block.files ?? [])
559
- write(file);
583
+ write(file, true);
560
584
  // cn() — shadcn init would have created it; transplant guarantees it (M18 step-5 friction).
561
585
  const utils = path.join(srcRoot, "lib", "utils.ts");
562
586
  if (!existsSync(utils)) {
@@ -564,7 +588,7 @@ export async function transplantBlock(block, targetDir) {
564
588
  writeFileSync(utils, `import { clsx, type ClassValue } from "clsx"\nimport { twMerge } from "tailwind-merge"\n\nexport function cn(...inputs: ClassValue[]) {\n return twMerge(clsx(inputs))\n}\n`);
565
589
  written.push(utils);
566
590
  }
567
- return { written, npmDeps, requiredCssVars: [...requiredVars] };
591
+ return { written, unchanged, conflicts, npmDeps, requiredCssVars: [...requiredVars] };
568
592
  }
569
593
  // ---------------------------------------------------------------------------
570
594
  // Orchestrator — brief → DESIGN.md → tokens → transplant → required-var check → verify
package/dist/local.js ADDED
@@ -0,0 +1,36 @@
1
+ // Repo-only commands for the local `askde` shim (cs724). They run the existing scripts, so the
2
+ // repo keeps one source of truth, and they never appear for npm users.
3
+ import { spawnSync } from "node:child_process";
4
+ import { existsSync } from "node:fs";
5
+ import path from "node:path";
6
+ export class LocalToolError extends Error {
7
+ }
8
+ export function localRepo() {
9
+ return process.env.ASKDE_REPO?.trim() || null;
10
+ }
11
+ function runRepoTool(repo, command, script, args) {
12
+ const file = path.join(repo, script);
13
+ if (!existsSync(file))
14
+ throw new LocalToolError(`${script} not found under ASKDE_REPO=${repo}`);
15
+ const result = spawnSync(command, [file, ...args], {
16
+ cwd: repo,
17
+ stdio: "inherit",
18
+ env: { ...process.env, PYTHONUTF8: "1" },
19
+ });
20
+ if (result.error)
21
+ throw new LocalToolError(`could not run ${command}: ${result.error.message}`);
22
+ return result.status ?? 1;
23
+ }
24
+ export function siteFind(repo, query, limit) {
25
+ const python = process.platform === "win32" ? "python" : "python3";
26
+ // `--` keeps a query like "--approve" from reaching catalog.py's write options.
27
+ return runRepoTool(repo, python, "scripts/recreate/catalog.py", ["--limit", String(limit), "--", query]);
28
+ }
29
+ export function siteResolve(repo, reference, options) {
30
+ const args = [reference];
31
+ if (options.local)
32
+ args.push("--local");
33
+ if (options.file)
34
+ args.push("--file", options.file);
35
+ return runRepoTool(repo, process.execPath, "scripts/recreate/resolve-site-reference.mjs", args);
36
+ }
package/dist/mascot.js CHANGED
@@ -4,34 +4,34 @@
4
4
  // 사용 규칙(정본 §3): 라벤더 #B298DC 단색 고정 · 등폭 전제 · 기계 소비 출력 금지.
5
5
  export const MASCOT_POSES = ["lying", "standing", "sleepy", "mini"];
6
6
  const ART = {
7
- lying: ` ▄██▄ ▄██▄
8
- █ ██ █ ██
9
- ▄██████████████████▄
10
- █▄ █████████████████████
11
- ▀█▄ ████ ████ █████████
12
- ▀██▄████ ████ █████████
13
- ▀███████████████████▀
7
+ lying: ` ▄██▄ ▄██▄
8
+ █ ██ █ ██
9
+ ▄██████████████████▄
10
+ █▄ █████████████████████
11
+ ▀█▄ ████ ████ █████████
12
+ ▀██▄████ ████ █████████
13
+ ▀███████████████████▀
14
14
  ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀`,
15
- standing: ` ▄██▄ ▄██▄
16
- █ ██ █ ██
17
- ▄██████████████▄
18
- ███ ████ █████
19
- ███ ████ █████
20
- ████████████████
21
- ████████████████
22
- ▀████▀▀▀████▀▀██
15
+ standing: ` ▄██▄ ▄██▄
16
+ █ ██ █ ██
17
+ ▄██████████████▄
18
+ ███ ████ █████
19
+ ███ ████ █████
20
+ ████████████████
21
+ ████████████████
22
+ ▀████▀▀▀████▀▀██
23
23
  ████ ████ ▀███▄▄▄`,
24
- sleepy: ` ▄██▄ ▄██▄
25
- █ ██ █ ██
26
- ▄██████████████████▄
27
- █▄ █████████████████████
28
- ▀█▄ █████████████████████
29
- ▀██▄███ ██ ████████
30
- ▀███████████████████▀
24
+ sleepy: ` ▄██▄ ▄██▄
25
+ █ ██ █ ██
26
+ ▄██████████████████▄
27
+ █▄ █████████████████████
28
+ ▀█▄ █████████████████████
29
+ ▀██▄███ ██ ████████
30
+ ▀███████████████████▀
31
31
  ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀`,
32
- mini: ` ▄██▄▄▄▄██▄
33
- ██████████
34
- ██ ██ ██
32
+ mini: ` ▄██▄▄▄▄██▄
33
+ ██████████
34
+ ██ ██ ██
35
35
  ▀████████▀`,
36
36
  };
37
37
  /** askewly.lavender #B298DC — ANSI 24-bit. */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@askewly/design",
3
- "version": "0.6.3",
3
+ "version": "0.7.0",
4
4
  "description": "Query, inject, and verify the Askewly Design system (tokens, UI vocabulary, recipes) from your terminal or coding agent.",
5
5
  "license": "MIT",
6
6
  "homepage": "https://ui.askewly.com",