create-screenfix 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/bin/create-screenfix.js +40 -0
  2. package/lib/doctor.js +120 -0
  3. package/lib/init.js +165 -0
  4. package/lib/uninstall.js +67 -0
  5. package/lib/update.js +62 -0
  6. package/package.json +35 -0
  7. package/python/build/lib/screenfix/__init__.py +3 -0
  8. package/python/build/lib/screenfix/annotation_window.py +222 -0
  9. package/python/build/lib/screenfix/clipboard_watcher.py +164 -0
  10. package/python/build/lib/screenfix/config.py +66 -0
  11. package/python/build/lib/screenfix/daemon.py +204 -0
  12. package/python/build/lib/screenfix/mcp_server.py +318 -0
  13. package/python/build/lib/screenfix/task_tracker.py +129 -0
  14. package/python/pyproject.toml +24 -0
  15. package/python/requirements.txt +5 -0
  16. package/python/screenfix/__init__.py +3 -0
  17. package/python/screenfix/annotation_window.py +222 -0
  18. package/python/screenfix/clipboard_watcher.py +164 -0
  19. package/python/screenfix/config.py +66 -0
  20. package/python/screenfix/daemon.py +204 -0
  21. package/python/screenfix/mcp_server.py +318 -0
  22. package/python/screenfix/task_tracker.py +129 -0
  23. package/python/screenfix.egg-info/PKG-INFO +11 -0
  24. package/python/screenfix.egg-info/SOURCES.txt +14 -0
  25. package/python/screenfix.egg-info/dependency_links.txt +1 -0
  26. package/python/screenfix.egg-info/entry_points.txt +3 -0
  27. package/python/screenfix.egg-info/requires.txt +5 -0
  28. package/python/screenfix.egg-info/top_level.txt +1 -0
  29. package/templates/commands/screenfix-list-tasks.md +6 -0
  30. package/templates/commands/screenfix-start.md +8 -0
  31. package/templates/commands/screenfix-status.md +6 -0
  32. package/templates/commands/screenfix-stop.md +6 -0
  33. package/templates/commands/screenfix-tasks-next.md +14 -0
  34. package/templates/commands/screenfix-tasks-yolo.md +14 -0
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require('commander');
4
+ const init = require('../lib/init');
5
+ const doctor = require('../lib/doctor');
6
+ const uninstall = require('../lib/uninstall');
7
+ const update = require('../lib/update');
8
+
9
+ program
10
+ .name('create-screenfix')
11
+ .description('ScreenFix - Screenshot capture tool for Claude Code')
12
+ .version('0.1.0');
13
+
14
+ program
15
+ .command('init')
16
+ .description('Initialize ScreenFix in the current project')
17
+ .option('--skip-python', 'Skip Python dependency installation')
18
+ .action(init);
19
+
20
+ program
21
+ .command('doctor')
22
+ .description('Check ScreenFix installation and diagnose issues')
23
+ .action(doctor);
24
+
25
+ program
26
+ .command('uninstall')
27
+ .description('Remove ScreenFix from the current project')
28
+ .action(uninstall);
29
+
30
+ program
31
+ .command('update')
32
+ .description('Update ScreenFix to the latest version')
33
+ .action(update);
34
+
35
+ // Default action: run init when called without subcommand
36
+ if (process.argv.length === 2) {
37
+ init({});
38
+ } else {
39
+ program.parse();
40
+ }
package/lib/doctor.js ADDED
@@ -0,0 +1,120 @@
1
+ const { execSync } = require('child_process');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+
6
+ function check(name, fn) {
7
+ try {
8
+ const result = fn();
9
+ if (result === true) {
10
+ console.log(`\x1b[32m✓\x1b[0m ${name}`);
11
+ return true;
12
+ } else if (result === false) {
13
+ console.log(`\x1b[31m✗\x1b[0m ${name}`);
14
+ return false;
15
+ } else {
16
+ console.log(`\x1b[32m✓\x1b[0m ${name}: ${result}`);
17
+ return true;
18
+ }
19
+ } catch (e) {
20
+ console.log(`\x1b[31m✗\x1b[0m ${name}: ${e.message}`);
21
+ return false;
22
+ }
23
+ }
24
+
25
+ async function doctor() {
26
+ console.log('\n\x1b[1mScreenFix Diagnostics\x1b[0m\n');
27
+
28
+ const installDir = process.cwd();
29
+ let allPassed = true;
30
+
31
+ // Check macOS
32
+ allPassed &= check('macOS', () => {
33
+ if (process.platform !== 'darwin') {
34
+ throw new Error('ScreenFix only works on macOS');
35
+ }
36
+ return true;
37
+ });
38
+
39
+ // Check Python version
40
+ allPassed &= check('Python 3.10+', () => {
41
+ const version = execSync('python3 --version', { encoding: 'utf8' }).trim();
42
+ const match = version.match(/Python (\d+)\.(\d+)/);
43
+ if (!match) throw new Error('Could not determine version');
44
+ const major = parseInt(match[1], 10);
45
+ const minor = parseInt(match[2], 10);
46
+ if (major < 3 || (major === 3 && minor < 10)) {
47
+ throw new Error(`Found ${major}.${minor}, need 3.10+`);
48
+ }
49
+ return `${major}.${minor}`;
50
+ });
51
+
52
+ // Check screenfix package installed
53
+ allPassed &= check('screenfix Python package', () => {
54
+ try {
55
+ execSync('python3 -c "import screenfix"', { encoding: 'utf8', stdio: 'pipe' });
56
+ return true;
57
+ } catch {
58
+ throw new Error('Not installed. Run: npx create-screenfix');
59
+ }
60
+ });
61
+
62
+ // Check .mcp.json exists
63
+ allPassed &= check('.mcp.json exists', () => {
64
+ const mcpPath = path.join(installDir, '.mcp.json');
65
+ if (!fs.existsSync(mcpPath)) {
66
+ throw new Error('Not found');
67
+ }
68
+ return true;
69
+ });
70
+
71
+ // Check .mcp.json has screenfix config
72
+ allPassed &= check('.mcp.json has screenfix config', () => {
73
+ const mcpPath = path.join(installDir, '.mcp.json');
74
+ if (!fs.existsSync(mcpPath)) {
75
+ throw new Error('.mcp.json not found');
76
+ }
77
+ const config = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
78
+ if (!config.mcpServers?.screenfix) {
79
+ throw new Error('screenfix not configured');
80
+ }
81
+ return true;
82
+ });
83
+
84
+ // Check slash commands exist
85
+ allPassed &= check('Slash commands installed', () => {
86
+ const commandsDir = path.join(installDir, '.claude', 'commands');
87
+ if (!fs.existsSync(commandsDir)) {
88
+ throw new Error('.claude/commands not found');
89
+ }
90
+ const commands = fs.readdirSync(commandsDir).filter(f => f.startsWith('screenfix-'));
91
+ if (commands.length === 0) {
92
+ throw new Error('No screenfix commands found');
93
+ }
94
+ return `${commands.length} commands`;
95
+ });
96
+
97
+ // Check daemon state
98
+ allPassed &= check('Daemon status', () => {
99
+ const statePath = path.join(os.homedir(), '.config', 'screenfix', 'state.json');
100
+ if (!fs.existsSync(statePath)) {
101
+ return 'Not running';
102
+ }
103
+ const state = JSON.parse(fs.readFileSync(statePath, 'utf8'));
104
+ if (state.listening) {
105
+ return `Running (PID: ${state.pid})`;
106
+ }
107
+ return 'Not running';
108
+ });
109
+
110
+ console.log('');
111
+
112
+ if (allPassed) {
113
+ console.log('\x1b[32mAll checks passed!\x1b[0m\n');
114
+ } else {
115
+ console.log('\x1b[33mSome checks failed. Run "npx create-screenfix" to fix.\x1b[0m\n');
116
+ process.exit(1);
117
+ }
118
+ }
119
+
120
+ module.exports = doctor;
package/lib/init.js ADDED
@@ -0,0 +1,165 @@
1
+ const { execSync, spawn } = require('child_process');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const os = require('os');
5
+
6
+ function log(msg) {
7
+ console.log(msg);
8
+ }
9
+
10
+ function success(msg) {
11
+ console.log(`\x1b[32m✓\x1b[0m ${msg}`);
12
+ }
13
+
14
+ function error(msg) {
15
+ console.error(`\x1b[31m✗\x1b[0m ${msg}`);
16
+ }
17
+
18
+ function checkPrerequisites() {
19
+ // Check macOS
20
+ if (process.platform !== 'darwin') {
21
+ throw new Error('ScreenFix only works on macOS');
22
+ }
23
+
24
+ // Check Python 3.10+
25
+ try {
26
+ const pythonVersion = execSync('python3 --version', { encoding: 'utf8' });
27
+ const match = pythonVersion.match(/Python (\d+)\.(\d+)/);
28
+ if (!match) {
29
+ throw new Error('Could not determine Python version');
30
+ }
31
+ const major = parseInt(match[1], 10);
32
+ const minor = parseInt(match[2], 10);
33
+ if (major < 3 || (major === 3 && minor < 10)) {
34
+ throw new Error(`Python 3.10+ is required (found ${major}.${minor})`);
35
+ }
36
+ success(`Python ${major}.${minor} detected`);
37
+ } catch (e) {
38
+ if (e.message.includes('Python 3.10+')) {
39
+ throw e;
40
+ }
41
+ throw new Error('Python 3 is not installed. Please install Python 3.10+');
42
+ }
43
+
44
+ // Check pip
45
+ try {
46
+ execSync('python3 -m pip --version', { encoding: 'utf8', stdio: 'pipe' });
47
+ success('pip available');
48
+ } catch {
49
+ throw new Error('pip is not available. Please install pip for Python 3');
50
+ }
51
+ }
52
+
53
+ function installPythonPackage(installDir, options) {
54
+ if (options.skipPython) {
55
+ log('Skipping Python package installation (--skip-python)');
56
+ return;
57
+ }
58
+
59
+ log('Installing Python dependencies...');
60
+
61
+ // Get the path to bundled Python source
62
+ const pythonSrcDir = path.join(__dirname, '..', 'python');
63
+
64
+ if (!fs.existsSync(pythonSrcDir)) {
65
+ throw new Error('Bundled Python package not found. Package may be corrupted.');
66
+ }
67
+
68
+ try {
69
+ // Install in user mode from bundled source
70
+ execSync(`python3 -m pip install --user "${pythonSrcDir}"`, {
71
+ encoding: 'utf8',
72
+ stdio: 'inherit'
73
+ });
74
+ success('Python package installed');
75
+ } catch (e) {
76
+ throw new Error('Failed to install Python package: ' + e.message);
77
+ }
78
+ }
79
+
80
+ function copyMcpConfig(installDir) {
81
+ const mcpConfig = {
82
+ mcpServers: {
83
+ screenfix: {
84
+ type: 'stdio',
85
+ command: 'python3',
86
+ args: ['-m', 'screenfix.mcp_server']
87
+ }
88
+ }
89
+ };
90
+
91
+ const mcpPath = path.join(installDir, '.mcp.json');
92
+
93
+ try {
94
+ if (fs.existsSync(mcpPath)) {
95
+ // Merge with existing .mcp.json
96
+ const existing = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
97
+ existing.mcpServers = existing.mcpServers || {};
98
+ existing.mcpServers.screenfix = mcpConfig.mcpServers.screenfix;
99
+ fs.writeFileSync(mcpPath, JSON.stringify(existing, null, 2) + '\n');
100
+ success('Updated .mcp.json (merged with existing)');
101
+ } else {
102
+ fs.writeFileSync(mcpPath, JSON.stringify(mcpConfig, null, 2) + '\n');
103
+ success('Created .mcp.json');
104
+ }
105
+ } catch (e) {
106
+ throw new Error('Failed to create .mcp.json: ' + e.message);
107
+ }
108
+ }
109
+
110
+ function copySlashCommands(installDir) {
111
+ const commandsDir = path.join(installDir, '.claude', 'commands');
112
+ const templatesDir = path.join(__dirname, '..', 'templates', 'commands');
113
+
114
+ try {
115
+ // Create .claude/commands directory if it doesn't exist
116
+ fs.mkdirSync(commandsDir, { recursive: true });
117
+
118
+ // Get all command templates
119
+ const commands = fs.readdirSync(templatesDir).filter(f => f.endsWith('.md'));
120
+
121
+ for (const cmd of commands) {
122
+ const src = path.join(templatesDir, cmd);
123
+ const dest = path.join(commandsDir, cmd);
124
+ fs.copyFileSync(src, dest);
125
+ }
126
+
127
+ success(`Installed ${commands.length} slash commands`);
128
+ } catch (e) {
129
+ throw new Error('Failed to copy slash commands: ' + e.message);
130
+ }
131
+ }
132
+
133
+ async function init(options) {
134
+ console.log('\n\x1b[1mScreenFix Setup\x1b[0m\n');
135
+
136
+ const installDir = process.cwd();
137
+
138
+ try {
139
+ // Step 1: Check prerequisites
140
+ log('Checking prerequisites...');
141
+ checkPrerequisites();
142
+
143
+ // Step 2: Install Python package
144
+ installPythonPackage(installDir, options);
145
+
146
+ // Step 3: Copy MCP config
147
+ copyMcpConfig(installDir);
148
+
149
+ // Step 4: Copy slash commands
150
+ copySlashCommands(installDir);
151
+
152
+ // Done
153
+ console.log('\n\x1b[32m\x1b[1mScreenFix installed successfully!\x1b[0m\n');
154
+ console.log('Next steps:');
155
+ console.log(' 1. Restart Claude Code to load the MCP server');
156
+ console.log(' 2. Run /screenfix-start to begin');
157
+ console.log(' 3. Use Cmd+Ctrl+Shift+4 to capture screenshots\n');
158
+
159
+ } catch (e) {
160
+ error(e.message);
161
+ process.exit(1);
162
+ }
163
+ }
164
+
165
+ module.exports = init;
@@ -0,0 +1,67 @@
1
+ const { execSync } = require('child_process');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ function success(msg) {
6
+ console.log(`\x1b[32m✓\x1b[0m ${msg}`);
7
+ }
8
+
9
+ function warn(msg) {
10
+ console.log(`\x1b[33m!\x1b[0m ${msg}`);
11
+ }
12
+
13
+ async function uninstall() {
14
+ console.log('\n\x1b[1mUninstalling ScreenFix\x1b[0m\n');
15
+
16
+ const installDir = process.cwd();
17
+
18
+ // Remove slash commands
19
+ const commandsDir = path.join(installDir, '.claude', 'commands');
20
+ if (fs.existsSync(commandsDir)) {
21
+ const commands = fs.readdirSync(commandsDir).filter(f => f.startsWith('screenfix-'));
22
+ for (const cmd of commands) {
23
+ fs.unlinkSync(path.join(commandsDir, cmd));
24
+ }
25
+ if (commands.length > 0) {
26
+ success(`Removed ${commands.length} slash commands`);
27
+ }
28
+ }
29
+
30
+ // Remove screenfix from .mcp.json
31
+ const mcpPath = path.join(installDir, '.mcp.json');
32
+ if (fs.existsSync(mcpPath)) {
33
+ try {
34
+ const config = JSON.parse(fs.readFileSync(mcpPath, 'utf8'));
35
+ if (config.mcpServers?.screenfix) {
36
+ delete config.mcpServers.screenfix;
37
+
38
+ // If no other MCP servers, remove the file
39
+ if (Object.keys(config.mcpServers).length === 0) {
40
+ fs.unlinkSync(mcpPath);
41
+ success('Removed .mcp.json (no other servers configured)');
42
+ } else {
43
+ fs.writeFileSync(mcpPath, JSON.stringify(config, null, 2) + '\n');
44
+ success('Removed screenfix from .mcp.json');
45
+ }
46
+ }
47
+ } catch (e) {
48
+ warn('Could not update .mcp.json: ' + e.message);
49
+ }
50
+ }
51
+
52
+ // Uninstall Python package
53
+ try {
54
+ execSync('python3 -m pip uninstall -y screenfix', {
55
+ encoding: 'utf8',
56
+ stdio: 'pipe'
57
+ });
58
+ success('Uninstalled Python package');
59
+ } catch {
60
+ warn('Python package may not have been installed');
61
+ }
62
+
63
+ console.log('\n\x1b[32mScreenFix uninstalled.\x1b[0m\n');
64
+ console.log('Note: Screenshots in ./screenfix/ were not deleted.\n');
65
+ }
66
+
67
+ module.exports = uninstall;
package/lib/update.js ADDED
@@ -0,0 +1,62 @@
1
+ const { execSync } = require('child_process');
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+
5
+ function success(msg) {
6
+ console.log(`\x1b[32m✓\x1b[0m ${msg}`);
7
+ }
8
+
9
+ function log(msg) {
10
+ console.log(msg);
11
+ }
12
+
13
+ async function update() {
14
+ console.log('\n\x1b[1mUpdating ScreenFix\x1b[0m\n');
15
+
16
+ const installDir = process.cwd();
17
+
18
+ // Re-install Python package from bundled source
19
+ log('Updating Python package...');
20
+ const pythonSrcDir = path.join(__dirname, '..', 'python');
21
+
22
+ if (!fs.existsSync(pythonSrcDir)) {
23
+ console.error('\x1b[31m✗\x1b[0m Bundled Python package not found');
24
+ process.exit(1);
25
+ }
26
+
27
+ try {
28
+ execSync(`python3 -m pip install --user --upgrade "${pythonSrcDir}"`, {
29
+ encoding: 'utf8',
30
+ stdio: 'inherit'
31
+ });
32
+ success('Python package updated');
33
+ } catch (e) {
34
+ console.error('\x1b[31m✗\x1b[0m Failed to update Python package');
35
+ process.exit(1);
36
+ }
37
+
38
+ // Re-copy slash commands (in case of updates)
39
+ log('Updating slash commands...');
40
+ const commandsDir = path.join(installDir, '.claude', 'commands');
41
+ const templatesDir = path.join(__dirname, '..', 'templates', 'commands');
42
+
43
+ try {
44
+ fs.mkdirSync(commandsDir, { recursive: true });
45
+
46
+ const commands = fs.readdirSync(templatesDir).filter(f => f.endsWith('.md'));
47
+ for (const cmd of commands) {
48
+ const src = path.join(templatesDir, cmd);
49
+ const dest = path.join(commandsDir, cmd);
50
+ fs.copyFileSync(src, dest);
51
+ }
52
+ success(`Updated ${commands.length} slash commands`);
53
+ } catch (e) {
54
+ console.error('\x1b[31m✗\x1b[0m Failed to update slash commands: ' + e.message);
55
+ process.exit(1);
56
+ }
57
+
58
+ console.log('\n\x1b[32mScreenFix updated successfully!\x1b[0m\n');
59
+ console.log('Restart Claude Code to apply changes.\n');
60
+ }
61
+
62
+ module.exports = update;
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "create-screenfix",
3
+ "version": "0.1.0",
4
+ "description": "Screenshot capture tool for Claude Code via MCP",
5
+ "bin": {
6
+ "create-screenfix": "./bin/create-screenfix.js"
7
+ },
8
+ "keywords": [
9
+ "claude-code",
10
+ "mcp",
11
+ "screenshot",
12
+ "screenfix",
13
+ "claude"
14
+ ],
15
+ "engines": {
16
+ "node": ">=16.0.0"
17
+ },
18
+ "os": [
19
+ "darwin"
20
+ ],
21
+ "files": [
22
+ "bin/",
23
+ "lib/",
24
+ "templates/",
25
+ "python/"
26
+ ],
27
+ "repository": {
28
+ "type": "git",
29
+ "url": "https://github.com/anthropics/screenfix.git"
30
+ },
31
+ "license": "MIT",
32
+ "dependencies": {
33
+ "commander": "^11.0.0"
34
+ }
35
+ }
@@ -0,0 +1,3 @@
1
+ """ScreenFix - Screen snipping tool integrated with Claude Code via MCP."""
2
+
3
+ __version__ = "0.1.0"