taskai-cli 1.5.2__tar.gz → 1.5.4__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 (32) hide show
  1. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/DEVLOG.md +23 -0
  2. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/PKG-INFO +1 -1
  3. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/pyproject.toml +1 -1
  4. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/browser.py +7 -2
  5. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/static/canvas.js +35 -0
  6. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/static/console.js +51 -0
  7. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/static/editpanel.js +10 -0
  8. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/.github/workflows/publish-to-pypi.yml +0 -0
  9. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/.gitignore +0 -0
  10. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/.readthedocs.yaml +0 -0
  11. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/DEVPLAN.md +0 -0
  12. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/README.md +0 -0
  13. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/docs/conf.py +0 -0
  14. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/docs/index.rst +0 -0
  15. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/docs/requirements.txt +0 -0
  16. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/scripts/bump_version.py +0 -0
  17. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/cli.py +0 -0
  18. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/config.py +0 -0
  19. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/help_menu.py +0 -0
  20. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/json_dir_database.py +0 -0
  21. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/models.py +0 -0
  22. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/services/ai.py +0 -0
  23. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/services/pomodoro.py +0 -0
  24. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/services/repair_database.py +0 -0
  25. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/services/user_setup.py +0 -0
  26. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/static/index.html +0 -0
  27. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/static/style.css +0 -0
  28. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/views.py +0 -0
  29. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/test/test_cli.py +0 -0
  30. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/test/test_execution.py +0 -0
  31. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/test/test_json_dir_database.py +0 -0
  32. {taskai_cli-1.5.2 → taskai_cli-1.5.4}/test/test_view.py +0 -0
@@ -1,3 +1,26 @@
1
+ # 8-19
2
+
3
+ Fixed a typo in `browser.py` that caused uvicorn to fail with "Could not import
4
+ module 'taskai.browser'" when launching on a new machine: `os.path.dirname(__file)`
5
+ should have been `__file__` (double underscores). The `NameError` was raised at
6
+ module import time before FastAPI could even get the app object.
7
+
8
+ Added three console QoL commands, handled entirely client-side (no server round-trip):
9
+
10
+ - `zoom in` / `zoom out` — zoom ×1.5/÷1.5 around the canvas center
11
+ - `pan left` / `pan right` / `pan up` / `pan down` — shift viewport 250px per step
12
+
13
+ Implemented as `easeView` / `canvasZoom` / `canvasPan` helpers in `canvas.js`
14
+ (same ease-out cubic as `focusOnNode`), with a `CLIENT_COMMANDS` dispatch map in
15
+ `console.js` that intercepts matching input before it reaches the fetch path.
16
+
17
+ Added `edit <id|name>` console command: resolves the target via the existing
18
+ `show` server path (reuses `_find_item_by_identifier`, so fnmatch patterns work),
19
+ then selects the node, focuses/zooms the canvas to it, and expands the edit panel.
20
+ Added `openEditPanel()` to `editpanel.js` for this purpose.
21
+
22
+ ---
23
+
1
24
  # 8-18
2
25
 
3
26
  Continued the browser/`canvas.js` work in a later session (DEVLOG is a
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: taskai-cli
3
- Version: 1.5.2
3
+ Version: 1.5.4
4
4
  Author-email: Alex Paskal <alexcpaskal@gmail.com>
5
5
  Requires-Python: >=3.12
6
6
  Requires-Dist: fastapi>=0.141.1
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "taskai-cli"
3
- version = "1.5.2"
3
+ version = "1.5.4"
4
4
  description = ""
5
5
  authors = [{name = "Alex Paskal", email = "alexcpaskal@gmail.com"}]
6
6
  readme = "README.md"
@@ -1,5 +1,6 @@
1
1
  import contextlib
2
2
  import io
3
+ import os
3
4
 
4
5
  from fastapi import FastAPI
5
6
  from fastapi.responses import FileResponse
@@ -11,11 +12,15 @@ from taskai.cli import Controller, _parse_arg_string, _parse_remaining, db, exec
11
12
 
12
13
  app = FastAPI()
13
14
 
14
- app.mount("/static", StaticFiles(directory="taskai/static"), name="static")
15
+ # get baspath to static
16
+ static_abs_dir = os.path.join(os.path.dirname(__file__), "static")
17
+
18
+
19
+ app.mount("/static", StaticFiles(directory=static_abs_dir), name="static")
15
20
 
16
21
  @app.get("/", response_class=FileResponse)
17
22
  def index():
18
- return FileResponse("taskai/static/index.html")
23
+ return FileResponse(os.path.join(static_abs_dir, "index.html"))
19
24
 
20
25
 
21
26
  def _full_tree():
@@ -268,6 +268,41 @@ function focusOnNode(node, scale = STYLE.zoom.focusScale, duration = STYLE.zoom.
268
268
  }
269
269
 
270
270
 
271
+ // animates view to target offset/scale using the same ease-out cubic as focusOnNode
272
+ function easeView(targetOffsetX, targetOffsetY, targetScale, duration = STYLE.zoom.focusDurationMs) {
273
+ const startOffsetX = view.offsetX;
274
+ const startOffsetY = view.offsetY;
275
+ const startScale = view.scale;
276
+ const startTime = performance.now();
277
+
278
+ function step(now) {
279
+ const t = Math.min(1, (now - startTime) / duration);
280
+ const eased = 1 - Math.pow(1 - t, 3);
281
+
282
+ view.scale = startScale + (targetScale - startScale) * eased;
283
+ view.offsetX = startOffsetX + (targetOffsetX - startOffsetX) * eased;
284
+ view.offsetY = startOffsetY + (targetOffsetY - startOffsetY) * eased;
285
+
286
+ draw();
287
+ if (t < 1) requestAnimationFrame(step);
288
+ }
289
+ requestAnimationFrame(step);
290
+ }
291
+
292
+ // zoom around the canvas center by `factor` (e.g. 1.5 = in, 1/1.5 = out)
293
+ function canvasZoom(factor) {
294
+ const cx = canvas.width / 2;
295
+ const cy = canvas.height / 2;
296
+ const worldCenter = screenToWorld(cx, cy);
297
+ const newScale = Math.min(STYLE.zoom.max, Math.max(STYLE.zoom.min, view.scale * factor));
298
+ easeView(cx - worldCenter.x * newScale, cy - worldCenter.y * newScale, newScale);
299
+ }
300
+
301
+ // pan by dx/dy screen pixels (positive dx = camera moves left, revealing content to the right)
302
+ function canvasPan(dx, dy) {
303
+ easeView(view.offsetX + dx, view.offsetY + dy, view.scale);
304
+ }
305
+
271
306
  // trims text, always appending an ellipsis, until "text…" fits maxWidth
272
307
  function truncateWithEllipsis(ctx, text, maxWidth) {
273
308
  let truncated = text;
@@ -20,6 +20,18 @@ function appendLine(text) {
20
20
  consoleScrollback.scrollTop = consoleScrollback.scrollHeight;
21
21
  }
22
22
 
23
+ const PAN_AMOUNT = 250; // screen pixels per pan command
24
+ const ZOOM_FACTOR = 1.5;
25
+
26
+ const CLIENT_COMMANDS = {
27
+ "zoom in": () => canvasZoom(ZOOM_FACTOR),
28
+ "zoom out": () => canvasZoom(1 / ZOOM_FACTOR),
29
+ "pan left": () => canvasPan(PAN_AMOUNT, 0),
30
+ "pan right": () => canvasPan(-PAN_AMOUNT, 0),
31
+ "pan up": () => canvasPan(0, PAN_AMOUNT),
32
+ "pan down": () => canvasPan(0, -PAN_AMOUNT),
33
+ };
34
+
23
35
  consoleInput.addEventListener("keydown", async (e) => {
24
36
  if (e.key !== "Enter") return;
25
37
 
@@ -29,6 +41,45 @@ consoleInput.addEventListener("keydown", async (e) => {
29
41
  appendLine("> " + command);
30
42
  consoleInput.value = "";
31
43
 
44
+ const clientHandler = CLIENT_COMMANDS[command.toLowerCase()];
45
+ if (clientHandler) {
46
+ clientHandler();
47
+ return;
48
+ }
49
+
50
+ // `edit <id|name>` — resolve via show, then select + focus + open panel
51
+ const editMatch = command.match(/^edit\s+(.+)$/i);
52
+ if (editMatch) {
53
+ const target = editMatch[1].trim();
54
+ let data;
55
+ try {
56
+ const res = await fetch("/api/command", {
57
+ method: "POST",
58
+ headers: { "Content-Type": "application/json" },
59
+ body: JSON.stringify({ input: `show ${target}` }),
60
+ });
61
+ data = await res.json();
62
+ } catch (err) {
63
+ appendLine("Error: " + err.message);
64
+ return;
65
+ }
66
+
67
+ applyTree(data.tree);
68
+
69
+ if (data.focus) {
70
+ const node = nodes.find(n => n.id === String(data.focus));
71
+ if (node) {
72
+ selectedNode = node;
73
+ if (typeof onNodeSelected === "function") onNodeSelected(latestItemsById[node.id]);
74
+ focusOnNode(node);
75
+ if (typeof openEditPanel === "function") openEditPanel();
76
+ }
77
+ } else {
78
+ appendLine(data.output || `No item found matching '${target}'`);
79
+ }
80
+ return;
81
+ }
82
+
32
83
  let data;
33
84
  try {
34
85
  const res = await fetch("/api/command", {
@@ -168,4 +168,14 @@ function onNodeSelected(item) {
168
168
  renderEditForm(item);
169
169
  }
170
170
 
171
+ // expands the edit panel if it isn't already — called by the console's
172
+ // `edit` command so the panel opens without the user having to click the toggle
173
+ function openEditPanel() {
174
+ if (!editPanel.classList.contains("expanded")) {
175
+ editPanel.classList.add("expanded");
176
+ editToggle.setAttribute("aria-expanded", "true");
177
+ setRightPanelWidth(EDIT_PANEL_EXPANDED_WIDTH);
178
+ }
179
+ }
180
+
171
181
  renderEditForm(null);
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes