@superclaude-org/superflag 3.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.
@@ -0,0 +1,192 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ SuperFlag - Claude Code Hook
4
+ Simple flag detection and message output for Claude Code
5
+ """
6
+
7
+ import sys
8
+ import json
9
+ import yaml
10
+ from pathlib import Path
11
+
12
+ # Constants
13
+ FLAGS_YAML_PATH = Path.home() / ".superflag" / "flags.yaml"
14
+ AUTO_FLAG = '--auto'
15
+ RESET_FLAG = '--reset'
16
+
17
+
18
+ def load_config():
19
+ """Load YAML configuration file"""
20
+ if not FLAGS_YAML_PATH.exists():
21
+ return None
22
+
23
+ try:
24
+ with open(FLAGS_YAML_PATH, 'r', encoding='utf-8') as f:
25
+ return yaml.safe_load(f)
26
+ except Exception:
27
+ return None
28
+
29
+
30
+ def extract_valid_flags(user_input, valid_flags):
31
+ """Extract flags using simple 'in' check - 100% coverage"""
32
+ # Use set to avoid duplicates, then convert back to list
33
+ found_flags = [flag for flag in valid_flags if flag in user_input]
34
+ # Preserve order from valid_flags but remove duplicates
35
+ return list(dict.fromkeys(found_flags))
36
+
37
+
38
+ def get_auto_message(has_other_flags, other_flags, hook_messages):
39
+ """Generate message for --auto flag"""
40
+ if has_other_flags:
41
+ # Auto with other flags
42
+ config = hook_messages.get('auto_with_context', {})
43
+ # Format as comma-separated list instead of JSON array
44
+ other_flags_str = ', '.join(other_flags)
45
+ return config.get('message', '').format(
46
+ other_flags=other_flags_str
47
+ )
48
+ else:
49
+ # Auto alone
50
+ config = hook_messages.get('auto_authority', {})
51
+ # No formatting needed for auto alone message
52
+ return config.get('message', '')
53
+
54
+
55
+ def get_other_flags_message(other_flags, hook_messages):
56
+ """Generate message for non-auto flags"""
57
+ other_flags_set = set(other_flags)
58
+
59
+ # Check if it's ONLY --reset
60
+ if other_flags_set == {RESET_FLAG}:
61
+ config = hook_messages.get('reset_protocol', {})
62
+ # Check if --reset is with other flags
63
+ elif RESET_FLAG in other_flags_set:
64
+ config = hook_messages.get('reset_with_others', {})
65
+ else:
66
+ # Standard execution for all other cases
67
+ config = hook_messages.get('standard_execution', {})
68
+
69
+ message_template = config.get('message', '')
70
+ if message_template:
71
+ # Format as comma-separated list instead of JSON array
72
+ return message_template.format(
73
+ flag_list=', '.join(other_flags),
74
+ flags=', '.join(other_flags)
75
+ )
76
+ return None
77
+
78
+
79
+ def generate_messages(flags, hook_messages):
80
+ """Generate appropriate messages based on detected flags"""
81
+ if not flags:
82
+ return []
83
+
84
+ messages = []
85
+ detected_set = set(flags)
86
+
87
+ # Process --auto flag independently
88
+ if AUTO_FLAG in detected_set:
89
+ other_flags = [f for f in flags if f != AUTO_FLAG]
90
+ auto_message = get_auto_message(bool(other_flags), other_flags, hook_messages)
91
+ if auto_message:
92
+ messages.append(auto_message)
93
+ else:
94
+ other_flags = flags
95
+
96
+ # Process remaining flags if any (but not when --auto is present)
97
+ if other_flags and AUTO_FLAG not in detected_set:
98
+ other_message = get_other_flags_message(other_flags, hook_messages)
99
+ if other_message:
100
+ messages.append(other_message)
101
+
102
+ return messages
103
+
104
+
105
+ def process_input(user_input):
106
+ """Main processing logic"""
107
+ # Load configuration
108
+ config = load_config()
109
+ if not config:
110
+ return None
111
+
112
+ # Get valid flags from directives
113
+ directives = config.get('directives', {})
114
+ valid_flags = set(directives.keys())
115
+
116
+ # Extract valid flags directly (100% coverage approach)
117
+ flags = extract_valid_flags(user_input, valid_flags)
118
+ if not flags:
119
+ return None
120
+
121
+ # Generate messages
122
+ hook_messages = config.get('hook_messages', {})
123
+ # messages = generate_messages(flags, hook_messages)
124
+ messages = generate_messages(flags, hook_messages)
125
+
126
+ if messages:
127
+ return {
128
+ # 'flags': flags,
129
+ 'messages': messages
130
+ }
131
+ return None
132
+
133
+
134
+ def main():
135
+ """Main entry point for Claude Code Hook"""
136
+ try:
137
+ # Read input from stdin
138
+ data = sys.stdin.read().strip()
139
+
140
+ # Parse input - Claude Code may send JSON
141
+ user_input = ""
142
+ if data:
143
+ # Try JSON parsing first (like hook_handler.py)
144
+ if data.startswith('{') and data.endswith('}'):
145
+ try:
146
+ parsed = json.loads(data)
147
+ # Extract prompt/message/input field
148
+ user_input = parsed.get('prompt', parsed.get('message', parsed.get('input', data)))
149
+ except json.JSONDecodeError:
150
+ user_input = data
151
+ else:
152
+ user_input = data
153
+
154
+ # Process input
155
+ result = process_input(user_input) if user_input else None
156
+
157
+ # Output result
158
+ if result and result.get('messages'):
159
+ # Plain text 출력용 메시지 준비 (JSON에는 포함 안 함)
160
+ display_message = ""
161
+ if isinstance(result.get('messages'), list):
162
+ display_message = "\n".join([m for m in result['messages'] if m])
163
+ else:
164
+ display_message = str(result.get('messages', ''))
165
+
166
+ # Plain text만 출력 (JSON 제거)
167
+ if display_message:
168
+ print(display_message)
169
+
170
+ # JSON 출력 제거 - Claude가 plain text도 파싱할 수 있음
171
+ # print(json.dumps(result, ensure_ascii=False))
172
+ else:
173
+ # No valid flags or messages
174
+ print("{}")
175
+
176
+ return 0
177
+
178
+ except KeyboardInterrupt:
179
+ # User interrupted with Ctrl+C
180
+ print("{}")
181
+ return 130
182
+
183
+ except Exception as e:
184
+ # Log error to stderr (not visible in Claude Code output)
185
+ print(f"Hook error: {str(e)}", file=sys.stderr)
186
+ # Return safe empty JSON for Claude
187
+ print("{}")
188
+ return 1
189
+
190
+
191
+ if __name__ == "__main__":
192
+ sys.exit(main())
package/package.json ADDED
@@ -0,0 +1,59 @@
1
+ {
2
+ "name": "@superclaude-org/superflag",
3
+ "version": "3.1.0",
4
+ "description": "SuperFlag - MCP-based flag system for AI assistants",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "superflag": "dist/index.js",
9
+ "superflag-install": "dist/install.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc && node update-version.js",
13
+ "watch": "tsc --watch",
14
+ "prepare": "npm run build",
15
+ "test": "echo \"Error: no test specified\" && exit 1"
16
+ },
17
+ "repository": {
18
+ "type": "git",
19
+ "url": "git+https://github.com/SuperClaude-Org/superflag.git"
20
+ },
21
+ "keywords": [
22
+ "mcp",
23
+ "claude",
24
+ "ai",
25
+ "assistant",
26
+ "context",
27
+ "flags"
28
+ ],
29
+ "author": "SuperClaude-Org",
30
+ "license": "MIT",
31
+ "bugs": {
32
+ "url": "https://github.com/SuperClaude-Org/superflag/issues"
33
+ },
34
+ "homepage": "https://github.com/SuperClaude-Org/superflag#readme",
35
+ "files": [
36
+ "dist",
37
+ "hooks",
38
+ "flags.yaml",
39
+ "README.md"
40
+ ],
41
+ "dependencies": {
42
+ "@modelcontextprotocol/sdk": "^0.5.0",
43
+ "@types/inquirer": "^9.0.9",
44
+ "chalk": "^5.3.0",
45
+ "inquirer": "^12.9.6",
46
+ "js-yaml": "^4.1.0",
47
+ "yargs": "^17.7.2"
48
+ },
49
+ "devDependencies": {
50
+ "@types/js-yaml": "^4.0.9",
51
+ "@types/node": "^22.0.0",
52
+ "@types/yargs": "^17.0.32",
53
+ "shx": "^0.3.4",
54
+ "typescript": "^5.3.3"
55
+ },
56
+ "engines": {
57
+ "node": ">=16.0.0"
58
+ }
59
+ }