taskops-cli 0.4.1__tar.gz → 0.4.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.
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ARCHITECTURE.md +26 -1
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/CHANGELOG.md +34 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/PKG-INFO +1 -1
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/_version.py +1 -1
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/serving.py +9 -3
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/types.py +12 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/upstream.py +59 -7
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/schema.py +1 -1
- taskops_cli-0.4.2/src/taskops/ui/app.js +61 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_facts.py +4 -4
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/activity.py +7 -1
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_topology.py +90 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_verbs.py +29 -1
- taskops_cli-0.4.2/ui/smoke/sections/all-chapters-scope.tsx +99 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/App.tsx +24 -2
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/chrome/MilestonePicker.tsx +28 -9
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/useBoard.ts +6 -3
- taskops_cli-0.4.1/src/taskops/ui/app.js +0 -61
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.claude/settings.json +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.gitattributes +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.github/workflows/ci.yml +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.github/workflows/workflow.yml +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.gitignore +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.mcp.json +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/board/cache.sqlite +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/board/cache.sqlite-shm +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/board/cache.sqlite-wal +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/board/events.jsonl +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/board/live.sqlite +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/board/live.sqlite-shm +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/board/live.sqlite-wal +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/board.json +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/reports/lifecycle-chapter.html +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/reports/reports-chapter.html +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/.taskops/ui.lock +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/CLAUDE.md +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/README.md +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/pyproject.toml +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/__init__.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/_clock.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/_errors.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/_ids.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/_json.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/_locate.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/_wire.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/board.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/__init__.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/__main__.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/admin.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/claude.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/commands.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/enrol.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/github.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/grants.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/hooks.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/main.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/operate.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/paging.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/parser.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/pull.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/push.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/remote.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/rm.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/team.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/watch.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/window.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/cli/wording.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/__init__.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/actors.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/challenge.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/chapters.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/event.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/forge.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/graph.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/holding.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/hours.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/kinds.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/machine.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/mentions.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/replay.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/reports.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/review.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/scope.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/core/seams.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/__init__.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/bind.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/catchup.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/claudefiles.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/diff.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/install.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/landing.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/patch.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/remote.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/run.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/sig.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/trailer.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/gitwork/trees.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/__init__.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/admin.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/auth.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/feed.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/gitdoor.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/grants.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/handler.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/ingest.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/login.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/members.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/mounts.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/removal.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/rpc.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/scoped.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/server.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/static.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/http/watcher.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/identity.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/__init__.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/__main__.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/activity.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/before.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/boards.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/boardview.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/brief.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/chapter.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/dossier.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/fields.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/gitmoves.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/hello.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/integrate.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/orders.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/render.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/server.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/thread.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/mcp/tools.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/py.typed +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/session.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/__init__.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/cache.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/creds.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/handover.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/live.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/log.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/pubkeys.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/reviews.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/server.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/store/stores.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/ui/index.html +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/ui/style.css +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/__init__.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_args.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_cards.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_chapter.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_context.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_mentions.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_rows.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_stories.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_waiting.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/_windows.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/assign.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/card.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/events.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/filed.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/plan.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/project.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/pulse.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/record.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/report.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/review.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/take.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/src/taskops/verbs/update.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/__init__.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/conftest.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_architecture.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_claude.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_core.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_git.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_mcp.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_report.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_store.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_ui.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/test_window.py +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/tests/ui_harness.js +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/build.mjs +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/index.html +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/package-lock.json +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/package.json +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/fixture.json +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/main.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/run.mjs +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/actors-window-filter.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/actors.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/board-columns.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/board-flow-view.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/board-motion-and-avatars.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/chapter-completion-derivation.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/chapter-landed.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/chapter-story-grid.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/chapters-open.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/comment-box.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/comment-toasts-model.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/comment-toasts-stack.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/diff-page.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/dossier.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/event-stream.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/feed-reconnect.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/forge-links.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/forge-opens-the-board.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/git-diff.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/monitor-panes.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/overlay-escape.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/report-markdown.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/report-sandbox.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/reviewing-skew.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/section.ts +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/swarm-topology.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/thread-closing-note.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections/worktrees-index.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/smoke/sections.mjs +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/client.ts +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/completed.ts +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/actors/Daysheet.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/actors/DevPanel.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/board/CardTile.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/board/Column.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/board/ViewToggle.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/board/flip.ts +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/board/useFlip.ts +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/card/CommentBox.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/card/Drawer.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/card/Patch.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/card/Sections.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/card/Thread.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/card/split.ts +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/card/tokens.ts +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/chrome/AvatarStack.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/chrome/Header.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/chrome/KpiRail.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/chrome/TabNav.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/flow/FlowView.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/flow/layout.ts +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/Chapter.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/DependencyChain.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/EditSurface.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/EventStream.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/LeaseHealth.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/LiveLeases.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/Mentions.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/Pane.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/Swarm.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/Throughput.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/monitor/panels.ts +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/reports/ReportFrame.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/shared/Avatar.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/shared/Markdown.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/shared/Overlay.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/shared/Segmented.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/shared/overlayStack.ts +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/story/ChapterStory.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/story/stats.ts +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/toasts/ToastStack.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/toasts/model.ts +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/components/toasts/useToasts.ts +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/format.ts +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/hoursWindow.ts +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/links.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/main.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/markdown.ts +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/pages/Actors.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/pages/Board.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/pages/Monitor.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/pages/Reports.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/pages/WorktreeDiff.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/pages/Worktrees.tsx +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/theme/theme.ts +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/theme/tokens.css +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/types.ts +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/src/useEvents.ts +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/styles/index.css +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/ui/tsconfig.json +0 -0
- {taskops_cli-0.4.1 → taskops_cli-0.4.2}/uv.lock +0 -0
|
@@ -1361,7 +1361,17 @@ files that answer it:
|
|
|
1361
1361
|
* **the card dossier drawer**, opening over Monitor and Board through `App`'s
|
|
1362
1362
|
`openCard` — it renders the acceptance criteria no v1 screen ever drew, and
|
|
1363
1363
|
carries the UI's ONE write, the comment box with its mention picker
|
|
1364
|
-
. The header's milestone picker scopes every tab at once
|
|
1364
|
+
. The header's milestone picker scopes every tab at once, and **"all
|
|
1365
|
+
chapters" is an ARGUMENT** — `milestone=*` (`core/types.py::EVERYTHING`), not
|
|
1366
|
+
the absence of one. It sent nothing for a wave, which the server reads as
|
|
1367
|
+
"resolve the scope yourself" and resolves to the single open chapter: the page
|
|
1368
|
+
arrived narrowed to that chapter with the ✓ sitting on "all chapters" the whole
|
|
1369
|
+
time, and clicking the option changed no argument (2026-08-18). Absence still
|
|
1370
|
+
means exactly that — it is the dashboard's OPENING state, and `App::scopeOf`
|
|
1371
|
+
resolves it against the chapter the answer came back scoped to, so the ✓ marks
|
|
1372
|
+
the scope the page is actually drawn at. `verbs/activity.py` refuses `*` in the
|
|
1373
|
+
words of its own doors: a story is one chapter's cards by definition.
|
|
1374
|
+
`ui/smoke/sections/all-chapters-scope.tsx`.
|
|
1365
1375
|
|
|
1366
1376
|
* **the comment toasts**, a stack bottom-right over every tab
|
|
1367
1377
|
(`ui/src/components/toasts/`), because the UI's one write was invisible to
|
|
@@ -1659,6 +1669,21 @@ local process**; the browser holds only the token `taskops ui` mints into
|
|
|
1659
1669
|
`ui.json`, which is local and revocable. `tests/test_topology.py` searches every
|
|
1660
1670
|
served byte, and their headers, for the team token.
|
|
1661
1671
|
|
|
1672
|
+
**A SESSION RUNS OUT AND THE WINDOW DOES NOT** (2026-08-18). A host session is
|
|
1673
|
+
twelve hours (`http/login.py::SESSION_TTL`), and a window is a thing you leave
|
|
1674
|
+
open: `_upstream` read the token lying in `remote.json` and `Upstream` held it
|
|
1675
|
+
for the life of the process, so the morning after, every read answered "that
|
|
1676
|
+
credential expired" — and the habit that taught was `taskops join`, a human
|
|
1677
|
+
re-pasting a credential the machine can mint itself, which is the exact thing
|
|
1678
|
+
the session chapter exists to end. Two halves, both fixed: the command opens
|
|
1679
|
+
with `session.fresh`, like every other command has since that chapter landed,
|
|
1680
|
+
and the window is handed `session.refresher` so it can sign in AGAIN mid-flight
|
|
1681
|
+
— `board.py::RemoteBoard.call`'s retry, narrow for the same reason (the server
|
|
1682
|
+
said `EXPIRED`, this process holds a key). A window with no way back in — a
|
|
1683
|
+
standing bearer, a public viewer — is unchanged: `refresh` is None and nobody
|
|
1684
|
+
replaces a token behind its owner's back (`session.py`). `Upstream` still knows
|
|
1685
|
+
nothing about keys or files: it is handed a callable.
|
|
1686
|
+
|
|
1662
1687
|
**A refusal from the remote arrives in the SERVER's own words**, status and all:
|
|
1663
1688
|
`upstream.py` returns `(status, bytes)` and re-wraps only a body that is not an
|
|
1664
1689
|
envelope. A board that says "held by agent:berna/w1" with a 409 must not reach
|
|
@@ -3,6 +3,40 @@
|
|
|
3
3
|
The source of truth for release notes — GitHub Releases are extracted from
|
|
4
4
|
here, never written twice.
|
|
5
5
|
|
|
6
|
+
## 0.4.2 — the filter that filtered nothing, and the window that stopped signing in
|
|
7
|
+
|
|
8
|
+
- **"All chapters" is an ARGUMENT now, and it really shows every chapter.** The
|
|
9
|
+
option sent no `milestone=` at all — and an absent milestone never meant "the
|
|
10
|
+
whole board": it means "server, resolve the scope yourself", which resolves to
|
|
11
|
+
the single open chapter when there is exactly one. So on a board with one
|
|
12
|
+
chapter open and seven landed (Berna's, reported 2026-08-18) the page came back
|
|
13
|
+
narrowed to that chapter, the menu drew its ✓ beside a scope that had never
|
|
14
|
+
been in effect, and clicking the option changed no argument at all — the only
|
|
15
|
+
way to see other work was to name every chapter in turn. Board-wide is now
|
|
16
|
+
asked for by name (`milestone=*`, `core/types.py::EVERYTHING`). Absence still
|
|
17
|
+
means exactly what it meant, so no agent and no existing caller sees any
|
|
18
|
+
change: it is the dashboard's OPENING state, and the ✓ now falls on the chapter
|
|
19
|
+
the server actually resolved, so the pill can no longer claim a scope the page
|
|
20
|
+
is not drawn at. `taskops_activity` refuses `*` in the words of its own doors —
|
|
21
|
+
a story is one chapter's cards by definition — where it used to answer
|
|
22
|
+
`milestone * does not exist`, a lookup's answer to a question that was never a
|
|
23
|
+
lookup.
|
|
24
|
+
- **A window whose session ran out signs in again instead of teaching `taskops
|
|
25
|
+
join`.** A host session is twelve hours and a window is a thing you leave open.
|
|
26
|
+
Two halves of one hole, both on the `taskops ui` path and nowhere else: the
|
|
27
|
+
command read the token lying in `remote.json` instead of `session.fresh` —
|
|
28
|
+
which every other command has taken since sessions landed — so a window opened
|
|
29
|
+
on yesterday's session was dead on arrival; and the forwarder held the token it
|
|
30
|
+
was built with for the life of the process, so a window that was open when the
|
|
31
|
+
session expired answered "that credential expired" to every read. Neither path
|
|
32
|
+
could come back, so the habit it taught was re-joining: a human pasting a
|
|
33
|
+
credential the machine can mint itself from the key it already has. The retry
|
|
34
|
+
is `RemoteBoard.call`'s and just as narrow — the SERVER said EXPIRED, and this
|
|
35
|
+
process holds a key — and it is a callable passed in, so the forwarder still
|
|
36
|
+
knows nothing about keys, files or logins. A window with no way back in (a
|
|
37
|
+
standing bearer token, a public viewer) is unchanged: nobody replaces a token
|
|
38
|
+
behind its owner's back.
|
|
39
|
+
|
|
6
40
|
## 0.4.1 — the board comes alive: the forge enrols the team, hours stop lying, comments become visible
|
|
7
41
|
|
|
8
42
|
- **A comment now SAYS so, on every open dashboard.** The UI's one write was
|
|
@@ -25,7 +25,7 @@ if TYPE_CHECKING: # a NAME for the annotation, never an import at start-up
|
|
|
25
25
|
from ..http.upstream import Upstream
|
|
26
26
|
|
|
27
27
|
from . import window
|
|
28
|
-
from .. import _clock
|
|
28
|
+
from .. import _clock, session
|
|
29
29
|
from .._json import as_object
|
|
30
30
|
from ..board import DIR, find_root, is_project, read_config
|
|
31
31
|
from .._errors import TaskopsError
|
|
@@ -133,7 +133,13 @@ def _upstream(root: Path) -> Upstream | None:
|
|
|
133
133
|
url = str(config.get("url", ""))
|
|
134
134
|
if not url:
|
|
135
135
|
return None
|
|
136
|
-
|
|
136
|
+
# The SESSION, refreshed here if it has run out and refreshable again from
|
|
137
|
+
# inside the window if it runs out while somebody is reading (2026-08-18,
|
|
138
|
+
# `http/upstream.py`): a host session is twelve hours and this window is
|
|
139
|
+
# meant to be left open. `fresh` falls back to the cached token whenever it
|
|
140
|
+
# cannot sign in, and `refresher` is None for a standing bearer — neither
|
|
141
|
+
# replaces a token nobody may replace, which is `session.py`'s own rule.
|
|
142
|
+
token = str(session.fresh(root, config, _clock.now()))
|
|
137
143
|
if not token and config.get("readonly"):
|
|
138
144
|
# A viewer's window (`commands.py::_watch`): an Upstream with no bearer.
|
|
139
145
|
# `Mounts.public` reads exactly this — no token means this window is
|
|
@@ -145,7 +151,7 @@ def _upstream(root: Path) -> Upstream | None:
|
|
|
145
151
|
f"{root / DIR}/board.json points at {url} but there is no credential in "
|
|
146
152
|
"remote.json — run: taskops join <url with ?token= or ?invite=>"
|
|
147
153
|
)
|
|
148
|
-
return Remote(url, token)
|
|
154
|
+
return Remote(url, token, refresh=session.refresher(root, config))
|
|
149
155
|
|
|
150
156
|
|
|
151
157
|
def _open(url: str) -> int:
|
|
@@ -161,6 +161,18 @@ MILESTONE_STATUSES = ("open", "done", "dropped")
|
|
|
161
161
|
CLOSED = ("done", "dropped")
|
|
162
162
|
LEASE_TTL = 900.0 # 15 min; every MCP call renews it
|
|
163
163
|
PROJECT = "project" # the `task` of board-level events
|
|
164
|
+
EVERYTHING = "*"
|
|
165
|
+
"""`milestone=*`: the WHOLE board, said out loud — no chapter in scope.
|
|
166
|
+
|
|
167
|
+
An ABSENT milestone means "resolve it yourself" (`verbs/_facts.py::in_scope`),
|
|
168
|
+
which with exactly one open chapter IS that chapter. Right for an agent, and it
|
|
169
|
+
left the dashboard's "all chapters" unreachable: it sent no `milestone=`, the
|
|
170
|
+
board came back narrowed anyway, and the picker drew a ✓ beside a scope that had
|
|
171
|
+
never been in effect and that clicking could not change (2026-08-18). Absent and
|
|
172
|
+
"all" are two questions; they now have two arguments, and nothing that omitted
|
|
173
|
+
it sees any change. `verbs/activity.py` is where the two genuinely part — it
|
|
174
|
+
REQUIRES a chapter, and answered `milestone * does not exist`, a lookup's answer
|
|
175
|
+
to a question that was never a lookup."""
|
|
164
176
|
|
|
165
177
|
|
|
166
178
|
# Fields of a Card that `edited` may target. Anything else is a BadRequest, so
|
|
@@ -28,6 +28,20 @@ it, because then the reader is debugging this host instead of reading the board.
|
|
|
28
28
|
Only a server that could not be reached at all is spoken for here, and it says
|
|
29
29
|
so in the same envelope shape everything else uses.
|
|
30
30
|
|
|
31
|
+
**A SESSION RUNS OUT, and the window must not** (2026-08-18). A host session is
|
|
32
|
+
`login.py::SESSION_TTL` — twelve hours — and this class held the token it was
|
|
33
|
+
built with for the life of the process, so a window left open overnight woke up
|
|
34
|
+
answering "that credential expired" to every read, and the habit it taught was
|
|
35
|
+
`taskops join`: re-pasting a credential the machine can mint by itself, which is
|
|
36
|
+
exactly what the session chapter exists to end. The retry here is
|
|
37
|
+
`board.py::RemoteBoard.call`'s, narrow for the same reason — the SERVER said
|
|
38
|
+
EXPIRED, and this process was handed a way to sign in — and it is a callable
|
|
39
|
+
passed in rather than a key read here, so this module still knows nothing about
|
|
40
|
+
keys, files or logins (`cli/serving.py` builds it out of `session.refresher`).
|
|
41
|
+
A window with no way to refresh (a standing bearer token, a public viewer) is
|
|
42
|
+
unchanged: `refresh` is None and the refusal travels to the page as it always
|
|
43
|
+
did.
|
|
44
|
+
|
|
31
45
|
**The feed is a poll, not a relay** (`feed.py`: a message is a SIGNAL, not a
|
|
32
46
|
payload). `mounts.py` asks `head()` on an interval while somebody is connected
|
|
33
47
|
and pokes the page when the number moves; the page then refetches through the
|
|
@@ -39,10 +53,12 @@ something the board did not say.
|
|
|
39
53
|
from __future__ import annotations
|
|
40
54
|
|
|
41
55
|
import json
|
|
56
|
+
from typing import Callable
|
|
42
57
|
from urllib.error import URLError, HTTPError
|
|
43
58
|
from urllib.request import Request, urlopen
|
|
44
59
|
|
|
45
60
|
from .._json import as_object
|
|
61
|
+
from ..store.creds import EXPIRED
|
|
46
62
|
|
|
47
63
|
TIMEOUT = 20.0
|
|
48
64
|
"""Seconds to wait on the remote for one /rpc. The same budget `board.py` gives
|
|
@@ -53,16 +69,38 @@ waiting on, not a background job."""
|
|
|
53
69
|
class Upstream:
|
|
54
70
|
"""One remote board, addressed by url, spoken to with one credential."""
|
|
55
71
|
|
|
56
|
-
def __init__(
|
|
72
|
+
def __init__(
|
|
73
|
+
self,
|
|
74
|
+
url: str,
|
|
75
|
+
token: str,
|
|
76
|
+
timeout: float = TIMEOUT,
|
|
77
|
+
refresh: Callable[[], str] | None = None,
|
|
78
|
+
) -> None:
|
|
57
79
|
self.url = url.rstrip("/")
|
|
58
80
|
self.token = token
|
|
59
81
|
self.timeout = timeout
|
|
82
|
+
# How to mint ANOTHER session when this one runs out, or None when
|
|
83
|
+
# nobody may replace this token behind its owner's back — the docstring
|
|
84
|
+
# above argues both halves.
|
|
85
|
+
self.refresh = refresh
|
|
60
86
|
|
|
61
87
|
def rpc(self, body: bytes) -> tuple[int, bytes]:
|
|
62
|
-
"""(status, bytes) — the remote's own answer, untouched
|
|
88
|
+
"""(status, bytes) — the remote's own answer, untouched, with ONE retry
|
|
89
|
+
for the one case a retry can fix: the session ran out and this window
|
|
90
|
+
knows how to sign in again.
|
|
63
91
|
|
|
64
92
|
Returns rather than raises: this sits under an HTTP handler whose job is
|
|
65
93
|
to hand the page what the board said, and a refusal IS an answer."""
|
|
94
|
+
status, answer = self._post(body)
|
|
95
|
+
if self.refresh is None or not _ran_out(answer):
|
|
96
|
+
return status, answer
|
|
97
|
+
try:
|
|
98
|
+
self.token = self.refresh()
|
|
99
|
+
except Exception: # noqa: BLE001 — a failed refresh is the board's refusal, unchanged
|
|
100
|
+
return status, answer
|
|
101
|
+
return self._post(body)
|
|
102
|
+
|
|
103
|
+
def _post(self, body: bytes) -> tuple[int, bytes]:
|
|
66
104
|
headers = {"Content-Type": "application/json"}
|
|
67
105
|
if self.token:
|
|
68
106
|
# No token is the VIEWER's window onto a public board: sending an
|
|
@@ -94,14 +132,28 @@ class Upstream:
|
|
|
94
132
|
return seq_of(answer) if status == 200 else 0
|
|
95
133
|
|
|
96
134
|
|
|
135
|
+
def _ran_out(answer: bytes) -> bool:
|
|
136
|
+
"""Is this the board saying the SESSION expired — not any other refusal?
|
|
137
|
+
|
|
138
|
+
`store/creds.py::EXPIRED` is the named sentence `board.py` matches on for
|
|
139
|
+
the same reason: the words after it are a human's instruction and drift."""
|
|
140
|
+
body = as_object(_parsed(answer))
|
|
141
|
+
if body.get("ok"):
|
|
142
|
+
return False
|
|
143
|
+
return EXPIRED in str(as_object(body.get("error")).get("message", ""))
|
|
144
|
+
|
|
145
|
+
|
|
146
|
+
def _parsed(answer: bytes) -> object:
|
|
147
|
+
try:
|
|
148
|
+
return json.loads(answer)
|
|
149
|
+
except ValueError:
|
|
150
|
+
return None
|
|
151
|
+
|
|
152
|
+
|
|
97
153
|
def seq_of(answer: bytes) -> int:
|
|
98
154
|
"""The `seq` out of an envelope, or 0. Never raises: a body that is not an
|
|
99
155
|
envelope means the poke is skipped, which costs one tick."""
|
|
100
|
-
|
|
101
|
-
parsed: object = json.loads(answer)
|
|
102
|
-
except ValueError:
|
|
103
|
-
return 0
|
|
104
|
-
seq: object = as_object(parsed).get("seq", 0)
|
|
156
|
+
seq: object = as_object(_parsed(answer)).get("seq", 0)
|
|
105
157
|
return seq if isinstance(seq, int) else 0
|
|
106
158
|
|
|
107
159
|
|
|
@@ -29,7 +29,7 @@ SCHEMAS: dict[str, dict[str, Any]] = {
|
|
|
29
29
|
**DEV_SCHEMAS,
|
|
30
30
|
"taskops_board": _object(
|
|
31
31
|
{
|
|
32
|
-
"milestone": _text("ms-…
|
|
32
|
+
"milestone": _text("ms-… one chapter, * the whole board; default: the open one"),
|
|
33
33
|
"window": _text(
|
|
34
34
|
'hours over "7d" (the last N calendar days), "month" (this month so '
|
|
35
35
|
'far), "2026-07" (that calendar month) or "total" (the whole log)'
|