@nebutra/ui 0.2.0 → 0.2.2
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/LICENSE +21 -676
- package/README.md +81 -86
- package/dist/agent/index.d.ts +119 -0
- package/dist/agent/index.js +135 -0
- package/dist/agent/index.js.map +1 -0
- package/dist/canonical-CrO71UPO.d.ts +4058 -0
- package/dist/components/index.d.ts +51 -56
- package/dist/components/index.js +1467 -1160
- package/dist/components/index.js.map +1 -1
- package/dist/hooks/index.d.ts +62 -15
- package/dist/hooks/index.js +56 -34
- package/dist/hooks/index.js.map +1 -1
- package/dist/index-CKLIGfiU.d.ts +19 -0
- package/dist/index-Dg4jRJV2.d.ts +57 -0
- package/dist/layout/index.d.ts +5 -1
- package/dist/layout/index.js +427 -533
- package/dist/layout/index.js.map +1 -1
- package/dist/layouts/index.js +11 -9
- package/dist/layouts/index.js.map +1 -1
- package/dist/patterns/index.d.ts +128 -2
- package/dist/patterns/index.js +1478 -786
- package/dist/patterns/index.js.map +1 -1
- package/dist/primitives/canonical.d.ts +25 -0
- package/dist/primitives/canonical.js +11903 -0
- package/dist/primitives/canonical.js.map +1 -0
- package/dist/primitives/index.d.ts +577 -3545
- package/dist/primitives/index.js +8002 -6159
- package/dist/primitives/index.js.map +1 -1
- package/dist/shared/animation/css/index.d.ts +1 -0
- package/dist/shared/animation/css/index.js +14 -0
- package/dist/shared/animation/css/index.js.map +1 -0
- package/dist/shared/animation/index.d.ts +6 -0
- package/dist/shared/animation/index.js +272 -0
- package/dist/shared/animation/index.js.map +1 -0
- package/dist/shared/animation/motion/index.d.ts +4 -0
- package/dist/shared/animation/motion/index.js +205 -0
- package/dist/shared/animation/motion/index.js.map +1 -0
- package/dist/styles/brand-override.css +11 -11
- package/dist/tailwind.preset.d.ts +10 -10
- package/dist/tailwind.preset.js +7 -4
- package/dist/tailwind.preset.js.map +1 -1
- package/dist/theme/index.d.ts +2 -2
- package/dist/tokens-EAC6uPem.d.ts +302 -0
- package/dist/typography/fonts.css +31 -12
- package/dist/typography/index.d.ts +27 -27
- package/dist/typography/index.js +24 -4
- package/dist/typography/index.js.map +1 -1
- package/dist/utils/index.d.ts +36 -14
- package/dist/utils/index.js +44 -10
- package/dist/utils/index.js.map +1 -1
- package/package.json +38 -12
- package/src/styles/brand-override.css +61 -0
- package/src/styles/preset.css +29 -0
- package/src/styles/sources.css +27 -0
- package/src/typography/fonts.css +129 -0
package/README.md
CHANGED
|
@@ -1,22 +1,11 @@
|
|
|
1
1
|
# @nebutra/ui
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Nebutra design-system UI primitives, layouts, patterns, hooks, typography, and
|
|
4
|
+
token-integrated React components.
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
| ---------------- | ----------------------------------------------------------------------------- |
|
|
10
|
-
| `@lobehub/ui` | Chat bubbles, streaming text, model selector, chat input, markdown renderer |
|
|
11
|
-
| `@lobehub/icons` | AI provider icons: OpenAI, Anthropic, Claude, Gemini, Mistral, DeepSeek, etc. |
|
|
12
|
-
| `antd` | Back-office admin tables / forms if needed in enterprise tier |
|
|
13
|
-
|
|
14
|
-
Usage in `apps/web`:
|
|
15
|
-
|
|
16
|
-
```tsx
|
|
17
|
-
import { ChatList, ChatInputArea } from "@nebutra/ui/components";
|
|
18
|
-
import { Anthropic, Claude } from "@nebutra/ui/icons";
|
|
19
|
-
```
|
|
6
|
+
This package is the shared UI layer for Nebutra products. It combines Nebutra
|
|
7
|
+
runtime tokens with Nebutra primitives, selected compatibility bridges, icons, and
|
|
8
|
+
product-ready SaaS composition patterns.
|
|
20
9
|
|
|
21
10
|
## Installation
|
|
22
11
|
|
|
@@ -24,117 +13,123 @@ import { Anthropic, Claude } from "@nebutra/ui/icons";
|
|
|
24
13
|
pnpm add @nebutra/ui
|
|
25
14
|
```
|
|
26
15
|
|
|
16
|
+
For app rendering, load Nebutra runtime tokens once at the application root:
|
|
17
|
+
|
|
18
|
+
```tsx
|
|
19
|
+
import "@nebutra/tokens/styles.css";
|
|
20
|
+
```
|
|
21
|
+
|
|
27
22
|
## Usage
|
|
28
23
|
|
|
29
|
-
### Theme
|
|
24
|
+
### Theme Bridge
|
|
30
25
|
|
|
31
|
-
|
|
26
|
+
Use `NebutraThemeProvider` when consuming compatibility surfaces that depend on Lobe UI or Ant Design:
|
|
32
27
|
|
|
33
28
|
```tsx
|
|
34
29
|
import { NebutraThemeProvider } from "@nebutra/ui";
|
|
30
|
+
import { Button } from "@nebutra/ui/components";
|
|
35
31
|
|
|
36
|
-
function App() {
|
|
32
|
+
export function App() {
|
|
37
33
|
return (
|
|
38
34
|
<NebutraThemeProvider appearance="auto">
|
|
39
|
-
<
|
|
35
|
+
<Button type="primary">Create workspace</Button>
|
|
40
36
|
</NebutraThemeProvider>
|
|
41
37
|
);
|
|
42
38
|
}
|
|
43
39
|
```
|
|
44
40
|
|
|
45
|
-
###
|
|
41
|
+
### Product Layouts
|
|
46
42
|
|
|
47
43
|
```tsx
|
|
48
|
-
import {
|
|
44
|
+
import { AppShell, EmptyState, PageHeader } from "@nebutra/ui/layout";
|
|
49
45
|
|
|
50
|
-
|
|
51
|
-
|
|
46
|
+
export function DashboardEmptyState() {
|
|
47
|
+
return (
|
|
48
|
+
<AppShell>
|
|
49
|
+
<PageHeader title="Projects" description="Manage reusable product surfaces." />
|
|
50
|
+
<EmptyState title="No projects yet" description="Create one to start shipping." />
|
|
51
|
+
</AppShell>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
52
54
|
```
|
|
53
55
|
|
|
54
|
-
###
|
|
56
|
+
### Components And Patterns
|
|
55
57
|
|
|
56
58
|
```tsx
|
|
57
|
-
import {
|
|
58
|
-
|
|
59
|
-
// In a model selector
|
|
60
|
-
<OpenAI size={24} />
|
|
61
|
-
<Anthropic size={24} />
|
|
62
|
-
<Google size={24} />
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
### All Lobe Icons
|
|
59
|
+
import { AnimateIn, Button } from "@nebutra/ui/components";
|
|
60
|
+
import { DashboardPanel } from "@nebutra/ui/patterns";
|
|
66
61
|
|
|
67
|
-
|
|
68
|
-
|
|
62
|
+
export function WorkspaceSurface() {
|
|
63
|
+
return (
|
|
64
|
+
<DashboardPanel
|
|
65
|
+
title="Workspace"
|
|
66
|
+
description="Reusable command surfaces for product teams."
|
|
67
|
+
>
|
|
68
|
+
<AnimateIn preset="fadeUp">
|
|
69
|
+
<Button type="primary">Open command center</Button>
|
|
70
|
+
</AnimateIn>
|
|
71
|
+
</DashboardPanel>
|
|
72
|
+
);
|
|
73
|
+
}
|
|
69
74
|
```
|
|
70
75
|
|
|
71
|
-
###
|
|
76
|
+
### Icons
|
|
72
77
|
|
|
73
78
|
```tsx
|
|
74
|
-
import {
|
|
79
|
+
import { OpenAI, Search, Settings } from "@nebutra/ui/icons";
|
|
75
80
|
|
|
81
|
+
<OpenAI size={24} />;
|
|
82
|
+
<Search size={20} />;
|
|
76
83
|
<Settings size={20} />;
|
|
77
84
|
```
|
|
78
85
|
|
|
79
|
-
|
|
86
|
+
## Exports
|
|
87
|
+
|
|
88
|
+
| Path | Description |
|
|
89
|
+
| --- | --- |
|
|
90
|
+
| `@nebutra/ui` | Theme bridge plus selected common icon exports |
|
|
91
|
+
| `@nebutra/ui/components` | Nebutra components, chat input/list surfaces, animation helpers, AI prompt box, node graph canvas, and product widgets |
|
|
92
|
+
| `@nebutra/ui/layout` | App shell, page header, status, section, and empty/loading/error states |
|
|
93
|
+
| `@nebutra/ui/layouts` | Section container, themed section, and bento grid layouts |
|
|
94
|
+
| `@nebutra/ui/icons` | Lobe, Lucide, and Nebutra icon exports |
|
|
95
|
+
| `@nebutra/ui/theme` | `NebutraThemeProvider` and compatibility theme bridge types |
|
|
96
|
+
| `@nebutra/ui/primitives` | Low-level UI primitives and visual building blocks |
|
|
97
|
+
| `@nebutra/ui/primitives/canonical` | Canonical primitive token exports |
|
|
98
|
+
| `@nebutra/ui/patterns` | SaaS dashboard, command, card, terminal, QA, sidebar, and workspace patterns |
|
|
99
|
+
| `@nebutra/ui/typography` | Font utilities and typography tokens |
|
|
100
|
+
| `@nebutra/ui/typography/fonts.css` | Font-face CSS for Nebutra typography |
|
|
101
|
+
| `@nebutra/ui/hooks` | Responsive, focus, undo, hotkey, media query, and interaction hooks |
|
|
102
|
+
| `@nebutra/ui/utils` | Shared utilities such as `cn` |
|
|
103
|
+
| `@nebutra/ui/tailwind.preset` | Tailwind preset integration |
|
|
104
|
+
|
|
105
|
+
## Token Architecture
|
|
80
106
|
|
|
81
|
-
Runtime design tokens live in `@nebutra/tokens/styles.css`
|
|
82
|
-
Use Tailwind classes or `var()` in
|
|
107
|
+
Runtime design tokens live in `@nebutra/tokens/styles.css` as CSS variables.
|
|
108
|
+
Use Tailwind classes backed by those variables, or `var()` directly in places
|
|
109
|
+
where Tailwind is not available.
|
|
83
110
|
|
|
84
111
|
```tsx
|
|
85
|
-
// Tailwind classes (preferred)
|
|
86
112
|
<div className="bg-primary text-foreground border-border" />
|
|
87
|
-
|
|
88
|
-
// CSS variables (when Tailwind isn't available)
|
|
89
113
|
<div style={{ color: "var(--color-primary)" }} />
|
|
90
114
|
```
|
|
91
115
|
|
|
92
|
-
|
|
93
|
-
> but these are an internal Lobe UI bridge and are deprecated for app use.
|
|
116
|
+
Source-of-truth flow:
|
|
94
117
|
|
|
95
|
-
|
|
118
|
+
```text
|
|
119
|
+
@nebutra/brand -> brand primitives
|
|
120
|
+
@nebutra/tokens -> runtime CSS variables and theme provider primitives
|
|
121
|
+
@nebutra/ui -> token-consuming components, layouts, hooks, and patterns
|
|
122
|
+
```
|
|
96
123
|
|
|
97
|
-
|
|
98
|
-
| ------------------------ | ------------------------------------------------------- |
|
|
99
|
-
| `@nebutra/ui` | Main exports (theme provider + common components/icons) |
|
|
100
|
-
| `@nebutra/ui/components` | All Lobe UI components + AnimateIn |
|
|
101
|
-
| `@nebutra/ui/layout` | Layout components (PageHeader, EmptyState, etc.) |
|
|
102
|
-
| `@nebutra/ui/icons` | All icons (Lobe + Lucide) |
|
|
103
|
-
| `@nebutra/ui/theme` | Lobe UI theme provider (NebutraThemeProvider) |
|
|
104
|
-
| `@nebutra/ui/primitives` | Design primitive tokens (spacing, typography, etc.) |
|
|
105
|
-
| `@nebutra/ui/typography` | Typography system (font families, type styles) |
|
|
106
|
-
| `@nebutra/ui/utils` | Utilities (cn, etc.) |
|
|
107
|
-
|
|
108
|
-
## Available AI Icons
|
|
109
|
-
|
|
110
|
-
- **OpenAI**: `OpenAI`, `ChatGPT`, `Gpt`
|
|
111
|
-
- **Anthropic**: `Anthropic`, `Claude`
|
|
112
|
-
- **Google**: `Google`, `Gemini`, `GoogleColor`
|
|
113
|
-
- **Meta**: `Meta`, `Llama`
|
|
114
|
-
- **Mistral**: `Mistral`
|
|
115
|
-
- **Cohere**: `Cohere`
|
|
116
|
-
- **Hugging Face**: `HuggingFace`
|
|
117
|
-
- **Perplexity**: `Perplexity`
|
|
118
|
-
- **Groq**: `Groq`
|
|
119
|
-
- **DeepSeek**: `DeepSeek`
|
|
120
|
-
- **Qwen/Tongyi**: `Qwen`, `Tongyi`
|
|
121
|
-
- **Baidu/Wenxin**: `Baidu`, `Wenxin`
|
|
122
|
-
- **Zhipu**: `Zhipu`
|
|
123
|
-
- **Moonshot**: `Moonshot`
|
|
124
|
-
- **Yi**: `Yi`
|
|
125
|
-
- And many more from `@lobehub/icons`
|
|
124
|
+
## Repository Model
|
|
126
125
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
@nebutra/brand → Brand primitives (color definitions, motion language)
|
|
131
|
-
@nebutra/tokens → Runtime CSS variables (light/dark, 12-step scales) ★ SOURCE OF TRUTH
|
|
132
|
-
@nebutra/theme → Multi-theme presets (6 oklch variants for SaaS)
|
|
133
|
-
@nebutra/ui → Component library (consumes tokens via CSS variables)
|
|
134
|
-
```
|
|
126
|
+
The canonical source for this package lives in the Nebutra Sailor monorepo at
|
|
127
|
+
`packages/design/ui`. Public subrepo mirrors are generated from that source for
|
|
128
|
+
discovery, standalone cloning, and contribution intake.
|
|
135
129
|
|
|
136
130
|
## License
|
|
137
131
|
|
|
138
|
-
|
|
132
|
+
MIT
|
|
139
133
|
|
|
140
|
-
|
|
134
|
+
AI provider and third-party icons may be subject to their respective trademark
|
|
135
|
+
guidelines.
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
type AgentDocsStatus = "stable" | "beta" | "deprecated" | "experimental";
|
|
2
|
+
type AgentDocsMaturity = "experimental" | "beta" | "stable" | "canonical";
|
|
3
|
+
type AgentDocsLayer = "foundation" | "primitive" | "composition" | "pattern" | "registry" | "api" | "guide";
|
|
4
|
+
type AgentDocsSubstrate = "native" | "custom" | "mixed";
|
|
5
|
+
interface AgentComponentSummary {
|
|
6
|
+
name: string;
|
|
7
|
+
title: string;
|
|
8
|
+
description: string;
|
|
9
|
+
status: AgentDocsStatus;
|
|
10
|
+
maturity: AgentDocsMaturity;
|
|
11
|
+
layer: AgentDocsLayer;
|
|
12
|
+
source: string;
|
|
13
|
+
href: string;
|
|
14
|
+
tags: string[];
|
|
15
|
+
}
|
|
16
|
+
interface AgentManifest {
|
|
17
|
+
$schema: string;
|
|
18
|
+
name: "nebutra-ui-agent";
|
|
19
|
+
version: 1;
|
|
20
|
+
generatedAt: string;
|
|
21
|
+
homepage: string;
|
|
22
|
+
registry: string;
|
|
23
|
+
commands: Array<{
|
|
24
|
+
name: "search" | "component" | "validate" | "template" | "migrate";
|
|
25
|
+
status: "available" | "planned";
|
|
26
|
+
description: string;
|
|
27
|
+
json: boolean;
|
|
28
|
+
}>;
|
|
29
|
+
rules: {
|
|
30
|
+
sourceOfTruth: string[];
|
|
31
|
+
importPolicy: string;
|
|
32
|
+
registryPolicy: string;
|
|
33
|
+
tokenPolicy: string;
|
|
34
|
+
};
|
|
35
|
+
components: AgentComponentSummary[];
|
|
36
|
+
}
|
|
37
|
+
interface AgentComponentContract extends AgentComponentSummary {
|
|
38
|
+
package: "@nebutra/ui" | "@nebutra/tokens";
|
|
39
|
+
substrate: AgentDocsSubstrate;
|
|
40
|
+
imports: {
|
|
41
|
+
package: string;
|
|
42
|
+
registry: string;
|
|
43
|
+
};
|
|
44
|
+
dependencies: {
|
|
45
|
+
npm: string[];
|
|
46
|
+
registry: string[];
|
|
47
|
+
};
|
|
48
|
+
files: Array<{
|
|
49
|
+
path: string;
|
|
50
|
+
type: string;
|
|
51
|
+
target?: string;
|
|
52
|
+
}>;
|
|
53
|
+
tokens: string[];
|
|
54
|
+
evidence: {
|
|
55
|
+
source: true;
|
|
56
|
+
docs: boolean;
|
|
57
|
+
storybook: boolean;
|
|
58
|
+
registry: true;
|
|
59
|
+
tokens: boolean;
|
|
60
|
+
};
|
|
61
|
+
docs: {
|
|
62
|
+
source?: string;
|
|
63
|
+
routes: string[];
|
|
64
|
+
storybook?: string;
|
|
65
|
+
lastVerified: string;
|
|
66
|
+
};
|
|
67
|
+
usage: {
|
|
68
|
+
recommended: string;
|
|
69
|
+
antiPatterns: string[];
|
|
70
|
+
};
|
|
71
|
+
migration: {
|
|
72
|
+
requiredForBreakingChanges: true;
|
|
73
|
+
codemods: string[];
|
|
74
|
+
hints: string[];
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
interface AgentContractPaths {
|
|
78
|
+
manifestPath: string;
|
|
79
|
+
componentDir: string;
|
|
80
|
+
}
|
|
81
|
+
interface LoadAgentContractOptions {
|
|
82
|
+
root?: string;
|
|
83
|
+
manifestPath?: string;
|
|
84
|
+
}
|
|
85
|
+
interface SearchAgentComponentsOptions {
|
|
86
|
+
limit?: number;
|
|
87
|
+
offset?: number;
|
|
88
|
+
tag?: string;
|
|
89
|
+
status?: AgentDocsStatus;
|
|
90
|
+
maturity?: AgentDocsMaturity;
|
|
91
|
+
}
|
|
92
|
+
interface SearchAgentComponentsResult {
|
|
93
|
+
query: string;
|
|
94
|
+
total: number;
|
|
95
|
+
count: number;
|
|
96
|
+
offset: number;
|
|
97
|
+
limit: number;
|
|
98
|
+
hasMore: boolean;
|
|
99
|
+
nextOffset?: number;
|
|
100
|
+
items: AgentComponentSummary[];
|
|
101
|
+
}
|
|
102
|
+
interface AgentValidationResult {
|
|
103
|
+
name: string;
|
|
104
|
+
valid: boolean;
|
|
105
|
+
errors: string[];
|
|
106
|
+
warnings: string[];
|
|
107
|
+
evidence: AgentComponentContract["evidence"];
|
|
108
|
+
migration: AgentComponentContract["migration"];
|
|
109
|
+
}
|
|
110
|
+
declare function resolveAgentContractPaths(options?: LoadAgentContractOptions): AgentContractPaths;
|
|
111
|
+
declare function normalizeAgentComponentName(name: string): string;
|
|
112
|
+
declare function assertSafeAgentComponentName(name: string): string;
|
|
113
|
+
declare function loadAgentManifest(options?: LoadAgentContractOptions): AgentManifest;
|
|
114
|
+
declare function loadAgentComponentContract(name: string, options?: LoadAgentContractOptions): AgentComponentContract;
|
|
115
|
+
declare function findAgentComponent(manifest: AgentManifest, name: string): AgentComponentSummary | undefined;
|
|
116
|
+
declare function searchAgentComponents(manifest: AgentManifest, query?: string, options?: SearchAgentComponentsOptions): SearchAgentComponentsResult;
|
|
117
|
+
declare function validateAgentComponent(contract: AgentComponentContract): AgentValidationResult;
|
|
118
|
+
|
|
119
|
+
export { type AgentComponentContract, type AgentComponentSummary, type AgentContractPaths, type AgentDocsLayer, type AgentDocsMaturity, type AgentDocsStatus, type AgentDocsSubstrate, type AgentManifest, type AgentValidationResult, type LoadAgentContractOptions, type SearchAgentComponentsOptions, type SearchAgentComponentsResult, assertSafeAgentComponentName, findAgentComponent, loadAgentComponentContract, loadAgentManifest, normalizeAgentComponentName, resolveAgentContractPaths, searchAgentComponents, validateAgentComponent };
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { resolve, join } from 'path';
|
|
3
|
+
|
|
4
|
+
// src/agent/index.ts
|
|
5
|
+
function resolveAgentContractPaths(options = {}) {
|
|
6
|
+
const manifestPath = options.manifestPath ? resolve(options.manifestPath) : join(
|
|
7
|
+
resolve(options.root ?? process.cwd()),
|
|
8
|
+
"apps",
|
|
9
|
+
"design-docs",
|
|
10
|
+
"public",
|
|
11
|
+
"agent-manifest.json"
|
|
12
|
+
);
|
|
13
|
+
return {
|
|
14
|
+
manifestPath,
|
|
15
|
+
componentDir: join(manifestPath, "..", "agent", "components")
|
|
16
|
+
};
|
|
17
|
+
}
|
|
18
|
+
function normalizeAgentComponentName(name) {
|
|
19
|
+
return name.trim().toLowerCase();
|
|
20
|
+
}
|
|
21
|
+
function assertSafeAgentComponentName(name) {
|
|
22
|
+
const normalized = normalizeAgentComponentName(name);
|
|
23
|
+
if (!/^[a-z0-9-]+$/.test(normalized)) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Invalid UI component name "${name}". Use a registry kebab-case id such as "button".`
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
return normalized;
|
|
29
|
+
}
|
|
30
|
+
function loadAgentManifest(options = {}) {
|
|
31
|
+
const { manifestPath } = resolveAgentContractPaths(options);
|
|
32
|
+
if (!existsSync(manifestPath)) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Missing Nebutra UI agent manifest at ${manifestPath}. Run pnpm --filter @nebutra/design-docs build:registry.`
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
return JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
38
|
+
}
|
|
39
|
+
function loadAgentComponentContract(name, options = {}) {
|
|
40
|
+
const safeName = assertSafeAgentComponentName(name);
|
|
41
|
+
const { componentDir } = resolveAgentContractPaths(options);
|
|
42
|
+
const componentPath = join(componentDir, `${safeName}.json`);
|
|
43
|
+
if (!existsSync(componentPath)) {
|
|
44
|
+
throw new Error(`UI component contract not found: ${safeName}`);
|
|
45
|
+
}
|
|
46
|
+
return JSON.parse(readFileSync(componentPath, "utf8"));
|
|
47
|
+
}
|
|
48
|
+
function findAgentComponent(manifest, name) {
|
|
49
|
+
const safeName = normalizeAgentComponentName(name);
|
|
50
|
+
return manifest.components.find((component) => component.name === safeName);
|
|
51
|
+
}
|
|
52
|
+
function searchAgentComponents(manifest, query = "", options = {}) {
|
|
53
|
+
const normalizedQuery = query.trim().toLowerCase();
|
|
54
|
+
const limit = clampInteger(options.limit ?? 20, 1, 100);
|
|
55
|
+
const offset = clampInteger(options.offset ?? 0, 0, Number.MAX_SAFE_INTEGER);
|
|
56
|
+
const tag = options.tag?.trim().toLowerCase();
|
|
57
|
+
const scored = manifest.components.filter((component) => {
|
|
58
|
+
if (options.status && component.status !== options.status) return false;
|
|
59
|
+
if (options.maturity && component.maturity !== options.maturity) return false;
|
|
60
|
+
if (tag && !component.tags.includes(tag)) return false;
|
|
61
|
+
if (!normalizedQuery) return true;
|
|
62
|
+
return searchableText(component).includes(normalizedQuery);
|
|
63
|
+
}).map((component) => ({ component, score: scoreComponent(component, normalizedQuery) })).sort((a, b) => b.score - a.score || a.component.name.localeCompare(b.component.name)).map(({ component }) => component);
|
|
64
|
+
const items = scored.slice(offset, offset + limit);
|
|
65
|
+
const nextOffset = offset + items.length;
|
|
66
|
+
return {
|
|
67
|
+
query,
|
|
68
|
+
total: scored.length,
|
|
69
|
+
count: items.length,
|
|
70
|
+
offset,
|
|
71
|
+
limit,
|
|
72
|
+
hasMore: nextOffset < scored.length,
|
|
73
|
+
...nextOffset < scored.length && { nextOffset },
|
|
74
|
+
items
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function validateAgentComponent(contract) {
|
|
78
|
+
const errors = [];
|
|
79
|
+
const warnings = [];
|
|
80
|
+
if (!contract.evidence.source) errors.push("Missing source evidence.");
|
|
81
|
+
if (!contract.evidence.docs) errors.push("Missing docs evidence.");
|
|
82
|
+
if (!contract.evidence.registry) errors.push("Missing registry evidence.");
|
|
83
|
+
if ((contract.status === "stable" || contract.maturity === "canonical") && !contract.evidence.storybook) {
|
|
84
|
+
errors.push("Stable/canonical components must have Storybook evidence.");
|
|
85
|
+
}
|
|
86
|
+
if (!contract.evidence.tokens && contract.package === "@nebutra/ui") {
|
|
87
|
+
warnings.push(
|
|
88
|
+
"No token evidence was detected; confirm this primitive intentionally has no CSS variables."
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
if (contract.migration.requiredForBreakingChanges && contract.migration.hints.length === 0) {
|
|
92
|
+
warnings.push(
|
|
93
|
+
"Breaking-change migration policy is enabled but no migration hints are present."
|
|
94
|
+
);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
name: contract.name,
|
|
98
|
+
valid: errors.length === 0,
|
|
99
|
+
errors,
|
|
100
|
+
warnings,
|
|
101
|
+
evidence: contract.evidence,
|
|
102
|
+
migration: contract.migration
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
function searchableText(component) {
|
|
106
|
+
return [
|
|
107
|
+
component.name,
|
|
108
|
+
component.title,
|
|
109
|
+
component.description,
|
|
110
|
+
component.status,
|
|
111
|
+
component.maturity,
|
|
112
|
+
component.layer,
|
|
113
|
+
component.source,
|
|
114
|
+
...component.tags
|
|
115
|
+
].join(" ").toLowerCase();
|
|
116
|
+
}
|
|
117
|
+
function scoreComponent(component, query) {
|
|
118
|
+
if (!query) return component.maturity === "canonical" ? 20 : 10;
|
|
119
|
+
if (component.name === query) return 100;
|
|
120
|
+
if (component.name.startsWith(query)) return 80;
|
|
121
|
+
if (component.title.toLowerCase().startsWith(query)) return 70;
|
|
122
|
+
if (component.tags.includes(query)) return 60;
|
|
123
|
+
if (component.name.includes(query)) return 50;
|
|
124
|
+
if (component.title.toLowerCase().includes(query)) return 40;
|
|
125
|
+
if (component.description.toLowerCase().includes(query)) return 20;
|
|
126
|
+
return 1;
|
|
127
|
+
}
|
|
128
|
+
function clampInteger(value, min, max) {
|
|
129
|
+
if (!Number.isFinite(value)) return min;
|
|
130
|
+
return Math.min(Math.max(Math.trunc(value), min), max);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
export { assertSafeAgentComponentName, findAgentComponent, loadAgentComponentContract, loadAgentManifest, normalizeAgentComponentName, resolveAgentContractPaths, searchAgentComponents, validateAgentComponent };
|
|
134
|
+
//# sourceMappingURL=index.js.map
|
|
135
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../src/agent/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import { existsSync, readFileSync } from \"node:fs\";\nimport { join, resolve } from \"node:path\";\n\nexport type AgentDocsStatus = \"stable\" | \"beta\" | \"deprecated\" | \"experimental\";\nexport type AgentDocsMaturity = \"experimental\" | \"beta\" | \"stable\" | \"canonical\";\nexport type AgentDocsLayer =\n | \"foundation\"\n | \"primitive\"\n | \"composition\"\n | \"pattern\"\n | \"registry\"\n | \"api\"\n | \"guide\";\nexport type AgentDocsSubstrate = \"native\" | \"custom\" | \"mixed\";\n\nexport interface AgentComponentSummary {\n name: string;\n title: string;\n description: string;\n status: AgentDocsStatus;\n maturity: AgentDocsMaturity;\n layer: AgentDocsLayer;\n source: string;\n href: string;\n tags: string[];\n}\n\nexport interface AgentManifest {\n $schema: string;\n name: \"nebutra-ui-agent\";\n version: 1;\n generatedAt: string;\n homepage: string;\n registry: string;\n commands: Array<{\n name: \"search\" | \"component\" | \"validate\" | \"template\" | \"migrate\";\n status: \"available\" | \"planned\";\n description: string;\n json: boolean;\n }>;\n rules: {\n sourceOfTruth: string[];\n importPolicy: string;\n registryPolicy: string;\n tokenPolicy: string;\n };\n components: AgentComponentSummary[];\n}\n\nexport interface AgentComponentContract extends AgentComponentSummary {\n package: \"@nebutra/ui\" | \"@nebutra/tokens\";\n substrate: AgentDocsSubstrate;\n imports: {\n package: string;\n registry: string;\n };\n dependencies: {\n npm: string[];\n registry: string[];\n };\n files: Array<{\n path: string;\n type: string;\n target?: string;\n }>;\n tokens: string[];\n evidence: {\n source: true;\n docs: boolean;\n storybook: boolean;\n registry: true;\n tokens: boolean;\n };\n docs: {\n source?: string;\n routes: string[];\n storybook?: string;\n lastVerified: string;\n };\n usage: {\n recommended: string;\n antiPatterns: string[];\n };\n migration: {\n requiredForBreakingChanges: true;\n codemods: string[];\n hints: string[];\n };\n}\n\nexport interface AgentContractPaths {\n manifestPath: string;\n componentDir: string;\n}\n\nexport interface LoadAgentContractOptions {\n root?: string;\n manifestPath?: string;\n}\n\nexport interface SearchAgentComponentsOptions {\n limit?: number;\n offset?: number;\n tag?: string;\n status?: AgentDocsStatus;\n maturity?: AgentDocsMaturity;\n}\n\nexport interface SearchAgentComponentsResult {\n query: string;\n total: number;\n count: number;\n offset: number;\n limit: number;\n hasMore: boolean;\n nextOffset?: number;\n items: AgentComponentSummary[];\n}\n\nexport interface AgentValidationResult {\n name: string;\n valid: boolean;\n errors: string[];\n warnings: string[];\n evidence: AgentComponentContract[\"evidence\"];\n migration: AgentComponentContract[\"migration\"];\n}\n\nexport function resolveAgentContractPaths(\n options: LoadAgentContractOptions = {},\n): AgentContractPaths {\n const manifestPath = options.manifestPath\n ? resolve(options.manifestPath)\n : join(\n resolve(options.root ?? process.cwd()),\n \"apps\",\n \"design-docs\",\n \"public\",\n \"agent-manifest.json\",\n );\n\n return {\n manifestPath,\n componentDir: join(manifestPath, \"..\", \"agent\", \"components\"),\n };\n}\n\nexport function normalizeAgentComponentName(name: string): string {\n return name.trim().toLowerCase();\n}\n\nexport function assertSafeAgentComponentName(name: string): string {\n const normalized = normalizeAgentComponentName(name);\n if (!/^[a-z0-9-]+$/.test(normalized)) {\n throw new Error(\n `Invalid UI component name \"${name}\". Use a registry kebab-case id such as \"button\".`,\n );\n }\n return normalized;\n}\n\nexport function loadAgentManifest(options: LoadAgentContractOptions = {}): AgentManifest {\n const { manifestPath } = resolveAgentContractPaths(options);\n if (!existsSync(manifestPath)) {\n throw new Error(\n `Missing Nebutra UI agent manifest at ${manifestPath}. Run pnpm --filter @nebutra/design-docs build:registry.`,\n );\n }\n return JSON.parse(readFileSync(manifestPath, \"utf8\")) as AgentManifest;\n}\n\nexport function loadAgentComponentContract(\n name: string,\n options: LoadAgentContractOptions = {},\n): AgentComponentContract {\n const safeName = assertSafeAgentComponentName(name);\n const { componentDir } = resolveAgentContractPaths(options);\n const componentPath = join(componentDir, `${safeName}.json`);\n if (!existsSync(componentPath)) {\n throw new Error(`UI component contract not found: ${safeName}`);\n }\n return JSON.parse(readFileSync(componentPath, \"utf8\")) as AgentComponentContract;\n}\n\nexport function findAgentComponent(\n manifest: AgentManifest,\n name: string,\n): AgentComponentSummary | undefined {\n const safeName = normalizeAgentComponentName(name);\n return manifest.components.find((component) => component.name === safeName);\n}\n\nexport function searchAgentComponents(\n manifest: AgentManifest,\n query = \"\",\n options: SearchAgentComponentsOptions = {},\n): SearchAgentComponentsResult {\n const normalizedQuery = query.trim().toLowerCase();\n const limit = clampInteger(options.limit ?? 20, 1, 100);\n const offset = clampInteger(options.offset ?? 0, 0, Number.MAX_SAFE_INTEGER);\n const tag = options.tag?.trim().toLowerCase();\n\n const scored = manifest.components\n .filter((component) => {\n if (options.status && component.status !== options.status) return false;\n if (options.maturity && component.maturity !== options.maturity) return false;\n if (tag && !component.tags.includes(tag)) return false;\n if (!normalizedQuery) return true;\n\n return searchableText(component).includes(normalizedQuery);\n })\n .map((component) => ({ component, score: scoreComponent(component, normalizedQuery) }))\n .sort((a, b) => b.score - a.score || a.component.name.localeCompare(b.component.name))\n .map(({ component }) => component);\n\n const items = scored.slice(offset, offset + limit);\n const nextOffset = offset + items.length;\n\n return {\n query,\n total: scored.length,\n count: items.length,\n offset,\n limit,\n hasMore: nextOffset < scored.length,\n ...(nextOffset < scored.length && { nextOffset }),\n items,\n };\n}\n\nexport function validateAgentComponent(contract: AgentComponentContract): AgentValidationResult {\n const errors: string[] = [];\n const warnings: string[] = [];\n\n if (!contract.evidence.source) errors.push(\"Missing source evidence.\");\n if (!contract.evidence.docs) errors.push(\"Missing docs evidence.\");\n if (!contract.evidence.registry) errors.push(\"Missing registry evidence.\");\n\n if (\n (contract.status === \"stable\" || contract.maturity === \"canonical\") &&\n !contract.evidence.storybook\n ) {\n errors.push(\"Stable/canonical components must have Storybook evidence.\");\n }\n\n if (!contract.evidence.tokens && contract.package === \"@nebutra/ui\") {\n warnings.push(\n \"No token evidence was detected; confirm this primitive intentionally has no CSS variables.\",\n );\n }\n\n if (contract.migration.requiredForBreakingChanges && contract.migration.hints.length === 0) {\n warnings.push(\n \"Breaking-change migration policy is enabled but no migration hints are present.\",\n );\n }\n\n return {\n name: contract.name,\n valid: errors.length === 0,\n errors,\n warnings,\n evidence: contract.evidence,\n migration: contract.migration,\n };\n}\n\nfunction searchableText(component: AgentComponentSummary): string {\n return [\n component.name,\n component.title,\n component.description,\n component.status,\n component.maturity,\n component.layer,\n component.source,\n ...component.tags,\n ]\n .join(\" \")\n .toLowerCase();\n}\n\nfunction scoreComponent(component: AgentComponentSummary, query: string): number {\n if (!query) return component.maturity === \"canonical\" ? 20 : 10;\n if (component.name === query) return 100;\n if (component.name.startsWith(query)) return 80;\n if (component.title.toLowerCase().startsWith(query)) return 70;\n if (component.tags.includes(query)) return 60;\n if (component.name.includes(query)) return 50;\n if (component.title.toLowerCase().includes(query)) return 40;\n if (component.description.toLowerCase().includes(query)) return 20;\n return 1;\n}\n\nfunction clampInteger(value: number, min: number, max: number): number {\n if (!Number.isFinite(value)) return min;\n return Math.min(Math.max(Math.trunc(value), min), max);\n}\n"]}
|