@ansh-ui/cli 0.3.0 → 0.4.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/cli.js +311 -17
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +107 -25
- package/dist/index.js +267 -14
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/cli.js
CHANGED
|
@@ -1,9 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { readFileSync, existsSync, mkdirSync, writeFileSync } from 'fs';
|
|
3
3
|
import { parseArgs } from 'util';
|
|
4
|
-
import { join, dirname, resolve } from 'path';
|
|
4
|
+
import { join, dirname, resolve, relative } from 'path';
|
|
5
5
|
import { parseColor, generatePalette, rgbToOklch, toOklchString, toHex, createGradient, createScale, checkContrast, harmony, effects, behaviours } from '@ansh-ui/core';
|
|
6
|
+
import { createInterface } from 'readline';
|
|
6
7
|
|
|
8
|
+
var CONFIG_FILE = "ansh.config.json";
|
|
9
|
+
function readConfig(root) {
|
|
10
|
+
const path = join(root, CONFIG_FILE);
|
|
11
|
+
if (!existsSync(path)) return null;
|
|
12
|
+
try {
|
|
13
|
+
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
14
|
+
return parsed && typeof parsed === "object" ? parsed : null;
|
|
15
|
+
} catch {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// src/project.ts
|
|
7
21
|
function findRoot(cwd) {
|
|
8
22
|
let current = resolve(cwd);
|
|
9
23
|
for (let depth = 0; depth < 10; depth++) {
|
|
@@ -31,11 +45,12 @@ function detectProject(cwd = process.cwd()) {
|
|
|
31
45
|
const framework = deps.nuxt ? "nuxt" : deps.next ? "next" : deps.vue ? "vue" : deps.react ? "react" : "unknown";
|
|
32
46
|
const typescript = Boolean(deps.typescript) || existsSync(join(root, "tsconfig.json"));
|
|
33
47
|
const packageManager = existsSync(join(root, "pnpm-lock.yaml")) ? "pnpm" : existsSync(join(root, "bun.lockb")) || existsSync(join(root, "bun.lock")) ? "bun" : existsSync(join(root, "yarn.lock")) ? "yarn" : "npm";
|
|
48
|
+
const config = readConfig(root);
|
|
34
49
|
return {
|
|
35
50
|
root,
|
|
36
51
|
framework,
|
|
37
52
|
typescript,
|
|
38
|
-
componentsDir: detectComponentsDir(root, framework),
|
|
53
|
+
componentsDir: config?.componentsDir ?? detectComponentsDir(root, framework),
|
|
39
54
|
alias: detectAlias(root),
|
|
40
55
|
packageManager,
|
|
41
56
|
installed: Boolean(
|
|
@@ -66,10 +81,8 @@ function detectAlias(root) {
|
|
|
66
81
|
return null;
|
|
67
82
|
}
|
|
68
83
|
var KNOWN_INCONSISTENCIES = [
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
detail: "A leftover from the Aurelis \u2192 Ansh rename (packages/license/src/public-key.ts). Changing it without a dual-prefix verification path would invalidate already-issued keys \u2014 needs a maintainer decision, not a drive-by fix. See STATUS.md \xA77."
|
|
72
|
-
}
|
|
84
|
+
// Empty since the licence key prefix moved to `ansh_` (with `aur_` still accepted). Add an entry
|
|
85
|
+
// here when STATUS.md §7 gains one.
|
|
73
86
|
];
|
|
74
87
|
function isAnshRepoRoot(root) {
|
|
75
88
|
const pkg = readJson(join(root, "package.json"));
|
|
@@ -78,6 +91,16 @@ function isAnshRepoRoot(root) {
|
|
|
78
91
|
function detectKnownInconsistencies(root) {
|
|
79
92
|
return isAnshRepoRoot(root) ? KNOWN_INCONSISTENCIES : [];
|
|
80
93
|
}
|
|
94
|
+
function packagesFor(framework) {
|
|
95
|
+
switch (framework) {
|
|
96
|
+
case "nuxt":
|
|
97
|
+
return ["@ansh-ui/nuxt"];
|
|
98
|
+
case "vue":
|
|
99
|
+
return ["@ansh-ui/vue", "@ansh-ui/tokens"];
|
|
100
|
+
default:
|
|
101
|
+
return ["@ansh-ui/react", "@ansh-ui/tokens"];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
81
104
|
function installCommand(info, packages) {
|
|
82
105
|
const list = packages.join(" ");
|
|
83
106
|
switch (info.packageManager) {
|
|
@@ -184,15 +207,7 @@ function addComponent(options) {
|
|
|
184
207
|
return { path, contents, written: true, missingDependencies };
|
|
185
208
|
}
|
|
186
209
|
function missingFor(project) {
|
|
187
|
-
|
|
188
|
-
switch (project.framework) {
|
|
189
|
-
case "nuxt":
|
|
190
|
-
return ["@ansh-ui/nuxt"];
|
|
191
|
-
case "vue":
|
|
192
|
-
return ["@ansh-ui/vue", "@ansh-ui/tokens"];
|
|
193
|
-
default:
|
|
194
|
-
return ["@ansh-ui/react", "@ansh-ui/tokens"];
|
|
195
|
-
}
|
|
210
|
+
return project.installed ? [] : packagesFor(project.framework);
|
|
196
211
|
}
|
|
197
212
|
function literal(value) {
|
|
198
213
|
if (typeof value === "string") return JSON.stringify(value);
|
|
@@ -394,6 +409,242 @@ ${accentLines}
|
|
|
394
409
|
}
|
|
395
410
|
return { css, unsafeOnLight, unsafeOnDark, nearestStep };
|
|
396
411
|
}
|
|
412
|
+
function initProject(options = {}) {
|
|
413
|
+
const project = detectProject(options.cwd);
|
|
414
|
+
if (project.framework === "unknown") {
|
|
415
|
+
throw new Error(
|
|
416
|
+
"Could not detect a React, Next, Vue or Nuxt project. Run this from your project root, or install the framework first."
|
|
417
|
+
);
|
|
418
|
+
}
|
|
419
|
+
const configPath = join(project.root, CONFIG_FILE);
|
|
420
|
+
if (existsSync(configPath) && !options.force) {
|
|
421
|
+
throw new Error(`${CONFIG_FILE} already exists. Pass --force to overwrite it.`);
|
|
422
|
+
}
|
|
423
|
+
const themeRelative = options.brand ? join(stylesDir(project), "ansh-theme.css") : null;
|
|
424
|
+
const themePath = themeRelative ? join(project.root, themeRelative) : null;
|
|
425
|
+
if (themePath && existsSync(themePath) && !options.force) {
|
|
426
|
+
throw new Error(`${themePath} already exists. Pass --force to overwrite it.`);
|
|
427
|
+
}
|
|
428
|
+
const config = {
|
|
429
|
+
framework: project.framework,
|
|
430
|
+
typescript: project.typescript,
|
|
431
|
+
componentsDir: options.componentsDir ?? project.componentsDir,
|
|
432
|
+
alias: project.alias,
|
|
433
|
+
theme: themeRelative
|
|
434
|
+
};
|
|
435
|
+
const written = [];
|
|
436
|
+
if (!options.dryRun) {
|
|
437
|
+
if (options.brand && themePath) {
|
|
438
|
+
generateTheme({ color: options.brand, out: themePath });
|
|
439
|
+
written.push(themePath);
|
|
440
|
+
}
|
|
441
|
+
writeFileSync(configPath, `${JSON.stringify(config, null, 2)}
|
|
442
|
+
`, "utf8");
|
|
443
|
+
written.push(configPath);
|
|
444
|
+
} else if (options.brand) {
|
|
445
|
+
generateTheme({ color: options.brand });
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
project,
|
|
449
|
+
config,
|
|
450
|
+
written,
|
|
451
|
+
install: project.installed ? null : installCommand(project, packagesFor(project.framework)),
|
|
452
|
+
setup: setupFor(project, themeRelative)
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
function stylesDir(project) {
|
|
456
|
+
if (project.framework === "nuxt") return "assets/css";
|
|
457
|
+
return existsSync(join(project.root, "src")) ? "src/styles" : "styles";
|
|
458
|
+
}
|
|
459
|
+
function setupFor(project, theme) {
|
|
460
|
+
if (project.framework === "nuxt") {
|
|
461
|
+
const css = ["'@ansh-ui/vue/styles.css'", ...theme ? [`'~/${theme}'`] : []].join(", ");
|
|
462
|
+
return {
|
|
463
|
+
file: "nuxt.config.ts",
|
|
464
|
+
code: `export default defineNuxtConfig({
|
|
465
|
+
modules: ['@ansh-ui/nuxt'],
|
|
466
|
+
css: [${css}],
|
|
467
|
+
})`
|
|
468
|
+
};
|
|
469
|
+
}
|
|
470
|
+
const binding = project.framework === "vue" ? "vue" : "react";
|
|
471
|
+
const entry = project.framework === "next" ? existsSync(join(project.root, "src/app")) ? "src/app/layout.tsx" : "app/layout.tsx" : project.framework === "vue" ? `src/main.${project.typescript ? "ts" : "js"}` : `src/main.${project.typescript ? "tsx" : "jsx"}`;
|
|
472
|
+
const themeImport = theme ? `
|
|
473
|
+
import '${themeImportPath(project, entry, theme)}'` : "";
|
|
474
|
+
return {
|
|
475
|
+
file: entry,
|
|
476
|
+
code: `import '@ansh-ui/tokens/ansh.css'
|
|
477
|
+
import '@ansh-ui/${binding}/styles.css'${themeImport}`
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
function themeImportPath(project, entry, theme) {
|
|
481
|
+
const path = relative(join(project.root, entry, ".."), join(project.root, theme));
|
|
482
|
+
return path.startsWith(".") ? path : `./${path}`;
|
|
483
|
+
}
|
|
484
|
+
var PROTOCOL_VERSION = "2025-06-18";
|
|
485
|
+
var str = (value) => typeof value === "string" ? value : void 0;
|
|
486
|
+
var TOOLS = [
|
|
487
|
+
{
|
|
488
|
+
name: "ansh_list",
|
|
489
|
+
description: "List Ansh canvas effects and DOM behaviours with their component names and option counts.",
|
|
490
|
+
inputSchema: {
|
|
491
|
+
type: "object",
|
|
492
|
+
properties: { kind: { type: "string", enum: ["effect", "behaviour"] } }
|
|
493
|
+
},
|
|
494
|
+
run: (args) => {
|
|
495
|
+
const kind = args.kind === "effect" || args.kind === "behaviour" ? args.kind : void 0;
|
|
496
|
+
const entries = listCatalogue(kind).map(({ name, kind: kind2, component, options }) => ({
|
|
497
|
+
name,
|
|
498
|
+
kind: kind2,
|
|
499
|
+
component,
|
|
500
|
+
options: options.length
|
|
501
|
+
}));
|
|
502
|
+
return JSON.stringify(entries, null, 2);
|
|
503
|
+
}
|
|
504
|
+
},
|
|
505
|
+
{
|
|
506
|
+
name: "ansh_info",
|
|
507
|
+
description: "Every option of one Ansh effect or behaviour, with its default value. Accepts the catalogue name (aurora) or component name (AuroraBackground).",
|
|
508
|
+
inputSchema: {
|
|
509
|
+
type: "object",
|
|
510
|
+
properties: { name: { type: "string" } },
|
|
511
|
+
required: ["name"]
|
|
512
|
+
},
|
|
513
|
+
run: (args) => {
|
|
514
|
+
const entry = findEntry(str(args.name) ?? "");
|
|
515
|
+
if (!entry) throw new Error(`Unknown component "${String(args.name)}". Call ansh_list.`);
|
|
516
|
+
return JSON.stringify(entry, null, 2);
|
|
517
|
+
}
|
|
518
|
+
},
|
|
519
|
+
{
|
|
520
|
+
name: "ansh_setup",
|
|
521
|
+
description: "How to set up Ansh in the current project: detected framework, install command, and the imports or nuxt.config lines to add. Pass write: true to also create ansh.config.json (and a theme file when brand is given).",
|
|
522
|
+
inputSchema: {
|
|
523
|
+
type: "object",
|
|
524
|
+
properties: {
|
|
525
|
+
brand: { type: "string", description: "Brand colour for a generated theme, e.g. #0275f4" },
|
|
526
|
+
write: { type: "boolean", default: false }
|
|
527
|
+
}
|
|
528
|
+
},
|
|
529
|
+
run: (args, cwd) => {
|
|
530
|
+
const result = initProject({ cwd, brand: str(args.brand), dryRun: args.write !== true });
|
|
531
|
+
return JSON.stringify(
|
|
532
|
+
{
|
|
533
|
+
framework: result.config.framework,
|
|
534
|
+
install: result.install,
|
|
535
|
+
setupFile: result.setup.file,
|
|
536
|
+
setupCode: result.setup.code,
|
|
537
|
+
config: result.config,
|
|
538
|
+
written: result.written
|
|
539
|
+
},
|
|
540
|
+
null,
|
|
541
|
+
2
|
|
542
|
+
);
|
|
543
|
+
}
|
|
544
|
+
},
|
|
545
|
+
{
|
|
546
|
+
name: "ansh_add",
|
|
547
|
+
description: "Generate a typed, project-local wrapper component around an Ansh effect or behaviour, with the project\u2019s defaults. Returns the file path and contents; pass write: true to write the file.",
|
|
548
|
+
inputSchema: {
|
|
549
|
+
type: "object",
|
|
550
|
+
properties: {
|
|
551
|
+
name: { type: "string" },
|
|
552
|
+
as: { type: "string", description: "Local component name, e.g. HeroBackground" },
|
|
553
|
+
write: { type: "boolean", default: false }
|
|
554
|
+
},
|
|
555
|
+
required: ["name"]
|
|
556
|
+
},
|
|
557
|
+
run: (args, cwd) => {
|
|
558
|
+
const result = addComponent({
|
|
559
|
+
cwd,
|
|
560
|
+
name: str(args.name) ?? "",
|
|
561
|
+
as: str(args.as),
|
|
562
|
+
dryRun: args.write !== true
|
|
563
|
+
});
|
|
564
|
+
return JSON.stringify(result, null, 2);
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
{
|
|
568
|
+
name: "ansh_theme",
|
|
569
|
+
description: "Generate an Ansh token override stylesheet from a brand colour, with WCAG contrast notes for each step.",
|
|
570
|
+
inputSchema: {
|
|
571
|
+
type: "object",
|
|
572
|
+
properties: { color: { type: "string" }, withAccent: { type: "boolean", default: false } },
|
|
573
|
+
required: ["color"]
|
|
574
|
+
},
|
|
575
|
+
run: (args) => {
|
|
576
|
+
const report = generateTheme({
|
|
577
|
+
color: str(args.color) ?? "",
|
|
578
|
+
withAccent: args.withAccent === true
|
|
579
|
+
});
|
|
580
|
+
return JSON.stringify(report, null, 2);
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
];
|
|
584
|
+
function callTool(params, cwd) {
|
|
585
|
+
const tool = TOOLS.find((t) => t.name === params?.name);
|
|
586
|
+
if (!tool)
|
|
587
|
+
return {
|
|
588
|
+
content: [{ type: "text", text: `Unknown tool "${String(params?.name)}".` }],
|
|
589
|
+
isError: true
|
|
590
|
+
};
|
|
591
|
+
const args = params?.arguments ?? {};
|
|
592
|
+
try {
|
|
593
|
+
return { content: [{ type: "text", text: tool.run(args, cwd) }] };
|
|
594
|
+
} catch (error) {
|
|
595
|
+
const text = error instanceof Error ? error.message : String(error);
|
|
596
|
+
return { content: [{ type: "text", text }], isError: true };
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
function handleMessage(message, options) {
|
|
600
|
+
const cwd = options.cwd ?? process.cwd();
|
|
601
|
+
const reply = (result) => ({ jsonrpc: "2.0", id: message.id ?? null, result });
|
|
602
|
+
if (message.id === void 0) return null;
|
|
603
|
+
switch (message.method) {
|
|
604
|
+
case "initialize":
|
|
605
|
+
return reply({
|
|
606
|
+
protocolVersion: str(message.params?.protocolVersion) ?? PROTOCOL_VERSION,
|
|
607
|
+
capabilities: { tools: {} },
|
|
608
|
+
serverInfo: { name: "ansh", version: options.version },
|
|
609
|
+
instructions: "Ansh is a UI and animation library for React, Next.js, Vue and Nuxt. Call ansh_setup before writing Ansh code in a project, and ansh_info for an effect\u2019s exact options."
|
|
610
|
+
});
|
|
611
|
+
case "ping":
|
|
612
|
+
return reply({});
|
|
613
|
+
case "tools/list":
|
|
614
|
+
return reply({
|
|
615
|
+
tools: TOOLS.map(({ name, description, inputSchema }) => ({
|
|
616
|
+
name,
|
|
617
|
+
description,
|
|
618
|
+
inputSchema
|
|
619
|
+
}))
|
|
620
|
+
});
|
|
621
|
+
case "tools/call":
|
|
622
|
+
return reply(callTool(message.params, cwd));
|
|
623
|
+
default:
|
|
624
|
+
return {
|
|
625
|
+
jsonrpc: "2.0",
|
|
626
|
+
id: message.id,
|
|
627
|
+
error: { code: -32601, message: `Method not found: ${message.method}` }
|
|
628
|
+
};
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
function serveMcp(version, input = process.stdin, output = process.stdout) {
|
|
632
|
+
const send = (payload) => output.write(`${JSON.stringify(payload)}
|
|
633
|
+
`);
|
|
634
|
+
createInterface({ input }).on("line", (line) => {
|
|
635
|
+
if (!line.trim()) return;
|
|
636
|
+
let message;
|
|
637
|
+
try {
|
|
638
|
+
message = JSON.parse(line);
|
|
639
|
+
} catch {
|
|
640
|
+
send({ jsonrpc: "2.0", id: null, error: { code: -32700, message: "Parse error" } });
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
const response = handleMessage(message, { version });
|
|
644
|
+
if (response) send(response);
|
|
645
|
+
});
|
|
646
|
+
console.error(`ansh mcp v${version} ready on stdio`);
|
|
647
|
+
}
|
|
397
648
|
|
|
398
649
|
// src/cli.ts
|
|
399
650
|
var useColor = process.stdout.isTTY && !process.env.NO_COLOR;
|
|
@@ -413,14 +664,19 @@ ${bold("USAGE")}
|
|
|
413
664
|
ansh <command> [options]
|
|
414
665
|
|
|
415
666
|
${bold("COMMANDS")}
|
|
667
|
+
${cyan("init")} [--brand <color>] Set up Ansh in this project
|
|
416
668
|
${cyan("list")} [--effects|--behaviours] List the component catalogue
|
|
417
669
|
${cyan("info")} <name> Show a component's full option surface
|
|
418
670
|
${cyan("add")} <name> [--as <Name>] Generate a local wrapper component
|
|
419
671
|
${cyan("theme")} <color> [--out <file>] Generate a token override stylesheet
|
|
420
672
|
${cyan("palette")} [colors...] Generate or inspect a palette
|
|
421
673
|
${cyan("doctor")} Report the detected project setup
|
|
674
|
+
${cyan("mcp")} Run as an MCP server for AI coding agents
|
|
422
675
|
|
|
423
676
|
${bold("EXAMPLES")}
|
|
677
|
+
${dim("# Set up a project, with a theme from your brand colour")}
|
|
678
|
+
ansh init --brand '#0275f4'
|
|
679
|
+
|
|
424
680
|
${dim("# See what is available")}
|
|
425
681
|
ansh list
|
|
426
682
|
|
|
@@ -442,6 +698,41 @@ function fail(message, hint) {
|
|
|
442
698
|
if (hint) console.error(`${dim("hint")} ${hint}`);
|
|
443
699
|
process.exit(1);
|
|
444
700
|
}
|
|
701
|
+
function commandInit(argv) {
|
|
702
|
+
const { values } = parseArgs({
|
|
703
|
+
args: argv,
|
|
704
|
+
options: {
|
|
705
|
+
brand: { type: "string" },
|
|
706
|
+
dir: { type: "string" },
|
|
707
|
+
force: { type: "boolean", default: false },
|
|
708
|
+
"dry-run": { type: "boolean", default: false }
|
|
709
|
+
}
|
|
710
|
+
});
|
|
711
|
+
try {
|
|
712
|
+
const result = initProject({
|
|
713
|
+
brand: values.brand,
|
|
714
|
+
componentsDir: values.dir,
|
|
715
|
+
force: values.force,
|
|
716
|
+
dryRun: values["dry-run"]
|
|
717
|
+
});
|
|
718
|
+
console.log(`
|
|
719
|
+
${bold("ansh init")} ${dim(`${result.config.framework} project detected`)}
|
|
720
|
+
`);
|
|
721
|
+
for (const path of result.written) console.log(`${green("created")} ${path}`);
|
|
722
|
+
if (result.install) console.log(`
|
|
723
|
+
${yellow("1")} Install:
|
|
724
|
+
${result.install}`);
|
|
725
|
+
console.log(`
|
|
726
|
+
${yellow(result.install ? "2" : "1")} Add to ${result.setup.file}:
|
|
727
|
+
`);
|
|
728
|
+
console.log(result.setup.code.replace(/^/gm, " "));
|
|
729
|
+
console.log(`
|
|
730
|
+
${dim(`Then try: ansh add aurora --as HeroBackground`)}
|
|
731
|
+
`);
|
|
732
|
+
} catch (error) {
|
|
733
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
734
|
+
}
|
|
735
|
+
}
|
|
445
736
|
function commandList(argv) {
|
|
446
737
|
const { values } = parseArgs({
|
|
447
738
|
args: argv,
|
|
@@ -645,9 +936,8 @@ ${bold("PROJECT")}
|
|
|
645
936
|
console.log(` import alias ${project.alias ?? dim("none")}`);
|
|
646
937
|
console.log(` ansh ${project.installed ? green("installed") : yellow("not installed")}`);
|
|
647
938
|
if (!project.installed) {
|
|
648
|
-
const packages = project.framework === "nuxt" ? ["@ansh-ui/nuxt"] : project.framework === "vue" ? ["@ansh-ui/vue", "@ansh-ui/tokens"] : ["@ansh-ui/react", "@ansh-ui/tokens"];
|
|
649
939
|
console.log(`
|
|
650
|
-
${yellow("next")} ${installCommand(project,
|
|
940
|
+
${yellow("next")} ${installCommand(project, packagesFor(project.framework))}
|
|
651
941
|
`);
|
|
652
942
|
} else {
|
|
653
943
|
console.log("");
|
|
@@ -673,6 +963,8 @@ function swatch(hex) {
|
|
|
673
963
|
function main() {
|
|
674
964
|
const [command, ...rest] = process.argv.slice(2);
|
|
675
965
|
switch (command) {
|
|
966
|
+
case "init":
|
|
967
|
+
return commandInit(rest);
|
|
676
968
|
case "list":
|
|
677
969
|
case "ls":
|
|
678
970
|
return commandList(rest);
|
|
@@ -687,6 +979,8 @@ function main() {
|
|
|
687
979
|
return commandPalette(rest);
|
|
688
980
|
case "doctor":
|
|
689
981
|
return commandDoctor();
|
|
982
|
+
case "mcp":
|
|
983
|
+
return serveMcp(VERSION);
|
|
690
984
|
case "--version":
|
|
691
985
|
case "-v":
|
|
692
986
|
console.log(VERSION);
|