spritecook-mcp 0.2.11 → 0.2.13

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/src/setup.mjs CHANGED
@@ -1,160 +1,160 @@
1
- import chalk from 'chalk';
2
- import prompts from 'prompts';
3
- import { printBanner, step, success, info, warn, error } from './ui.mjs';
4
- import { authenticate } from './auth.mjs';
5
- import { verifyApiKey } from './verify.mjs';
6
- import { detectEditors, writeConfigs, readExistingKey } from './editors.mjs';
7
- import { maybeInstallSkill } from './skill.mjs';
8
-
9
- /**
10
- * Main setup flow orchestrator.
11
- * Called by bin/cli.mjs as the entry point.
12
- */
13
- export async function run() {
14
- printBanner();
15
-
16
- // ── Step 1: Authenticate ──────────────────────────────────────────
17
- step(1, 'Authentication');
18
-
19
- let apiKey = null;
20
-
21
- // Check if there's already a working SpriteCook key in an editor config
22
- const existingKey = readExistingKey();
23
- if (existingKey) {
24
- info('Found existing SpriteCook API key in your editor config.');
25
- const verification = await verifyApiKey(existingKey);
26
- if (verification.ok) {
27
- const response = await prompts({
28
- type: 'select',
29
- name: 'action',
30
- message: 'Existing key is valid. What would you like to do?',
31
- choices: [
32
- { title: 'Keep existing key and update configs', value: 'keep' },
33
- { title: 'Create a new key (replaces current)', value: 'new' },
34
- ],
35
- initial: 0,
36
- });
37
-
38
- if (response.action === 'keep') {
39
- apiKey = existingKey;
40
- success('Using existing API key.');
41
- }
42
- } else {
43
- warn('Existing key is no longer valid. Please authenticate again.');
44
- }
45
- }
46
-
47
- if (!apiKey) {
48
- apiKey = await authenticate();
49
- if (!apiKey) {
50
- console.log();
51
- error('Setup cancelled. No API key obtained.');
52
- process.exit(1);
53
- }
54
-
55
- // ── Step 2: Verify the key works ──────────────────────────────────
56
- step(2, 'Verification');
57
-
58
- const verification = await verifyApiKey(apiKey);
59
- if (!verification.ok) {
60
- error('API key verification failed. Please check your key and try again.');
61
- process.exit(1);
62
- }
63
- }
64
-
65
- // ── Step 3: Detect editors and write configs ──────────────────────
66
- step(3, 'Editor Configuration');
67
-
68
- const editors = detectEditors();
69
-
70
- // Let user select which editors to configure
71
- const editorResponse = await prompts({
72
- type: 'multiselect',
73
- name: 'editors',
74
- message: 'Configure MCP for:',
75
- choices: editors.map((e) => ({
76
- title: e.detected ? chalk.white(e.name) : chalk.dim(e.name),
77
- value: e.name,
78
- selected: e.detected,
79
- })),
80
- hint: 'Space to toggle, Enter to confirm',
81
- instructions: false,
82
- });
83
-
84
- const selected = editors.filter((e) => editorResponse.editors?.includes(e.name));
85
-
86
- if (selected.length === 0) {
87
- warn('No editors selected. Skipping config.');
88
- console.log();
89
- info('You can configure manually later. Your API key:');
90
- console.log(` ${chalk.dim(apiKey.slice(0, 16) + '...')}`);
91
- } else {
92
- // Ask about scope for editors that support both project and global
93
- const multiScopeEditors = selected.filter((e) => e.scopes.length > 1);
94
- if (multiScopeEditors.length > 0) {
95
- console.log();
96
- const scopeResponse = await prompts({
97
- type: 'select',
98
- name: 'scope',
99
- message: 'Install MCP config:',
100
- choices: [
101
- { title: 'This project only (recommended)', value: 'project', description: 'Writes to project config files' },
102
- { title: 'Global (all projects)', value: 'global', description: 'Writes to user-level config' },
103
- ],
104
- initial: 0,
105
- });
106
-
107
- const chosenScope = scopeResponse.scope || 'project';
108
- for (const e of selected) {
109
- if (e.scopes.includes(chosenScope)) {
110
- e._chosenScope = chosenScope;
111
- }
112
- // Editors with only 1 scope keep their default
113
- }
114
- }
115
-
116
- console.log();
117
- info('Writing MCP config...');
118
- const written = writeConfigs(selected, apiKey);
119
-
120
- if (written === 0) {
121
- warn('No configs were written. You may need to configure manually.');
122
- }
123
- }
124
-
125
- // ── Step 4: Optional agent skill ─────────────────────────────────
126
- step(4, 'Agent Skill (optional)');
127
- await maybeInstallSkill(selected);
128
-
129
- // ── Done ──────────────────────────────────────────────────────────
130
- const configuredNames = selected.map((e) => e.name);
131
- const hasCursor = configuredNames.includes('Cursor');
132
- const hasVSCode = configuredNames.includes('VS Code');
133
-
134
- console.log();
135
- console.log(chalk.bold.green(' Setup complete!'));
136
- console.log();
137
-
138
- if (hasCursor || hasVSCode) {
139
- console.log(chalk.bgYellow.black.bold(' ACTION REQUIRED '));
140
- console.log();
141
- if (hasCursor) {
142
- console.log(chalk.white.bold(' Cursor:'));
143
- console.log(chalk.white(' 1. Open Settings (Ctrl+Shift+J / Cmd+Shift+J)'));
144
- console.log(chalk.white(' 2. Go to Tools & MCP'));
145
- console.log(chalk.white(' 3. Enable "spritecook"'));
146
- }
147
- if (hasVSCode) {
148
- if (hasCursor) console.log();
149
- console.log(chalk.white.bold(' VS Code:'));
150
- console.log(chalk.white(' Reload the window (Ctrl+Shift+P > "Reload Window")'));
151
- }
152
- console.log();
153
- console.log(chalk.dim(' ─────────────────────────────────────────'));
154
- }
155
-
156
- console.log();
157
- console.log(chalk.dim(' Then try asking your AI agent:'));
158
- console.log(chalk.white(' "Generate a chicken sprite"'));
159
- console.log();
160
- }
1
+ import chalk from 'chalk';
2
+ import prompts from 'prompts';
3
+ import { printBanner, step, success, info, warn, error } from './ui.mjs';
4
+ import { authenticate } from './auth.mjs';
5
+ import { verifyApiKey } from './verify.mjs';
6
+ import { detectEditors, writeConfigs, readExistingKey } from './editors.mjs';
7
+ import { maybeInstallSkill } from './skill.mjs';
8
+
9
+ /**
10
+ * Main setup flow orchestrator.
11
+ * Called by bin/cli.mjs as the entry point.
12
+ */
13
+ export async function run() {
14
+ printBanner();
15
+
16
+ // ── Step 1: Authenticate ──────────────────────────────────────────
17
+ step(1, 'Authentication');
18
+
19
+ let apiKey = null;
20
+
21
+ // Check if there's already a working SpriteCook key in an editor config
22
+ const existingKey = readExistingKey();
23
+ if (existingKey) {
24
+ info('Found existing SpriteCook API key in your editor config.');
25
+ const verification = await verifyApiKey(existingKey);
26
+ if (verification.ok) {
27
+ const response = await prompts({
28
+ type: 'select',
29
+ name: 'action',
30
+ message: 'Existing key is valid. What would you like to do?',
31
+ choices: [
32
+ { title: 'Keep existing key and update configs', value: 'keep' },
33
+ { title: 'Create a new key (replaces current)', value: 'new' },
34
+ ],
35
+ initial: 0,
36
+ });
37
+
38
+ if (response.action === 'keep') {
39
+ apiKey = existingKey;
40
+ success('Using existing API key.');
41
+ }
42
+ } else {
43
+ warn('Existing key is no longer valid. Please authenticate again.');
44
+ }
45
+ }
46
+
47
+ if (!apiKey) {
48
+ apiKey = await authenticate();
49
+ if (!apiKey) {
50
+ console.log();
51
+ error('Setup cancelled. No API key obtained.');
52
+ process.exit(1);
53
+ }
54
+
55
+ // ── Step 2: Verify the key works ──────────────────────────────────
56
+ step(2, 'Verification');
57
+
58
+ const verification = await verifyApiKey(apiKey);
59
+ if (!verification.ok) {
60
+ error('API key verification failed. Please check your key and try again.');
61
+ process.exit(1);
62
+ }
63
+ }
64
+
65
+ // ── Step 3: Detect editors and write configs ──────────────────────
66
+ step(3, 'Editor Configuration');
67
+
68
+ const editors = detectEditors();
69
+
70
+ // Let user select which editors to configure
71
+ const editorResponse = await prompts({
72
+ type: 'multiselect',
73
+ name: 'editors',
74
+ message: 'Configure MCP for:',
75
+ choices: editors.map((e) => ({
76
+ title: e.detected ? chalk.white(e.name) : chalk.dim(e.name),
77
+ value: e.name,
78
+ selected: e.detected,
79
+ })),
80
+ hint: 'Space to toggle, Enter to confirm',
81
+ instructions: false,
82
+ });
83
+
84
+ const selected = editors.filter((e) => editorResponse.editors?.includes(e.name));
85
+
86
+ if (selected.length === 0) {
87
+ warn('No editors selected. Skipping config.');
88
+ console.log();
89
+ info('You can configure manually later. Your API key:');
90
+ console.log(` ${chalk.dim(apiKey.slice(0, 16) + '...')}`);
91
+ } else {
92
+ // Ask about scope for editors that support both project and global
93
+ const multiScopeEditors = selected.filter((e) => e.scopes.length > 1);
94
+ if (multiScopeEditors.length > 0) {
95
+ console.log();
96
+ const scopeResponse = await prompts({
97
+ type: 'select',
98
+ name: 'scope',
99
+ message: 'Install MCP config:',
100
+ choices: [
101
+ { title: 'This project only (recommended)', value: 'project', description: 'Writes to project config files' },
102
+ { title: 'Global (all projects)', value: 'global', description: 'Writes to user-level config' },
103
+ ],
104
+ initial: 0,
105
+ });
106
+
107
+ const chosenScope = scopeResponse.scope || 'project';
108
+ for (const e of selected) {
109
+ if (e.scopes.includes(chosenScope)) {
110
+ e._chosenScope = chosenScope;
111
+ }
112
+ // Editors with only 1 scope keep their default
113
+ }
114
+ }
115
+
116
+ console.log();
117
+ info('Writing MCP config...');
118
+ const written = writeConfigs(selected, apiKey);
119
+
120
+ if (written === 0) {
121
+ warn('No configs were written. You may need to configure manually.');
122
+ }
123
+ }
124
+
125
+ // ── Step 4: Optional agent skill ─────────────────────────────────
126
+ step(4, 'Agent Skill (optional)');
127
+ await maybeInstallSkill(selected);
128
+
129
+ // ── Done ──────────────────────────────────────────────────────────
130
+ const configuredNames = selected.map((e) => e.name);
131
+ const hasCursor = configuredNames.includes('Cursor');
132
+ const hasVSCode = configuredNames.includes('VS Code');
133
+
134
+ console.log();
135
+ console.log(chalk.bold.green(' Setup complete!'));
136
+ console.log();
137
+
138
+ if (hasCursor || hasVSCode) {
139
+ console.log(chalk.bgYellow.black.bold(' ACTION REQUIRED '));
140
+ console.log();
141
+ if (hasCursor) {
142
+ console.log(chalk.white.bold(' Cursor:'));
143
+ console.log(chalk.white(' 1. Open Settings (Ctrl+Shift+J / Cmd+Shift+J)'));
144
+ console.log(chalk.white(' 2. Go to Tools & MCP'));
145
+ console.log(chalk.white(' 3. Enable "spritecook"'));
146
+ }
147
+ if (hasVSCode) {
148
+ if (hasCursor) console.log();
149
+ console.log(chalk.white.bold(' VS Code:'));
150
+ console.log(chalk.white(' Reload the window (Ctrl+Shift+P > "Reload Window")'));
151
+ }
152
+ console.log();
153
+ console.log(chalk.dim(' ─────────────────────────────────────────'));
154
+ }
155
+
156
+ console.log();
157
+ console.log(chalk.dim(' Then try asking your AI agent:'));
158
+ console.log(chalk.white(' "Generate a chicken sprite"'));
159
+ console.log();
160
+ }
package/src/skill.mjs CHANGED
@@ -1,102 +1,124 @@
1
- import { execSync } from 'node:child_process';
2
- import { platform } from 'node:os';
3
- import { mkdirSync, writeFileSync } from 'node:fs';
4
- import { join } from 'node:path';
5
- import prompts from 'prompts';
6
- import { success, info, warn } from './ui.mjs';
7
-
8
- const SKILL_RAW_URL =
9
- 'https://raw.githubusercontent.com/SpriteCook/skills/main/skills/spritecook-generate-sprites/SKILL.md';
10
-
11
- /**
12
- * Download SKILL.md directly from GitHub and write it into each
13
- * selected editor's skill directory. Used as a fallback when
14
- * `npx skills add` fails (e.g. git not installed).
15
- *
16
- * @param {Array<{skillDirs:{project:string|null,global:string|null}, _chosenScope?:string}>} editors
17
- */
18
- async function fallbackInstall(editors) {
19
- info('Trying direct download fallback...');
20
-
21
- let body;
22
- try {
23
- const res = await fetch(SKILL_RAW_URL);
24
- if (!res.ok) throw new Error(`HTTP ${res.status}`);
25
- body = await res.text();
26
- } catch (e) {
27
- warn(`Could not download skill file: ${e.message}`);
28
- return false;
29
- }
30
-
31
- let wrote = 0;
32
- for (const editor of editors) {
33
- const dirs = editor.skillDirs;
34
- if (!dirs) continue;
35
-
36
- const scope = editor._chosenScope || 'project';
37
- const dir = dirs[scope] || dirs.project || dirs.global;
38
- if (!dir) continue;
39
-
40
- try {
41
- mkdirSync(dir, { recursive: true });
42
- writeFileSync(join(dir, 'SKILL.md'), body, 'utf-8');
43
- wrote++;
44
- } catch {
45
- // best-effort, skip on failure
46
- }
47
- }
48
-
49
- if (wrote > 0) {
50
- success(`Agent skill installed to ${wrote} editor(s) via direct download.`);
51
- return true;
52
- }
53
- return false;
54
- }
55
-
56
- /**
57
- * Install or update the SpriteCook agent skill.
58
- *
59
- * Primary: `npx skills add SpriteCook/skills` (requires git).
60
- * Fallback: download SKILL.md directly from GitHub and place it
61
- * in each selected editor's skill directory.
62
- *
63
- * @param {Array} [selectedEditors] - editors the user chose in step 3
64
- */
65
- export async function maybeInstallSkill(selectedEditors = []) {
66
- info('The agent skill teaches your AI how to generate sprites autonomously.');
67
- const response = await prompts({
68
- type: 'confirm',
69
- name: 'install',
70
- message: 'Install SpriteCook agent skill? (highly recommended)',
71
- initial: true,
72
- });
73
-
74
- if (!response.install) {
75
- return;
76
- }
77
-
78
- info('Installing skill via npx skills add SpriteCook/skills...');
79
-
80
- try {
81
- const npxCmd = platform() === 'win32' ? 'npx.cmd' : 'npx';
82
- execSync(`${npxCmd} -y skills add SpriteCook/skills`, {
83
- stdio: 'inherit',
84
- timeout: 60_000,
85
- });
86
- success('Agent skill installed.');
87
- return;
88
- } catch {
89
- // Primary method failed -- try fallback
90
- }
91
-
92
- // ── Fallback: direct download ────────────────────────────────────
93
- if (selectedEditors.length > 0) {
94
- const ok = await fallbackInstall(selectedEditors);
95
- if (ok) return;
96
- }
97
-
98
- warn('Skill install failed (git may not be installed).');
99
- console.log(' You can install it manually later:');
100
- console.log(' npx skills add SpriteCook/skills');
101
- console.log(' Or install git and re-run this setup.');
102
- }
1
+ import { execSync } from 'node:child_process';
2
+ import { platform } from 'node:os';
3
+ import { mkdirSync, writeFileSync } from 'node:fs';
4
+ import { basename, dirname, join } from 'node:path';
5
+ import prompts from 'prompts';
6
+ import { success, info, warn } from './ui.mjs';
7
+
8
+ const SKILLS = [
9
+ {
10
+ name: 'spritecook-workflow-essentials',
11
+ rawUrl: 'https://raw.githubusercontent.com/SpriteCook/skills/main/skills/spritecook-workflow-essentials/SKILL.md',
12
+ },
13
+ {
14
+ name: 'spritecook-generate-sprites',
15
+ rawUrl: 'https://raw.githubusercontent.com/SpriteCook/skills/main/skills/spritecook-generate-sprites/SKILL.md',
16
+ },
17
+ {
18
+ name: 'spritecook-animate-assets',
19
+ rawUrl: 'https://raw.githubusercontent.com/SpriteCook/skills/main/skills/spritecook-animate-assets/SKILL.md',
20
+ },
21
+ ];
22
+
23
+ /**
24
+ * Download SKILL.md directly from GitHub and write it into each
25
+ * selected editor's skill directory. Used as a fallback when
26
+ * `npx skills add` fails (e.g. git not installed).
27
+ *
28
+ * @param {Array<{skillDirs:{project:string|null,global:string|null}, _chosenScope?:string}>} editors
29
+ */
30
+ async function fallbackInstall(editors) {
31
+ info('Trying direct download fallback...');
32
+
33
+ const skillBodies = [];
34
+ try {
35
+ for (const skill of SKILLS) {
36
+ const res = await fetch(skill.rawUrl);
37
+ if (!res.ok) throw new Error(`${skill.name}: HTTP ${res.status}`);
38
+ skillBodies.push({
39
+ name: skill.name,
40
+ body: await res.text(),
41
+ });
42
+ }
43
+ } catch (e) {
44
+ warn(`Could not download skill files: ${e.message}`);
45
+ return false;
46
+ }
47
+
48
+ let wrote = 0;
49
+ for (const editor of editors) {
50
+ const dirs = editor.skillDirs;
51
+ if (!dirs) continue;
52
+
53
+ const scope = editor._chosenScope || 'project';
54
+ const dir = dirs[scope] || dirs.project || dirs.global;
55
+ if (!dir) continue;
56
+
57
+ const skillsRoot = basename(dir) === 'spritecook' ? dirname(dir) : dir;
58
+
59
+ try {
60
+ for (const skill of skillBodies) {
61
+ const skillDir = join(skillsRoot, skill.name);
62
+ mkdirSync(skillDir, { recursive: true });
63
+ writeFileSync(join(skillDir, 'SKILL.md'), skill.body, 'utf-8');
64
+ wrote++;
65
+ }
66
+ } catch {
67
+ // best-effort, skip on failure
68
+ }
69
+ }
70
+
71
+ if (wrote > 0) {
72
+ success(`Agent skills installed via direct download (${wrote} files written).`);
73
+ return true;
74
+ }
75
+ return false;
76
+ }
77
+
78
+ /**
79
+ * Install or update the SpriteCook agent skill.
80
+ *
81
+ * Primary: `npx skills add SpriteCook/skills --skill '*'` (requires git).
82
+ * Fallback: download SKILL.md directly from GitHub and place it
83
+ * in each selected editor's skill directory.
84
+ *
85
+ * @param {Array} [selectedEditors] - editors the user chose in step 3
86
+ */
87
+ export async function maybeInstallSkill(selectedEditors = []) {
88
+ info('The agent skill teaches your AI how to generate sprites autonomously.');
89
+ const response = await prompts({
90
+ type: 'confirm',
91
+ name: 'install',
92
+ message: 'Install SpriteCook agent skill? (highly recommended)',
93
+ initial: true,
94
+ });
95
+
96
+ if (!response.install) {
97
+ return;
98
+ }
99
+
100
+ info('Installing skills via npx skills add SpriteCook/skills --skill "*" ...');
101
+
102
+ try {
103
+ const npxCmd = platform() === 'win32' ? 'npx.cmd' : 'npx';
104
+ execSync(`${npxCmd} -y skills add SpriteCook/skills --skill "*"`, {
105
+ stdio: 'inherit',
106
+ timeout: 60_000,
107
+ });
108
+ success('Agent skills installed.');
109
+ return;
110
+ } catch {
111
+ // Primary method failed -- try fallback
112
+ }
113
+
114
+ // ── Fallback: direct download ────────────────────────────────────
115
+ if (selectedEditors.length > 0) {
116
+ const ok = await fallbackInstall(selectedEditors);
117
+ if (ok) return;
118
+ }
119
+
120
+ warn('Skill install failed (git may not be installed).');
121
+ console.log(' You can install it manually later:');
122
+ console.log(' npx skills add SpriteCook/skills --skill "*"');
123
+ console.log(' Or install git and re-run this setup.');
124
+ }