@flareum/mcp 0.2.6 → 0.2.9
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/first-pull.js +4 -1
- package/dist/key-check.d.ts +14 -0
- package/dist/key-check.js +34 -0
- package/dist/project-config.d.ts +10 -0
- package/dist/project-config.js +45 -0
- package/dist/server.js +15 -0
- package/package.json +1 -1
- package/skill/SKILL.md +36 -0
package/dist/first-pull.js
CHANGED
|
@@ -39,8 +39,11 @@ export const firstPullReport = (outcome) => {
|
|
|
39
39
|
return `[flareum] could NOT pull the stylesheets into ${outcome.dir} — ${outcome.error}. `
|
|
40
40
|
+ 'The token tools still work; run `npx -p @flareum/mcp flareum pull` to retry.';
|
|
41
41
|
const { written, failed } = outcome.result;
|
|
42
|
+
// Naming the import is the difference between 24 files on disk and 24 files that apply: nothing
|
|
43
|
+
// loads them until the project's global stylesheet says so.
|
|
42
44
|
const head = `[flareum] no local stylesheets — pulled ${written.length} into ${outcome.dir}. `
|
|
43
|
-
+
|
|
45
|
+
+ `Import them once in your global stylesheet — \`@use '${outcome.dir.replace(/^src\//, './')}/main';\` `
|
|
46
|
+
+ '— then `npx -p @flareum/mcp flareum watch` keeps them current (FLAREUM_AUTO_PULL=off to stop).';
|
|
44
47
|
return failed.length ? `${head}\n[flareum] ${failed.length} could not be written: `
|
|
45
48
|
+ failed.map(({ path, reason }) => `${path} — ${reason}`).join('; ') : head;
|
|
46
49
|
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export type KeyCheck = {
|
|
2
|
+
usable: true;
|
|
3
|
+
} | {
|
|
4
|
+
usable: false;
|
|
5
|
+
fatal: true;
|
|
6
|
+
message: string;
|
|
7
|
+
} | {
|
|
8
|
+
usable: false;
|
|
9
|
+
fatal: false;
|
|
10
|
+
message: string;
|
|
11
|
+
};
|
|
12
|
+
export declare const classifyKeyCheck: (error: unknown) => KeyCheck;
|
|
13
|
+
/** One cheap authenticated call, so "connected" cannot mean "holding a key the server rejects". */
|
|
14
|
+
export declare const checkKey: (probe: () => Promise<unknown>) => Promise<KeyCheck>;
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { FlareumApiError } from './client.js';
|
|
2
|
+
// A revoked key answers this; a project with nothing pushed answers NOT_PUBLISHED, which proves the
|
|
3
|
+
// key works. Anything else is the network, and a blip must not take the server down.
|
|
4
|
+
const REJECTED = new Set(['UNAUTHORIZED', 'FORBIDDEN', 'HTTP_401', 'HTTP_403']);
|
|
5
|
+
export const classifyKeyCheck = (error) => {
|
|
6
|
+
if (!error)
|
|
7
|
+
return { usable: true };
|
|
8
|
+
if (error instanceof FlareumApiError && REJECTED.has(error.code))
|
|
9
|
+
return {
|
|
10
|
+
usable: false,
|
|
11
|
+
fatal: true,
|
|
12
|
+
message: `[flareum] ${error.message} ${error.hint}\n`
|
|
13
|
+
+ '[flareum] Refusing to start: a connected server holding a dead key reports itself healthy, '
|
|
14
|
+
+ 'and every token lookup then fails for a reason nothing on screen explains.',
|
|
15
|
+
};
|
|
16
|
+
if (error instanceof FlareumApiError)
|
|
17
|
+
return { usable: true };
|
|
18
|
+
return {
|
|
19
|
+
usable: false,
|
|
20
|
+
fatal: false,
|
|
21
|
+
message: `[flareum] could not verify the key — ${error instanceof Error ? error.message : String(error)}. `
|
|
22
|
+
+ 'Starting anyway; the token tools will report their own errors.',
|
|
23
|
+
};
|
|
24
|
+
};
|
|
25
|
+
/** One cheap authenticated call, so "connected" cannot mean "holding a key the server rejects". */
|
|
26
|
+
export const checkKey = async (probe) => {
|
|
27
|
+
try {
|
|
28
|
+
await probe();
|
|
29
|
+
return { usable: true };
|
|
30
|
+
}
|
|
31
|
+
catch (error) {
|
|
32
|
+
return classifyKeyCheck(error);
|
|
33
|
+
}
|
|
34
|
+
};
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export type ConfigWrite = {
|
|
2
|
+
wrote: false;
|
|
3
|
+
reason: 'already-there' | 'no-token';
|
|
4
|
+
} | {
|
|
5
|
+
wrote: true;
|
|
6
|
+
path: string;
|
|
7
|
+
gitignored: boolean;
|
|
8
|
+
};
|
|
9
|
+
export declare const writeProjectConfig: (cwd: string, env: Record<string, string | undefined>, out?: string) => Promise<ConfigWrite>;
|
|
10
|
+
export declare const projectConfigReport: (result: ConfigWrite) => string | null;
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { appendFile, mkdir, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { CONFIG_PATH } from './config.js';
|
|
5
|
+
const IGNORE_LINE = '.flareum/';
|
|
6
|
+
// Appended only when the pattern is absent: this file holds a key, and a project that commits it
|
|
7
|
+
// has published one. Never rewrites an existing .gitignore beyond that one line.
|
|
8
|
+
const ensureGitignored = async (cwd) => {
|
|
9
|
+
const path = join(cwd, '.gitignore');
|
|
10
|
+
try {
|
|
11
|
+
if (existsSync(path) && readFileSync(path, 'utf8').split('\n').some(l => l.trim() === IGNORE_LINE))
|
|
12
|
+
return true;
|
|
13
|
+
if (!existsSync(join(cwd, '.git')))
|
|
14
|
+
return false;
|
|
15
|
+
await appendFile(path, `\n# Holds a Flareum API key\n${IGNORE_LINE}\n`, 'utf8');
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
// Gives the CLI the key the server was started with, so a connected project is never keyless.
|
|
23
|
+
// Written once and never overwritten — an existing file is the developer's.
|
|
24
|
+
export const writeProjectConfig = async (cwd, env, out) => {
|
|
25
|
+
const path = join(cwd, CONFIG_PATH);
|
|
26
|
+
if (existsSync(path))
|
|
27
|
+
return { wrote: false, reason: 'already-there' };
|
|
28
|
+
if (!env.FLAREUM_TOKEN)
|
|
29
|
+
return { wrote: false, reason: 'no-token' };
|
|
30
|
+
const gitignored = await ensureGitignored(cwd);
|
|
31
|
+
await mkdir(dirname(path), { recursive: true });
|
|
32
|
+
await writeFile(path, `${JSON.stringify({
|
|
33
|
+
token: env.FLAREUM_TOKEN,
|
|
34
|
+
...(env.FLAREUM_API ? { api: env.FLAREUM_API } : {}),
|
|
35
|
+
...(out ? { out } : {}),
|
|
36
|
+
}, null, 2)}\n`, 'utf8');
|
|
37
|
+
return { wrote: true, path, gitignored };
|
|
38
|
+
};
|
|
39
|
+
export const projectConfigReport = (result) => {
|
|
40
|
+
if (!result.wrote)
|
|
41
|
+
return null;
|
|
42
|
+
const head = `[flareum] wrote ${CONFIG_PATH} so \`flareum pull\` works here without a key argument.`;
|
|
43
|
+
return result.gitignored ? head
|
|
44
|
+
: `${head} It holds a key — add ${IGNORE_LINE} to your .gitignore.`;
|
|
45
|
+
};
|
package/dist/server.js
CHANGED
|
@@ -9,6 +9,8 @@ import { FlareumApiError, FlareumClient } from './client.js';
|
|
|
9
9
|
import { TOOL_DESCRIPTIONS, formatError, formatSearch, formatVariable } from './tools.js';
|
|
10
10
|
import { installSkill, skillInstallReport } from './skill.js';
|
|
11
11
|
import { configuredOut, firstPullReport, runFirstPull } from './first-pull.js';
|
|
12
|
+
import { projectConfigReport, writeProjectConfig } from './project-config.js';
|
|
13
|
+
import { checkKey } from './key-check.js';
|
|
12
14
|
// A missing key threw at module load, and an editor shows that as a Node stack trace with the
|
|
13
15
|
// message buried in it. Say it in one line and exit, the way the CLI already does.
|
|
14
16
|
let client;
|
|
@@ -76,6 +78,19 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
76
78
|
console.error(skillInstallReport(await installSkill(process.cwd())));
|
|
77
79
|
// First connection with no stylesheets pulled yet: fetch them now, into the folder this project
|
|
78
80
|
// already keeps its styles in. Reported, never asked — a stdio server has nobody to prompt.
|
|
81
|
+
// The CLI reads its key from the project, the server from its environment — two sources that can
|
|
82
|
+
// disagree, and did: `flareum pull` said "no key" in a project whose server was connected.
|
|
83
|
+
// "Connected" only means this process started, so a revoked key looked healthy while every lookup
|
|
84
|
+
// failed. One authenticated call decides it before anything else runs.
|
|
85
|
+
const key = await checkKey(() => client.published());
|
|
86
|
+
if (!key.usable) {
|
|
87
|
+
console.error(key.message);
|
|
88
|
+
if (key.fatal)
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
const configWritten = projectConfigReport(await writeProjectConfig(process.cwd(), process.env, configuredOut(process.cwd(), process.env)));
|
|
92
|
+
if (configWritten)
|
|
93
|
+
console.error(configWritten);
|
|
79
94
|
const firstPull = firstPullReport(await runFirstPull(client, process.cwd(), process.env, configuredOut(process.cwd(), process.env)));
|
|
80
95
|
if (firstPull)
|
|
81
96
|
console.error(firstPull);
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -31,6 +31,42 @@ page, and it is worth reading in full before the first line of code.
|
|
|
31
31
|
| Any text — a heading, a label, body copy, a pseudo-element's `content` | [references/typography.md](references/typography.md) |
|
|
32
32
|
| A token name, or judging one that already exists | [references/naming.md](references/naming.md) |
|
|
33
33
|
|
|
34
|
+
## Before the first token: check the stylesheets are imported
|
|
35
|
+
|
|
36
|
+
A token only resolves if the pulled stylesheets are actually loaded. `flareum pull` writes them into
|
|
37
|
+
the project — it does not wire them in, because which file is the global entry point is the
|
|
38
|
+
project's decision, not the tool's.
|
|
39
|
+
|
|
40
|
+
So the first time you use a token in a project, check the import exists, and add it if it does not.
|
|
41
|
+
Otherwise every `var([prefix]-…)` you write is correct and renders as nothing — the failure is
|
|
42
|
+
silent, and it looks like the token is wrong rather than absent.
|
|
43
|
+
|
|
44
|
+
**Find the pulled files** (`src/styles/flareum/`, or wherever `out` in `.flareum/config.json`
|
|
45
|
+
points), then **find the global stylesheet** — the one the app already loads for everything:
|
|
46
|
+
|
|
47
|
+
| Project | Usually |
|
|
48
|
+
|---|---|
|
|
49
|
+
| Angular | the `styles` entry in `angular.json` — commonly `src/styles.scss` |
|
|
50
|
+
| Vite / React / Vue | the CSS imported by the entry module — `src/main.tsx`, `src/index.css` |
|
|
51
|
+
| Next.js | `app/globals.css`, imported by the root layout |
|
|
52
|
+
| Plain | whatever the HTML `<link>`s |
|
|
53
|
+
|
|
54
|
+
Add the import at the **top**, before anything that uses a token:
|
|
55
|
+
|
|
56
|
+
```scss
|
|
57
|
+
@use './styles/flareum/main'; // SCSS
|
|
58
|
+
```
|
|
59
|
+
```css
|
|
60
|
+
@import './styles/flareum/main.css'; /* plain CSS */
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
Then confirm it resolves — build, or grep the built CSS for a `--[prefix]-` declaration. A token
|
|
64
|
+
that is imported but still not applying is usually the `font:` shorthand or a missing
|
|
65
|
+
`--line-height`; [references/typography.md](references/typography.md) covers both.
|
|
66
|
+
|
|
67
|
+
If the pulled folder does not exist at all, the project has never pulled: say so and give the
|
|
68
|
+
command — `npx -p @flareum/mcp flareum pull` — rather than writing tokens that cannot resolve.
|
|
69
|
+
|
|
34
70
|
## Reading a search result
|
|
35
71
|
|
|
36
72
|
The header tells you which kind of answer you got, and they mean different things:
|