@imfusion/web-ui 0.6.4-dev.57.g664ba66a → 0.6.4-dev.58.g88c8fcaa
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/README.md +19 -54
- package/dist/assets/vendors/base-ui.d.ts +2 -3
- package/dist/breakpoints/min-width.d.ts +3 -7
- package/dist/breakpoints/registry.d.ts +3 -9
- package/dist/codegen/gen-breakpoints-css.d.ts +0 -4
- package/dist/codegen/gen-token-css.d.ts +0 -2
- package/dist/components/button/button.d.ts +1 -1
- package/dist/components/callout/callout.d.ts +7 -10
- package/dist/components/card/card.d.ts +2 -4
- package/dist/components/chip/chip.cva.d.ts +2 -4
- package/dist/components/code/code.d.ts +1 -1
- package/dist/components/logo/imfusion/imfusion.d.ts +1 -4
- package/dist/components/logo/logo.d.ts +1 -1
- package/dist/components/navigation-menu/subs/flyout-link.d.ts +3 -5
- package/dist/components/navigation-menu/subs/inline-submenu.d.ts +2 -4
- package/dist/components/navigation-menu/subs/trigger.d.ts +5 -7
- package/dist/components/number-field/number-field.d.ts +2 -3
- package/dist/components/select/select.d.ts +3 -3
- package/dist/components/stack/stack.d.ts +2 -3
- package/dist/components/table/table.d.ts +3 -5
- package/dist/components/tabs/tabs.d.ts +5 -8
- package/dist/components/tooltip/tooltip.d.ts +5 -8
- package/dist/components/typo/typo.d.ts +1 -3
- package/dist/docgen/gen-docgen.utils.d.ts +3 -4
- package/dist/hooks/use-color-scheme.d.ts +5 -11
- package/dist/hooks/use-media-query.d.ts +2 -7
- package/dist/integrations/image-display-options/image-display-options-view.utils.d.ts +0 -1
- package/dist/style.css +1 -1
- package/dist/tokens/apply.d.ts +4 -9
- package/dist/tokens/control-registry.d.ts +3 -6
- package/dist/tokens/types.d.ts +7 -24
- package/dist/tokens/use-token-controls.d.ts +3 -12
- package/dist/types/meta.d.ts +15 -32
- package/dist/vite/readable-css-module-names.d.ts +3 -12
- package/dist/web-ui-cli.js +322 -0
- package/docs/user-guide/AgentTooling.mdx +127 -0
- package/docs/user-guide/BrandAssets.mdx +25 -28
- package/docs/user-guide/GettingStarted.mdx +34 -19
- package/docs/user-guide/HowItsBuilt.mdx +92 -12
- package/docs/user-guide/Tokens.mdx +12 -10
- package/docs/user-guide/UsagePatterns.mdx +64 -21
- package/package.json +6 -5
- package/src/docgen/doc.gen.json +20 -20
- package/src/llms/install-templates/AGENTS.md +4 -4
- package/src/llms/install-templates/hooks/baseline-staleness.sh +9 -2
- package/src/llms/skills/imf-web-ui/SKILL.md +8 -9
- package/src/llms/skills/imf-web-ui-audit/SKILL.md +4 -4
- package/src/llms/skills/imf-web-ui-components/SKILL.md +3 -0
- package/src/llms/skills/imf-web-ui-conventions/templates/AUDIT_CHECKLIST.md +0 -1
- package/src/llms/skills/imf-web-ui-conventions/topics/agent-tooling.md +15 -10
- package/src/llms/skills/imf-web-ui-conventions/topics/git.md +2 -1
- package/src/llms/skills/imf-web-ui-conventions/topics/styling.md +1 -1
- package/src/llms/skills/imf-web-ui-conventions/topics/tooling.md +1 -1
- package/src/llms/skills/imf-web-ui-setup/SKILL.md +1 -1
- package/src/llms/skills/imf-web-ui-update/SKILL.md +15 -13
- package/bin/install.js +0 -446
- package/docs/user-guide/AiAgents.mdx +0 -51
- package/docs/user-guide/Introduction.mdx +0 -21
package/dist/tokens/apply.d.ts
CHANGED
|
@@ -5,12 +5,8 @@ import { TokenOverrides } from './types';
|
|
|
5
5
|
* Pure token-application layer — no React, no router, no DOM ownership beyond the one imperative
|
|
6
6
|
* writer ({@link applyTokenOverrides}). The stateful, persistence-agnostic hook is `useTokenControls`.
|
|
7
7
|
*/
|
|
8
|
-
/** Flatten a possibly scheme-keyed numeric value to the active scheme. */
|
|
9
8
|
export declare function resolveScheme(field: number | Record<ColorScheme, number>, scheme: ColorScheme): number;
|
|
10
|
-
/**
|
|
11
|
-
* A control resolved to the active scheme — the panel-facing view. Slider controls carry numeric
|
|
12
|
-
* bounds; select controls carry options.
|
|
13
|
-
*/
|
|
9
|
+
/** A control resolved to the active scheme — the panel-facing view of a token control. */
|
|
14
10
|
export type ResolvedControl = {
|
|
15
11
|
id: TokenId;
|
|
16
12
|
cssVar: string;
|
|
@@ -47,9 +43,8 @@ export declare function resolve(control: TokenControl, scheme: ColorScheme): Res
|
|
|
47
43
|
*/
|
|
48
44
|
export declare function deriveAll(live: TokenOverrides): TokenOverrides;
|
|
49
45
|
/**
|
|
50
|
-
*
|
|
51
|
-
*
|
|
52
|
-
*
|
|
53
|
-
* a layout effect, and hosts (Storybook, consumers) can call it directly.
|
|
46
|
+
* Reconciles `documentElement`'s inline custom properties to `overrides`: sets present vars, removes
|
|
47
|
+
* every registry var absent from `overrides` (both `-light`/`-dark`), so reset and scheme switches
|
|
48
|
+
* leave nothing stale. This is the one DOM writer.
|
|
54
49
|
*/
|
|
55
50
|
export declare function applyTokenOverrides(overrides: TokenOverrides, root?: HTMLElement): void;
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
import { TokenControl } from './token-registry';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
* Not every token appears here: the semantic font aliases (`font-family-heading` / `-body` / `-ui`
|
|
8
|
-
* / `-code`) are `var()` indirections with no direct control, so they have no entry.
|
|
3
|
+
* Every {@link TokenControl}: a token with a live adjuster, spreading its data from
|
|
4
|
+
* `TOKEN_REGISTRY`. Semantic font aliases (`font-family-heading`/`-body`/`-ui`/`-code`) are
|
|
5
|
+
* `var()` indirections with no direct control, so they have no entry here.
|
|
9
6
|
*/
|
|
10
7
|
export declare const TOKEN_CONTROLS: readonly TokenControl[];
|
package/dist/tokens/types.d.ts
CHANGED
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
import { ColorScheme } from '../types/theme';
|
|
2
|
-
/**
|
|
3
|
-
* A **token** is a CSS custom property under `--imf-ui-` carrying a design value; every token lives
|
|
4
|
-
* in `TOKEN_REGISTRY`. A **TokenControl** is a live adjuster *bound to* a token, not a kind of token —
|
|
5
|
-
* it carries the token's own fields plus the control's `id`. Not every token has a control.
|
|
6
|
-
*
|
|
7
|
-
* The shapes below are generic over the id type to avoid an import cycle with the registry that
|
|
8
|
-
* defines the concrete `TokenId` union. Import `Token` / `TokenControl` from `./token-registry` (bound
|
|
9
|
-
* to the concrete id), not from here directly.
|
|
10
|
-
*/
|
|
2
|
+
/** Import `Token` / `TokenControl` from `./token-registry`, not from here directly. */
|
|
11
3
|
/** A continuous or per-scheme numeric value (slider tokens) or a fixed string (select tokens). */
|
|
12
4
|
export type TokenValue = number | string | Record<ColorScheme, number>;
|
|
13
5
|
/** A numeric value that may be resolved per color scheme (min / max / default on slider controls). */
|
|
@@ -15,14 +7,9 @@ export type NumericTokenValue = number | Record<ColorScheme, number>;
|
|
|
15
7
|
/** Grouping for panel layout. */
|
|
16
8
|
export type TokenGroup = "brand" | "surface" | "primary" | "status" | "accent" | "shadow" | "shape" | "font";
|
|
17
9
|
/**
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
* -
|
|
21
|
-
* `Record<TokenId, Token>`, so every key and every `id` is checked against it);
|
|
22
|
-
* - every `id` / `derivedFrom` on a control is checked against it.
|
|
23
|
-
*
|
|
24
|
-
* Adding or renaming a token is a deliberate two-step edit: this union **and** `TOKEN_REGISTRY`. The
|
|
25
|
-
* `token-registry.test.ts` "union ↔ registry agree" test fails loudly if the two drift.
|
|
10
|
+
* Hand-authored, not derived from the registry. This is the source of truth for valid `--imf-ui-*` token ids.
|
|
11
|
+
* Adding or renaming a token requires updating this union and `TOKEN_REGISTRY` together;
|
|
12
|
+
* `token-registry.test.ts` fails if the two drift.
|
|
26
13
|
*/
|
|
27
14
|
export type TokenId = "color-brand-luma" | "color-brand-chroma" | "color-brand-hue" | "color-surface-bg-luma" | "color-surface-fg-luma" | "color-surface-chroma" | "color-surface-main-hue" | "color-surface-support-hue" | "color-surface-minor-hue" | "color-primary-bg-luma" | "color-primary-fg-luma" | "color-primary-chroma" | "color-primary-hue" | "color-status-bg-luma" | "color-status-fg-luma" | "color-status-chroma" | "color-status-positive-hue" | "color-status-negative-hue" | "color-status-warning-hue" | "color-status-info-hue" | "color-accent-1-bg-luma" | "color-accent-1-fg-luma" | "color-accent-1-chroma" | "color-accent-1-hue" | "color-accent-2-bg-luma" | "color-accent-2-fg-luma" | "color-accent-2-chroma" | "color-accent-2-hue" | "color-accent-3-bg-luma" | "color-accent-3-fg-luma" | "color-accent-3-chroma" | "color-accent-3-hue" | "shadow-angle" | "shadow-hardness" | "shadow-spread" | "shadow-intensity" | "corner-radius" | "font-family-sans" | "font-family-mono" | "font-family-heading" | "font-family-body" | "font-family-ui" | "font-family-code";
|
|
28
15
|
/**
|
|
@@ -43,13 +30,9 @@ export interface Token {
|
|
|
43
30
|
description: string;
|
|
44
31
|
}
|
|
45
32
|
/**
|
|
46
|
-
* A live adjuster bound to a token
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
* - `select` — a discrete choice from an enumerated option list (e.g. font stacks).
|
|
50
|
-
*
|
|
51
|
-
* The finer constraint — slider ↔ numeric token, select ↔ string token — is a runtime/test guard, not
|
|
52
|
-
* encoded here (encoding it would add cryptic type machinery for little gain).
|
|
33
|
+
* A live adjuster bound to a token, carrying the token's own fields plus kind-specific control metadata.
|
|
34
|
+
* The finer constraint (slider ↔ numeric token, select ↔ string token) is enforced by a
|
|
35
|
+
* runtime/test guard, not encoded in the type; that would add cryptic generics for little gain.
|
|
53
36
|
*/
|
|
54
37
|
export type TokenControl = Token & ({
|
|
55
38
|
kind: "slider";
|
|
@@ -14,18 +14,9 @@ export interface UseTokenControlsResult {
|
|
|
14
14
|
reset: () => void;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
* `useTokenControls` owns *no* persistence — the host decides where `overrides` lives and how writes
|
|
22
|
-
* are debounced, then passes the current map and a commit callback in on every render.
|
|
23
|
-
*
|
|
24
|
-
* **Contract — synchronous read-back:** each mutator builds its next map by spreading the current
|
|
25
|
-
* `overrides`. If the host debounces persistence, it must still surface each committed map as
|
|
26
|
-
* `overrides` *synchronously* (e.g. a `useState` working copy) so back-to-back `setControl` calls in
|
|
27
|
-
* one tick don't clobber each other. A host that only reflects the committed map after an async
|
|
28
|
-
* round trip will drop rapid successive edits.
|
|
17
|
+
* Owns no persistence — the host supplies `overrides`/`onChange`. Contract: each mutator spreads the
|
|
18
|
+
* current `overrides`, so the host must surface each committed map back synchronously (e.g. a
|
|
19
|
+
* `useState` working copy) or back-to-back calls in one tick will clobber each other.
|
|
29
20
|
*/
|
|
30
21
|
export declare function useTokenControls({ overrides, onChange, scheme }: {
|
|
31
22
|
/** Current overrides, keyed by full CSS custom property name. Must update synchronously on commit. */
|
package/dist/types/meta.d.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
export type ComponentCategoryId = "buttons" | "inputs" | "layout" | "display";
|
|
2
2
|
export type ComponentStatus = "stable" | "experimental" | "deprecated";
|
|
3
3
|
/**
|
|
4
|
-
* Provenance of a component. `"adapted"` wraps an upstream behavioral package
|
|
5
|
-
* Base UI by default, or another
|
|
6
|
-
*
|
|
7
|
-
* whether the design took inspiration from elsewhere. Surfaces as the origin
|
|
8
|
-
* badge in the docs UI and on consumer-side surfaces that disambiguate the
|
|
9
|
-
* implementation source.
|
|
4
|
+
* Provenance of a component. `"adapted"` wraps an upstream behavioral package
|
|
5
|
+
* (Base UI by default, or another library); `"native"` has no upstream runtime
|
|
6
|
+
* dependency, regardless of whether the design took inspiration elsewhere.
|
|
10
7
|
*/
|
|
11
8
|
export type ComponentOrigin = "adapted" | "native";
|
|
12
9
|
/**
|
|
@@ -24,23 +21,16 @@ export interface ComponentUpstream {
|
|
|
24
21
|
/** LLM-friendly documentation URL — typically the `.md` companion of `docLink`. */
|
|
25
22
|
llmDocLink?: string;
|
|
26
23
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
* primitives, which are already present transitively; an absent field and an
|
|
31
|
-
* empty array both mean "no extra install". An array (not a single string)
|
|
32
|
-
* because one adapt may require several independently-installable peers (a
|
|
33
|
-
* lib plus a required companion package). Docs read this to render an install
|
|
34
|
-
* hint; `name` remains the human-readable display name.
|
|
24
|
+
* Install-time package specifiers, e.g. `["@tanstack/react-table"]`. Omitted
|
|
25
|
+
* for Base-UI-adapted primitives (already present transitively); an absent
|
|
26
|
+
* field and an empty array both mean "no extra install".
|
|
35
27
|
*/
|
|
36
28
|
runtimeDependencies?: string[];
|
|
37
29
|
}
|
|
38
30
|
/**
|
|
39
|
-
* A taxonomic category as it appears on a component's meta
|
|
40
|
-
*
|
|
41
|
-
*
|
|
42
|
-
* can swap `ComponentCategories` for locale-aware sources without changing the
|
|
43
|
-
* `ComponentCategory` shape consumers read.
|
|
31
|
+
* A taxonomic category as it appears on a component's meta: a stable `id`
|
|
32
|
+
* (slug used for grouping/sorting) and a human-readable `label`. Labels are English only;
|
|
33
|
+
* locale-aware sources can replace `ComponentCategories` without changing this shape.
|
|
44
34
|
*/
|
|
45
35
|
export interface ComponentCategory {
|
|
46
36
|
id: ComponentCategoryId;
|
|
@@ -48,8 +38,7 @@ export interface ComponentCategory {
|
|
|
48
38
|
}
|
|
49
39
|
/**
|
|
50
40
|
* Canonical category objects. Every `*.meta.ts` should reference one of these
|
|
51
|
-
*
|
|
52
|
-
* — a literal allows ID/label drift and duplicates the label string per meta.
|
|
41
|
+
* rather than inlining a literal, which would allow ID/label drift.
|
|
53
42
|
*/
|
|
54
43
|
export declare const ComponentCategories: {
|
|
55
44
|
readonly buttons: {
|
|
@@ -70,13 +59,9 @@ export declare const ComponentCategories: {
|
|
|
70
59
|
};
|
|
71
60
|
};
|
|
72
61
|
/**
|
|
73
|
-
* Self-descriptive metadata for an exported component
|
|
74
|
-
*
|
|
75
|
-
*
|
|
76
|
-
* usage examples, props, or part enumerations. Props are derived from the
|
|
77
|
-
* component's TypeScript Props interface at build time; longer-form
|
|
78
|
-
* documentation lives in MDX; named sub-exports are the source of truth for
|
|
79
|
-
* compound-component parts.
|
|
62
|
+
* Self-descriptive metadata for an exported component: name, taxonomy,
|
|
63
|
+
* lifecycle, identity line, provenance. NOT documentation prose, usage
|
|
64
|
+
* examples, props, or part enumerations — those live in MDX and the Props interface.
|
|
80
65
|
*/
|
|
81
66
|
export interface ComponentMeta {
|
|
82
67
|
/** PascalCase component name as exported from `@imfusion/web-ui`. Matches the `data-imf-ui-component` attribute. */
|
|
@@ -99,10 +84,8 @@ export interface ComponentMeta {
|
|
|
99
84
|
upstream?: ComponentUpstream;
|
|
100
85
|
/**
|
|
101
86
|
* PascalCase names of other `@imfusion/web-ui` components this component
|
|
102
|
-
* composes internally (e.g. CopyButton renders through Button).
|
|
103
|
-
*
|
|
104
|
-
* implication — purely discovery/documentation, surfaced by doc tooling
|
|
105
|
-
* (DocHeader "Uses" row). Omit when the component composes nothing.
|
|
87
|
+
* composes internally (e.g. CopyButton renders through Button). No install
|
|
88
|
+
* implication — surfaced by doc tooling (DocHeader "Uses" row).
|
|
106
89
|
*/
|
|
107
90
|
internalDependencies?: string[];
|
|
108
91
|
}
|
|
@@ -4,17 +4,8 @@ export interface ReadableCssModuleNamesOptions {
|
|
|
4
4
|
prefix: string;
|
|
5
5
|
}
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* suffix.
|
|
11
|
-
*
|
|
12
|
-
* Register it in every tool that compiles the CSS (the app build AND Storybook). A
|
|
13
|
-
* compiler left out generates different class names for the same source file and its
|
|
14
|
-
* styles silently don't apply.
|
|
15
|
-
*
|
|
16
|
-
* Defaults to opting into Lightning CSS, which drops `css.modules` options it has no
|
|
17
|
-
* equivalent for (`localsConvention`). Set `css.transformer: "postcss"` to stay on
|
|
18
|
-
* the PostCSS pipeline.
|
|
7
|
+
* `button.module.css` yields `{prefix}-button-module-root`. Register in every CSS-compiling
|
|
8
|
+
* tool (app build AND Storybook) or class names diverge and styles silently break. Defaults to
|
|
9
|
+
* Lightning CSS, which drops `css.modules` options like `localsConvention`.
|
|
19
10
|
*/
|
|
20
11
|
export declare function readableCssModuleNames({ prefix }: ReadableCssModuleNamesOptions): Plugin;
|
|
@@ -0,0 +1,322 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { chmodSync as e, cpSync as t, existsSync as n, lstatSync as r, mkdirSync as i, readFileSync as a, readdirSync as o, readlinkSync as s, realpathSync as c, rmSync as l, symlinkSync as u, writeFileSync as d } from "node:fs";
|
|
3
|
+
import { dirname as f, relative as p, resolve as m } from "node:path";
|
|
4
|
+
import { fileURLToPath as ee } from "node:url";
|
|
5
|
+
import { Command as te, Option as ne } from "commander";
|
|
6
|
+
import * as h from "@clack/prompts";
|
|
7
|
+
import { spawnSync as g } from "node:child_process";
|
|
8
|
+
var _ = {
|
|
9
|
+
harnesses: {
|
|
10
|
+
claude: {
|
|
11
|
+
label: "Claude Code",
|
|
12
|
+
hookSettings: ".claude/settings.json",
|
|
13
|
+
hookTemplate: "settings.json"
|
|
14
|
+
},
|
|
15
|
+
codex: {
|
|
16
|
+
label: "Codex",
|
|
17
|
+
hookSettings: ".codex/hooks.json",
|
|
18
|
+
hookTemplate: "codex-hooks.json"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
namespace: "imf-web-ui",
|
|
22
|
+
hooksDir: ".agents/hooks/imf-web-ui",
|
|
23
|
+
templatesDir: "src/llms/install-templates",
|
|
24
|
+
skillsSource: "src/llms/skills",
|
|
25
|
+
skillsDir: ".agents/skills",
|
|
26
|
+
linkDir: ".claude/skills",
|
|
27
|
+
versionMarker: ".imf-web-ui-skill-version.json"
|
|
28
|
+
};
|
|
29
|
+
//#endregion
|
|
30
|
+
//#region src/cli/paths.ts
|
|
31
|
+
function re(e) {
|
|
32
|
+
let t = e;
|
|
33
|
+
for (; !n(m(t, "package.json"));) {
|
|
34
|
+
let n = f(t);
|
|
35
|
+
if (n === t) throw Error(`Could not find package.json above ${e}`);
|
|
36
|
+
t = n;
|
|
37
|
+
}
|
|
38
|
+
return t;
|
|
39
|
+
}
|
|
40
|
+
var v = re(f(ee(import.meta.url))), y = process.cwd(), ie = `<!-- ${_.namespace}:begin`, b = `<!-- ${_.namespace}:end -->`, ae = m(v, _.templatesDir, "AGENTS.md"), x = m(y, "AGENTS.md");
|
|
41
|
+
function S(e) {
|
|
42
|
+
let t = e.indexOf(ie), n = e.indexOf(b);
|
|
43
|
+
return t === -1 || n === -1 ? null : e.slice(t, n + b.length);
|
|
44
|
+
}
|
|
45
|
+
function oe() {
|
|
46
|
+
if (!n(x)) return "absent";
|
|
47
|
+
let e = S(a(ae, "utf-8"));
|
|
48
|
+
if (!e) return "absent";
|
|
49
|
+
let t = a(x, "utf-8"), r = S(t), i = r ? t.replace(r, e) : `${t.trimEnd()}\n\n${e}\n`;
|
|
50
|
+
return i === t ? "unchanged" : (d(x, i), r ? "updated" : "added");
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region src/cli/skills.ts
|
|
54
|
+
var C = Object.keys(_.harnesses).filter((e) => e in _.harnesses), se = [...C, "all"];
|
|
55
|
+
function w(e) {
|
|
56
|
+
return e === "all" ? C : [e];
|
|
57
|
+
}
|
|
58
|
+
function T(e) {
|
|
59
|
+
return `./${p(y, e)}`;
|
|
60
|
+
}
|
|
61
|
+
var E = _.namespace, D = _.versionMarker, O = m(v, _.skillsSource), k = m(y, _.skillsDir), A = m(y, _.linkDir), j = class extends Error {}, M = JSON.parse(a(m(v, "package.json"), "utf-8"));
|
|
62
|
+
if (typeof M != "object" || !M || !("version" in M) || typeof M.version != "string") throw Error(`${m(v, "package.json")} has no string "version" field`);
|
|
63
|
+
var N = M.version;
|
|
64
|
+
function P(e) {
|
|
65
|
+
return e === E || e.startsWith(`${E}-`);
|
|
66
|
+
}
|
|
67
|
+
function F() {
|
|
68
|
+
return n(O) ? o(O, { withFileTypes: !0 }).filter((e) => e.isDirectory() && P(e.name)).map((e) => e.name).sort() : [];
|
|
69
|
+
}
|
|
70
|
+
function I(e) {
|
|
71
|
+
let t = m(k, e, D);
|
|
72
|
+
if (!n(t)) return null;
|
|
73
|
+
try {
|
|
74
|
+
let e = JSON.parse(a(t, "utf-8"));
|
|
75
|
+
if (typeof e != "object" || !e) return null;
|
|
76
|
+
let n = "harnesses" in e && Array.isArray(e.harnesses) ? e.harnesses : null;
|
|
77
|
+
return n ? C.filter((e) => n.includes(e)) : null;
|
|
78
|
+
} catch {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
function L(e) {
|
|
83
|
+
for (let t of e) {
|
|
84
|
+
let e = I(t);
|
|
85
|
+
if (e && e.length > 0) return e;
|
|
86
|
+
}
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
function R(e, t, r) {
|
|
90
|
+
if (n(e)) for (let n of o(e).filter(P)) t.has(n) || (l(m(e, n), {
|
|
91
|
+
recursive: !0,
|
|
92
|
+
force: !0
|
|
93
|
+
}), h.log.info(`Removed skill: ${n} (${r})`));
|
|
94
|
+
}
|
|
95
|
+
function ce() {
|
|
96
|
+
return n(A) && n(k) && c(A) === c(k);
|
|
97
|
+
}
|
|
98
|
+
function le(e) {
|
|
99
|
+
let t = m(A, e), n = p(A, m(k, e)), a = r(t, { throwIfNoEntry: !1 });
|
|
100
|
+
a?.isSymbolicLink() && s(t) === n || (a && l(t, {
|
|
101
|
+
recursive: !0,
|
|
102
|
+
force: !0
|
|
103
|
+
}), i(A, { recursive: !0 }), u(n, t));
|
|
104
|
+
}
|
|
105
|
+
function z(e, n) {
|
|
106
|
+
try {
|
|
107
|
+
i(k, { recursive: !0 });
|
|
108
|
+
for (let r of e) {
|
|
109
|
+
let e = m(k, r);
|
|
110
|
+
l(e, {
|
|
111
|
+
recursive: !0,
|
|
112
|
+
force: !0
|
|
113
|
+
}), t(m(O, r), e, { recursive: !0 }), d(m(e, D), JSON.stringify({
|
|
114
|
+
version: N,
|
|
115
|
+
harnesses: n
|
|
116
|
+
}, null, 2) + "\n");
|
|
117
|
+
}
|
|
118
|
+
if (R(k, new Set(e), "no longer in this bundle"), h.log.success(`Skills -> ${T(k)}/${E}*`), ce()) return;
|
|
119
|
+
if (n.includes("claude")) {
|
|
120
|
+
for (let t of e) le(t);
|
|
121
|
+
R(A, new Set(e), "no longer in this bundle"), h.log.success(`Claude Code -> ${T(A)}/${E}* (symlinks -> ${T(k)})`);
|
|
122
|
+
} else R(A, /* @__PURE__ */ new Set(), "claude not selected");
|
|
123
|
+
} catch (e) {
|
|
124
|
+
throw e instanceof Error ? new j(e.message) : e;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
//#endregion
|
|
128
|
+
//#region src/cli/hooks.ts
|
|
129
|
+
var B = m(v, _.templatesDir), V = m(B, "hooks"), H = m(y, _.hooksDir), U = class extends Error {};
|
|
130
|
+
function ue(e, t) {
|
|
131
|
+
if (typeof e != "object" || !e || Array.isArray(e)) throw new U(`${t} is not a JSON object.`);
|
|
132
|
+
let n = { ...e };
|
|
133
|
+
if ("hooks" in n) {
|
|
134
|
+
if (typeof n.hooks != "object" || n.hooks === null || Array.isArray(n.hooks)) throw new U(`${t}: "hooks" is not a JSON object.`);
|
|
135
|
+
for (let [e, r] of Object.entries(n.hooks)) if (!Array.isArray(r)) throw new U(`${t}: "hooks.${e}" is not a JSON array.`);
|
|
136
|
+
}
|
|
137
|
+
return n;
|
|
138
|
+
}
|
|
139
|
+
function W(e) {
|
|
140
|
+
let t;
|
|
141
|
+
try {
|
|
142
|
+
t = JSON.parse(a(e, "utf-8"));
|
|
143
|
+
} catch {
|
|
144
|
+
throw new U(`${e} is not valid JSON.`);
|
|
145
|
+
}
|
|
146
|
+
return ue(t, e);
|
|
147
|
+
}
|
|
148
|
+
function de(e) {
|
|
149
|
+
let t = y;
|
|
150
|
+
for (; t !== f(t) && !n(m(t, "node_modules", ".bin"));) t = f(t);
|
|
151
|
+
let r = m(t, "node_modules", ".bin", "prettier");
|
|
152
|
+
n(r) && g(r, ["--write", e], {
|
|
153
|
+
cwd: y,
|
|
154
|
+
stdio: "ignore"
|
|
155
|
+
});
|
|
156
|
+
}
|
|
157
|
+
function fe() {
|
|
158
|
+
let n = o(V).filter((e) => e.endsWith(".sh"));
|
|
159
|
+
i(H, { recursive: !0 });
|
|
160
|
+
for (let r of n) {
|
|
161
|
+
let n = m(H, r);
|
|
162
|
+
t(m(V, r), n, { force: !0 }), e(n, 493);
|
|
163
|
+
}
|
|
164
|
+
let r = o(H).filter((e) => e.endsWith(".sh") && !n.includes(e));
|
|
165
|
+
for (let e of r) l(m(H, e), { force: !0 });
|
|
166
|
+
return {
|
|
167
|
+
installed: n,
|
|
168
|
+
removed: r
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
var G = `${_.hooksDir}/`;
|
|
172
|
+
function K(e) {
|
|
173
|
+
if (typeof e != "string") return null;
|
|
174
|
+
let t = e.lastIndexOf(G);
|
|
175
|
+
if (t === -1) return null;
|
|
176
|
+
let n = /^([\w.-]+)["']?$/.exec(e.slice(t + G.length));
|
|
177
|
+
return n ? n[1] : null;
|
|
178
|
+
}
|
|
179
|
+
function q(e) {
|
|
180
|
+
return typeof e == "object" && !!e && "hooks" in e && Array.isArray(e.hooks);
|
|
181
|
+
}
|
|
182
|
+
function J(e) {
|
|
183
|
+
return q(e) ? e.hooks : null;
|
|
184
|
+
}
|
|
185
|
+
function Y(e) {
|
|
186
|
+
return typeof e == "object" && e && "command" in e ? e.command : void 0;
|
|
187
|
+
}
|
|
188
|
+
function X(e) {
|
|
189
|
+
let t = J(e);
|
|
190
|
+
return t ? t.map((e) => K(Y(e))).filter((e) => e !== null) : [];
|
|
191
|
+
}
|
|
192
|
+
function pe(e, t, n) {
|
|
193
|
+
e.hooks ??= {};
|
|
194
|
+
let r = 0, i = 0, a = [];
|
|
195
|
+
for (let [r, o] of Object.entries(e.hooks)) {
|
|
196
|
+
let s = 0, c = o.map((e) => {
|
|
197
|
+
if (!q(e)) return e;
|
|
198
|
+
let t = e.hooks, r = t.filter((e) => {
|
|
199
|
+
let t = K(Y(e));
|
|
200
|
+
return t === null || n.includes(t);
|
|
201
|
+
});
|
|
202
|
+
return s += t.length - r.length, r.length === t.length ? e : {
|
|
203
|
+
...e,
|
|
204
|
+
hooks: r
|
|
205
|
+
};
|
|
206
|
+
}).filter((e) => {
|
|
207
|
+
let t = J(e);
|
|
208
|
+
return t === null || t.length > 0;
|
|
209
|
+
});
|
|
210
|
+
s !== 0 && (i += s, c.length === 0 && !(r in t) ? a.push(r) : e.hooks[r] = c);
|
|
211
|
+
}
|
|
212
|
+
a.length > 0 && (e.hooks = Object.fromEntries(Object.entries(e.hooks).filter(([e]) => !a.includes(e))));
|
|
213
|
+
for (let [n, i] of Object.entries(t)) {
|
|
214
|
+
e.hooks[n] ??= [];
|
|
215
|
+
for (let t of i) {
|
|
216
|
+
let i = X(t);
|
|
217
|
+
e.hooks[n].some((e) => X(e).some((e) => i.includes(e))) || (e.hooks[n].push(t), r++);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return {
|
|
221
|
+
added: r,
|
|
222
|
+
removed: i
|
|
223
|
+
};
|
|
224
|
+
}
|
|
225
|
+
function me(e) {
|
|
226
|
+
let t = /* @__PURE__ */ new Map(), r = /* @__PURE__ */ new Map();
|
|
227
|
+
for (let i of C) {
|
|
228
|
+
let a = m(y, _.harnesses[i].hookSettings);
|
|
229
|
+
if (n(a) && t.set(i, W(a)), e.includes(i)) {
|
|
230
|
+
let e = m(B, _.harnesses[i].hookTemplate);
|
|
231
|
+
r.set(i, W(e).hooks ?? {});
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
let { installed: a, removed: o } = fe();
|
|
235
|
+
h.log.success(`Agent hooks -> ${T(H)} (${String(a.length)} scripts)`), o.length > 0 && h.log.info(`Removed stale hook script(s): ${o.join(", ")} (no longer shipped)`);
|
|
236
|
+
for (let n of C) {
|
|
237
|
+
let o = e.includes(n);
|
|
238
|
+
if (!o && !t.has(n)) continue;
|
|
239
|
+
let s = m(y, _.harnesses[n].hookSettings), c = r.get(n) ?? {}, l = t.get(n) ?? {}, u = pe(l, c, o ? a : []);
|
|
240
|
+
if (u.added === 0 && u.removed === 0) {
|
|
241
|
+
o && h.log.info(`Hook registrations already present in ${T(s)}`);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
i(f(s), { recursive: !0 }), d(s, JSON.stringify(l, null, 2) + "\n"), de(s);
|
|
245
|
+
let p = [u.added > 0 && `registered ${String(u.added)}`, u.removed > 0 && `removed ${String(u.removed)} stale`].filter(Boolean);
|
|
246
|
+
h.log.success(`${p.join(", ")} hook registration(s) in ${T(s)}`);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
//#endregion
|
|
250
|
+
//#region src/cli/index.ts
|
|
251
|
+
function Z() {
|
|
252
|
+
return new ne("--harness <harness>", "claude, codex, or all").choices(se);
|
|
253
|
+
}
|
|
254
|
+
async function he(e, t, n) {
|
|
255
|
+
if (t) return h.log.info(`Harness from --harness flag: ${t.join(", ")}`), t;
|
|
256
|
+
let r = L(e);
|
|
257
|
+
if (r && !n) return h.log.info(`Refreshing the existing install: ${r.join(", ")} — pass --reconfigure to choose differently.`), r;
|
|
258
|
+
if (!process.stdin.isTTY) return console.error("Pass --harness=claude|codex|all — no terminal to prompt in."), process.exitCode = 1, null;
|
|
259
|
+
let i = await h.multiselect({
|
|
260
|
+
message: "Install into which harness (or harnesses)?",
|
|
261
|
+
options: C.map((e) => ({
|
|
262
|
+
value: e,
|
|
263
|
+
label: _.harnesses[e].label
|
|
264
|
+
})),
|
|
265
|
+
required: !0
|
|
266
|
+
});
|
|
267
|
+
return h.isCancel(i) ? (h.cancel("Cancelled — nothing installed."), null) : i;
|
|
268
|
+
}
|
|
269
|
+
async function ge(e) {
|
|
270
|
+
let t = e.harness ? w(e.harness) : null, n = F();
|
|
271
|
+
if (h.intro(`@imfusion/web-ui install skills — ${String(n.length)} skills`), n.length === 0) {
|
|
272
|
+
h.log.error("No skills found. Reinstall @imfusion/web-ui and try again."), h.outro("Nothing installed."), process.exitCode = 1;
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
let r = await he(n, t, e.reconfigure === !0);
|
|
276
|
+
if (!r) return;
|
|
277
|
+
h.log.message(`Skills in this bundle:\n${n.map((e) => ` - ${e}`).join("\n")}`);
|
|
278
|
+
try {
|
|
279
|
+
z(n, r);
|
|
280
|
+
} catch (e) {
|
|
281
|
+
if (e instanceof j) {
|
|
282
|
+
h.log.error(e.message), h.outro("Install failed."), process.exitCode = 1;
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
throw e;
|
|
286
|
+
}
|
|
287
|
+
let i = oe();
|
|
288
|
+
(i === "updated" || i === "added") && h.log.success(`Refreshed the ${_.namespace} block in ./AGENTS.md`), h.outro(`Done — ${String(n.length)} skills installed.`);
|
|
289
|
+
}
|
|
290
|
+
function _e(e) {
|
|
291
|
+
let t = e.harness ? w(e.harness) : null, n = L(F());
|
|
292
|
+
if (!t && !n) {
|
|
293
|
+
console.error("No harness recorded. Pass --harness=claude|codex|all."), process.exitCode = 1;
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
let r = t ?? n ?? [], i = r.filter((e) => !(n ?? []).includes(e));
|
|
297
|
+
if (i.length > 0) {
|
|
298
|
+
for (let e of i) console.error(`No skills installed for ${e}. Run \`npx web-ui install skills --harness=${e}\` first.`);
|
|
299
|
+
process.exitCode = 1;
|
|
300
|
+
return;
|
|
301
|
+
}
|
|
302
|
+
h.intro("@imfusion/web-ui install hooks");
|
|
303
|
+
try {
|
|
304
|
+
me(r);
|
|
305
|
+
} catch (e) {
|
|
306
|
+
if (e instanceof U) {
|
|
307
|
+
console.error(e.message), process.exitCode = 1;
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
throw e;
|
|
311
|
+
}
|
|
312
|
+
h.outro("Done — agent hooks installed.");
|
|
313
|
+
}
|
|
314
|
+
var Q = new te();
|
|
315
|
+
Q.name("web-ui").description("Command-line tools for @imfusion/web-ui").version(N);
|
|
316
|
+
var $ = Q.command("install").description("Install @imfusion/web-ui agent skills or hooks into a project");
|
|
317
|
+
$.command("skills").description("Install skills for the chosen harness").addOption(Z()).option("--reconfigure", "Re-open the harness selection prompt on an existing install").action(ge), $.command("hooks").description("Install agent lifecycle hooks (requires skills already installed for the harness)").addOption(Z()).action(_e), $.action(() => {
|
|
318
|
+
$.help();
|
|
319
|
+
}), Q.action(() => {
|
|
320
|
+
Q.help();
|
|
321
|
+
}), await Q.parseAsync();
|
|
322
|
+
//#endregion
|