quoxcli 0.4.0 → 0.5.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.
Files changed (175) hide show
  1. package/CHANGELOG.md +61 -0
  2. package/README.md +2 -2
  3. package/bin/quox.js +19 -1
  4. package/bin/quoxon.js +6 -1
  5. package/lib/api/endpoints/admin.js +7 -0
  6. package/lib/api/endpoints/agent-attach.js +7 -0
  7. package/lib/api/endpoints/compliance.js +25 -0
  8. package/lib/api/endpoints/inbox.js +6 -0
  9. package/lib/api/endpoints/inboxq.js +19 -0
  10. package/lib/api/endpoints/index.js +2 -0
  11. package/lib/api/endpoints/mon.js +122 -0
  12. package/lib/api/endpoints/tools.js +13 -0
  13. package/lib/api/endpoints/v1.js +13 -0
  14. package/lib/commands/admin.js +35 -0
  15. package/lib/commands/agentAttach.js +15 -0
  16. package/lib/commands/approvals.js +193 -3
  17. package/lib/commands/ask.js +1 -1
  18. package/lib/commands/auth.js +64 -11
  19. package/lib/commands/backup.js +11 -4
  20. package/lib/commands/capabilities.js +3 -2
  21. package/lib/commands/chat.js +15 -0
  22. package/lib/commands/claude.js +4 -1
  23. package/lib/commands/compliance.js +109 -0
  24. package/lib/commands/connect.js +69 -0
  25. package/lib/commands/dev.js +6 -8
  26. package/lib/commands/doctor.js +22 -3
  27. package/lib/commands/evidence.js +2 -1
  28. package/lib/commands/gripe.js +24 -13
  29. package/lib/commands/inbox.js +37 -3
  30. package/lib/commands/inboxq.js +147 -0
  31. package/lib/commands/kuma.js +78 -5
  32. package/lib/commands/licenceLinkView.js +311 -0
  33. package/lib/commands/linkedin.js +19 -0
  34. package/lib/commands/loop.js +30 -3
  35. package/lib/commands/lucid.js +1 -0
  36. package/lib/commands/mail.js +161 -20
  37. package/lib/commands/memory.js +68 -4
  38. package/lib/commands/memorySync.js +158 -0
  39. package/lib/commands/mon.js +55 -144
  40. package/lib/commands/notes.js +29 -7
  41. package/lib/commands/notesSeal.js +259 -0
  42. package/lib/commands/objective.js +5 -2
  43. package/lib/commands/offload.js +2 -3
  44. package/lib/commands/plan.js +17 -0
  45. package/lib/commands/provider.js +29 -8
  46. package/lib/commands/qlarity.js +32 -1
  47. package/lib/commands/run.js +2 -1
  48. package/lib/commands/server.js +37 -0
  49. package/lib/commands/setup.js +127 -75
  50. package/lib/commands/skills.js +4 -1
  51. package/lib/commands/slot.js +49 -9
  52. package/lib/commands/status.js +92 -44
  53. package/lib/commands/tool.js +30 -0
  54. package/lib/commands/vault.js +6 -3
  55. package/lib/commands/walkthrough.js +14 -2
  56. package/lib/commands/watch.js +11 -2
  57. package/lib/commands/wazuh.js +824 -0
  58. package/lib/commands/wazuhIntake.js +317 -0
  59. package/lib/commands/wazuhReport.js +192 -0
  60. package/lib/commands/workflow.js +47 -4
  61. package/lib/commands/x.js +50 -0
  62. package/lib/core/announce.js +86 -0
  63. package/lib/core/auth.js +20 -2
  64. package/lib/core/callMe.js +118 -0
  65. package/lib/core/client.js +27 -1
  66. package/lib/core/commandRegistrars.js +8 -0
  67. package/lib/core/config.js +28 -2
  68. package/lib/core/confirm.js +68 -7
  69. package/lib/core/crossOrgApprovals.js +69 -0
  70. package/lib/core/errors.js +52 -1
  71. package/lib/core/governedSession.js +135 -5
  72. package/lib/core/inboxResolution.js +3 -2
  73. package/lib/core/loginGuard.js +73 -0
  74. package/lib/core/offload.js +37 -0
  75. package/lib/core/org-override.js +5 -1
  76. package/lib/core/output.js +1 -1
  77. package/lib/core/profileTrust.js +182 -0
  78. package/lib/core/quoxglyph.js +22 -1
  79. package/lib/core/router.js +14 -0
  80. package/lib/core/stepUpPrompt.js +99 -0
  81. package/lib/core/version.js +1 -1
  82. package/lib/core/walkthrough.js +9 -5
  83. package/lib/dragon/select.js +2 -2
  84. package/lib/quoxon/EXTENSIONS.md +142 -19
  85. package/lib/quoxon/api.js +185 -34
  86. package/lib/quoxon/app.js +1032 -152
  87. package/lib/quoxon/approvalBatch.js +117 -0
  88. package/lib/quoxon/approvalFacts.js +56 -2
  89. package/lib/quoxon/approvalSentence.js +8 -4
  90. package/lib/quoxon/approvalsView.js +69 -11
  91. package/lib/quoxon/autoOpen.js +94 -0
  92. package/lib/quoxon/backgroundWork.js +59 -0
  93. package/lib/quoxon/bang.js +1 -1
  94. package/lib/quoxon/capabilities.js +7 -2
  95. package/lib/quoxon/commandShort.js +7 -0
  96. package/lib/quoxon/commands.js +40 -10
  97. package/lib/quoxon/connect.js +5 -3
  98. package/lib/quoxon/deckLazy.js +58 -0
  99. package/lib/quoxon/destructive.js +35 -0
  100. package/lib/quoxon/dnd.js +71 -0
  101. package/lib/quoxon/dragonCompanion.js +37 -7
  102. package/lib/quoxon/dragonMoments.js +78 -0
  103. package/lib/quoxon/echo.js +1 -0
  104. package/lib/quoxon/events.js +43 -7
  105. package/lib/quoxon/extensions.js +398 -38
  106. package/lib/quoxon/gripeQueue.js +135 -0
  107. package/lib/quoxon/inboxView.js +125 -22
  108. package/lib/quoxon/keys.js +2 -2
  109. package/lib/quoxon/loopCard.js +5 -1
  110. package/lib/quoxon/loopCards.js +17 -8
  111. package/lib/quoxon/loopStream.js +20 -9
  112. package/lib/quoxon/mailView.js +71 -0
  113. package/lib/quoxon/main.js +20 -5
  114. package/lib/quoxon/markdown.js +1 -1
  115. package/lib/quoxon/model.js +15 -0
  116. package/lib/quoxon/neverAnswered.js +20 -0
  117. package/lib/quoxon/objects/away.js +43 -5
  118. package/lib/quoxon/onboarding/connectServer.js +162 -0
  119. package/lib/quoxon/onboarding/copy.js +6 -0
  120. package/lib/quoxon/onboarding/index.js +5 -43
  121. package/lib/quoxon/onboarding/probe.js +20 -1
  122. package/lib/quoxon/onboarding/view.js +16 -3
  123. package/lib/quoxon/orgSwitch.js +22 -0
  124. package/lib/quoxon/print.js +2 -0
  125. package/lib/quoxon/profileSwitch.js +7 -0
  126. package/lib/quoxon/promptHistory.js +3 -1
  127. package/lib/quoxon/render.js +63 -14
  128. package/lib/quoxon/roster.js +10 -5
  129. package/lib/quoxon/rosterSessions.js +44 -0
  130. package/lib/quoxon/screenContext.js +1 -1
  131. package/lib/quoxon/secretShape.js +86 -0
  132. package/lib/quoxon/slash/agents.js +2 -1
  133. package/lib/quoxon/slash/argSources.js +9 -2
  134. package/lib/quoxon/slash/args.js +4 -2
  135. package/lib/quoxon/slash/broadcast.js +18 -5
  136. package/lib/quoxon/slash/broadcastRun.js +13 -2
  137. package/lib/quoxon/slash/cliLeaves.json +63 -1
  138. package/lib/quoxon/slash/hatWalk.js +1 -1
  139. package/lib/quoxon/slash/palette.js +22 -1
  140. package/lib/quoxon/slash/paletteController.js +10 -4
  141. package/lib/quoxon/slash/restart.js +7 -4
  142. package/lib/quoxon/slash/stepUp.js +5 -5
  143. package/lib/quoxon/slash/stepUpCmds.js +74 -1
  144. package/lib/quoxon/slashSpinner.js +35 -0
  145. package/lib/quoxon/slotAttach.js +8 -0
  146. package/lib/quoxon/slotChat.js +17 -0
  147. package/lib/quoxon/slotFormat.js +1 -1
  148. package/lib/quoxon/slotOutbox.js +113 -0
  149. package/lib/quoxon/suggestCommand.js +1 -1
  150. package/lib/quoxon/term.js +25 -1
  151. package/lib/quoxon/transcript.js +2 -1
  152. package/lib/quoxon/welcome.js +1 -1
  153. package/lib/quoxon/whatsnew.js +1 -1
  154. package/lib/quoxon/whatsnew.jsonl +36 -0
  155. package/lib/server/installFlow.js +168 -0
  156. package/lib/server/status.js +15 -1
  157. package/lib/toolOutputShape.js +55 -0
  158. package/package.json +3 -2
  159. package/vendor/dragonlab/SOURCE +3 -3
  160. package/vendor/dragonlab/design/quoxperticus/v2/quoxperticus-master.svg +59 -0
  161. package/vendor/dragonlab/design/quoxperticus/v3/character.mjs +43 -0
  162. package/vendor/dragonlab/design/quoxperticus/v4/character.mjs +57 -0
  163. package/vendor/dragonlab/design/quoxperticus/v4/review.mjs +35 -0
  164. package/vendor/dragonlab/design/quoxperticus/v4/watch.mjs +56 -0
  165. package/vendor/dragonlab/design/vulcan/v1/character.mjs +34 -0
  166. package/vendor/dragonlab/design/vulcan/v1/vulcan-master.svg +57 -0
  167. package/vendor/dragonlab/design/vulcan/v2/character.mjs +39 -0
  168. package/vendor/dragonlab/design/vulcan/v3/character.mjs +41 -0
  169. package/vendor/dragonlab/design/vulcan/v3/review.mjs +35 -0
  170. package/vendor/dragonlab/design/vulcan/v3/watch.mjs +56 -0
  171. package/vendor/dragonlab/factory/svg-rig-contour.mjs +64 -0
  172. package/vendor/dragonlab/integration/companions-v1/characters.mjs +20 -0
  173. package/vendor/dragonlab/integration/companions-v1/quoxperticus.mjs +2 -0
  174. package/vendor/dragonlab/integration/companions-v1/view.mjs +1 -1
  175. package/vendor/dragonlab/integration/companions-v1/vulcan.mjs +2 -0
@@ -11,6 +11,9 @@
11
11
  * turn : null | { id, status: running|interrupting, startedAt, stage, activity }
12
12
  */
13
13
 
14
+ import { eventLine } from '../core/announce.js';
15
+ import { isDestructive, isDestructiveTool, destructiveObject } from './destructive.js';
16
+ import { heldReason } from './bulkRisk.js';
14
17
  import { approvalSentence } from './approvalSentence.js';
15
18
  import { dividerStep } from './dividerAocl.js';
16
19
  import { approvalFacts } from './approvalFacts.js';
@@ -80,6 +83,15 @@ export function textGate(st, ev) {
80
83
 
81
84
  const tms = (v) => { const t = typeof v === 'number' ? v : Date.parse(v); return Number.isFinite(t) ? t : null; };
82
85
 
86
+ /** A machine source key (`devsuite_pack_install:<host>:<pack>:<ver>`) is never shown as a requester: it names a human-readable origin instead. */
87
+ const SOURCE_KEY = /^[A-Za-z][\w.-]*(:[^:\s]+){2,}$/;
88
+ const SOURCE_NAMES = [[/^devsuite_/i, 'DevSuite (skill-pack sync)']];
89
+ export function humanRequester(v) {
90
+ const t = String(v ?? '').trim(); if (!t || !SOURCE_KEY.test(t)) return t || null;
91
+ const hit = SOURCE_NAMES.find(([re]) => re.test(t)); if (hit) return hit[1];
92
+ const head = t.split(':')[0].replace(/[_-]+/g, ' ').trim();
93
+ return head ? head.charAt(0).toUpperCase() + head.slice(1) : 'plugin or workflow';
94
+ }
83
95
  const short = (v, n = 120) => { const t = typeof v === 'string' ? v : (v == null ? '' : JSON.stringify(v)); return t.length > n ? t.slice(0, n - 1) + '…' : t; };
84
96
  const argText = (a) => a && typeof a === 'object' && !Array.isArray(a) && (a.argv || a.command) ? argText(a.argv ?? a.command) : Array.isArray(a) ? a.join(' ') : (a && typeof a === 'object' ? Object.entries(a).filter(([, v]) => v != null && typeof v !== 'object').slice(0, 4).map(([k, v]) => `${k}=${v}`).join(' ') : (a == null ? '' : String(a)));
85
97
 
@@ -97,7 +109,7 @@ export function describeInboxItem(it) {
97
109
  const argv = m.argv ?? m.args ?? c.argv ?? c.args ?? p.argv ?? p.args ?? m.command ?? c.command ?? p.command ?? null;
98
110
  const cmd = short([m.leaf || c.leaf || p.leaf, argText(argv)].filter(Boolean).join(' ').trim() || null);
99
111
  const target = w.target || m.target || c.target || p.target || cmd || w.target_none || c.target_org || c.org_name || p.org_name || c.org_id || p.org_id || null;
100
- const by = ob.agent || ob.agent_id || ob.session_name || ob.session || c.requested_by?.agent_label || c.source_label || p.source || it?.source || null;
112
+ const by = humanRequester(ob.agent || ob.agent_id || ob.session_name || ob.session || c.requested_by?.agent_label || c.source_label || p.source || it?.source || null);
101
113
  const what = w.summary ? String(w.summary) : (it?.title ? short(String(it.title), 160) : null);
102
114
  return { tool, target: target ? short(target, 200) : null, title: what, by: by ? String(by) : null, at: it?.created_at ?? it?.createdAt ?? null };
103
115
  }
@@ -129,6 +141,9 @@ export function cardFromInboxItem(it, { since = 0, userId = null, conversationId
129
141
  tool, target: d.target, what: d.title, askedBy: d.by, askedAt: d.at, scope: null, risk: c.risk || null,
130
142
  url: it.dashboard_url || null, shownAt: now, undescribable: !d.tool && !d.target && !it._plan,
131
143
  plan: it._plan ? { lines: planLines(it._plan.steps), loaded: it._plan.loaded !== false, orgId: it._plan.orgId || null, orgName: it._plan.orgName || null } : null, // the plan's steps + the item's own org
144
+ destructive: isDestructive(it) ? destructiveObject(it) : null, // typed-name confirm before a y approves it
145
+ disruptive: [it, it.payload, c, m, it.payload?.what].some(o => o && o.disruptive === true), // server-marked: never batched
146
+ held: heldReason(it), // a risky ask is never part of a batch card
132
147
  facts: approvalFacts(it), // APPROVAL-WWWH: the exact command, hosts, chain, reason, time and read-only class
133
148
  // only an item that names THIS conversation is ours; anything else came from another session and must not take the keyboard
134
149
  foreign: !here,
@@ -140,7 +155,7 @@ export function cardFromInboxItem(it, { since = 0, userId = null, conversationId
140
155
  /** Where an inbox approval came from: its source label, else the payload source, else its type. */
141
156
  export function approvalSourceLabel(it) {
142
157
  const c = it?.payload?.approval_context || {};
143
- return String(c.source_label || it?.payload?.source || it?.source || (c.requested_by && c.requested_by.agent_label) || it?.payload?.type || 'plugin or workflow');
158
+ return humanRequester(c.source_label || it?.payload?.source || it?.source || (c.requested_by && c.requested_by.agent_label) || it?.payload?.type || 'plugin or workflow');
144
159
  }
145
160
 
146
161
  /**
@@ -377,7 +392,7 @@ function reduceInner(s, a) {
377
392
  case 'help':
378
393
  return { ...s, history: [...s.history, { id: nid(), kind: 'help', model: a.model }] };
379
394
  case 'object': // inline object view (objects/): pure data, rebuilt at render width
380
- return { ...s, history: [...s.history, { id: nid(), kind: 'object', obj: a.obj }] };
395
+ return { ...s, notify: scopeNotify(s.notify, a.obj), history: [...s.history, { id: nid(), kind: 'object', obj: a.obj }] };
381
396
  case 'error':
382
397
  return flush(addBlock(s, { kind: 'error', text: a.text }), false);
383
398
  case 'verbose':
@@ -435,11 +450,11 @@ function reduceInner(s, a) {
435
450
  const card = cardFromInboxItem(a.item, { since: s.turn?.startedAt ?? a.since ?? 0, userId: a.userId, conversationId: s.ctx.conversationId, now: a.now || 0 });
436
451
  if (card && card.approvalId) { // an event card raised without an inbox id learns it from the poll (else y answers a null id: 404)
437
452
  const j = s.approvals.findIndex(x => !x.inboxItemId && x.approvalId === card.approvalId);
438
- if (j >= 0) { const approvals = s.approvals.slice(); approvals[j] = { ...approvals[j], inboxItemId: card.inboxItemId, url: approvals[j].url || card.url, facts: approvals[j].facts || card.facts }; return { ...s, approvals, seenInbox: { ...s.seenInbox, [card.inboxItemId]: true } }; }
453
+ if (j >= 0) { const approvals = s.approvals.slice(); approvals[j] = { ...approvals[j], inboxItemId: card.inboxItemId, url: approvals[j].url || card.url, facts: approvals[j].facts || card.facts, destructive: approvals[j].destructive || card.destructive }; return { ...s, approvals, seenInbox: { ...s.seenInbox, [card.inboxItemId]: true } }; }
439
454
  }
440
455
  if (card && card.approvalId) { // an event stub that could not be described learns its description from the inbox item
441
456
  const j = s.approvals.findIndex(x => x.undescribable && (x.inboxItemId === card.inboxItemId || x.approvalId === card.approvalId));
442
- if (j >= 0 && !card.undescribable) { const approvals = s.approvals.slice(); approvals[j] = { ...approvals[j], tool: card.tool, target: card.target, what: card.what, askedBy: card.askedBy, askedAt: card.askedAt, facts: card.facts, undescribable: false }; return { ...s, approvals }; }
457
+ if (j >= 0 && !card.undescribable) { const approvals = s.approvals.slice(); approvals[j] = { ...approvals[j], tool: card.tool, target: card.target, what: card.what, askedBy: card.askedBy, askedAt: card.askedAt, facts: card.facts, destructive: approvals[j].destructive || card.destructive, undescribable: false }; return { ...s, approvals }; }
443
458
  }
444
459
  if (!card || s.seenInbox[card.inboxItemId] || s.approvals.some(x => x.inboxItemId === card.inboxItemId || (card.approvalId && x.approvalId === card.approvalId))) return s;
445
460
  if (card.foreign) { // another conversation's card: a compact notice, never the focused card
@@ -461,6 +476,10 @@ function reduceInner(s, a) {
461
476
  if (a.batch) return rest; // /approve N prints its own one-line summary
462
477
  if (a.quiet && a.action === 'approve') return rest;
463
478
  if (a.action !== 'approve') return note(rest, approvalSentence(a, false), 'warn');
479
+ if (a.foreign && a.approvalId) { // tracked: the poll adds one follow-up line with the outcome
480
+ const run = { callId: a.callId, approvalId: a.approvalId, tool: a.tool, target: a.target };
481
+ return note({ ...rest, running: [...rest.running.filter(x => x.callId !== a.callId), run] }, approvalSentence(a, true), 'ok');
482
+ }
464
483
  if (a.foreign) return note(rest, approvalSentence(a, true), 'ok');
465
484
  if (a.tool === 'hat.switch') { // applied server-side by the inbox approve; nothing to wait for
466
485
  const to = String(a.target || '').split('\u2192').pop().replace(/\(privileged\)/, '').trim() || 'the new hat';
@@ -475,7 +494,7 @@ function reduceInner(s, a) {
475
494
  const n = { ...s, running: s.running.filter(x => x.callId !== a.callId) };
476
495
  const what = run ? `${run.tool || 'approval'}${run.target ? ` on ${run.target}` : ''}` : 'approval';
477
496
  if (a.outcome === 'done') return note(n, `result: ${what}: ${a.text || 'completed'}`, 'ok');
478
- if (a.outcome === 'failed') return note(n, `failed: ${what}: ${a.text || 'error'}`, 'warn');
497
+ if (a.outcome === 'failed') return note(n, `${Number.isInteger(a.exit) && a.exit >= 1 ? `ran, exit ${a.exit}` : 'failed'}: ${what}: ${a.text || 'error'}`, 'warn');
479
498
  if (a.outcome === 'denied') return note(n, `denied: ${what}`, 'warn');
480
499
  // FW4: never silent and never a guess. Say what is unknown and where the authoritative answer is.
481
500
  const check = run && run.approvalId ? `quox approvals result ${run.approvalId} (or /approvals)` : '/approvals (no approval id was returned, so it cannot be tracked from here)';
@@ -495,6 +514,15 @@ function reduceInner(s, a) {
495
514
  }
496
515
 
497
516
  /** The first card is armed from the moment it is actually SHOWN, not from when it was received. */
517
+ /** An away/sitrep card that lists older items or a source it cannot see: the approvals bar must not say all-clear next to it. */
518
+ function scopeNotify(n, obj) {
519
+ const d = obj?.data;
520
+ if (!d || (obj.type !== 'away' && obj.type !== 'sitrep')) return n;
521
+ const outstanding = obj.type === 'away' ? (d.standing?.count || 0) : (d.needs || []).length;
522
+ const blind = Array.isArray(d.blind) ? d.blind.length : 0;
523
+ return { ...n, outstanding, blind };
524
+ }
525
+
498
526
  export function reduce(s, a) {
499
527
  let n = reduceInner(s, a);
500
528
  // TUI-DIVIDER: the bottom-rule AOCL strip, moved only by real turn events (dividerAocl.js).
@@ -593,7 +621,8 @@ function reduceEvent(s, ev, now) {
593
621
  target: ev.target, scope: ev.scope, risk: ev.risk, url: ev.dashboard_url, shownAt: now,
594
622
  undescribable: ev.describable === false || !ev.target,
595
623
  turnId: s.turn?.id ?? ev.turn_id ?? null, // the turn this card arrived in (a stream event is always this conversation)
596
- privileged: isPrivileged({ tool: ev.tool, risk: ev.risk, flag: ev.privileged === true || ev.elevated === true })
624
+ privileged: isPrivileged({ tool: ev.tool, risk: ev.risk, flag: ev.privileged === true || ev.elevated === true }),
625
+ destructive: ev.destructive === true || ev.irreversible === true || isDestructiveTool(ev.tool) ? (() => { const v = String(ev.object_name || ev.target || ev.inbox_item_id || ev.tool || 'item').slice(0, 80); return { label: v, name: v }; })() : null
597
626
  };
598
627
  if (s.approvals.some(x => x.callId === card.callId)) return s;
599
628
  const dup = s.approvals.findIndex(x => (card.inboxItemId && x.inboxItemId === card.inboxItemId) || (card.approvalId && x.approvalId === card.approvalId));
@@ -611,6 +640,13 @@ function reduceEvent(s, ev, now) {
611
640
  const n = { ...s, approvals: s.approvals.filter(x => x.callId !== ev.call_id) };
612
641
  return card || ev.decision ? note(n, `${ev.decision === 'approved' ? '✔ approved by you' : `approval ${ev.decision || 'resolved'}`}${card ? `: ${card.tool} on ${card.target}` : ''}`, ev.decision === 'approved' ? 'ok' : 'warn') : n;
613
642
  }
643
+ // QUOX-ANNOUNCE A2: a Quox effect of this turn (memory saved, approval held, delegation ...), one line, same renderer as the CLI verbs.
644
+ case 'quox_event': {
645
+ const text = eventLine(ev);
646
+ if (!text) return s;
647
+ if (ev.status === 'fail') return addBlock(s, { kind: 'error', text });
648
+ return addBlock(s, { kind: 'note', text, tone: ev.status === 'ok' ? 'ok' : ev.status === 'busy' ? 'accent' : 'warn' });
649
+ }
614
650
  case 'steer.accepted': {
615
651
  const i = s.steering.findIndex(x => x.text === ev.text);
616
652
  const steering = i >= 0 ? s.steering.filter((_, k) => k !== i) : s.steering.slice(1);