opencode-workaholic 0.2.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.
- package/LICENSE +21 -0
- package/README.md +127 -0
- package/dist/command/workaholic.md +56 -0
- package/dist/index.js +12551 -0
- package/dist/scripts/timer.py +108 -0
- package/package.json +57 -0
- package/scripts/timer.py +108 -0
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
TIMER_DIR = "/tmp/workaholic_timers"
|
|
8
|
+
|
|
9
|
+
def get_timer_file(session_id):
|
|
10
|
+
os.makedirs(TIMER_DIR, exist_ok=True)
|
|
11
|
+
return os.path.join(TIMER_DIR, f"{session_id}.json")
|
|
12
|
+
|
|
13
|
+
def load_timer(session_id):
|
|
14
|
+
timer_file = get_timer_file(session_id)
|
|
15
|
+
if os.path.exists(timer_file):
|
|
16
|
+
with open(timer_file, "r") as f:
|
|
17
|
+
return json.load(f)
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
def save_timer(data, session_id):
|
|
21
|
+
timer_file = get_timer_file(session_id)
|
|
22
|
+
with open(timer_file, "w") as f:
|
|
23
|
+
json.dump(data, f)
|
|
24
|
+
|
|
25
|
+
def start(seconds, session_id):
|
|
26
|
+
data = {
|
|
27
|
+
"start_time": time.time(),
|
|
28
|
+
"min_duration": seconds,
|
|
29
|
+
"started": True,
|
|
30
|
+
"session_id": session_id
|
|
31
|
+
}
|
|
32
|
+
save_timer(data, session_id)
|
|
33
|
+
print(f"Timer started: {seconds}s, session: {session_id}")
|
|
34
|
+
|
|
35
|
+
def status(session_id):
|
|
36
|
+
data = load_timer(session_id)
|
|
37
|
+
if not data or not data.get("started"):
|
|
38
|
+
print(f"Timer not started (session: {session_id})")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
elapsed = time.time() - data["start_time"]
|
|
42
|
+
remaining = max(0, data["min_duration"] - elapsed)
|
|
43
|
+
can_end = elapsed >= data["min_duration"]
|
|
44
|
+
|
|
45
|
+
print(f"Session: {session_id}")
|
|
46
|
+
print(f" Min: {data['min_duration']}s, Elapsed: {elapsed:.1f}s, Remaining: {remaining:.1f}s")
|
|
47
|
+
print(f" Can end: {'YES' if can_end else 'NO'}")
|
|
48
|
+
|
|
49
|
+
def can_end(session_id):
|
|
50
|
+
data = load_timer(session_id)
|
|
51
|
+
if not data or not data.get("started"):
|
|
52
|
+
print("1")
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
elapsed = time.time() - data["start_time"]
|
|
56
|
+
print("1" if elapsed >= data["min_duration"] else "0")
|
|
57
|
+
|
|
58
|
+
def remaining(session_id):
|
|
59
|
+
data = load_timer(session_id)
|
|
60
|
+
if not data or not data.get("started"):
|
|
61
|
+
print("0")
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
elapsed = time.time() - data["start_time"]
|
|
65
|
+
print(max(0, int(data["min_duration"] - elapsed)))
|
|
66
|
+
|
|
67
|
+
def stop(session_id):
|
|
68
|
+
timer_file = get_timer_file(session_id)
|
|
69
|
+
if os.path.exists(timer_file):
|
|
70
|
+
os.remove(timer_file)
|
|
71
|
+
print(f"Timer stopped (session: {session_id})")
|
|
72
|
+
|
|
73
|
+
def main():
|
|
74
|
+
if len(sys.argv) < 2:
|
|
75
|
+
print(__doc__)
|
|
76
|
+
sys.exit(1)
|
|
77
|
+
|
|
78
|
+
command = sys.argv[1]
|
|
79
|
+
|
|
80
|
+
if command == "start":
|
|
81
|
+
if len(sys.argv) < 3:
|
|
82
|
+
print("Usage: timer.py start <seconds> [session_id]")
|
|
83
|
+
sys.exit(1)
|
|
84
|
+
try:
|
|
85
|
+
seconds = int(sys.argv[2])
|
|
86
|
+
session_id = sys.argv[3] if len(sys.argv) > 3 else "default"
|
|
87
|
+
start(seconds, session_id)
|
|
88
|
+
except ValueError:
|
|
89
|
+
print("Duration must be an integer")
|
|
90
|
+
sys.exit(1)
|
|
91
|
+
else:
|
|
92
|
+
session_id = sys.argv[2] if len(sys.argv) > 2 else "default"
|
|
93
|
+
|
|
94
|
+
if command == "status":
|
|
95
|
+
status(session_id)
|
|
96
|
+
elif command == "can-end":
|
|
97
|
+
can_end(session_id)
|
|
98
|
+
elif command == "remaining":
|
|
99
|
+
remaining(session_id)
|
|
100
|
+
elif command == "stop":
|
|
101
|
+
stop(session_id)
|
|
102
|
+
else:
|
|
103
|
+
print(f"Unknown command: {command}")
|
|
104
|
+
print(__doc__)
|
|
105
|
+
sys.exit(1)
|
|
106
|
+
|
|
107
|
+
if __name__ == "__main__":
|
|
108
|
+
main()
|
package/package.json
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-workaholic",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "Your OpenCode becomes a workaholic. Prevents AI from ending tasks prematurely.",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Roderick Qiu",
|
|
7
|
+
"email": "scrisqiu@hotmail.com",
|
|
8
|
+
"url": "https://r-q.name"
|
|
9
|
+
},
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"type": "module",
|
|
12
|
+
"main": "./dist/index.js",
|
|
13
|
+
"types": "./dist/index.d.ts",
|
|
14
|
+
"exports": {
|
|
15
|
+
".": {
|
|
16
|
+
"types": "./dist/index.d.ts",
|
|
17
|
+
"default": "./dist/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "git+https://github.com/RoderickQiu/opencode-workaholic.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/RoderickQiu/opencode-workaholic/issues"
|
|
26
|
+
},
|
|
27
|
+
"keywords": ["opencode", "plugin", "workaholic", "ai", "productivity", "timer", "focus", "deep-work", "task-duration", "task-management", "enforcement", "concentration", "agent", "automation"],
|
|
28
|
+
"homepage": "https://github.com/RoderickQiu/opencode-workaholic#readme",
|
|
29
|
+
"publishConfig": {
|
|
30
|
+
"access": "public",
|
|
31
|
+
"provenance": true
|
|
32
|
+
},
|
|
33
|
+
"files": [
|
|
34
|
+
"dist",
|
|
35
|
+
"scripts"
|
|
36
|
+
],
|
|
37
|
+
"scripts": {
|
|
38
|
+
"build": "bun build ./src/index.ts --outdir dist --target bun && cp -r src/command dist/ && cp -r scripts dist/"
|
|
39
|
+
},
|
|
40
|
+
"dependencies": {
|
|
41
|
+
"@opencode-ai/plugin": "1.0.85",
|
|
42
|
+
"zod": "^3.24.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"@eslint/js": "^9.39.1",
|
|
46
|
+
"@types/node": "^20.11.5",
|
|
47
|
+
"@typescript-eslint/eslint-plugin": "8.47.0",
|
|
48
|
+
"@typescript-eslint/parser": "8.47.0",
|
|
49
|
+
"bun-types": "latest",
|
|
50
|
+
"eslint": "^9.39.1",
|
|
51
|
+
"eslint-config-prettier": "10.1.8",
|
|
52
|
+
"eslint-plugin-prettier": "^5.1.3",
|
|
53
|
+
"prettier": "^3.2.4",
|
|
54
|
+
"typescript-eslint": "^8.47.0",
|
|
55
|
+
"vitest": "^3.2.4"
|
|
56
|
+
}
|
|
57
|
+
}
|
package/scripts/timer.py
ADDED
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
import json
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
|
|
7
|
+
TIMER_DIR = "/tmp/workaholic_timers"
|
|
8
|
+
|
|
9
|
+
def get_timer_file(session_id):
|
|
10
|
+
os.makedirs(TIMER_DIR, exist_ok=True)
|
|
11
|
+
return os.path.join(TIMER_DIR, f"{session_id}.json")
|
|
12
|
+
|
|
13
|
+
def load_timer(session_id):
|
|
14
|
+
timer_file = get_timer_file(session_id)
|
|
15
|
+
if os.path.exists(timer_file):
|
|
16
|
+
with open(timer_file, "r") as f:
|
|
17
|
+
return json.load(f)
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
def save_timer(data, session_id):
|
|
21
|
+
timer_file = get_timer_file(session_id)
|
|
22
|
+
with open(timer_file, "w") as f:
|
|
23
|
+
json.dump(data, f)
|
|
24
|
+
|
|
25
|
+
def start(seconds, session_id):
|
|
26
|
+
data = {
|
|
27
|
+
"start_time": time.time(),
|
|
28
|
+
"min_duration": seconds,
|
|
29
|
+
"started": True,
|
|
30
|
+
"session_id": session_id
|
|
31
|
+
}
|
|
32
|
+
save_timer(data, session_id)
|
|
33
|
+
print(f"Timer started: {seconds}s, session: {session_id}")
|
|
34
|
+
|
|
35
|
+
def status(session_id):
|
|
36
|
+
data = load_timer(session_id)
|
|
37
|
+
if not data or not data.get("started"):
|
|
38
|
+
print(f"Timer not started (session: {session_id})")
|
|
39
|
+
return
|
|
40
|
+
|
|
41
|
+
elapsed = time.time() - data["start_time"]
|
|
42
|
+
remaining = max(0, data["min_duration"] - elapsed)
|
|
43
|
+
can_end = elapsed >= data["min_duration"]
|
|
44
|
+
|
|
45
|
+
print(f"Session: {session_id}")
|
|
46
|
+
print(f" Min: {data['min_duration']}s, Elapsed: {elapsed:.1f}s, Remaining: {remaining:.1f}s")
|
|
47
|
+
print(f" Can end: {'YES' if can_end else 'NO'}")
|
|
48
|
+
|
|
49
|
+
def can_end(session_id):
|
|
50
|
+
data = load_timer(session_id)
|
|
51
|
+
if not data or not data.get("started"):
|
|
52
|
+
print("1")
|
|
53
|
+
return
|
|
54
|
+
|
|
55
|
+
elapsed = time.time() - data["start_time"]
|
|
56
|
+
print("1" if elapsed >= data["min_duration"] else "0")
|
|
57
|
+
|
|
58
|
+
def remaining(session_id):
|
|
59
|
+
data = load_timer(session_id)
|
|
60
|
+
if not data or not data.get("started"):
|
|
61
|
+
print("0")
|
|
62
|
+
return
|
|
63
|
+
|
|
64
|
+
elapsed = time.time() - data["start_time"]
|
|
65
|
+
print(max(0, int(data["min_duration"] - elapsed)))
|
|
66
|
+
|
|
67
|
+
def stop(session_id):
|
|
68
|
+
timer_file = get_timer_file(session_id)
|
|
69
|
+
if os.path.exists(timer_file):
|
|
70
|
+
os.remove(timer_file)
|
|
71
|
+
print(f"Timer stopped (session: {session_id})")
|
|
72
|
+
|
|
73
|
+
def main():
|
|
74
|
+
if len(sys.argv) < 2:
|
|
75
|
+
print(__doc__)
|
|
76
|
+
sys.exit(1)
|
|
77
|
+
|
|
78
|
+
command = sys.argv[1]
|
|
79
|
+
|
|
80
|
+
if command == "start":
|
|
81
|
+
if len(sys.argv) < 3:
|
|
82
|
+
print("Usage: timer.py start <seconds> [session_id]")
|
|
83
|
+
sys.exit(1)
|
|
84
|
+
try:
|
|
85
|
+
seconds = int(sys.argv[2])
|
|
86
|
+
session_id = sys.argv[3] if len(sys.argv) > 3 else "default"
|
|
87
|
+
start(seconds, session_id)
|
|
88
|
+
except ValueError:
|
|
89
|
+
print("Duration must be an integer")
|
|
90
|
+
sys.exit(1)
|
|
91
|
+
else:
|
|
92
|
+
session_id = sys.argv[2] if len(sys.argv) > 2 else "default"
|
|
93
|
+
|
|
94
|
+
if command == "status":
|
|
95
|
+
status(session_id)
|
|
96
|
+
elif command == "can-end":
|
|
97
|
+
can_end(session_id)
|
|
98
|
+
elif command == "remaining":
|
|
99
|
+
remaining(session_id)
|
|
100
|
+
elif command == "stop":
|
|
101
|
+
stop(session_id)
|
|
102
|
+
else:
|
|
103
|
+
print(f"Unknown command: {command}")
|
|
104
|
+
print(__doc__)
|
|
105
|
+
sys.exit(1)
|
|
106
|
+
|
|
107
|
+
if __name__ == "__main__":
|
|
108
|
+
main()
|