doclogs-cli 0.1.1__tar.gz → 0.1.2__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 (46) hide show
  1. {doclogs_cli-0.1.1/doclogs_cli.egg-info → doclogs_cli-0.1.2}/PKG-INFO +1 -1
  2. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/commands/capture.py +17 -3
  3. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2/doclogs_cli.egg-info}/PKG-INFO +1 -1
  4. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/capture_prompts.py +14 -4
  5. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/story.py +26 -9
  6. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/syntax.py +3 -0
  7. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/task_notes.py +27 -6
  8. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/weekly.py +2 -2
  9. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/pyproject.toml +1 -1
  10. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/LICENSE +0 -0
  11. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/README.md +0 -0
  12. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/commands/__init__.py +0 -0
  13. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/commands/config.py +0 -0
  14. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/commands/generate.py +0 -0
  15. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/commands/publish.py +0 -0
  16. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/commands/sanitize.py +0 -0
  17. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/commands/weekly.py +0 -0
  18. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/doclogs_cli.egg-info/SOURCES.txt +0 -0
  19. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/doclogs_cli.egg-info/dependency_links.txt +0 -0
  20. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/doclogs_cli.egg-info/entry_points.txt +0 -0
  21. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/doclogs_cli.egg-info/requires.txt +0 -0
  22. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/doclogs_cli.egg-info/top_level.txt +0 -0
  23. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/__init__.py +0 -0
  24. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/capture.py +0 -0
  25. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/entry.py +0 -0
  26. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/generate.py +0 -0
  27. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/git_collector.py +0 -0
  28. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/llm/__init__.py +0 -0
  29. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/llm/base.py +0 -0
  30. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/llm/config.py +0 -0
  31. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/llm/copilot_cli.py +0 -0
  32. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/llm/cursor_cli.py +0 -0
  33. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/llm/prompt_only.py +0 -0
  34. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/llm/registry.py +0 -0
  35. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/paths.py +0 -0
  36. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/prompts.py +0 -0
  37. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/publish_git.py +0 -0
  38. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/sanitize.py +0 -0
  39. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/templates/blog.md +0 -0
  40. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/templates/changelog.md +0 -0
  41. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/templates/config.yaml +0 -0
  42. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/templates/interview.md +0 -0
  43. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/templates/linkedin.md +0 -0
  44. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/helper/templates/resume.md +0 -0
  45. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/main.py +0 -0
  46. {doclogs_cli-0.1.1 → doclogs_cli-0.1.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: doclogs-cli
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: CLI for capturing engineering work and turning it into career artifacts.
5
5
  Author: Mridul Tiwari
6
6
  License-Expression: MIT
@@ -14,11 +14,25 @@ def _print_summary(entry, path) -> None:
14
14
  typer.echo(f" commits today: {len(entry.commits)}")
15
15
 
16
16
 
17
+ def _notes_with_topic(notes: str, topic: str | None) -> str:
18
+ if not topic or not topic.strip():
19
+ return notes
20
+ topic_line = f"topic: {topic.strip()}"
21
+ if notes.lstrip().startswith("topic:"):
22
+ return notes
23
+ return f"{topic_line}\n{notes}"
24
+
25
+
17
26
  def register(app: typer.Typer):
18
27
 
19
28
  @app.command("capture", help="Capture today's engineering work into local storage.")
20
29
  def capture(
21
30
  notes: Optional[str] = typer.Option(None, "-n", "--notes", help="Optional notes (skips interactive prompts)."),
31
+ topic: Optional[str] = typer.Option(
32
+ None,
33
+ "--topic",
34
+ help="Short task name (used in weekly review and doclog generate -t).",
35
+ ),
22
36
  no_interactive: bool = typer.Option(False, "--no-interactive", help="Skip questions; git-only capture."),
23
37
  include_terminal: bool = typer.Option(False, help="Include optional terminal history evidence."),
24
38
  include_tickets: bool = typer.Option(False, help="Include optional ticket IDs or issue references."),
@@ -34,7 +48,7 @@ def register(app: typer.Typer):
34
48
  entry = None
35
49
  saved_any = False
36
50
 
37
- for batch, replace in iter_interactive_tasks():
51
+ for batch, replace in iter_interactive_tasks(topic=topic):
38
52
  entry = build_capture_entry(notes=batch, replace_notes=replace)
39
53
  path = save_entry(entry)
40
54
  saved_any = True
@@ -48,7 +62,7 @@ def register(app: typer.Typer):
48
62
  return
49
63
 
50
64
  # Non-interactive: git only or single --notes
51
- final_notes = merge_notes(notes)
65
+ final_notes = merge_notes(_notes_with_topic(notes, topic) if notes else None)
52
66
  entry = build_capture_entry(notes=final_notes)
53
67
  path = save_entry(entry)
54
- _print_summary(entry, path)
68
+ _print_summary(entry, path)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: doclogs-cli
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: CLI for capturing engineering work and turning it into career artifacts.
5
5
  Author: Mridul Tiwari
6
6
  License-Expression: MIT
@@ -59,28 +59,38 @@ def _next_task_number() -> int:
59
59
  return max(nums, default=0) + 1
60
60
 
61
61
 
62
- def iter_interactive_tasks():
62
+ def iter_interactive_tasks(topic: str | None = None):
63
63
  updated = maybe_complete_stub()
64
64
  if updated:
65
65
  yield updated, True # ← replace entire notes
66
66
  task_num = _next_task_number()
67
+ first_task = True
67
68
  while True:
68
69
  typer.echo(f"\n--- Task {task_num} ---\n")
69
- batch = collect_interactive_notes()
70
+ default_topic = topic if first_task else None
71
+ batch = collect_interactive_notes(default_topic=default_topic)
72
+ first_task = False
70
73
  if batch:
71
74
  yield f"### Task {task_num}\n{batch}", False # ← append new task
72
75
  if not typer.confirm("Add another task?", default=False):
73
76
  break
74
77
  task_num += 1
75
78
 
76
- def collect_interactive_notes() -> str | None:
79
+ def collect_interactive_notes(default_topic: str | None = None) -> str | None:
77
80
  typer.echo("\n📋 Daily capture — answer briefly (Enter to skip optional questions)\n")
78
81
 
82
+ topic_answer = typer.prompt(
83
+ "Task topic (short name for weekly & generate)",
84
+ default=default_topic or "",
85
+ ).strip()
79
86
  worked_on = typer.prompt("What did you work on today?", default="").strip()
80
87
  if not worked_on:
81
88
  return None
82
89
 
83
- lines = [f"worked_on: {worked_on}"]
90
+ lines: list[str] = []
91
+ if topic_answer:
92
+ lines.append(f"topic: {topic_answer}")
93
+ lines.append(f"worked_on: {worked_on}")
84
94
 
85
95
  if typer.confirm("Add more details later?", default=False):
86
96
  lines.append("status: details_later")
@@ -2,7 +2,7 @@ from __future__ import annotations
2
2
 
3
3
  import re
4
4
 
5
- from helper.task_notes import extract_worked_on, normalize_title, parse_task_blocks
5
+ from helper.task_notes import extract_topic, extract_worked_on, normalize_title, parse_task_blocks, story_title
6
6
  from helper.weekly import build_story_candidates, load_entries_for_days
7
7
 
8
8
  _WEEKLY_DATE_SUFFIX = re.compile(r"\s+\(\d{4}-\d{2}-\d{2}\)\s*$")
@@ -53,14 +53,16 @@ def find_story_text(title: str, days: int = 7) -> str | None:
53
53
  )
54
54
 
55
55
  for block in parse_task_blocks(entry.notes):
56
- task_title = extract_worked_on(block)
57
- if not task_title:
56
+ titles = _task_titles(block)
57
+ if not titles:
58
58
  continue
59
- candidate = normalize_title(task_title)
60
- if candidate == needle:
61
- return block
62
- if _is_partial_match(needle, candidate):
63
- partial_matches.append(block)
59
+ for candidate in titles:
60
+ normalized = normalize_title(candidate)
61
+ if needle == normalized:
62
+ return block
63
+ if _is_partial_match(needle, normalized):
64
+ partial_matches.append(block)
65
+ break
64
66
 
65
67
  if len(partial_matches) == 1:
66
68
  return partial_matches[0]
@@ -68,6 +70,21 @@ def find_story_text(title: str, days: int = 7) -> str | None:
68
70
 
69
71
 
70
72
  def _is_partial_match(needle: str, candidate: str) -> bool:
71
- if len(needle) < 8:
73
+ if len(needle) < 3:
72
74
  return False
73
75
  return needle in candidate or candidate in needle
76
+
77
+
78
+ def _task_titles(block: str) -> list[str]:
79
+ titles: list[str] = []
80
+ topic = extract_topic(block)
81
+ worked_on = extract_worked_on(block)
82
+ if topic:
83
+ titles.append(topic)
84
+ if worked_on and worked_on not in titles:
85
+ titles.append(worked_on)
86
+ if not titles:
87
+ fallback = story_title(block)
88
+ if fallback:
89
+ titles.append(fallback)
90
+ return titles
@@ -37,6 +37,7 @@ SYNTAX: dict[str, CommandSyntax] = {
37
37
  usage="doclog capture [OPTIONS]",
38
38
  options=(
39
39
  "-n, --notes TEXT Notes text (skips interactive prompts)",
40
+ "--topic TEXT Short task name (weekly & generate -t)",
40
41
  "--no-interactive Git-only capture; skip questions",
41
42
  "--include-terminal Include terminal history (planned)",
42
43
  "--include-tickets Include ticket IDs (planned)",
@@ -44,6 +45,8 @@ SYNTAX: dict[str, CommandSyntax] = {
44
45
  ),
45
46
  examples=(
46
47
  "doclog capture",
48
+ "doclog capture --topic \"nginx akamai TLS\"",
49
+ 'doclog capture --topic "DocLogs" -n "worked_on: shipped publish command"',
47
50
  "doclog capture --no-interactive",
48
51
  'doclog capture -n "Fixed nginx TLS handshake with Akamai"',
49
52
  "doclog capture --syntax",
@@ -17,6 +17,19 @@ def extract_worked_on(block: str) -> str | None:
17
17
  return _extract_worked_on(block)
18
18
 
19
19
 
20
+ def extract_topic(block: str) -> str | None:
21
+ return _extract_field(
22
+ block,
23
+ "topic",
24
+ stop_fields=("worked_on", "impact", "blockers", "remember", "status", "### Task"),
25
+ )
26
+
27
+
28
+ def story_title(block: str) -> str | None:
29
+ """Short label for weekly/generate; prefers topic over worked_on."""
30
+ return extract_topic(block) or extract_worked_on(block)
31
+
32
+
20
33
  def normalize_title(text: str) -> str:
21
34
  """Collapse whitespace and lowercase for fuzzy title matching."""
22
35
  return " ".join(text.strip().lower().split())
@@ -33,21 +46,27 @@ def list_incomplete_tasks(notes: str | None) -> list[tuple[int, str]]:
33
46
  continue
34
47
  match = re.search(r"### Task (\d+)", block)
35
48
  task_num = int(match.group(1)) if match else 0
36
- summary = _extract_worked_on(block) or (f"Task {task_num}" if task_num else "Incomplete entry")
49
+ summary = story_title(block) or (f"Task {task_num}" if task_num else "Incomplete entry")
37
50
  results.append((task_num, summary))
38
51
 
39
52
  return results
40
53
 
41
54
 
42
55
  def _extract_worked_on(block: str) -> str | None:
43
- match = re.search(
44
- r"worked_on:\s*(.+?)(?=\n(?:impact|blockers|remember|status:|### Task|\Z))",
56
+ return _extract_field(
45
57
  block,
46
- re.DOTALL,
58
+ "worked_on",
59
+ stop_fields=("topic", "impact", "blockers", "remember", "status", "### Task"),
47
60
  )
61
+
62
+
63
+ def _extract_field(block: str, field: str, *, stop_fields: tuple[str, ...]) -> str | None:
64
+ stops = "|".join(re.escape(name) for name in stop_fields)
65
+ pattern = rf"{field}:\s*(.+?)(?=\n(?:{stops}|\Z))"
66
+ match = re.search(pattern, block, re.DOTALL)
48
67
  if not match:
49
68
  return None
50
- return " ".join(match.group(1).split()) # flatten multiline YAML
69
+ return " ".join(match.group(1).split())
51
70
 
52
71
 
53
72
  def complete_task(notes: str, task_num: int, followup: str) -> str:
@@ -61,7 +80,9 @@ def complete_task(notes: str, task_num: int, followup: str) -> str:
61
80
  if not match:
62
81
  raise ValueError(f"Task {task_num} not found")
63
82
  worked_on = _extract_worked_on(match.group(0)) or ""
64
- new_block = f"### Task {task_num}\nworked_on: {worked_on}\n{followup}"
83
+ topic = extract_topic(match.group(0))
84
+ topic_line = f"topic: {topic}\n" if topic else ""
85
+ new_block = f"### Task {task_num}\n{topic_line}worked_on: {worked_on}\n{followup}"
65
86
 
66
87
  match = re.search(pattern, notes, re.DOTALL)
67
88
  if not match:
@@ -5,7 +5,7 @@ from datetime import date, timedelta
5
5
 
6
6
  from helper.entry import DailyEntry, load_entry
7
7
  from helper.paths import entries_dir
8
- from helper.task_notes import DETAILS_LATER, extract_worked_on, parse_task_blocks
8
+ from helper.task_notes import DETAILS_LATER, parse_task_blocks, story_title
9
9
 
10
10
 
11
11
  @dataclass
@@ -45,7 +45,7 @@ def build_story_candidates(
45
45
  )
46
46
 
47
47
  for block in parse_task_blocks(entry.notes):
48
- title = extract_worked_on(block)
48
+ title = story_title(block)
49
49
  if not title:
50
50
  continue
51
51
  key = title.lower()
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "doclogs-cli"
7
- version = "0.1.1"
7
+ version = "0.1.2"
8
8
  description = "CLI for capturing engineering work and turning it into career artifacts."
9
9
  readme = "README.md"
10
10
  license = "MIT"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes