taskuary 0.3.4.2__tar.gz → 0.3.4.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.
- {taskuary-0.3.4.2/taskuary.egg-info → taskuary-0.3.4.4}/PKG-INFO +8 -3
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/README.md +7 -2
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/pyproject.toml +1 -1
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/agents.py +102 -9
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/cliinstall.py +93 -8
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/clis.py +20 -3
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/clisetup.py +7 -3
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/concierge.py +30 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/funnel.py +39 -2
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/general.py +63 -8
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/guard.py +1 -1
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/processing_unread.py +12 -1
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/remote_assistant.py +64 -6
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/server.py +97 -13
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/store.py +9 -1
- taskuary-0.3.4.4/taskuary/templates/profile.md +19 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/terminal.py +5 -2
- taskuary-0.3.4.2/taskuary/web/assets/HubView-Cg1MGv3V.js → taskuary-0.3.4.4/taskuary/web/assets/HubView-LlnAPjsa.js +1 -1
- taskuary-0.3.4.2/taskuary/web/assets/StudioScene-DcSntQzU.js → taskuary-0.3.4.4/taskuary/web/assets/StudioScene-DrqOg5SE.js +1 -1
- taskuary-0.3.4.4/taskuary/web/assets/index--05Rw1gj.css +32 -0
- taskuary-0.3.4.4/taskuary/web/assets/index-BpcAELPA.js +1156 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/web/index.html +2 -2
- {taskuary-0.3.4.2 → taskuary-0.3.4.4/taskuary.egg-info}/PKG-INFO +8 -3
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary.egg-info/SOURCES.txt +9 -6
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_agent_profiles.py +84 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_api.py +4 -2
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_api_worker_signals.py +15 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_assistant_reactions.py +14 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_audit_fixes.py +7 -0
- taskuary-0.3.4.4/tests/test_cli_failure.py +57 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_cli_install.py +5 -0
- taskuary-0.3.4.4/tests/test_cli_update.py +91 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_concierge.py +5 -1
- taskuary-0.3.4.4/tests/test_devin.py +114 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_funnel.py +23 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_live.py +13 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_remote_assistant.py +43 -1
- taskuary-0.3.4.4/tests/test_setup_walk.py +155 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_terminal.py +4 -0
- taskuary-0.3.4.2/taskuary/web/assets/GeneralWorkspace-ByR6SrWm.js +0 -12
- taskuary-0.3.4.2/taskuary/web/assets/GeneralWorkspace-D6r5JDIs.css +0 -1
- taskuary-0.3.4.2/taskuary/web/assets/index-BvGrOZeZ.js +0 -1146
- taskuary-0.3.4.2/taskuary/web/assets/index-CUX94PZu.css +0 -32
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/LICENSE +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/setup.cfg +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/__init__.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/aidefaults.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/aisetup.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/artifacts.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/assistant.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/aws.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/azure.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/blackboard.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/brief.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/browserview.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/calendar.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/categories.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/chains.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/channels.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/ci.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/cli.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/climodels.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/coder.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/compose.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/config.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/context.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/counsel.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/db.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/demo.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/deps.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/desktop.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/devtools.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/digest.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/docsync.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/evalset.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/evening.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/files.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/funnel_presentation.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/funnel_selection.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/github.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/handbook.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/handraise.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/histgen.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/hooks.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/hub.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/imapmail.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/imessage.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/ingest.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/intacct.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/interview.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/invoice_workflow.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/knowledge.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/learn.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/learnedgraph.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/live.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/llm.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/logs.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/markets.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/mcp.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/messengers.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/msauth.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/mssql.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/operations.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/outbound.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/outbox.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/ownwork.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/phone.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/playbooks.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/pm.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/policy.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/problems.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/processing.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/processing_all.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/processing_inventory.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/processing_membership.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/processing_navigation.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/processing_order.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/processing_projection.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/processing_reads.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/processing_startup.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/projects.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/promptmap.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/proof.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/proposals.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/quickbooks.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/rank.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/reports.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/research.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/reshape.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/responder.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/retention.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/robinhood.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/routing.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/scopes.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/selfclose.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/semantic.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/senders.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/session_artifacts.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/setup.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/sharepoint.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/sheets.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/simplefin.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/skills/soul-interview/SKILL.md +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/skills/taskuary-setup/SKILL.md +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/spawn.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/teller.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/templates/agent.md +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/templates/analyst.md +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/templates/coder.md +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/templates/coordinator.md +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/templates/counsel.md +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/templates/digest.md +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/templates/history/counsel-0.3.3.5.md +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/templates/how-it-works.md +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/templates/learned.md +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/templates/marketer.md +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/templates/playbook.md +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/templates/researcher.md +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/templates/soul.md +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/templates/style.md +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/templates/trader.md +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/templates/triage.md +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/testing.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/toil.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/toolcatalog.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/triage.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/update.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/verdicts.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/voice.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/wabridge.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/waitroom.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/web/favicon.ico +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/web/favicon.png +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/webguard.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/whatsapp/bridge.mjs +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/whatsapp/package.json +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/whatsapp/policy.mjs +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/whatsapp/policy.test.mjs +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/whatsapp/roster.mjs +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/whatsapp/roster.test.mjs +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/whoami.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/witness.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/workerstate.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/workflows.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary/zoho.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary.egg-info/dependency_links.txt +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary.egg-info/entry_points.txt +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary.egg-info/requires.txt +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/taskuary.egg-info/top_level.txt +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_agent_answer_route.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_agent_environment.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_agent_identity.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_agent_source.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_agent_wall.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_ai_defaults.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_aisetup.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_answered_yourself.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_answers.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_app_uptime.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_approval_interrupt.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_assistant.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_assistant_can_look_things_up.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_assistant_notifications.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_assistant_opens.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_assistant_presentation.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_assistant_verdicts.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_async_triage.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_audit_chain.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_auto_start.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_aws_region.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_blackboard.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_browser_session.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_browserview.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_calendar.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_calendar_today.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_categories.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_cc.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_cc_triage.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_chat_doors.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_chat_freshness.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_chat_is_not_one_task.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_chat_proposals.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_chat_queue.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_chat_relationship.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_chat_retention.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_chat_setup.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_clean_context.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_cli_setup.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_climodels.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_clis.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_cloud.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_cloud_sources.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_completion_freshness.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_compose.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_compose_sources.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_compose_workflows.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_concierge_counsel.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_config.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_context.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_context_refresh_freshness.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_coordination.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_core.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_counsel.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_counsel_consumers.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_counsel_migration.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_counsel_sections.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_current_restore.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_cwlogs_paging.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_demo.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_deps.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_desktop.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_devtools.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_diff_review.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_digest_brief.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_dispatch_retries.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_docs_and_llm.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_docs_flow.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_duplicate_connectors.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_email_chains.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_email_identity_routing.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_evening_brief.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_explicit_completion.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_factory.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_feed_working.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_files.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_final_answer_capture.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_finished_chat_stays_finished.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_floating_assistant.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_follow_up_verdict.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_fresh_evaluation.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_freshness.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_freshness_walk.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_general.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_github_auto.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_github_images.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_guard.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_handbook.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_handbook_topics.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_handoff_channels.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_handraise.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_harvest.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_histgen.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_home_is_never_yours.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_hooks_version.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_ideas_triage.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_ignore_sender.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_imap_catchup.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_imap_history.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_imapmail.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_imessage.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_intacct.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_interrupted_work.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_invoice_workflow.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_kind_dispatch.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_knowledge.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_learnedgraph.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_lifecycle.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_local_file.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_mail_catchup.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_mail_folders.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_mark_read.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_markets.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_mcp.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_meeting_prep.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_message_thread.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_messengers.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_msauth.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_mssql.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_muse.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_noop_ending.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_not_coding.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_note_retrieval.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_operations.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_outbound_reports.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_outbox.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_own_rows_no_reply.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_peer_updates.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_playbooks.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_pm.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_poll_lanes.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_poll_overlap.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_poll_parallel.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_poll_sources.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_pr_comments.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_problems.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_problems_dismiss.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_procedure_selection.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_processing_demo_replay.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_processing_ledger.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_projects.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_promptmap_and_catalog.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_proof_ci_proposals.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_pypi_downloads.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_quickbooks.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_rank.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_rate_limit.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_reclassify.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_reply_always_drafts.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_reply_channels.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_reply_envelope.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_reply_sources.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_reply_voice.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_repo_choice_triage.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_report_alerts.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_report_order_and_research.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_report_prompt_isolation.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_report_refusal.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_report_triage.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_research.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_reviews.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_robinhood.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_scopes.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_seed_fits_a_tty.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_seeded_reports.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_selector_vocabulary.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_semantic.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_send_outcomes.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_send_targets.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_sender_trust.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_session_artifacts.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_setup.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_setup_skill.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_sheets_sharepoint.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_simplefin.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_someone_else_replied.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_soul_interview.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_startup_contract.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_stay_open.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_stay_open_doors.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_store_app_cli.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_store_hotpaths.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_sync_clock.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_task_ask.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_task_checklist.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_task_controls_operations.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_task_identity.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_teller.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_toil.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_triage_dataset.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_triage_errors.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_triage_images.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_update.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_update_refused.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_urgent_and_handoff.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_verdict_paths.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_verdict_sticks.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_vision.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_voice.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_wabridge.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_waitroom.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_walk_order.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_watermark.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_web_entrypoint.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_webguard.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_whatsapp_documents.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_whatsapp_images.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_whoami.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_withdrawn.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_witness.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_worker_brief.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_worker_events.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_worker_scenarios.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_worker_surfaces.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_workflows.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_zz_chipverbs.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_zz_lastword.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_zz_setupchat.py +0 -0
- {taskuary-0.3.4.2 → taskuary-0.3.4.4}/tests/test_zz_toolcatalog.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: taskuary
|
|
3
|
-
Version: 0.3.4.
|
|
3
|
+
Version: 0.3.4.4
|
|
4
4
|
Summary: Automate your job - the local-first operating system for your work: AI triage in, coding agents out.
|
|
5
5
|
License: MIT
|
|
6
6
|
Project-URL: Homepage, https://github.com/ldbumble/taskuary
|
|
@@ -66,7 +66,7 @@ Dynamic: license-file
|
|
|
66
66
|
# Taskuary
|
|
67
67
|
|
|
68
68
|
[](https://github.com/ldbumble/taskuary/actions/workflows/ci.yml)
|
|
69
|
-
[](https://pypi.org/project/taskuary/)
|
|
70
70
|
[](https://github.com/ldbumble/taskuary)
|
|
71
71
|
[](LICENSE)
|
|
72
72
|
|
|
@@ -77,7 +77,7 @@ your agents, and brings decisions back to you. Nothing sends or ships without yo
|
|
|
77
77
|
|
|
78
78
|

|
|
79
79
|
|
|
80
|
-
Taskuary is early—currently **v0.3.4.
|
|
80
|
+
Taskuary is early—currently **v0.3.4.4**—so breaking changes are still possible before 1.0.
|
|
81
81
|
|
|
82
82
|
<p align="center">
|
|
83
83
|
<a href="https://taskuary.com/demo/"><img
|
|
@@ -114,6 +114,11 @@ review the work, make the call, and approve what happens next.
|
|
|
114
114
|
**Agents in your own repos**
|
|
115
115
|
|
|
116
116
|
Send coding work to Claude Code, Codex, Gemini, Cursor, Copilot, Muse Code, or another CLI.
|
|
117
|
+
Install and sign in under Connections → AI CLI agents. Manage named workers, their CLI settings,
|
|
118
|
+
and their instructions under Docs → Profiles.
|
|
119
|
+
Use **Add profile** to choose a CLI and describe when triage should select it. Leave
|
|
120
|
+
**Available to triage for new tasks** on to include it automatically. Each profile starts with
|
|
121
|
+
editable instructions; coding workers such as Coder and Codex share `CODER.md`.
|
|
117
122
|
|
|
118
123
|
**Live workspaces**
|
|
119
124
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# Taskuary
|
|
2
2
|
|
|
3
3
|
[](https://github.com/ldbumble/taskuary/actions/workflows/ci.yml)
|
|
4
|
-
[](https://pypi.org/project/taskuary/)
|
|
5
5
|
[](https://github.com/ldbumble/taskuary)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
@@ -12,7 +12,7 @@ your agents, and brings decisions back to you. Nothing sends or ships without yo
|
|
|
12
12
|
|
|
13
13
|

|
|
14
14
|
|
|
15
|
-
Taskuary is early—currently **v0.3.4.
|
|
15
|
+
Taskuary is early—currently **v0.3.4.4**—so breaking changes are still possible before 1.0.
|
|
16
16
|
|
|
17
17
|
<p align="center">
|
|
18
18
|
<a href="https://taskuary.com/demo/"><img
|
|
@@ -49,6 +49,11 @@ review the work, make the call, and approve what happens next.
|
|
|
49
49
|
**Agents in your own repos**
|
|
50
50
|
|
|
51
51
|
Send coding work to Claude Code, Codex, Gemini, Cursor, Copilot, Muse Code, or another CLI.
|
|
52
|
+
Install and sign in under Connections → AI CLI agents. Manage named workers, their CLI settings,
|
|
53
|
+
and their instructions under Docs → Profiles.
|
|
54
|
+
Use **Add profile** to choose a CLI and describe when triage should select it. Leave
|
|
55
|
+
**Available to triage for new tasks** on to include it automatically. Each profile starts with
|
|
56
|
+
editable instructions; coding workers such as Coder and Codex share `CODER.md`.
|
|
52
57
|
|
|
53
58
|
**Live workspaces**
|
|
54
59
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "taskuary"
|
|
7
|
-
version = "0.3.4.
|
|
7
|
+
version = "0.3.4.4"
|
|
8
8
|
description = "Automate your job - the local-first operating system for your work: AI triage in, coding agents out."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = { text = "MIT" }
|
|
@@ -235,7 +235,8 @@ _SIGNED_OUT = re.compile(r'OAuth session expired|Failed to authenticate|not logg
|
|
|
235
235
|
_LOGIN_HOW = {'claude': "run `claude` and type `/login`", 'copilot': "run `copilot` and type `/login`",
|
|
236
236
|
'codex': "run `codex login`", 'cursor': "run `cursor-agent login`",
|
|
237
237
|
'gemini': "run `gemini` once and finish Google's sign-in",
|
|
238
|
-
'muse': "run `muse` once and finish the browser sign-in at dev.meta.ai"
|
|
238
|
+
'muse': "run `muse` once and finish the browser sign-in at dev.meta.ai",
|
|
239
|
+
'devin': "run `devin auth login` and finish the browser sign-in"}
|
|
239
240
|
# Provider/plan exhaustion is different from an agent failing the work. Only this availability
|
|
240
241
|
# class is safe to hand to another configured agent automatically: a compile error should remain
|
|
241
242
|
# with the agent that owns it, while "session limit; resets at 11:50" should not strand the task.
|
|
@@ -300,6 +301,55 @@ def rate_limit_msg(name: str, info: dict) -> str:
|
|
|
300
301
|
f'report or its setup - it will run normally once the allowance resets.{extra}')
|
|
301
302
|
|
|
302
303
|
|
|
304
|
+
# A CLI's REASON is on stdout, with its events; stderr is where it says it is getting started.
|
|
305
|
+
# Reading the reason off stderr made "codex exit 1: Reading prompt from stdin..." the whole
|
|
306
|
+
# account of a run that had in fact been told, in JSON on stdout, that the model in
|
|
307
|
+
# ~/.codex/config.toml needed a newer Codex. Thirteen hours of "Agent is working" over a two-second
|
|
308
|
+
# failure nobody could see (the owner, TQ-0496, 2026-09-11).
|
|
309
|
+
_NOISE = re.compile(r'^\s*reading prompt from stdin\.*\s*$', re.I)
|
|
310
|
+
|
|
311
|
+
|
|
312
|
+
def _said(msg) -> str:
|
|
313
|
+
"""The sentence inside an error envelope. A provider's 400 reaches the owner through two
|
|
314
|
+
layers of JSON-as-a-string, and what they need out of it is the one line in English."""
|
|
315
|
+
text = str(msg or '').strip()
|
|
316
|
+
for _ in range(3): # deep enough for provider-in-CLI-in-event
|
|
317
|
+
if not text.startswith('{'): break
|
|
318
|
+
try: j = json.loads(text)
|
|
319
|
+
except ValueError: break
|
|
320
|
+
if not isinstance(j, dict): break
|
|
321
|
+
inner = j.get('error') if isinstance(j.get('error'), dict) else {}
|
|
322
|
+
nxt = str(inner.get('message') or j.get('message') or '').strip()
|
|
323
|
+
if not nxt: break
|
|
324
|
+
text = nxt
|
|
325
|
+
return text
|
|
326
|
+
|
|
327
|
+
|
|
328
|
+
def _event_error(line: str) -> str:
|
|
329
|
+
"""The failure one event line reports, or '' - an ordinary event is not a fault."""
|
|
330
|
+
try: j = json.loads(line)
|
|
331
|
+
except ValueError: return ''
|
|
332
|
+
if not isinstance(j, dict): return ''
|
|
333
|
+
if j.get('type') in ('error', 'turn.failed'):
|
|
334
|
+
inner = j.get('error') if isinstance(j.get('error'), dict) else {}
|
|
335
|
+
return _said(inner.get('message') or j.get('message'))
|
|
336
|
+
item = j.get('item') if isinstance(j.get('item'), dict) else {}
|
|
337
|
+
return _said(item.get('message')) if item.get('type') == 'error' else ''
|
|
338
|
+
|
|
339
|
+
|
|
340
|
+
def cli_failure(raw, err: str = '') -> str:
|
|
341
|
+
"""Why the run failed: the CLI's own newest error event, else what it put on stderr.
|
|
342
|
+
|
|
343
|
+
The newest wins because an earlier one is usually a warning the run carried on past - codex
|
|
344
|
+
grumbles about unknown model metadata and then fails on the refusal that actually stopped it.
|
|
345
|
+
"""
|
|
346
|
+
for line in reversed(list(raw or [])):
|
|
347
|
+
said = _event_error(str(line))
|
|
348
|
+
if said: return said
|
|
349
|
+
clean = '\n'.join(ln for ln in str(err or '').splitlines() if ln.strip() and not _NOISE.match(ln))
|
|
350
|
+
return clean.strip() or '\n'.join(list(raw or [])[-5:]) or 'no output'
|
|
351
|
+
|
|
352
|
+
|
|
303
353
|
_DENIED = re.compile(r'access is denied|winerror 5|permission denied|operation not permitted', re.I)
|
|
304
354
|
_NO_HOME = re.compile(r'could not find home directory|finding codex home|HOME.{0,20}not set', re.I)
|
|
305
355
|
|
|
@@ -403,6 +453,45 @@ def seed_profiles(cfg: dict) -> list:
|
|
|
403
453
|
return added
|
|
404
454
|
|
|
405
455
|
|
|
456
|
+
def profile_purpose(name: str, prof: dict, kind: str = 'coding') -> str:
|
|
457
|
+
return str(prof.get('purpose') or DEFAULT_PROFILES.get(name, {}).get('purpose')
|
|
458
|
+
or ('writes and changes code, in a repository' if kind == 'coding' else '')).strip()
|
|
459
|
+
|
|
460
|
+
|
|
461
|
+
def profile_document(store, name: str, prof: dict = None) -> str:
|
|
462
|
+
"""The job owns the instructions; changing its CLI does not create CODEX.md."""
|
|
463
|
+
row = store.get_agent(name) or {}
|
|
464
|
+
if prof is None:
|
|
465
|
+
try: prof = json.loads(row.get('Config') or '{}')
|
|
466
|
+
except ValueError: prof = {}
|
|
467
|
+
explicit = str(prof.get('rules_doc') or '').strip()
|
|
468
|
+
if explicit and re.fullmatch(r'[a-z0-9][a-z0-9_-]*', explicit): return explicit
|
|
469
|
+
kind = prof.get('kind') or DEFAULT_PROFILES.get(name, {}).get('kind') or row.get('Kind')
|
|
470
|
+
return 'coder' if kind == 'coding' else name
|
|
471
|
+
|
|
472
|
+
|
|
473
|
+
def profile_template(store, name: str) -> str:
|
|
474
|
+
"""A shipped role, or useful starter instructions for a new named worker."""
|
|
475
|
+
if not re.fullmatch(r'[a-z0-9][a-z0-9_-]*', name): return ''
|
|
476
|
+
folder = Path(__file__).parent / 'templates'
|
|
477
|
+
source = folder / f'{name}.md'
|
|
478
|
+
if source.is_file(): return source.read_text(encoding='utf-8')
|
|
479
|
+
row = store.get_agent(name)
|
|
480
|
+
if not row: return ''
|
|
481
|
+
try: prof = json.loads(row.get('Config') or '{}')
|
|
482
|
+
except ValueError: prof = {}
|
|
483
|
+
purpose = profile_purpose(name, prof, row.get('Kind') or 'coding') or 'Complete the task assigned by the owner.'
|
|
484
|
+
return (folder / 'profile.md').read_text(encoding='utf-8').replace('PROFILE_NAME', name.upper()).replace('PROFILE_PURPOSE', purpose)
|
|
485
|
+
|
|
486
|
+
|
|
487
|
+
def ensure_profile_document(store, name: str) -> str:
|
|
488
|
+
doc = profile_document(store, name)
|
|
489
|
+
if not str(store.get_doc(doc) or '').strip():
|
|
490
|
+
starter = profile_template(store, doc)
|
|
491
|
+
if starter.strip(): store.save_doc(doc, starter, 'template')
|
|
492
|
+
return doc
|
|
493
|
+
|
|
494
|
+
|
|
406
495
|
def roster(store) -> str:
|
|
407
496
|
"""The workers triage may choose between: one line each, name and purpose. Same shape as the
|
|
408
497
|
playbook menu (playbooks.menu) because triage validates the answer against these very lines -
|
|
@@ -412,8 +501,8 @@ def roster(store) -> str:
|
|
|
412
501
|
if not a.get('Active', 1): continue
|
|
413
502
|
try: prof = json.loads(a.get('Config') or '{}')
|
|
414
503
|
except ValueError: prof = {}
|
|
415
|
-
|
|
416
|
-
|
|
504
|
+
if prof.get('triage_enabled') is False: continue
|
|
505
|
+
purpose = profile_purpose(a['Name'], prof, a.get('Kind') or 'coding')
|
|
417
506
|
if purpose: out.append(f"- {a['Name']}: {purpose}")
|
|
418
507
|
return '\n'.join(out)
|
|
419
508
|
|
|
@@ -567,15 +656,19 @@ def run_cli(profile: dict, prompt: str, trace, resume: str = None, cancel=None,
|
|
|
567
656
|
err_t.join(5) # the exit code can land before the stderr reader has appended - 'boom' read as 'no output' on a fast CI box
|
|
568
657
|
if p.returncode != 0:
|
|
569
658
|
if cancel is not None and cancel.is_set(): raise RuntimeError('cancelled')
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
659
|
+
err = ''.join(err_buf)
|
|
660
|
+
said = cli_failure(raw, err)
|
|
661
|
+
why = (f'timed out after {profile.get("timeout", 1200)}s' if timed.is_set() else said)[:500]
|
|
662
|
+
# the CLASSIFYING patterns read both streams: a sign-in refusal or a Windows launch
|
|
663
|
+
# refusal lands on stderr, while the reason the owner reads now comes off stdout
|
|
664
|
+
hay = f'{said}\n{err}'
|
|
665
|
+
limit = rate_limited(raw) or rate_limited(hay)
|
|
573
666
|
if limit: raise RuntimeError(rate_limit_msg(name, limit))
|
|
574
|
-
if _SIGNED_OUT.search(
|
|
667
|
+
if _SIGNED_OUT.search(hay): raise RuntimeError(signed_out_msg(name, said, cmd[0] if cmd else ''))
|
|
575
668
|
# a refusal to START, not a failed run: the CLI produced no output of its own and the
|
|
576
669
|
# only thing on stderr is the refusal
|
|
577
|
-
if _DENIED.search(
|
|
578
|
-
if _NO_HOME.search(
|
|
670
|
+
if _DENIED.search(hay) and not raw: raise RuntimeError(denied_msg(name, cmd[0] if cmd else '', said))
|
|
671
|
+
if _NO_HOME.search(hay): raise RuntimeError(no_home_msg(_cli_name(name) or name))
|
|
579
672
|
raise RuntimeError(f'{name} exit {p.returncode}: {why}')
|
|
580
673
|
if final is not None: out, sid = str(final.get('result') or '').strip(), final.get('session_id')
|
|
581
674
|
elif streamed_out: out, sid = streamed_out, streamed_sid
|
|
@@ -27,7 +27,7 @@ the rest of the day:
|
|
|
27
27
|
3. the registry (Windows) or the shell rc file (posix), so a terminal opened tomorrow has it.
|
|
28
28
|
Only (3) is what people mean by "on PATH", and it is the one that helps least today.
|
|
29
29
|
"""
|
|
30
|
-
import os, platform, shutil, threading, time
|
|
30
|
+
import os, platform, shutil, subprocess, threading, time
|
|
31
31
|
from pathlib import Path
|
|
32
32
|
|
|
33
33
|
from loguru import logger
|
|
@@ -61,11 +61,24 @@ RECIPES = {
|
|
|
61
61
|
# plan() then returns [] on Windows and the row draws no Install button over a road that
|
|
62
62
|
# hard-fails. A Windows owner reaches Muse Spark through the `meta` connector instead.
|
|
63
63
|
'muse': [{'how': 'script', 'os': 'posix', 'cmd': ['bash', '-lc', 'curl -fsSL https://dev.meta.ai/install.sh | bash']}],
|
|
64
|
+
# devin publishes a script per OS and nothing else - no npm package, and its releases are
|
|
65
|
+
# laid out by the installer's own versioned scheme rather than as triple-named archives, so
|
|
66
|
+
# `binary` is not a road. Both scripts END BY RUNNING `devin setup`, the CLI's interactive
|
|
67
|
+
# wizard, which has no terminal to run in when it is spawned from here: the short timeout is
|
|
68
|
+
# how long we wait for a wizard that may never return, and install() treats a timeout with a
|
|
69
|
+
# binary on disk as the success it is.
|
|
70
|
+
'devin': [
|
|
71
|
+
{'how': 'script', 'os': 'nt', 'timeout': 300,
|
|
72
|
+
'cmd': ['powershell', '-NoProfile', '-ExecutionPolicy', 'Bypass',
|
|
73
|
+
'-Command', 'irm https://static.devin.ai/cli/setup.ps1 | iex']},
|
|
74
|
+
{'how': 'script', 'os': 'posix', 'timeout': 300,
|
|
75
|
+
'cmd': ['bash', '-lc', 'curl -fsSL https://cli.devin.ai/install.sh | bash']},
|
|
76
|
+
],
|
|
64
77
|
}
|
|
65
78
|
|
|
66
79
|
# what to look for once an installer says it is done - the bin name, not the profile's nickname
|
|
67
80
|
BINARY = {'claude': 'claude', 'codex': 'codex', 'gemini': 'gemini', 'copilot': 'copilot', 'cursor': 'cursor-agent',
|
|
68
|
-
'muse': 'muse'}
|
|
81
|
+
'muse': 'muse', 'devin': 'devin'}
|
|
69
82
|
CMD2NAME = {v: k for k, v in BINARY.items()} # cursor-agent -> cursor: the bin is not the recipe
|
|
70
83
|
|
|
71
84
|
|
|
@@ -81,13 +94,75 @@ def recipe_for(cmd: str) -> str:
|
|
|
81
94
|
return base if base in RECIPES else CMD2NAME.get(base, '')
|
|
82
95
|
|
|
83
96
|
|
|
84
|
-
|
|
97
|
+
# UPDATING IS NOT INSTALLING AGAIN. A CLI that ships its own updater is the one thing that knows
|
|
98
|
+
# where it put itself - codex keeps its releases under ~/.codex/packages/standalone/<version>
|
|
99
|
+
# behind a junction, and an `npm -g` over that leaves a second copy with the old one still first
|
|
100
|
+
# on PATH. So the vendor's updater first, the package manager only as the fallback.
|
|
101
|
+
#
|
|
102
|
+
# The button exists because a CLI too old for its own configured model fails every single run and
|
|
103
|
+
# says so only in the JSON it writes to stdout: codex 0.148.0 answering "The 'gpt-6-astra' model
|
|
104
|
+
# requires a newer version of Codex" to everything Taskuary asked it (the owner, 2026-09-11).
|
|
105
|
+
# A closed table, for the same reason RECIPES is one: this runs a program on the owner's machine.
|
|
106
|
+
UPDATES = {
|
|
107
|
+
'claude': [{'how': 'self', 'args': ['update']}, {'how': 'npm', 'pkg': '@anthropic-ai/claude-code@latest'}],
|
|
108
|
+
'codex': [{'how': 'self', 'args': ['update']}, {'how': 'npm', 'pkg': '@openai/codex@latest'}],
|
|
109
|
+
'gemini': [{'how': 'npm', 'pkg': '@google/gemini-cli@latest'}],
|
|
110
|
+
'copilot': [{'how': 'npm', 'pkg': '@github/copilot@latest'}],
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
_STATE = {'phase': 'idle', 'name': '', 'verb': 'install', 'detail': '', 'path': '', 'at': 0.0} # idle|installing|done|failed
|
|
85
114
|
_LOCK = threading.Lock()
|
|
86
115
|
|
|
87
116
|
|
|
88
117
|
def state() -> dict: return dict(_STATE)
|
|
89
|
-
def reset() -> None: _STATE.update(phase='idle', name='', detail='', path='', at=0.0)
|
|
90
|
-
def _set(phase, name='', detail='', path=''
|
|
118
|
+
def reset() -> None: _STATE.update(phase='idle', name='', verb='install', detail='', path='', at=0.0)
|
|
119
|
+
def _set(phase, name='', detail='', path='', verb='install'):
|
|
120
|
+
_STATE.update(phase=phase, name=name, verb=verb, detail=str(detail)[-400:], path=path, at=time.time())
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def update_plan(name: str, has_npm: bool = None) -> list:
|
|
124
|
+
"""The update roads that could run here, best first. Pure, like `plan` - `updatable` in the UI
|
|
125
|
+
is `bool(update_plan(...))`, so a button is never drawn over a road that does not exist."""
|
|
126
|
+
have_npm = bool(npm()) if has_npm is None else has_npm
|
|
127
|
+
return [r for r in UPDATES.get(name, ()) if r['how'] != 'npm' or have_npm]
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def update(name: str) -> dict:
|
|
131
|
+
"""Bring an already-installed CLI up to date. Synchronous - `start_update` is the API's."""
|
|
132
|
+
roads = update_plan(name)
|
|
133
|
+
if not roads:
|
|
134
|
+
_set('failed', name, f'Taskuary has no updater for {name} - reinstall it the way you installed it', verb='update')
|
|
135
|
+
return state()
|
|
136
|
+
# An update is not an install: with nothing here there is nothing to bring up to date, and
|
|
137
|
+
# running a package manager would quietly become an install the owner did not press.
|
|
138
|
+
exe = find(name)
|
|
139
|
+
if not exe:
|
|
140
|
+
_set('failed', name, f'{name} is not on this machine - install it first', verb='update')
|
|
141
|
+
return state()
|
|
142
|
+
_set('installing', name, f'updating {name}…', verb='update')
|
|
143
|
+
last = ''
|
|
144
|
+
for r in roads:
|
|
145
|
+
try:
|
|
146
|
+
cmd = [exe] + list(r['args']) if r['how'] == 'self' else [npm() or 'npm', 'install', '-g', r['pkg']]
|
|
147
|
+
rc, out = _run(cmd, timeout=r.get('timeout', 900))
|
|
148
|
+
except Exception as e:
|
|
149
|
+
last = str(e); logger.warning(f'{name}: {r["how"]} update raised - {e}'); continue
|
|
150
|
+
if rc != 0:
|
|
151
|
+
last = out or f'{r["how"]} exited {rc}'; logger.warning(f'{name}: {r["how"]} update failed - {last[-200:]}'); continue
|
|
152
|
+
_set('done', name, out.strip()[-400:] or f'{name} is up to date', find(name) or exe, verb='update')
|
|
153
|
+
logger.info(f'updated {name}')
|
|
154
|
+
return state()
|
|
155
|
+
_set('failed', name, f'could not update {name}: {last}', verb='update')
|
|
156
|
+
return state()
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
def start_update(name: str, **kw) -> dict:
|
|
160
|
+
"""Update in the background, for the same reason `start` installs in one."""
|
|
161
|
+
with _LOCK:
|
|
162
|
+
if _STATE['phase'] == 'installing': return state()
|
|
163
|
+
_set('installing', name, f'updating {name}…', verb='update')
|
|
164
|
+
threading.Thread(target=update, args=(name,), kwargs=kw, daemon=True, name=f'update-{name}').start()
|
|
165
|
+
return state()
|
|
91
166
|
|
|
92
167
|
|
|
93
168
|
def npm() -> str:
|
|
@@ -144,7 +219,11 @@ def find(name: str) -> str:
|
|
|
144
219
|
if found: return found
|
|
145
220
|
home = Path.home()
|
|
146
221
|
roots = [bin_dir(), home / '.local' / 'bin', home / 'bin']
|
|
147
|
-
|
|
222
|
+
# devin's own scheme: %LOCALAPPDATA%\devin\cli\bin on Windows, which its installer puts on the
|
|
223
|
+
# USER path - a path this long-running process will not see until it is restarted, so looking
|
|
224
|
+
# there is the difference between "installed" and "the installer said yes and left nothing"
|
|
225
|
+
if WINDOWS: roots += [Path(os.getenv('APPDATA', '')) / 'npm', home / '.local' / 'bin',
|
|
226
|
+
Path(os.getenv('LOCALAPPDATA', '')) / 'devin' / 'cli' / 'bin']
|
|
148
227
|
else: roots += [Path('/usr/local/bin'), Path('/opt/homebrew/bin')]
|
|
149
228
|
for d in roots:
|
|
150
229
|
for ext in ('.exe', '.cmd', '.bat', '') if WINDOWS else ('',):
|
|
@@ -259,11 +338,17 @@ def install(name: str, has_npm: bool = None, system: str = None) -> dict:
|
|
|
259
338
|
_binary(name, r)
|
|
260
339
|
else:
|
|
261
340
|
cmd = list(r['cmd']) if r['how'] == 'script' else [npm() or 'npm', 'install', '-g', r['pkg']]
|
|
262
|
-
rc, out = _run(cmd)
|
|
341
|
+
rc, out = _run(cmd, timeout=r.get('timeout', 900))
|
|
263
342
|
if rc != 0: last = out or f'{r["how"]} exited {rc}'; logger.warning(f'{name}: {r["how"]} failed - {last[-200:]}'); continue
|
|
264
343
|
last = out
|
|
265
344
|
except Exception as e:
|
|
266
|
-
last = str(e); logger.warning(f'{name}: {r["how"]} raised - {e}')
|
|
345
|
+
last = str(e); logger.warning(f'{name}: {r["how"]} raised - {e}')
|
|
346
|
+
# A TIMEOUT IS NOT PROOF OF FAILURE. An installer that ends by starting the CLI's own
|
|
347
|
+
# interactive wizard (devin's does) never returns when it is spawned with no terminal
|
|
348
|
+
# - while the binary it wrote a second earlier is installed and runnable. Only a
|
|
349
|
+
# timeout gets this second look: any other exception left the install where it fell.
|
|
350
|
+
if not (isinstance(e, subprocess.TimeoutExpired) and find(name)): continue
|
|
351
|
+
last = 'the installer finished but its setup wizard needed a terminal'
|
|
267
352
|
# rc 0 proves the installer ran, not that anything is runnable: only a binary does that
|
|
268
353
|
found = find(name)
|
|
269
354
|
if found:
|
|
@@ -20,8 +20,6 @@ KNOWN = [
|
|
|
20
20
|
'args': ['exec', '--dangerously-bypass-approvals-and-sandbox'], 'timeout': 1500},
|
|
21
21
|
{'name': 'gemini', 'cmd': 'gemini', 'label': 'Gemini CLI',
|
|
22
22
|
'args': ['-p', '--yolo'], 'timeout': 1500},
|
|
23
|
-
{'name': 'aider', 'cmd': 'aider', 'label': 'Aider',
|
|
24
|
-
'args': ['--yes-always', '--no-auto-commits', '--message'], 'timeout': 1500},
|
|
25
23
|
{'name': 'cursor', 'cmd': 'cursor-agent', 'label': 'Cursor CLI',
|
|
26
24
|
'args': ['-p', '--force', '--output-format', 'text'], 'timeout': 1500},
|
|
27
25
|
{'name': 'copilot', 'cmd': 'copilot', 'label': 'GitHub Copilot CLI',
|
|
@@ -32,6 +30,16 @@ KNOWN = [
|
|
|
32
30
|
# the run is read as plain text. POSIX only - see cliinstall.RECIPES.
|
|
33
31
|
{'name': 'muse', 'cmd': 'muse', 'label': 'Meta Muse Code',
|
|
34
32
|
'args': ['exec', '--yolo'], 'timeout': 1500},
|
|
33
|
+
# Cognition's Devin for Terminal - claude's shape (-p is the headless turn), different
|
|
34
|
+
# spellings for the two things a headless run cannot do without:
|
|
35
|
+
# --permission-mode dangerous the approval bypass, a MODE here rather than a flag;
|
|
36
|
+
# --respect-workspace-trust --print cannot draw the trust prompt, so in an untrusted
|
|
37
|
+
# folder it refuses to start - and half of what this app runs is ~/.taskuary/scratch,
|
|
38
|
+
# not a checkout (the same dead end codex's --skip-git-repo-check answers). The `=`
|
|
39
|
+
# form on purpose: the value is optional, and a space-separated one is read as a prompt.
|
|
40
|
+
# No stream-json equivalent, so the Board reads the run as plain text.
|
|
41
|
+
{'name': 'devin', 'cmd': 'devin', 'label': 'Devin CLI',
|
|
42
|
+
'args': ['--permission-mode', 'dangerous', '--respect-workspace-trust=false', '-p'], 'timeout': 1500},
|
|
35
43
|
]
|
|
36
44
|
|
|
37
45
|
|
|
@@ -61,7 +69,11 @@ READONLY = {'claude': (('--dangerously-skip-permissions',), ('--tools', '')),
|
|
|
61
69
|
'gemini': (('--yolo',), ()),
|
|
62
70
|
# muse: same reasoning as gemini. --yolo is what turns approval AND the sandbox off, so
|
|
63
71
|
# dropping it puts both back; its default on-request mode has nobody to ask in `exec`.
|
|
64
|
-
'muse': (('--yolo',), ())
|
|
72
|
+
'muse': (('--yolo',), ()),
|
|
73
|
+
# devin: the bypass is a mode, so it is swapped rather than dropped. `normal` auto-
|
|
74
|
+
# approves reads inside the folder and asks before every write or shell command -
|
|
75
|
+
# and headlessly there is nobody to ask, which is exactly the point.
|
|
76
|
+
'devin': (('--permission-mode', 'dangerous'), ('--permission-mode', 'normal'))}
|
|
65
77
|
|
|
66
78
|
# A report is allowed to LOOK, but not to act. That is deliberately different from the mail
|
|
67
79
|
# classifier above, which gets no tools at all because the text it classifies is untrusted input.
|
|
@@ -83,6 +95,7 @@ REPORT_READ = {
|
|
|
83
95
|
'codex': READONLY['codex'],
|
|
84
96
|
'gemini': READONLY['gemini'],
|
|
85
97
|
'muse': READONLY['muse'],
|
|
98
|
+
'devin': READONLY['devin'],
|
|
86
99
|
}
|
|
87
100
|
|
|
88
101
|
|
|
@@ -164,6 +177,9 @@ def detect(store=None) -> list:
|
|
|
164
177
|
# `installable`: never draw a button over a road that does not exist
|
|
165
178
|
out.append({**k, 'installed': bool(found), 'path': found or '', 'runs': runs, 'store': blocked,
|
|
166
179
|
'install': recipe, 'installable': installable, 'configured': k['name'] in have,
|
|
180
|
+
# a CLI that IS here can still be too old to run: `updatable` is the Update
|
|
181
|
+
# button's own road test, and only a CLI already on this machine has one
|
|
182
|
+
'updatable': bool(found and cliinstall.update_plan(recipe)),
|
|
167
183
|
'why_not': '' if (found or installable) else cliinstall.why_not(recipe),
|
|
168
184
|
'setup': recipe if recipe in clisetup.SETUP else ''})
|
|
169
185
|
import json, os
|
|
@@ -183,6 +199,7 @@ def detect(store=None) -> list:
|
|
|
183
199
|
'args': list(prof.get('args') or []), 'installed': bool(found), 'path': found or '',
|
|
184
200
|
'runs': runs, 'store': blocked, 'install': recipe,
|
|
185
201
|
'installable': bool(cliinstall.plan(recipe)), 'configured': True,
|
|
202
|
+
'updatable': bool(found and cliinstall.update_plan(recipe)),
|
|
186
203
|
'why_not': '' if (found or cliinstall.plan(recipe)) else cliinstall.why_not(recipe),
|
|
187
204
|
'setup': recipe if recipe in clisetup.SETUP else ''})
|
|
188
205
|
return out
|
|
@@ -33,13 +33,17 @@ KIND = aisetup.KIND # a set-up is a setup task, and Done alrea
|
|
|
33
33
|
finish = aisetup.finish # ...so its ending is aisetup's, unchanged
|
|
34
34
|
|
|
35
35
|
# The CLIs whose first run Taskuary will open. Closed, and keyed by RECIPE name (cursor, not
|
|
36
|
-
# cursor-agent) so this, cliinstall.RECIPES and clis.detect's `install` field all agree.
|
|
37
|
-
#
|
|
36
|
+
# cursor-agent) so this, cliinstall.RECIPES and clis.detect's `install` field all agree. A CLI
|
|
37
|
+
# that only takes an API key out of a config file has no first run to open, and does not belong
|
|
38
|
+
# on this tab at all - a key is the connectors' door, not this one (aider, removed 2026-09-11).
|
|
38
39
|
# muse belongs here for the usual reason: its first run opens a browser sign-in against a Meta
|
|
39
40
|
# developer account and mints the CLI's own key, and that is exactly the conversation this pane
|
|
40
41
|
# exists to let the owner have. argv() still refuses it when the binary is absent, which on
|
|
41
42
|
# Windows it always will be.
|
|
42
|
-
|
|
43
|
+
# devin belongs here twice over: its installer already tried to run `devin setup` and could not
|
|
44
|
+
# (no terminal), and its sign-in is a browser round trip that falls back to pasting a token back
|
|
45
|
+
# into the CLI - a conversation that needs the pane, not a spawn nobody can see.
|
|
46
|
+
SETUP = frozenset({'claude', 'codex', 'gemini', 'copilot', 'cursor', 'muse', 'devin'})
|
|
43
47
|
|
|
44
48
|
|
|
45
49
|
def tag(name: str) -> str: return f'cli:{name}'
|
|
@@ -1562,6 +1562,36 @@ def close_task(store, tid: int, actor: str = 'owner') -> bool:
|
|
|
1562
1562
|
# no repository, no checkout, nothing built. Real building is a hand-off the owner asks for by name.
|
|
1563
1563
|
SETUP_KIND = 'general'
|
|
1564
1564
|
|
|
1565
|
+
WALK_SORT = ('The owner asked for something to be set up. Decide which of two jobs this is. '
|
|
1566
|
+
'CONFIGURING TASKUARY ITSELF: its AI brain, a connector card, a source it reads, a '
|
|
1567
|
+
'report or workflow, one of its operator documents - anything that has a screen in '
|
|
1568
|
+
'this app. OPERATING ANOTHER SYSTEM on their behalf: signing into a site, driving a '
|
|
1569
|
+
'portal, a tool this app has no card for. Answer with JSON only: {"taskuary": true} '
|
|
1570
|
+
'for the first, {"taskuary": false} for the second.')
|
|
1571
|
+
|
|
1572
|
+
|
|
1573
|
+
def walk_is_external(store, text: str, llm=None) -> bool:
|
|
1574
|
+
"""Is this walk over the owner's OWN systems rather than over configuring Taskuary?
|
|
1575
|
+
|
|
1576
|
+
The shipped taskuary-setup SKILL is the procedure for configuring this install, and the wrong
|
|
1577
|
+
map for anything else: asked to log into a payroll portal and clock in every morning, a walk
|
|
1578
|
+
carrying it reads out the AI-brain-and-inbound-source checklist instead (the owner, 2026-09-10:
|
|
1579
|
+
"it's supposed to walk me through this?").
|
|
1580
|
+
|
|
1581
|
+
The MODEL reads the intent - there is no word list here - and only a clear "no" moves the walk
|
|
1582
|
+
off the skill. An unsure answer, a brain that is not there, one that returns prose or dies
|
|
1583
|
+
keeps it, because most set-ups really are about this install."""
|
|
1584
|
+
try: brain_ = llm or brain(store, fast=True)
|
|
1585
|
+
except Exception as e:
|
|
1586
|
+
logger.debug(f'the walk sort has no brain - {e}'); return False
|
|
1587
|
+
if not brain_: return False
|
|
1588
|
+
from . import compose
|
|
1589
|
+
try: out = compose._json(brain_(WALK_SORT, str(text or '')[:2_000], max_tokens=60)) or {}
|
|
1590
|
+
except Exception as e:
|
|
1591
|
+
logger.info(f'the walk sort did not answer, so the walk keeps the skill - {str(e)[:200]}'); return False
|
|
1592
|
+
return out.get('taskuary') is False
|
|
1593
|
+
|
|
1594
|
+
|
|
1565
1595
|
def setup_task(store, text: str, actor: str = 'owner', title: str = '', kind: str = SETUP_KIND,
|
|
1566
1596
|
agent_job: bool = False) -> dict:
|
|
1567
1597
|
"""'Set up a report that...': a task with the owner's words in it, opened for the agent that can
|
|
@@ -22,6 +22,24 @@ LANES = ('blocked', 'time', 'approve', 'asked', 'queued', 'broken', 'forgotten',
|
|
|
22
22
|
LANE_WORDS = {'blocked': ('agent waiting', 'you'), 'broken': ('a check failed', 'bad'), 'time': ('coming up', 'working'), 'approve': ('needs your yes', 'you'),
|
|
23
23
|
'asked': ('asked you', 'working'), 'queued': ('waiting to start', 'working'), 'forgotten': ('slipped', 'info'), 'report': ('landed', 'info'), 'fyi': ('fyi', None),
|
|
24
24
|
'working': ('agent working', 'working')} # visible in band 5 until the agent stops or asks
|
|
25
|
+
# A chat cannot draw an icon, so it wears the emoji the desktop already uses - these ARE the marks in
|
|
26
|
+
# website/src/funnelPile.js (LANE_META/KIND_META), and the two tables must stay in step. A few KINDS
|
|
27
|
+
# outrank their lane: an agent's own finish and a report you set up share the 'report' lane.
|
|
28
|
+
LANE_MARKS = {'blocked': '👋', 'time': '⏱', 'approve': '✉️', 'broken': '🛠', 'asked': '🙋', 'queued': '⏳',
|
|
29
|
+
'forgotten': '🧵', 'report': '📄', 'fyi': '👀', 'working': '⚙️'}
|
|
30
|
+
KIND_MARKS = {'agentdone': '✅', 'wrapup': '🗂'}
|
|
31
|
+
# ...and where it came from. The desktop paints a brand logo here; a chat gets one emoji per source,
|
|
32
|
+
# and a source we have no mark for gets NONE - an invented glyph says something untrue about it.
|
|
33
|
+
CHANNEL_MARKS = {'email': '📧', 'teams': '👥', 'slack': '💬', 'telegram': '✈️', 'whatsapp': '📱', 'imessage': '📱',
|
|
34
|
+
'discord': '🎮', 'github': '🐙', 'gitlab': '🦊', 'jira': '🐞', 'sentry': '🚨', 'pagerduty': '📟',
|
|
35
|
+
'report': '📄', 'own': '📝', 'assistant': '✨', 'ai': '✨', 'idea': '💡', 'meeting': '📅',
|
|
36
|
+
'promise': '🤝', 'followup': '📨', 'prep': '📅', 'cold': '🥶'}
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def mark_for(item: dict) -> str:
|
|
40
|
+
"""The one emoji that identifies a row: its kind's when that says more than its lane's."""
|
|
41
|
+
if not item: return ''
|
|
42
|
+
return KIND_MARKS.get(str(item.get('kind') or '')) or LANE_MARKS.get(str(item.get('lane') or ''), '')
|
|
25
43
|
SOON_MIN, ALERT_MIN, STARTED_MIN = 120, 15, 5 # calendar visibility window; attention boundary; grace after the start
|
|
26
44
|
SETUP_GRACE_MIN = 15 # a walk-through the owner is still in does not raise its own hand
|
|
27
45
|
LATER_HOURS = 3 # "not now" - it comes back this much later
|
|
@@ -892,15 +910,34 @@ def notify(store, e: dict, by: str = 'assistant'):
|
|
|
892
910
|
store.set_funnel_state(f"notice:{e['tid']}", 'notice', by, None, json.dumps({k: v for k, v in e.items() if k != 'card'}))
|
|
893
911
|
|
|
894
912
|
|
|
913
|
+
def worked_now(store) -> set:
|
|
914
|
+
"""The tasks an agent is on RIGHT NOW - a live session or a running run."""
|
|
915
|
+
from . import terminal as term
|
|
916
|
+
try: live = {t.get('taskId') for t in term.live_sessions(tail=0, details=False)}
|
|
917
|
+
except Exception: live = set()
|
|
918
|
+
return {t for t in live if t} | {r['TaskId'] for r in store.running_runs() if r.get('TaskId')}
|
|
919
|
+
|
|
920
|
+
|
|
895
921
|
def notices(store, states: dict = None) -> list:
|
|
896
|
-
"""The strip's own notices: the watcher's events, in the shape of an alert, until Open or Later.
|
|
922
|
+
"""The strip's own notices: the watcher's events, in the shape of an alert, until Open or Later.
|
|
923
|
+
|
|
924
|
+
A `working` notice is a CLAIM about live state, so it is checked against live state when it is
|
|
925
|
+
read. The transition that would retire it (working -> done) is seen only by the in-memory watcher,
|
|
926
|
+
and a restart makes its first look remember instead of announce - so the row outlived its agent by
|
|
927
|
+
days and every fresh phone walk re-told it (the owner, 2026-09-10: "there are no agents open??").
|
|
928
|
+
A `done` notice is history and stands until the owner puts it down.
|
|
929
|
+
"""
|
|
897
930
|
states = states if states is not None else store.funnel_states()
|
|
898
|
-
out = []
|
|
931
|
+
out, worked = [], None
|
|
899
932
|
for k, st in states.items():
|
|
900
933
|
if not k.startswith('notice:') or st.get('Status') != 'notice' or not st.get('Note'): continue
|
|
901
934
|
try: e = json.loads(st['Note'])
|
|
902
935
|
except ValueError: continue
|
|
903
936
|
working = e.get('kind') == 'working'
|
|
937
|
+
if working:
|
|
938
|
+
if worked is None: worked = worked_now(store)
|
|
939
|
+
if e.get('tid') not in worked or (store.get_task(e.get('tid')) or {}).get('Status') in ('done', 'dropped'):
|
|
940
|
+
store.clear_funnel_state(k); continue
|
|
904
941
|
out.append({'key': k, 'item': f"{'agent' if working else 'task'}:{e.get('tid')}", 'kind': e.get('kind'), 'lane': 'working' if working else 'report',
|
|
905
942
|
'text': e.get('text') or '', 'notice': True, 'order_band': 3, 'at': st.get('At'), 'tid': e.get('tid'), 'ref': e.get('ref')})
|
|
906
943
|
return sorted(out, key=lambda a: str(a.get('at') or ''))
|