deepvista-cli 0.2.0__tar.gz → 0.4.0__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 (93) hide show
  1. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/.github/workflows/ci.yml +26 -0
  2. deepvista_cli-0.4.0/.release-please-manifest.json +3 -0
  3. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/CHANGELOG.md +20 -0
  4. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/PKG-INFO +39 -1
  5. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/README.md +38 -0
  6. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/commands/auth.py +33 -0
  7. deepvista_cli-0.4.0/deepvista_cli/commands/schedule.py +169 -0
  8. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/main.py +3 -0
  9. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/output/formatter.py +2 -2
  10. deepvista_cli-0.4.0/install.ps1 +401 -0
  11. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/plugins/claude-code/.claude-plugin/plugin.json +1 -1
  12. deepvista_cli-0.4.0/plugins/claude-code/commands/deepvista.md +115 -0
  13. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/pyproject.toml +1 -1
  14. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/uv.lock +1 -1
  15. deepvista_cli-0.2.0/.release-please-manifest.json +0 -3
  16. deepvista_cli-0.2.0/plugins/claude-code/commands/deepvista.md +0 -129
  17. deepvista_cli-0.2.0/plugins/claude-code/skills/daily-planning/SKILL.md +0 -161
  18. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/.claude-plugin/marketplace.json +0 -0
  19. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/.github/workflows/publish.yml +0 -0
  20. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/.github/workflows/release-please.yml +0 -0
  21. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/.gitignore +0 -0
  22. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/.pre-commit-config.yaml +0 -0
  23. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/CLAUDE.md +0 -0
  24. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/CONTRIBUTING.md +0 -0
  25. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/LICENSE +0 -0
  26. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/__init__.py +0 -0
  27. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/agent_catalog.py +0 -0
  28. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/auth/__init__.py +0 -0
  29. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/auth/callback_server.py +0 -0
  30. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/auth/login.py +0 -0
  31. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/auth/tokens.py +0 -0
  32. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/client/__init__.py +0 -0
  33. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/client/http.py +0 -0
  34. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/client/origin.py +0 -0
  35. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/commands/__init__.py +0 -0
  36. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/commands/agents.py +0 -0
  37. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/commands/card.py +0 -0
  38. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/commands/chat.py +0 -0
  39. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/commands/config.py +0 -0
  40. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/commands/lint.py +0 -0
  41. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/commands/memory.py +0 -0
  42. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/commands/notes.py +0 -0
  43. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/commands/session.py +0 -0
  44. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/commands/skill.py +0 -0
  45. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/commands/upgrade.py +0 -0
  46. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/commands/vistabase.py +0 -0
  47. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/config.py +0 -0
  48. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/output/__init__.py +0 -0
  49. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/resources/__init__.py +0 -0
  50. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/resources/workflow_host_runtime.md +0 -0
  51. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/session_note.py +0 -0
  52. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/skill_catalog.py +0 -0
  53. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/tui/__init__.py +0 -0
  54. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/tui/app.py +0 -0
  55. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/deepvista_cli/workflow_doc.py +0 -0
  56. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/docs/assets/deepvista-banner.png +0 -0
  57. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/install.sh +0 -0
  58. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/plugins/README.md +0 -0
  59. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/plugins/claude-code/README.md +0 -0
  60. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/plugins/claude-code/agents/.gitignore +0 -0
  61. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/plugins/claude-code/commands/refresh-skills.md +0 -0
  62. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/plugins/claude-code/hooks/hooks.json +0 -0
  63. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/plugins/claude-code/scripts/deepvista-session-end.sh +0 -0
  64. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/plugins/claude-code/scripts/deepvista-session-start.sh +0 -0
  65. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/plugins/claude-code/scripts/deepvista-session-turn.sh +0 -0
  66. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/plugins/claude-code/scripts/deepvista-skill-url.py +0 -0
  67. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/plugins/claude-code/scripts/deepvista-sync.sh +0 -0
  68. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/plugins/claude-code/skills/.gitignore +0 -0
  69. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/plugins/claude-code/skills/install-deepvista-cli/SKILL.md +0 -0
  70. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/release-please-config.json +0 -0
  71. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/scripts/check_plugin_version.py +0 -0
  72. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/skills/deepvista/SKILL.md +0 -0
  73. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/skills/deepvista/reference/chat.md +0 -0
  74. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/skills/deepvista/reference/lint.md +0 -0
  75. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/skills/deepvista/reference/memory.md +0 -0
  76. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/skills/deepvista/reference/notes.md +0 -0
  77. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/skills/deepvista/reference/openclaw.md +0 -0
  78. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/skills/deepvista/reference/session.md +0 -0
  79. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/skills/deepvista/reference/shared.md +0 -0
  80. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/skills/deepvista/reference/skill-analyze-notes.md +0 -0
  81. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/skills/deepvista/reference/skill-create-from-note.md +0 -0
  82. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/skills/deepvista/reference/skill-import-files.md +0 -0
  83. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/skills/deepvista/reference/skill-research-to-skill.md +0 -0
  84. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/skills/deepvista/reference/skill.md +0 -0
  85. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/skills/deepvista/reference/vistabase-card.md +0 -0
  86. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/skills/deepvista/reference/vistabase.md +0 -0
  87. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/skills/dv-workflow/SKILL.md +0 -0
  88. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/tests/__init__.py +0 -0
  89. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/tests/test_agent_id_tagging.py +0 -0
  90. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/tests/test_session_note_format.py +0 -0
  91. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/tests/test_skill_catalog.py +0 -0
  92. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/tests/test_skill_commands.py +0 -0
  93. {deepvista_cli-0.2.0 → deepvista_cli-0.4.0}/uninstall.sh +0 -0
@@ -47,3 +47,29 @@ jobs:
47
47
  env:
48
48
  GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
49
49
  run: gh skill publish --dry-run
50
+
51
+ windows-smoke:
52
+ runs-on: windows-latest
53
+ steps:
54
+ - uses: actions/checkout@v4
55
+
56
+ - name: Parse install.ps1 (syntax check)
57
+ shell: pwsh
58
+ run: |
59
+ $errors = $null
60
+ [System.Management.Automation.Language.Parser]::ParseFile("$PWD/install.ps1", [ref]$null, [ref]$errors) | Out-Null
61
+ if ($errors.Count -gt 0) {
62
+ $errors | ForEach-Object { Write-Error $_.Message }
63
+ exit 1
64
+ }
65
+ Write-Host "install.ps1 parses cleanly"
66
+
67
+ - uses: astral-sh/setup-uv@v6
68
+ with:
69
+ enable-cache: true
70
+
71
+ - name: Install deepvista CLI from checkout
72
+ run: uv tool install ".[ui]"
73
+
74
+ - name: Smoke test - deepvista --help
75
+ run: deepvista --help
@@ -0,0 +1,3 @@
1
+ {
2
+ ".": "0.4.0"
3
+ }
@@ -37,6 +37,26 @@ users what's new between the version they have installed and the latest release.
37
37
  adopts a pre-existing server-side row instead of failing when the local
38
38
  file is missing.
39
39
 
40
+ ## [0.4.0](https://github.com/DeepVista-AI/deepvista-cli/compare/v0.3.0...v0.4.0) (2026-06-03)
41
+
42
+
43
+ ### Features
44
+
45
+ * **DV-941:** add windows install script and trampoline troubleshooting docs ([#156](https://github.com/DeepVista-AI/deepvista-cli/issues/156)) ([eed7335](https://github.com/DeepVista-AI/deepvista-cli/commit/eed7335ffdc1dfcfd84523ed0e77be066b7fe9c3))
46
+ * **DV-942:** print next-step hints after auth login ([#157](https://github.com/DeepVista-AI/deepvista-cli/issues/157)) ([0fca6c9](https://github.com/DeepVista-AI/deepvista-cli/commit/0fca6c9309623ee690c2c9e16870d528b558fd80))
47
+
48
+ ## [0.3.0](https://github.com/DeepVista-AI/deepvista-cli/compare/v0.2.0...v0.3.0) (2026-05-31)
49
+
50
+
51
+ ### Features
52
+
53
+ * **DV-871:** move daily-planning to server; add schedule command ([#151](https://github.com/DeepVista-AI/deepvista-cli/issues/151)) ([7c317e7](https://github.com/DeepVista-AI/deepvista-cli/commit/7c317e7d6f71763f6622354b8581ead8b10ef8ea))
54
+
55
+
56
+ ### Bug Fixes
57
+
58
+ * **types:** mark output_error as NoReturn to fix Pyright errors in schedule.py ([#153](https://github.com/DeepVista-AI/deepvista-cli/issues/153)) ([7ac8b12](https://github.com/DeepVista-AI/deepvista-cli/commit/7ac8b12f1b10a94cd6dbe8533b122a2cf8489228))
59
+
40
60
  ## [0.2.0](https://github.com/DeepVista-AI/deepvista-cli/compare/v0.1.18...v0.2.0) (2026-05-28)
41
61
 
42
62
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: deepvista-cli
3
- Version: 0.2.0
3
+ Version: 0.4.0
4
4
  Summary: CLI for DeepVista — chat, notes, skills, and memory from your terminal.
5
5
  Project-URL: Homepage, https://deepvista.ai
6
6
  Project-URL: Repository, https://github.com/DeepVista-AI/deepvista-cli
@@ -164,14 +164,52 @@ then `deepvista auth login`).
164
164
 
165
165
  ### For non-Claude-Code agents (Cursor, OpenCode, OpenClaw, …)
166
166
 
167
+ **macOS / Linux (bash):**
168
+
167
169
  ```bash
168
170
  curl -sSL https://raw.githubusercontent.com/DeepVista-AI/deepvista-cli/main/install.sh | bash
169
171
  ```
170
172
 
173
+ **Windows (PowerShell):**
174
+
175
+ ```powershell
176
+ powershell -ExecutionPolicy ByPass -c "irm https://raw.githubusercontent.com/DeepVista-AI/deepvista-cli/main/install.ps1 | iex"
177
+ ```
178
+
171
179
  The script auto-detects your package manager (`uv`, `pipx`, or `pip`) and copies the consolidated `deepvista` skill into your agent's skill directory (Cursor, OpenCode, OpenClaw, and Claude Code as a fallback). If you upgraded from an earlier release, it also sweeps out the 12 legacy `deepvista-*` skills so you don't end up with both.
172
180
 
173
181
  Prefer GitHub's CLI? `gh skill install DeepVista-AI/deepvista-cli` works too (GitHub CLI ≥ 2.90, preview).
174
182
 
183
+ #### Windows troubleshooting: `uv trampoline failed to canonicalize script path`
184
+
185
+ On Windows, running `deepvista` may fail with:
186
+
187
+ ```
188
+ error: uv trampoline failed to canonicalize script path
189
+ ```
190
+
191
+ This is a [known uv-on-Windows issue](https://github.com/astral-sh/uv/issues): the launcher (trampoline) that `uv tool install` creates can fail to resolve its own path. It is aggravated by working directories that contain **spaces** (e.g. `C:\Users\you\Claude Folder`) or are inside **OneDrive**-synced folders.
192
+
193
+ Fixes, in order of preference:
194
+
195
+ 1. **Update uv and reinstall the launcher:**
196
+
197
+ ```powershell
198
+ uv self update
199
+ uv tool install --reinstall "deepvista-cli[ui]"
200
+ ```
201
+
202
+ 2. **Run via `uv tool run` as a fallback** (bypasses the broken trampoline entirely):
203
+
204
+ ```powershell
205
+ uv tool run deepvista auth login
206
+ uv tool run deepvista notes +quick "My first note"
207
+ ```
208
+
209
+ 3. **Avoid spaces / OneDrive in your working directory.** Run from a path without spaces, e.g. `C:\dev`, rather than `C:\Users\you\Claude Folder`.
210
+
211
+ The `install.ps1` script performs this check automatically: after installing it runs `deepvista --version`, and if it hits the trampoline error it falls back to `uv tool run deepvista` and prints the guidance above. It also adds `%USERPROFILE%\.local\bin` (uv's tool bin directory) to your user PATH if it isn't already there — restart your shell afterward so new terminals pick it up.
212
+
175
213
  ### Get Started
176
214
 
177
215
  Open your agent and paste:
@@ -135,14 +135,52 @@ then `deepvista auth login`).
135
135
 
136
136
  ### For non-Claude-Code agents (Cursor, OpenCode, OpenClaw, …)
137
137
 
138
+ **macOS / Linux (bash):**
139
+
138
140
  ```bash
139
141
  curl -sSL https://raw.githubusercontent.com/DeepVista-AI/deepvista-cli/main/install.sh | bash
140
142
  ```
141
143
 
144
+ **Windows (PowerShell):**
145
+
146
+ ```powershell
147
+ powershell -ExecutionPolicy ByPass -c "irm https://raw.githubusercontent.com/DeepVista-AI/deepvista-cli/main/install.ps1 | iex"
148
+ ```
149
+
142
150
  The script auto-detects your package manager (`uv`, `pipx`, or `pip`) and copies the consolidated `deepvista` skill into your agent's skill directory (Cursor, OpenCode, OpenClaw, and Claude Code as a fallback). If you upgraded from an earlier release, it also sweeps out the 12 legacy `deepvista-*` skills so you don't end up with both.
143
151
 
144
152
  Prefer GitHub's CLI? `gh skill install DeepVista-AI/deepvista-cli` works too (GitHub CLI ≥ 2.90, preview).
145
153
 
154
+ #### Windows troubleshooting: `uv trampoline failed to canonicalize script path`
155
+
156
+ On Windows, running `deepvista` may fail with:
157
+
158
+ ```
159
+ error: uv trampoline failed to canonicalize script path
160
+ ```
161
+
162
+ This is a [known uv-on-Windows issue](https://github.com/astral-sh/uv/issues): the launcher (trampoline) that `uv tool install` creates can fail to resolve its own path. It is aggravated by working directories that contain **spaces** (e.g. `C:\Users\you\Claude Folder`) or are inside **OneDrive**-synced folders.
163
+
164
+ Fixes, in order of preference:
165
+
166
+ 1. **Update uv and reinstall the launcher:**
167
+
168
+ ```powershell
169
+ uv self update
170
+ uv tool install --reinstall "deepvista-cli[ui]"
171
+ ```
172
+
173
+ 2. **Run via `uv tool run` as a fallback** (bypasses the broken trampoline entirely):
174
+
175
+ ```powershell
176
+ uv tool run deepvista auth login
177
+ uv tool run deepvista notes +quick "My first note"
178
+ ```
179
+
180
+ 3. **Avoid spaces / OneDrive in your working directory.** Run from a path without spaces, e.g. `C:\dev`, rather than `C:\Users\you\Claude Folder`.
181
+
182
+ The `install.ps1` script performs this check automatically: after installing it runs `deepvista --version`, and if it hits the trampoline error it falls back to `uv tool run deepvista` and prints the guidance above. It also adds `%USERPROFILE%\.local\bin` (uv's tool bin directory) to your user PATH if it isn't already there — restart your shell afterward so new terminals pick it up.
183
+
146
184
  ### Get Started
147
185
 
148
186
  Open your agent and paste:
@@ -14,6 +14,7 @@ from deepvista_cli.auth.tokens import (
14
14
  remove_account,
15
15
  switch_active_account,
16
16
  )
17
+ from deepvista_cli.client.origin import detect_agent_tool
17
18
  from deepvista_cli.config import credentials_path
18
19
  from deepvista_cli.output.formatter import format_output, output_error
19
20
 
@@ -23,6 +24,37 @@ def auth_group() -> None:
23
24
  """Authenticate with DeepVista."""
24
25
 
25
26
 
27
+ def _print_next_steps() -> None:
28
+ """Print actionable next-step options after a successful login (DV-942).
29
+
30
+ Written to stderr so the JSON result on stdout stays machine-parseable.
31
+ Suggestions adapt to whoever is driving the CLI (agent vs. terminal).
32
+ """
33
+ tool, _version = detect_agent_tool()
34
+ if tool == "claude-code":
35
+ steps = [
36
+ "/refresh-skills — sync the DeepVista skill catalog",
37
+ 'say "Help me get started with DeepVista."',
38
+ 'deepvista notes +quick "<fact>" — capture your first note',
39
+ ]
40
+ elif tool == "deepvista-cli": # direct terminal usage
41
+ steps = [
42
+ 'deepvista notes +quick "My first note" — capture a note',
43
+ "deepvista skill list — browse your workflow skills",
44
+ "deepvista ui — launch the terminal UI",
45
+ "deepvista chat — talk to your DeepVista agent",
46
+ ]
47
+ else: # some other AI agent is driving us
48
+ steps = [
49
+ 'deepvista notes +quick "<fact>" — capture a note for the user',
50
+ "deepvista skill list — discover available workflow skills",
51
+ 'deepvista vistabase +search "<topic>" — recall the user\'s stored context',
52
+ ]
53
+ click.echo("\n What's next? Pick one:", err=True)
54
+ for step in steps:
55
+ click.echo(f" - {step}", err=True)
56
+
57
+
26
58
  @auth_group.command("login")
27
59
  @click.option("--code", default=None, help="One-time auth code from the browser.")
28
60
  @click.option("--dry-run", is_flag=True, default=False, help="Preview what would happen without making any changes.")
@@ -64,6 +96,7 @@ def auth_login(ctx: click.Context, code: str | None, dry_run: bool) -> None:
64
96
  }
65
97
  format_output(result, ctx.obj.output_format)
66
98
  click.echo(f" Logged in as {tokens.email or tokens.user_id}", err=True)
99
+ _print_next_steps()
67
100
 
68
101
 
69
102
  @auth_group.command("status")
@@ -0,0 +1,169 @@
1
+ """deepvista schedule — opt-in recurring runs of the daily-planning skill.
2
+
3
+ Daily planning generation lives on the DeepVista server (the
4
+ ``deepvista-daily-planning`` skill). This command lets the user *explicitly*
5
+ activate a recurring server job that generates today's planning note on a cron
6
+ schedule — nothing runs automatically until the user opts in here, which keeps
7
+ token spend under their control.
8
+
9
+ Under the hood each "activation" is a row in the server's ``scheduled_jobs``
10
+ table whose ``prompt`` asks the agent to run the daily-planning skill. The
11
+ heartbeat dispatcher drains due rows and runs the agent.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import click
17
+
18
+ from deepvista_cli.client.http import DeepVistaClient
19
+ from deepvista_cli.output.formatter import format_output, output_error
20
+
21
+ # The server-side workflow skill that generates the planning note.
22
+ DAILY_PLANNING_SKILL = "deepvista-daily-planning"
23
+ # Stable title used to find this job again (activate is idempotent on it).
24
+ JOB_TITLE = "Daily Planning"
25
+ # Cron is evaluated in UTC server-side (no per-user timezone yet — see DV-879).
26
+ DEFAULT_DAILY_CRON = "0 8 * * *"
27
+ DEFAULT_WEEKLY_CRON = "0 8 * * 1"
28
+
29
+ # Columns shown in `--format table`.
30
+ _JOB_COLUMNS = ["id", "title", "cron_schedule", "enabled", "next_run_at", "last_run_at"]
31
+
32
+
33
+ def _client(ctx: click.Context) -> DeepVistaClient:
34
+ return ctx.obj._client
35
+
36
+
37
+ def _find_daily_planning_job(ctx: click.Context) -> dict | None:
38
+ """Return the existing daily-planning job for this user, or None.
39
+
40
+ Matches on the stable ``JOB_TITLE`` so repeated activations re-use the same
41
+ row instead of stacking duplicates.
42
+ """
43
+ data = _client(ctx).get("/scheduled-jobs")
44
+ for job in data.get("jobs", []):
45
+ if job.get("title") == JOB_TITLE:
46
+ return job
47
+ return None
48
+
49
+
50
+ @click.group("schedule")
51
+ def schedule_group() -> None:
52
+ """Manage the recurring daily-planning job (opt-in)."""
53
+
54
+
55
+ @schedule_group.command("activate")
56
+ @click.option("--cron", "cron_schedule", default=None, help="5-field cron in UTC (default: daily 08:00 UTC).")
57
+ @click.option("--weekly", is_flag=True, default=False, help="Run weekly (Mon 08:00 UTC) instead of daily.")
58
+ @click.pass_context
59
+ def schedule_activate(ctx: click.Context, cron_schedule: str | None, weekly: bool) -> None:
60
+ """Activate the recurring daily-planning job.
61
+
62
+ Idempotent: if a daily-planning job already exists it is re-enabled rather
63
+ than duplicated. To change the cron of an existing job, deactivate + delete
64
+ it first, then activate again with a new --cron.
65
+ """
66
+ cadence = "weekly" if weekly else "daily"
67
+ if cron_schedule is None:
68
+ cron_schedule = DEFAULT_WEEKLY_CRON if weekly else DEFAULT_DAILY_CRON
69
+
70
+ existing = _find_daily_planning_job(ctx)
71
+ if existing is not None:
72
+ if existing.get("enabled"):
73
+ format_output(
74
+ {"status": "already_active", "job": existing},
75
+ ctx.obj.output_format,
76
+ title="Daily planning already active",
77
+ )
78
+ return
79
+ resp = _client(ctx).patch(f"/scheduled-jobs/{existing['id']}", {"enabled": True})
80
+ format_output(
81
+ {"status": "reactivated", "job": resp.get("job", resp)},
82
+ ctx.obj.output_format,
83
+ title="Daily planning reactivated",
84
+ )
85
+ return
86
+
87
+ prompt = (
88
+ f"Run the {DAILY_PLANNING_SKILL} skill to generate today's daily "
89
+ f"planning note (cadence: {cadence}). List the workflow Skills that "
90
+ f"should run today as context-card chips in the note's Planning section."
91
+ )
92
+ resp = _client(ctx).post(
93
+ "/scheduled-jobs",
94
+ {
95
+ "prompt": prompt,
96
+ "cron_schedule": cron_schedule,
97
+ "title": JOB_TITLE,
98
+ "enabled": True,
99
+ },
100
+ )
101
+ if not resp.get("success"):
102
+ output_error(1, "Failed to activate daily planning", resp.get("error", ""))
103
+ format_output(
104
+ {"status": "activated", "cadence": cadence, "job": resp.get("job", resp)},
105
+ ctx.obj.output_format,
106
+ title="Daily planning activated",
107
+ )
108
+
109
+
110
+ @schedule_group.command("deactivate")
111
+ @click.pass_context
112
+ def schedule_deactivate(ctx: click.Context) -> None:
113
+ """Disable the daily-planning job (keeps the row so it can be re-activated)."""
114
+ existing = _find_daily_planning_job(ctx)
115
+ if existing is None:
116
+ output_error(1, "No daily-planning job found", "Run: deepvista schedule activate")
117
+ if not existing.get("enabled"):
118
+ format_output(
119
+ {"status": "already_inactive", "job": existing},
120
+ ctx.obj.output_format,
121
+ title="Daily planning already inactive",
122
+ )
123
+ return
124
+ resp = _client(ctx).patch(f"/scheduled-jobs/{existing['id']}", {"enabled": False})
125
+ format_output(
126
+ {"status": "deactivated", "job": resp.get("job", resp)},
127
+ ctx.obj.output_format,
128
+ title="Daily planning deactivated",
129
+ )
130
+
131
+
132
+ @schedule_group.command("list")
133
+ @click.pass_context
134
+ def schedule_list(ctx: click.Context) -> None:
135
+ """List the caller's scheduled jobs (read-only)."""
136
+ data = _client(ctx).get("/scheduled-jobs")
137
+ jobs = data.get("jobs", [])
138
+ format_output(
139
+ {"jobs": jobs, "count": len(jobs)},
140
+ ctx.obj.output_format,
141
+ columns=_JOB_COLUMNS,
142
+ title="Scheduled Jobs",
143
+ )
144
+
145
+
146
+ @schedule_group.command("delete")
147
+ @click.argument("job_id", required=False)
148
+ @click.pass_context
149
+ def schedule_delete(ctx: click.Context, job_id: str | None) -> None:
150
+ """Delete a scheduled job permanently.
151
+
152
+ With no JOB_ID, deletes the daily-planning job. Use this (then ``activate``)
153
+ to change a job's cron, which PATCH can't edit. Pass ``--dry-run`` on the
154
+ root command to preview without deleting.
155
+ """
156
+ if job_id is None:
157
+ existing = _find_daily_planning_job(ctx)
158
+ if existing is None:
159
+ output_error(1, "No daily-planning job found", "Pass a JOB_ID, or run: deepvista schedule list")
160
+ job_id = existing["id"]
161
+
162
+ resp = _client(ctx).delete(f"/scheduled-jobs/{job_id}")
163
+ if not resp.get("success"):
164
+ output_error(1, "Failed to delete scheduled job", resp.get("error", ""))
165
+ format_output(
166
+ {"status": "deleted", "job_id": job_id},
167
+ ctx.obj.output_format,
168
+ title="Scheduled job deleted",
169
+ )
@@ -31,6 +31,7 @@ from deepvista_cli.commands.config import config_group
31
31
  from deepvista_cli.commands.lint import lint_command
32
32
  from deepvista_cli.commands.memory import vistabase_group
33
33
  from deepvista_cli.commands.notes import notes_group
34
+ from deepvista_cli.commands.schedule import schedule_group
34
35
  from deepvista_cli.commands.session import session_group
35
36
  from deepvista_cli.commands.skill import skill_group
36
37
  from deepvista_cli.commands.upgrade import upgrade_command
@@ -87,6 +88,8 @@ for _name, _cmd in card_group.commands.items():
87
88
  cli.add_command(vistabase_group, name="memory")
88
89
  # Agent orchestration
89
90
  cli.add_command(agents_group)
91
+ # Opt-in recurring daily-planning job
92
+ cli.add_command(schedule_group)
90
93
  # Agent session transcripts (DV-742) — `init` / `tick` / `finalize`
91
94
  cli.add_command(session_group)
92
95
  # Supporting commands
@@ -8,7 +8,7 @@ from __future__ import annotations
8
8
 
9
9
  import json
10
10
  import sys
11
- from typing import Any
11
+ from typing import Any, NoReturn
12
12
 
13
13
  import click
14
14
 
@@ -170,7 +170,7 @@ def output_table(data: Any, columns: list[str] | None = None, title: str | None
170
170
  output_json(data)
171
171
 
172
172
 
173
- def output_error(code: int, message: str, detail: str = "") -> None:
173
+ def output_error(code: int, message: str, detail: str = "") -> NoReturn:
174
174
  """Write structured error to stderr and exit."""
175
175
  err = {"error": {"code": code, "message": message}}
176
176
  if detail: