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.
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/DEVLOG.md +23 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/PKG-INFO +1 -1
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/pyproject.toml +1 -1
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/browser.py +7 -2
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/static/canvas.js +35 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/static/console.js +51 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/static/editpanel.js +10 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/.github/workflows/publish-to-pypi.yml +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/.gitignore +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/.readthedocs.yaml +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/DEVPLAN.md +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/README.md +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/docs/conf.py +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/docs/index.rst +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/docs/requirements.txt +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/scripts/bump_version.py +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/cli.py +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/config.py +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/help_menu.py +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/json_dir_database.py +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/models.py +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/services/ai.py +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/services/pomodoro.py +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/services/repair_database.py +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/services/user_setup.py +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/static/index.html +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/static/style.css +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/taskai/views.py +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/test/test_cli.py +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/test/test_execution.py +0 -0
- {taskai_cli-1.5.2 → taskai_cli-1.5.4}/test/test_json_dir_database.py +0 -0
- {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,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
|
-
|
|
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("
|
|
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
|
|
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
|
|
File without changes
|