quoxcli 0.0.0-stage → 0.2.0
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 +142 -2
- package/bin/quox.js +292 -0
- package/bin/quoxon.js +8 -0
- package/lib/api/endpoints/admin.js +125 -0
- package/lib/api/endpoints/aee.js +38 -0
- package/lib/api/endpoints/agent-attach.js +126 -0
- package/lib/api/endpoints/agents.js +56 -0
- package/lib/api/endpoints/assistants.js +50 -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/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 +54 -0
- package/lib/api/endpoints/consent.js +26 -0
- package/lib/api/endpoints/contactsq.js +66 -0
- package/lib/api/endpoints/conversations.js +32 -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 +32 -0
- package/lib/api/endpoints/file.js +20 -0
- package/lib/api/endpoints/files.js +26 -0
- package/lib/api/endpoints/github.js +21 -0
- package/lib/api/endpoints/governance.js +43 -0
- package/lib/api/endpoints/hat.js +36 -0
- package/lib/api/endpoints/inbox.js +32 -0
- package/lib/api/endpoints/inboxq.js +120 -0
- package/lib/api/endpoints/index.js +228 -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 +75 -0
- package/lib/api/endpoints/mfa.js +32 -0
- package/lib/api/endpoints/mon.js +178 -0
- package/lib/api/endpoints/notesq.js +43 -0
- package/lib/api/endpoints/objectives.js +62 -0
- package/lib/api/endpoints/orgs.js +38 -0
- package/lib/api/endpoints/plugins.js +134 -0
- package/lib/api/endpoints/powers.js +75 -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 +114 -0
- package/lib/api/endpoints/quoxseo.js +108 -0
- package/lib/api/endpoints/reach.js +47 -0
- package/lib/api/endpoints/runs.js +38 -0
- package/lib/api/endpoints/safety.js +46 -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 +180 -0
- package/lib/api/endpoints/sso.js +67 -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 +70 -0
- package/lib/api/endpoints/user-library.js +41 -0
- package/lib/api/endpoints/users.js +20 -0
- package/lib/api/endpoints/v1.js +499 -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 +40 -0
- package/lib/api/endpoints/webhooks.js +46 -0
- package/lib/api/endpoints/workflows.js +162 -0
- package/lib/api/endpoints/x.js +24 -0
- package/lib/api/endpoints.js +15 -0
- package/lib/commands/admin.js +359 -0
- package/lib/commands/aee.js +99 -0
- package/lib/commands/agent.js +201 -0
- package/lib/commands/agentAttach.js +1440 -0
- package/lib/commands/approvals.js +47 -0
- package/lib/commands/assistant.js +114 -0
- package/lib/commands/auth.js +458 -0
- package/lib/commands/awareness.js +88 -0
- package/lib/commands/backup.js +373 -0
- package/lib/commands/brain.js +994 -0
- package/lib/commands/budget.js +241 -0
- package/lib/commands/build.js +156 -0
- package/lib/commands/calendar.js +216 -0
- package/lib/commands/capabilities.js +34 -0
- package/lib/commands/chat.js +240 -0
- package/lib/commands/claude.js +354 -0
- package/lib/commands/config-cmd.js +106 -0
- package/lib/commands/consent.js +84 -0
- package/lib/commands/contacts.js +967 -0
- package/lib/commands/contactsSelftest.js +132 -0
- package/lib/commands/conversation.js +74 -0
- package/lib/commands/department.js +75 -0
- package/lib/commands/dev.js +620 -0
- package/lib/commands/devResident.js +314 -0
- package/lib/commands/devSupervisor.js +1050 -0
- package/lib/commands/discord.js +96 -0
- package/lib/commands/doctor.js +181 -0
- package/lib/commands/domain.js +114 -0
- package/lib/commands/done.js +277 -0
- package/lib/commands/engagement.js +586 -0
- package/lib/commands/entity.js +119 -0
- package/lib/commands/facebook.js +212 -0
- package/lib/commands/file.js +128 -0
- package/lib/commands/fleet.js +882 -0
- package/lib/commands/governance.js +638 -0
- package/lib/commands/gripe.js +324 -0
- package/lib/commands/hat.js +205 -0
- package/lib/commands/helpers.js +79 -0
- package/lib/commands/inbox.js +122 -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 +280 -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 +591 -0
- package/lib/commands/mail.js +531 -0
- package/lib/commands/memory.js +284 -0
- package/lib/commands/mon.js +514 -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 +92 -0
- package/lib/commands/objective.js +229 -0
- package/lib/commands/onboard.js +328 -0
- package/lib/commands/open.js +56 -0
- package/lib/commands/org.js +266 -0
- package/lib/commands/plan.js +906 -0
- package/lib/commands/plugin-doctor.js +1091 -0
- package/lib/commands/plugin.js +539 -0
- package/lib/commands/policy.js +78 -0
- package/lib/commands/powers.js +296 -0
- package/lib/commands/prompt.js +350 -0
- package/lib/commands/qlarity.js +690 -0
- package/lib/commands/quoxbox.js +270 -0
- package/lib/commands/quoxchat.js +266 -0
- package/lib/commands/quoxseo.js +340 -0
- package/lib/commands/reach.js +158 -0
- package/lib/commands/role.js +286 -0
- package/lib/commands/run.js +132 -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/service.js +89 -0
- package/lib/commands/settings.js +99 -0
- package/lib/commands/setup.js +543 -0
- package/lib/commands/skillManifest.js +113 -0
- package/lib/commands/skillRightsFormat.js +38 -0
- package/lib/commands/skills.js +644 -0
- package/lib/commands/slot.js +1198 -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 +140 -0
- package/lib/commands/tg.js +240 -0
- package/lib/commands/tiktok.js +218 -0
- package/lib/commands/tool.js +216 -0
- package/lib/commands/trace.js +525 -0
- package/lib/commands/trigger.js +128 -0
- package/lib/commands/vault.js +1424 -0
- package/lib/commands/voice.js +182 -0
- package/lib/commands/volt.js +528 -0
- package/lib/commands/watch.js +115 -0
- package/lib/commands/webhook.js +137 -0
- package/lib/commands/workflow.js +503 -0
- package/lib/commands/x.js +439 -0
- package/lib/core/actingAs.js +136 -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/auth.js +350 -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/client.js +584 -0
- package/lib/core/completions.js +163 -0
- package/lib/core/config.js +551 -0
- package/lib/core/confirm.js +37 -0
- package/lib/core/deeplinks.js +76 -0
- package/lib/core/doneEngineFetch.js +219 -0
- package/lib/core/errors.js +132 -0
- package/lib/core/global-flags.js +90 -0
- package/lib/core/governedSession.js +402 -0
- package/lib/core/lazyCommands.js +119 -0
- package/lib/core/liveSessionsReport.js +112 -0
- package/lib/core/org-override.js +52 -0
- package/lib/core/output.js +239 -0
- package/lib/core/prodEnv.js +7 -0
- package/lib/core/quoxglyph.js +158 -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 +104 -0
- package/lib/core/sessionHeartbeat.js +192 -0
- package/lib/core/sessionsOverview.js +160 -0
- package/lib/core/slotKeeper.js +282 -0
- package/lib/core/supervisorEngine.js +864 -0
- package/lib/core/systemdInstaller.js +256 -0
- package/lib/core/version.js +87 -0
- package/lib/core/websiteSession.js +148 -0
- package/lib/lucid/LucidIntro.jsx +105 -0
- package/lib/lucid/blind.js +71 -0
- package/lib/lucid/deck.js +32 -0
- package/lib/lucid/fmt.js +139 -0
- package/lib/lucid/sitrep.js +173 -0
- package/lib/plugins/loader.js +93 -0
- package/lib/quoxon/activity.js +104 -0
- package/lib/quoxon/api.js +473 -0
- package/lib/quoxon/app.js +1262 -0
- package/lib/quoxon/capabilities.js +75 -0
- package/lib/quoxon/commands.js +154 -0
- package/lib/quoxon/connect.js +467 -0
- package/lib/quoxon/connectCard.js +28 -0
- package/lib/quoxon/context.js +68 -0
- package/lib/quoxon/copy.js +33 -0
- package/lib/quoxon/demo.fixtures.js +24 -0
- package/lib/quoxon/demo.js +35 -0
- package/lib/quoxon/egg.js +36 -0
- package/lib/quoxon/events.js +458 -0
- package/lib/quoxon/gallery.js +223 -0
- package/lib/quoxon/indicator.js +193 -0
- package/lib/quoxon/intent.js +41 -0
- package/lib/quoxon/keys.js +121 -0
- package/lib/quoxon/main.js +244 -0
- package/lib/quoxon/markdown.js +317 -0
- package/lib/quoxon/model.js +205 -0
- package/lib/quoxon/motion/clock.js +69 -0
- package/lib/quoxon/motion/color.js +35 -0
- package/lib/quoxon/motion/frame.js +14 -0
- package/lib/quoxon/motion/reveal.js +54 -0
- package/lib/quoxon/motion/ring.js +79 -0
- package/lib/quoxon/objects/agent.js +61 -0
- package/lib/quoxon/objects/crystal.js +100 -0
- package/lib/quoxon/objects/fetch.js +170 -0
- package/lib/quoxon/objects/index.js +85 -0
- package/lib/quoxon/objects/ladder.js +71 -0
- package/lib/quoxon/objects/memory.js +44 -0
- package/lib/quoxon/objects/model.js +118 -0
- package/lib/quoxon/objects/proof.js +110 -0
- package/lib/quoxon/objects/skill.js +105 -0
- package/lib/quoxon/objects/view.js +76 -0
- package/lib/quoxon/objects/workflow.js +119 -0
- package/lib/quoxon/observers.js +42 -0
- package/lib/quoxon/onboarding/copy.js +235 -0
- package/lib/quoxon/onboarding/credential.js +95 -0
- package/lib/quoxon/onboarding/detect.js +22 -0
- package/lib/quoxon/onboarding/finish.js +119 -0
- package/lib/quoxon/onboarding/index.js +67 -0
- package/lib/quoxon/onboarding/installRunner.js +225 -0
- package/lib/quoxon/onboarding/intent.js +53 -0
- package/lib/quoxon/onboarding/journey.js +73 -0
- package/lib/quoxon/onboarding/preflight.js +142 -0
- package/lib/quoxon/onboarding/runner.js +58 -0
- package/lib/quoxon/onboarding/view.js +625 -0
- package/lib/quoxon/print.js +94 -0
- package/lib/quoxon/qart.js +379 -0
- package/lib/quoxon/qglyph.js +143 -0
- package/lib/quoxon/qhello.js +58 -0
- package/lib/quoxon/qmotion.js +111 -0
- package/lib/quoxon/render.js +648 -0
- package/lib/quoxon/roster.js +221 -0
- package/lib/quoxon/route.js +38 -0
- package/lib/quoxon/rules.js +130 -0
- package/lib/quoxon/session.js +68 -0
- package/lib/quoxon/skills.js +30 -0
- package/lib/quoxon/slash/compact.js +90 -0
- package/lib/quoxon/slash/ledger.js +83 -0
- package/lib/quoxon/slash/listView.js +28 -0
- package/lib/quoxon/slash/palette.js +104 -0
- package/lib/quoxon/slash/paletteController.js +59 -0
- package/lib/quoxon/slash/panels.js +101 -0
- package/lib/quoxon/slash/picker.js +38 -0
- package/lib/quoxon/slash/restart.js +144 -0
- package/lib/quoxon/slash/resume.js +59 -0
- package/lib/quoxon/slash/settings.js +50 -0
- package/lib/quoxon/sound/index.js +186 -0
- package/lib/quoxon/sound/mapping.js +119 -0
- package/lib/quoxon/sound/output.js +52 -0
- package/lib/quoxon/sound/synth.js +122 -0
- package/lib/quoxon/term.js +106 -0
- package/lib/quoxon/textBuffer.js +242 -0
- package/lib/quoxon/theme.js +253 -0
- package/lib/quoxon/transcript.js +150 -0
- package/lib/quoxon/web/qface.js +81 -0
- package/lib/quoxon/welcome.js +52 -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/InboxView.jsx +228 -0
- package/lib/tui/views/LucidDeck.jsx +727 -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 +72 -4
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E1 skill card from a real QSKL skill record (GET /quoxflow/skills/:id) plus, when reachable, its
|
|
3
|
+
* quoxflow scoreboard row (GET /api/quox-skills/scoreboards). Tests: the record carries test
|
|
4
|
+
* DEFINITIONS in `tests`; results exist only once run-tests has written `evals.tests`
|
|
5
|
+
* ({ total, passed, failed, last_run_at, results }). Never shows a pass that was not recorded.
|
|
6
|
+
*/
|
|
7
|
+
import { seg, fit, ago, day, plural, short, boxWidth } from './model.js';
|
|
8
|
+
|
|
9
|
+
const EDITION_ROLE = { stable: 'ok', experimental: 'warn', archived: 'muted' };
|
|
10
|
+
|
|
11
|
+
/** -> { word, role } for the review state the record actually states. */
|
|
12
|
+
export function reviewState(s) {
|
|
13
|
+
if (s?.reviewNeeded) return { word: 'review needed', role: 'warn' };
|
|
14
|
+
const e = String(s?.edition || s?.status || '').toLowerCase();
|
|
15
|
+
return e ? { word: e, role: EDITION_ROLE[e] || 'muted' } : { word: 'state unknown', role: 'muted' };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** -> { dots:[{ch,role}], text, role } or null when the skill has no tests at all. */
|
|
19
|
+
export function testsSummary(s, now) {
|
|
20
|
+
const run = s?.evals?.tests;
|
|
21
|
+
if (run && Number.isFinite(Number(run.total))) {
|
|
22
|
+
const total = Number(run.total), passed = Number(run.passed) || 0, failed = Number(run.failed) || 0;
|
|
23
|
+
const res = Array.isArray(run.results) ? run.results : null;
|
|
24
|
+
const dots = res && res.length === total
|
|
25
|
+
? res.map(r => ({ ch: '●', role: r.passed === true || r.status === 'passed' || r.ok === true ? 'ok' : 'error' }))
|
|
26
|
+
: [...Array(passed).fill({ ch: '●', role: 'ok' }), ...Array(Math.max(0, total - passed)).fill({ ch: '●', role: 'error' })];
|
|
27
|
+
const when = run.last_run_at ? ` · ${ago(run.last_run_at, now)} ago` : '';
|
|
28
|
+
if (passed === total && total > 0) return { dots, text: `${passed}/${total} pass${when}`, role: 'ok' };
|
|
29
|
+
return { dots, text: `${passed}/${total} pass · ${failed || total - passed} failing${when}`, role: 'error' };
|
|
30
|
+
}
|
|
31
|
+
const defined = s?.tests && typeof s.tests === 'object' ? Object.keys(s.tests).length : 0;
|
|
32
|
+
if (!defined) return null;
|
|
33
|
+
return { dots: Array(defined).fill({ ch: '○', role: 'muted' }), text: `${defined} defined · never run`, role: 'muted' };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const sourceOf = (s) => {
|
|
37
|
+
const parts = [];
|
|
38
|
+
if (s.category) parts.push(s.category);
|
|
39
|
+
if (s.createdAt) parts.push(`created ${day(s.createdAt)}`);
|
|
40
|
+
const tag = (s.tags || []).find(t => /^source:/.test(t));
|
|
41
|
+
if (tag) parts.push(`from ${tag.split(':')[1] === 'notesq' ? 'NotesQ' : tag.split(':')[1]}`);
|
|
42
|
+
if (s.authoringEnvelopeId) parts.push(`authored in ${short(s.authoringEnvelopeId, 12)}`);
|
|
43
|
+
if (s.lastPatchedBy) parts.push('patched since');
|
|
44
|
+
return parts.join(' · ');
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* d: { skill, scoreboard: { ok, row } | null, agentName?: (id) => string, now }
|
|
49
|
+
* -> view { box, rows }
|
|
50
|
+
*/
|
|
51
|
+
export function skillCard(d, cols) {
|
|
52
|
+
const s = d.skill || {};
|
|
53
|
+
const now = d.now ?? Date.now();
|
|
54
|
+
const w = boxWidth(cols, 72), inner = w - 4, nw = w < 50;
|
|
55
|
+
const rv = reviewState(s);
|
|
56
|
+
const name = s.name || s.id;
|
|
57
|
+
const right = s.version ? (nw ? `v${s.version}` : `v${s.version} · ${rv.word}`) : rv.word;
|
|
58
|
+
const rows = [];
|
|
59
|
+
if (s.description) rows.push([seg(fit(s.description, inner))]);
|
|
60
|
+
if (nw) rows.push([seg(rv.word, rv.role)]);
|
|
61
|
+
|
|
62
|
+
const agentName = d.agentName || ((id) => id);
|
|
63
|
+
const agents = (s.bestForAgents || []).map(id => String(agentName(id) || id));
|
|
64
|
+
const t = testsSummary(s, now);
|
|
65
|
+
const testRow = [];
|
|
66
|
+
if (t) {
|
|
67
|
+
testRow.push(seg('tests ', 'muted'));
|
|
68
|
+
for (const dt of t.dots.slice(0, 8)) testRow.push(seg(dt.ch, dt.role));
|
|
69
|
+
testRow.push(seg(` ${t.text}`, t.role));
|
|
70
|
+
} else testRow.push(seg('tests ', 'muted'), seg('none defined', 'muted'));
|
|
71
|
+
if (!nw && agents.length) {
|
|
72
|
+
testRow.push(seg(' agents ', 'muted'));
|
|
73
|
+
agents.slice(0, 3).forEach((a, i) => { if (i) testRow.push(seg(' ')); testRow.push(seg(a.toUpperCase(), `agent:${a}`)); });
|
|
74
|
+
if (agents.length > 3) testRow.push(seg(` +${agents.length - 3}`, 'muted'));
|
|
75
|
+
}
|
|
76
|
+
rows.push(testRow);
|
|
77
|
+
if (nw && agents.length) rows.push([seg('agents ', 'muted'), seg(agents.slice(0, 3).map(a => a.toUpperCase()).join(', '), `agent:${agents[0]}`)]);
|
|
78
|
+
|
|
79
|
+
const src = sourceOf(s);
|
|
80
|
+
if (src) rows.push([seg(fit(src, inner), 'muted')]);
|
|
81
|
+
|
|
82
|
+
const use = [];
|
|
83
|
+
const sb = d.scoreboard;
|
|
84
|
+
if (sb?.ok) {
|
|
85
|
+
const r = sb.row;
|
|
86
|
+
const n30 = Number(r?.total_invocations_30d) || 0;
|
|
87
|
+
if (!r || !n30) use.push(r?.last_used_at ? `no runs in 30d · last ${ago(r.last_used_at, now)} ago` : 'no recorded runs');
|
|
88
|
+
else {
|
|
89
|
+
use.push(`used ${plural(n30, 'time')} in 30d`);
|
|
90
|
+
if (r.last_used_at) use.push(`last ${ago(r.last_used_at, now)} ago`);
|
|
91
|
+
if (r.success_rate_30d != null) use.push(`${Math.round(Number(r.success_rate_30d) * 100)}% ok`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
const judge = s.evals?.judge;
|
|
95
|
+
if (judge && judge.avg_score != null) use.push(`judge ${judge.avg_score}/5${judge.scored_at ? ` (${day(judge.scored_at)})` : ''}`);
|
|
96
|
+
if (use.length) rows.push([seg(fit(use.join(' · '), inner), 'muted')]);
|
|
97
|
+
|
|
98
|
+
if (!nw && Array.isArray(s.policySatisfies) && s.policySatisfies.length) rows.push([seg(fit(`satisfies ${s.policySatisfies.join(', ')}`, inner), 'muted')]);
|
|
99
|
+
|
|
100
|
+
// Next possibility: only a capability that exists today. /skill runs a skill through a governed turn.
|
|
101
|
+
if (String(s.edition).toLowerCase() !== 'archived') rows.push([seg('next ', 'muted'), seg(`/skill ${name}`, 'key'), seg(nw ? ' runs it' : ' [args] runs it (governed turn)', 'muted')]);
|
|
102
|
+
else rows.push([seg('archived: kept for the record, not offered to agents', 'muted')]);
|
|
103
|
+
|
|
104
|
+
return { box: { width: w, title: `Skill · ${name}`, right, rightRole: rv.role, border: 'sky' }, rows };
|
|
105
|
+
}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* <ObjectView/>: the one Ink component for the inline object views. It maps a pure view (rows of
|
|
3
|
+
* { text, role } segments, optional box) to <Text> runs. `t` is seconds since arrival for the lab's
|
|
4
|
+
* decode-in (rows start 0.1 s apart); the default Infinity is the settled card, which is what history
|
|
5
|
+
* shows while there is no shared motion clock on main. Quiet (NO_COLOR or reduced motion) is always
|
|
6
|
+
* settled; NO_COLOR also drops every colour (c() returns undefined).
|
|
7
|
+
*/
|
|
8
|
+
import React from 'react';
|
|
9
|
+
import { Box, Text } from 'ink';
|
|
10
|
+
import { c, agentColor, mono } from '../theme.js';
|
|
11
|
+
import { reducedMotion } from '../motion/clock.js';
|
|
12
|
+
import { buildObject } from './index.js';
|
|
13
|
+
import { decodeRow, padRow, fit, len } from './model.js';
|
|
14
|
+
|
|
15
|
+
const h = React.createElement;
|
|
16
|
+
|
|
17
|
+
/** Role -> Ink colour. agent:<name> uses the stable agent shade. */
|
|
18
|
+
export function roleColor(role) {
|
|
19
|
+
if (mono()) return undefined;
|
|
20
|
+
if (typeof role === 'string' && role.startsWith('agent:')) return agentColor(role.slice(6));
|
|
21
|
+
return c(role || 'value');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function runsOf(cells, key) {
|
|
25
|
+
const out = [];
|
|
26
|
+
let run = '', role, bold = false, glyph = false, k = 0;
|
|
27
|
+
const flush = () => { if (run) out.push(h(Text, { key: `${key}-${k++}`, color: glyph ? roleColor('faint') : roleColor(role), bold }, run)); run = ''; };
|
|
28
|
+
for (const cl of cells) {
|
|
29
|
+
if (cl.role !== role || cl.bold !== bold || cl.glyph !== glyph) { flush(); role = cl.role; bold = cl.bold; glyph = cl.glyph; }
|
|
30
|
+
run += cl.ch;
|
|
31
|
+
}
|
|
32
|
+
flush();
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/** Rows start this far apart (at most 0.1 s, the lab) so the last one lands inside the 0.65 s reveal window. */
|
|
37
|
+
export const STAGGER_SPAN = 0.3;
|
|
38
|
+
export const rowStart = (i, n) => i * Math.min(0.1, STAGGER_SPAN / Math.max(1, n - 1));
|
|
39
|
+
const rowNode = (row, i, n, t, key) => h(Text, { key, wrap: 'truncate' }, ...runsOf(decodeRow(row, t, rowStart(i, n), 0.25, 70 + i), key));
|
|
40
|
+
|
|
41
|
+
export function ObjectView({ item, cols, fx = null, t = fx && fx.e != null ? fx.e : Infinity, now }) {
|
|
42
|
+
const view = buildObject(item.obj || item, cols, now);
|
|
43
|
+
const tt = reducedMotion() ? Infinity : t;
|
|
44
|
+
const rows = view.rows || [];
|
|
45
|
+
if (!view.box) {
|
|
46
|
+
return h(Box, { flexDirection: 'column', marginTop: 1, paddingLeft: 2 }, ...rows.map((r, i) => rowNode(r, i, rows.length, tt, `r${i}`)));
|
|
47
|
+
}
|
|
48
|
+
const b = view.box, w = b.width, inner = w - 4;
|
|
49
|
+
const bc = roleColor(b.border || 'muted');
|
|
50
|
+
const right = b.right ? ` ${b.right} ` : '';
|
|
51
|
+
const title = b.title ? ` ${fit(b.title, Math.max(4, w - 6 - (right ? len(right) + 1 : 0)))} ` : '';
|
|
52
|
+
const fill = Math.max(0, w - 3 - len(title) - len(right) - 1);
|
|
53
|
+
const top = h(Text, { key: 'top', wrap: 'truncate' },
|
|
54
|
+
h(Text, { color: bc }, '╭─'),
|
|
55
|
+
h(Text, { color: roleColor(b.titleRole || 'value'), bold: true }, title),
|
|
56
|
+
h(Text, { color: bc }, '─'.repeat(fill)),
|
|
57
|
+
h(Text, { color: roleColor(b.rightRole || 'muted') }, right),
|
|
58
|
+
h(Text, { color: bc }, '─╮'));
|
|
59
|
+
const body = rows.map((r, i) => h(Text, { key: `b${i}`, wrap: 'truncate' },
|
|
60
|
+
h(Text, { color: bc }, '│ '), ...runsOf(decodeRow(padRow(r, inner), tt, rowStart(i, rows.length), 0.25, 70 + i), `b${i}`), h(Text, { color: bc }, ' │')));
|
|
61
|
+
const bottom = h(Text, { key: 'bot', color: bc }, '╰' + '─'.repeat(w - 2) + '╯');
|
|
62
|
+
return h(Box, { flexDirection: 'column', marginTop: 1, paddingLeft: 1 }, top, ...body, bottom);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/** Plain text of a view at a width (tests, transcripts, --print): the box drawn with the same geometry. */
|
|
66
|
+
export function objectText(item, cols, now) {
|
|
67
|
+
const view = buildObject(item.obj || item, cols, now);
|
|
68
|
+
const rt = (r) => r.map(s => s.text).join('');
|
|
69
|
+
if (!view.box) return view.rows.map(r => ' ' + rt(fitRowText(r, cols - 2))).join('\n');
|
|
70
|
+
const b = view.box, w = b.width, inner = w - 4;
|
|
71
|
+
const right = b.right ? ` ${b.right} ` : '';
|
|
72
|
+
const title = b.title ? ` ${fit(b.title, Math.max(4, w - 6 - (right ? len(right) + 1 : 0)))} ` : '';
|
|
73
|
+
const fill = Math.max(0, w - 3 - len(title) - len(right) - 1);
|
|
74
|
+
return [` ╭─${title}${'─'.repeat(fill)}${right}─╮`, ...view.rows.map(r => ` │ ${rt(padRow(r, inner))} │`), ` ╰${'─'.repeat(w - 2)}╯`].join('\n');
|
|
75
|
+
}
|
|
76
|
+
const fitRowText = (r, n) => padRow(r, n).filter((s, i, a) => !(i === a.length - 1 && /^ +$/.test(s.text)));
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E2 workflow step flow from ONE real execution: the step list comes from the workflow definition
|
|
3
|
+
* (GET /api/workflows/:id on quoxflow) in edge order, and each step's state and timing from that
|
|
4
|
+
* execution's AEE envelopes (GET /api/executions/:id/envelopes: quox.flow.node.started | completed |
|
|
5
|
+
* failed | skipped, keyed by payload.nodeId). Only the running step moves; a human gate
|
|
6
|
+
* (approval:gate, human:task) that is reached shows amber. A step with no envelope is shown as
|
|
7
|
+
* not reached, and when no envelopes could be read every state is '?', never guessed.
|
|
8
|
+
*/
|
|
9
|
+
import { seg, fit, dur, hhmm, short, parseTs } from './model.js';
|
|
10
|
+
|
|
11
|
+
export const isGateType = (type) => /^(approval|human|hitl)[:_-]|(^|:)(approval|hitl|human)(gate|task)?$/i.test(String(type || ''));
|
|
12
|
+
|
|
13
|
+
/** Definition nodes in execution order (Kahn over edges; definition order breaks ties). */
|
|
14
|
+
export function orderNodes(def) {
|
|
15
|
+
const nodes = Array.isArray(def?.nodes) ? def.nodes : [];
|
|
16
|
+
const edges = Array.isArray(def?.edges) ? def.edges : [];
|
|
17
|
+
const ids = nodes.map(n => n.id);
|
|
18
|
+
const indeg = new Map(ids.map(id => [id, 0]));
|
|
19
|
+
for (const e of edges) if (indeg.has(e.target) && indeg.has(e.source)) indeg.set(e.target, indeg.get(e.target) + 1);
|
|
20
|
+
const out = [], seen = new Set();
|
|
21
|
+
const queue = ids.filter(id => indeg.get(id) === 0);
|
|
22
|
+
while (queue.length) {
|
|
23
|
+
const id = queue.shift();
|
|
24
|
+
if (seen.has(id)) continue;
|
|
25
|
+
seen.add(id); out.push(id);
|
|
26
|
+
for (const e of edges) if (e.source === id && indeg.has(e.target)) { indeg.set(e.target, indeg.get(e.target) - 1); if (indeg.get(e.target) === 0) queue.push(e.target); }
|
|
27
|
+
}
|
|
28
|
+
for (const id of ids) if (!seen.has(id)) out.push(id); // a cycle or dangling edge: keep definition order
|
|
29
|
+
const byId = new Map(nodes.map(n => [n.id, n]));
|
|
30
|
+
return out.map(id => byId.get(id));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** nodeId -> { started, completed, failed, skipped, payload, nodeName, nodeType } from envelopes. */
|
|
34
|
+
export function nodeStates(envelopes) {
|
|
35
|
+
const st = new Map();
|
|
36
|
+
for (const e of envelopes || []) {
|
|
37
|
+
const p = e?.payload || {};
|
|
38
|
+
const id = p.nodeId;
|
|
39
|
+
if (!id || !/^quox\.flow\.node\./.test(String(e.intent || ''))) continue;
|
|
40
|
+
const s = st.get(id) || { id };
|
|
41
|
+
const ts = parseTs(e.ts);
|
|
42
|
+
const kind = String(e.intent).slice('quox.flow.node.'.length);
|
|
43
|
+
if (kind === 'started') { if (s.started == null || ts < s.started) s.started = ts; s.nodeName ??= p.nodeName; s.nodeType ??= p.nodeType; }
|
|
44
|
+
else if (kind === 'completed') { s.completed = ts; s.result = p; }
|
|
45
|
+
else if (kind === 'failed') { s.failed = ts; s.error = p.error || p.message || null; }
|
|
46
|
+
else if (kind === 'skipped') s.skipped = ts;
|
|
47
|
+
st.set(id, s);
|
|
48
|
+
}
|
|
49
|
+
return st;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const triggerDetail = (n) => {
|
|
53
|
+
const c = n?.config || {};
|
|
54
|
+
return c.label || c.expression || c.event || c.path || '';
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* d: { execution, envelopes: [] | null, def: { nodes, edges } | null, now }
|
|
59
|
+
* -> view { rows } (no box: the lab draws this as an indented flow)
|
|
60
|
+
*/
|
|
61
|
+
export function stepFlow(d, cols) {
|
|
62
|
+
const ex = d.execution || {};
|
|
63
|
+
const now = d.now ?? Date.now();
|
|
64
|
+
const nw = cols < 60;
|
|
65
|
+
const rows = [];
|
|
66
|
+
const status = String(ex.status || 'unknown');
|
|
67
|
+
const head = [seg(ex.workflowName || ex.workflowId || 'workflow', 'value', true), seg(' ')];
|
|
68
|
+
const meta = [`run ${short(ex.id, 10)}`, status];
|
|
69
|
+
if (!nw && ex.startedAt) meta.push(`started ${hhmm(ex.startedAt)}`);
|
|
70
|
+
head.push(seg(meta.join(' · '), status === 'failed' ? 'error' : 'muted'));
|
|
71
|
+
rows.push(head);
|
|
72
|
+
|
|
73
|
+
const states = d.envelopes ? nodeStates(d.envelopes) : null;
|
|
74
|
+
let nodes = d.def ? orderNodes(d.def) : [];
|
|
75
|
+
if (!nodes.length && states) nodes = [...states.values()].filter(s => !/_replay_\d+$/.test(s.id)).map(s => ({ id: s.id, name: s.nodeName, type: s.nodeType }));
|
|
76
|
+
if (!nodes.length) {
|
|
77
|
+
rows.push([seg(d.envelopes ? 'No step records for this run.' : 'Steps could not be read for this run (no definition or envelopes).', 'muted')]);
|
|
78
|
+
return { rows };
|
|
79
|
+
}
|
|
80
|
+
const running = status === 'running' || status === 'paused';
|
|
81
|
+
const nameW = Math.min(Math.max(12, Math.floor((cols - 8) * 0.45)), Math.max(8, ...nodes.map(n => [...String(n.name || n.id)].length)));
|
|
82
|
+
for (const n of nodes) {
|
|
83
|
+
const s = states?.get(n.id);
|
|
84
|
+
const type = n.type || s?.nodeType || '';
|
|
85
|
+
const gate = isGateType(type);
|
|
86
|
+
let mark = '?', mrole = 'muted', nrole = 'value', det = '', drole = 'muted', bold = false;
|
|
87
|
+
if (!states) { det = 'state not readable'; }
|
|
88
|
+
else if (/^trigger/.test(type)) { mark = '●'; mrole = 'sky'; det = triggerDetail(n); }
|
|
89
|
+
else if (s?.failed != null) { mark = '✖'; mrole = 'error'; det = fit(s.error ? `failed: ${s.error}` : 'failed', 60); drole = 'error'; }
|
|
90
|
+
else if (s?.completed != null) {
|
|
91
|
+
mark = '✓'; mrole = 'ok';
|
|
92
|
+
const r = s.result || {};
|
|
93
|
+
const bits = [];
|
|
94
|
+
if (r.actions_taken != null) bits.push(`${r.actions_taken} action${r.actions_taken === 1 ? '' : 's'}`);
|
|
95
|
+
if (s.started != null) bits.push(dur(s.completed - s.started));
|
|
96
|
+
det = bits.join(' · ');
|
|
97
|
+
} else if (s?.skipped != null) { mark = '○'; mrole = 'faint'; nrole = 'muted'; det = 'skipped'; }
|
|
98
|
+
else if (s?.started != null && running) {
|
|
99
|
+
if (gate) { mark = '◐'; mrole = 'warn'; det = 'waiting for approval'; drole = 'warn'; }
|
|
100
|
+
else { mark = '◐'; mrole = 'sky'; bold = true; det = `running · ${dur(now - s.started)}`; }
|
|
101
|
+
} else if (s?.started != null) { mark = '?'; det = `started, no end recorded (run ${status})`; drole = 'warn'; }
|
|
102
|
+
else { mark = '○'; mrole = 'faint'; nrole = 'muted'; if (gate && running) { det = 'human gate'; drole = 'warn'; } }
|
|
103
|
+
rows.push([seg(' │', 'faint')]);
|
|
104
|
+
const nm = fit(n.name || n.id, nameW);
|
|
105
|
+
const row = [seg(' '), seg(mark, mrole, bold), seg(' '), seg(nm, nrole, bold)];
|
|
106
|
+
if (det) {
|
|
107
|
+
const pad = nw ? ' ' : ' '.repeat(Math.max(2, nameW + 2 - [...nm].length));
|
|
108
|
+
row.push(seg(pad), seg(fit(det, Math.max(6, cols - 6 - (nw ? [...nm].length + 2 : nameW + 2))), drole));
|
|
109
|
+
}
|
|
110
|
+
rows.push(row);
|
|
111
|
+
}
|
|
112
|
+
if (!running && (ex.duration != null || ex.completedAt)) {
|
|
113
|
+
const total = ex.duration != null ? dur(ex.duration) : dur(parseTs(ex.completedAt) - parseTs(ex.startedAt));
|
|
114
|
+
rows.push([seg(' ')]);
|
|
115
|
+
if (status === 'failed') rows.push([seg('✖ ', 'error'), seg(fit(`failed${total ? ` after ${total}` : ''}${ex.error ? `: ${typeof ex.error === 'string' ? ex.error : ex.error.message || ''}` : ''}`, cols - 6), 'error')]);
|
|
116
|
+
else rows.push([seg(status === 'completed' ? '✔ ' : '· ', status === 'completed' ? 'ok' : 'muted'), seg(`${status}${total ? ` in ${total}` : ''}${ex.envelopeCount != null ? ` · ${ex.envelopeCount} envelopes` : ''}`, 'muted')]);
|
|
117
|
+
}
|
|
118
|
+
return { rows };
|
|
119
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* /observers: facts from persisted data only (LUCID situation, LUCID changes, lessons taught,
|
|
3
|
+
* reflect / learn / insight counters). No model-generated text: every line is a stored field
|
|
4
|
+
* or a count of them. Pure: takes api.observers() output, returns [[text, tone]].
|
|
5
|
+
*/
|
|
6
|
+
import { clip } from './theme.js';
|
|
7
|
+
|
|
8
|
+
const why = (p) => (p.unavailable ? 'no endpoint on this server' : `unreadable (${p.error})`);
|
|
9
|
+
|
|
10
|
+
export function observerLines(o, cols = 80) {
|
|
11
|
+
const out = [];
|
|
12
|
+
const add = (t, tone) => out.push([clip(t, cols - 4), tone]);
|
|
13
|
+
const sit = o.situation;
|
|
14
|
+
if (!sit?.ok) add(`◉ LUCID situation: ${why(sit || { error: 'no answer' })}`, 'warn');
|
|
15
|
+
else {
|
|
16
|
+
const s = sit.v?.situation || {};
|
|
17
|
+
add(`◉ LUCID: ${s.L0 || 'no summary recorded'}`);
|
|
18
|
+
const needs = s.needs_you || [];
|
|
19
|
+
needs.slice(0, 3).forEach(n => add(` needs you: ${n.L0 || n.ref}`, 'warn'));
|
|
20
|
+
if (needs.length > 3) add(` +${needs.length - 3} more needing you (quox lucid)`);
|
|
21
|
+
const cs = s.cannot_see || {};
|
|
22
|
+
const blind = [...(cs.unwired || []).map(x => `${x} not wired`), ...(cs.silent || []).map(x => `${x.feed} silent`), ...(cs.failing || []).map(x => `${x.feed || x} failing`)];
|
|
23
|
+
if (cs.coverage) add(` sees ${cs.coverage.live} of ${cs.coverage.expected} sources${blind.length ? `; ${blind.join(', ')}` : ''}`);
|
|
24
|
+
const total = s.changed_total ?? (s.changed || []).length;
|
|
25
|
+
const range = s.cursor ? ` (journal ${s.cursor.from} to ${s.cursor.to})` : '';
|
|
26
|
+
add(`◉ Changed since you last looked: ${total}${range}`);
|
|
27
|
+
(s.changed || []).slice(0, 5).forEach(c => add(` ${c.L0 || c.ref}${c.from && c.to ? ` (${c.from} to ${c.to})` : ''}`));
|
|
28
|
+
if (total > 5) add(` +${total - 5} more (quox lucid since-last)`);
|
|
29
|
+
}
|
|
30
|
+
out.push(['', undefined]);
|
|
31
|
+
add('Self-learning records');
|
|
32
|
+
const les = o.lessons;
|
|
33
|
+
add(les?.ok ? ` LUCID lessons taught: ${(les.v?.lessons || []).length}` : ` LUCID lessons: ${why(les || { error: 'no answer' })}`);
|
|
34
|
+
for (const p of o.learning || []) {
|
|
35
|
+
if (!p.ok) { add(` ${p.label || 'stat'}: ${why(p)}`); continue; }
|
|
36
|
+
const f = p.v.fields || {};
|
|
37
|
+
const last = f.lastOutcomeAt || f.lastObservationAt || f.lastSignalAt || f.lastClusterAt;
|
|
38
|
+
add(` ${p.v.label}: ${p.v.text || 'no data'}${last ? ` · last ${last}` : ''}`);
|
|
39
|
+
}
|
|
40
|
+
add(' Memory consolidation: no read API found, not shown.');
|
|
41
|
+
return out;
|
|
42
|
+
}
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Getting started: EVERY line the setup guide shows lives in this file, so the owner can review the whole
|
|
3
|
+
* first impression in one place (FIRST_RUN_JOURNEY.md, "Copy review"). House voice: no em dashes, no
|
|
4
|
+
* double hyphens in prose, `Quox` mixed case, nothing claimed that the code does not do.
|
|
5
|
+
*
|
|
6
|
+
* Functions take the facts they print (a URL, a size, a provider name); nothing here computes anything.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
export const LABEL = 'Getting started';
|
|
10
|
+
export const LABEL_SUB = 'setup guide · no AI yet, every line here is scripted';
|
|
11
|
+
|
|
12
|
+
// The Q says HELLO itself, decoded in beside the mark right after the intro (qmotion.js HelloIntro); the
|
|
13
|
+
// guide then carries on with GREETING_REST. GREETING is the whole sentence, for anywhere that shows it at once.
|
|
14
|
+
export const HELLO = "Hi, I'm Quox.";
|
|
15
|
+
export const GREETING_REST = 'I run AI agents for you on your own machine, and I keep proof of everything they do.';
|
|
16
|
+
export const GREETING = `${HELLO} ${GREETING_REST}`;
|
|
17
|
+
export const QUESTION = 'What would you like help with?';
|
|
18
|
+
export const EXAMPLES = ['watch my servers', 'automate something', 'code with agents', 'sort my email', 'just look around'];
|
|
19
|
+
export const INPUT_PLACEHOLDER = 'Type anything, or a number from the list';
|
|
20
|
+
|
|
21
|
+
/** One tailored line per intent: what Quox will do for that. */
|
|
22
|
+
export const INTENT_LINES = {
|
|
23
|
+
servers: 'Quox can keep an eye on your servers, tell you what changed, and ask you before it touches anything.',
|
|
24
|
+
automate: 'Quox can run that as a workflow on a schedule, with an approval step wherever you want one.',
|
|
25
|
+
code: 'Quox can run coding agents on your repos, and every change they make leaves a receipt you can check.',
|
|
26
|
+
email: 'Quox can sort your inbox and draft replies. Nothing is sent without your say so.',
|
|
27
|
+
explore: 'Good plan. Once your Quox is running I will show you around, with real agents and real receipts.'
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/** Off-script: an honest deferral, shown instantly (no model is running yet). */
|
|
31
|
+
export const DEFERRAL = 'I can answer that properly once your Quox is running.';
|
|
32
|
+
|
|
33
|
+
// The install time is NOT promised as a number until it has been measured on a clean machine (C2 exit).
|
|
34
|
+
export const OFFER = "To do that I need a QuoxCORE, the engine your agents run in. It runs here in Docker, it's free for your own use, and the first install builds from source, so it takes a while.";
|
|
35
|
+
export const OFFER_ASK = 'Set it up now? (Y/n)';
|
|
36
|
+
export const ALREADY = 'Already have one? /connect server <url>';
|
|
37
|
+
export const DECLINED = 'No problem. Run quox any time and we will pick up here.';
|
|
38
|
+
export const DECLINED_HINT = 'Type yes to set up now, /connect server <url> to use one you have, or /exit.';
|
|
39
|
+
|
|
40
|
+
export const RESUME = 'Picking up where we left off.';
|
|
41
|
+
export const resumeRequest = (text) => `You asked: "${text}"`;
|
|
42
|
+
|
|
43
|
+
// ---- /connect server ---------------------------------------------------------------------------
|
|
44
|
+
export const CONNECT_USAGE = 'Usage: /connect server <url> for example /connect server https://quox.example.com';
|
|
45
|
+
export const connectBadUrl = (u) => `That does not look like a server address: ${u}. Use the full URL, starting with https://`;
|
|
46
|
+
export const connectHandoff = (url) => `Connecting to ${url}. Sign in with your QuoxCORE email and password.`;
|
|
47
|
+
export const CONNECT_DONE = 'Signed in. Starting Quox.';
|
|
48
|
+
export const connectFailed = (url) => `Sign-in to ${url} did not complete. Check the address, then try /connect server again.`;
|
|
49
|
+
export const CONNECT_INTENT_LINE = 'Great. Point me at it with /connect server <url> and sign in, and we go straight to your agents.';
|
|
50
|
+
|
|
51
|
+
// ---- preflight ---------------------------------------------------------------------------------
|
|
52
|
+
export const PREFLIGHT_TITLE = 'Checking this machine';
|
|
53
|
+
export const PREFLIGHT_RETRY = 'r to check again · q to stop';
|
|
54
|
+
export const PREFLIGHT_RETRY_DRY = 'r to check again · c to continue anyway (dry run) · q to stop';
|
|
55
|
+
export const PREFLIGHT_CONTINUED = 'Continuing anyway: dry run, so nothing will be installed on this machine.';
|
|
56
|
+
export const PREFLIGHT_OK = 'This machine is ready.';
|
|
57
|
+
export const PREFLIGHT_FAIL = 'Fix the items above, then press r to check again.';
|
|
58
|
+
export const SETUP_DOCS = 'https://quox.ai/docs/setup';
|
|
59
|
+
|
|
60
|
+
export const CHECK = {
|
|
61
|
+
docker: {
|
|
62
|
+
name: 'DOCKER',
|
|
63
|
+
ok: (v) => `Docker ${v}, daemon reachable`,
|
|
64
|
+
missing: 'Docker is not installed',
|
|
65
|
+
missingFix: 'Install Docker, then press r.',
|
|
66
|
+
missingLink: 'https://docs.docker.com/get-docker/',
|
|
67
|
+
perm: 'Docker is installed, but this user cannot reach the daemon',
|
|
68
|
+
permFix: 'Run: sudo usermod -aG docker $USER && newgrp docker then press r.',
|
|
69
|
+
down: 'Docker is installed, but the daemon is not running',
|
|
70
|
+
downFix: 'Start Docker (Docker Desktop, or: sudo systemctl start docker), then press r.',
|
|
71
|
+
link: 'https://docs.docker.com/engine/install/'
|
|
72
|
+
},
|
|
73
|
+
compose: {
|
|
74
|
+
name: 'COMPOSE',
|
|
75
|
+
ok: (v) => `Compose v${String(v).replace(/^v/, '')}`,
|
|
76
|
+
missing: 'Docker Compose v2 was not found',
|
|
77
|
+
missingFix: 'Install the Compose plugin (it ships with Docker Desktop), then press r.',
|
|
78
|
+
old: (v) => `Compose ${v} is too old, v2 is needed`,
|
|
79
|
+
link: 'https://docs.docker.com/compose/install/'
|
|
80
|
+
},
|
|
81
|
+
tools: {
|
|
82
|
+
name: 'TOOLS',
|
|
83
|
+
ok: 'git and openssl found',
|
|
84
|
+
missing: (list) => `Not found: ${list.join(', ')}`,
|
|
85
|
+
fix: (list) => `Install ${list.join(' and ')} with your package manager, then press r.`,
|
|
86
|
+
link: 'https://quox.ai/docs/setup'
|
|
87
|
+
},
|
|
88
|
+
disk: {
|
|
89
|
+
name: 'DISK',
|
|
90
|
+
ok: (gb) => `${gb} GB free`,
|
|
91
|
+
low: (gb, rec) => `${gb} GB free, ${rec} GB recommended (it may run out while building)`,
|
|
92
|
+
fail: (gb, min) => `${gb} GB free, at least ${min} GB needed`,
|
|
93
|
+
fix: (min) => `Free up space until at least ${min} GB is available, then press r.`,
|
|
94
|
+
link: 'https://quox.ai/docs/setup'
|
|
95
|
+
},
|
|
96
|
+
ram: {
|
|
97
|
+
name: 'MEMORY',
|
|
98
|
+
ok: (gb) => `${gb} GB of RAM`,
|
|
99
|
+
low: (gb, rec) => `${gb} GB of RAM, ${rec} GB recommended (it will run, slowly)`,
|
|
100
|
+
fail: (gb, min) => `${gb} GB of RAM, at least ${min} GB needed`,
|
|
101
|
+
fix: (min) => `Quox needs a machine with at least ${min} GB of RAM.`,
|
|
102
|
+
link: 'https://quox.ai/docs/setup'
|
|
103
|
+
},
|
|
104
|
+
ports: {
|
|
105
|
+
name: 'PORTS',
|
|
106
|
+
ok: (n) => `${n} ports free`,
|
|
107
|
+
busy: (list) => `In use: ${list.length > 5 ? `${list.slice(0, 4).join(', ')} and ${list.length - 4} more` : list.join(', ')}`,
|
|
108
|
+
ownInstall: 'In use by the Quox being installed here',
|
|
109
|
+
quoxFix: 'Quox looks like it is already running here. Use it with /connect server https://localhost:3000, or stop it first.',
|
|
110
|
+
fix: (p) => `Stop whatever is using port ${p} (see: ss -ltnp 'sport = :${p}'), then press r.`,
|
|
111
|
+
link: 'https://quox.ai/docs/setup'
|
|
112
|
+
},
|
|
113
|
+
running: 'checking'
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// ---- credential --------------------------------------------------------------------------------
|
|
117
|
+
export const CRED_TITLE = 'Which AI should your agents use?';
|
|
118
|
+
export const CRED_HINT = '↑↓ to choose · Enter to pick';
|
|
119
|
+
export const CRED_OPTIONS = {
|
|
120
|
+
chatgpt: { label: 'Sign in with ChatGPT', note: 'coming soon' },
|
|
121
|
+
openai: { label: 'OpenAI API key', note: '' },
|
|
122
|
+
anthropic: { label: 'Claude API key', note: '' },
|
|
123
|
+
ollama: { label: 'Ollama on this machine', note: 'found at localhost:11434' },
|
|
124
|
+
// B3: offered even when nothing answers locally; the user enters the LAN URL of the machine running it.
|
|
125
|
+
ollamaLan: { label: 'Ollama on another machine', note: 'enter its URL' },
|
|
126
|
+
skip: { label: 'Skip for now', note: 'agents will be limited' }
|
|
127
|
+
};
|
|
128
|
+
export const credPrompt = (label) => `Paste your ${label}. It stays in memory only: never written to disk, history or the transcript.`;
|
|
129
|
+
export const CRED_INPUT_HINT = 'Enter to use it · Esc to go back';
|
|
130
|
+
export const credHeld = (label, fp) => `${label} held in memory (fingerprint ${fp}). It is stored in your Quox vault after install, never on disk here.`;
|
|
131
|
+
export const CRED_EMPTY = 'Nothing was pasted. Paste the key, or Esc to choose again.';
|
|
132
|
+
export const CRED_OLLAMA = 'Ollama it is. The Quox server runs in Docker and cannot reach "localhost", so after install connect it by this machine\'s LAN address: start Ollama with OLLAMA_HOST=0.0.0.0, then /connect ollama http://<lan-ip>:11434';
|
|
133
|
+
export const OLLAMA_URL_PROMPT = 'The Ollama URL on your network, for example http://<lan-ip>:11434. Not a secret. On that machine, start Ollama with OLLAMA_HOST=0.0.0.0 and allow port 11434.';
|
|
134
|
+
export const OLLAMA_URL_HINT = 'Enter to use it · Esc to go back';
|
|
135
|
+
export const OLLAMA_URL_BAD = 'That does not look like an Ollama URL. Use http://<host>:11434, or Esc to choose again.';
|
|
136
|
+
export const ollamaChosen = (url) => `Ollama it is, at ${url}. After install, run /connect ollama ${url} so the server checks it and lists its models.`;
|
|
137
|
+
export const CRED_SKIPPED = 'Skipped. Until you add an AI your agents are limited: they cannot think or answer for themselves. Add one later with /connect.';
|
|
138
|
+
export const CRED_CHATGPT_SOON = 'Sign in with ChatGPT is coming soon. Pick another option for now.';
|
|
139
|
+
|
|
140
|
+
// ---- install -----------------------------------------------------------------------------------
|
|
141
|
+
export const INSTALL_TITLE = 'Installing Quox';
|
|
142
|
+
export const INSTALL_DRY = 'dry run: nothing is downloaded, built or started';
|
|
143
|
+
export const INSTALL_HINT = 'v to show the log · Ctrl+C to stop (you can pick it up again)';
|
|
144
|
+
export const STAGES = {
|
|
145
|
+
fetch: 'Fetch the source',
|
|
146
|
+
configure: 'Configure secrets and certificates',
|
|
147
|
+
build: 'Build the images',
|
|
148
|
+
start: 'Start the services',
|
|
149
|
+
health: 'Wait until healthy'
|
|
150
|
+
};
|
|
151
|
+
export const INSTALL_FAILED = (stage) => `The install stopped at: ${stage}. Press v for the log, r to try again.`;
|
|
152
|
+
export const INSTALL_NOT_YET = 'Installing from here arrives in the next release. For now, run this in another terminal:';
|
|
153
|
+
export const INSTALL_COMMAND = 'curl -fsSL https://get.quox.ai | sh';
|
|
154
|
+
export const INSTALL_NOT_YET_AFTER = 'Then run quox again and type /connect server https://localhost:3000';
|
|
155
|
+
export const INSTALL_KEY_GONE = 'Your key was not kept: it only ever lived in memory. You will add it again after install.';
|
|
156
|
+
export const installWhere = (dir) => `Installing into ${dir}`;
|
|
157
|
+
export const installScriptLead = (url, bytes) => `Installer ${url} (${Math.max(1, Math.round(bytes / 1024))} KB), SHA256`;
|
|
158
|
+
export const installScript = (url, sha, bytes) => `${installScriptLead(url, bytes)} ${sha}`;
|
|
159
|
+
export const INSTALL_STOP_ASK = 'Stop the install? You can pick it up next time. (y/N)';
|
|
160
|
+
export const INSTALL_STOP_KEEP = 'Carrying on.';
|
|
161
|
+
export const INSTALL_STOPPED = 'Install stopped. Run quox to pick up where you left off.';
|
|
162
|
+
export const INSTALL_EARLIER = 'done earlier';
|
|
163
|
+
export const INSTALL_HEALTH_ONLY = 'Your Quox was already started last time, so this only checks that it is healthy.';
|
|
164
|
+
export const INSTALL_DEGRADED = 'Some services are still starting. Press r to check again in a minute, or v for the log.';
|
|
165
|
+
export const INSTALL_FAIL_HINT = 'v log · r try again · Ctrl+C stop';
|
|
166
|
+
|
|
167
|
+
// ---- owner account (C2c) -----------------------------------------------------------------------
|
|
168
|
+
export const OWNER_TITLE = 'Your owner account';
|
|
169
|
+
export const OWNER_WHY = 'This account owns your Quox. You sign in to the dashboard with it.';
|
|
170
|
+
export const OWNER_EMAIL_PLACEHOLDER = 'your email address';
|
|
171
|
+
export const OWNER_EMAIL_BAD = 'That does not look like an email address.';
|
|
172
|
+
export const OWNER_PW_PROMPT = 'Choose a password: 12 or more characters with upper and lower case, a number and a symbol. Or press Enter on an empty line and I will make a strong one.';
|
|
173
|
+
export const OWNER_PW_HINT = 'Enter to use it · Enter on empty to generate one · Esc to change the email';
|
|
174
|
+
export const ownerPwShownOnce = (pw) => `Your password, shown once: ${pw}`;
|
|
175
|
+
export const OWNER_PW_SAVE = 'Save it in your password manager now. Quox keeps only a sign-in token on this machine, never the password.';
|
|
176
|
+
export const OWNER_EXISTS = 'This Quox already has an owner. Enter that account\'s email and password to sign in.';
|
|
177
|
+
|
|
178
|
+
// ---- finishing ---------------------------------------------------------------------------------
|
|
179
|
+
export const FINISH_TITLE = 'Finishing';
|
|
180
|
+
export const FIN = {
|
|
181
|
+
account: { name: 'ACCOUNT', running: 'creating your owner account', ok: (e) => `owner ${e}`, existing: (e) => `existing owner ${e}` },
|
|
182
|
+
signin: { name: 'SIGN-IN', running: 'signing you in', ok: (org) => `signed in${org ? ` to ${org}` : ''}, token saved` },
|
|
183
|
+
vault: {
|
|
184
|
+
name: 'AI KEY', running: (label) => `testing your ${label}, then storing it in your vault`,
|
|
185
|
+
skipped: 'no key to store', ollama: 'Ollama: add it to your vault with quox vault create --type ollama'
|
|
186
|
+
},
|
|
187
|
+
link: { name: 'DASHBOARD', running: 'making your one-time sign-in link', ok: 'one-time link ready (10 minutes, single use)', fail: (e) => `no link (${e}), sign in with your owner account instead` }
|
|
188
|
+
};
|
|
189
|
+
export const KEY_GONE_AFTER = 'The key is gone from this terminal\'s memory now.';
|
|
190
|
+
export const keyNotStored = (provider) => `Add it again any time with /connect ${provider}.`;
|
|
191
|
+
|
|
192
|
+
// ---- finale ------------------------------------------------------------------------------------
|
|
193
|
+
export const FINALE_DRY_BANNER = 'Dry run: this is the planned finish. Nothing was installed.';
|
|
194
|
+
export const FINALE_RUNNING = 'Your Quox is running.';
|
|
195
|
+
export const REPLAY_TOUR = 'Give me a short tour of what you can do here.';
|
|
196
|
+
export const finaleReplay = (text) => text ? `Your first real turn sends what you asked: "${text}"` : 'Your first real turn: a short tour of what your agents can do.';
|
|
197
|
+
export const FINALE_REPLAY_DRY = '(the reply and its receipt appear here once the install is real)';
|
|
198
|
+
export const finaleDashboard = (url) => `Your dashboard is ready: ${url} (one-time sign-in link)`;
|
|
199
|
+
export const finaleDashboardPlain = (url) => `Your dashboard is ready: ${url} (sign in with your owner account)`;
|
|
200
|
+
// When the line does not fit the terminal, the URL goes on its own unwrapped line so it stays clickable.
|
|
201
|
+
export const FINALE_DASHBOARD_LEAD = 'Your dashboard is ready (one-time sign-in link, 10 minutes):';
|
|
202
|
+
export const FINALE_DASHBOARD_LEAD_PLAIN = 'Your dashboard is ready (sign in with your owner account):';
|
|
203
|
+
export const FINALE_CERT = 'Your browser will warn about the certificate the first time: it is the self-signed one this install made for you.';
|
|
204
|
+
export const FINALE_PROOF = 'Everything you just did is there, with the proof.';
|
|
205
|
+
export const FINALE_TIPS_TITLE = 'Three things to try:';
|
|
206
|
+
export const TIPS = {
|
|
207
|
+
servers: ['/observers shows what Quox can see right now, and what it cannot', '/connect shows what is connected and adds AI keys safely', 'Just run quox any time; your conversations are kept on your server'],
|
|
208
|
+
automate: ['/skills lists what your agents already know how to do', '/approvals shows anything waiting for your yes', 'Just run quox any time; your conversations are kept on your server'],
|
|
209
|
+
code: ['/attach <agent> sends your turns to one agent, /detach comes back', '/connect shows what is connected and adds AI keys safely', 'Just run quox any time; your conversations are kept on your server'],
|
|
210
|
+
email: ['/approvals shows anything waiting for your yes; nothing is sent without it', '/connect shows what is connected and adds AI keys safely', 'Just run quox any time; your conversations are kept on your server'],
|
|
211
|
+
explore: ['/help lists every command', '/attach <agent> sends your turns to one agent, /detach comes back', 'Just run quox any time; your conversations are kept on your server']
|
|
212
|
+
};
|
|
213
|
+
TIPS.offscript = TIPS.explore;
|
|
214
|
+
export const FINALE_TIPS = TIPS.explore; // the dry run and any intent without its own set
|
|
215
|
+
// K2: one gentle, honest next connection that fits what they said they wanted.
|
|
216
|
+
export const K2 = {
|
|
217
|
+
servers: 'You said you want to watch your servers. None are connected yet, and adding one from the terminal is not built yet: add your first host on the dashboard Fleet page.',
|
|
218
|
+
automate: 'You said you want to automate something. Already use n8n? Bring a workflow across with: quox workflow import-n8n --file <path>',
|
|
219
|
+
code: 'You said you want to code with agents. Connect GitHub so they can see your repos: quox vault create --type github',
|
|
220
|
+
email: 'You said you want help with email. Connect your mailbox: quox mail connect --provider gmail (or outlook, icloud)',
|
|
221
|
+
explore: 'Want your agents to do more? /connect shows what is connected and what you can add.'
|
|
222
|
+
};
|
|
223
|
+
K2.offscript = K2.explore;
|
|
224
|
+
export const k2NoAi = (provider) => `Your agents have no AI yet, so they cannot answer for themselves. Connect one now? /connect ${provider}`;
|
|
225
|
+
export const FINALE_EXIT = 'Enter to start talking to your Quox';
|
|
226
|
+
export const FINALE_EXIT_DRY = 'Enter to finish';
|
|
227
|
+
|
|
228
|
+
// ---- help, misc --------------------------------------------------------------------------------
|
|
229
|
+
export const HELP = [
|
|
230
|
+
['/connect server <url>', 'use a QuoxCORE you already have'],
|
|
231
|
+
['/exit', 'leave; next time we pick up here']
|
|
232
|
+
];
|
|
233
|
+
export const SLASH_UNKNOWN = 'Only /connect server, /help and /exit work until your Quox is running.';
|
|
234
|
+
export const BYE = 'See you soon. Run quox to pick up where you left off.';
|
|
235
|
+
export const NO_SERVER_PRINT = 'Run quox (without --print) to get started.';
|