@frontify/fondue 13.7.2 → 13.7.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/Flyout/helpers/getVerticalPositioning.es.js.map +1 -1
- package/dist/components/InputLabel/InputLabel.es.js.map +1 -1
- package/dist/components/Tree/Tree.es.js.map +1 -1
- package/dist/components/Tree/TreeContext.es.js.map +1 -1
- package/dist/components/Tree/TreeItem/DragHandle.es.js.map +1 -1
- package/dist/components/Tree/TreeItem/ExpandButton.es.js.map +1 -1
- package/dist/components/Tree/TreeItem/TreeItem.es.js.map +1 -1
- package/dist/components/Tree/TreeItem/TreeItemMultiselect.es.js.map +1 -1
- package/dist/components/Tree/TreeItem/TreeItemOverlay.es.js.map +1 -1
- package/dist/components/Tree/TreeItem/useMultiselectTreeItem.es.js.map +1 -1
- package/dist/components/Tree/TreeItem/useTreeItem.es.js.map +1 -1
- package/dist/components/Tree/helpers/constants.es.js.map +1 -1
- package/dist/components/Tree/helpers/getMovementAnnouncements.es.js.map +1 -1
- package/dist/components/Tree/helpers/multiselect.es.js.map +1 -1
- package/dist/components/Tree/helpers/multiselectTreeItemstyling.es.js.map +1 -1
- package/dist/components/Tree/helpers/nodes.es.js.map +1 -1
- package/dist/components/Tree/helpers/projection.es.js.map +1 -1
- package/dist/components/Tree/helpers/reducer.es.js.map +1 -1
- package/dist/components/Tree/helpers/sensorsActivationConstraint.es.js.map +1 -1
- package/dist/components/Tree/helpers/treeHandleKeyDown.es.js.map +1 -1
- package/dist/components/Tree/types.es.js.map +1 -1
- package/dist/components/Tree/utils/keyboardCoordinates.es.js.map +1 -1
- package/dist/components/Tree/utils/removeFragmentsAndEnrichChildren.es.js.map +1 -1
- package/dist/components/Tree/utils/useDeepCompareEffect.es.js.map +1 -1
- package/dist/index.cjs.js.map +1 -1
- package/dist/index.d.ts +261 -3
- package/dist/index.umd.js.map +1 -1
- package/dist/packages/components/style.css +1 -1
- package/dist/packages/rte/style.css +1 -1
- package/dist/tools/codemod/index.js +87 -1
- package/dist/tools/internal/index.js +30 -36
- package/package.json +5 -6
- package/dist/tools/sdk-cli/adapters/claude-skill/skill/SKILL.md +0 -207
- package/dist/tools/sdk-cli/adapters/claude-skill/skill/reference.md +0 -235
- package/dist/tools/sdk-cli/index.js +0 -224
|
@@ -1,235 +0,0 @@
|
|
|
1
|
-
## Contents
|
|
2
|
-
|
|
3
|
-
- [Exports](#exports) — the three singletons and the shared `QueryApi` shape
|
|
4
|
-
- [Components](#components) — `ComponentFilter`, `ComponentNode`, icons
|
|
5
|
-
- [Tokens](#tokens) — `TokenFilter`, `TokenNode`, utilities
|
|
6
|
-
- [Guides](#guides) — guide ids and shape
|
|
7
|
-
- [Facets](#facets) — `FacetNode` shape and how to reach them
|
|
8
|
-
- [Error semantics](#error-semantics) — what throws, what returns `undefined`
|
|
9
|
-
- [Runtime guarantees](#runtime-guarantees) — module type, Node version, deps
|
|
10
|
-
|
|
11
|
-
## Exports
|
|
12
|
-
|
|
13
|
-
```ts
|
|
14
|
-
import { components, tokens, guides } from '@frontify/fondue/sdk';
|
|
15
|
-
```
|
|
16
|
-
|
|
17
|
-
Three singletons. Each has the same query surface; each returns its own node types.
|
|
18
|
-
|
|
19
|
-
```ts
|
|
20
|
-
interface QueryApi<Node, Filter> {
|
|
21
|
-
list(): readonly Node[];
|
|
22
|
-
get(id: string): Node | undefined; // never throws; returns undefined for unknown ids
|
|
23
|
-
has(id: string): boolean;
|
|
24
|
-
where(filter: Filter): readonly Node[]; // AND-combined; array-valued clauses OR within
|
|
25
|
-
readonly size: number;
|
|
26
|
-
}
|
|
27
|
-
```
|
|
28
|
-
|
|
29
|
-
Plain arrays returned from `list()` / `where()` / `node.related()` are arrays — they don't carry the query surface. Use native `.filter` / `.find`, or navigate back to a facet to re-query.
|
|
30
|
-
|
|
31
|
-
## Components
|
|
32
|
-
|
|
33
|
-
```ts
|
|
34
|
-
components: QueryApi<ComponentNode, ComponentFilter> & {
|
|
35
|
-
categories(): readonly ComponentFacetNode[];
|
|
36
|
-
category(name: string): ComponentFacetNode | undefined;
|
|
37
|
-
tags(): readonly ComponentFacetNode[];
|
|
38
|
-
tag(name: string): ComponentFacetNode | undefined;
|
|
39
|
-
};
|
|
40
|
-
```
|
|
41
|
-
|
|
42
|
-
### `ComponentFilter`
|
|
43
|
-
|
|
44
|
-
| Clause | Type | Notes |
|
|
45
|
-
| ---------- | ----------------------------- | ------------------------------------------------------------------- |
|
|
46
|
-
| `category` | `string \| readonly string[]` | OR within array |
|
|
47
|
-
| `status` | `string \| readonly string[]` | OR within array |
|
|
48
|
-
| `tag` | `string \| readonly string[]` | Matches if component carries any tag in the array |
|
|
49
|
-
| `text` | `string` | Case-insensitive substring across name, description, category, tags |
|
|
50
|
-
|
|
51
|
-
All clauses AND-combine.
|
|
52
|
-
|
|
53
|
-
### `ComponentNode`
|
|
54
|
-
|
|
55
|
-
```ts
|
|
56
|
-
interface ComponentNode {
|
|
57
|
-
// scalar fields
|
|
58
|
-
name: string;
|
|
59
|
-
description: string;
|
|
60
|
-
status: string; // '' for icons
|
|
61
|
-
importStatement: string;
|
|
62
|
-
instructions: string; // hand-written usage notes, often empty
|
|
63
|
-
props: readonly ComponentProp[];
|
|
64
|
-
subComponents: readonly ComponentSubComponent[];
|
|
65
|
-
examples: readonly ComponentExample[];
|
|
66
|
-
typeDefinitions: Readonly<Record<string, string>>;
|
|
67
|
-
|
|
68
|
-
// graph edges (methods)
|
|
69
|
-
category(): ComponentFacetNode; // throws on data inconsistency only
|
|
70
|
-
tags(): readonly ComponentFacetNode[];
|
|
71
|
-
related(): readonly ComponentNode[]; // unknown names silently skipped
|
|
72
|
-
toJSON(): ComponentDetails;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
interface ComponentProp {
|
|
76
|
-
name: string;
|
|
77
|
-
type: string;
|
|
78
|
-
required: boolean;
|
|
79
|
-
defaultValue: string | null;
|
|
80
|
-
description: string;
|
|
81
|
-
deprecated: boolean;
|
|
82
|
-
deprecationMessage: string;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
interface ComponentExample {
|
|
86
|
-
name: string;
|
|
87
|
-
description: string;
|
|
88
|
-
code: string;
|
|
89
|
-
isCanonical: boolean; // there is at most one canonical example per component
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
interface ComponentSubComponent {
|
|
93
|
-
name: string; // e.g. 'Dialog.Header'
|
|
94
|
-
props: readonly ComponentProp[];
|
|
95
|
-
}
|
|
96
|
-
```
|
|
97
|
-
|
|
98
|
-
### Icons
|
|
99
|
-
|
|
100
|
-
Icons are components with `category: 'icon'`. They have:
|
|
101
|
-
|
|
102
|
-
- empty `status`, empty `props`, empty `related()`, empty `subComponents`
|
|
103
|
-
- a non-empty `importStatement` (e.g. `import { IconAdobeCreativeCloud } from '@frontify/fondue/icons';`)
|
|
104
|
-
- tags (e.g. `'arrow'`, `'brand'`)
|
|
105
|
-
|
|
106
|
-
Filter to icons with `components.where({ category: 'icon' })`. Detect at the node with `node.category().name === 'icon'`.
|
|
107
|
-
|
|
108
|
-
## Tokens
|
|
109
|
-
|
|
110
|
-
```ts
|
|
111
|
-
tokens: QueryApi<TokenNode, TokenFilter> & {
|
|
112
|
-
categories(): readonly TokenFacetNode[];
|
|
113
|
-
category(name: string): TokenFacetNode | undefined;
|
|
114
|
-
types(): readonly TokenFacetNode[];
|
|
115
|
-
type(name: TokenValueType): TokenFacetNode | undefined;
|
|
116
|
-
utilities: TokenUtilitiesApi;
|
|
117
|
-
};
|
|
118
|
-
|
|
119
|
-
type TokenValueType = 'color' | 'float' | 'shadow' | 'string';
|
|
120
|
-
```
|
|
121
|
-
|
|
122
|
-
### `TokenFilter`
|
|
123
|
-
|
|
124
|
-
| Clause | Type | Notes |
|
|
125
|
-
| ------------------- | --------------------------------------------- | --------------------------------------------------- |
|
|
126
|
-
| `category` | `string \| readonly string[]` | e.g. `'colors'`, `'sizes'` |
|
|
127
|
-
| `type` | `TokenValueType \| readonly TokenValueType[]` | |
|
|
128
|
-
| `themeable` | `boolean` | |
|
|
129
|
-
| `keyPathStartsWith` | `string` | Dot-joined keyPath prefix, e.g. `'colors.charts'` |
|
|
130
|
-
| `text` | `string` | Case-insensitive against id, tailwindClass, keyPath |
|
|
131
|
-
|
|
132
|
-
### `TokenNode`
|
|
133
|
-
|
|
134
|
-
```ts
|
|
135
|
-
interface TokenNode {
|
|
136
|
-
id: string; // e.g. 'color-charts-primary-default'
|
|
137
|
-
value: string; // often `var(--token)` or a literal
|
|
138
|
-
cssVariable: string; // 'var(--color-charts-primary-default)'
|
|
139
|
-
tailwindClass: string; // e.g. '*-charts-primary'
|
|
140
|
-
themeable: boolean;
|
|
141
|
-
keyPath: readonly string[]; // ['colors','charts','primary','default']
|
|
142
|
-
|
|
143
|
-
category(): TokenFacetNode;
|
|
144
|
-
type(): TokenFacetNode;
|
|
145
|
-
toJSON(): Token;
|
|
146
|
-
}
|
|
147
|
-
```
|
|
148
|
-
|
|
149
|
-
### Utilities
|
|
150
|
-
|
|
151
|
-
```ts
|
|
152
|
-
tokens.utilities: QueryApi<TokenUtilityNode, TokenUtilityFilter> & {
|
|
153
|
-
classes(): readonly string[];
|
|
154
|
-
};
|
|
155
|
-
```
|
|
156
|
-
|
|
157
|
-
No `categories()` / `types()` on utilities — they're a leaf domain.
|
|
158
|
-
|
|
159
|
-
```ts
|
|
160
|
-
interface TokenUtilityFilter {
|
|
161
|
-
themeable?: boolean;
|
|
162
|
-
keyPathStartsWith?: string; // typography utilities live under 'utilities.text'
|
|
163
|
-
text?: string;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
interface TokenUtilityNode {
|
|
167
|
-
id: string;
|
|
168
|
-
tailwindClass: string; // e.g. 'tw-body-large-strong'
|
|
169
|
-
themeable: boolean;
|
|
170
|
-
keyPath: readonly string[];
|
|
171
|
-
properties: readonly TokenUtilityProperty[];
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
interface TokenUtilityProperty {
|
|
175
|
-
id: string;
|
|
176
|
-
type: TokenValueType;
|
|
177
|
-
value: string;
|
|
178
|
-
cssVariable: string;
|
|
179
|
-
}
|
|
180
|
-
```
|
|
181
|
-
|
|
182
|
-
## Guides
|
|
183
|
-
|
|
184
|
-
```ts
|
|
185
|
-
guides: QueryApi<Guide, GuideFilter>;
|
|
186
|
-
|
|
187
|
-
interface Guide {
|
|
188
|
-
id: string; // slug from filename, e.g. 'getting-started'
|
|
189
|
-
title: string; // extracted from the first `# Title` line
|
|
190
|
-
content: string; // raw markdown body, includes the leading `# Title`
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
interface GuideFilter {
|
|
194
|
-
text?: string; // case-insensitive against id, title, content
|
|
195
|
-
}
|
|
196
|
-
```
|
|
197
|
-
|
|
198
|
-
Known ids include `getting-started`, `contributing`, `upgrading`. The bundled set may grow; call `guides.list()` to see the current corpus.
|
|
199
|
-
|
|
200
|
-
## Facets
|
|
201
|
-
|
|
202
|
-
`ComponentFacetNode` and `TokenFacetNode` share this shape:
|
|
203
|
-
|
|
204
|
-
```ts
|
|
205
|
-
interface FacetNode<Node, Filter> {
|
|
206
|
-
name: string;
|
|
207
|
-
list(): readonly Node[];
|
|
208
|
-
get(id: string): Node | undefined;
|
|
209
|
-
has(id: string): boolean;
|
|
210
|
-
where(filter: Filter): readonly Node[];
|
|
211
|
-
size: number;
|
|
212
|
-
}
|
|
213
|
-
```
|
|
214
|
-
|
|
215
|
-
Reach a facet via the domain's accessor or via a node's edge:
|
|
216
|
-
|
|
217
|
-
```ts
|
|
218
|
-
components.category('input'); // facet
|
|
219
|
-
components.tag('cta'); // facet
|
|
220
|
-
components.get('Button')?.category(); // facet from a node
|
|
221
|
-
tokens.category('colors')?.where({ themeable: true });
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
## Error semantics
|
|
225
|
-
|
|
226
|
-
- `get(id)` never throws — returns `undefined` for unknown ids.
|
|
227
|
-
- `node.related()` silently skips unknown names.
|
|
228
|
-
- `node.category()` throws only on internal data inconsistency (a component referencing a category that doesn't exist) — treat as a bug to report, not a runtime case to handle.
|
|
229
|
-
- `where(filter)` with an empty filter returns every node in the domain.
|
|
230
|
-
|
|
231
|
-
## Runtime guarantees
|
|
232
|
-
|
|
233
|
-
- Synchronous, zero file I/O.
|
|
234
|
-
- Pure ES module; Node 18+.
|
|
235
|
-
- No peer dependencies, no React, no DOM.
|
|
@@ -1,224 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from "commander";
|
|
3
|
-
import { existsSync, readFileSync, writeFileSync, readdirSync, statSync, rmSync, mkdirSync, copyFileSync } from "node:fs";
|
|
4
|
-
import { dirname, resolve, join } from "node:path";
|
|
5
|
-
import { homedir } from "node:os";
|
|
6
|
-
import { fileURLToPath } from "node:url";
|
|
7
|
-
const log = (message) => {
|
|
8
|
-
process.stdout.write(`${message}
|
|
9
|
-
`);
|
|
10
|
-
};
|
|
11
|
-
const fail = (message, code = 1) => {
|
|
12
|
-
process.stderr.write(`fondue: ${message}
|
|
13
|
-
`);
|
|
14
|
-
process.exit(code);
|
|
15
|
-
};
|
|
16
|
-
const TOOL_DIR = dirname(fileURLToPath(import.meta.url));
|
|
17
|
-
const SKILL_SOURCE_DIR = resolve(TOOL_DIR, "adapters", "claude-skill", "skill");
|
|
18
|
-
const SKILL_NAME = "fondue";
|
|
19
|
-
const CONSUMER_PACKAGE = "@frontify/fondue";
|
|
20
|
-
const FONDUE_VERSION = "13.7.2";
|
|
21
|
-
const scopeOf = (options) => options.user ? "user" : "project";
|
|
22
|
-
const projectRoot = () => {
|
|
23
|
-
let dir = process.cwd();
|
|
24
|
-
while (true) {
|
|
25
|
-
if (existsSync(join(dir, "package.json")) || existsSync(join(dir, ".git"))) {
|
|
26
|
-
return dir;
|
|
27
|
-
}
|
|
28
|
-
const parent = dirname(dir);
|
|
29
|
-
if (parent === dir) {
|
|
30
|
-
return process.cwd();
|
|
31
|
-
}
|
|
32
|
-
dir = parent;
|
|
33
|
-
}
|
|
34
|
-
};
|
|
35
|
-
const skillsRoot = (scope) => scope === "user" ? join(homedir(), ".claude", "skills") : join(projectRoot(), ".claude", "skills");
|
|
36
|
-
const targetDirFor = (scope) => join(skillsRoot(scope), SKILL_NAME);
|
|
37
|
-
const stampPathFor = (scope) => join(skillsRoot(scope), `.${SKILL_NAME}-installed.json`);
|
|
38
|
-
const readStamp = (stampPath) => {
|
|
39
|
-
if (!existsSync(stampPath)) {
|
|
40
|
-
return { status: "missing" };
|
|
41
|
-
}
|
|
42
|
-
try {
|
|
43
|
-
const stamp = JSON.parse(readFileSync(stampPath, "utf8"));
|
|
44
|
-
return { status: "ok", stamp };
|
|
45
|
-
} catch (error) {
|
|
46
|
-
return { status: "corrupted", error: error instanceof Error ? error.message : String(error) };
|
|
47
|
-
}
|
|
48
|
-
};
|
|
49
|
-
const writeStamp = (stampPath, scope) => {
|
|
50
|
-
const stamp = {
|
|
51
|
-
package: CONSUMER_PACKAGE,
|
|
52
|
-
version: FONDUE_VERSION,
|
|
53
|
-
installedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
54
|
-
scope
|
|
55
|
-
};
|
|
56
|
-
writeFileSync(stampPath, `${JSON.stringify(stamp, null, 2)}
|
|
57
|
-
`);
|
|
58
|
-
};
|
|
59
|
-
const install = (options) => {
|
|
60
|
-
const scope = scopeOf(options);
|
|
61
|
-
const target = targetDirFor(scope);
|
|
62
|
-
const stampPath = stampPathFor(scope);
|
|
63
|
-
if (!existsSync(SKILL_SOURCE_DIR)) {
|
|
64
|
-
fail(`Bundled skill directory missing at ${SKILL_SOURCE_DIR}. Reinstall ${CONSUMER_PACKAGE}.`);
|
|
65
|
-
}
|
|
66
|
-
const files = readdirSync(SKILL_SOURCE_DIR).filter((name) => statSync(join(SKILL_SOURCE_DIR, name)).isFile());
|
|
67
|
-
if (!files.includes("SKILL.md")) {
|
|
68
|
-
fail(`Bundled skill is missing SKILL.md. Reinstall ${CONSUMER_PACKAGE}.`);
|
|
69
|
-
}
|
|
70
|
-
let previousVersion = null;
|
|
71
|
-
if (existsSync(target)) {
|
|
72
|
-
const read = readStamp(stampPath);
|
|
73
|
-
if (read.status === "ok") {
|
|
74
|
-
previousVersion = read.stamp.version;
|
|
75
|
-
} else if (read.status === "corrupted" && !options.force) {
|
|
76
|
-
fail(
|
|
77
|
-
`Stamp file at ${stampPath} is unreadable (${read.error}). Inspect it, or pass --force to overwrite both the stamp and the skill.`
|
|
78
|
-
);
|
|
79
|
-
} else if (read.status === "missing" && !options.force) {
|
|
80
|
-
fail(
|
|
81
|
-
`${target} exists and was not installed by this CLI. Remove it manually, or pass --force to overwrite.`
|
|
82
|
-
);
|
|
83
|
-
}
|
|
84
|
-
rmSync(target, { recursive: true, force: true });
|
|
85
|
-
}
|
|
86
|
-
mkdirSync(target, { recursive: true });
|
|
87
|
-
for (const name of files) {
|
|
88
|
-
copyFileSync(join(SKILL_SOURCE_DIR, name), join(target, name));
|
|
89
|
-
}
|
|
90
|
-
writeStamp(stampPath, scope);
|
|
91
|
-
if (previousVersion === null) {
|
|
92
|
-
log(`Installed Fondue Claude Code skill (${CONSUMER_PACKAGE}@${FONDUE_VERSION}) → ${target}`);
|
|
93
|
-
} else if (previousVersion === FONDUE_VERSION) {
|
|
94
|
-
log(`Refreshed Fondue Claude Code skill at ${CONSUMER_PACKAGE}@${FONDUE_VERSION} → ${target}`);
|
|
95
|
-
} else {
|
|
96
|
-
log(
|
|
97
|
-
`Updated Fondue Claude Code skill (${CONSUMER_PACKAGE}@${previousVersion} → @${FONDUE_VERSION}) → ${target}`
|
|
98
|
-
);
|
|
99
|
-
}
|
|
100
|
-
log(scope === "project" ? "Scope: project (this directory only)." : "Scope: user (every project).");
|
|
101
|
-
if (previousVersion === null) {
|
|
102
|
-
log("");
|
|
103
|
-
log("Next steps:");
|
|
104
|
-
log(` • Open Claude Code in a project that has \`${CONSUMER_PACKAGE}\` installed.`);
|
|
105
|
-
log(' • Ask "what Fondue component should I use for X?" — Claude picks the skill up automatically,');
|
|
106
|
-
log(` or invoke it explicitly with /${SKILL_NAME}.`);
|
|
107
|
-
if (scope === "project") {
|
|
108
|
-
log(` • Commit \`.claude/skills/${SKILL_NAME}/\` if you want it shared with your team.`);
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
const status = (options) => {
|
|
113
|
-
const scope = scopeOf(options);
|
|
114
|
-
const target = targetDirFor(scope);
|
|
115
|
-
const stampPath = stampPathFor(scope);
|
|
116
|
-
const targetExists = existsSync(target);
|
|
117
|
-
const read = readStamp(stampPath);
|
|
118
|
-
if (!targetExists && read.status === "missing") {
|
|
119
|
-
log(`Not installed at ${target}.`);
|
|
120
|
-
return;
|
|
121
|
-
}
|
|
122
|
-
if (read.status === "ok") {
|
|
123
|
-
if (!targetExists) {
|
|
124
|
-
log(`Stamp at ${stampPath} but skill directory is missing at ${target}.`);
|
|
125
|
-
log("Run `fondue adapter install claude-skill` to repair.");
|
|
126
|
-
return;
|
|
127
|
-
}
|
|
128
|
-
log(`Installed at ${target}`);
|
|
129
|
-
log(` package: ${read.stamp.package}@${read.stamp.version}`);
|
|
130
|
-
log(` scope: ${read.stamp.scope}`);
|
|
131
|
-
log(` installedAt: ${read.stamp.installedAt}`);
|
|
132
|
-
return;
|
|
133
|
-
}
|
|
134
|
-
if (read.status === "corrupted") {
|
|
135
|
-
log(`Stamp at ${stampPath} is unreadable (${read.error}).`);
|
|
136
|
-
if (targetExists) {
|
|
137
|
-
log(`Skill directory present at ${target}. Re-run install to repair, or pass --force.`);
|
|
138
|
-
}
|
|
139
|
-
return;
|
|
140
|
-
}
|
|
141
|
-
log(`Directory exists at ${target} but was not installed by this CLI.`);
|
|
142
|
-
};
|
|
143
|
-
const uninstall = (options) => {
|
|
144
|
-
const scope = scopeOf(options);
|
|
145
|
-
const target = targetDirFor(scope);
|
|
146
|
-
const stampPath = stampPathFor(scope);
|
|
147
|
-
const targetExists = existsSync(target);
|
|
148
|
-
const stampExists = existsSync(stampPath);
|
|
149
|
-
if (!targetExists && !stampExists) {
|
|
150
|
-
log(`Nothing to uninstall — no skill at ${target}.`);
|
|
151
|
-
return;
|
|
152
|
-
}
|
|
153
|
-
if (targetExists) {
|
|
154
|
-
const read = readStamp(stampPath);
|
|
155
|
-
if (read.status === "missing" && !options.force) {
|
|
156
|
-
fail(`${target} exists but was not installed by this CLI. Remove it manually, or pass --force.`);
|
|
157
|
-
}
|
|
158
|
-
if (read.status === "corrupted" && !options.force) {
|
|
159
|
-
fail(
|
|
160
|
-
`Stamp file at ${stampPath} is unreadable (${read.error}). Inspect it, or pass --force to remove the skill and the stamp.`
|
|
161
|
-
);
|
|
162
|
-
}
|
|
163
|
-
rmSync(target, { recursive: true, force: true });
|
|
164
|
-
}
|
|
165
|
-
if (stampExists) {
|
|
166
|
-
rmSync(stampPath, { force: true });
|
|
167
|
-
}
|
|
168
|
-
log(`Removed ${target}.`);
|
|
169
|
-
};
|
|
170
|
-
const claudeSkill = {
|
|
171
|
-
name: "claude-skill",
|
|
172
|
-
description: "Claude Code skill — teaches Claude to query the Fondue SDK directly",
|
|
173
|
-
install,
|
|
174
|
-
uninstall,
|
|
175
|
-
status
|
|
176
|
-
};
|
|
177
|
-
const ADAPTERS = [claudeSkill];
|
|
178
|
-
const adapterNames = () => ADAPTERS.map((i) => i.name).join(", ");
|
|
179
|
-
const adapterCatalog = () => {
|
|
180
|
-
const width = Math.max(...ADAPTERS.map((i) => i.name.length));
|
|
181
|
-
return ADAPTERS.map((i) => ` ${i.name.padEnd(width)} ${i.description}`).join("\n");
|
|
182
|
-
};
|
|
183
|
-
const requireAdapter = (name, verb) => {
|
|
184
|
-
if (name === void 0) {
|
|
185
|
-
return fail(
|
|
186
|
-
`Specify which adapter to ${verb}. Available:
|
|
187
|
-
${adapterCatalog()}
|
|
188
|
-
|
|
189
|
-
Example: fondue adapter ${verb} ${ADAPTERS[0]?.name ?? "<name>"}`
|
|
190
|
-
);
|
|
191
|
-
}
|
|
192
|
-
const adapter2 = ADAPTERS.find((i) => i.name === name);
|
|
193
|
-
if (adapter2 !== void 0) {
|
|
194
|
-
return adapter2;
|
|
195
|
-
}
|
|
196
|
-
return fail(`Unknown adapter "${name}". Available: ${adapterNames()}.`);
|
|
197
|
-
};
|
|
198
|
-
const program = new Command().name("fondue").description("CLI for Frontify Fondue.").version("13.7.2");
|
|
199
|
-
const adapter = program.command("adapter").description("Install and manage Fondue SDK adapters (Claude Code skill, MCP, REST, …).");
|
|
200
|
-
adapter.command("install [name]").aliases(["add", "update"]).description(
|
|
201
|
-
"Install an adapter, or refresh an existing install in place. Run `adapter list` to see what `<name>` accepts."
|
|
202
|
-
).option("--user", "Install for the current user instead of the current project").option("--force", "Overwrite a directory at the target path that was not installed by this CLI").action((name, options) => {
|
|
203
|
-
requireAdapter(name, "install").install(options);
|
|
204
|
-
});
|
|
205
|
-
adapter.command("uninstall [name]").alias("remove").description("Uninstall an adapter previously installed by this CLI.").option("--user", "Target the user-scope install").option("--force", "Force removal even if not installed by this CLI").action((name, options) => {
|
|
206
|
-
requireAdapter(name, "uninstall").uninstall(options);
|
|
207
|
-
});
|
|
208
|
-
adapter.command("status [name]").description("Show installation status for one adapter, or every adapter when no name is given.").option("--user", "Target the user-scope install").action((name, options) => {
|
|
209
|
-
if (name !== void 0) {
|
|
210
|
-
requireAdapter(name, "status").status(options);
|
|
211
|
-
return;
|
|
212
|
-
}
|
|
213
|
-
for (const [index, registered] of ADAPTERS.entries()) {
|
|
214
|
-
if (index > 0) {
|
|
215
|
-
log("");
|
|
216
|
-
}
|
|
217
|
-
log(`[${registered.name}]`);
|
|
218
|
-
registered.status(options);
|
|
219
|
-
}
|
|
220
|
-
});
|
|
221
|
-
adapter.command("list").alias("ls").description("List every registered adapter and what it does.").action(() => {
|
|
222
|
-
log(adapterCatalog());
|
|
223
|
-
});
|
|
224
|
-
program.parse();
|