@jini-ai/agent-plugins 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.
Files changed (100) hide show
  1. package/CHANGELOG.md +57 -0
  2. package/LICENSE +201 -0
  3. package/README.md +261 -0
  4. package/dist/index.d.ts +3 -0
  5. package/dist/index.js +2 -0
  6. package/dist/lifecycle/activation.d.ts +177 -0
  7. package/dist/lifecycle/activation.js +874 -0
  8. package/dist/lifecycle/bundled-digests.d.ts +27 -0
  9. package/dist/lifecycle/bundled-digests.js +149 -0
  10. package/dist/lifecycle/bundled-source-archive.d.ts +13 -0
  11. package/dist/lifecycle/bundled-source-archive.js +96 -0
  12. package/dist/lifecycle/capability-projection.d.ts +11 -0
  13. package/dist/lifecycle/capability-projection.js +60 -0
  14. package/dist/lifecycle/fetch-archive.d.ts +88 -0
  15. package/dist/lifecycle/fetch-archive.js +215 -0
  16. package/dist/lifecycle/index.d.ts +174 -0
  17. package/dist/lifecycle/index.js +94 -0
  18. package/dist/lifecycle/install-from-url.d.ts +78 -0
  19. package/dist/lifecycle/install-from-url.js +73 -0
  20. package/dist/lifecycle/install.d.ts +52 -0
  21. package/dist/lifecycle/install.js +286 -0
  22. package/dist/lifecycle/layout.d.ts +20 -0
  23. package/dist/lifecycle/layout.js +30 -0
  24. package/dist/lifecycle/manifest.d.ts +214 -0
  25. package/dist/lifecycle/manifest.js +409 -0
  26. package/dist/lifecycle/mcp-provisioning.d.ts +20 -0
  27. package/dist/lifecycle/mcp-provisioning.js +14 -0
  28. package/dist/lifecycle/node.d.ts +11 -0
  29. package/dist/lifecycle/node.js +34 -0
  30. package/dist/lifecycle/package-paths.d.ts +12 -0
  31. package/dist/lifecycle/package-paths.js +74 -0
  32. package/dist/lifecycle/ports.d.ts +81 -0
  33. package/dist/lifecycle/ports.js +2 -0
  34. package/dist/lifecycle/resolve-agent-plugin-refs.d.ts +18 -0
  35. package/dist/lifecycle/resolve-agent-plugin-refs.js +143 -0
  36. package/dist/lifecycle/retire-bundled.d.ts +36 -0
  37. package/dist/lifecycle/retire-bundled.js +69 -0
  38. package/dist/lifecycle/search.d.ts +23 -0
  39. package/dist/lifecycle/search.js +50 -0
  40. package/dist/lifecycle/seed-bundled.d.ts +29 -0
  41. package/dist/lifecycle/seed-bundled.js +118 -0
  42. package/dist/lifecycle/set-enabled.d.ts +21 -0
  43. package/dist/lifecycle/set-enabled.js +39 -0
  44. package/dist/lifecycle/trusted-plugin-files.d.ts +29 -0
  45. package/dist/lifecycle/trusted-plugin-files.js +82 -0
  46. package/dist/lifecycle/types.d.ts +20 -0
  47. package/dist/lifecycle/types.js +2 -0
  48. package/dist/lifecycle/uninstall.d.ts +41 -0
  49. package/dist/lifecycle/uninstall.js +148 -0
  50. package/dist/lifecycle/yauzl-archive-reader.d.ts +62 -0
  51. package/dist/lifecycle/yauzl-archive-reader.js +73 -0
  52. package/dist/manifest.d.ts +68 -0
  53. package/dist/manifest.js +38 -0
  54. package/package.json +92 -0
  55. package/ui-ux-design/com.anthropic.claude-code/hooks/hooks.json +4 -0
  56. package/ui-ux-design/mcp.json +4 -0
  57. package/ui-ux-design/plugin.json +5 -0
  58. package/ui-ux-design/skills/frontend-accessibility/SKILL.md +60 -0
  59. package/ui-ux-design/skills/gstack-design/SKILL.md +53 -0
  60. package/ui-ux-design/skills/gstack-design/references/consultation.md +30 -0
  61. package/ui-ux-design/skills/gstack-design/references/html.md +29 -0
  62. package/ui-ux-design/skills/gstack-design/references/review.md +28 -0
  63. package/ui-ux-design/skills/gstack-design/references/shotgun.md +33 -0
  64. package/ui-ux-design/skills/gstack-design/references/upstream-notes.md +23 -0
  65. package/ui-ux-design/skills/interface-design/ORIGINAL.md +16 -0
  66. package/ui-ux-design/skills/interface-design/SKILL.md +41 -0
  67. package/ui-ux-design/skills/interface-design/references/commands/audit.md +40 -0
  68. package/ui-ux-design/skills/interface-design/references/commands/critique.md +21 -0
  69. package/ui-ux-design/skills/interface-design/references/commands/extract.md +34 -0
  70. package/ui-ux-design/skills/interface-design/references/commands/init.md +31 -0
  71. package/ui-ux-design/skills/interface-design/references/commands/status.md +22 -0
  72. package/ui-ux-design/skills/interface-design/references/examples/system-precision.md +29 -0
  73. package/ui-ux-design/skills/interface-design/references/examples/system-warmth.md +29 -0
  74. package/ui-ux-design/skills/interface-design/references/system-template.md +57 -0
  75. package/ui-ux-design/skills/shadcn-ui/README.md +244 -0
  76. package/ui-ux-design/skills/shadcn-ui/SKILL.md +326 -0
  77. package/ui-ux-design/skills/shadcn-ui/examples/auth-layout.tsx +177 -0
  78. package/ui-ux-design/skills/shadcn-ui/examples/data-table.tsx +313 -0
  79. package/ui-ux-design/skills/shadcn-ui/examples/form-pattern.tsx +177 -0
  80. package/ui-ux-design/skills/shadcn-ui/resources/component-catalog.md +481 -0
  81. package/ui-ux-design/skills/shadcn-ui/resources/customization-guide.md +516 -0
  82. package/ui-ux-design/skills/shadcn-ui/resources/migration-guide.md +463 -0
  83. package/ui-ux-design/skills/shadcn-ui/resources/setup-guide.md +412 -0
  84. package/ui-ux-design/skills/shadcn-ui/scripts/verify-setup.sh +134 -0
  85. package/ui-ux-design/skills/ui-ux-design/SKILL.md +77 -0
  86. package/ui-ux-design/skills/ui-ux-design/references/brand-and-voice.md +72 -0
  87. package/ui-ux-design/skills/ui-ux-design/references/components-and-states.md +107 -0
  88. package/ui-ux-design/skills/ui-ux-design/references/delight-and-motion.md +65 -0
  89. package/ui-ux-design/skills/ui-ux-design/references/foundations.md +144 -0
  90. package/ui-ux-design/skills/ui-ux-design/references/inclusive-ai-imagery.md +57 -0
  91. package/ui-ux-design/skills/ui-ux-design/references/kole-jain-uiux-concepts.md +102 -0
  92. package/ui-ux-design/skills/ui-ux-design/references/openai-frontend-skill.md +139 -0
  93. package/ui-ux-design/skills/ui-ux-design/references/premium-ui.md +63 -0
  94. package/ui-ux-design/skills/ui-ux-design/references/research-and-validation.md +78 -0
  95. package/ui-ux-design/skills/ui-ux-design/references/sam-crawford-premium-websites.md +93 -0
  96. package/ui-ux-design/skills/ui-ux-design/references/self-made-web-designer-core-skills.md +123 -0
  97. package/ui-ux-design/skills/ui-ux-design/references/visual-storytelling.md +57 -0
  98. package/ui-ux-design/skills/vercel-web-design-guidelines/SKILL.md +39 -0
  99. package/ui-ux-design/skills/web-compliance/SKILL.md +129 -0
  100. package/ui-ux-design/skills/web-compliance/references/audit-evidence.md +59 -0
@@ -0,0 +1,20 @@
1
+ import type { PluginManifestAuthor } from "../manifest.js";
2
+ /** Lifecycle value contracts shared by host ports and implementations. */
3
+ export type AgentPluginDeliveryMode = "inject" | "pointer";
4
+ export interface InstalledAgentPluginSkill {
5
+ readonly name: string;
6
+ readonly skillPath: string;
7
+ }
8
+ export interface InstalledAgentPlugin {
9
+ readonly pluginId: string;
10
+ readonly version?: (string) | undefined;
11
+ readonly description?: (string) | undefined;
12
+ readonly keywords?: (readonly string[]) | undefined;
13
+ readonly author?: string | PluginManifestAuthor | undefined;
14
+ readonly license?: (string) | undefined;
15
+ readonly archiveDigest: string;
16
+ readonly packageRoot: string;
17
+ readonly files: readonly string[];
18
+ readonly skills: readonly InstalledAgentPluginSkill[];
19
+ }
20
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,41 @@
1
+ import type { AgentPluginLayout } from "./layout.js";
2
+ import type { AgentPluginLifecyclePorts } from "./ports.js";
3
+ export declare class AgentPluginNotFoundError extends Error {
4
+ constructor({ message }: {
5
+ readonly message: string;
6
+ }, optional?: ErrorOptions);
7
+ }
8
+ export declare class AgentPluginNotUninstallableError extends Error {
9
+ constructor({ message }: {
10
+ readonly message: string;
11
+ }, optional?: ErrorOptions);
12
+ }
13
+ export declare class AgentPluginChangedSincePreviewError extends Error {
14
+ constructor({ message }: {
15
+ readonly message: string;
16
+ }, optional?: ErrorOptions);
17
+ }
18
+ export interface UninstallAgentPluginRequired {
19
+ readonly layout: AgentPluginLayout;
20
+ readonly workspaceId: string;
21
+ readonly pluginId: string;
22
+ }
23
+ export interface UninstallAgentPluginOptional {
24
+ readonly confirmedPreview?: (AgentPluginUninstallPreview) | undefined;
25
+ readonly retiredBundled?: (boolean) | undefined;
26
+ }
27
+ export interface UninstallAgentPluginResult {
28
+ readonly pluginId: string;
29
+ readonly removedDigests: readonly string[];
30
+ }
31
+ export interface AgentPluginUninstallPreview {
32
+ readonly pluginId: string;
33
+ readonly versions: readonly string[];
34
+ readonly archiveDigests: readonly string[];
35
+ }
36
+ /** Internal composition; one state/queue instance per injected host context. */
37
+ export declare function createUninstallModule(ports: AgentPluginLifecyclePorts): {
38
+ previewAgentPluginUninstall: (required: UninstallAgentPluginRequired) => Promise<AgentPluginUninstallPreview>;
39
+ uninstallAgentPlugin: (required: UninstallAgentPluginRequired, optional?: UninstallAgentPluginOptional) => Promise<UninstallAgentPluginResult>;
40
+ };
41
+ //# sourceMappingURL=uninstall.d.ts.map
@@ -0,0 +1,148 @@
1
+ import path from "node:path";
2
+ import { createActivationModule } from "./activation.js";
3
+ import { createResolveAgentPluginRefsModule } from "./resolve-agent-plugin-refs.js";
4
+ export class AgentPluginNotFoundError extends Error {
5
+ constructor({ message }, optional = {}) { super(message, optional); }
6
+ }
7
+ export class AgentPluginNotUninstallableError extends Error {
8
+ constructor({ message }, optional = {}) { super(message, optional); }
9
+ }
10
+ export class AgentPluginChangedSincePreviewError extends Error {
11
+ constructor({ message }, optional = {}) { super(message, optional); }
12
+ }
13
+ function buildModule(ports) {
14
+ const randomUUID = () => ports.ids.newId();
15
+ const chmod = ports.filesystem.chmod.bind(ports.filesystem);
16
+ const readdir = ports.filesystem.readdir.bind(ports.filesystem);
17
+ const rename = ports.filesystem.rename.bind(ports.filesystem);
18
+ const rm = ports.filesystem.rm.bind(ports.filesystem);
19
+ const stat = ports.filesystem.stat.bind(ports.filesystem);
20
+ const { deleteAgentPluginActivation, isAgentPluginRecordedAsBundled, resolveAgentPluginActivation } = createActivationModule(ports);
21
+ const { listInstalledPlugins } = createResolveAgentPluginRefsModule(ports);
22
+ const STAGED_DIRNAME_PREFIX = ".uninstalling-";
23
+ async function previewAgentPluginUninstall(required) {
24
+ const { matches } = await resolveUninstallTargets(required);
25
+ const versions = [...new Set(matches.flatMap((plugin) => (plugin.version === undefined ? [] : [plugin.version])))];
26
+ return { pluginId: required.pluginId, versions, archiveDigests: matches.map((plugin) => plugin.archiveDigest) };
27
+ }
28
+ async function uninstallAgentPlugin(required, optional = {}) {
29
+ const { workspaceRoot, packagesDir, matches } = await resolveUninstallTargets(required, optional.retiredBundled === true);
30
+ assertUnchangedSincePreview(required.pluginId, optional.confirmedPreview, matches);
31
+ const staged = [];
32
+ try {
33
+ for (const plugin of matches) {
34
+ staged.push(await stageForRemoval(packagesDir, plugin.packageRoot));
35
+ }
36
+ await deleteAgentPluginActivation({ workspaceRoot, pluginId: required.pluginId });
37
+ }
38
+ catch (error) {
39
+ throw await restoreStagedTrees(staged, error);
40
+ }
41
+ for (const tree of staged) {
42
+ await removeFrozenPackageTree(tree.quarantined);
43
+ }
44
+ return { pluginId: required.pluginId, removedDigests: matches.map((plugin) => plugin.archiveDigest) };
45
+ }
46
+ function assertUnchangedSincePreview(pluginId, confirmedPreview, matches) {
47
+ if (confirmedPreview === undefined)
48
+ return;
49
+ const previewed = [...confirmedPreview.archiveDigests].sort();
50
+ const installedNow = matches.map((plugin) => plugin.archiveDigest).sort();
51
+ if (previewed.join(",") === installedNow.join(","))
52
+ return;
53
+ throw new AgentPluginChangedSincePreviewError({ message: `Agent Plugin '${pluginId}' changed after its uninstall was previewed (previewed archive digests: ${previewed.join(", ")}; ` +
54
+ `installed now: ${installedNow.join(", ")}) — nothing was removed` });
55
+ }
56
+ async function stageForRemoval(packagesDir, packageRoot) {
57
+ const quarantined = path.join(packagesDir, `${STAGED_DIRNAME_PREFIX}${randomUUID()}`);
58
+ const originalMode = (await stat(packageRoot)).mode & 0o777;
59
+ await chmod(packageRoot, originalMode | 0o700);
60
+ try {
61
+ await rename(packageRoot, quarantined);
62
+ }
63
+ catch (error) {
64
+ await chmod(packageRoot, originalMode).catch(() => undefined);
65
+ throw error;
66
+ }
67
+ return { quarantined, original: packageRoot, originalMode };
68
+ }
69
+ async function restoreStagedTrees(staged, cause) {
70
+ const stranded = [];
71
+ for (const tree of [...staged].reverse()) {
72
+ try {
73
+ await rename(tree.quarantined, tree.original);
74
+ await chmod(tree.original, tree.originalMode).catch(() => undefined);
75
+ }
76
+ catch {
77
+ stranded.push(tree.quarantined);
78
+ }
79
+ }
80
+ if (stranded.length === 0)
81
+ return cause;
82
+ const reason = cause instanceof Error ? cause.message : String(cause);
83
+ return new Error(`Agent Plugin uninstall failed (${reason}) and ${stranded.length} package tree(s) could not be put back. ` +
84
+ `They still hold the package bytes and were left in place for recovery: ${stranded.join(", ")}`, { cause });
85
+ }
86
+ async function resolveUninstallTargets(required, retiredBundled = false) {
87
+ const { layout, workspaceId, pluginId } = required;
88
+ const workspaceLayout = layout.forWorkspace({ workspaceId });
89
+ const installed = await listInstalledPlugins(workspaceLayout.packages);
90
+ const matches = installed.filter((plugin) => plugin.pluginId === pluginId);
91
+ if (matches.length === 0) {
92
+ throw new AgentPluginNotFoundError({ message: `Agent Plugin '${pluginId}' is not installed in this workspace — nothing to uninstall` });
93
+ }
94
+ if (!retiredBundled && (await isAgentPluginRecordedAsBundled(workspaceLayout.root, pluginId))) {
95
+ throw new AgentPluginNotUninstallableError({ message: bundledRefusalMessage(pluginId) });
96
+ }
97
+ if ((await resolveAgentPluginActivation(workspaceLayout.root, pluginId)).verdict === "undetermined") {
98
+ throw new AgentPluginNotUninstallableError({ message: malformedEntryRefusalMessage(pluginId) });
99
+ }
100
+ return { workspaceRoot: workspaceLayout.root, packagesDir: workspaceLayout.packages, matches };
101
+ }
102
+ function bundledRefusalMessage(pluginId) {
103
+ return (`Agent Plugin '${pluginId}' is bundled with ${ports.productName} and cannot be uninstalled — it is re-seeded on every boot ` +
104
+ "(seed-bundled.ts), so removing its files now would silently reappear on the next restart. To stop it being " +
105
+ "used, disable it through the host's activation control.");
106
+ }
107
+ function malformedEntryRefusalMessage(pluginId) {
108
+ return (`Agent Plugin '${pluginId}' cannot be uninstalled: its entry in this workspace's activation record is malformed, so ` +
109
+ `whether it is bundled with ${ports.productName} (and would be re-seeded on the next boot) cannot be established. Nothing was ` +
110
+ "removed. Tell the user an operator has to repair that entry in activations.json first; until then this " +
111
+ "plugin's tool calls and plugin-pinned runs are refused.");
112
+ }
113
+ async function removeFrozenPackageTree(root) {
114
+ await makeTreeWritable(root);
115
+ await rm(root, { recursive: true, force: true });
116
+ }
117
+ async function makeTreeWritable(dir) {
118
+ let entries;
119
+ try {
120
+ entries = await readdir(dir, { withFileTypes: true });
121
+ }
122
+ catch {
123
+ return;
124
+ }
125
+ await chmod(dir, 0o700).catch(() => undefined);
126
+ for (const entry of entries) {
127
+ const absolute = path.join(dir, entry.name);
128
+ if (entry.isDirectory()) {
129
+ await makeTreeWritable(absolute);
130
+ }
131
+ else {
132
+ await chmod(absolute, 0o600).catch(() => undefined);
133
+ }
134
+ }
135
+ }
136
+ return { previewAgentPluginUninstall, uninstallAgentPlugin };
137
+ }
138
+ const instances = new WeakMap();
139
+ /** Internal composition; one state/queue instance per injected host context. */
140
+ export function createUninstallModule(ports) {
141
+ let instance = instances.get(ports);
142
+ if (!instance) {
143
+ instance = buildModule(ports);
144
+ instances.set(ports, instance);
145
+ }
146
+ return instance;
147
+ }
148
+ //# sourceMappingURL=uninstall.js.map
@@ -0,0 +1,62 @@
1
+ /**
2
+ * Historical lifecycle rationale, carried with the implementation during consumer adoption.
3
+ * @file The real `AgentPluginArchiveReaderPort` adapter, backed by `yauzl`.
4
+ *
5
+ * Library choice (owner-decided, recorded so it is not re-litigated): `yauzl` — streaming,
6
+ * memory-efficient, built for a backend service parsing an untrusted uploaded archive; it enforces
7
+ * no policy of its own, which is exactly the shape this codebase's port+adapter discipline wants —
8
+ * `install.ts` owns every containment/size/entry-kind rule, this file only turns zip bytes into the
9
+ * `AgentPluginArchiveEntry` shape `install.ts` already knows how to harden against. Rejected:
10
+ * `extract-zip` (bundles its own path sanitization and writes to disk itself — it would extract
11
+ * past the caps `install.ts` enforces mid-stream, fighting this module's own hardening rather than
12
+ * feeding it); `adm-zip` (loads the whole archive into memory and blocks the event loop — the
13
+ * opposite of what a 32MB-capped but still untrusted upload should do); `node-unzip-2` (deprecated,
14
+ * known vulnerabilities). If the choice changes later, only this file and its own tests should need
15
+ * to move — `AgentPluginArchiveReaderPort` is the seam precisely so a library swap never touches
16
+ * `install.ts`'s hardening logic.
17
+ *
18
+ * ---------------------------------------------------------------------------
19
+ * Symlink detection — the one thing this file has to get right that yauzl doesn't do for you
20
+ * ---------------------------------------------------------------------------
21
+ * The base ZIP spec has no first-class symlink concept. The de facto convention (Info-ZIP, Python's
22
+ * `zipfile`, and this module's own `yazl`-based test fixtures all follow it) is: when an entry's
23
+ * "version made by" upper byte names a Unix host (`3`), the upper 16 bits of `externalFileAttributes`
24
+ * are the Unix `st_mode`, and a symlink is `(mode & S_IFMT) === S_IFLNK` — the entry's own content
25
+ * (its "file data") is the link target text. This module never reads that content: `install.ts`
26
+ * rejects every symlink-kind entry outright before any content read would matter, so decoding the
27
+ * target string would be pure waste against bytes that are about to be refused anyway.
28
+ *
29
+ * A zip with NO Unix "version made by" (host byte != 3, e.g. an archiver that only sets the MS-DOS
30
+ * convention) reports `externalFileAttributes`' upper bits as meaningless — this module only
31
+ * consults them when the host byte says Unix, so an ordinary non-Unix-authored zip is never
32
+ * misclassified as containing a symlink it doesn't have.
33
+ *
34
+ * Architectural role:
35
+ * Adapter only — no policy. Every hardening rule this module's header cites lives in `install.ts`
36
+ * and is exercised against BOTH the scripted double (`install.unit.test.ts`) and this real reader
37
+ * (`__tests__/unit/yauzl-archive-reader.unit.test.ts`), per the team directive that the adversarial
38
+ * suite is the acceptance criteria for a real archive library, not just for the double.
39
+ */
40
+ /** Optional yauzl adapter. Pass the peer library explicitly; importing lifecycle never loads it. */
41
+ export interface YauzlEntry {
42
+ readonly versionMadeBy: number;
43
+ readonly externalFileAttributes: number;
44
+ readonly fileName: string;
45
+ readonly uncompressedSize: number;
46
+ }
47
+ export interface YauzlZipFile {
48
+ eachEntry(): AsyncIterable<YauzlEntry>;
49
+ openReadStreamPromise(entry: YauzlEntry): Promise<AsyncIterable<Uint8Array>>;
50
+ close(): void;
51
+ }
52
+ export interface YauzlPort {
53
+ fromBufferPromise(buffer: Buffer, options: {
54
+ lazyEntries: boolean;
55
+ validateEntrySizes: boolean;
56
+ }): Promise<YauzlZipFile>;
57
+ }
58
+ import type { AgentPluginArchiveReaderPort } from "./install.js";
59
+ export declare function createYauzlAgentPluginArchiveReader({ yauzl }: {
60
+ readonly yauzl: YauzlPort;
61
+ }): AgentPluginArchiveReaderPort;
62
+ //# sourceMappingURL=yauzl-archive-reader.d.ts.map
@@ -0,0 +1,73 @@
1
+ /**
2
+ * Unix `st_mode` file-type mask and the symlink bit pattern within it (`S_IFMT` / `S_IFLNK`).
3
+ */
4
+ const S_IFMT = 0xf000;
5
+ const S_IFLNK = 0xa000;
6
+ /**
7
+ * "Version made by" host-system byte for Unix, per the ZIP spec's APPNOTE.TXT §4.4.2.2.
8
+ */
9
+ const UNIX_HOST_SYSTEM = 3;
10
+ /**
11
+ * Any bit in the standard rwx-for-owner/group/other executable mask.
12
+ */
13
+ const EXECUTABLE_MODE_MASK = 0o111;
14
+ export function createYauzlAgentPluginArchiveReader({ yauzl }) {
15
+ return {
16
+ async *entries({ archive }) {
17
+ /**
18
+ * Matches install.ts's own "bytes actually observed, not declared" discipline one layer
19
+ * down: yauzl itself refuses to hand back a stream whose actual decompressed byte count
20
+ * disagrees with the entry's own declared uncompressedSize.
21
+ */
22
+ const zipfile = await yauzl.fromBufferPromise(Buffer.from(archive), {
23
+ lazyEntries: true,
24
+ validateEntrySizes: true,
25
+ });
26
+ try {
27
+ for await (const entry of zipfile.eachEntry()) {
28
+ /**
29
+ * The entry's content (the link target text) is deliberately never read — see this
30
+ * module's header. `install.ts` rejects every symlink entry unconditionally.
31
+ */
32
+ const unixMode = readUnixMode(entry);
33
+ if (entry.fileName.endsWith("/")) {
34
+ yield { kind: "directory", entryPath: entry.fileName };
35
+ continue;
36
+ }
37
+ if (unixMode !== null && (unixMode & S_IFMT) === S_IFLNK) {
38
+ yield { kind: "symlink", entryPath: entry.fileName };
39
+ continue;
40
+ }
41
+ yield {
42
+ kind: "file",
43
+ entryPath: entry.fileName,
44
+ declaredSize: entry.uncompressedSize,
45
+ executable: unixMode !== null && (unixMode & EXECUTABLE_MODE_MASK) !== 0,
46
+ openReadStream: () => streamEntry(zipfile, entry),
47
+ };
48
+ }
49
+ }
50
+ finally {
51
+ zipfile.close();
52
+ }
53
+ },
54
+ };
55
+ }
56
+ /**
57
+ * Returns the entry's Unix `st_mode`, or `null` when the archive's "version made by" does not
58
+ * name a Unix host — in which case `externalFileAttributes`' upper bits carry no Unix meaning and
59
+ * must not be interpreted as one.
60
+ */
61
+ function readUnixMode(entry) {
62
+ const hostSystem = entry.versionMadeBy >>> 8;
63
+ if (hostSystem !== UNIX_HOST_SYSTEM)
64
+ return null;
65
+ return (entry.externalFileAttributes >>> 16) & 0xffff;
66
+ }
67
+ async function* streamEntry(zipfile, entry) {
68
+ const readStream = await zipfile.openReadStreamPromise(entry);
69
+ for await (const chunk of readStream) {
70
+ yield chunk;
71
+ }
72
+ }
73
+ //# sourceMappingURL=yauzl-archive-reader.js.map
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Types for the Agent Plugins 1.0.0 open standard (published by a Technical Steering Committee
3
+ * with maintainers from Amazon, Cursor, Google, Microsoft, OpenAI, and Vercel). A plugin is a
4
+ * directory: a `plugin.json` manifest, an optional `skills/<name>/` tree following the Agent
5
+ * Skills spec, an optional `mcp.json` for MCP server declarations, and optional
6
+ * reverse-domain-namespaced directories (e.g. `com.example.client/`) for client-specific
7
+ * extensions that unrecognizing clients ignore.
8
+ *
9
+ * Field lists below are read from the published schemas (`AGENT_PLUGINS_SCHEMA_URL`,
10
+ * `AGENT_PLUGINS_MCP_SCHEMA_URL`), not inferred — both still carry an index signature since
11
+ * neither type asserts it has captured every future schema addition.
12
+ */
13
+ export declare const AGENT_PLUGINS_SCHEMA_URL = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json";
14
+ export declare const AGENT_PLUGINS_MCP_SCHEMA_URL = "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json";
15
+ export declare const PLUGIN_MANIFEST_FILENAME = "plugin.json";
16
+ export declare const PLUGIN_SKILLS_DIRNAME = "skills";
17
+ export declare const PLUGIN_MCP_MANIFEST_FILENAME = "mcp.json";
18
+ export interface PluginManifestAuthor {
19
+ readonly name?: string;
20
+ readonly email?: string;
21
+ readonly url?: string;
22
+ }
23
+ export interface PluginManifest {
24
+ readonly $schema?: string;
25
+ /** 1-64 chars, lowercase alphanumeric/dots/hyphens, no leading/trailing/doubled separators. */
26
+ readonly name: string;
27
+ readonly version?: string;
28
+ readonly description?: string;
29
+ readonly author?: PluginManifestAuthor;
30
+ readonly homepage?: string;
31
+ readonly repository?: string;
32
+ readonly license?: string;
33
+ readonly keywords?: readonly string[];
34
+ /** Reverse-domain-namespaced client extensions (e.g. `com.example.client`) — the schema assigns
35
+ * no semantics to what's inside each namespace's object. */
36
+ readonly extensions?: Readonly<Record<string, unknown>>;
37
+ readonly [key: string]: unknown;
38
+ }
39
+ export declare function isPluginManifest(value: unknown): value is PluginManifest;
40
+ export type McpServerEntry = {
41
+ readonly type: 'stdio';
42
+ readonly command: string;
43
+ readonly args?: readonly string[];
44
+ readonly env?: Readonly<Record<string, string>>;
45
+ readonly cwd?: string;
46
+ } | {
47
+ readonly type: 'streamable-http';
48
+ readonly url: string;
49
+ readonly headers?: Readonly<Record<string, string>>;
50
+ } | {
51
+ readonly type: 'sse';
52
+ readonly url: string;
53
+ readonly headers?: Readonly<Record<string, string>>;
54
+ };
55
+ export interface McpManifest {
56
+ readonly $schema?: string;
57
+ readonly mcpServers: Readonly<Record<string, McpServerEntry>>;
58
+ }
59
+ export declare function isMcpManifest(value: unknown): value is McpManifest;
60
+ /** Object-argument facade for the legacy structural validator. */
61
+ export declare function validatePluginManifest(required: {
62
+ readonly value: unknown;
63
+ }): boolean;
64
+ /** Object-argument facade for the legacy structural validator. */
65
+ export declare function validateMcpManifest(required: {
66
+ readonly value: unknown;
67
+ }): boolean;
68
+ //# sourceMappingURL=manifest.d.ts.map
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Types for the Agent Plugins 1.0.0 open standard (published by a Technical Steering Committee
3
+ * with maintainers from Amazon, Cursor, Google, Microsoft, OpenAI, and Vercel). A plugin is a
4
+ * directory: a `plugin.json` manifest, an optional `skills/<name>/` tree following the Agent
5
+ * Skills spec, an optional `mcp.json` for MCP server declarations, and optional
6
+ * reverse-domain-namespaced directories (e.g. `com.example.client/`) for client-specific
7
+ * extensions that unrecognizing clients ignore.
8
+ *
9
+ * Field lists below are read from the published schemas (`AGENT_PLUGINS_SCHEMA_URL`,
10
+ * `AGENT_PLUGINS_MCP_SCHEMA_URL`), not inferred — both still carry an index signature since
11
+ * neither type asserts it has captured every future schema addition.
12
+ */
13
+ export const AGENT_PLUGINS_SCHEMA_URL = 'https://agent-plugins.org/schemas/1.0.0/plugin.schema.json';
14
+ export const AGENT_PLUGINS_MCP_SCHEMA_URL = 'https://agent-plugins.org/schemas/1.0.0/mcp.schema.json';
15
+ export const PLUGIN_MANIFEST_FILENAME = 'plugin.json';
16
+ export const PLUGIN_SKILLS_DIRNAME = 'skills';
17
+ export const PLUGIN_MCP_MANIFEST_FILENAME = 'mcp.json';
18
+ export function isPluginManifest(value) {
19
+ if (typeof value !== 'object' || value === null)
20
+ return false;
21
+ const name = value.name;
22
+ return typeof name === 'string' && name.trim().length > 0;
23
+ }
24
+ export function isMcpManifest(value) {
25
+ if (typeof value !== 'object' || value === null)
26
+ return false;
27
+ const servers = value.mcpServers;
28
+ return typeof servers === 'object' && servers !== null && !Array.isArray(servers);
29
+ }
30
+ /** Object-argument facade for the legacy structural validator. */
31
+ export function validatePluginManifest(required) {
32
+ return isPluginManifest(required.value);
33
+ }
34
+ /** Object-argument facade for the legacy structural validator. */
35
+ export function validateMcpManifest(required) {
36
+ return isMcpManifest(required.value);
37
+ }
38
+ //# sourceMappingURL=manifest.js.map
package/package.json ADDED
@@ -0,0 +1,92 @@
1
+ {
2
+ "name": "@jini-ai/agent-plugins",
3
+ "version": "0.4.0",
4
+ "description": "Support for Agent Plugins, the open, vendor-neutral spec (v1.0.0, published 2026-08-06, Technical Steering Committee with maintainers from Amazon, Cursor, Google, Microsoft, OpenAI, and Vercel) for bundling Agent Skills and MCP servers into one portable directory: PluginManifest/McpManifest types + structural validators on the package root, plus the bundled plugins themselves, each on a subpath named for itself (./ui-ux-design). Extracted 2026-08-18 from @jini-ai/plugins, which now only reserves the unrelated, unimplemented ./host format.",
5
+ "license": "Apache-2.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/AINSEP/Jini.git",
9
+ "directory": "packages/agent-plugins"
10
+ },
11
+ "jini": {
12
+ "domain": "tooling",
13
+ "kind": "plugin-packaging",
14
+ "runtime": "universal",
15
+ "entries": {
16
+ ".": "universal",
17
+ "./ui-ux-design/*": "universal",
18
+ "./lifecycle": "node",
19
+ "./lifecycle/node": "node",
20
+ "./lifecycle/yauzl": "node",
21
+ "./manifest": "universal"
22
+ }
23
+ },
24
+ "type": "module",
25
+ "sideEffects": false,
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js",
30
+ "default": "./dist/index.js"
31
+ },
32
+ "./ui-ux-design/*": "./ui-ux-design/*",
33
+ "./lifecycle": {
34
+ "types": "./dist/lifecycle/index.d.ts",
35
+ "import": "./dist/lifecycle/index.js",
36
+ "default": "./dist/lifecycle/index.js"
37
+ },
38
+ "./lifecycle/node": {
39
+ "types": "./dist/lifecycle/node.d.ts",
40
+ "import": "./dist/lifecycle/node.js",
41
+ "default": "./dist/lifecycle/node.js"
42
+ },
43
+ "./lifecycle/yauzl": {
44
+ "types": "./dist/lifecycle/yauzl-archive-reader.d.ts",
45
+ "import": "./dist/lifecycle/yauzl-archive-reader.js",
46
+ "default": "./dist/lifecycle/yauzl-archive-reader.js"
47
+ },
48
+ "./manifest": {
49
+ "types": "./dist/lifecycle/manifest.d.ts",
50
+ "import": "./dist/lifecycle/manifest.js",
51
+ "default": "./dist/lifecycle/manifest.js"
52
+ }
53
+ },
54
+ "files": [
55
+ "dist",
56
+ "README.md",
57
+ "CHANGELOG.md",
58
+ "LICENSE",
59
+ "ui-ux-design",
60
+ "!dist/**/*.test.js",
61
+ "!dist/**/*.test.d.ts",
62
+ "!dist/**/*.test.js.map",
63
+ "!dist/**/*.test.d.ts.map",
64
+ "!dist/**/__tests__/**"
65
+ ],
66
+ "publishConfig": {
67
+ "access": "public",
68
+ "registry": "https://registry.npmjs.org"
69
+ },
70
+ "devDependencies": {
71
+ "@vitest/coverage-v8": "^2.1.9",
72
+ "yazl": "^3.3.1"
73
+ },
74
+ "peerDependencies": {
75
+ "yauzl": "^3.4.0"
76
+ },
77
+ "peerDependenciesMeta": {
78
+ "yauzl": {
79
+ "optional": true
80
+ }
81
+ },
82
+ "dependencies": {
83
+ "@jini-ai/core": "^0.4.0",
84
+ "@jini-ai/platform": "^0.5.0"
85
+ },
86
+ "scripts": {
87
+ "build": "tsc -p tsconfig.json",
88
+ "typecheck": "tsc -p tsconfig.json --noEmit",
89
+ "test": "vitest run",
90
+ "test:coverage": "vitest run --coverage"
91
+ }
92
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$comment": "Example only, not real config. `com.anthropic.claude-code` follows the reverse-DNS convention Agent Plugins §8.2 describes (a client that controls a domain uses its reverse), but the spec publishes no namespace registry — this string is a plausible guess, not a confirmed/registered Claude Code identifier. The spec assigns no portable semantics to what's inside a client's own namespace directory in any case, so this file's shape is illustrative only.",
3
+ "hooks": []
4
+ }
@@ -0,0 +1,4 @@
1
+ {
2
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/mcp.schema.json",
3
+ "mcpServers": {}
4
+ }
@@ -0,0 +1,5 @@
1
+ {
2
+ "$schema": "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json",
3
+ "name": "ui-ux-design",
4
+ "description": "UI/UX design, interface-design-system, accessibility, and shadcn/ui component skills bundled as one portable Agent Plugin."
5
+ }
@@ -0,0 +1,60 @@
1
+ ---
2
+ name: frontend-accessibility
3
+ version: 1.0.0
4
+ last_updated: 2026-02-26
5
+ description: WCAG 2.1 AA compliance guidance for frontend code review and E2E testing.
6
+ ---
7
+
8
+ # Skill: Frontend Accessibility
9
+
10
+ Accessibility is not a feature — it is a baseline quality requirement. A UI component that cannot be operated via keyboard or read by a screen reader is broken for a significant portion of users. This skill provides the WCAG 2.1 AA checklist that Code Inspection and QA/E2E agents use to catch violations before they ship.
11
+
12
+ ## WCAG 2.1 AA — What to Check in Code Inspection
13
+
14
+ ### Perceivable
15
+ - All non-text content has a text alternative (`alt` on images, `aria-label` or `aria-labelledby` on icon buttons).
16
+ - Color is not the only means of conveying information (error state must also have text or icon, not just red color).
17
+ - Minimum contrast ratio: 4.5:1 for normal text, 3:1 for large text and UI components.
18
+ - No content flashes more than 3 times per second.
19
+
20
+ ### Operable
21
+ - All functionality accessible via keyboard (Tab, Shift+Tab, Enter, Space, Arrow keys).
22
+ - Visible focus indicator on all interactive elements — never `outline: none` without a replacement.
23
+ - Skip navigation link for pages with repeated navigation.
24
+ - No keyboard traps — user can always Tab out of a component.
25
+
26
+ ### Understandable
27
+ - Form inputs have associated `<label>` elements (not just placeholder text).
28
+ - Error messages identify the field and describe the error — not just "invalid input".
29
+ - Language attribute on `<html>` element.
30
+
31
+ ### Robust
32
+ - Semantic HTML — use `<button>` for buttons, `<a>` for links, heading hierarchy (`<h1>` → `<h2>` → `<h3>`).
33
+ - ARIA roles only when native HTML semantics are insufficient.
34
+ - ARIA attributes used correctly — do not add `aria-label` to elements that already have visible text.
35
+
36
+ ## Common Violations to Flag in Code Inspection
37
+
38
+ - `<div onClick>` instead of `<button>` — not keyboard accessible, no ARIA role.
39
+ - `<img>` without `alt` attribute.
40
+ - `<input>` without associated `<label>` (placeholder is not a label).
41
+ - Interactive element with no visible focus state.
42
+ - Hardcoded color values with insufficient contrast.
43
+ - Error state communicated only via color change.
44
+
45
+ ## E2E Testing for Accessibility (axe-core integration)
46
+
47
+ - Inject `@axe-core/playwright` in E2E tests.
48
+ - Run `checkA11y()` after each page navigation and after dynamic content loads.
49
+ - Configure to WCAG 2.1 AA ruleset.
50
+ - Treat Critical and Serious violations as test failures.
51
+ - Moderate violations are warnings — log but do not fail.
52
+
53
+ ## Keyboard Navigation Test Checklist
54
+
55
+ - [ ] Can reach all interactive elements via Tab
56
+ - [ ] Can activate buttons with Enter and Space
57
+ - [ ] Can navigate dropdowns/menus with Arrow keys
58
+ - [ ] Modals trap focus correctly (Tab stays inside modal while open)
59
+ - [ ] Focus returns to trigger element when modal closes
60
+ - [ ] Skip link present and functional on pages with navigation