doclogs-cli 0.1.0__tar.gz → 0.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 (51) hide show
  1. {doclogs_cli-0.1.0/doclogs_cli.egg-info → doclogs_cli-0.1.1}/PKG-INFO +50 -3
  2. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/README.md +49 -2
  3. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/commands/capture.py +3 -0
  4. doclogs_cli-0.1.1/commands/config.py +88 -0
  5. doclogs_cli-0.1.1/commands/generate.py +133 -0
  6. doclogs_cli-0.1.1/commands/publish.py +89 -0
  7. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/commands/sanitize.py +3 -0
  8. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/commands/weekly.py +6 -1
  9. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1/doclogs_cli.egg-info}/PKG-INFO +50 -3
  10. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/doclogs_cli.egg-info/SOURCES.txt +10 -0
  11. doclogs_cli-0.1.1/helper/generate.py +67 -0
  12. doclogs_cli-0.1.1/helper/llm/__init__.py +10 -0
  13. doclogs_cli-0.1.1/helper/llm/base.py +23 -0
  14. doclogs_cli-0.1.1/helper/llm/config.py +99 -0
  15. doclogs_cli-0.1.1/helper/llm/copilot_cli.py +79 -0
  16. doclogs_cli-0.1.1/helper/llm/cursor_cli.py +68 -0
  17. doclogs_cli-0.1.1/helper/llm/prompt_only.py +16 -0
  18. doclogs_cli-0.1.1/helper/llm/registry.py +39 -0
  19. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/helper/paths.py +1 -1
  20. doclogs_cli-0.1.1/helper/publish_git.py +160 -0
  21. doclogs_cli-0.1.1/helper/story.py +73 -0
  22. doclogs_cli-0.1.1/helper/syntax.py +227 -0
  23. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/helper/task_notes.py +5 -0
  24. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/helper/templates/config.yaml +19 -1
  25. doclogs_cli-0.1.1/main.py +41 -0
  26. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/pyproject.toml +1 -1
  27. doclogs_cli-0.1.0/commands/config.py +0 -28
  28. doclogs_cli-0.1.0/commands/generate.py +0 -49
  29. doclogs_cli-0.1.0/helper/generate.py +0 -17
  30. doclogs_cli-0.1.0/helper/story.py +0 -31
  31. doclogs_cli-0.1.0/main.py +0 -22
  32. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/LICENSE +0 -0
  33. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/commands/__init__.py +0 -0
  34. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/doclogs_cli.egg-info/dependency_links.txt +0 -0
  35. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/doclogs_cli.egg-info/entry_points.txt +0 -0
  36. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/doclogs_cli.egg-info/requires.txt +0 -0
  37. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/doclogs_cli.egg-info/top_level.txt +0 -0
  38. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/helper/__init__.py +0 -0
  39. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/helper/capture.py +0 -0
  40. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/helper/capture_prompts.py +0 -0
  41. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/helper/entry.py +0 -0
  42. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/helper/git_collector.py +0 -0
  43. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/helper/prompts.py +0 -0
  44. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/helper/sanitize.py +0 -0
  45. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/helper/templates/blog.md +0 -0
  46. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/helper/templates/changelog.md +0 -0
  47. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/helper/templates/interview.md +0 -0
  48. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/helper/templates/linkedin.md +0 -0
  49. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/helper/templates/resume.md +0 -0
  50. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/helper/weekly.py +0 -0
  51. {doclogs_cli-0.1.0 → doclogs_cli-0.1.1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: doclogs-cli
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: CLI for capturing engineering work and turning it into career artifacts.
5
5
  Author: Mridul Tiwari
6
6
  License-Expression: MIT
@@ -39,6 +39,8 @@ DocLogs helps you turn day-to-day engineering activity into durable artifacts by
39
39
  - generating markdown artifacts for blog posts, LinkedIn, resumes, and interview prep
40
40
  - keeping model usage provider-agnostic and safe with sanitization
41
41
 
42
+ Use `--syntax` on any command for usage, options, and examples (e.g. `doclog generate --syntax`). Standard `--help` is also available.
43
+
42
44
  ## Core commands
43
45
 
44
46
  - `doclog capture`
@@ -49,9 +51,14 @@ DocLogs helps you turn day-to-day engineering activity into durable artifacts by
49
51
  - surface candidate stories worth expanding
50
52
  - `doclog generate <type>`
51
53
  - create reusable artifacts such as `blog`, `linkedin`, `resume`, `interview`, or `changelog`
54
+ - default: saves a sanitized prompt file for manual use in Cursor or Copilot
55
+ - optional: auto-generate via logged-in Cursor CLI or GitHub Copilot CLI
52
56
  - `doclog sanitize`
53
57
  - sanitize captured content before any LLM request
54
58
  - redact internal URLs, tokens, IP addresses, and other sensitive details
59
+ - `doclog publish`
60
+ - configure a git repo for publishing generated posts
61
+ - commit and push a post to that repo
55
62
 
56
63
  ## Recommended repository structure
57
64
 
@@ -85,7 +92,7 @@ For implementation, start by focusing on:
85
92
  1. evidence collection and local storage
86
93
  2. weekly summaries and story selection
87
94
  3. safe prompt generation
88
- 4. provider adapters for OpenAI, Ollama, Anthropic, Gemini, or other APIs
95
+ 4. provider adapters for IDE CLIs (Cursor, Copilot) and optional API providers
89
96
 
90
97
  ## Getting started
91
98
 
@@ -120,10 +127,50 @@ doclog capture
120
127
 
121
128
  ### Daily use
122
129
 
123
- 1. Configure `~/.doclog/config.yaml` with your preferred LLM provider (created automatically on first run).
130
+ 1. Configure `~/.doclog/config.yaml` (created automatically on first run).
124
131
  2. Add a scheduler entry outside the CLI to invoke `doclog capture` at your preferred check-in time.
125
132
  3. Capture daily progress and generate reusable career artifacts from the same captured story.
126
133
 
134
+ ### Generate posts
135
+
136
+ ```bash
137
+ # Default: prompt file only (paste into Cursor/Copilot chat manually)
138
+ doclog weekly
139
+ doclog generate blog -t "nginx fix"
140
+
141
+ # Auto-generate via logged-in Cursor CLI
142
+ doclog config set provider cursor
143
+ doclog generate blog -t "nginx fix"
144
+
145
+ # Or Copilot
146
+ doclog config set provider copilot
147
+
148
+ # Back to prompt-only
149
+ doclog config set provider prompt_only
150
+
151
+ # Check provider availability
152
+ doclog config
153
+ ```
154
+
155
+ Ensure Cursor/Copilot CLI is installed and authenticated when using those providers (`agent login` or `copilot`).
156
+
157
+ ### Publish a post to git
158
+
159
+ Point DocLogs at a local clone of your blog/docs repo, then commit and push generated posts:
160
+
161
+ ```bash
162
+ doclog publish set repo ~/projects/my-blog
163
+ doclog publish set branch main
164
+ doclog generate blog -t 1
165
+ doclog publish push --latest
166
+ ```
167
+
168
+ Or push a specific file:
169
+
170
+ ```bash
171
+ doclog publish push ~/.doclog/posts/nginx-fix-blog.md -m "Add nginx TLS post"
172
+ ```
173
+
127
174
  ## Publish to PyPI (GitHub Actions)
128
175
 
129
176
  Publishing runs via GitHub Actions — no local `twine upload` needed.
@@ -13,6 +13,8 @@ DocLogs helps you turn day-to-day engineering activity into durable artifacts by
13
13
  - generating markdown artifacts for blog posts, LinkedIn, resumes, and interview prep
14
14
  - keeping model usage provider-agnostic and safe with sanitization
15
15
 
16
+ Use `--syntax` on any command for usage, options, and examples (e.g. `doclog generate --syntax`). Standard `--help` is also available.
17
+
16
18
  ## Core commands
17
19
 
18
20
  - `doclog capture`
@@ -23,9 +25,14 @@ DocLogs helps you turn day-to-day engineering activity into durable artifacts by
23
25
  - surface candidate stories worth expanding
24
26
  - `doclog generate <type>`
25
27
  - create reusable artifacts such as `blog`, `linkedin`, `resume`, `interview`, or `changelog`
28
+ - default: saves a sanitized prompt file for manual use in Cursor or Copilot
29
+ - optional: auto-generate via logged-in Cursor CLI or GitHub Copilot CLI
26
30
  - `doclog sanitize`
27
31
  - sanitize captured content before any LLM request
28
32
  - redact internal URLs, tokens, IP addresses, and other sensitive details
33
+ - `doclog publish`
34
+ - configure a git repo for publishing generated posts
35
+ - commit and push a post to that repo
29
36
 
30
37
  ## Recommended repository structure
31
38
 
@@ -59,7 +66,7 @@ For implementation, start by focusing on:
59
66
  1. evidence collection and local storage
60
67
  2. weekly summaries and story selection
61
68
  3. safe prompt generation
62
- 4. provider adapters for OpenAI, Ollama, Anthropic, Gemini, or other APIs
69
+ 4. provider adapters for IDE CLIs (Cursor, Copilot) and optional API providers
63
70
 
64
71
  ## Getting started
65
72
 
@@ -94,10 +101,50 @@ doclog capture
94
101
 
95
102
  ### Daily use
96
103
 
97
- 1. Configure `~/.doclog/config.yaml` with your preferred LLM provider (created automatically on first run).
104
+ 1. Configure `~/.doclog/config.yaml` (created automatically on first run).
98
105
  2. Add a scheduler entry outside the CLI to invoke `doclog capture` at your preferred check-in time.
99
106
  3. Capture daily progress and generate reusable career artifacts from the same captured story.
100
107
 
108
+ ### Generate posts
109
+
110
+ ```bash
111
+ # Default: prompt file only (paste into Cursor/Copilot chat manually)
112
+ doclog weekly
113
+ doclog generate blog -t "nginx fix"
114
+
115
+ # Auto-generate via logged-in Cursor CLI
116
+ doclog config set provider cursor
117
+ doclog generate blog -t "nginx fix"
118
+
119
+ # Or Copilot
120
+ doclog config set provider copilot
121
+
122
+ # Back to prompt-only
123
+ doclog config set provider prompt_only
124
+
125
+ # Check provider availability
126
+ doclog config
127
+ ```
128
+
129
+ Ensure Cursor/Copilot CLI is installed and authenticated when using those providers (`agent login` or `copilot`).
130
+
131
+ ### Publish a post to git
132
+
133
+ Point DocLogs at a local clone of your blog/docs repo, then commit and push generated posts:
134
+
135
+ ```bash
136
+ doclog publish set repo ~/projects/my-blog
137
+ doclog publish set branch main
138
+ doclog generate blog -t 1
139
+ doclog publish push --latest
140
+ ```
141
+
142
+ Or push a specific file:
143
+
144
+ ```bash
145
+ doclog publish push ~/.doclog/posts/nginx-fix-blog.md -m "Add nginx TLS post"
146
+ ```
147
+
101
148
  ## Publish to PyPI (GitHub Actions)
102
149
 
103
150
  Publishing runs via GitHub Actions — no local `twine upload` needed.
@@ -5,6 +5,7 @@ from helper.capture import build_capture_entry, merge_notes
5
5
  from helper.capture_prompts import iter_interactive_tasks
6
6
  from helper.entry import save_entry
7
7
  from helper.git_collector import find_git_root
8
+ from helper.syntax import maybe_show_syntax
8
9
 
9
10
 
10
11
  def _print_summary(entry, path) -> None:
@@ -21,7 +22,9 @@ def register(app: typer.Typer):
21
22
  no_interactive: bool = typer.Option(False, "--no-interactive", help="Skip questions; git-only capture."),
22
23
  include_terminal: bool = typer.Option(False, help="Include optional terminal history evidence."),
23
24
  include_tickets: bool = typer.Option(False, help="Include optional ticket IDs or issue references."),
25
+ syntax: bool = typer.Option(False, "--syntax", help="Show command syntax, options, and examples."),
24
26
  ) -> None:
27
+ maybe_show_syntax("capture", syntax)
25
28
  if find_git_root() is None:
26
29
  typer.echo("⚠️ Not inside a git repo — git evidence will be empty.")
27
30
 
@@ -0,0 +1,88 @@
1
+ import typer
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
5
+ import yaml
6
+
7
+ from helper.llm import PROVIDER_NAMES, get_provider
8
+ from helper.llm.config import (
9
+ load_llm_config,
10
+ load_raw_config,
11
+ provider_name_from_config,
12
+ set_llm_provider,
13
+ )
14
+ from helper.paths import ensure_config
15
+ from helper.syntax import maybe_show_syntax
16
+
17
+
18
+ def load_config(path: Path | None = None) -> dict[str, object]:
19
+ config_file = path or ensure_config()
20
+ if not config_file.exists():
21
+ raise typer.Exit(code=1, message=f"Config file not found: {config_file}")
22
+ with config_file.open("r", encoding="utf-8") as stream:
23
+ return yaml.safe_load(stream) or {}
24
+
25
+
26
+ def _show_config(path: Path | None = None) -> None:
27
+ active = path or ensure_config()
28
+ settings = load_config(active)
29
+ provider_name = provider_name_from_config(settings)
30
+
31
+ typer.echo(f"Active DocLogs configuration ({active}):")
32
+ typer.echo(settings)
33
+ typer.echo("")
34
+ typer.echo(f"Active provider: {provider_name}")
35
+ typer.echo(f"Cursor CLI: {_provider_status('cursor')}")
36
+ typer.echo(f"Copilot CLI: {_provider_status('copilot')}")
37
+ typer.echo("")
38
+ typer.echo("Change provider: doclog config set provider cursor|copilot|prompt_only")
39
+
40
+
41
+ def _provider_status(name: str) -> str:
42
+ provider = get_provider(name)
43
+ if provider.is_available():
44
+ return "available"
45
+ return f"not available — {provider.availability_hint()}"
46
+
47
+
48
+ def register(app: typer.Typer):
49
+ config_app = typer.Typer(help="Show or update DocLogs configuration.")
50
+
51
+ @config_app.callback(invoke_without_command=True)
52
+ def config_root(
53
+ ctx: typer.Context,
54
+ path: Optional[Path] = typer.Option(None, "-c", "--config", help="Path to the config file."),
55
+ syntax: bool = typer.Option(False, "--syntax", help="Show command syntax, options, and examples."),
56
+ ) -> None:
57
+ if syntax and ctx.invoked_subcommand is None:
58
+ maybe_show_syntax("config", True)
59
+ if ctx.invoked_subcommand is not None:
60
+ return
61
+ _show_config(path)
62
+
63
+ @config_app.command("set", help="Update a configuration value.")
64
+ def config_set(
65
+ key: str = typer.Argument(..., help="Setting name (currently: provider)."),
66
+ value: str = typer.Argument(..., help="New value."),
67
+ path: Optional[Path] = typer.Option(None, "-c", "--config", help="Path to the config file."),
68
+ syntax: bool = typer.Option(False, "--syntax", help="Show command syntax, options, and examples."),
69
+ ) -> None:
70
+ maybe_show_syntax("config set", syntax)
71
+ normalized_key = key.strip().lower().replace(".", "_")
72
+ if normalized_key not in {"provider", "llm_provider"}:
73
+ typer.echo(f"Unknown setting {key!r}. Supported: provider")
74
+ raise typer.Exit(code=1)
75
+
76
+ try:
77
+ config_file = set_llm_provider(value, path)
78
+ except ValueError as exc:
79
+ typer.echo(str(exc))
80
+ raise typer.Exit(code=1) from exc
81
+
82
+ typer.echo(f"Set llm.provider to {value.strip().lower()} in {config_file}")
83
+ if value.strip().lower() != "prompt_only":
84
+ selected = get_provider(value.strip().lower(), load_llm_config(config_file))
85
+ if not selected.is_available():
86
+ typer.echo(f"Warning: {selected.availability_hint()}")
87
+
88
+ app.add_typer(config_app, name="config")
@@ -0,0 +1,133 @@
1
+ import re
2
+
3
+ import typer
4
+ from typing import Optional
5
+
6
+ from helper.generate import build_prompt, save_and_generate
7
+ from helper.llm import PROVIDER_NAMES, ProviderError, get_provider, resolve_provider_name
8
+ from helper.llm.config import load_llm_config, provider_settings
9
+ from helper.paths import posts_dir
10
+ from helper.story import find_story_text
11
+ from helper.syntax import maybe_show_syntax
12
+
13
+
14
+ def slugify(text: str) -> str:
15
+ text = text.strip().lower()
16
+ text = re.sub(r"[^\w\s-]", "", text)
17
+ return re.sub(r"[-\s]+", "-", text).strip("-")
18
+
19
+
20
+ def register(app: typer.Typer):
21
+
22
+ @app.command("generate", help="Generate a reusable artifact from a captured story.")
23
+ def generate(
24
+ artifact_type: str = typer.Argument(..., help="blog, linkedin, resume, interview, changelog"),
25
+ title: Optional[str] = typer.Option(None, "-t", "--title", help="Story title from doclog weekly."),
26
+ days: int = typer.Option(7, help="Days back to search for the story."),
27
+ provider: Optional[str] = typer.Option(
28
+ None,
29
+ "--provider",
30
+ help=f"Override LLM provider: {', '.join(PROVIDER_NAMES)}",
31
+ ),
32
+ prompt_only: bool = typer.Option(
33
+ False,
34
+ "--prompt-only",
35
+ help="Write the sanitized prompt file only.",
36
+ ),
37
+ force: bool = typer.Option(
38
+ False,
39
+ "--force",
40
+ help="Send to an external provider even when sanitize flags are present.",
41
+ ),
42
+ trust: bool = typer.Option(
43
+ False,
44
+ "--trust",
45
+ help="Pass --trust to Cursor CLI (required for non-interactive agent runs).",
46
+ ),
47
+ syntax: bool = typer.Option(False, "--syntax", help="Show command syntax, options, and examples."),
48
+ ) -> None:
49
+ maybe_show_syntax("generate", syntax)
50
+ if not title:
51
+ typer.echo('Pass a story title: doclog generate blog -t "Phase 0 completed"')
52
+ raise typer.Exit(code=1)
53
+
54
+ story = find_story_text(title, days=days)
55
+ if not story:
56
+ typer.echo(f"Story not found: {title!r}")
57
+ typer.echo("Run doclog weekly to see available titles.")
58
+ typer.echo('Tip: use the list number, e.g. doclog generate blog -t 1')
59
+ typer.echo(' or a short unique phrase (do not include the date in parentheses)')
60
+ raise typer.Exit(code=1)
61
+
62
+ config = load_llm_config()
63
+ if trust:
64
+ config = dict(config)
65
+ cursor_cfg = dict(provider_settings(config, "cursor"))
66
+ cursor_cfg["trust_workspace"] = True
67
+ config["cursor"] = cursor_cfg
68
+ try:
69
+ provider_name = resolve_provider_name(
70
+ provider,
71
+ prompt_only=prompt_only,
72
+ config=config,
73
+ )
74
+ selected_provider = get_provider(provider_name, config)
75
+ except ValueError as exc:
76
+ typer.echo(str(exc))
77
+ raise typer.Exit(code=1) from exc
78
+
79
+ if provider_name != "prompt_only" and not selected_provider.is_available():
80
+ typer.echo(f"Provider {provider_name!r} is not available.")
81
+ typer.echo(selected_provider.availability_hint())
82
+ raise typer.Exit(code=1)
83
+
84
+ prompt, findings, flags = build_prompt(artifact_type, story)
85
+
86
+ if flags:
87
+ typer.echo("Sensitive patterns flagged — review before sending to an external provider:")
88
+ for flag in flags:
89
+ typer.echo(f" - [{flag.kind}] {flag.matched[:60]}")
90
+ if provider_name != "prompt_only" and not force:
91
+ slug = slugify(title)
92
+ output_dir = posts_dir()
93
+ output_dir.mkdir(parents=True, exist_ok=True)
94
+ prompt_path = output_dir / f"{slug}-{artifact_type}-prompt.md"
95
+ prompt_path.write_text(prompt, encoding="utf-8")
96
+ typer.echo("Re-run with --force to send this prompt to the selected provider.")
97
+ typer.echo(f"Prompt saved to {prompt_path}")
98
+ raise typer.Exit(code=1)
99
+
100
+ slug = slugify(title)
101
+ output_dir = posts_dir()
102
+ output_dir.mkdir(parents=True, exist_ok=True)
103
+
104
+ if provider_name == "prompt_only":
105
+ prompt_path = output_dir / f"{slug}-{artifact_type}.md"
106
+ artifact_path = None
107
+ else:
108
+ prompt_path = output_dir / f"{slug}-{artifact_type}-prompt.md"
109
+ artifact_path = output_dir / f"{slug}-{artifact_type}.md"
110
+
111
+ try:
112
+ result = save_and_generate(
113
+ prompt,
114
+ findings,
115
+ flags,
116
+ selected_provider,
117
+ prompt_path=prompt_path,
118
+ artifact_path=artifact_path,
119
+ )
120
+ except ProviderError as exc:
121
+ prompt_path.write_text(prompt, encoding="utf-8")
122
+ typer.echo(str(exc))
123
+ typer.echo(f"Prompt saved to {prompt_path}")
124
+ raise typer.Exit(code=1) from exc
125
+
126
+ if provider_name == "prompt_only":
127
+ typer.echo(f"Prompt saved to {result.prompt_path}")
128
+ else:
129
+ typer.echo(f"Prompt saved to {result.prompt_path}")
130
+ typer.echo(f"Artifact saved to {result.artifact_path} (provider: {provider_name})")
131
+
132
+ if findings:
133
+ typer.echo(f" ({len(findings)} item(s) redacted in evidence)")
@@ -0,0 +1,89 @@
1
+ import typer
2
+ from pathlib import Path
3
+ from typing import Optional
4
+
5
+ from helper.publish_git import (
6
+ PUBLISH_KEYS,
7
+ PublishError,
8
+ load_publish_config,
9
+ push_post,
10
+ resolve_post_file,
11
+ set_publish_value,
12
+ )
13
+ from helper.syntax import maybe_show_syntax
14
+
15
+
16
+ def _show_publish_config() -> None:
17
+ config = load_publish_config()
18
+ typer.echo("Publish configuration:")
19
+ typer.echo(f" repo: {config.repo_path or '(not set)'}")
20
+ typer.echo(f" branch: {config.branch}")
21
+ typer.echo(f" remote: {config.remote}")
22
+ typer.echo(f" subdir: {config.subdir}")
23
+ typer.echo("")
24
+ typer.echo("Configure: doclog publish set repo /path/to/clone")
25
+ typer.echo("Push post: doclog publish push --latest")
26
+
27
+
28
+ def register(app: typer.Typer):
29
+ publish_app = typer.Typer(help="Configure and push generated posts to a git repo.")
30
+
31
+ @publish_app.callback(invoke_without_command=True)
32
+ def publish_root(
33
+ ctx: typer.Context,
34
+ syntax: bool = typer.Option(False, "--syntax", help="Show command syntax, options, and examples."),
35
+ ) -> None:
36
+ if syntax and ctx.invoked_subcommand is None:
37
+ maybe_show_syntax("publish", True)
38
+ if ctx.invoked_subcommand is not None:
39
+ return
40
+ _show_publish_config()
41
+
42
+ @publish_app.command("set", help="Configure the git repo used to publish posts.")
43
+ def publish_set(
44
+ key: str = typer.Argument(..., help=f"One of: {', '.join(sorted(PUBLISH_KEYS))}"),
45
+ value: str = typer.Argument(..., help="Setting value."),
46
+ syntax: bool = typer.Option(False, "--syntax", help="Show command syntax, options, and examples."),
47
+ ) -> None:
48
+ maybe_show_syntax("publish set", syntax)
49
+ try:
50
+ config_file = set_publish_value(key, value)
51
+ except ValueError as exc:
52
+ typer.echo(str(exc))
53
+ raise typer.Exit(code=1) from exc
54
+
55
+ typer.echo(f"Set publish.{key.strip().lower()} in {config_file}")
56
+
57
+ @publish_app.command("push", help="Copy a post into the publish repo, commit, and push.")
58
+ def publish_push(
59
+ post: Optional[Path] = typer.Argument(
60
+ None,
61
+ help="Path to a generated post under ~/.doclog/posts/",
62
+ ),
63
+ latest: bool = typer.Option(
64
+ False,
65
+ "--latest",
66
+ "-l",
67
+ help="Publish the most recently generated post.",
68
+ ),
69
+ message: Optional[str] = typer.Option(
70
+ None,
71
+ "-m",
72
+ "--message",
73
+ help="Custom git commit message.",
74
+ ),
75
+ syntax: bool = typer.Option(False, "--syntax", help="Show command syntax, options, and examples."),
76
+ ) -> None:
77
+ maybe_show_syntax("publish push", syntax)
78
+ try:
79
+ post_file = resolve_post_file(post, latest=latest)
80
+ dest = push_post(post_file, load_publish_config(), message=message)
81
+ except PublishError as exc:
82
+ typer.echo(str(exc))
83
+ raise typer.Exit(code=1) from exc
84
+
85
+ typer.echo(f"Published {post_file.name}")
86
+ typer.echo(f" copied to {dest}")
87
+ typer.echo(f" pushed to remote")
88
+
89
+ app.add_typer(publish_app, name="publish")
@@ -3,6 +3,7 @@ from typing import *
3
3
 
4
4
  from helper.entry import entry_path_for, load_entry
5
5
  from helper.sanitize import redact, sanitize_with_review, text_from_entry
6
+ from helper.syntax import maybe_show_syntax
6
7
 
7
8
 
8
9
  def register(app: typer.Typer):
@@ -10,7 +11,9 @@ def register(app: typer.Typer):
10
11
  @app.command("sanitize", help="Sanitize content before sending to an LLM.")
11
12
  def sanitize(
12
13
  source: Optional[str] = typer.Argument(None, help="Text to sanitize. If omitted, uses today's capture."),
14
+ syntax: bool = typer.Option(False, "--syntax", help="Show command syntax, options, and examples."),
13
15
  ) -> None:
16
+ maybe_show_syntax("sanitize", syntax)
14
17
  if source:
15
18
  text = source
16
19
  else:
@@ -2,6 +2,7 @@ from ast import Return
2
2
  import typer
3
3
  from typing import *
4
4
  from helper.weekly import build_story_candidates, load_entries_for_days
5
+ from helper.syntax import maybe_show_syntax
5
6
 
6
7
 
7
8
  def register(app: typer.Typer):
@@ -9,9 +10,11 @@ def register(app: typer.Typer):
9
10
  @app.command("weekly", help="run workflows")
10
11
  def weekly(
11
12
  limit: int = typer.Option(5, help="Maximum number of story candidates to display."),
12
- days: int = typer.Option(7, help="How many days back to review.")
13
+ days: int = typer.Option(7, help="How many days back to review."),
14
+ syntax: bool = typer.Option(False, "--syntax", help="Show command syntax, options, and examples."),
13
15
  ) -> None:
14
16
  """Summarize the week and surface candidate stories for expansion."""
17
+ maybe_show_syntax("weekly", syntax)
15
18
  entries = load_entries_for_days(days)
16
19
  if not entries:
17
20
  typer.echo(f"📅 No captures found in the last {days} days.")
@@ -25,3 +28,5 @@ def register(app: typer.Typer):
25
28
  return
26
29
  for i, story in enumerate(candidates, start=1):
27
30
  typer.echo(f"{i}. [{story.source}] {story.title} ({story.date})")
31
+ typer.echo("")
32
+ typer.echo('Generate: doclog generate blog -t 1 (or a short unique phrase from the title)')
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: doclogs-cli
3
- Version: 0.1.0
3
+ Version: 0.1.1
4
4
  Summary: CLI for capturing engineering work and turning it into career artifacts.
5
5
  Author: Mridul Tiwari
6
6
  License-Expression: MIT
@@ -39,6 +39,8 @@ DocLogs helps you turn day-to-day engineering activity into durable artifacts by
39
39
  - generating markdown artifacts for blog posts, LinkedIn, resumes, and interview prep
40
40
  - keeping model usage provider-agnostic and safe with sanitization
41
41
 
42
+ Use `--syntax` on any command for usage, options, and examples (e.g. `doclog generate --syntax`). Standard `--help` is also available.
43
+
42
44
  ## Core commands
43
45
 
44
46
  - `doclog capture`
@@ -49,9 +51,14 @@ DocLogs helps you turn day-to-day engineering activity into durable artifacts by
49
51
  - surface candidate stories worth expanding
50
52
  - `doclog generate <type>`
51
53
  - create reusable artifacts such as `blog`, `linkedin`, `resume`, `interview`, or `changelog`
54
+ - default: saves a sanitized prompt file for manual use in Cursor or Copilot
55
+ - optional: auto-generate via logged-in Cursor CLI or GitHub Copilot CLI
52
56
  - `doclog sanitize`
53
57
  - sanitize captured content before any LLM request
54
58
  - redact internal URLs, tokens, IP addresses, and other sensitive details
59
+ - `doclog publish`
60
+ - configure a git repo for publishing generated posts
61
+ - commit and push a post to that repo
55
62
 
56
63
  ## Recommended repository structure
57
64
 
@@ -85,7 +92,7 @@ For implementation, start by focusing on:
85
92
  1. evidence collection and local storage
86
93
  2. weekly summaries and story selection
87
94
  3. safe prompt generation
88
- 4. provider adapters for OpenAI, Ollama, Anthropic, Gemini, or other APIs
95
+ 4. provider adapters for IDE CLIs (Cursor, Copilot) and optional API providers
89
96
 
90
97
  ## Getting started
91
98
 
@@ -120,10 +127,50 @@ doclog capture
120
127
 
121
128
  ### Daily use
122
129
 
123
- 1. Configure `~/.doclog/config.yaml` with your preferred LLM provider (created automatically on first run).
130
+ 1. Configure `~/.doclog/config.yaml` (created automatically on first run).
124
131
  2. Add a scheduler entry outside the CLI to invoke `doclog capture` at your preferred check-in time.
125
132
  3. Capture daily progress and generate reusable career artifacts from the same captured story.
126
133
 
134
+ ### Generate posts
135
+
136
+ ```bash
137
+ # Default: prompt file only (paste into Cursor/Copilot chat manually)
138
+ doclog weekly
139
+ doclog generate blog -t "nginx fix"
140
+
141
+ # Auto-generate via logged-in Cursor CLI
142
+ doclog config set provider cursor
143
+ doclog generate blog -t "nginx fix"
144
+
145
+ # Or Copilot
146
+ doclog config set provider copilot
147
+
148
+ # Back to prompt-only
149
+ doclog config set provider prompt_only
150
+
151
+ # Check provider availability
152
+ doclog config
153
+ ```
154
+
155
+ Ensure Cursor/Copilot CLI is installed and authenticated when using those providers (`agent login` or `copilot`).
156
+
157
+ ### Publish a post to git
158
+
159
+ Point DocLogs at a local clone of your blog/docs repo, then commit and push generated posts:
160
+
161
+ ```bash
162
+ doclog publish set repo ~/projects/my-blog
163
+ doclog publish set branch main
164
+ doclog generate blog -t 1
165
+ doclog publish push --latest
166
+ ```
167
+
168
+ Or push a specific file:
169
+
170
+ ```bash
171
+ doclog publish push ~/.doclog/posts/nginx-fix-blog.md -m "Add nginx TLS post"
172
+ ```
173
+
127
174
  ## Publish to PyPI (GitHub Actions)
128
175
 
129
176
  Publishing runs via GitHub Actions — no local `twine upload` needed.
@@ -6,6 +6,7 @@ commands/__init__.py
6
6
  commands/capture.py
7
7
  commands/config.py
8
8
  commands/generate.py
9
+ commands/publish.py
9
10
  commands/sanitize.py
10
11
  commands/weekly.py
11
12
  doclogs_cli.egg-info/PKG-INFO
@@ -22,10 +23,19 @@ helper/generate.py
22
23
  helper/git_collector.py
23
24
  helper/paths.py
24
25
  helper/prompts.py
26
+ helper/publish_git.py
25
27
  helper/sanitize.py
26
28
  helper/story.py
29
+ helper/syntax.py
27
30
  helper/task_notes.py
28
31
  helper/weekly.py
32
+ helper/llm/__init__.py
33
+ helper/llm/base.py
34
+ helper/llm/config.py
35
+ helper/llm/copilot_cli.py
36
+ helper/llm/cursor_cli.py
37
+ helper/llm/prompt_only.py
38
+ helper/llm/registry.py
29
39
  helper/templates/blog.md
30
40
  helper/templates/changelog.md
31
41
  helper/templates/config.yaml