abelworkflow 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 (51) hide show
  1. package/.gitignore +13 -0
  2. package/.skill-lock.json +29 -0
  3. package/AGENTS.md +45 -0
  4. package/README.md +147 -0
  5. package/bin/abelworkflow.mjs +2 -0
  6. package/commands/oc/diagnose.md +63 -0
  7. package/commands/oc/implementation.md +157 -0
  8. package/commands/oc/init.md +27 -0
  9. package/commands/oc/plan.md +88 -0
  10. package/commands/oc/research.md +126 -0
  11. package/lib/cli.mjs +222 -0
  12. package/package.json +23 -0
  13. package/skills/confidence-check/SKILL.md +124 -0
  14. package/skills/confidence-check/confidence.ts +335 -0
  15. package/skills/context7-auto-research/.env +4 -0
  16. package/skills/context7-auto-research/.env.example +4 -0
  17. package/skills/context7-auto-research/SKILL.md +83 -0
  18. package/skills/context7-auto-research/context7-api.js +283 -0
  19. package/skills/dev-browser/SKILL.md +225 -0
  20. package/skills/dev-browser/bun.lock +443 -0
  21. package/skills/dev-browser/package-lock.json +2988 -0
  22. package/skills/dev-browser/package.json +31 -0
  23. package/skills/dev-browser/references/scraping.md +155 -0
  24. package/skills/dev-browser/resolve-skill-dir.sh +35 -0
  25. package/skills/dev-browser/scripts/start-relay.ts +32 -0
  26. package/skills/dev-browser/scripts/start-server.ts +117 -0
  27. package/skills/dev-browser/server.sh +24 -0
  28. package/skills/dev-browser/src/client.ts +474 -0
  29. package/skills/dev-browser/src/index.ts +287 -0
  30. package/skills/dev-browser/src/relay.ts +731 -0
  31. package/skills/dev-browser/src/snapshot/browser-script.ts +877 -0
  32. package/skills/dev-browser/src/snapshot/index.ts +14 -0
  33. package/skills/dev-browser/src/snapshot/inject.ts +13 -0
  34. package/skills/dev-browser/src/types.ts +34 -0
  35. package/skills/dev-browser/tsconfig.json +36 -0
  36. package/skills/dev-browser/vitest.config.ts +12 -0
  37. package/skills/git-commit/SKILL.md +124 -0
  38. package/skills/grok-search/.env.example +24 -0
  39. package/skills/grok-search/SKILL.md +114 -0
  40. package/skills/grok-search/requirements.txt +2 -0
  41. package/skills/grok-search/scripts/groksearch_cli.py +1214 -0
  42. package/skills/grok-search/scripts/groksearch_entry.py +116 -0
  43. package/skills/prompt-enhancer/ADVANCED.md +74 -0
  44. package/skills/prompt-enhancer/SKILL.md +71 -0
  45. package/skills/prompt-enhancer/TEMPLATE.md +91 -0
  46. package/skills/prompt-enhancer/scripts/enhance.py +142 -0
  47. package/skills/sequential-think/SKILL.md +198 -0
  48. package/skills/sequential-think/scripts/.env.example +5 -0
  49. package/skills/sequential-think/scripts/sequential_think_cli.py +253 -0
  50. package/skills/time/SKILL.md +116 -0
  51. package/skills/time/scripts/time_cli.py +104 -0
@@ -0,0 +1,253 @@
1
+ #!/usr/bin/env python3
2
+ """Sequential Think CLI - Standalone iterative thinking engine for complex problem-solving."""
3
+
4
+ import argparse
5
+ import json
6
+ import sys
7
+ from dataclasses import dataclass, field, asdict
8
+ from datetime import datetime
9
+ from pathlib import Path
10
+ from typing import List, Optional, Dict
11
+
12
+
13
+ # ============================================================================
14
+ # Data Models
15
+ # ============================================================================
16
+
17
+ @dataclass
18
+ class ThoughtData:
19
+ thought: str
20
+ thought_number: int
21
+ total_thoughts: int
22
+ next_thought_needed: bool = True
23
+ is_revision: bool = False
24
+ revises_thought: Optional[int] = None
25
+ branch_from_thought: Optional[int] = None
26
+ branch_id: Optional[str] = None
27
+ needs_more_thoughts: bool = False
28
+ timestamp: str = field(default_factory=lambda: datetime.now().isoformat())
29
+
30
+
31
+ # ============================================================================
32
+ # Thought History Manager
33
+ # ============================================================================
34
+
35
+ class ThoughtHistoryManager:
36
+ _instance = None
37
+
38
+ def __new__(cls):
39
+ if cls._instance is None:
40
+ cls._instance = super().__new__(cls)
41
+ cls._instance._history: List[ThoughtData] = []
42
+ cls._instance._branches: Dict[str, List[ThoughtData]] = {}
43
+ cls._instance._history_file: Optional[Path] = None
44
+ return cls._instance
45
+
46
+ @property
47
+ def history_file(self) -> Path:
48
+ if self._history_file is None:
49
+ config_dir = Path.home() / ".config" / "sequential-think"
50
+ config_dir.mkdir(parents=True, exist_ok=True)
51
+ self._history_file = config_dir / "thought_history.json"
52
+ return self._history_file
53
+
54
+ def _load_history(self) -> None:
55
+ if self.history_file.exists():
56
+ try:
57
+ with open(self.history_file, 'r', encoding='utf-8') as f:
58
+ data = json.load(f)
59
+ self._history = [ThoughtData(**t) for t in data.get("history", [])]
60
+ self._branches = {
61
+ k: [ThoughtData(**t) for t in v]
62
+ for k, v in data.get("branches", {}).items()
63
+ }
64
+ except (json.JSONDecodeError, IOError, TypeError):
65
+ self._history = []
66
+ self._branches = {}
67
+
68
+ def _save_history(self) -> None:
69
+ data = {
70
+ "history": [asdict(t) for t in self._history],
71
+ "branches": {k: [asdict(t) for t in v] for k, v in self._branches.items()}
72
+ }
73
+ with open(self.history_file, 'w', encoding='utf-8') as f:
74
+ json.dump(data, f, ensure_ascii=False, indent=2)
75
+
76
+ def add_thought(self, thought: ThoughtData) -> Dict:
77
+ self._load_history()
78
+
79
+ # Auto-adjust total if thought_number exceeds it
80
+ if thought.thought_number > thought.total_thoughts:
81
+ thought.total_thoughts = thought.thought_number
82
+
83
+ self._history.append(thought)
84
+
85
+ # Track branches
86
+ if thought.branch_from_thought and thought.branch_id:
87
+ if thought.branch_id not in self._branches:
88
+ self._branches[thought.branch_id] = []
89
+ self._branches[thought.branch_id].append(thought)
90
+
91
+ self._save_history()
92
+
93
+ return {
94
+ "thoughtNumber": thought.thought_number,
95
+ "totalThoughts": thought.total_thoughts,
96
+ "nextThoughtNeeded": thought.next_thought_needed,
97
+ "branches": list(self._branches.keys()),
98
+ "thoughtHistoryLength": len(self._history)
99
+ }
100
+
101
+ def get_history(self) -> Dict:
102
+ self._load_history()
103
+ return {
104
+ "history": [asdict(t) for t in self._history],
105
+ "branches": {k: [asdict(t) for t in v] for k, v in self._branches.items()},
106
+ "totalThoughts": len(self._history)
107
+ }
108
+
109
+ def clear_history(self) -> Dict:
110
+ self._history = []
111
+ self._branches = {}
112
+ if self.history_file.exists():
113
+ self.history_file.unlink()
114
+ return {"status": "cleared", "message": "Thought history cleared"}
115
+
116
+
117
+ manager = ThoughtHistoryManager()
118
+
119
+
120
+ # ============================================================================
121
+ # Formatters
122
+ # ============================================================================
123
+
124
+ def format_thought_text(thought: ThoughtData) -> str:
125
+ prefix = ""
126
+ context = ""
127
+
128
+ if thought.is_revision:
129
+ prefix = "🔄 Revision"
130
+ context = f" (revising thought {thought.revises_thought})"
131
+ elif thought.branch_from_thought:
132
+ prefix = "🌿 Branch"
133
+ context = f" (from thought {thought.branch_from_thought}, ID: {thought.branch_id})"
134
+ else:
135
+ prefix = "💭 Thought"
136
+
137
+ header = f"{prefix} {thought.thought_number}/{thought.total_thoughts}{context}"
138
+ border = "─" * max(len(header), min(len(thought.thought), 60)) + "────"
139
+
140
+ return f"""
141
+ ┌{border}┐
142
+ │ {header.ljust(len(border) - 2)} │
143
+ ├{border}┤
144
+ │ {thought.thought[:len(border) - 2].ljust(len(border) - 2)} │
145
+ └{border}┘"""
146
+
147
+
148
+ def format_history_text(history: Dict) -> str:
149
+ if not history["history"]:
150
+ return "No thoughts recorded yet."
151
+
152
+ lines = ["=" * 60, "THOUGHT HISTORY", "=" * 60, ""]
153
+
154
+ for t in history["history"]:
155
+ thought = ThoughtData(**t)
156
+ lines.append(format_thought_text(thought))
157
+
158
+ if history["branches"]:
159
+ lines.extend(["", "-" * 60, "BRANCHES:", "-" * 60])
160
+ for branch_id, thoughts in history["branches"].items():
161
+ lines.append(f"\n[{branch_id}]")
162
+ for t in thoughts:
163
+ lines.append(f" Thought {t['thought_number']}: {t['thought'][:50]}...")
164
+
165
+ return "\n".join(lines)
166
+
167
+
168
+ # ============================================================================
169
+ # Commands
170
+ # ============================================================================
171
+
172
+ def cmd_think(args) -> None:
173
+ thought = ThoughtData(
174
+ thought=args.thought,
175
+ thought_number=args.thought_number,
176
+ total_thoughts=args.total_thoughts,
177
+ next_thought_needed=not args.no_next,
178
+ is_revision=args.is_revision,
179
+ revises_thought=args.revises_thought,
180
+ branch_from_thought=args.branch_from,
181
+ branch_id=args.branch_id,
182
+ needs_more_thoughts=args.needs_more
183
+ )
184
+
185
+ result = manager.add_thought(thought)
186
+
187
+ # Print formatted thought to stderr for visibility
188
+ if not args.quiet:
189
+ print(format_thought_text(thought), file=sys.stderr)
190
+
191
+ # Output JSON result
192
+ print(json.dumps(result, ensure_ascii=False, indent=2))
193
+
194
+
195
+ def cmd_history(args) -> None:
196
+ history = manager.get_history()
197
+
198
+ if args.format == "json":
199
+ print(json.dumps(history, ensure_ascii=False, indent=2))
200
+ else:
201
+ print(format_history_text(history))
202
+
203
+
204
+ def cmd_clear(args) -> None:
205
+ result = manager.clear_history()
206
+ print(json.dumps(result, ensure_ascii=False, indent=2))
207
+
208
+
209
+ # ============================================================================
210
+ # Main
211
+ # ============================================================================
212
+
213
+ def main():
214
+ parser = argparse.ArgumentParser(
215
+ prog="sequential_think_cli",
216
+ description="Sequential Think CLI - Iterative thinking engine for complex problem-solving"
217
+ )
218
+
219
+ subparsers = parser.add_subparsers(dest="command", required=True)
220
+
221
+ # think command
222
+ p_think = subparsers.add_parser("think", help="Process a thought in the chain")
223
+ p_think.add_argument("--thought", "-t", required=True, help="Current thinking step content")
224
+ p_think.add_argument("--thought-number", "-n", type=int, required=True, help="Current position (1-based)")
225
+ p_think.add_argument("--total-thoughts", "-T", type=int, required=True, help="Estimated total thoughts")
226
+ p_think.add_argument("--no-next", action="store_true", help="Mark as final thought (no more needed)")
227
+ p_think.add_argument("--is-revision", action="store_true", help="This thought revises previous thinking")
228
+ p_think.add_argument("--revises-thought", type=int, help="Which thought number is being reconsidered")
229
+ p_think.add_argument("--branch-from", type=int, help="Branching point thought number")
230
+ p_think.add_argument("--branch-id", help="Identifier for current branch")
231
+ p_think.add_argument("--needs-more", action="store_true", help="Signal more thoughts needed beyond estimate")
232
+ p_think.add_argument("--quiet", "-q", action="store_true", help="Suppress formatted output to stderr")
233
+
234
+ # history command
235
+ p_history = subparsers.add_parser("history", help="View thought history")
236
+ p_history.add_argument("--format", "-f", choices=["json", "text"], default="text", help="Output format")
237
+
238
+ # clear command
239
+ subparsers.add_parser("clear", help="Clear thought history")
240
+
241
+ args = parser.parse_args()
242
+
243
+ commands = {
244
+ "think": cmd_think,
245
+ "history": cmd_history,
246
+ "clear": cmd_clear,
247
+ }
248
+
249
+ commands[args.command](args)
250
+
251
+
252
+ if __name__ == "__main__":
253
+ main()
@@ -0,0 +1,116 @@
1
+ ---
2
+ name: time
3
+ description: |
4
+ Time and timezone utilities for getting current time and converting between timezones. Use when: (1) Getting current time in any timezone, (2) Converting time between different timezones, (3) Working with IANA timezone names, (4) Scheduling across timezones, (5) Time-sensitive operations. Triggers: "what time is it", "current time", "convert time", "timezone", "time in [city]". Supports both MCP server and standalone CLI.
5
+ ---
6
+
7
+ # Time
8
+
9
+ Time and timezone conversion utilities. Supports both MCP server and standalone CLI.
10
+
11
+ ## Execution Methods
12
+
13
+ ### Method 1: MCP Tools (if available)
14
+ Use `mcp__time__*` tools directly:
15
+ - `mcp__time__get_current_time` - Get current time in a timezone
16
+ - `mcp__time__convert_time` - Convert time between timezones
17
+
18
+ ### Method 2: CLI Script (no MCP dependency)
19
+ Run `scripts/time_cli.py` via Bash:
20
+
21
+ ```bash
22
+ # Prerequisites: pip install pytz (or use Python 3.9+ with zoneinfo)
23
+
24
+ # Get current time in a timezone
25
+ python scripts/time_cli.py get --timezone "Asia/Shanghai"
26
+ python scripts/time_cli.py get --timezone "America/New_York"
27
+ python scripts/time_cli.py get # Uses system timezone
28
+
29
+ # Convert time between timezones
30
+ python scripts/time_cli.py convert \
31
+ --time "16:30" \
32
+ --from "America/New_York" \
33
+ --to "Asia/Tokyo"
34
+
35
+ # List available timezones
36
+ python scripts/time_cli.py list [--filter "Asia"]
37
+ ```
38
+
39
+ ## Tool Capability Matrix
40
+
41
+ | Tool | Parameters | Output |
42
+ |------|------------|--------|
43
+ | `get_current_time` | `timezone` (required, IANA name) | `{timezone, datetime, is_dst}` |
44
+ | `convert_time` | `source_timezone`, `time` (HH:MM), `target_timezone` | `{source, target, time_difference}` |
45
+
46
+ ## Common IANA Timezone Names
47
+
48
+ | Region | Timezone |
49
+ |--------|----------|
50
+ | China | `Asia/Shanghai` |
51
+ | Japan | `Asia/Tokyo` |
52
+ | Korea | `Asia/Seoul` |
53
+ | US East | `America/New_York` |
54
+ | US West | `America/Los_Angeles` |
55
+ | UK | `Europe/London` |
56
+ | Germany | `Europe/Berlin` |
57
+ | France | `Europe/Paris` |
58
+ | Australia | `Australia/Sydney` |
59
+ | UTC | `UTC` |
60
+
61
+ ## Workflow
62
+
63
+ ### Getting Current Time
64
+ 1. Identify target timezone (use IANA name)
65
+ 2. Call `get_current_time` with timezone parameter
66
+ 3. Response includes ISO 8601 datetime and DST status
67
+
68
+ ### Converting Time
69
+ 1. Identify source timezone and time (24-hour format HH:MM)
70
+ 2. Identify target timezone
71
+ 3. Call `convert_time` with all parameters
72
+ 4. Response includes both times and time difference
73
+
74
+ ## Output Format
75
+
76
+ ### get_current_time Response
77
+ ```json
78
+ {
79
+ "timezone": "Asia/Shanghai",
80
+ "datetime": "2024-01-01T21:00:00+08:00",
81
+ "is_dst": false
82
+ }
83
+ ```
84
+
85
+ ### convert_time Response
86
+ ```json
87
+ {
88
+ "source": {
89
+ "timezone": "America/New_York",
90
+ "datetime": "2024-01-01T16:30:00-05:00",
91
+ "is_dst": false
92
+ },
93
+ "target": {
94
+ "timezone": "Asia/Tokyo",
95
+ "datetime": "2024-01-02T06:30:00+09:00",
96
+ "is_dst": false
97
+ },
98
+ "time_difference": "+14.0h"
99
+ }
100
+ ```
101
+
102
+ ## Error Handling
103
+
104
+ | Error | Recovery |
105
+ |-------|----------|
106
+ | Invalid timezone | Check IANA timezone name spelling |
107
+ | Invalid time format | Use 24-hour format HH:MM |
108
+ | MCP unavailable | Fall back to CLI script |
109
+
110
+ ## Anti-Patterns
111
+
112
+ | Prohibited | Correct |
113
+ |------------|---------|
114
+ | Use city names directly | Use IANA timezone names (e.g., `Asia/Tokyo` not `Tokyo`) |
115
+ | Use 12-hour format | Use 24-hour format (e.g., `16:30` not `4:30 PM`) |
116
+ | Assume timezone | Always specify timezone explicitly |
@@ -0,0 +1,104 @@
1
+ #!/usr/bin/env python3
2
+ """Time CLI - Standalone time and timezone utilities."""
3
+
4
+ import argparse
5
+ import json
6
+ import sys
7
+ from datetime import datetime
8
+
9
+ try:
10
+ from zoneinfo import ZoneInfo, available_timezones
11
+ except ImportError:
12
+ from pytz import timezone as ZoneInfo, all_timezones as _all_tz
13
+ def available_timezones():
14
+ return set(_all_tz)
15
+
16
+
17
+ def get_current_time(tz_name: str) -> dict:
18
+ """Get current time in specified timezone."""
19
+ try:
20
+ tz = ZoneInfo(tz_name)
21
+ now = datetime.now(tz)
22
+ return {
23
+ "timezone": tz_name,
24
+ "datetime": now.isoformat(),
25
+ "is_dst": bool(now.dst()) if hasattr(now, 'dst') and now.dst() else False
26
+ }
27
+ except Exception as e:
28
+ return {"error": str(e)}
29
+
30
+
31
+ def convert_time(source_tz: str, time_str: str, target_tz: str) -> dict:
32
+ """Convert time between timezones."""
33
+ try:
34
+ hour, minute = map(int, time_str.split(':'))
35
+ source = ZoneInfo(source_tz)
36
+ target = ZoneInfo(target_tz)
37
+
38
+ today = datetime.now(source).date()
39
+ source_dt = datetime(today.year, today.month, today.day, hour, minute, tzinfo=source)
40
+ target_dt = source_dt.astimezone(target)
41
+
42
+ source_offset = source_dt.utcoffset().total_seconds() / 3600
43
+ target_offset = target_dt.utcoffset().total_seconds() / 3600
44
+ diff = target_offset - source_offset
45
+
46
+ return {
47
+ "source": {
48
+ "timezone": source_tz,
49
+ "datetime": source_dt.isoformat(),
50
+ "is_dst": bool(source_dt.dst()) if hasattr(source_dt, 'dst') and source_dt.dst() else False
51
+ },
52
+ "target": {
53
+ "timezone": target_tz,
54
+ "datetime": target_dt.isoformat(),
55
+ "is_dst": bool(target_dt.dst()) if hasattr(target_dt, 'dst') and target_dt.dst() else False
56
+ },
57
+ "time_difference": f"{diff:+.1f}h"
58
+ }
59
+ except Exception as e:
60
+ return {"error": str(e)}
61
+
62
+
63
+ def list_timezones(filter_str: str = None) -> list:
64
+ """List available timezones."""
65
+ zones = sorted(available_timezones())
66
+ if filter_str:
67
+ zones = [z for z in zones if filter_str.lower() in z.lower()]
68
+ return zones
69
+
70
+
71
+ def main():
72
+ parser = argparse.ArgumentParser(description="Time and timezone utilities")
73
+ subparsers = parser.add_subparsers(dest="command", required=True)
74
+
75
+ # get command
76
+ get_parser = subparsers.add_parser("get", help="Get current time")
77
+ get_parser.add_argument("--timezone", "-tz", default="UTC", help="IANA timezone name")
78
+
79
+ # convert command
80
+ convert_parser = subparsers.add_parser("convert", help="Convert time between timezones")
81
+ convert_parser.add_argument("--time", "-t", required=True, help="Time in HH:MM format")
82
+ convert_parser.add_argument("--from", "-f", dest="source", required=True, help="Source timezone")
83
+ convert_parser.add_argument("--to", "-o", dest="target", required=True, help="Target timezone")
84
+
85
+ # list command
86
+ list_parser = subparsers.add_parser("list", help="List available timezones")
87
+ list_parser.add_argument("--filter", "-f", help="Filter timezones by substring")
88
+
89
+ args = parser.parse_args()
90
+
91
+ if args.command == "get":
92
+ result = get_current_time(args.timezone)
93
+ print(json.dumps(result, indent=2))
94
+ elif args.command == "convert":
95
+ result = convert_time(args.source, args.time, args.target)
96
+ print(json.dumps(result, indent=2))
97
+ elif args.command == "list":
98
+ zones = list_timezones(args.filter)
99
+ for z in zones:
100
+ print(z)
101
+
102
+
103
+ if __name__ == "__main__":
104
+ main()