taskai-cli 1.0.1__tar.gz → 1.1.1__tar.gz

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 (24) hide show
  1. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/.gitignore +4 -1
  2. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/DEVLOG.md +23 -1
  3. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/PKG-INFO +1 -1
  4. taskai_cli-1.1.1/migrations/_001_removing_lists.py +50 -0
  5. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/pyproject.toml +1 -1
  6. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/taskai/cli.py +83 -13
  7. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/taskai/config.py +2 -2
  8. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/taskai/services/ai.py +4 -8
  9. taskai_cli-1.1.1/taskai/services/pomodoro.py +65 -0
  10. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/test/test_cli.py +20 -2
  11. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/.github/workflows/publish-to-pypi.yml +0 -0
  12. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/README.md +0 -0
  13. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/docs/index.md +0 -0
  14. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/migrations/convert_title_to_name.py +0 -0
  15. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/mkdocs.yml +0 -0
  16. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/taskai/help_menu.py +0 -0
  17. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/taskai/json_dir_database.py +0 -0
  18. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/taskai/models.py +0 -0
  19. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/taskai/services/repair_database.py +0 -0
  20. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/taskai/services/user_setup.py +0 -0
  21. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/taskai/views.py +0 -0
  22. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/test/test_execution.py +0 -0
  23. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/test/test_json_dir_database.py +0 -0
  24. {taskai_cli-1.0.1 → taskai_cli-1.1.1}/test/test_view.py +0 -0
@@ -12,4 +12,7 @@ PROMPT.md
12
12
  _tmp_database_dir/tmp/
13
13
  tmp*/
14
14
  .taskai
15
- *venv
15
+ *venv
16
+ _tmp_database_dir/
17
+ .dev
18
+ .prod
@@ -1,3 +1,25 @@
1
+ # 6-25
2
+
3
+ Alright let's start to think a bit about how I want to handle the pomo service.
4
+
5
+ task pomo 25 3
6
+
7
+
8
+ Let's start with spawning the timer, and then we can worry about logging and shit.
9
+
10
+ Pomo should spawn a process and write a log to a database. The process
11
+ should continuously monitor that log and see "am I active?"
12
+ - if so, it keeps running
13
+ - if not, we prompt the user for what they want to do?
14
+ - rest/reset/cancel
15
+ do I need this to be serialized even? maybe not if I'm just starting - we can start pomo in another thread (or just the same thread)
16
+
17
+ Yeah let's honestly not even worry about the multiprocessing part of it right now.
18
+
19
+ Let's just enter a loop and count down, while I keep clearing the screen
20
+
21
+ I need to figure out how to make it play a bell or have a screen pop up (if it's running in the background)
22
+
1
23
  # 6-20
2
24
 
3
25
  Let's think critically about what i want this API to lookk like. How should be people be
@@ -365,4 +387,4 @@ Pages:
365
387
  - Comments
366
388
  - Text CLI
367
389
  - Dockerize
368
- -
390
+ -
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: taskai-cli
3
- Version: 1.0.1
3
+ Version: 1.1.1
4
4
  Author-email: Alex Paskal <alexcpaskal@gmail.com>
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: google-genai>=2.8
@@ -0,0 +1,50 @@
1
+ import argparse
2
+ import orjson as json
3
+ from taskai.models import TodoItem
4
+
5
+ def migrate(data):
6
+ if "TodoList" in data:
7
+ for k, record in data["TodoList"].items():
8
+
9
+ record["child_ids"] = record.pop("item_ids")
10
+ TodoItem(**record)
11
+ print(f"Converting {k} to Item")
12
+ data["TodoItem"][k] = record
13
+ data.pop("TodoList")
14
+
15
+ for k, record in data["TodoItem"].items():
16
+
17
+ if "title" in record:
18
+ print(f"converting title->name for {k}")
19
+ record["title"] = record["name"]
20
+ record.pop("title")
21
+
22
+ if "parent" in record:
23
+ print(f"converting parent->parent_id for {k}")
24
+ record["parent_id"] = record["parent"]
25
+ record.pop("parent")
26
+
27
+ if "child_ids" not in record:
28
+ print(f"Adding Child IDs {k}")
29
+ record["child_ids"] = []
30
+
31
+ try:
32
+ data["TodoItem"][k] = TodoItem(**record).model_dump()
33
+ except:
34
+ print("validation for {} failed".format(k))
35
+ import sys
36
+ sys.exit()
37
+
38
+
39
+ arg_parser = argparse.ArgumentParser()
40
+ arg_parser.add_argument("path", help="path to taskai root")
41
+ args = arg_parser.parse_args()
42
+
43
+ with open(args.path, "rb") as f:
44
+ data = json.loads(f.read())
45
+
46
+ migrate(data)
47
+
48
+
49
+ with open(args.path, "wb") as f:
50
+ f.write(json.dumps(data))
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "taskai-cli"
3
- version = "1.0.1"
3
+ version = "1.1.1"
4
4
  description = ""
5
5
  authors = [{name = "Alex Paskal", email = "alexcpaskal@gmail.com"}]
6
6
  readme = "README.md"
@@ -15,6 +15,7 @@ from taskai.models import TodoItem, Comment
15
15
  from taskai.services.ai import ai_headstart_service, ai_natural_language_service
16
16
  from taskai.services.user_setup import user_setup_service
17
17
  from taskai.services.repair_database import repair_database_service
18
+ from taskai.services.pomodoro import pomodoro_service
18
19
  from taskai.help_menu import help_menu
19
20
  from taskai.config import GlobalConfig
20
21
 
@@ -208,13 +209,45 @@ class Controller:
208
209
  db.commit()
209
210
 
210
211
  # utilities
212
+ def _parse_arg_string(arg_string: str) -> list[str]:
213
+ """parses a string properly before dispatching it to the argument parser"""
214
+
215
+ # scan for quote characters
216
+
217
+ currently_enclosed = False
218
+ current_quote_char = None
219
+ quote_chars = ('"',"'")
220
+ buffer = ""
221
+ arg_parts = []
222
+ for c in arg_string:
223
+ if c == " ":
224
+ if currently_enclosed:
225
+ buffer += c
226
+ else:
227
+ arg_parts.append(buffer)
228
+ buffer = ""
229
+ elif c == current_quote_char:
230
+ currently_enclosed = False
231
+ current_quote_char = None
232
+
233
+ elif c in quote_chars and not currently_enclosed:
234
+ currently_enclosed = True
235
+ current_quote_char = c
236
+ else:
237
+ buffer += c
238
+ if buffer:
239
+ arg_parts.append(buffer)
240
+ return arg_parts
241
+
211
242
  def _parse_remaining(remaining_args: list[str]) -> tuple[list, dict]:
212
243
 
213
- for i, _arg in enumerate(remaining_args):
214
- remaining_args[i] = _arg.replace(" ", "+-*/")
215
- remaining_args = " ".join(remaining_args).replace("="," ").split(" ")
216
- for i, _arg in enumerate(remaining_args):
217
- remaining_args[i] = _arg.replace("+-*/", " ")
244
+ # parse flags
245
+
246
+ #for i, _arg in enumerate(remaining_args):
247
+ # remaining_args[i] = _arg.replace(" ", "+-*/")
248
+ #remaining_args = " ".join(remaining_args).replace("="," ").split(" ")
249
+ #for i, _arg in enumerate(remaining_args):
250
+ # remaining_args[i] = _arg.replace("+-*/", " ")
218
251
 
219
252
  # outputs
220
253
  args = []
@@ -223,11 +256,16 @@ def _parse_remaining(remaining_args: list[str]) -> tuple[list, dict]:
223
256
  while remaining_args:
224
257
  next_arg = remaining_args.pop(0)
225
258
  if next_arg.startswith("--"):
226
- assert remaining_args, "kwarg specified with no value provided"
227
- kwargs[next_arg[2:]] = remaining_args.pop(0)
259
+ if "=" in next_arg:
260
+ key, value = next_arg.split("=")
261
+ kwargs[key] = value
262
+ else:
263
+ assert remaining_args, "kwarg specified with no value provided"
264
+ kwargs[next_arg[2:]] = remaining_args.pop(0)
228
265
  else:
229
266
  args.append(next_arg)
230
-
267
+
268
+
231
269
  return args, kwargs
232
270
 
233
271
  def _is_int(val: any) -> bool:
@@ -325,7 +363,10 @@ def execute_commands(*args, **kwargs) -> int:
325
363
  match (src_id, dst_id):
326
364
  case _ if _is_int(src_id) and _is_int(dst_id): Controller.add_dependency(src_id, dst_id)
327
365
  case _: Controller.throw_error("Invalid arrow argument, must be one of (->, <-)")
328
-
366
+
367
+ case "pomo":
368
+ pomodoro_service(int(args[1]), int(args[2]))
369
+
329
370
  # developer use
330
371
  case "db":
331
372
  import orjson as json
@@ -349,22 +390,51 @@ def interactive_program():
349
390
  # builtins.print = console.print
350
391
 
351
392
  response = ""
393
+ last_show_command = [("show", "all"), {}]
394
+ args = None
395
+ kwargs = None
396
+
352
397
  _clear_screen()
353
398
  while True:
354
399
 
355
400
  try:
401
+
402
+ # render last show command
403
+ if last_show_command is not None:
404
+ try:
405
+ execute_commands(*last_show_command[0], **last_show_command[1])
406
+ except Exception as e:
407
+ print(e)
408
+
409
+ Console().rule()
410
+
411
+ # prompt user input
356
412
  response = Prompt.ask("Type your commands:", default=response)
357
- _clear_screen()
358
- args, kwargs = _parse_remaining(response.split(" "))
413
+
414
+ # parse commands
415
+ args_remaining = _parse_arg_string(response)
416
+ args, kwargs = _parse_remaining(args_remaining)
417
+ print("arg_string:", args_remaining)
418
+ print("args:", args)
419
+ print("kwargs:", kwargs)
359
420
  if args[0] == "task":
360
421
  args = args[1:]
361
- return_code = execute_commands(*args, **kwargs)
422
+
423
+ # defer show
424
+ if args[0] == "show":
425
+ last_show_command = (args, kwargs)
426
+ return_code = 1
427
+ else:
428
+ return_code = execute_commands(*args, **kwargs)
429
+
430
+ _clear_screen()
362
431
  if return_code == 0:
363
432
  break
364
- Console().rule()
365
433
 
366
434
  except KeyboardInterrupt:
367
435
  break
436
+ except Exception as e:
437
+ print(e)
368
438
 
369
439
  _clear_screen()
370
440
  sys.exit(1)
@@ -7,9 +7,9 @@ class GlobalConfig:
7
7
  _data_store: dict[str, any]
8
8
 
9
9
  @classmethod
10
- def load_dict(cls, d: dict):
10
+ def load_dict(cls, model):
11
11
  """loads values from a configuration"""
12
- cls._data_store = d
12
+ cls._data_store = model.model_dump()
13
13
 
14
14
  @classmethod
15
15
  def get(cls, key: str):
@@ -30,8 +30,7 @@ def ai_headstart_service(
30
30
  - Parse and return its response
31
31
  """
32
32
 
33
-
34
- item:TodoItem = db.read(item_id)
33
+ item:TodoItem = db.get_item(item_id)
35
34
 
36
35
 
37
36
  from google import genai
@@ -67,11 +66,8 @@ task name: {item.name}
67
66
 
68
67
  prompt += f"\ntask comments:"
69
68
  for comment_id in item.comment_ids:
70
- comment: Comment = db.read(comment_id)
69
+ comment: Comment = db.get_comment(comment_id)
71
70
  prompt += f"\n\t- {comment.content}"
72
-
73
- # return "Survey says go fuck yourself"
74
-
75
71
  # query model
76
72
  client = genai.Client(api_key=api_key)
77
73
  response = client.models.generate_content(
@@ -101,7 +97,7 @@ def ai_natural_language_service(
101
97
  _visited_set = {}
102
98
 
103
99
  def _add_info(item_id, level=0):
104
- item: TodoItem = db.read(id_)
100
+ item: TodoItem = db.get_item(id_)
105
101
  user_info.append(" "*level + f"{item.id} {item.name}")
106
102
  _visited_set.add(item_id)
107
103
  if item.child_ids:
@@ -109,7 +105,7 @@ def ai_natural_language_service(
109
105
  _add_info(child_id, level+1)
110
106
 
111
107
 
112
- for id_ in db.items:
108
+ for id_ in db.get_item_ids():
113
109
  if id_ not in _visited_set:
114
110
  _add_info(id_)
115
111
 
@@ -0,0 +1,65 @@
1
+ from taskai.json_dir_database import JsonDirectoryDatabase
2
+ import datetime
3
+ import time
4
+ from rich import print
5
+ from rich.console import Console
6
+
7
+ def pomodoro_service(
8
+ minutes_on: int,
9
+ minutes_off: int
10
+ ):
11
+
12
+ # STATES
13
+ end_time = datetime.datetime.now()
14
+
15
+ try:
16
+
17
+ state = "ready"
18
+ history = []
19
+ console = Console()
20
+ while True:
21
+ if state == "ready":
22
+ prompt = "Press enter to begin ..."
23
+ input(prompt)
24
+ history.append(f"Starting Pomo for {minutes_on} minutes")
25
+ now = datetime.datetime.now()
26
+ end_time = now + datetime.timedelta(minutes=minutes_on)
27
+ #end_time = now + datetime.timedelta(seconds=5)
28
+ state = "running"
29
+ elif state == "running":
30
+ now = datetime.datetime.now()
31
+ if now < end_time:
32
+ diff = end_time - now
33
+ message = f"{diff.seconds // 60}:{str(diff.seconds % 60).zfill(2)} to go"
34
+ if history[-1].endswith("to go"):
35
+ history.pop()
36
+ history.append(message)
37
+ time.sleep(0.995)
38
+ else:
39
+ state = "done"
40
+ if state == "done":
41
+ history.append("All Finished! Taking rest now")
42
+ state = "resting"
43
+ end_time = datetime.datetime.now() + datetime.timedelta(minutes=minutes_off)
44
+ #end_time = now + datetime.timedelta(seconds=5)
45
+ if state == "resting":
46
+ now = datetime.datetime.now()
47
+ if now < end_time:
48
+ diff = end_time - now
49
+ message = f"{diff.seconds // 60}:{str(diff.seconds % 60).zfill(2)} to go"
50
+ if history[-1].endswith("to go"):
51
+ history.pop()
52
+ history.append(message)
53
+ time.sleep(0.995)
54
+ else:
55
+ state = "ready"
56
+
57
+ console.clear()
58
+ for val in history:
59
+ console.print(val)
60
+ except KeyboardInterrupt:
61
+ return
62
+
63
+
64
+
65
+
@@ -1,4 +1,5 @@
1
1
  from taskai.json_dir_database import JsonDirectoryDatabase
2
+ from taskai.cli import _parse_arg_string
2
3
  import os
3
4
  import shutil
4
5
 
@@ -53,6 +54,23 @@ def test_run_commands():
53
54
  finally:
54
55
  _cleanup_db()
55
56
 
57
+ def test_args_parser():
58
+
59
+ def _assert_list_equal(l1, l2):
60
+ assert len(l1) == len(l2), f"{l1} length not equal to {l2} length"
61
+ assert all([thing1==thing2 for thing1, thing2 in zip(l1, l2)]), f"{l1} not equal to {l2}"
62
+
63
+ dataset = [
64
+ ("show thing 1", ["show", "thing", "1"]),
65
+ ("show 'this is a string' 2", ["show", "this is a string", "2"]),
66
+ ("show https://url.com/thing?arg=1 hello there", ["show", "https://url.com/thing?arg=1", "hello", "there"])
67
+ ]
68
+
69
+ for input, label in dataset:
70
+ output = _parse_arg_string(input)
71
+ _assert_list_equal(label, output)
72
+
73
+
56
74
 
57
75
  def _cleanup_db():
58
76
  os.chdir(CWD)
@@ -60,5 +78,5 @@ def _cleanup_db():
60
78
  shutil.rmtree(TESTING_DIR)
61
79
 
62
80
  if __name__ == "__main__":
63
-
64
- test_run_commands()
81
+ test_args_parser()
82
+ # test_run_commands()
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes