@aexol/spectral 0.9.229 → 0.9.231
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/CHANGELOG.md +2 -0
- package/dist/auth-helper.d.ts.map +1 -1
- package/dist/auth-helper.js +14 -8
- package/dist/commands/login.d.ts +12 -4
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +36 -15
- package/dist/commands/serve.d.ts.map +1 -1
- package/dist/commands/serve.js +3 -0
- package/dist/config.d.ts +15 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +10 -1
- package/dist/designer/data/skills/blog-post.d.ts +3 -0
- package/dist/designer/data/skills/blog-post.d.ts.map +1 -0
- package/dist/designer/data/skills/blog-post.js +84 -0
- package/dist/designer/data/skills/blog-post.ts +87 -0
- package/dist/designer/data/skills/critique.d.ts +3 -0
- package/dist/designer/data/skills/critique.d.ts.map +1 -0
- package/dist/designer/data/skills/critique.js +109 -0
- package/dist/designer/data/skills/critique.ts +112 -0
- package/dist/designer/data/skills/dashboard.d.ts +3 -0
- package/dist/designer/data/skills/dashboard.d.ts.map +1 -0
- package/dist/designer/data/skills/dashboard.js +165 -0
- package/dist/designer/data/skills/dashboard.ts +168 -0
- package/dist/designer/data/skills/design-brief.d.ts +3 -0
- package/dist/designer/data/skills/design-brief.d.ts.map +1 -0
- package/dist/designer/data/skills/design-brief.js +141 -0
- package/dist/designer/data/skills/design-brief.ts +144 -0
- package/dist/designer/data/skills/design-skill.d.ts +10 -0
- package/dist/designer/data/skills/design-skill.d.ts.map +1 -0
- package/dist/designer/data/skills/design-skill.js +1 -0
- package/dist/designer/data/skills/design-skill.ts +9 -0
- package/dist/designer/data/skills/docs-page.d.ts +3 -0
- package/dist/designer/data/skills/docs-page.d.ts.map +1 -0
- package/dist/designer/data/skills/docs-page.js +68 -0
- package/dist/designer/data/skills/docs-page.ts +71 -0
- package/dist/designer/data/skills/index.d.ts +3 -0
- package/dist/designer/data/skills/index.d.ts.map +1 -0
- package/dist/designer/data/skills/index.js +28 -0
- package/dist/designer/data/skills/index.ts +30 -0
- package/dist/designer/data/skills/magazine-poster.d.ts +3 -0
- package/dist/designer/data/skills/magazine-poster.d.ts.map +1 -0
- package/dist/designer/data/skills/magazine-poster.js +70 -0
- package/dist/designer/data/skills/magazine-poster.ts +73 -0
- package/dist/designer/data/skills/mobile-app.d.ts +3 -0
- package/dist/designer/data/skills/mobile-app.d.ts.map +1 -0
- package/dist/designer/data/skills/mobile-app.js +229 -0
- package/dist/designer/data/skills/mobile-app.ts +232 -0
- package/dist/designer/data/skills/motion-frames.d.ts +3 -0
- package/dist/designer/data/skills/motion-frames.d.ts.map +1 -0
- package/dist/designer/data/skills/motion-frames.js +121 -0
- package/dist/designer/data/skills/motion-frames.ts +124 -0
- package/dist/designer/data/skills/pricing-page.d.ts +3 -0
- package/dist/designer/data/skills/pricing-page.d.ts.map +1 -0
- package/dist/designer/data/skills/pricing-page.js +76 -0
- package/dist/designer/data/skills/pricing-page.ts +79 -0
- package/dist/designer/data/skills/saas-landing.d.ts +3 -0
- package/dist/designer/data/skills/saas-landing.d.ts.map +1 -0
- package/dist/designer/data/skills/saas-landing.js +71 -0
- package/dist/designer/data/skills/saas-landing.ts +74 -0
- package/dist/designer/data/skills/slide-deck.d.ts +3 -0
- package/dist/designer/data/skills/slide-deck.d.ts.map +1 -0
- package/dist/designer/data/skills/slide-deck.js +213 -0
- package/dist/designer/data/skills/slide-deck.ts +216 -0
- package/dist/designer/data/skills/social-carousel.d.ts +3 -0
- package/dist/designer/data/skills/social-carousel.d.ts.map +1 -0
- package/dist/designer/data/skills/social-carousel.js +135 -0
- package/dist/designer/data/skills/social-carousel.ts +138 -0
- package/dist/designer/data/skills/web-prototype.d.ts +3 -0
- package/dist/designer/data/skills/web-prototype.d.ts.map +1 -0
- package/dist/designer/data/skills/web-prototype.js +159 -0
- package/dist/designer/data/skills/web-prototype.ts +162 -0
- package/dist/designer/skills.d.ts +4 -10
- package/dist/designer/skills.d.ts.map +1 -1
- package/dist/designer/skills.js +2 -1643
- package/dist/extensions/wizard/index.js +9 -1
- package/dist/generated/parser/ast/nodes.d.ts +115 -0
- package/dist/generated/parser/ast/nodes.d.ts.map +1 -0
- package/dist/generated/parser/ast/nodes.js +8 -0
- package/dist/generated/parser/lexer/lexer.d.ts +28 -0
- package/dist/generated/parser/lexer/lexer.d.ts.map +1 -0
- package/dist/generated/parser/{lexer.js → lexer/lexer.js} +1 -180
- package/dist/generated/parser/{lexer.d.ts → lexer/tokens.d.ts} +5 -24
- package/dist/generated/parser/lexer/tokens.d.ts.map +1 -0
- package/dist/generated/parser/lexer/tokens.js +187 -0
- package/dist/generated/parser/mod.d.ts +5 -7
- package/dist/generated/parser/mod.d.ts.map +1 -1
- package/dist/generated/parser/mod.js +2 -1
- package/dist/generated/parser/parser/cursor.d.ts +47 -0
- package/dist/generated/parser/parser/cursor.d.ts.map +1 -0
- package/dist/generated/parser/parser/cursor.js +12 -0
- package/dist/generated/parser/parser/definitions.d.ts +16 -0
- package/dist/generated/parser/parser/definitions.d.ts.map +1 -0
- package/dist/generated/parser/parser/definitions.js +353 -0
- package/dist/generated/parser/parser/expressions.d.ts +17 -0
- package/dist/generated/parser/parser/expressions.d.ts.map +1 -0
- package/dist/generated/parser/parser/expressions.js +165 -0
- package/dist/generated/parser/parser.d.ts +18 -133
- package/dist/generated/parser/parser.d.ts.map +1 -1
- package/dist/generated/parser/parser.js +9 -505
- package/dist/generated/parser/tokenizer.d.ts +1 -2
- package/dist/generated/parser/tokenizer.d.ts.map +1 -1
- package/dist/generated/parser/tokenizer.js +2 -1
- package/dist/generated/parser/validator.d.ts +1 -1
- package/dist/generated/parser/validator.d.ts.map +1 -1
- package/dist/generated/zeus/const.d.ts.map +1 -1
- package/dist/generated/zeus/const.js +21 -0
- package/dist/generated/zeus/index.d.ts +98 -0
- package/dist/generated/zeus/index.d.ts.map +1 -1
- package/dist/mcp/glimpse-ui.js +9 -1
- package/dist/relay/dispatcher.d.ts +11 -104
- package/dist/relay/dispatcher.d.ts.map +1 -1
- package/dist/relay/dispatcher.js +10 -1130
- package/dist/relay/registration.d.ts +10 -2
- package/dist/relay/registration.d.ts.map +1 -1
- package/dist/relay/registration.js +32 -3
- package/dist/relay/rest.d.ts +31 -0
- package/dist/relay/rest.d.ts.map +1 -0
- package/dist/relay/rest.js +85 -0
- package/dist/relay/routing.d.ts +109 -0
- package/dist/relay/routing.d.ts.map +1 -0
- package/dist/relay/routing.js +669 -0
- package/dist/relay/wizard-login.d.ts +17 -0
- package/dist/relay/wizard-login.d.ts.map +1 -0
- package/dist/relay/wizard-login.js +419 -0
- package/dist/sdk/agent-core/agent-loop.d.ts +2 -9
- package/dist/sdk/agent-core/agent-loop.d.ts.map +1 -1
- package/dist/sdk/agent-core/agent-loop.js +4 -288
- package/dist/sdk/agent-core/tool-execution.d.ts +33 -0
- package/dist/sdk/agent-core/tool-execution.d.ts.map +1 -0
- package/dist/sdk/agent-core/tool-execution.js +300 -0
- package/dist/sdk/ai/env-api-keys.js +9 -1
- package/dist/sdk/ai/providers/openai/reasoning-replay.d.ts +48 -0
- package/dist/sdk/ai/providers/openai/reasoning-replay.d.ts.map +1 -0
- package/dist/sdk/ai/providers/openai/reasoning-replay.js +209 -0
- package/dist/sdk/ai/providers/openai-completions.d.ts.map +1 -1
- package/dist/sdk/ai/providers/openai-completions.js +1 -203
- package/dist/sdk/coding-agent/core/compaction/compaction.d.ts +6 -118
- package/dist/sdk/coding-agent/core/compaction/compaction.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/compaction/compaction.js +11 -733
- package/dist/sdk/coding-agent/core/compaction/cut-points.d.ts +58 -0
- package/dist/sdk/coding-agent/core/compaction/cut-points.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/compaction/cut-points.js +191 -0
- package/dist/sdk/coding-agent/core/compaction/dedupe.d.ts +28 -0
- package/dist/sdk/coding-agent/core/compaction/dedupe.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/compaction/dedupe.js +156 -0
- package/dist/sdk/coding-agent/core/compaction/summarize.d.ts +21 -0
- package/dist/sdk/coding-agent/core/compaction/summarize.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/compaction/summarize.js +181 -0
- package/dist/sdk/coding-agent/core/compaction/tokens.d.ts +47 -0
- package/dist/sdk/coding-agent/core/compaction/tokens.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/compaction/tokens.js +224 -0
- package/dist/sdk/coding-agent/core/extensions/session-events.d.ts +157 -0
- package/dist/sdk/coding-agent/core/extensions/session-events.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/extensions/session-events.js +13 -0
- package/dist/sdk/coding-agent/core/extensions/types.d.ts +6 -365
- package/dist/sdk/coding-agent/core/extensions/types.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/extensions/types.js +2 -10
- package/dist/sdk/coding-agent/core/extensions/ui-types.d.ts +219 -0
- package/dist/sdk/coding-agent/core/extensions/ui-types.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/extensions/ui-types.js +4 -0
- package/dist/sdk/coding-agent/core/package/default-package-manager.d.ts +115 -0
- package/dist/sdk/coding-agent/core/package/default-package-manager.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/package/default-package-manager.js +1184 -0
- package/dist/sdk/coding-agent/core/package/discovery.d.ts +20 -0
- package/dist/sdk/coding-agent/core/package/discovery.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/package/discovery.js +283 -0
- package/dist/sdk/coding-agent/core/package/ignore.d.ts +29 -0
- package/dist/sdk/coding-agent/core/package/ignore.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/package/ignore.js +235 -0
- package/dist/sdk/coding-agent/core/package/types.d.ts +128 -0
- package/dist/sdk/coding-agent/core/package/types.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/package/types.js +18 -0
- package/dist/sdk/coding-agent/core/package/update-check.d.ts +43 -0
- package/dist/sdk/coding-agent/core/package/update-check.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/package/update-check.js +253 -0
- package/dist/sdk/coding-agent/core/package-manager.d.ts +2 -188
- package/dist/sdk/coding-agent/core/package-manager.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/package-manager.js +1 -1940
- package/dist/sdk/coding-agent/core/session/migrate.d.ts +18 -0
- package/dist/sdk/coding-agent/core/session/migrate.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/session/migrate.js +78 -0
- package/dist/sdk/coding-agent/core/session/types.d.ts +165 -0
- package/dist/sdk/coding-agent/core/session/types.d.ts.map +1 -0
- package/dist/sdk/coding-agent/core/session/types.js +4 -0
- package/dist/sdk/coding-agent/core/session-manager.d.ts +3 -161
- package/dist/sdk/coding-agent/core/session-manager.d.ts.map +1 -1
- package/dist/sdk/coding-agent/core/session-manager.js +4 -72
- package/package.json +2 -2
- package/dist/generated/parser/lexer.d.ts.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"auth-helper.d.ts","sourceRoot":"","sources":["../src/auth-helper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAOrB,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,eAAO,MAAM,YAAY;IACvB,4DAA4D;;IAE5D,6EAA6E;;IAE7E,2DAA2D;;IAE3D,uCAAuC;;CAE/B,CAAC;AACX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AA8F3E;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,MAAM,CAAC,EAAE,MAAM,EACf,SAAS,GAAE,MAAyB,EACpC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,WAAW,CAAC,CAmEtB;AAMD,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAUjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,gBAAgB,CAAC,
|
|
1
|
+
{"version":3,"file":"auth-helper.d.ts","sourceRoot":"","sources":["../src/auth-helper.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,OAAO,EAKL,KAAK,cAAc,EACpB,MAAM,aAAa,CAAC;AAOrB,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,OAAO,CAAC;IACZ,MAAM,EAAE,cAAc,GAAG,IAAI,CAAC;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,WAAW;IAC1B,EAAE,EAAE,OAAO,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;;;GAIG;AACH,eAAO,MAAM,YAAY;IACvB,4DAA4D;;IAE5D,6EAA6E;;IAE7E,2DAA2D;;IAE3D,uCAAuC;;CAE/B,CAAC;AACX,MAAM,MAAM,WAAW,GAAG,CAAC,OAAO,YAAY,CAAC,CAAC,MAAM,OAAO,YAAY,CAAC,CAAC;AA8F3E;;;;;;;GAOG;AACH,wBAAsB,YAAY,CAChC,MAAM,CAAC,EAAE,MAAM,EACf,SAAS,GAAE,MAAyB,EACpC,MAAM,CAAC,EAAE,WAAW,GACnB,OAAO,CAAC,WAAW,CAAC,CAmEtB;AAMD,0EAA0E;AAC1E,wBAAgB,WAAW,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAUjD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,mBAAmB,IAAI,OAAO,CAAC,gBAAgB,CAAC,CAuErE"}
|
package/dist/auth-helper.js
CHANGED
|
@@ -208,15 +208,21 @@ export async function ensureAuthenticated() {
|
|
|
208
208
|
}
|
|
209
209
|
const existingCfg = await readConfig();
|
|
210
210
|
const apiUrl = existingCfg?.apiUrl ?? process.env.SPECTRAL_MCP_URL ?? DEFAULT_API_URL;
|
|
211
|
-
|
|
211
|
+
// Chain: team key, then user key, then the OAuth JWT.
|
|
212
|
+
const token = existingCfg?.teamApiKey || existingCfg?.userApiKey || existingCfg?.userJwt || "";
|
|
212
213
|
// If we have a token, verify it still works
|
|
213
214
|
if (token) {
|
|
214
|
-
//
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
215
|
+
// The stored user JWT is locally known to be expired and it is the token
|
|
216
|
+
// we'd use (no team/user API key ahead of it in the chain) — skip the
|
|
217
|
+
// network roundtrip for the JWT itself, but try the API-key fallbacks
|
|
218
|
+
// before triggering the OAuth browser flow.
|
|
219
|
+
const tokenIsExpiredUserJwt = token === existingCfg?.userJwt && isJwtExpired(existingCfg.userJwt);
|
|
220
|
+
if (tokenIsExpiredUserJwt) {
|
|
221
|
+
// userJwt expired — if we have an API key, try that before re-auth
|
|
222
|
+
const fallbackToken = existingCfg?.teamApiKey ?? existingCfg?.userApiKey;
|
|
223
|
+
if (fallbackToken) {
|
|
218
224
|
try {
|
|
219
|
-
const client = new AexolMcpClient(apiUrl,
|
|
225
|
+
const client = new AexolMcpClient(apiUrl, fallbackToken);
|
|
220
226
|
await client.listTools();
|
|
221
227
|
return { ok: true, config: existingCfg };
|
|
222
228
|
}
|
|
@@ -225,10 +231,10 @@ export async function ensureAuthenticated() {
|
|
|
225
231
|
const msg = err instanceof Error ? err.message : String(err);
|
|
226
232
|
return { ok: false, config: existingCfg, reason: `Backend unreachable: ${msg}` };
|
|
227
233
|
}
|
|
228
|
-
//
|
|
234
|
+
// API key also failed — fall through to OAuth
|
|
229
235
|
}
|
|
230
236
|
}
|
|
231
|
-
// no
|
|
237
|
+
// no API key — fall through to OAuth
|
|
232
238
|
}
|
|
233
239
|
else {
|
|
234
240
|
try {
|
package/dist/commands/login.d.ts
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* `spectral login` — interactive authentication against the Aexol MCP backend.
|
|
3
3
|
*
|
|
4
4
|
* Flow:
|
|
5
|
-
* 1. Choose auth method: Team API key or OAuth (browser).
|
|
6
|
-
* 2a. API
|
|
7
|
-
* sk-aexol-team- prefix locally so we don't send
|
|
8
|
-
* credentials. The API URL is taken from env or the
|
|
5
|
+
* 1. Choose auth method: Team API key, User API key, or OAuth (browser).
|
|
6
|
+
* 2a. API keys: prompt for the key (masked input). Verify the
|
|
7
|
+
* sk-aexol-team- / sk-aexol-user- prefix locally so we don't send
|
|
8
|
+
* obviously-wrong credentials. The API URL is taken from env or the
|
|
9
|
+
* hard default. A user key (sk-aexol-user-) is pinned to a team but
|
|
10
|
+
* owned by a single user — machines registered with it are PRIVATE.
|
|
9
11
|
* 2b. OAuth: open browser for Aexol Studio authorization.
|
|
10
12
|
* 3. Verify reachability + auth by calling tools/list against the URL.
|
|
11
13
|
* 4. On success: persist to ~/.spectral/config.json (mode 0600).
|
|
@@ -19,6 +21,12 @@
|
|
|
19
21
|
*/
|
|
20
22
|
export interface PerformLoginOptions {
|
|
21
23
|
apiUrl: string;
|
|
24
|
+
/**
|
|
25
|
+
* API key to verify. Accepts both the team prefix ("sk-aexol-team-") and
|
|
26
|
+
* the user prefix ("sk-aexol-user-"). The key kind determines how it is
|
|
27
|
+
* persisted: a team key sets `teamApiKey` (clearing any stale `userApiKey`),
|
|
28
|
+
* a user key sets `userApiKey`.
|
|
29
|
+
*/
|
|
22
30
|
teamApiKey: string;
|
|
23
31
|
}
|
|
24
32
|
export interface PerformLoginResult {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"login.d.ts","sourceRoot":"","sources":["../../src/commands/login.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;GAoBG;AAoBH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,MAAM,CAAC;IACf;;;;;OAKG;IACH,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;;;GAQG;AACH,wBAAsB,YAAY,CAAC,IAAI,EAAE,mBAAmB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAoDzF;AAED,wBAAsB,QAAQ,IAAI,OAAO,CAAC,IAAI,CAAC,CAyF9C"}
|
package/dist/commands/login.js
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
* `spectral login` — interactive authentication against the Aexol MCP backend.
|
|
3
3
|
*
|
|
4
4
|
* Flow:
|
|
5
|
-
* 1. Choose auth method: Team API key or OAuth (browser).
|
|
6
|
-
* 2a. API
|
|
7
|
-
* sk-aexol-team- prefix locally so we don't send
|
|
8
|
-
* credentials. The API URL is taken from env or the
|
|
5
|
+
* 1. Choose auth method: Team API key, User API key, or OAuth (browser).
|
|
6
|
+
* 2a. API keys: prompt for the key (masked input). Verify the
|
|
7
|
+
* sk-aexol-team- / sk-aexol-user- prefix locally so we don't send
|
|
8
|
+
* obviously-wrong credentials. The API URL is taken from env or the
|
|
9
|
+
* hard default. A user key (sk-aexol-user-) is pinned to a team but
|
|
10
|
+
* owned by a single user — machines registered with it are PRIVATE.
|
|
9
11
|
* 2b. OAuth: open browser for Aexol Studio authorization.
|
|
10
12
|
* 3. Verify reachability + auth by calling tools/list against the URL.
|
|
11
13
|
* 4. On success: persist to ~/.spectral/config.json (mode 0600).
|
|
@@ -19,7 +21,7 @@
|
|
|
19
21
|
*/
|
|
20
22
|
import { password, select } from "@inquirer/prompts";
|
|
21
23
|
import pc from "picocolors";
|
|
22
|
-
import { TEAM_API_KEY_PREFIX, getApiUrl, getConfigFile, validateTeamApiKey, writeConfig, } from "../config.js";
|
|
24
|
+
import { TEAM_API_KEY_PREFIX, USER_API_KEY_PREFIX, getApiUrl, getConfigFile, readConfig, validateTeamApiKey, validateUserApiKey, writeConfig, } from "../config.js";
|
|
23
25
|
import { AexolMcpClient, AexolMcpError } from "../mcp-client.js";
|
|
24
26
|
import { runOAuthFlow, OAUTH_REASON } from "../auth-helper.js";
|
|
25
27
|
/**
|
|
@@ -37,8 +39,10 @@ export async function performLogin(opts) {
|
|
|
37
39
|
if (!apiUrl) {
|
|
38
40
|
throw new Error("API URL is required");
|
|
39
41
|
}
|
|
40
|
-
|
|
41
|
-
|
|
42
|
+
const isTeamKey = validateTeamApiKey(teamApiKey);
|
|
43
|
+
const isUserKey = validateUserApiKey(teamApiKey);
|
|
44
|
+
if (!isTeamKey && !isUserKey) {
|
|
45
|
+
throw new Error(`Invalid API key prefix: token must start with "${TEAM_API_KEY_PREFIX}" or "${USER_API_KEY_PREFIX}"`);
|
|
42
46
|
}
|
|
43
47
|
const client = new AexolMcpClient(apiUrl, teamApiKey);
|
|
44
48
|
let toolCount;
|
|
@@ -59,7 +63,20 @@ export async function performLogin(opts) {
|
|
|
59
63
|
const msg = err instanceof Error ? err.message : String(err);
|
|
60
64
|
throw new Error(`Could not reach ${apiUrl}: ${msg}`);
|
|
61
65
|
}
|
|
62
|
-
|
|
66
|
+
// Preserve credentials we didn't just verify — most importantly the user JWT
|
|
67
|
+
// from a previous OAuth login (previously overwritten here, silently dropping
|
|
68
|
+
// Studio auth). A team-key login clears any stale userApiKey so the explicit
|
|
69
|
+
// choice of team auth isn't later overridden by registration priority; a
|
|
70
|
+
// user-key login keeps the team key (both stay valid for the team).
|
|
71
|
+
const existing = await readConfig();
|
|
72
|
+
const cfg = isTeamKey
|
|
73
|
+
? { apiUrl, teamApiKey, userJwt: existing?.userJwt }
|
|
74
|
+
: {
|
|
75
|
+
apiUrl,
|
|
76
|
+
teamApiKey: existing?.teamApiKey ?? "",
|
|
77
|
+
userApiKey: teamApiKey,
|
|
78
|
+
userJwt: existing?.userJwt,
|
|
79
|
+
};
|
|
63
80
|
await writeConfig(cfg);
|
|
64
81
|
return { toolCount };
|
|
65
82
|
}
|
|
@@ -73,6 +90,7 @@ export async function runLogin() {
|
|
|
73
90
|
message: "How would you like to authenticate?",
|
|
74
91
|
choices: [
|
|
75
92
|
{ name: "Team API key", value: "apikey" },
|
|
93
|
+
{ name: "User API key", value: "userkey" },
|
|
76
94
|
{ name: "OAuth (browser)", value: "oauth" },
|
|
77
95
|
],
|
|
78
96
|
});
|
|
@@ -90,19 +108,22 @@ export async function runLogin() {
|
|
|
90
108
|
await runLoginOAuth();
|
|
91
109
|
return;
|
|
92
110
|
}
|
|
93
|
-
//
|
|
111
|
+
// API key flow: no URL prompt — use env or default.
|
|
94
112
|
const apiUrl = getApiUrl();
|
|
95
|
-
|
|
113
|
+
const isUserKey = authMethod === "userkey";
|
|
114
|
+
const keyPrefix = isUserKey ? USER_API_KEY_PREFIX : TEAM_API_KEY_PREFIX;
|
|
115
|
+
let apiKey;
|
|
96
116
|
try {
|
|
97
|
-
|
|
98
|
-
message:
|
|
117
|
+
apiKey = (await password({
|
|
118
|
+
message: `${isUserKey ? "User" : "Team"} API key (starts with "${keyPrefix}")`,
|
|
99
119
|
mask: "*",
|
|
100
120
|
validate: (v) => {
|
|
101
121
|
const trimmed = v.trim();
|
|
102
122
|
if (trimmed.length === 0)
|
|
103
123
|
return "Token is required";
|
|
104
|
-
|
|
105
|
-
|
|
124
|
+
const valid = isUserKey ? validateUserApiKey(trimmed) : validateTeamApiKey(trimmed);
|
|
125
|
+
if (!valid) {
|
|
126
|
+
return `Token must start with "${keyPrefix}"`;
|
|
106
127
|
}
|
|
107
128
|
return true;
|
|
108
129
|
},
|
|
@@ -119,7 +140,7 @@ export async function runLogin() {
|
|
|
119
140
|
process.stdout.write(pc.dim(`\nVerifying credentials against ${apiUrl} ...\n`));
|
|
120
141
|
let result;
|
|
121
142
|
try {
|
|
122
|
-
result = await performLogin({ apiUrl, teamApiKey });
|
|
143
|
+
result = await performLogin({ apiUrl, teamApiKey: apiKey });
|
|
123
144
|
}
|
|
124
145
|
catch (err) {
|
|
125
146
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAwBH,OAAO,EAAE,WAAW,EAAmB,MAAM,oBAAoB,CAAC;AAYlE,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGnE,OAAO,EAAmB,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAarE;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAazD;AAED,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,eAAe,CA6B9D;AAED,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,uCAAuC;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,qBAAqB,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;CAC/E;AAED,MAAM,WAAW,cAAc;IAC7B,qCAAqC;IACrC,KAAK,EAAE,YAAY,CAAC;IACpB,wDAAwD;IACxD,OAAO,EAAE,oBAAoB,CAAC;IAC9B,4CAA4C;IAC5C,KAAK,EAAE,WAAW,CAAC;IACnB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAsB,QAAQ,CAAC,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAwBH,OAAO,EAAE,WAAW,EAAmB,MAAM,oBAAoB,CAAC;AAYlE,OAAO,KAAK,EAAE,aAAa,EAAc,MAAM,6BAA6B,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,6BAA6B,CAAC;AAGnE,OAAO,EAAmB,YAAY,EAAE,MAAM,sBAAsB,CAAC;AAarE;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,UAAU,EAAE,MAAM,GAAG,MAAM,CAazD;AAED,MAAM,WAAW,eAAe;IAC9B,8CAA8C;IAC9C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,cAAc,EAAE,OAAO,CAAC;CACzB;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,eAAe,CA6B9D;AAED,MAAM,WAAW,eAAe;IAC9B,iEAAiE;IACjE,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,qBAAqB,CAAC,EAAE,OAAO,CAAC;IAChC,uCAAuC;IACvC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,wCAAwC;IACxC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,yDAAyD;IACzD,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,6EAA6E;IAC7E,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB;;;OAGG;IACH,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B;;;OAGG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,4CAA4C;IAC5C,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IACzB,+DAA+D;IAC/D,aAAa,CAAC,EAAE,qBAAqB,CAAC,OAAO,WAAW,CAAC,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC,CAAC;CAC/E;AAED,MAAM,WAAW,cAAc;IAC7B,qCAAqC;IACrC,KAAK,EAAE,YAAY,CAAC;IACpB,wDAAwD;IACxD,OAAO,EAAE,oBAAoB,CAAC;IAC9B,4CAA4C;IAC5C,KAAK,EAAE,WAAW,CAAC;IACnB,+CAA+C;IAC/C,SAAS,EAAE,MAAM,CAAC;IAClB,4CAA4C;IAC5C,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC;CACxB;AAED,wBAAsB,QAAQ,CAAC,IAAI,GAAE,eAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,CA+kBlF;AAmFD;;;GAGG;AACH,wBAAsB,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAc5E"}
|
package/dist/commands/serve.js
CHANGED
|
@@ -172,6 +172,7 @@ export async function runServe(opts = {}) {
|
|
|
172
172
|
registration = await ensureMachineRegistered({
|
|
173
173
|
backendUrl,
|
|
174
174
|
apiKey: cfg.teamApiKey,
|
|
175
|
+
userApiKey: cfg.userApiKey,
|
|
175
176
|
userJwt: cfg.userJwt,
|
|
176
177
|
machineNameOverride: opts.machineName,
|
|
177
178
|
version,
|
|
@@ -213,6 +214,7 @@ export async function runServe(opts = {}) {
|
|
|
213
214
|
const newReg = await ensureMachineRegistered({
|
|
214
215
|
backendUrl,
|
|
215
216
|
apiKey: cfg.teamApiKey,
|
|
217
|
+
userApiKey: cfg.userApiKey,
|
|
216
218
|
userJwt: cfg.userJwt,
|
|
217
219
|
machineNameOverride: opts.machineName,
|
|
218
220
|
version,
|
|
@@ -476,6 +478,7 @@ export async function runServe(opts = {}) {
|
|
|
476
478
|
const newReg = await ensureMachineRegistered({
|
|
477
479
|
backendUrl,
|
|
478
480
|
apiKey: cfg.teamApiKey,
|
|
481
|
+
userApiKey: cfg.userApiKey,
|
|
479
482
|
userJwt: cfg.userJwt,
|
|
480
483
|
machineNameOverride: opts.machineName,
|
|
481
484
|
version,
|
package/dist/config.d.ts
CHANGED
|
@@ -12,9 +12,22 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export declare const DEFAULT_API_URL = "https://api.aexol.ai/mcp";
|
|
14
14
|
export declare const TEAM_API_KEY_PREFIX = "sk-aexol-team-";
|
|
15
|
+
export declare const USER_API_KEY_PREFIX = "sk-aexol-user-";
|
|
15
16
|
export interface SpectralConfig {
|
|
16
17
|
apiUrl: string;
|
|
18
|
+
/**
|
|
19
|
+
* Team API key ("sk-aexol-team-"). Normalized to "" when absent — e.g. an
|
|
20
|
+
* OAuth-only login stores no team key. At least one of `teamApiKey`,
|
|
21
|
+
* `userApiKey`, or `userJwt` must be present for the config to be valid.
|
|
22
|
+
*/
|
|
17
23
|
teamApiKey: string;
|
|
24
|
+
/**
|
|
25
|
+
* User API key ("sk-aexol-user-"), pinned to a team but owned by a single
|
|
26
|
+
* user. Machines registered with it are PRIVATE with `ownerId` derived from
|
|
27
|
+
* the key. Takes priority over `teamApiKey` for machine registration
|
|
28
|
+
* (see `relay/registration.ts`).
|
|
29
|
+
*/
|
|
30
|
+
userApiKey?: string;
|
|
18
31
|
/** User JWT from OAuth login. When present, used for machine registration (owns the machine). */
|
|
19
32
|
userJwt?: string;
|
|
20
33
|
}
|
|
@@ -40,6 +53,8 @@ export declare const CONFIG_FILE: string;
|
|
|
40
53
|
export declare function getApiUrl(stored?: string): string;
|
|
41
54
|
/** Validate the team API key prefix. The full token check happens server-side. */
|
|
42
55
|
export declare function validateTeamApiKey(key: unknown): key is string;
|
|
56
|
+
/** Validate the user API key prefix. The full token check happens server-side. */
|
|
57
|
+
export declare function validateUserApiKey(key: unknown): key is string;
|
|
43
58
|
/** Read config; returns null if missing or malformed. Never throws. */
|
|
44
59
|
export declare function readConfig(): Promise<SpectralConfig | null>;
|
|
45
60
|
/** Persist config with restrictive permissions (dir 0700, file 0600). */
|
package/dist/config.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,eAAO,MAAM,eAAe,6BAA6B,CAAC;AAC1D,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AAEpD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,iGAAiG;IACjG,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;;;;GAKG;AACH,eAAO,MAAM,UAAU,QAAiB,CAAC;AACzC,eAAO,MAAM,WAAW,QAAkB,CAAC;AAE3C,gFAAgF;AAChF,wBAAgB,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,MAAM,CAE9D;AAED,uEAAuE;AACvE,wBAAsB,UAAU,IAAI,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAMH,eAAO,MAAM,eAAe,6BAA6B,CAAC;AAC1D,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AACpD,eAAO,MAAM,mBAAmB,mBAAmB,CAAC;AAEpD,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,MAAM,CAAC;IACf;;;;OAIG;IACH,UAAU,EAAE,MAAM,CAAC;IACnB;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,iGAAiG;IACjG,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,0EAA0E;AAC1E,wBAAgB,aAAa,IAAI,MAAM,CAEtC;AAED;;;;;GAKG;AACH,eAAO,MAAM,UAAU,QAAiB,CAAC;AACzC,eAAO,MAAM,WAAW,QAAkB,CAAC;AAE3C,gFAAgF;AAChF,wBAAgB,SAAS,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,CAEjD;AAED,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,MAAM,CAE9D;AAED,kFAAkF;AAClF,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,OAAO,GAAG,GAAG,IAAI,MAAM,CAE9D;AAED,uEAAuE;AACvE,wBAAsB,UAAU,IAAI,OAAO,CAAC,cAAc,GAAG,IAAI,CAAC,CAgCjE;AAED,yEAAyE;AACzE,wBAAsB,WAAW,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAQpE;AAED,+FAA+F;AAC/F,wBAAsB,YAAY,IAAI,OAAO,CAAC,OAAO,CAAC,CASrD"}
|
package/dist/config.js
CHANGED
|
@@ -15,6 +15,7 @@ import { homedir } from "node:os";
|
|
|
15
15
|
import { dirname, join } from "node:path";
|
|
16
16
|
export const DEFAULT_API_URL = "https://api.aexol.ai/mcp";
|
|
17
17
|
export const TEAM_API_KEY_PREFIX = "sk-aexol-team-";
|
|
18
|
+
export const USER_API_KEY_PREFIX = "sk-aexol-user-";
|
|
18
19
|
/**
|
|
19
20
|
* Resolve the directory used to store Spectral configuration.
|
|
20
21
|
*
|
|
@@ -45,6 +46,10 @@ export function getApiUrl(stored) {
|
|
|
45
46
|
export function validateTeamApiKey(key) {
|
|
46
47
|
return typeof key === "string" && key.startsWith(TEAM_API_KEY_PREFIX);
|
|
47
48
|
}
|
|
49
|
+
/** Validate the user API key prefix. The full token check happens server-side. */
|
|
50
|
+
export function validateUserApiKey(key) {
|
|
51
|
+
return typeof key === "string" && key.startsWith(USER_API_KEY_PREFIX);
|
|
52
|
+
}
|
|
48
53
|
/** Read config; returns null if missing or malformed. Never throws. */
|
|
49
54
|
export async function readConfig() {
|
|
50
55
|
let raw;
|
|
@@ -64,11 +69,15 @@ export async function readConfig() {
|
|
|
64
69
|
const userJwt = typeof parsed.userJwt === "string" && parsed.userJwt.length > 0
|
|
65
70
|
? parsed.userJwt
|
|
66
71
|
: undefined;
|
|
67
|
-
|
|
72
|
+
const userApiKey = typeof parsed.userApiKey === "string" && parsed.userApiKey.length > 0
|
|
73
|
+
? parsed.userApiKey
|
|
74
|
+
: undefined;
|
|
75
|
+
if (!teamApiKey && !userApiKey && !userJwt)
|
|
68
76
|
return null;
|
|
69
77
|
return {
|
|
70
78
|
apiUrl: parsed.apiUrl,
|
|
71
79
|
teamApiKey,
|
|
80
|
+
userApiKey,
|
|
72
81
|
userJwt,
|
|
73
82
|
};
|
|
74
83
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"blog-post.d.ts","sourceRoot":"","sources":["../../../../src/designer/data/skills/blog-post.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,eAAO,MAAM,QAAQ,EAAE,WAoFpB,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
export const blogPost = {
|
|
2
|
+
name: "blog-post",
|
|
3
|
+
scenario: "content",
|
|
4
|
+
description: "Blog article layout — title, author metadata, featured image, prose body with headings/images/quotes, related posts. Single-page, reading-optimized typography with comfortable measure.",
|
|
5
|
+
outputKind: "html",
|
|
6
|
+
workflow: `# Blog Post Skill
|
|
7
|
+
|
|
8
|
+
Produce a single blog article page with reading-optimized typography.
|
|
9
|
+
|
|
10
|
+
## Phase 1 — Structure
|
|
11
|
+
|
|
12
|
+
\`\`\`html
|
|
13
|
+
<article class="blog-post">
|
|
14
|
+
<header class="post-header">
|
|
15
|
+
<div class="post-category">Category</div>
|
|
16
|
+
<h1>Article Title That Is Specific and Engaging</h1>
|
|
17
|
+
<div class="post-meta">
|
|
18
|
+
<span class="post-author">Author Name</span>
|
|
19
|
+
<span class="post-date">Month DD, YYYY</span>
|
|
20
|
+
<span class="post-read-time">N min read</span>
|
|
21
|
+
</div>
|
|
22
|
+
</header>
|
|
23
|
+
<figure class="post-hero">
|
|
24
|
+
<img src="..." alt="...">
|
|
25
|
+
<figcaption>Image caption (optional)</figcaption>
|
|
26
|
+
</figure>
|
|
27
|
+
<div class="post-content">
|
|
28
|
+
<!-- prose -->
|
|
29
|
+
</div>
|
|
30
|
+
<footer class="post-footer">
|
|
31
|
+
<div class="post-tags"><!-- tag pills --></div>
|
|
32
|
+
<section class="related-posts"><!-- 3 related cards --></section>
|
|
33
|
+
</footer>
|
|
34
|
+
</article>
|
|
35
|
+
\`\`\`
|
|
36
|
+
|
|
37
|
+
## Phase 2 — Typography
|
|
38
|
+
|
|
39
|
+
\`\`\`css
|
|
40
|
+
.blog-post { max-width: 680px; margin: 0 auto; padding: 64px 24px; }
|
|
41
|
+
|
|
42
|
+
.post-header { margin-bottom: 32px; }
|
|
43
|
+
.post-category { font-size: 13px; font-weight: 600; color: var(--accent); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 12px; }
|
|
44
|
+
.post-header h1 { font-size: 40px; font-weight: 700; line-height: 1.2; letter-spacing: -0.02em; margin-bottom: 16px; }
|
|
45
|
+
.post-meta { display: flex; gap: 16px; font-size: 14px; color: var(--muted); }
|
|
46
|
+
|
|
47
|
+
.post-hero { margin: 0 -24px 40px; }
|
|
48
|
+
.post-hero img { width: 100%; height: 400px; object-fit: cover; border-radius: 12px; }
|
|
49
|
+
.post-hero figcaption { font-size: 13px; color: var(--muted); text-align: center; margin-top: 8px; }
|
|
50
|
+
|
|
51
|
+
/* Prose — comfortable reading measure */
|
|
52
|
+
.post-content { font-size: 18px; line-height: 1.8; color: var(--fg); }
|
|
53
|
+
.post-content p { margin: 24px 0; }
|
|
54
|
+
.post-content h2 { font-size: 28px; font-weight: 600; margin: 48px 0 16px; }
|
|
55
|
+
.post-content h3 { font-size: 22px; font-weight: 600; margin: 36px 0 12px; }
|
|
56
|
+
.post-content blockquote { border-left: 3px solid var(--accent); padding: 8px 20px; margin: 32px 0; font-style: italic; color: var(--muted); }
|
|
57
|
+
.post-content ul, .post-content ol { padding-left: 24px; margin: 16px 0; }
|
|
58
|
+
.post-content li { margin: 8px 0; }
|
|
59
|
+
.post-content img { max-width: 100%; border-radius: 8px; margin: 24px 0; }
|
|
60
|
+
.post-content code { font-family: var(--font-mono); font-size: 0.9em; background: var(--surface); padding: 2px 6px; border-radius: 4px; }
|
|
61
|
+
.post-content pre { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 20px 24px; overflow-x: auto; font-family: var(--font-mono); font-size: 15px; line-height: 1.6; margin: 24px 0; }
|
|
62
|
+
|
|
63
|
+
/* Tags */
|
|
64
|
+
.post-tags { display: flex; flex-wrap: wrap; gap: 8px; margin: 48px 0; padding-top: 32px; border-top: 1px solid var(--border); }
|
|
65
|
+
.post-tag { padding: 4px 12px; background: var(--surface); border-radius: 999px; font-size: 13px; color: var(--muted); }
|
|
66
|
+
|
|
67
|
+
/* Related posts */
|
|
68
|
+
.related-posts { margin-top: 48px; }
|
|
69
|
+
.related-posts h3 { font-size: 20px; font-weight: 600; margin-bottom: 16px; }
|
|
70
|
+
.related-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; }
|
|
71
|
+
.related-card { border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
|
|
72
|
+
.related-card img { width: 100%; height: 140px; object-fit: cover; }
|
|
73
|
+
.related-card-content { padding: 16px; }
|
|
74
|
+
.related-card-content h4 { font-size: 15px; font-weight: 600; margin-bottom: 4px; }
|
|
75
|
+
.related-card-content time { font-size: 12px; color: var(--muted); }
|
|
76
|
+
\`\`\`
|
|
77
|
+
|
|
78
|
+
## Phase 3 — Content Rules
|
|
79
|
+
- Article body: 800-2000 words of meaningful content (no lorem ipsum)
|
|
80
|
+
- Every heading is specific and informative
|
|
81
|
+
- Include at least one blockquote and one code block
|
|
82
|
+
- Feature image is a gradient shape if no real image
|
|
83
|
+
- Author bio is not generic — use a plausible name and role`,
|
|
84
|
+
};
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { DesignSkill } from "./design-skill.js";
|
|
2
|
+
|
|
3
|
+
export const blogPost: DesignSkill = {
|
|
4
|
+
name: "blog-post",
|
|
5
|
+
scenario: "content",
|
|
6
|
+
description:
|
|
7
|
+
"Blog article layout — title, author metadata, featured image, prose body with headings/images/quotes, related posts. Single-page, reading-optimized typography with comfortable measure.",
|
|
8
|
+
outputKind: "html",
|
|
9
|
+
workflow: `# Blog Post Skill
|
|
10
|
+
|
|
11
|
+
Produce a single blog article page with reading-optimized typography.
|
|
12
|
+
|
|
13
|
+
## Phase 1 — Structure
|
|
14
|
+
|
|
15
|
+
\`\`\`html
|
|
16
|
+
<article class="blog-post">
|
|
17
|
+
<header class="post-header">
|
|
18
|
+
<div class="post-category">Category</div>
|
|
19
|
+
<h1>Article Title That Is Specific and Engaging</h1>
|
|
20
|
+
<div class="post-meta">
|
|
21
|
+
<span class="post-author">Author Name</span>
|
|
22
|
+
<span class="post-date">Month DD, YYYY</span>
|
|
23
|
+
<span class="post-read-time">N min read</span>
|
|
24
|
+
</div>
|
|
25
|
+
</header>
|
|
26
|
+
<figure class="post-hero">
|
|
27
|
+
<img src="..." alt="...">
|
|
28
|
+
<figcaption>Image caption (optional)</figcaption>
|
|
29
|
+
</figure>
|
|
30
|
+
<div class="post-content">
|
|
31
|
+
<!-- prose -->
|
|
32
|
+
</div>
|
|
33
|
+
<footer class="post-footer">
|
|
34
|
+
<div class="post-tags"><!-- tag pills --></div>
|
|
35
|
+
<section class="related-posts"><!-- 3 related cards --></section>
|
|
36
|
+
</footer>
|
|
37
|
+
</article>
|
|
38
|
+
\`\`\`
|
|
39
|
+
|
|
40
|
+
## Phase 2 — Typography
|
|
41
|
+
|
|
42
|
+
\`\`\`css
|
|
43
|
+
.blog-post { max-width: 680px; margin: 0 auto; padding: 64px 24px; }
|
|
44
|
+
|
|
45
|
+
.post-header { margin-bottom: 32px; }
|
|
46
|
+
.post-category { font-size: 13px; font-weight: 600; color: var(--accent); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 12px; }
|
|
47
|
+
.post-header h1 { font-size: 40px; font-weight: 700; line-height: 1.2; letter-spacing: -0.02em; margin-bottom: 16px; }
|
|
48
|
+
.post-meta { display: flex; gap: 16px; font-size: 14px; color: var(--muted); }
|
|
49
|
+
|
|
50
|
+
.post-hero { margin: 0 -24px 40px; }
|
|
51
|
+
.post-hero img { width: 100%; height: 400px; object-fit: cover; border-radius: 12px; }
|
|
52
|
+
.post-hero figcaption { font-size: 13px; color: var(--muted); text-align: center; margin-top: 8px; }
|
|
53
|
+
|
|
54
|
+
/* Prose — comfortable reading measure */
|
|
55
|
+
.post-content { font-size: 18px; line-height: 1.8; color: var(--fg); }
|
|
56
|
+
.post-content p { margin: 24px 0; }
|
|
57
|
+
.post-content h2 { font-size: 28px; font-weight: 600; margin: 48px 0 16px; }
|
|
58
|
+
.post-content h3 { font-size: 22px; font-weight: 600; margin: 36px 0 12px; }
|
|
59
|
+
.post-content blockquote { border-left: 3px solid var(--accent); padding: 8px 20px; margin: 32px 0; font-style: italic; color: var(--muted); }
|
|
60
|
+
.post-content ul, .post-content ol { padding-left: 24px; margin: 16px 0; }
|
|
61
|
+
.post-content li { margin: 8px 0; }
|
|
62
|
+
.post-content img { max-width: 100%; border-radius: 8px; margin: 24px 0; }
|
|
63
|
+
.post-content code { font-family: var(--font-mono); font-size: 0.9em; background: var(--surface); padding: 2px 6px; border-radius: 4px; }
|
|
64
|
+
.post-content pre { background: var(--surface); border: 1px solid var(--border); border-radius: 8px; padding: 20px 24px; overflow-x: auto; font-family: var(--font-mono); font-size: 15px; line-height: 1.6; margin: 24px 0; }
|
|
65
|
+
|
|
66
|
+
/* Tags */
|
|
67
|
+
.post-tags { display: flex; flex-wrap: wrap; gap: 8px; margin: 48px 0; padding-top: 32px; border-top: 1px solid var(--border); }
|
|
68
|
+
.post-tag { padding: 4px 12px; background: var(--surface); border-radius: 999px; font-size: 13px; color: var(--muted); }
|
|
69
|
+
|
|
70
|
+
/* Related posts */
|
|
71
|
+
.related-posts { margin-top: 48px; }
|
|
72
|
+
.related-posts h3 { font-size: 20px; font-weight: 600; margin-bottom: 16px; }
|
|
73
|
+
.related-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 16px; }
|
|
74
|
+
.related-card { border: 1px solid var(--border); border-radius: 8px; overflow: hidden; }
|
|
75
|
+
.related-card img { width: 100%; height: 140px; object-fit: cover; }
|
|
76
|
+
.related-card-content { padding: 16px; }
|
|
77
|
+
.related-card-content h4 { font-size: 15px; font-weight: 600; margin-bottom: 4px; }
|
|
78
|
+
.related-card-content time { font-size: 12px; color: var(--muted); }
|
|
79
|
+
\`\`\`
|
|
80
|
+
|
|
81
|
+
## Phase 3 — Content Rules
|
|
82
|
+
- Article body: 800-2000 words of meaningful content (no lorem ipsum)
|
|
83
|
+
- Every heading is specific and informative
|
|
84
|
+
- Include at least one blockquote and one code block
|
|
85
|
+
- Feature image is a gradient shape if no real image
|
|
86
|
+
- Author bio is not generic — use a plausible name and role`,
|
|
87
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"critique.d.ts","sourceRoot":"","sources":["../../../../src/designer/data/skills/critique.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAC;AAErD,eAAO,MAAM,QAAQ,EAAE,WA6GpB,CAAC"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
export const critique = {
|
|
2
|
+
name: "critique",
|
|
3
|
+
scenario: "design",
|
|
4
|
+
description: "5-dimension expert design review. Scores any HTML artifact on Philosophy Consistency, Visual Hierarchy, Detail Execution, Functionality, and Innovation (each 0-10). Outputs an HTML report with a radar chart, evidence-backed scores, and three action lists: Keep / Fix / Quick-wins.",
|
|
5
|
+
outputKind: "html",
|
|
6
|
+
workflow: `# Critique Skill · 5-Dimension Expert Review
|
|
7
|
+
|
|
8
|
+
Produce a single-file HTML design review report that scores any artifact across 5 dimensions and proposes actionable fixes.
|
|
9
|
+
|
|
10
|
+
## When to use
|
|
11
|
+
- After generating an artifact (deck, prototype, landing page) and the user asks "what's wrong with this?" or "review this"
|
|
12
|
+
- As a self-check loop before emitting output
|
|
13
|
+
- For comparing two variants of the same design
|
|
14
|
+
|
|
15
|
+
## The 5 Dimensions
|
|
16
|
+
|
|
17
|
+
### 1. Philosophy Consistency
|
|
18
|
+
> Does the artifact pick a clear direction and stick to it through every micro-decision?
|
|
19
|
+
|
|
20
|
+
Look for: one declared design direction vs three styles fighting; chrome/kicker vocabulary in one register; accent/serif/mono used by the same rule throughout.
|
|
21
|
+
0-4: Three styles fighting. 5-6: One direction but elements drift. 7-8: Coherent, occasional drift. 9-10: Every element argues for the same thesis.
|
|
22
|
+
|
|
23
|
+
### 2. Visual Hierarchy
|
|
24
|
+
> Can a stranger figure out what to read first, second, third — without being told?
|
|
25
|
+
|
|
26
|
+
Look for: largest type = most important thing; mono/serif/sans roles match information roles; clear primary/secondary/tertiary tiers.
|
|
27
|
+
0-4: Everything shouts. 5-6: Works on hero pages but breaks on body. 7-8: Clear tiers, occasional collision. 9-10: Eye moves with zero friction.
|
|
28
|
+
|
|
29
|
+
### 3. Detail Execution
|
|
30
|
+
> Alignment, leading, kerning at large sizes, image framing, edge-case spacing.
|
|
31
|
+
|
|
32
|
+
Look for: big numbers on baseline or floating; left/right column tops aligned; proportions consistent across pages; orphaned <br> causing single-character lines.
|
|
33
|
+
0-4: Visible tape and string. 5-6: Most pages clean, 1-2 ragged. 7-8: Polished, expert eye finds 2-3 misses. 9-10: Magazine-grade polish.
|
|
34
|
+
|
|
35
|
+
### 4. Functionality
|
|
36
|
+
> Does the artifact work for its intended use?
|
|
37
|
+
|
|
38
|
+
Look for: keyboard/wheel/touch nav working in decks; CTAs above the fold; phone numbers tappable on mobile; code blocks copyable; critical info readable from 4m away.
|
|
39
|
+
0-4: Visually fine but doesn't accomplish its job. 5-6: Core flow works, edge cases broken. 7-8: Robust through normal use. 9-10: Defensively engineered — handles iframe / fullscreen / paste / print.
|
|
40
|
+
|
|
41
|
+
### 5. Innovation
|
|
42
|
+
> Does this push past the median? Is there one element that makes people lean in?
|
|
43
|
+
|
|
44
|
+
Look for: one unexpected layout/motion/typographic move not required; or 100% safe generic output; innovation earned (matches direction) or grafted on.
|
|
45
|
+
0-4: Generic AI-slop median. 5-6: Competent and unmemorable. 7-8: One memorable moment, the rest solid. 9-10: Multiple moves you'd steal — each serving the thesis.
|
|
46
|
+
|
|
47
|
+
## Scoring Discipline
|
|
48
|
+
- **Always cite evidence** — "scored 4 because hero page mixes Playfair display with Inter sans on the same line" beats "feels inconsistent"
|
|
49
|
+
- **Don't average up** — if Hierarchy is 5 because page 3 is broken, don't bump to 7 because pages 1-2 are fine. The score is the worst sustained band.
|
|
50
|
+
- **Don't grade-inflate** — a 7 means strong, not acceptable. If every score is 7+, you're not reviewing critically.
|
|
51
|
+
- **Innovation is allowed to be low** — 5/10 is fine for production deliverables.
|
|
52
|
+
|
|
53
|
+
## Workflow
|
|
54
|
+
|
|
55
|
+
### Step 1 — Acquire the artifact
|
|
56
|
+
1. **Project file** — user said "review the index.html I just made": read it from the project folder.
|
|
57
|
+
2. **Pasted HTML** — user pasted code in chat: read it from the message.
|
|
58
|
+
3. **Self-critique** — you just emitted an artifact and want to self-critique: re-read your own output.
|
|
59
|
+
|
|
60
|
+
### Step 2 — Read enough to score
|
|
61
|
+
Skim the entire <style>, then read 6-8 representative content blocks. Do not score from intent alone — the score depends on executed design, not declared intent.
|
|
62
|
+
|
|
63
|
+
### Step 3 — Score with evidence
|
|
64
|
+
For each of the 5 dimensions, write the score and a 30-80 word evidence paragraph naming specific elements. Example:
|
|
65
|
+
\`\`\`
|
|
66
|
+
Dimension: Detail execution
|
|
67
|
+
Score: 6 / 10
|
|
68
|
+
Evidence: Stat-cards on page 3 align cleanly, but on page 8 the right column sits 2vh higher than the left because .callout has 3vh top margin while the figure doesn't. Image captions use mono on page 5 but sans on page 7 — pick one.
|
|
69
|
+
\`\`\`
|
|
70
|
+
|
|
71
|
+
### Step 4 — Build action lists
|
|
72
|
+
- **Keep** (3-5 bullets) — concrete things working that the user must not break. Cite by class/page/element.
|
|
73
|
+
- **Fix** (3-6 bullets) — must-do, ordered by visual cost saved per minute. Each bullet ≤ 1 sentence.
|
|
74
|
+
- **Quick wins** (3-5 bullets) — 5-15 minutes each, high signal-to-noise.
|
|
75
|
+
|
|
76
|
+
### Step 5 — Emit the report HTML
|
|
77
|
+
Build a single file with:
|
|
78
|
+
- Header: artifact name + date + reviewer + 1-line verdict
|
|
79
|
+
- Big radar chart (inline SVG, no library)
|
|
80
|
+
- 5 dimension cards in a 1-column or 2-column grid
|
|
81
|
+
- Three action lists at the bottom with checkbox affordance
|
|
82
|
+
|
|
83
|
+
Use the active design system tokens if one exists; otherwise default to a neutral light theme (white background, near-black text, one accent for radar fill).
|
|
84
|
+
|
|
85
|
+
## Radar Chart SVG Template
|
|
86
|
+
\`\`\`svg
|
|
87
|
+
<svg viewBox="0 0 400 400" xmlns="http://www.w3.org/2000/svg">
|
|
88
|
+
<!-- Pentagon grid lines -->
|
|
89
|
+
<polygon points="200,40 352,152 294,340 106,340 48,152" fill="none" stroke="#e0e0e0" stroke-width="1"/>
|
|
90
|
+
<polygon points="200,80 314,166 267,306 133,306 86,166" fill="none" stroke="#e0e0e0" stroke-width="1"/>
|
|
91
|
+
<polygon points="200,120 276,180 240,272 160,272 124,180" fill="none" stroke="#e0e0e0" stroke-width="1"/>
|
|
92
|
+
<!-- Score polygon (fill with accent at 20% opacity) -->
|
|
93
|
+
<polygon points="..." fill="var(--accent, #3B82F6)" fill-opacity="0.2" stroke="var(--accent, #3B82F6)" stroke-width="2"/>
|
|
94
|
+
<!-- Labels at each vertex -->
|
|
95
|
+
<text x="200" y="30" text-anchor="middle" font-size="11">Philosophy</text>
|
|
96
|
+
<text x="360" y="155" text-anchor="start" font-size="11">Hierarchy</text>
|
|
97
|
+
<text x="300" y="355" text-anchor="middle" font-size="11">Detail</text>
|
|
98
|
+
<text x="100" y="355" text-anchor="middle" font-size="11">Function</text>
|
|
99
|
+
<text x="40" y="155" text-anchor="end" font-size="11">Innovation</text>
|
|
100
|
+
</svg>
|
|
101
|
+
\`\`\`
|
|
102
|
+
|
|
103
|
+
## Hard Rules
|
|
104
|
+
- **5 scores, every time** — partial reports are not allowed.
|
|
105
|
+
- **Evidence per score** — no "feels off" / "needs work". If you can't cite an element, the score is not justified.
|
|
106
|
+
- **Don't grade-inflate** — overall mean above 8 is suspicious.
|
|
107
|
+
- **Single-file HTML only** — no external CSS/JS. Inline everything.
|
|
108
|
+
- **Radar chart is mandatory** — gives the report a recognizable silhouette.`,
|
|
109
|
+
};
|