quoxcli 0.0.0-stage → 0.3.1
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.
- package/LICENSE +35 -0
- package/README.md +144 -2
- package/bin/quox.js +265 -0
- package/bin/quoxon.js +8 -0
- package/lib/api/endpoints/admin.js +151 -0
- package/lib/api/endpoints/aee.js +38 -0
- package/lib/api/endpoints/agent-attach.js +126 -0
- package/lib/api/endpoints/agent-schedules.js +26 -0
- package/lib/api/endpoints/agents.js +63 -0
- package/lib/api/endpoints/assistants.js +62 -0
- package/lib/api/endpoints/audit.js +14 -0
- package/lib/api/endpoints/auth.js +50 -0
- package/lib/api/endpoints/awareness.js +34 -0
- package/lib/api/endpoints/away.js +15 -0
- package/lib/api/endpoints/backup-schedules.js +20 -0
- package/lib/api/endpoints/backups.js +86 -0
- package/lib/api/endpoints/brain.js +140 -0
- package/lib/api/endpoints/budgets.js +62 -0
- package/lib/api/endpoints/calendarq.js +71 -0
- package/lib/api/endpoints/catalog.js +14 -0
- package/lib/api/endpoints/chat.js +61 -0
- package/lib/api/endpoints/consent.js +26 -0
- package/lib/api/endpoints/contactsq.js +68 -0
- package/lib/api/endpoints/conversations.js +46 -0
- package/lib/api/endpoints/custom-roles.js +92 -0
- package/lib/api/endpoints/divisions.js +56 -0
- package/lib/api/endpoints/domains.js +38 -0
- package/lib/api/endpoints/engagement.js +231 -0
- package/lib/api/endpoints/engine.js +11 -0
- package/lib/api/endpoints/entity.js +50 -0
- package/lib/api/endpoints/executions.js +45 -0
- package/lib/api/endpoints/fetchAllowlist.js +27 -0
- package/lib/api/endpoints/file.js +20 -0
- package/lib/api/endpoints/files.js +40 -0
- package/lib/api/endpoints/github.js +21 -0
- package/lib/api/endpoints/governance.js +43 -0
- package/lib/api/endpoints/hat.js +66 -0
- package/lib/api/endpoints/inbox.js +52 -0
- package/lib/api/endpoints/inboxq.js +120 -0
- package/lib/api/endpoints/index.js +242 -0
- package/lib/api/endpoints/invites.js +19 -0
- package/lib/api/endpoints/llm-policy.js +43 -0
- package/lib/api/endpoints/lucid.js +89 -0
- package/lib/api/endpoints/members.js +32 -0
- package/lib/api/endpoints/memory.js +94 -0
- package/lib/api/endpoints/mfa.js +32 -0
- package/lib/api/endpoints/mind.js +13 -0
- package/lib/api/endpoints/mon.js +184 -0
- package/lib/api/endpoints/notesq.js +43 -0
- package/lib/api/endpoints/objectives.js +68 -0
- package/lib/api/endpoints/orgs.js +38 -0
- package/lib/api/endpoints/plugins.js +152 -0
- package/lib/api/endpoints/powers.js +75 -0
- package/lib/api/endpoints/presence.js +33 -0
- package/lib/api/endpoints/prompts.js +69 -0
- package/lib/api/endpoints/qlarity.js +122 -0
- package/lib/api/endpoints/quoxbox.js +55 -0
- package/lib/api/endpoints/quoxchat.js +54 -0
- package/lib/api/endpoints/quoxflow.js +32 -0
- package/lib/api/endpoints/quoxplan.js +121 -0
- package/lib/api/endpoints/quoxseo.js +114 -0
- package/lib/api/endpoints/reach.js +47 -0
- package/lib/api/endpoints/router.js +17 -0
- package/lib/api/endpoints/runs.js +38 -0
- package/lib/api/endpoints/safety.js +53 -0
- package/lib/api/endpoints/scim.js +46 -0
- package/lib/api/endpoints/scopes.js +14 -0
- package/lib/api/endpoints/security.js +69 -0
- package/lib/api/endpoints/setup.js +90 -0
- package/lib/api/endpoints/skills.js +200 -0
- package/lib/api/endpoints/sso.js +67 -0
- package/lib/api/endpoints/stepup.js +20 -0
- package/lib/api/endpoints/tags.js +32 -0
- package/lib/api/endpoints/teams.js +56 -0
- package/lib/api/endpoints/telephony.js +46 -0
- package/lib/api/endpoints/tg.js +56 -0
- package/lib/api/endpoints/tools.js +50 -0
- package/lib/api/endpoints/traces.js +157 -0
- package/lib/api/endpoints/triggers.js +96 -0
- package/lib/api/endpoints/user-library.js +41 -0
- package/lib/api/endpoints/users.js +20 -0
- package/lib/api/endpoints/v1.js +585 -0
- package/lib/api/endpoints/vault.js +192 -0
- package/lib/api/endpoints/voice.js +22 -0
- package/lib/api/endpoints/volt.js +84 -0
- package/lib/api/endpoints/ward.js +47 -0
- package/lib/api/endpoints/webhooks.js +46 -0
- package/lib/api/endpoints/workflows.js +169 -0
- package/lib/api/endpoints/x.js +32 -0
- package/lib/api/endpoints.js +15 -0
- package/lib/commands/actions.js +61 -0
- package/lib/commands/admin.js +375 -0
- package/lib/commands/aee.js +99 -0
- package/lib/commands/agent.js +337 -0
- package/lib/commands/agentAttach.js +1528 -0
- package/lib/commands/approvals.js +119 -0
- package/lib/commands/aside.js +24 -0
- package/lib/commands/ask.js +144 -0
- package/lib/commands/assistant.js +169 -0
- package/lib/commands/auth.js +464 -0
- package/lib/commands/awareness.js +88 -0
- package/lib/commands/backup.js +377 -0
- package/lib/commands/brain.js +1085 -0
- package/lib/commands/budget.js +241 -0
- package/lib/commands/build.js +170 -0
- package/lib/commands/calendar.js +216 -0
- package/lib/commands/capabilities.js +34 -0
- package/lib/commands/chat.js +295 -0
- package/lib/commands/claude.js +380 -0
- package/lib/commands/config-cmd.js +106 -0
- package/lib/commands/consent.js +84 -0
- package/lib/commands/contacts.js +1016 -0
- package/lib/commands/contactsSelftest.js +132 -0
- package/lib/commands/conversation.js +105 -0
- package/lib/commands/dashboard.js +43 -0
- package/lib/commands/department.js +96 -0
- package/lib/commands/dev.js +620 -0
- package/lib/commands/devResident.js +326 -0
- package/lib/commands/devSupervisor.js +1062 -0
- package/lib/commands/discord.js +132 -0
- package/lib/commands/doctor.js +181 -0
- package/lib/commands/domain.js +114 -0
- package/lib/commands/done.js +347 -0
- package/lib/commands/engagement.js +597 -0
- package/lib/commands/entity.js +119 -0
- package/lib/commands/facebook.js +212 -0
- package/lib/commands/fetchAllowlist.js +71 -0
- package/lib/commands/file.js +165 -0
- package/lib/commands/fleet.js +1189 -0
- package/lib/commands/governance.js +647 -0
- package/lib/commands/gripe.js +355 -0
- package/lib/commands/hat.js +374 -0
- package/lib/commands/helpers.js +96 -0
- package/lib/commands/hyperion.js +58 -0
- package/lib/commands/inbox.js +173 -0
- package/lib/commands/instagram.js +264 -0
- package/lib/commands/integration.js +100 -0
- package/lib/commands/inventory.js +115 -0
- package/lib/commands/invites.js +45 -0
- package/lib/commands/licence.js +139 -0
- package/lib/commands/linkedin.js +178 -0
- package/lib/commands/llm-policy.js +117 -0
- package/lib/commands/llm.js +45 -0
- package/lib/commands/lucid.js +597 -0
- package/lib/commands/mail.js +531 -0
- package/lib/commands/memory.js +361 -0
- package/lib/commands/mind.js +71 -0
- package/lib/commands/mon.js +581 -0
- package/lib/commands/monitoring.js +86 -0
- package/lib/commands/motion.js +22 -0
- package/lib/commands/notes.js +1246 -0
- package/lib/commands/notesBatch.js +160 -0
- package/lib/commands/notesBridges.js +56 -0
- package/lib/commands/notesLinks.js +60 -0
- package/lib/commands/notification.js +57 -0
- package/lib/commands/nucleus.js +123 -0
- package/lib/commands/objective.js +240 -0
- package/lib/commands/offload.js +89 -0
- package/lib/commands/onboard.js +329 -0
- package/lib/commands/open.js +56 -0
- package/lib/commands/org.js +276 -0
- package/lib/commands/peek.js +28 -0
- package/lib/commands/plan.js +944 -0
- package/lib/commands/plugin-doctor.js +1091 -0
- package/lib/commands/plugin.js +649 -0
- package/lib/commands/policy.js +78 -0
- package/lib/commands/powers.js +296 -0
- package/lib/commands/presence.js +89 -0
- package/lib/commands/prompt.js +350 -0
- package/lib/commands/provider.js +159 -0
- package/lib/commands/qlarity.js +706 -0
- package/lib/commands/quoxbox.js +270 -0
- package/lib/commands/quoxchat.js +275 -0
- package/lib/commands/quoxseo.js +378 -0
- package/lib/commands/reach.js +159 -0
- package/lib/commands/role.js +286 -0
- package/lib/commands/router.js +77 -0
- package/lib/commands/rows.js +358 -0
- package/lib/commands/run.js +187 -0
- package/lib/commands/safety.js +107 -0
- package/lib/commands/scim.js +88 -0
- package/lib/commands/scope.js +58 -0
- package/lib/commands/security.js +157 -0
- package/lib/commands/server.js +292 -0
- package/lib/commands/service.js +89 -0
- package/lib/commands/settings.js +99 -0
- package/lib/commands/setup.js +569 -0
- package/lib/commands/skillManifest.js +113 -0
- package/lib/commands/skillRightsFormat.js +38 -0
- package/lib/commands/skills.js +687 -0
- package/lib/commands/slot.js +1451 -0
- package/lib/commands/sso.js +229 -0
- package/lib/commands/status.js +137 -0
- package/lib/commands/tag.js +91 -0
- package/lib/commands/team.js +142 -0
- package/lib/commands/tg.js +240 -0
- package/lib/commands/tiktok.js +218 -0
- package/lib/commands/tips.js +23 -0
- package/lib/commands/tool.js +304 -0
- package/lib/commands/trace.js +530 -0
- package/lib/commands/trigger.js +223 -0
- package/lib/commands/update.js +46 -0
- package/lib/commands/vault.js +1447 -0
- package/lib/commands/vm.js +237 -0
- package/lib/commands/voice.js +213 -0
- package/lib/commands/volt.js +570 -0
- package/lib/commands/walkthrough.js +32 -0
- package/lib/commands/watch.js +167 -0
- package/lib/commands/webhook.js +137 -0
- package/lib/commands/welcome.js +14 -0
- package/lib/commands/workflow.js +660 -0
- package/lib/commands/x.js +448 -0
- package/lib/core/actingAs.js +136 -0
- package/lib/core/activeSummary.js +186 -0
- package/lib/core/aee.js +53 -0
- package/lib/core/agentAlias.js +163 -0
- package/lib/core/approvalContext.js +61 -0
- package/lib/core/aside.js +54 -0
- package/lib/core/askDecision.js +60 -0
- package/lib/core/auth.js +501 -0
- package/lib/core/banner.js +143 -0
- package/lib/core/brainPrime.js +90 -0
- package/lib/core/callerSource.js +129 -0
- package/lib/core/claudeLs.js +94 -0
- package/lib/core/cliBuild.js +62 -0
- package/lib/core/client.js +693 -0
- package/lib/core/commandRegistrars.js +121 -0
- package/lib/core/completions.js +163 -0
- package/lib/core/config.js +669 -0
- package/lib/core/confirm.js +37 -0
- package/lib/core/customHats.js +168 -0
- package/lib/core/deeplinks.js +76 -0
- package/lib/core/doneEngineFetch.js +227 -0
- package/lib/core/errors.js +441 -0
- package/lib/core/global-flags.js +190 -0
- package/lib/core/governedSession.js +402 -0
- package/lib/core/hyperion.js +102 -0
- package/lib/core/inboxResolution.js +65 -0
- package/lib/core/lazyCommands.js +122 -0
- package/lib/core/liveSessionsReport.js +112 -0
- package/lib/core/offload.js +504 -0
- package/lib/core/org-override.js +52 -0
- package/lib/core/orgResolve.js +70 -0
- package/lib/core/output.js +239 -0
- package/lib/core/peek.js +126 -0
- package/lib/core/prodEnv.js +7 -0
- package/lib/core/profileUsers.js +51 -0
- package/lib/core/promptCancel.js +18 -0
- package/lib/core/quoxglyph.js +158 -0
- package/lib/core/repoPath.js +11 -0
- package/lib/core/residentAlarm.js +282 -0
- package/lib/core/residentAuth.js +243 -0
- package/lib/core/residentPump.js +229 -0
- package/lib/core/residentSeed.js +106 -0
- package/lib/core/rootOptions.js +39 -0
- package/lib/core/router.js +168 -0
- package/lib/core/selfUpdate.js +188 -0
- package/lib/core/sessionHeartbeat.js +192 -0
- package/lib/core/sessionsOverview.js +224 -0
- package/lib/core/slotKeeper.js +527 -0
- package/lib/core/slotWake.js +120 -0
- package/lib/core/supervisorEngine.js +933 -0
- package/lib/core/systemdInstaller.js +256 -0
- package/lib/core/version.js +87 -0
- package/lib/core/vm.js +361 -0
- package/lib/core/vmTui.js +86 -0
- package/lib/core/walkthrough.js +35 -0
- package/lib/hyperion/deck.js +29 -0
- package/lib/lucid/LucidCard.jsx +68 -0
- package/lib/lucid/LucidIntro.jsx +92 -0
- package/lib/lucid/blind.js +71 -0
- package/lib/lucid/cards.js +86 -0
- package/lib/lucid/deck.js +32 -0
- package/lib/lucid/fmt.js +139 -0
- package/lib/lucid/markdown.js +81 -0
- package/lib/lucid/motion.js +56 -0
- package/lib/lucid/sitrep.js +173 -0
- package/lib/plugins/loader.js +93 -0
- package/lib/quoxon/activity.js +116 -0
- package/lib/quoxon/aocl.js +117 -0
- package/lib/quoxon/api.js +952 -0
- package/lib/quoxon/app.js +4032 -0
- package/lib/quoxon/approvalFacts.js +94 -0
- package/lib/quoxon/approvalsView.js +325 -0
- package/lib/quoxon/asideView.js +40 -0
- package/lib/quoxon/ask.js +121 -0
- package/lib/quoxon/away.js +52 -0
- package/lib/quoxon/bang.js +224 -0
- package/lib/quoxon/bulkRisk.js +17 -0
- package/lib/quoxon/capabilities.js +82 -0
- package/lib/quoxon/cells.js +53 -0
- package/lib/quoxon/commandShort.js +112 -0
- package/lib/quoxon/commands.js +479 -0
- package/lib/quoxon/connect.js +654 -0
- package/lib/quoxon/connectCard.js +45 -0
- package/lib/quoxon/consent.js +86 -0
- package/lib/quoxon/context.js +68 -0
- package/lib/quoxon/copy.js +34 -0
- package/lib/quoxon/countdownLine.js +11 -0
- package/lib/quoxon/demo.autoplay.js +29 -0
- package/lib/quoxon/demo.fixtures.js +24 -0
- package/lib/quoxon/demo.js +76 -0
- package/lib/quoxon/devObserver.js +20 -0
- package/lib/quoxon/dividerAocl.js +191 -0
- package/lib/quoxon/dividerRule.js +57 -0
- package/lib/quoxon/dragonModes.js +150 -0
- package/lib/quoxon/dragonView.js +41 -0
- package/lib/quoxon/earlyInput.js +40 -0
- package/lib/quoxon/echo.js +15 -0
- package/lib/quoxon/egg.js +36 -0
- package/lib/quoxon/events.js +677 -0
- package/lib/quoxon/feedback.js +50 -0
- package/lib/quoxon/gallery.js +237 -0
- package/lib/quoxon/handoffOffer.js +78 -0
- package/lib/quoxon/hatSwitch.js +37 -0
- package/lib/quoxon/headless.js +161 -0
- package/lib/quoxon/hostUrls.js +41 -0
- package/lib/quoxon/indicator.js +197 -0
- package/lib/quoxon/intent.js +52 -0
- package/lib/quoxon/keys.js +164 -0
- package/lib/quoxon/launcher.js +103 -0
- package/lib/quoxon/main.js +176 -0
- package/lib/quoxon/markdown.js +341 -0
- package/lib/quoxon/markdownSettle.js +19 -0
- package/lib/quoxon/matrixDomains.json +46 -0
- package/lib/quoxon/mentions.js +90 -0
- package/lib/quoxon/mind.js +103 -0
- package/lib/quoxon/mode.js +46 -0
- package/lib/quoxon/model.js +311 -0
- package/lib/quoxon/modelPrefs.js +345 -0
- package/lib/quoxon/motion/clock.js +70 -0
- package/lib/quoxon/motion/color.js +35 -0
- package/lib/quoxon/motion/frame.js +14 -0
- package/lib/quoxon/motion/level.js +91 -0
- package/lib/quoxon/motion/reveal.js +54 -0
- package/lib/quoxon/motion/ring.js +79 -0
- package/lib/quoxon/mouse.js +211 -0
- package/lib/quoxon/noai.js +57 -0
- package/lib/quoxon/objects/agent.js +61 -0
- package/lib/quoxon/objects/away.js +72 -0
- package/lib/quoxon/objects/crystal.js +100 -0
- package/lib/quoxon/objects/fetch.js +182 -0
- package/lib/quoxon/objects/fleet.js +149 -0
- package/lib/quoxon/objects/health.js +186 -0
- package/lib/quoxon/objects/index.js +106 -0
- package/lib/quoxon/objects/ladder.js +71 -0
- package/lib/quoxon/objects/memory.js +44 -0
- package/lib/quoxon/objects/mirror.js +137 -0
- package/lib/quoxon/objects/model.js +118 -0
- package/lib/quoxon/objects/peek.js +17 -0
- package/lib/quoxon/objects/proof.js +110 -0
- package/lib/quoxon/objects/prove.js +130 -0
- package/lib/quoxon/objects/replay.js +125 -0
- package/lib/quoxon/objects/sitrep.js +58 -0
- package/lib/quoxon/objects/skill.js +105 -0
- package/lib/quoxon/objects/view.js +87 -0
- package/lib/quoxon/objects/workflow.js +119 -0
- package/lib/quoxon/observers.js +42 -0
- package/lib/quoxon/onboarding/copy.js +263 -0
- package/lib/quoxon/onboarding/credential.js +108 -0
- package/lib/quoxon/onboarding/detect.js +22 -0
- package/lib/quoxon/onboarding/dockerOffer.js +45 -0
- package/lib/quoxon/onboarding/failureCause.js +33 -0
- package/lib/quoxon/onboarding/finish.js +134 -0
- package/lib/quoxon/onboarding/index.js +94 -0
- package/lib/quoxon/onboarding/installRunner.js +284 -0
- package/lib/quoxon/onboarding/intent.js +53 -0
- package/lib/quoxon/onboarding/journey.js +73 -0
- package/lib/quoxon/onboarding/preflight.js +156 -0
- package/lib/quoxon/onboarding/probe.js +65 -0
- package/lib/quoxon/onboarding/runner.js +58 -0
- package/lib/quoxon/onboarding/view.js +682 -0
- package/lib/quoxon/orgLabel.js +10 -0
- package/lib/quoxon/orgSwitch.js +33 -0
- package/lib/quoxon/ownBroadcast.js +22 -0
- package/lib/quoxon/palette.js +43 -0
- package/lib/quoxon/print.js +98 -0
- package/lib/quoxon/profileSwitch.js +53 -0
- package/lib/quoxon/promptHistory.js +41 -0
- package/lib/quoxon/providers.js +48 -0
- package/lib/quoxon/qart.js +348 -0
- package/lib/quoxon/qdragon.js +410 -0
- package/lib/quoxon/qdragonFly.js +61 -0
- package/lib/quoxon/qdragonScenes.js +282 -0
- package/lib/quoxon/qeyes.js +213 -0
- package/lib/quoxon/qglyph.js +143 -0
- package/lib/quoxon/qhello.js +58 -0
- package/lib/quoxon/qmotion.js +132 -0
- package/lib/quoxon/render.js +1167 -0
- package/lib/quoxon/resend.js +72 -0
- package/lib/quoxon/roster.js +307 -0
- package/lib/quoxon/route.js +39 -0
- package/lib/quoxon/rows.js +711 -0
- package/lib/quoxon/rowsStore.js +80 -0
- package/lib/quoxon/rules.js +130 -0
- package/lib/quoxon/screenContext.js +150 -0
- package/lib/quoxon/screenFrames.js +254 -0
- package/lib/quoxon/session.js +238 -0
- package/lib/quoxon/sessionDead.js +17 -0
- package/lib/quoxon/signin.js +125 -0
- package/lib/quoxon/skills.js +30 -0
- package/lib/quoxon/slash/agents.js +112 -0
- package/lib/quoxon/slash/argSources.js +65 -0
- package/lib/quoxon/slash/args.js +65 -0
- package/lib/quoxon/slash/automate.js +89 -0
- package/lib/quoxon/slash/broadcast.js +140 -0
- package/lib/quoxon/slash/broadcastRun.js +108 -0
- package/lib/quoxon/slash/card.js +30 -0
- package/lib/quoxon/slash/challenge.js +114 -0
- package/lib/quoxon/slash/cliCommand.js +53 -0
- package/lib/quoxon/slash/cliLeaves.json +948 -0
- package/lib/quoxon/slash/compact.js +90 -0
- package/lib/quoxon/slash/confirm.js +30 -0
- package/lib/quoxon/slash/crystallize.js +69 -0
- package/lib/quoxon/slash/dashboard.js +136 -0
- package/lib/quoxon/slash/draft.js +54 -0
- package/lib/quoxon/slash/hatWalk.js +206 -0
- package/lib/quoxon/slash/inverses.js +77 -0
- package/lib/quoxon/slash/killer.js +24 -0
- package/lib/quoxon/slash/killerApi.js +16 -0
- package/lib/quoxon/slash/ledger.js +88 -0
- package/lib/quoxon/slash/listView.js +30 -0
- package/lib/quoxon/slash/modelCmd.js +261 -0
- package/lib/quoxon/slash/palette.js +118 -0
- package/lib/quoxon/slash/paletteController.js +169 -0
- package/lib/quoxon/slash/panels.js +116 -0
- package/lib/quoxon/slash/picker.js +56 -0
- package/lib/quoxon/slash/restart.js +202 -0
- package/lib/quoxon/slash/resume.js +59 -0
- package/lib/quoxon/slash/rowCmd.js +157 -0
- package/lib/quoxon/slash/rowWalk.js +175 -0
- package/lib/quoxon/slash/settings.js +60 -0
- package/lib/quoxon/slash/spawn.js +177 -0
- package/lib/quoxon/slash/stepUp.js +274 -0
- package/lib/quoxon/slash/stepUpCard.js +60 -0
- package/lib/quoxon/slash/stepUpCmds.js +78 -0
- package/lib/quoxon/slash/undoApi.js +38 -0
- package/lib/quoxon/slash/undoCmd.js +49 -0
- package/lib/quoxon/slash/undoWish.js +15 -0
- package/lib/quoxon/slash/watch.js +164 -0
- package/lib/quoxon/slash/wish.js +23 -0
- package/lib/quoxon/slash/wishCmd.js +32 -0
- package/lib/quoxon/slotChat.js +300 -0
- package/lib/quoxon/slotFormat.js +195 -0
- package/lib/quoxon/slotRun.js +131 -0
- package/lib/quoxon/sound/index.js +186 -0
- package/lib/quoxon/sound/mapping.js +120 -0
- package/lib/quoxon/sound/output.js +52 -0
- package/lib/quoxon/sound/synth.js +122 -0
- package/lib/quoxon/stray.js +22 -0
- package/lib/quoxon/suggestCommand.js +59 -0
- package/lib/quoxon/term.js +141 -0
- package/lib/quoxon/textBuffer.js +416 -0
- package/lib/quoxon/theme.js +286 -0
- package/lib/quoxon/tips.js +433 -0
- package/lib/quoxon/toolError.js +41 -0
- package/lib/quoxon/transcript.js +188 -0
- package/lib/quoxon/web/qface.js +89 -0
- package/lib/quoxon/welcome.js +52 -0
- package/lib/quoxon/whatsnew.js +136 -0
- package/lib/quoxon/whatsnew.jsonl +89 -0
- package/lib/quoxon/windowOrg.js +113 -0
- package/lib/server/diag.js +129 -0
- package/lib/server/doctor.js +71 -0
- package/lib/server/install.js +149 -0
- package/lib/server/pluginEnable.js +161 -0
- package/lib/server/skew.js +56 -0
- package/lib/server/snapshot.js +131 -0
- package/lib/server/status.js +128 -0
- package/lib/server/sys.js +65 -0
- package/lib/server/uninstall.js +63 -0
- package/lib/server/update.js +234 -0
- package/lib/toolOutputShape.js +315 -0
- package/lib/tui/app.jsx +289 -0
- package/lib/tui/components/Breadcrumb.jsx +42 -0
- package/lib/tui/components/ChatInput.jsx +68 -0
- package/lib/tui/components/Confirm.jsx +32 -0
- package/lib/tui/components/DataTable.jsx +270 -0
- package/lib/tui/components/DetailPanel.jsx +241 -0
- package/lib/tui/components/HelpOverlay.jsx +67 -0
- package/lib/tui/components/Layout.jsx +129 -0
- package/lib/tui/components/Palette.jsx +141 -0
- package/lib/tui/components/SearchInput.jsx +125 -0
- package/lib/tui/components/Sparkline.jsx +55 -0
- package/lib/tui/components/StatusBar.jsx +133 -0
- package/lib/tui/components/Toast.jsx +52 -0
- package/lib/tui/hooks/useApi.js +44 -0
- package/lib/tui/hooks/useHealth.js +90 -0
- package/lib/tui/hooks/usePolledApi.js +86 -0
- package/lib/tui/hooks/useTerminalSize.js +46 -0
- package/lib/tui/index.js +56 -0
- package/lib/tui/intro/Intro.jsx +332 -0
- package/lib/tui/jsx-loader.js +32 -0
- package/lib/tui/keys.js +87 -0
- package/lib/tui/registry.js +49 -0
- package/lib/tui/theme.js +103 -0
- package/lib/tui/views/AdminView.jsx +398 -0
- package/lib/tui/views/AgentView.jsx +204 -0
- package/lib/tui/views/BackupView.jsx +225 -0
- package/lib/tui/views/BrainView.jsx +223 -0
- package/lib/tui/views/ChatView.jsx +468 -0
- package/lib/tui/views/FilesView.jsx +218 -0
- package/lib/tui/views/FleetView.jsx +234 -0
- package/lib/tui/views/GovernanceView.jsx +453 -0
- package/lib/tui/views/HealthView.jsx +216 -0
- package/lib/tui/views/HyperionDeck.jsx +79 -0
- package/lib/tui/views/InboxView.jsx +228 -0
- package/lib/tui/views/LucidDeck.jsx +719 -0
- package/lib/tui/views/MainMenu.jsx +123 -0
- package/lib/tui/views/MemoryView.jsx +377 -0
- package/lib/tui/views/ObjectivesView.jsx +230 -0
- package/lib/tui/views/OrgView.jsx +229 -0
- package/lib/tui/views/ToolView.jsx +221 -0
- package/lib/tui/views/VaultView.jsx +422 -0
- package/lib/tui/views/WorkflowView.jsx +329 -0
- package/package.json +75 -4
|
@@ -0,0 +1,719 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* LucidDeck: the full-screen live LUCID situation deck (`quox lucid deck`), in the Quoxon look.
|
|
3
|
+
*
|
|
4
|
+
* One vertical cursor. Up/Down (j/k) move through the focused pane's list,
|
|
5
|
+
* Tab / Shift-Tab switch pane (Needs you, Since you looked, Areas), Enter
|
|
6
|
+
* opens / acts, Esc backs out, ? is help, q quits. Every action echoes what it
|
|
7
|
+
* did and why in a toast; keys that do nothing say so.
|
|
8
|
+
*
|
|
9
|
+
* Look (Quoxon design system): theme roles for every colour, headings in the sky to violet gradient, the
|
|
10
|
+
* perpetual Q as the status glyph (needs you = waiting, fetching = working, unreachable = failing, quiet =
|
|
11
|
+
* resting), areas and Needs-you items as inline object cards (lucid/cards.js) that drop in through katakana.
|
|
12
|
+
* Motion: ONE shared clock (quoxon/motion/clock.js). Frames are demanded only while something moves; idle
|
|
13
|
+
* the deck has no animation timer and writes nothing (the only recurring work is the fetch interval).
|
|
14
|
+
*
|
|
15
|
+
* Props are injected (api, intervalMs, intro) so the component is testable
|
|
16
|
+
* without a network. api = { situation(), measure(ref, id), teach(body), ack(seq) }.
|
|
17
|
+
* Never shows an empty all-quiet view on failure: errors render in the header
|
|
18
|
+
* and the last good data stays on screen, marked STALE.
|
|
19
|
+
*/
|
|
20
|
+
import React, { useState, useEffect, useRef, useCallback, useMemo } from 'react';
|
|
21
|
+
import { Box, Text, useInput, useApp, useStdout } from 'ink';
|
|
22
|
+
import { wrap, clip, ageOf, meter, clockLondon, whenText, changedWords, blindSpots } from '../../lucid/fmt.js';
|
|
23
|
+
import { LucidIntro } from '../../lucid/LucidIntro.jsx';
|
|
24
|
+
import { LucidCard } from '../../lucid/LucidCard.jsx';
|
|
25
|
+
import { lucidArea, lucidNeed, cardHeight } from '../../lucid/cards.js';
|
|
26
|
+
import { glyphStateOf, glyphAnimating, revealT, deckDemands, nextMinuteAt, SETTLE_MS, REVEAL_MS, STAGGER_MS } from '../../lucid/motion.js';
|
|
27
|
+
import { QGlyph } from '../../quoxon/qmotion.js';
|
|
28
|
+
import { c, gradient, mono } from '../../quoxon/theme.js';
|
|
29
|
+
import { Clock, motionAllowed } from '../../quoxon/motion/clock.js';
|
|
30
|
+
|
|
31
|
+
const PANES = ['needs', 'changed', 'areas'];
|
|
32
|
+
const PANE_TITLE = { needs: 'NEEDS YOU', changed: 'SINCE YOU LOOKED', areas: 'AREAS' };
|
|
33
|
+
const PANE_WORD = { needs: 'Needs you', changed: 'Since you looked', areas: 'Areas' };
|
|
34
|
+
const BAR_BG = '#1E3A8A'; // selected row bar
|
|
35
|
+
const NEW_MS = 4000;
|
|
36
|
+
const GHOST_MS = 4000;
|
|
37
|
+
const TOAST_MS = 3000;
|
|
38
|
+
|
|
39
|
+
const pad = (s, w) => { const a = [...String(s)]; return a.length >= w ? a.join('') : s + ' '.repeat(w - a.length); };
|
|
40
|
+
const nm = (x) => (typeof x === 'string' ? x : (x && (x.label || x.name || x.L0 || x.id)) || '');
|
|
41
|
+
|
|
42
|
+
export function itemsLines(it, expanded, w, base = 1) {
|
|
43
|
+
if (!expanded) return base;
|
|
44
|
+
const a = it.L1 || {};
|
|
45
|
+
let n = base;
|
|
46
|
+
for (const v of [a.who, a.what, a.why, whenText(a.when)]) if (v) n += wrap(v, w - 8).length;
|
|
47
|
+
if ((a.affected || []).length) n += 1;
|
|
48
|
+
const ms = (it.L2 && it.L2.measures) || [];
|
|
49
|
+
if (ms.length) n += 1 + ms.length + (ms.some((m) => m.command) ? 1 : 0);
|
|
50
|
+
else n += 1;
|
|
51
|
+
return n + 1;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/** Window over items of known line heights so the selected one is visible within `rows` lines. */
|
|
55
|
+
export function windowHeights(heights, sel, rows) {
|
|
56
|
+
let start = 0;
|
|
57
|
+
const fits = (s) => {
|
|
58
|
+
let used = 0;
|
|
59
|
+
for (let i = s; i <= sel; i++) used += heights[i];
|
|
60
|
+
return used <= rows;
|
|
61
|
+
};
|
|
62
|
+
while (start < sel && !fits(start)) start++;
|
|
63
|
+
let used = 0; let end = start;
|
|
64
|
+
while (end < heights.length && used + heights[end] <= rows) { used += heights[end]; end++; }
|
|
65
|
+
if (end === start && heights.length) end = start + 1;
|
|
66
|
+
return { start, end };
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** Window of items to render so the selected one is visible within `rows` lines. */
|
|
70
|
+
export function windowItems(items, sel, expandedRef, rows, w, base) {
|
|
71
|
+
const heights = items.map((it) => itemsLines(it, it.ref === expandedRef, w, base ? base(it) : 1));
|
|
72
|
+
return windowHeights(heights, sel, rows);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/** Window by rendered line heights; when the list overflows, 2 lines are kept for the "N more" markers. */
|
|
76
|
+
export function fitHeights(heights, sel, rows) {
|
|
77
|
+
let win = windowHeights(heights, sel, rows);
|
|
78
|
+
if (win.start > 0 || win.end < heights.length) win = windowHeights(heights, sel, Math.max(1, rows - 2));
|
|
79
|
+
return win;
|
|
80
|
+
}
|
|
81
|
+
export function fitWindow(items, sel, expandedRef, rows, w, base) {
|
|
82
|
+
let win = windowItems(items, sel, expandedRef, rows, w, base);
|
|
83
|
+
if (win.start > 0 || win.end < items.length) win = windowItems(items, sel, expandedRef, Math.max(1, rows - 2), w, base);
|
|
84
|
+
return win;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export function signature(sit) {
|
|
88
|
+
if (!sit) return '';
|
|
89
|
+
return JSON.stringify([
|
|
90
|
+
(sit.needs_you || []).map((i) => i.ref + i.urgency),
|
|
91
|
+
(sit.changed || []).map((c) => c.ref + c.ts),
|
|
92
|
+
(sit.areas || []).map((a) => a.state),
|
|
93
|
+
]);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** A heading in the Quoxon gradient (plain muted under NO_COLOR). */
|
|
97
|
+
function Grad({ text, bold = true }) {
|
|
98
|
+
return <>{gradient(text).map((g, i) => <Text key={i} bold={bold} color={g.color}>{g.ch}</Text>)}</>;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function Hints({ items }) {
|
|
102
|
+
return (
|
|
103
|
+
<Text wrap="truncate" color={c('muted')}>
|
|
104
|
+
{items.map(([k, l], i) => (
|
|
105
|
+
<Text key={k + l}>{i ? ' ' : ''}<Text bold color={c('key')}>{k}</Text><Text color={c('muted')}> {l}</Text></Text>
|
|
106
|
+
))}
|
|
107
|
+
</Text>
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** A pane title rule: gradient heading when focused, quiet when not, a rule running to the pane's edge. */
|
|
112
|
+
function PaneTitle({ title, count, focused, width, note }) {
|
|
113
|
+
const head = ` ${title} `;
|
|
114
|
+
const tail = `${count === '' ? '' : ` ${count}`}${note ? ` ${note}` : ''} `;
|
|
115
|
+
const fill = Math.max(0, width - 3 - [...head].length - [...tail].length);
|
|
116
|
+
return (
|
|
117
|
+
<Text wrap="truncate">
|
|
118
|
+
<Text color={focused ? c('sky') : c('faint')}>{focused ? '▶' : '─'}</Text>
|
|
119
|
+
<Text color={focused ? c('sky') : c('faint')}>─</Text>
|
|
120
|
+
{focused ? <Grad text={head} /> : <Text color={c('muted')} bold>{head}</Text>}
|
|
121
|
+
<Text color={focused ? c('value') : c('muted')}>{tail}</Text>
|
|
122
|
+
<Text color={focused ? c('sky') : c('faint')}>{'─'.repeat(fill)}</Text>
|
|
123
|
+
</Text>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function LucidDeck({ api, intervalMs = 10000, now = () => Date.now(), onExit, intro = false, motion }) {
|
|
128
|
+
const { exit } = useApp();
|
|
129
|
+
const { stdout } = useStdout();
|
|
130
|
+
const [, setSizeTick] = useState(0);
|
|
131
|
+
const cols = (stdout && stdout.columns) || 100;
|
|
132
|
+
const rows = (stdout && stdout.rows) || 30;
|
|
133
|
+
const motionOn = useMemo(() => (motion != null ? Boolean(motion) : motionAllowed(stdout && stdout.isTTY)), [motion, stdout]);
|
|
134
|
+
|
|
135
|
+
const [phase, setPhase] = useState(intro ? 'intro' : 'deck');
|
|
136
|
+
const [data, setData] = useState(null); // last good response
|
|
137
|
+
const [err, setErr] = useState(null);
|
|
138
|
+
const [fetchedAt, setFetchedAt] = useState(null);
|
|
139
|
+
const [fetching, setFetching] = useState(false);
|
|
140
|
+
const [, setFrame] = useState(0); // bumped by the shared clock
|
|
141
|
+
const [pane, setPane] = useState('needs');
|
|
142
|
+
const [filter, setFilter] = useState(null); // area key or null
|
|
143
|
+
const [sel, setSel] = useState(0);
|
|
144
|
+
const [chSel, setChSel] = useState(0);
|
|
145
|
+
const [arSel, setArSel] = useState(0); // 0 = All areas, 1.. = areas
|
|
146
|
+
const [expanded, setExpanded] = useState(null);
|
|
147
|
+
const [mSel, setMSel] = useState(0);
|
|
148
|
+
const [confirm, setConfirm] = useState(null); // {text, extra, run}
|
|
149
|
+
const [note, setNote] = useState(null); // {text, tone, at}
|
|
150
|
+
const [help, setHelp] = useState(false);
|
|
151
|
+
const [hinted, setHinted] = useState(true); // first-launch hint until first key
|
|
152
|
+
const [arrived, setArrived] = useState(0);
|
|
153
|
+
const sigRef = useRef('');
|
|
154
|
+
const prevNeeds = useRef(null);
|
|
155
|
+
const newAt = useRef(new Map());
|
|
156
|
+
const ghosts = useRef([]);
|
|
157
|
+
const inflight = useRef(false);
|
|
158
|
+
const startAt = useRef(new Map()); // card key -> when its drop-in starts (may be a few ms ahead)
|
|
159
|
+
const revealUntil = useRef(0);
|
|
160
|
+
const errAt = useRef(null);
|
|
161
|
+
const glyph = useRef({ state: null, since: null });
|
|
162
|
+
|
|
163
|
+
const flash = useCallback((text, tone = 'ok') => {
|
|
164
|
+
setNote({ text, tone, at: Date.now() });
|
|
165
|
+
}, []);
|
|
166
|
+
|
|
167
|
+
useEffect(() => {
|
|
168
|
+
if (!stdout || !stdout.on) return undefined;
|
|
169
|
+
const f = () => setSizeTick((x) => x + 1);
|
|
170
|
+
stdout.on('resize', f);
|
|
171
|
+
return () => stdout.off && stdout.off('resize', f);
|
|
172
|
+
}, [stdout]);
|
|
173
|
+
|
|
174
|
+
// Props held in refs so `refresh` is created once: a default `now = () => Date.now()` is a new
|
|
175
|
+
// function every render, and depending on it restarted the fetch loop on every redraw (the
|
|
176
|
+
// deck fetched continuously and ran out of memory after 8.5h, 2026-10-11).
|
|
177
|
+
const apiRef = useRef(api);
|
|
178
|
+
const nowRef = useRef(now);
|
|
179
|
+
apiRef.current = api;
|
|
180
|
+
nowRef.current = now;
|
|
181
|
+
|
|
182
|
+
const startCard = (key, at) => {
|
|
183
|
+
startAt.current.set(key, at);
|
|
184
|
+
revealUntil.current = Math.max(revealUntil.current, at + REVEAL_MS);
|
|
185
|
+
if (startAt.current.size > 400) for (const [k, v] of startAt.current) if (Date.now() - v > 60000) startAt.current.delete(k);
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
const refresh = useCallback(async () => {
|
|
189
|
+
if (inflight.current) return;
|
|
190
|
+
inflight.current = true;
|
|
191
|
+
setFetching(true);
|
|
192
|
+
try {
|
|
193
|
+
const res = await apiRef.current.situation();
|
|
194
|
+
const sit = res && res.situation;
|
|
195
|
+
const cur = (sit && sit.needs_you) || [];
|
|
196
|
+
const at = Date.now();
|
|
197
|
+
if (prevNeeds.current) {
|
|
198
|
+
const have = new Set(prevNeeds.current.map((i) => i.ref));
|
|
199
|
+
const nowRefs = new Set(cur.map((i) => i.ref));
|
|
200
|
+
let fresh = 0;
|
|
201
|
+
for (const i of cur) if (!have.has(i.ref)) { newAt.current.set(i.ref, at); startCard('n:' + i.ref, at); fresh++; }
|
|
202
|
+
for (const i of prevNeeds.current) if (!nowRefs.has(i.ref)) ghosts.current.push({ item: i, at });
|
|
203
|
+
if (fresh) setArrived((n) => n + fresh);
|
|
204
|
+
} else {
|
|
205
|
+
// the first board: cards drop in top to bottom
|
|
206
|
+
cur.forEach((i, k) => startCard('n:' + i.ref, at + k * STAGGER_MS));
|
|
207
|
+
((sit && sit.areas) || []).forEach((a, k) => startCard('a:' + a.area, at + (k + 1) * STAGGER_MS));
|
|
208
|
+
startCard('a:', at);
|
|
209
|
+
}
|
|
210
|
+
prevNeeds.current = cur;
|
|
211
|
+
sigRef.current = signature(sit);
|
|
212
|
+
setData(res);
|
|
213
|
+
setErr(null);
|
|
214
|
+
errAt.current = null;
|
|
215
|
+
setFetchedAt(nowRef.current());
|
|
216
|
+
} catch (e) {
|
|
217
|
+
if (errAt.current == null) errAt.current = Date.now();
|
|
218
|
+
setErr(e);
|
|
219
|
+
} finally {
|
|
220
|
+
inflight.current = false;
|
|
221
|
+
setFetching(false);
|
|
222
|
+
}
|
|
223
|
+
}, []);
|
|
224
|
+
|
|
225
|
+
useEffect(() => {
|
|
226
|
+
refresh();
|
|
227
|
+
const t = setInterval(refresh, intervalMs);
|
|
228
|
+
return () => clearInterval(t);
|
|
229
|
+
}, [refresh, intervalMs]);
|
|
230
|
+
|
|
231
|
+
// ---- the one clock -------------------------------------------------------------------------------
|
|
232
|
+
// Each render declares what it still needs (see lucid/motion.js); with nothing declared there is no timer.
|
|
233
|
+
const clockRef = useRef(null);
|
|
234
|
+
if (!clockRef.current) clockRef.current = new Clock(() => setFrame((x) => x + 1));
|
|
235
|
+
const demandRef = useRef(null);
|
|
236
|
+
useEffect(() => { if (demandRef.current) clockRef.current.schedule(demandRef.current()); });
|
|
237
|
+
useEffect(() => () => clockRef.current.stop(), []);
|
|
238
|
+
|
|
239
|
+
const introDone = useCallback(() => setPhase((p) => (p === 'intro' ? 'deck' : p)), []);
|
|
240
|
+
|
|
241
|
+
const sit = (data && data.situation) || null;
|
|
242
|
+
const areas = (sit && sit.areas) || [];
|
|
243
|
+
const allNeeds = (sit && sit.needs_you) || [];
|
|
244
|
+
const changed = (sit && sit.changed) || [];
|
|
245
|
+
const needs = useMemo(() => (filter ? allNeeds.filter((i) => i.area === filter) : allNeeds), [allNeeds, filter]);
|
|
246
|
+
const filterLabel = filter ? ((areas.find((a) => a.area === filter) || {}).label || filter) : null;
|
|
247
|
+
const safeSel = Math.min(sel, Math.max(0, needs.length - 1));
|
|
248
|
+
const safeCh = Math.min(chSel, Math.max(0, changed.length - 1));
|
|
249
|
+
const safeAr = Math.min(arSel, areas.length);
|
|
250
|
+
const cur = needs[safeSel];
|
|
251
|
+
const curMeasures = (cur && cur.L2 && cur.L2.measures) || [];
|
|
252
|
+
const isOpen = Boolean(cur && expanded === cur.ref);
|
|
253
|
+
const expandedRef = isOpen ? expanded : null;
|
|
254
|
+
|
|
255
|
+
// A resolved/vanished expanded item collapses itself.
|
|
256
|
+
useEffect(() => {
|
|
257
|
+
if (expanded && !allNeeds.some((i) => i.ref === expanded)) setExpanded(null);
|
|
258
|
+
}, [allNeeds, expanded]);
|
|
259
|
+
|
|
260
|
+
const runConfirmed = () => {
|
|
261
|
+
const c0 = confirm; setConfirm(null);
|
|
262
|
+
if (c0) c0.run();
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const askMeasure = (item, m) => {
|
|
266
|
+
setConfirm({
|
|
267
|
+
text: `Do ${m.label}?`,
|
|
268
|
+
extra: [m.risk && m.risk !== 'safe' ? m.risk : null, m.kind === 'run' ? 'This files an approval' : null].filter(Boolean).join(' · '),
|
|
269
|
+
run: () => {
|
|
270
|
+
flash(`Running "${m.label}"...`, 'busy');
|
|
271
|
+
api.measure(item.ref, m.id).then((r) => {
|
|
272
|
+
const bits = [r.say, r.command && `run: ${r.command}`, r.url && `open: ${r.url}`,
|
|
273
|
+
r.status === 'filed' ? 'Filed for approval. Approve it in Approvals.' : null].filter(Boolean);
|
|
274
|
+
flash(bits.join(' ') || `Done: ${m.label} (status ${r.status}).`, 'ok');
|
|
275
|
+
refresh();
|
|
276
|
+
}).catch((e) => flash(`"${m.label}" failed: ${e.status ? 'HTTP ' + e.status : e.message}`, 'bad'));
|
|
277
|
+
},
|
|
278
|
+
});
|
|
279
|
+
flash('Asked first, nothing has run yet.', 'dim');
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
const askTeach = (text, body, okMsg) => {
|
|
283
|
+
setConfirm({
|
|
284
|
+
text,
|
|
285
|
+
extra: '',
|
|
286
|
+
run: () => {
|
|
287
|
+
api.teach(body)
|
|
288
|
+
.then(() => { flash(okMsg, 'ok'); refresh(); })
|
|
289
|
+
.catch((e) => flash(`Teach failed: ${e.status ? 'HTTP ' + e.status : e.message}`, 'bad'));
|
|
290
|
+
},
|
|
291
|
+
});
|
|
292
|
+
};
|
|
293
|
+
|
|
294
|
+
const gotoPane = (delta) => {
|
|
295
|
+
const i = PANES.indexOf(pane);
|
|
296
|
+
const nxt = PANES[(i + delta + PANES.length) % PANES.length];
|
|
297
|
+
setPane(nxt);
|
|
298
|
+
flash(`Switched to ${PANE_WORD[nxt]}. Up/Down now moves through it.`, 'dim');
|
|
299
|
+
};
|
|
300
|
+
|
|
301
|
+
const move = (delta) => {
|
|
302
|
+
if (pane === 'needs') {
|
|
303
|
+
if (!needs.length) { flash(filter ? 'Nothing to move through: no needs in this area (Esc clears the filter).' : 'Nothing needs you, so there is nothing to select.', 'dim'); return; }
|
|
304
|
+
if (isOpen && curMeasures.length) { setMSel((m) => Math.max(0, Math.min(curMeasures.length - 1, m + delta))); return; }
|
|
305
|
+
if (isOpen) { flash('This item is open and has no actions. Esc closes it.', 'dim'); return; }
|
|
306
|
+
setSel((x) => Math.max(0, Math.min(needs.length - 1, Math.min(x, needs.length - 1) + delta))); setMSel(0);
|
|
307
|
+
} else if (pane === 'changed') {
|
|
308
|
+
if (!changed.length) { flash('Nothing has changed since you last looked.', 'dim'); return; }
|
|
309
|
+
setChSel((x) => Math.max(0, Math.min(changed.length - 1, Math.min(x, changed.length - 1) + delta)));
|
|
310
|
+
} else {
|
|
311
|
+
setArSel((x) => Math.max(0, Math.min(areas.length, Math.min(x, areas.length) + delta)));
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
const activate = () => {
|
|
316
|
+
if (pane === 'needs') {
|
|
317
|
+
if (!cur) { flash('Nothing is selected, so Enter has nothing to open.', 'dim'); return; }
|
|
318
|
+
if (!isOpen) { setExpanded(cur.ref); setMSel(0); return; }
|
|
319
|
+
if (curMeasures[mSel]) { askMeasure(cur, curMeasures[mSel]); return; }
|
|
320
|
+
setExpanded(null); flash('No actions on this item, closed it.', 'dim');
|
|
321
|
+
} else if (pane === 'areas') {
|
|
322
|
+
if (safeAr === 0 || (areas[safeAr - 1] && areas[safeAr - 1].area === filter)) {
|
|
323
|
+
setFilter(null); flash('Showing all areas.', 'ok');
|
|
324
|
+
} else {
|
|
325
|
+
const a = areas[safeAr - 1];
|
|
326
|
+
setFilter(a.area); flash(`Filtered Needs you to ${a.label}. Esc clears.`, 'ok');
|
|
327
|
+
}
|
|
328
|
+
setPane('needs'); setSel(0); setExpanded(null);
|
|
329
|
+
} else {
|
|
330
|
+
const c1 = changed[safeCh];
|
|
331
|
+
if (!c1) { flash('Nothing is selected.', 'dim'); return; }
|
|
332
|
+
flash(`${c1.L0 || 'Change'}${changedWords(c1) ? ': ' + changedWords(c1) : ''}`, 'ok');
|
|
333
|
+
}
|
|
334
|
+
};
|
|
335
|
+
|
|
336
|
+
const onKey = (input, key) => {
|
|
337
|
+
if (key.ctrl && input === 'c') { if (onExit) onExit(); exit(); return; }
|
|
338
|
+
if (phase === 'intro') { introDone(); return; }
|
|
339
|
+
setHinted(false);
|
|
340
|
+
if (input === 'q' && !confirm) { if (onExit) onExit(); exit(); return; }
|
|
341
|
+
if (help) { if (input === '?' || key.escape || input === 'q') setHelp(false); return; }
|
|
342
|
+
if (confirm) {
|
|
343
|
+
if (input === 'y' || input === 'Y') runConfirmed();
|
|
344
|
+
else if (input === 'n' || input === 'N' || key.escape) { setConfirm(null); flash('Cancelled, nothing was changed.', 'dim'); }
|
|
345
|
+
else flash('Answer y to do it or n to cancel.', 'dim');
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
if (input === '?') { setHelp(true); return; }
|
|
349
|
+
if (input === 'r') { flash('Refreshing now...', 'busy'); refresh(); return; }
|
|
350
|
+
if (key.tab) { gotoPane(key.shift ? -1 : 1); return; }
|
|
351
|
+
if (key.upArrow || input === 'k') { move(-1); return; }
|
|
352
|
+
if (key.downArrow || input === 'j') { move(1); return; }
|
|
353
|
+
if (key.return) { activate(); return; }
|
|
354
|
+
if (key.escape) {
|
|
355
|
+
if (isOpen) { setExpanded(null); flash('Closed the item.', 'dim'); }
|
|
356
|
+
else if (filter) { setFilter(null); setSel(0); flash('Filter cleared, showing all areas.', 'ok'); }
|
|
357
|
+
else flash('Nothing to close. Press q to quit.', 'dim');
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
if (input === 'd') {
|
|
361
|
+
if (pane !== 'needs') { flash('d acts on Needs you. Tab to that pane first.', 'dim'); return; }
|
|
362
|
+
if (!cur) { flash('Nothing selected to act on.', 'dim'); return; }
|
|
363
|
+
if (!isOpen) {
|
|
364
|
+
if (!curMeasures.length) { flash('This item has no actions I can offer.', 'dim'); return; }
|
|
365
|
+
setExpanded(cur.ref); setMSel(0); flash('Opened. Up/Down picks an action, Enter or d does it.', 'dim'); return;
|
|
366
|
+
}
|
|
367
|
+
if (!curMeasures[mSel]) { flash('This item has no actions I can offer.', 'dim'); return; }
|
|
368
|
+
askMeasure(cur, curMeasures[mSel]); return;
|
|
369
|
+
}
|
|
370
|
+
if (input === 'u' || input === 'c') {
|
|
371
|
+
if (pane !== 'needs' || !cur) { flash(`${input} acts on a Needs you item. Select one first.`, 'dim'); return; }
|
|
372
|
+
if (input === 'u') {
|
|
373
|
+
askTeach(`Treat all ${cur.kind} items as not urgent? y/n`,
|
|
374
|
+
{ scope: 'kind', target: cur.kind, field: 'urgency', value: 'whenever', reason: 'not useful (deck)' },
|
|
375
|
+
`Noted: ${cur.kind} items are "whenever" from now on.`);
|
|
376
|
+
} else {
|
|
377
|
+
askTeach(`Mark "${clip(cur.L0 || '', 40)}" as something you care about? y/n`,
|
|
378
|
+
{ scope: 'subject', target: cur.ref, field: 'watched', value: true, reason: 'I care (deck)' },
|
|
379
|
+
'Noted: you care about this one, LUCID will keep it in view.');
|
|
380
|
+
}
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
if (input === 'm') {
|
|
384
|
+
if (!sit || !sit.cursor) { flash('Nothing to mark: no cursor from LUCID yet.', 'dim'); return; }
|
|
385
|
+
const n = changed.length;
|
|
386
|
+
api.ack(sit.cursor.to)
|
|
387
|
+
.then(() => { flash(`Marked ${n} change${n === 1 ? '' : 's'} as seen, so "since you looked" restarts from now.`, 'ok'); refresh(); })
|
|
388
|
+
.catch((e) => flash(`Ack failed: ${e.status ? 'HTTP ' + e.status : e.message}`, 'bad'));
|
|
389
|
+
return;
|
|
390
|
+
}
|
|
391
|
+
flash(`"${input || 'that key'}" does nothing here. Press ? for keys.`, 'dim');
|
|
392
|
+
};
|
|
393
|
+
// Always dispatch to the latest closure: Ink re-subscribes in a passive effect,
|
|
394
|
+
// which can lag a render and would otherwise hand a key to stale state.
|
|
395
|
+
const keyRef = useRef(onKey);
|
|
396
|
+
keyRef.current = onKey;
|
|
397
|
+
useInput((input, key) => keyRef.current(input, key));
|
|
398
|
+
|
|
399
|
+
if (phase === 'intro') {
|
|
400
|
+
return (
|
|
401
|
+
<Box flexDirection="column" height={Math.max(10, rows - 1)} width={cols}>
|
|
402
|
+
<LucidIntro data={data} err={err} onDone={introDone} rows={rows} cols={cols} motion={motionOn} />
|
|
403
|
+
</Box>
|
|
404
|
+
);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
const wall = Date.now();
|
|
408
|
+
const t = now();
|
|
409
|
+
const cov = (sit && sit.cannot_see && sit.cannot_see.coverage) || { live: 0, expected: 0 };
|
|
410
|
+
const stale = Boolean(err && data);
|
|
411
|
+
const ageText = fetchedAt ? ageOf(fetchedAt, t) : null;
|
|
412
|
+
const noteLive = note && wall - note.at < (note.tone === 'bad' ? 6000 : TOAST_MS) ? note : null;
|
|
413
|
+
const noteRole = noteLive ? ({ ok: 'ok', bad: 'error', busy: 'sky', dim: 'muted' }[noteLive.tone]) : null;
|
|
414
|
+
const spots = blindSpots(sit && sit.cannot_see);
|
|
415
|
+
const dau = (sit && sit.doesnt_add_up) || [];
|
|
416
|
+
ghosts.current = ghosts.current.filter((g) => wall - g.at < GHOST_MS);
|
|
417
|
+
const liveGhosts = ghosts.current.filter((g) => !filter || g.item.area === filter);
|
|
418
|
+
|
|
419
|
+
// ---- the Q: LUCID state -> glyph state, animated only for a short window after a change ----------------
|
|
420
|
+
const gState = glyphStateOf({ err, fetching, needs: allNeeds.length });
|
|
421
|
+
if (glyph.current.state !== gState) glyph.current = { state: gState, since: wall };
|
|
422
|
+
const gAnimating = glyphAnimating({ state: gState, since: glyph.current.since, now: wall, motion: motionOn });
|
|
423
|
+
const headGlyph = <QGlyph state={gState} ms={wall} since={gState === 'failing' ? glyph.current.since : null} quiet={!gAnimating} />;
|
|
424
|
+
|
|
425
|
+
// ---- what this render still needs from the clock --------------------------------------------------------
|
|
426
|
+
const newExpiries = [...newAt.current.values()].map((v) => v + NEW_MS);
|
|
427
|
+
demandRef.current = () => {
|
|
428
|
+
const w = Date.now();
|
|
429
|
+
return deckDemands({
|
|
430
|
+
motion: motionOn,
|
|
431
|
+
frames: gAnimating || w < revealUntil.current,
|
|
432
|
+
now: w,
|
|
433
|
+
at: [
|
|
434
|
+
noteLive ? noteLive.at + (noteLive.tone === 'bad' ? 6000 : TOAST_MS) : null,
|
|
435
|
+
...ghosts.current.map((g) => g.at + GHOST_MS),
|
|
436
|
+
...newExpiries,
|
|
437
|
+
motionOn && glyph.current.since != null ? glyph.current.since + SETTLE_MS : null,
|
|
438
|
+
nextMinuteAt(w, t),
|
|
439
|
+
],
|
|
440
|
+
});
|
|
441
|
+
};
|
|
442
|
+
const revealOf = (key) => revealT(startAt.current.get(key), wall, motionOn);
|
|
443
|
+
|
|
444
|
+
// ── layout ──────────────────────────────────────────────────────────
|
|
445
|
+
const wide = cols >= 80;
|
|
446
|
+
const big = cols >= 110;
|
|
447
|
+
const leftW = big ? 24 : 22;
|
|
448
|
+
const avail = cols - 2 - leftW;
|
|
449
|
+
const rightW = big ? avail - Math.round(avail * 0.55) : 0; // below 110 cols Since-you-looked stacks under Needs-you
|
|
450
|
+
const mainW = avail - rightW;
|
|
451
|
+
const headerH = 3 + (err ? 1 : 0);
|
|
452
|
+
const hintH = hinted && !err ? 1 : 0;
|
|
453
|
+
const footerH = 3;
|
|
454
|
+
const bodyH = Math.max(8, rows - headerH - hintH - footerH - 1);
|
|
455
|
+
const listRows = bodyH - 1; // one line for the pane title rule
|
|
456
|
+
|
|
457
|
+
// ── header ──────────────────────────────────────────────────────────
|
|
458
|
+
const covRole = cov.expected && cov.live < cov.expected ? 'warn' : 'ok';
|
|
459
|
+
const hot = arrived > 0 && gState === 'waiting' && gAnimating;
|
|
460
|
+
const header = (
|
|
461
|
+
<Box flexDirection="column">
|
|
462
|
+
<Box borderStyle="round" borderColor={err ? (stale ? c('warn') : c('error')) : c('faint')} paddingX={1}>
|
|
463
|
+
<Text wrap="truncate">
|
|
464
|
+
{headGlyph}<Text> </Text><Grad text="LUCID" />
|
|
465
|
+
<Text color={c('muted')}> │ </Text>
|
|
466
|
+
<Text bold color={c('value')}>{clockLondon(t)}</Text><Text color={c('muted')}> London</Text>
|
|
467
|
+
<Text color={c('muted')}> │ </Text>
|
|
468
|
+
{sit ? <Text color={c(covRole)}>{meter(cov.live, cov.expected)} {cov.live}/{cov.expected}</Text> : <Text color={c('muted')}>▱▱▱▱▱ -/-</Text>}
|
|
469
|
+
<Text color={c('muted')}> │ </Text>
|
|
470
|
+
{!sit ? <Text color={c('muted')}>- need you</Text> : allNeeds.length
|
|
471
|
+
? <Text bold color={c('warn')}>{allNeeds.length} need you</Text>
|
|
472
|
+
: <Text bold color={c('ok')}>0 need you</Text>}
|
|
473
|
+
{filterLabel ? <Text bold color={c('key')}> │ filter: {filterLabel}</Text> : null}
|
|
474
|
+
{arrived > 0 ? <Text bold color={hot ? c('warn') : c('ok')}> +{arrived} new</Text> : null}
|
|
475
|
+
<Text color={c('muted')}> │ </Text>
|
|
476
|
+
<Text color={c('muted')}>{ageText == null ? 'connecting' : `updated ${ageText} ago`}</Text>
|
|
477
|
+
</Text>
|
|
478
|
+
</Box>
|
|
479
|
+
{err ? (
|
|
480
|
+
<Text bold wrap="truncate" color={stale ? c('warn') : c('error')}>
|
|
481
|
+
{stale ? `⚠ STALE ${ageText}` : '✖ NO DATA'} · LUCID unreachable: {err.status || err.code || err.message} · retrying, {stale ? 'showing the last good data' : 'nothing is known yet (this is not "all quiet")'}
|
|
482
|
+
</Text>
|
|
483
|
+
) : null}
|
|
484
|
+
{hinted && !err ? (
|
|
485
|
+
<Text wrap="truncate" backgroundColor={mono() ? undefined : BAR_BG} color={mono() ? undefined : '#FFFFFF'} inverse={mono()} bold>{pad(' ↑↓ move · Enter open · Tab switch panel · ? help ', cols - 2)}</Text>
|
|
486
|
+
) : null}
|
|
487
|
+
</Box>
|
|
488
|
+
);
|
|
489
|
+
|
|
490
|
+
if (!sit) {
|
|
491
|
+
return (
|
|
492
|
+
<Box flexDirection="column" paddingX={1}>
|
|
493
|
+
{header}
|
|
494
|
+
<Box marginTop={1}>
|
|
495
|
+
<Text color={err ? c('error') : c('muted')}>
|
|
496
|
+
{err ? 'No data yet. LUCID cannot be reached, so nothing is known (this is not "all quiet").' : 'Connecting to LUCID...'}
|
|
497
|
+
</Text>
|
|
498
|
+
</Box>
|
|
499
|
+
<Box marginTop={1}><Text color={c('muted')}>r refresh · q quit</Text></Box>
|
|
500
|
+
</Box>
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// ── panes ───────────────────────────────────────────────────────────
|
|
505
|
+
const focus = (p) => pane === p;
|
|
506
|
+
const isNewOf = (it) => newAt.current.has(it.ref) && wall - newAt.current.get(it.ref) < NEW_MS;
|
|
507
|
+
const areaLabelOf = (k) => ((areas.find((a) => a.area === k) || {}).label || '');
|
|
508
|
+
|
|
509
|
+
const needsBody = (w, rowsAvail) => {
|
|
510
|
+
const cw = Math.max(24, w - 1);
|
|
511
|
+
const f = focus('needs');
|
|
512
|
+
if (needs.length === 0 && !liveGhosts.length) {
|
|
513
|
+
return (
|
|
514
|
+
<Text color={c('ok')}>{filter ? 'Nothing needs you in this area.' : 'Nothing needs you right now.'}{spots.length ? <Text color={c('muted')}> (check what I can't see)</Text> : null}</Text>
|
|
515
|
+
);
|
|
516
|
+
}
|
|
517
|
+
const views = needs.map((it, i) => lucidNeed({
|
|
518
|
+
item: it, areaLabel: filter ? '' : areaLabelOf(it.area), isNew: isNewOf(it), selected: i === safeSel, focused: f,
|
|
519
|
+
open: expandedRef === it.ref, mSel, now: t,
|
|
520
|
+
}, cw));
|
|
521
|
+
const nGhost = Math.min(2, liveGhosts.length);
|
|
522
|
+
const win = fitHeights(views.map(cardHeight), safeSel, rowsAvail - nGhost);
|
|
523
|
+
const above = win.start;
|
|
524
|
+
const below = needs.length - win.end;
|
|
525
|
+
return (
|
|
526
|
+
<>
|
|
527
|
+
{above > 0 ? <Text color={c('muted')}> ↑ {above} more</Text> : null}
|
|
528
|
+
{needs.slice(win.start, win.end).map((it, k) => (
|
|
529
|
+
<LucidCard key={it.ref} view={views[win.start + k]} t={revealOf('n:' + it.ref)} />
|
|
530
|
+
))}
|
|
531
|
+
{liveGhosts.slice(0, 2).map((g) => (
|
|
532
|
+
<Text key={'g' + g.item.ref} wrap="truncate" color={c('muted')} dimColor> ✓ resolved {clip(g.item.L0 || '', Math.max(10, w - 16))}</Text>
|
|
533
|
+
))}
|
|
534
|
+
{below > 0 ? <Text color={c('muted')}> ↓ {below} more</Text> : null}
|
|
535
|
+
</>
|
|
536
|
+
);
|
|
537
|
+
};
|
|
538
|
+
|
|
539
|
+
const changedBody = (w, rowsAvail) => {
|
|
540
|
+
const iw = Math.max(10, w - 2);
|
|
541
|
+
const f = focus('changed');
|
|
542
|
+
if (!changed.length) return <Text color={c('muted')}>Nothing has changed.</Text>;
|
|
543
|
+
const tw = iw - 7;
|
|
544
|
+
const textOf = (c0) => {
|
|
545
|
+
const words = changedWords(c0);
|
|
546
|
+
return (c0.L0 || '') + (words ? ` · ${words}` : '');
|
|
547
|
+
};
|
|
548
|
+
const win = fitHeights(changed.map((c0) => wrap(textOf(c0), tw).length), safeCh, rowsAvail);
|
|
549
|
+
const above = win.start;
|
|
550
|
+
const below = changed.length - win.end;
|
|
551
|
+
return (
|
|
552
|
+
<>
|
|
553
|
+
{above > 0 ? <Text color={c('muted')}> ↑ {above} more</Text> : null}
|
|
554
|
+
{changed.slice(win.start, win.end).map((c0, k) => {
|
|
555
|
+
const i = win.start + k;
|
|
556
|
+
const on = i === safeCh && f;
|
|
557
|
+
return (
|
|
558
|
+
<Box key={c0.ref + c0.ts} flexDirection="column">
|
|
559
|
+
{wrap(textOf(c0), tw).map((ln, li) => (
|
|
560
|
+
<Text key={li} wrap="truncate" backgroundColor={on && !mono() ? BAR_BG : undefined} inverse={on && mono()} bold={on}>
|
|
561
|
+
<Text color={on ? '#FFFFFF' : c('muted')}>{li === 0 ? `${on ? '▶ ' : ' '}${pad(ageOf(c0.ts, t), 3)} ` : ' '.repeat(7)}</Text>
|
|
562
|
+
<Text color={on ? '#FFFFFF' : (li === 0 ? c('text') : c('muted'))}>{pad(ln, tw)}</Text>
|
|
563
|
+
</Text>
|
|
564
|
+
))}
|
|
565
|
+
</Box>
|
|
566
|
+
);
|
|
567
|
+
})}
|
|
568
|
+
{below > 0 ? <Text color={c('muted')}> ↓ {below} more</Text> : null}
|
|
569
|
+
</>
|
|
570
|
+
);
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
const areasBody = (w, rowsAvail) => {
|
|
574
|
+
const cw = Math.max(18, w - 1);
|
|
575
|
+
const f = focus('areas');
|
|
576
|
+
const list = [{ area: null, label: 'All areas', state: null }, ...areas];
|
|
577
|
+
const views = list.map((a, i) => {
|
|
578
|
+
const n = a.area ? allNeeds.filter((x) => x.area === a.area).length : allNeeds.length;
|
|
579
|
+
return lucidArea({
|
|
580
|
+
area: a.area, label: a.label, state: a.state, L0: a.L0, last: a.last, n, active: (a.area || null) === filter && filter != null,
|
|
581
|
+
selected: i === safeAr && f, focused: f, expanded: i === safeAr && f, now: t,
|
|
582
|
+
}, cw);
|
|
583
|
+
});
|
|
584
|
+
const win = fitHeights(views.map(cardHeight), safeAr, rowsAvail);
|
|
585
|
+
return (
|
|
586
|
+
<>
|
|
587
|
+
{win.start > 0 ? <Text color={c('muted')}> ↑ {win.start} more</Text> : null}
|
|
588
|
+
{list.slice(win.start, win.end).map((a, k) => (
|
|
589
|
+
<LucidCard key={a.area || 'all'} view={views[win.start + k]} t={revealOf('a:' + (a.area || ''))} />
|
|
590
|
+
))}
|
|
591
|
+
{win.end < list.length ? <Text color={c('muted')}> ↓ {list.length - win.end} more</Text> : null}
|
|
592
|
+
</>
|
|
593
|
+
);
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
const counts = {
|
|
597
|
+
needs: needs.length + (filter ? `/${allNeeds.length}` : ''),
|
|
598
|
+
changed: changed.length,
|
|
599
|
+
areas: areas.length,
|
|
600
|
+
};
|
|
601
|
+
const bodies = { needs: needsBody, changed: changedBody, areas: areasBody };
|
|
602
|
+
const col = (p, w, h0, children, note0) => (
|
|
603
|
+
<Box flexDirection="column" width={w} height={h0}>
|
|
604
|
+
<PaneTitle title={PANE_TITLE[p]} count={counts[p]} focused={focus(p)} width={w - 1} note={note0} />
|
|
605
|
+
{children}
|
|
606
|
+
</Box>
|
|
607
|
+
);
|
|
608
|
+
|
|
609
|
+
let body;
|
|
610
|
+
if (help) {
|
|
611
|
+
const K = (k, d) => (
|
|
612
|
+
<Text key={k}><Text bold color={c('key')}>{pad(k, 14)}</Text><Text color={c('text')}>{d}</Text></Text>
|
|
613
|
+
);
|
|
614
|
+
body = (
|
|
615
|
+
<Box flexDirection="column" height={bodyH}>
|
|
616
|
+
<PaneTitle title="KEYS" count="" focused width={cols - 2} />
|
|
617
|
+
{K('↑ ↓ j k', 'move the highlighted row (inside an open item: pick an action)')}
|
|
618
|
+
{K('Tab / S-Tab', 'switch panel: Needs you, Since you looked, Areas')}
|
|
619
|
+
{K('Enter', 'open an item / do the highlighted action / filter by area')}
|
|
620
|
+
{K('d', 'do something about the selected item (asks y/n first)')}
|
|
621
|
+
{K('Esc', 'close the item, cancel a question, clear the area filter')}
|
|
622
|
+
{K('u', 'teach: treat this kind of item as not urgent (asks y/n)')}
|
|
623
|
+
{K('c', 'teach: you care about this item (asks y/n)')}
|
|
624
|
+
{K('m', 'mark everything since you looked as seen')}
|
|
625
|
+
{K('r', 'refresh now (it also refreshes every 10s)')}
|
|
626
|
+
{K('?', 'close this help')}
|
|
627
|
+
{K('q Ctrl-C', 'quit')}
|
|
628
|
+
<Text color={c('muted')}>Press ? or Esc to go back.</Text>
|
|
629
|
+
</Box>
|
|
630
|
+
);
|
|
631
|
+
} else if (wide) {
|
|
632
|
+
const needsH = big ? bodyH : Math.ceil(bodyH * 0.6);
|
|
633
|
+
const chH = bodyH - needsH;
|
|
634
|
+
const nNote = filterLabel ? `· ${filterLabel}` : '';
|
|
635
|
+
body = (
|
|
636
|
+
<Box width={cols - 2}>
|
|
637
|
+
{col('areas', leftW, bodyH, areasBody(leftW, listRows))}
|
|
638
|
+
{big ? (
|
|
639
|
+
<>
|
|
640
|
+
{col('needs', mainW, bodyH, needsBody(mainW, listRows), nNote)}
|
|
641
|
+
{col('changed', rightW, bodyH, changedBody(rightW, listRows))}
|
|
642
|
+
</>
|
|
643
|
+
) : (
|
|
644
|
+
<Box flexDirection="column" width={mainW}>
|
|
645
|
+
{col('needs', mainW, needsH, needsBody(mainW, needsH - 1), nNote)}
|
|
646
|
+
{col('changed', mainW, chH, changedBody(mainW, chH - 1))}
|
|
647
|
+
</Box>
|
|
648
|
+
)}
|
|
649
|
+
</Box>
|
|
650
|
+
);
|
|
651
|
+
} else {
|
|
652
|
+
const stackW = cols - 2;
|
|
653
|
+
const others = PANES.filter((p) => p !== pane);
|
|
654
|
+
const focH = Math.max(6, bodyH - others.length);
|
|
655
|
+
body = (
|
|
656
|
+
<Box flexDirection="column">
|
|
657
|
+
{col(pane, stackW, focH, bodies[pane](stackW, focH - 1), filterLabel && pane === 'needs' ? `· ${filterLabel}` : '')}
|
|
658
|
+
{others.map((p) => (
|
|
659
|
+
<Text key={p} wrap="truncate" color={c('muted')}> ▸ {PANE_TITLE[p]} {counts[p]} (Tab)</Text>
|
|
660
|
+
))}
|
|
661
|
+
</Box>
|
|
662
|
+
);
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// ── status line (always says what you are on and what Enter does) ───
|
|
666
|
+
let where; let act;
|
|
667
|
+
if (confirm) {
|
|
668
|
+
where = 'Waiting for your answer'; act = [['y', 'yes, do it'], ['n', 'cancel']];
|
|
669
|
+
} else if (help) {
|
|
670
|
+
where = 'Help'; act = [['?', 'back'], ['q', 'quit']];
|
|
671
|
+
} else if (pane === 'needs') {
|
|
672
|
+
if (!cur) { where = `▶ Needs you · ${filter ? 'none in ' + filterLabel : 'nothing needs you'}`; act = [['Tab', 'switch panel'], ['?', 'help']]; }
|
|
673
|
+
else if (isOpen) {
|
|
674
|
+
const m = curMeasures[mSel];
|
|
675
|
+
where = `▶ Needs you · ${safeSel + 1} of ${needs.length} · open: ${clip(cur.L0 || '', 50)}`;
|
|
676
|
+
act = m ? [['↑↓', 'pick action'], ['Enter', `do "${clip(m.label, 18)}" (asks first)`], ['Esc', 'close']] : [['Esc', 'close']];
|
|
677
|
+
} else {
|
|
678
|
+
where = `▶ Needs you · ${safeSel + 1} of ${needs.length} · ${clip(cur.L0 || '', 60)}`;
|
|
679
|
+
act = [['Enter', 'open'], ['d', 'do something'], ['u', 'not urgent'], ['c', 'I care'], ['m', 'mark all seen']];
|
|
680
|
+
}
|
|
681
|
+
} else if (pane === 'changed') {
|
|
682
|
+
const c1 = changed[safeCh];
|
|
683
|
+
where = c1 ? `▶ Since you looked · ${safeCh + 1} of ${changed.length} · ${clip(c1.L0 || '', 60)}` : '▶ Since you looked · nothing changed';
|
|
684
|
+
act = [['Enter', 'show detail'], ['m', 'mark all seen'], ['Tab', 'next panel']];
|
|
685
|
+
} else {
|
|
686
|
+
const a = areas[safeAr - 1];
|
|
687
|
+
where = `▶ Areas · ${safeAr + 1} of ${areas.length + 1} · ${a ? a.label + ': ' + clip(a.L0 || '', 50) : 'all areas'}`;
|
|
688
|
+
act = [['Enter', a ? `filter Needs you to ${a.label}` : 'show everything'], ['Tab', 'next panel']];
|
|
689
|
+
}
|
|
690
|
+
|
|
691
|
+
const hints = confirm ? [['y', 'yes'], ['n', 'no'], ['Esc', 'cancel']]
|
|
692
|
+
: help ? [['?/Esc', 'close help'], ['q', 'quit']]
|
|
693
|
+
: isOpen ? [['↑↓', 'pick action'], ['Enter/d', 'do it'], ['Esc', 'close'], ['?', 'help'], ['q', 'quit']]
|
|
694
|
+
: [['↑↓', 'move'], ['Enter', pane === 'areas' ? 'filter' : 'open'], ['Tab', 'switch panel'], ['d', 'do'], ['m', 'mark seen'], ['?', 'help'], ['q', 'quit']];
|
|
695
|
+
|
|
696
|
+
return (
|
|
697
|
+
<Box flexDirection="column" paddingX={1}>
|
|
698
|
+
{header}
|
|
699
|
+
{body}
|
|
700
|
+
{confirm ? (
|
|
701
|
+
<Text wrap="truncate" backgroundColor={mono() ? undefined : c('warn')} inverse={mono()} color={mono() ? undefined : '#0B1220'} bold> {confirm.text.endsWith('y/n') ? confirm.text : confirm.text + ' y/n'}{confirm.extra ? ' · ' + confirm.extra : ''} </Text>
|
|
702
|
+
) : noteLive ? (
|
|
703
|
+
<Text wrap="truncate" bold color={c(noteRole)}>{noteLive.tone === 'ok' ? '✔ ' : noteLive.tone === 'bad' ? '✖ ' : noteLive.tone === 'busy' ? '◐ ' : 'ℹ '}{noteLive.text}</Text>
|
|
704
|
+
) : (
|
|
705
|
+
<Text wrap="truncate" color={spots.length || dau.length ? c('warn') : c('muted')}>
|
|
706
|
+
{dau.length ? `doesn't add up (${dau.length}): ${clip(dau[0].L0 || '', 60)} · ` : ''}{spots.length ? `can't see: ${spots.map((s) => s.text).join(' · ')}` : 'Every expected source is speaking.'}
|
|
707
|
+
</Text>
|
|
708
|
+
)}
|
|
709
|
+
<Text wrap="truncate">
|
|
710
|
+
<Text bold color={c('key')}>{where}</Text>
|
|
711
|
+
<Text color={c('muted')}> · </Text>
|
|
712
|
+
{act.map(([k, l], i) => (
|
|
713
|
+
<Text key={k}>{i ? ' ' : ''}<Text bold color={c('sky')}>{k}:</Text><Text color={c('muted')}> {l}</Text></Text>
|
|
714
|
+
))}
|
|
715
|
+
</Text>
|
|
716
|
+
<Hints items={hints} />
|
|
717
|
+
</Box>
|
|
718
|
+
);
|
|
719
|
+
}
|