quoxcli 0.5.0 → 0.6.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.
Files changed (130) hide show
  1. package/CHANGELOG.md +65 -1
  2. package/lib/api/endpoints/admin.js +21 -0
  3. package/lib/api/endpoints/calendarq.js +8 -0
  4. package/lib/api/endpoints/governance.js +7 -0
  5. package/lib/api/endpoints/inboxq.js +39 -0
  6. package/lib/api/endpoints/index.js +4 -0
  7. package/lib/api/endpoints/lens.js +34 -0
  8. package/lib/api/endpoints/lucid.js +18 -0
  9. package/lib/api/endpoints/mcp.js +35 -0
  10. package/lib/api/endpoints/mon.js +6 -7
  11. package/lib/api/endpoints/notesq.js +2 -0
  12. package/lib/api/endpoints/quoxplan.js +51 -0
  13. package/lib/api/endpoints/v1.js +57 -0
  14. package/lib/commands/actions.js +51 -13
  15. package/lib/commands/admin.js +86 -0
  16. package/lib/commands/approvals.js +5 -1
  17. package/lib/commands/calendar.js +27 -0
  18. package/lib/commands/chat.js +1 -1
  19. package/lib/commands/contacts.js +2 -0
  20. package/lib/commands/fleet.js +164 -0
  21. package/lib/commands/fleetRecipe.js +174 -0
  22. package/lib/commands/lens.js +268 -0
  23. package/lib/commands/lucid.js +6 -0
  24. package/lib/commands/lucidGoals.js +423 -0
  25. package/lib/commands/mail.js +287 -7
  26. package/lib/commands/mcp.js +190 -0
  27. package/lib/commands/mon.js +14 -0
  28. package/lib/commands/monitoring.js +73 -1
  29. package/lib/commands/notesSeal.js +380 -22
  30. package/lib/commands/offload.js +51 -10
  31. package/lib/commands/plan.js +507 -54
  32. package/lib/commands/research.js +78 -0
  33. package/lib/commands/settings.js +7 -1
  34. package/lib/commands/volt.js +6 -1
  35. package/lib/commands/wazuh.js +74 -9
  36. package/lib/commands/wazuhIntake.js +55 -6
  37. package/lib/core/commandRegistrars.js +2 -0
  38. package/lib/core/config.js +14 -1
  39. package/lib/core/offload.js +83 -0
  40. package/lib/core/output.js +29 -4
  41. package/lib/core/selfUpdate.js +2 -0
  42. package/lib/core/version.js +15 -2
  43. package/lib/dragon/select.js +2 -2
  44. package/lib/quoxon/api.js +4 -0
  45. package/lib/quoxon/app.js +258 -49
  46. package/lib/quoxon/approvalsView.js +3 -1
  47. package/lib/quoxon/ask.js +4 -2
  48. package/lib/quoxon/bang.js +7 -1
  49. package/lib/quoxon/commandShort.js +1 -1
  50. package/lib/quoxon/commands.js +5 -5
  51. package/lib/quoxon/delegationDragon.js +49 -0
  52. package/lib/quoxon/dnd.js +47 -0
  53. package/lib/quoxon/dragonCompanion.js +24 -12
  54. package/lib/quoxon/events.js +12 -1
  55. package/lib/quoxon/extensions.js +2 -0
  56. package/lib/quoxon/foreignText.js +49 -0
  57. package/lib/quoxon/inboxView.js +5 -5
  58. package/lib/quoxon/launcher.js +2 -1
  59. package/lib/quoxon/loopCard.js +2 -1
  60. package/lib/quoxon/main.js +8 -2
  61. package/lib/quoxon/onboarding/dockerOffer.js +3 -1
  62. package/lib/quoxon/onboarding/view.js +1 -1
  63. package/lib/quoxon/render.js +5 -2
  64. package/lib/quoxon/resend.js +14 -0
  65. package/lib/quoxon/rows.js +3 -1
  66. package/lib/quoxon/secretShape.js +2 -0
  67. package/lib/quoxon/slash/broadcast.js +30 -6
  68. package/lib/quoxon/slash/broadcastRun.js +12 -6
  69. package/lib/quoxon/slash/cliLeaves.json +54 -0
  70. package/lib/quoxon/slash/dashboard.js +2 -0
  71. package/lib/quoxon/slash/listView.js +2 -1
  72. package/lib/quoxon/slash/palette.js +41 -0
  73. package/lib/quoxon/slash/paletteController.js +6 -2
  74. package/lib/quoxon/slash/restart.js +2 -0
  75. package/lib/quoxon/slotChat.js +7 -3
  76. package/lib/quoxon/slotRun.js +2 -0
  77. package/lib/quoxon/webPty.js +15 -0
  78. package/lib/quoxon/whatsnew.jsonl +7 -0
  79. package/lib/render/planBrief.js +614 -0
  80. package/lib/toolOutputShape.js +6 -3
  81. package/package.json +1 -1
  82. package/vendor/dragonlab/SOURCE +3 -3
  83. package/vendor/dragonlab/design/architect/v2/architect-master.svg +67 -0
  84. package/vendor/dragonlab/design/architect/v2/character.mjs +83 -0
  85. package/vendor/dragonlab/design/architect/v2/review.mjs +35 -0
  86. package/vendor/dragonlab/design/architect/v2/watch.mjs +56 -0
  87. package/vendor/dragonlab/design/archivist/v2/archivist-master.svg +77 -0
  88. package/vendor/dragonlab/design/archivist/v3/character.mjs +56 -0
  89. package/vendor/dragonlab/design/archivist/v4/character.mjs +46 -0
  90. package/vendor/dragonlab/design/archivist/v4/review.mjs +35 -0
  91. package/vendor/dragonlab/design/archivist/v4/watch.mjs +56 -0
  92. package/vendor/dragonlab/design/argus/v2/character.mjs +69 -0
  93. package/vendor/dragonlab/design/argus/v3/argus-master.svg +68 -0
  94. package/vendor/dragonlab/design/argus/v3/character.mjs +63 -0
  95. package/vendor/dragonlab/design/argus/v3/review.mjs +35 -0
  96. package/vendor/dragonlab/design/argus/v3/watch.mjs +56 -0
  97. package/vendor/dragonlab/design/daedalus/v2/character.mjs +49 -0
  98. package/vendor/dragonlab/design/daedalus/v3/daedalus-master.svg +65 -0
  99. package/vendor/dragonlab/design/daedalus/v4/character.mjs +47 -0
  100. package/vendor/dragonlab/design/daedalus/v5/character.mjs +66 -0
  101. package/vendor/dragonlab/design/daedalus/v6/character.mjs +32 -0
  102. package/vendor/dragonlab/design/daedalus/v6/review.mjs +35 -0
  103. package/vendor/dragonlab/design/daedalus/v6/watch.mjs +56 -0
  104. package/vendor/dragonlab/design/janus/v1/janus-master.svg +70 -0
  105. package/vendor/dragonlab/design/janus/v2/character.mjs +56 -0
  106. package/vendor/dragonlab/design/janus/v3/character.mjs +7 -0
  107. package/vendor/dragonlab/design/janus/v4/character.mjs +150 -0
  108. package/vendor/dragonlab/design/janus/v5/character.mjs +55 -0
  109. package/vendor/dragonlab/design/janus/v5/review.mjs +35 -0
  110. package/vendor/dragonlab/design/janus/v5/watch.mjs +56 -0
  111. package/vendor/dragonlab/design/roady/v1/roady-master.svg +67 -0
  112. package/vendor/dragonlab/design/roady/v2/character.mjs +69 -0
  113. package/vendor/dragonlab/design/roady/v3/character.mjs +91 -0
  114. package/vendor/dragonlab/design/roady/v3/review.mjs +35 -0
  115. package/vendor/dragonlab/design/roady/v3/watch.mjs +56 -0
  116. package/vendor/dragonlab/design/weaver/v3/weaver-master.svg +57 -0
  117. package/vendor/dragonlab/design/weaver/v4/character.mjs +56 -0
  118. package/vendor/dragonlab/design/weaver/v5/character.mjs +52 -0
  119. package/vendor/dragonlab/design/weaver/v6/character.mjs +88 -0
  120. package/vendor/dragonlab/design/weaver/v6/review.mjs +35 -0
  121. package/vendor/dragonlab/design/weaver/v6/watch.mjs +56 -0
  122. package/vendor/dragonlab/integration/companions-v1/architect.mjs +2 -0
  123. package/vendor/dragonlab/integration/companions-v1/archivist.mjs +2 -0
  124. package/vendor/dragonlab/integration/companions-v1/argus.mjs +2 -0
  125. package/vendor/dragonlab/integration/companions-v1/characters.mjs +86 -0
  126. package/vendor/dragonlab/integration/companions-v1/daedalus.mjs +2 -0
  127. package/vendor/dragonlab/integration/companions-v1/janus.mjs +2 -0
  128. package/vendor/dragonlab/integration/companions-v1/roady.mjs +2 -0
  129. package/vendor/dragonlab/integration/companions-v1/view.mjs +4 -1
  130. package/vendor/dragonlab/integration/companions-v1/weaver.mjs +2 -0
package/CHANGELOG.md CHANGED
@@ -4,7 +4,64 @@ User-visible changes to the `quoxcli` package. Items marked *(unproven)* ship in
4
4
  the package and pass their tests, but nobody has run them end to end from the
5
5
  published package yet.
6
6
 
7
- ## 0.5.0 (unreleased)
7
+ ## 0.6.1 (2026-11-10)
8
+
9
+ - `quox --version` and `quox doctor` no longer report another project's git commit. Installed
10
+ under nvm, 0.6.0 printed `0.6.0 (bab86d5, detached)`: `bab86d5` was nvm's own commit,
11
+ because `~/.nvm` is a git clone. Git details now show only when the CLI runs from its own
12
+ source checkout; a package install prints the bare version. *(driven live: the 0.6.0
13
+ npm install patched with this fix printed `0.6.0`)*
14
+ - New: `quox lens panel` runs several QuoxLens critiques over one target, repeated, and keeps the
15
+ findings that recur across runs. *(unproven from the package; landed by another session the
16
+ same day)*
17
+ - `quox lens apply` now waits past the 5 minute model ceiling instead of the 30 second client
18
+ default. *(unproven from the package)*
19
+
20
+ ## 0.6.0 (2026-11-10)
21
+
22
+ Driven from the published 0.6.0 package on 2026-11-10 (npm install -g, Node under nvm):
23
+ `notes ls`, `doctor` (9 of 9), and the full seal round trip (`notes seal init`, `status`,
24
+ `new`, `open` by id, `print` then `open --armor-file`; the server copy held no plaintext).
25
+ Everything else below has not been run from the package yet. Items marked *(driven live)*
26
+ were driven against a live instance from the source tree before release.
27
+
28
+ ### New commands
29
+ - `quox notes seal convert <id>` and `quox notes seal purge <id>` (human sessions only):
30
+ seal an existing plaintext note in place, then wipe its old plaintext revisions. Convert
31
+ names any attachments and says they are NOT protected. *(driven live)*
32
+ - Sealed notes harden: each sealed note is bound to its note id (another note's envelope
33
+ is refused), the version comes from the authenticated envelope, and a local rollback
34
+ floor fails closed under a lock.
35
+ - `quox notes seal` finds ENQRYPT without setup: `QUOX_ENQRYPT_PATH`, then
36
+ `~/.quox/enqrypt-src` (a clone of quoxai/enqrypt main plus `npm ci`); a copy missing a
37
+ function the CLI calls is refused by name. ENQRYPT is not on npm, and the npm package
38
+ named `enqrypt` is unrelated malware. *(driven live)*
39
+ - `quox mail block | unblock | blocked | junk | trust`.
40
+ - `quox mcp token create | list | revoke`.
41
+ - `quox lucid goals`, `quox lucid insights`, `quox lucid brief`.
42
+ - `quox plan brief`, `quox plan watch`, `quox plan followups` / `followup add | done | status`,
43
+ `quox plan roadmap archive | unarchive`, `quox plan roadmaps --archived`.
44
+ - `quox monitoring incidents claim <id>` and `quox monitoring incidents status <id> <status>`: claim and move a
45
+ governance incident (witnessed, org scoped). `quox wazuh connect --indexer-ca` and
46
+ `--password-stdin` / `--indexer-password-stdin`.
47
+
48
+ ### Changes
49
+ - `quox plan add` honours `roadmap_id` in the JSON (streams used to land in the first
50
+ roadmap). `quox plan focus` counts substreams and caps its output; unblockers name the
51
+ whole chain.
52
+ - `quox wazuh vulns` totals are exact or labelled a lower bound (10,000+); `quox wazuh status`
53
+ says its agent count includes the manager; no MAINT? column when the org has no Uptime Kuma.
54
+ - `quox offload` lists every worker lease and warns 12h before one expires; `offload release
55
+ --worker` targets that worker, never the shared default.
56
+ - `quox tool exec --select .` returns the whole result. `quox actions list` fits the terminal.
57
+ - Conversation app (`quox` with no arguments): `/wake`, `/poke` and `/page` take several
58
+ slots; `/inbox show N` reads a whole item; `/morningreport` digests what happened since you
59
+ were last here; a typed `/org switch` or `/org back` no longer asks a second y/n; an inline
60
+ approval says approved only after the server records it and no longer re-opens as a popup;
61
+ decision cards never echo the asker's pick as their own advice; contact-consent approvals ask
62
+ for the lawful basis; slot messages from other sessions never show raw colour codes.
63
+
64
+ ## 0.5.0 (2026-11-06)
8
65
 
9
66
  ### New commands
10
67
  - `quox wazuh`: first-class Wazuh commands. `agents`, `fim`, `sca`, `rootcheck`
@@ -32,6 +89,13 @@ published package yet.
32
89
  - `quox notes seal init | status | new | open | edit | print`: ENQRYPT sealed
33
90
  notes. Encryption happens on your machine and the server stores only the
34
91
  envelope. *(unproven from the package)*
92
+ - `quox notes seal convert <id> --yes [--keep-title]` seals an existing note and
93
+ purges its earlier plaintext from the server (revisions, embeddings, derived
94
+ beliefs; human sessions only); `quox notes seal purge <id> --yes` reruns the
95
+ purge. Both say that backups taken earlier still hold the old text until they
96
+ expire. `seal edit` now takes the version from the sealed envelope, not from a
97
+ server field, and the local rollback floor refuses a damaged or missing floor
98
+ file instead of starting again from zero. *(unproven from the package)*
35
99
  - `quox admin users verify-email <email> --reason` (step-up PIN).
36
100
  - `quox memory sync`: Claude file memory is saved to Quox memory too
37
101
  (idempotent; credential-shaped text is refused).
@@ -175,4 +175,25 @@ export const admin = {
175
175
  path: () => `/api/admin/stats`,
176
176
  tags: ['live-safe'],
177
177
  },
178
+ // AQ-9H LADON-P1B: the scratch-org registry (fixture mail/calendar providers load only for a marked org, and
179
+ // only on a QUOX_FIXTURE_PROVIDERS=1 deployment). Collector routes, human instance admin only, enforced
180
+ // server-side by requireInstanceAdminMw (services/collector/server.js).
181
+ 'admin.scratch_orgs.list': {
182
+ method: 'GET',
183
+ service: 'collector',
184
+ path: () => `/api/v1/admin/scratch-orgs`,
185
+ tags: ['live-safe'],
186
+ },
187
+ 'admin.scratch_orgs.mark': {
188
+ method: 'PUT',
189
+ service: 'collector',
190
+ path: ({ orgId }) => `/api/v1/admin/scratch-orgs/${encodeURIComponent(orgId)}`,
191
+ tags: ['admin-write'],
192
+ },
193
+ 'admin.scratch_orgs.unmark': {
194
+ method: 'DELETE',
195
+ service: 'collector',
196
+ path: ({ orgId }) => `/api/v1/admin/scratch-orgs/${encodeURIComponent(orgId)}`,
197
+ tags: ['admin-write'],
198
+ },
178
199
  };
@@ -38,6 +38,14 @@ export const calendarq = {
38
38
  path: () => `/api/v1/calendarq/changes`,
39
39
  tags: ['live-safe'],
40
40
  },
41
+ // AQ-9H LADON-P1B: create a seeded FIXTURE calendar connection (scratch orgs on a fixture-enabled
42
+ // deployment only; the collector's gate decides). Google connects through the OAuth flow, not here.
43
+ 'calendarq.connections.create': {
44
+ method: 'POST',
45
+ service: 'collector',
46
+ path: () => `/api/v1/calendarq/connections`,
47
+ tags: ['live-write'],
48
+ },
41
49
  'calendarq.sync': {
42
50
  method: 'POST',
43
51
  service: 'collector',
@@ -22,6 +22,13 @@ export const governance = {
22
22
  path: () => `/governance/incidents`,
23
23
  tags: ['live-safe'],
24
24
  },
25
+ // WAZ P11 slice 1b: the witnessed lifecycle move (claim = self, status with a resolution for terminal states).
26
+ // Collector: lib/governanceIncidentLifecycle.js. Writes, so not live-safe.
27
+ 'governance.incidents.transition': {
28
+ method: 'POST',
29
+ service: 'collector',
30
+ path: ({ id }) => `/governance/incidents/${encodeURIComponent(id)}/transition`,
31
+ },
25
32
  'governance.policy.compile': {
26
33
  method: 'POST',
27
34
  service: 'collector',
@@ -136,4 +136,43 @@ export const inboxq = {
136
136
  path: ({ proposalId }) => `/api/v1/inboxq/proposals/${proposalId}/resolve`,
137
137
  tags: ['live-write'],
138
138
  },
139
+ // EMAIL-Q CLI audit (2026-11-09): the sender/junk routes the dashboard already
140
+ // calls (src/components/inboxq/lib/api.js), wrapped by `quox mail block|unblock|
141
+ // blocked|junk|trust`. No new server logic: these are the existing InboxQ routes.
142
+ 'inboxq.messages.search': {
143
+ method: 'GET',
144
+ service: 'collector',
145
+ path: () => `/api/v1/inboxq/messages/search`,
146
+ tags: ['live-safe'],
147
+ },
148
+ 'inboxq.messages.confirmJunk': {
149
+ method: 'POST',
150
+ service: 'collector',
151
+ path: () => `/api/v1/inboxq/messages/confirm-junk`,
152
+ tags: ['live-write'],
153
+ },
154
+ 'inboxq.senders.block': {
155
+ method: 'POST',
156
+ service: 'collector',
157
+ path: () => `/api/v1/inboxq/senders/block`,
158
+ tags: ['live-write'],
159
+ },
160
+ 'inboxq.senders.unblock': {
161
+ method: 'POST',
162
+ service: 'collector',
163
+ path: () => `/api/v1/inboxq/senders/unblock`,
164
+ tags: ['live-write'],
165
+ },
166
+ 'inboxq.senders.blocked': {
167
+ method: 'GET',
168
+ service: 'collector',
169
+ path: () => `/api/v1/inboxq/senders/blocked`,
170
+ tags: ['live-safe'],
171
+ },
172
+ 'inboxq.senders.trust': {
173
+ method: 'POST',
174
+ service: 'collector',
175
+ path: () => `/api/v1/inboxq/senders/trust`,
176
+ tags: ['live-write'],
177
+ },
139
178
  };
@@ -62,6 +62,7 @@ import { awareness } from './awareness.js';
62
62
  import { router } from './router.js';
63
63
  import { mind } from './mind.js';
64
64
  import { lucid } from './lucid.js';
65
+ import { lens } from './lens.js';
65
66
  import { loops } from './loops.js';
66
67
  import { away } from './away.js';
67
68
  import { presence } from './presence.js';
@@ -96,6 +97,7 @@ import { contactsq } from './contactsq.js';
96
97
  import { x } from './x.js';
97
98
  import { llmPolicy } from './llm-policy.js';
98
99
  import { hat } from './hat.js';
100
+ import { mcp } from './mcp.js';
99
101
  import { members } from './members.js';
100
102
  import { memory } from './memory.js';
101
103
  import { mfa } from './mfa.js';
@@ -155,6 +157,7 @@ export const ENDPOINTS = {
155
157
  ...router,
156
158
  ...mind,
157
159
  ...lucid,
160
+ ...lens,
158
161
  ...loops,
159
162
  ...away,
160
163
  ...presence,
@@ -189,6 +192,7 @@ export const ENDPOINTS = {
189
192
  ...x,
190
193
  ...llmPolicy,
191
194
  ...hat,
195
+ ...mcp,
192
196
  ...members,
193
197
  ...memory,
194
198
  ...mfa,
@@ -0,0 +1,34 @@
1
+ /**
2
+ * 'lens.*' endpoints: QuoxLens (`quox lens`).
3
+ * Collector routes under /api/v1/lens (services/collector/routes/quoxlens.js), mounted behind
4
+ * requireServiceOrUserAuth + requirePluginEntitlement('quoxlens') (402 LICENSE_REQUIRED).
5
+ *
6
+ * See cli/lib/api/endpoints/index.js for the fragment convention.
7
+ */
8
+
9
+ export const lens = {
10
+ 'lens.list': {
11
+ method: 'GET',
12
+ service: 'collector',
13
+ path: () => `/api/v1/lens/lenses`,
14
+ tags: ['live-safe'],
15
+ },
16
+ 'lens.get': {
17
+ method: 'GET',
18
+ service: 'collector',
19
+ path: ({ id }) => `/api/v1/lens/lenses/${encodeURIComponent(id)}`,
20
+ tags: ['live-safe'],
21
+ },
22
+ 'lens.apply': {
23
+ method: 'POST',
24
+ service: 'collector',
25
+ path: () => `/api/v1/lens/apply`,
26
+ tags: ['live-write'],
27
+ },
28
+ 'lens.panel': {
29
+ method: 'POST',
30
+ service: 'collector',
31
+ path: () => `/api/v1/lens/panel`,
32
+ tags: ['live-write'],
33
+ },
34
+ };
@@ -86,4 +86,22 @@ export const lucid = {
86
86
  path: () => `/api/v1/lucid/herald/test`,
87
87
  tags: ['live-safe'],
88
88
  },
89
+ // LUCID-INTELLIGENCE World Model (LI-1): goals, initiatives, decisions, links. People confirm; agents propose.
90
+ 'lucid.world.list': { method: 'GET', service: 'collector', path: () => `/api/v1/lucid/intelligence/world`, tags: ['live-safe'] },
91
+ 'lucid.world.create': { method: 'POST', service: 'collector', path: () => `/api/v1/lucid/intelligence/world`, tags: ['live-write'] },
92
+ 'lucid.world.export': { method: 'GET', service: 'collector', path: () => `/api/v1/lucid/intelligence/world/export`, tags: ['live-safe'] },
93
+ 'lucid.world.erase': { method: 'DELETE', service: 'collector', path: () => `/api/v1/lucid/intelligence/world/mine`, tags: ['live-write'] },
94
+ 'lucid.world.get': { method: 'GET', service: 'collector', path: ({ id }) => `/api/v1/lucid/intelligence/world/${encodeURIComponent(id)}`, tags: ['live-safe'] },
95
+ 'lucid.world.amend': { method: 'PATCH', service: 'collector', path: ({ id }) => `/api/v1/lucid/intelligence/world/${encodeURIComponent(id)}`, tags: ['live-write'] },
96
+ 'lucid.world.confirm': { method: 'POST', service: 'collector', path: ({ id }) => `/api/v1/lucid/intelligence/world/${encodeURIComponent(id)}/confirm`, tags: ['live-write'] },
97
+ 'lucid.world.archive': { method: 'POST', service: 'collector', path: ({ id }) => `/api/v1/lucid/intelligence/world/${encodeURIComponent(id)}/archive`, tags: ['live-write'] },
98
+ 'lucid.world.restore': { method: 'POST', service: 'collector', path: ({ id }) => `/api/v1/lucid/intelligence/world/${encodeURIComponent(id)}/restore`, tags: ['live-write'] },
99
+ 'lucid.world.history': { method: 'GET', service: 'collector', path: ({ id }) => `/api/v1/lucid/intelligence/world/${encodeURIComponent(id)}/history`, tags: ['live-safe'] },
100
+ // LUCID-INTELLIGENCE LI-2 shadow insights (read-only, except a person deciding a proposed link).
101
+ 'lucid.insights.list': { method: 'GET', service: 'collector', path: () => `/api/v1/lucid/intelligence/insights`, tags: ['live-safe'] },
102
+ 'lucid.insights.status': { method: 'GET', service: 'collector', path: () => `/api/v1/lucid/intelligence/insights/status`, tags: ['live-safe'] },
103
+ 'lucid.insights.decide': { method: 'POST', service: 'collector', path: ({ id, goalId }) => `/api/v1/lucid/intelligence/insights/${encodeURIComponent(id)}/links/${encodeURIComponent(goalId)}`, tags: ['live-write'] },
104
+ 'lucid.insights.get': { method: 'GET', service: 'collector', path: ({ id }) => `/api/v1/lucid/intelligence/insights/${encodeURIComponent(id)}`, tags: ['live-safe'] },
105
+ // LUCID-INTELLIGENCE LI-3 brief (read-only, never moves a cursor).
106
+ 'lucid.brief': { method: 'GET', service: 'collector', path: () => `/api/v1/lucid/intelligence/brief`, tags: ['live-safe'] },
89
107
  };
@@ -0,0 +1,35 @@
1
+ /**
2
+ * 'mcp.*' endpoints: per-user QuoxMCP tokens and the personalised Claude Desktop bundle.
3
+ *
4
+ * - mcp.tokens.* auth service, services/auth/routes/mcp-tokens.js (mounted /api/auth/tokens/mcp, requireAuth,
5
+ * scoped to the caller: list/revoke only ever see the caller's own tokens; create needs an org context).
6
+ * - mcp.bundle collector, services/collector/routes/mcp-bundle.js (JWT only). POST { token, collector_url, agent_id }
7
+ * returns a .mcpb zip whose manifest EMBEDS the token as QUOX_SERVICE_KEY. Binary, read with raw: true.
8
+ */
9
+
10
+ export const mcp = {
11
+ 'mcp.tokens.create': {
12
+ method: 'POST',
13
+ service: 'auth',
14
+ path: () => '/api/auth/tokens/mcp',
15
+ tags: ['live-write'],
16
+ },
17
+ 'mcp.tokens.list': {
18
+ method: 'GET',
19
+ service: 'auth',
20
+ path: () => '/api/auth/tokens/mcp',
21
+ tags: ['live-safe'],
22
+ },
23
+ 'mcp.tokens.revoke': {
24
+ method: 'DELETE',
25
+ service: 'auth',
26
+ path: ({ id }) => `/api/auth/tokens/mcp/${encodeURIComponent(id)}`,
27
+ tags: ['live-write'],
28
+ },
29
+ 'mcp.bundle': {
30
+ method: 'POST',
31
+ service: 'collector',
32
+ path: () => '/api/v1/mcp/bundle',
33
+ tags: ['live-write'],
34
+ },
35
+ };
@@ -6,13 +6,12 @@
6
6
  * collector-direct (no org segment in the URL — same shape as
7
7
  * `traces.*`/`aee.*`):
8
8
  *
9
- * - Docker fleet (`/docker/*`) — VERIFIED at the handlers (server.js
10
- * ~7232-7401): these routes have NO auth middleware applied at all, and
11
- * no org filtering — a bare fleet-wide scan/cache read across every
12
- * Docker host the collector can reach. The CLI still sends a bearer
13
- * (harmless) and still gates `quox mon docker *` behind
14
- * `auth.requireAuth()` for a consistent logged-in CLI UX, but the
15
- * backend itself would serve these to an unauthenticated caller.
9
+ * - Docker fleet (`/docker/*`) — the instance operator's fleet. Every
10
+ * route needs a login (requireServiceOrUserAuth). The reads run
11
+ * instanceFleetRouteGate: the operator org or an instance admin sees the
12
+ * fleet, any other org gets an honest empty list (or 404 for one
13
+ * container). The start/stop/restart action goes through executeTool and
14
+ * is refused for any other org (VULCAN FND_01M71D6KXP402M7CE1NGH0GVVZ).
16
15
  * - Wazuh (`/api/v1/wazuh/*`) — gated by `requireServiceOrUserAuth()`.
17
16
  * Reads (manager/status, agents, alerts, vulnerabilities,
18
17
  * deploy-history) are not org-filtered by the Wazuh API itself (the org
@@ -19,6 +19,8 @@ export const notesq = {
19
19
  'notesq.notes.revisions': { method: 'GET', service: 'collector', path: ({ id }) => `${B}/notes/${id}/revisions`, tags: ['live-safe'] },
20
20
  'notesq.notes.revision': { method: 'GET', service: 'collector', path: ({ id, rev }) => `${B}/notes/${id}/revisions/${rev}`, tags: ['live-safe'] },
21
21
  'notesq.notes.restore': { method: 'POST', service: 'collector', path: ({ id }) => `${B}/notes/${id}/restore`, tags: ['live-write'] },
22
+ // ENQRYPT-NOTES: human-only purge of a sealed note's earlier plaintext (revisions, embeddings, derived beliefs).
23
+ 'notesq.notes.sealPurge': { method: 'POST', service: 'collector', path: ({ id }) => `${B}/notes/${id}/seal-purge`, tags: ['live-write'] },
22
24
  'notesq.import': { method: 'POST', service: 'collector', path: () => `${B}/import`, tags: ['live-write'] },
23
25
  // AQ-9D P4: Action-Q item create (auth, org-scoped), used by `quox notes actions`.
24
26
  'notesq.actionq.create': { method: 'POST', service: 'auth', path: ({ orgId }) => `/api/orgs/${orgId}/actions`, tags: ['live-write'] },
@@ -40,6 +40,14 @@ export const quoxplan = {
40
40
  path: ({ roadmapId }) => `/api/v1/quoxplan/roadmaps/${roadmapId}/streams`,
41
41
  tags: ['live-safe'],
42
42
  },
43
+ // One-screen roadmap brief (QUOXPLAN-TERMINAL): top priorities, needs-you,
44
+ // unblockers, recent movers, stale count. Query: top (1..25), days (1..60).
45
+ 'quoxplan.roadmap.brief.get': {
46
+ method: 'GET',
47
+ service: 'collector',
48
+ path: ({ roadmapId }) => `/api/v1/quoxplan/roadmaps/${roadmapId}/brief`,
49
+ tags: ['live-safe'],
50
+ },
43
51
  'quoxplan.roadmap.timeline.get': {
44
52
  method: 'GET',
45
53
  service: 'collector',
@@ -58,6 +66,21 @@ export const quoxplan = {
58
66
  path: ({ roadmapId }) => `/api/v1/quoxplan/roadmaps/${roadmapId}/streams`,
59
67
  tags: ['live-write'],
60
68
  },
69
+ // Archive / unarchive a roadmap (QUOXPLAN-TERMINAL, gripe quoxai/quox#1586):
70
+ // hides it from GET /roadmaps and /search and refuses writes to its streams;
71
+ // reads by id keep working. Both idempotent server-side.
72
+ 'quoxplan.roadmap.archive': {
73
+ method: 'POST',
74
+ service: 'collector',
75
+ path: ({ roadmapId }) => `/api/v1/quoxplan/roadmaps/${roadmapId}/archive`,
76
+ tags: ['live-write'],
77
+ },
78
+ 'quoxplan.roadmap.unarchive': {
79
+ method: 'POST',
80
+ service: 'collector',
81
+ path: ({ roadmapId }) => `/api/v1/quoxplan/roadmaps/${roadmapId}/unarchive`,
82
+ tags: ['live-write'],
83
+ },
61
84
  'quoxplan.roadmap.export.get': {
62
85
  method: 'GET',
63
86
  service: 'collector',
@@ -118,4 +141,32 @@ export const quoxplan = {
118
141
  path: ({ id }) => `/api/v1/quoxplan/streams/${id}/plan/versions`,
119
142
  tags: ['live-safe'],
120
143
  },
144
+ // Action items (QUOXPLAN-TERMINAL: the dashboard's action-item panel had no
145
+ // CLI counterpart). Server: quoxplan/server/src/api/actionItems.js. Auto
146
+ // items (kind blocked/decision) are raised by stream status transitions;
147
+ // manual items are added here. Statuses: OPEN | DONE | CANCELLED.
148
+ 'quoxplan.roadmap.actionItems.list': {
149
+ method: 'GET',
150
+ service: 'collector',
151
+ path: ({ roadmapId }) => `/api/v1/quoxplan/roadmaps/${roadmapId}/action-items`,
152
+ tags: ['live-safe'],
153
+ },
154
+ 'quoxplan.stream.actionItems.list': {
155
+ method: 'GET',
156
+ service: 'collector',
157
+ path: ({ id }) => `/api/v1/quoxplan/streams/${id}/action-items`,
158
+ tags: ['live-safe'],
159
+ },
160
+ 'quoxplan.stream.actionItems.create': {
161
+ method: 'POST',
162
+ service: 'collector',
163
+ path: ({ id }) => `/api/v1/quoxplan/streams/${id}/action-items`,
164
+ tags: ['live-write'],
165
+ },
166
+ 'quoxplan.actionItem.update': {
167
+ method: 'PATCH',
168
+ service: 'collector',
169
+ path: ({ actionId }) => `/api/v1/quoxplan/action-items/${actionId}`,
170
+ tags: ['live-write'],
171
+ },
121
172
  };
@@ -67,6 +67,38 @@ export const v1 = {
67
67
  path: () => `/api/v1/fleet/ping`,
68
68
  tags: ['admin-write'],
69
69
  },
70
+ // BASTION-JANUS P3 — `quox fleet recipe list|show|add|remove|run`: the org's governed fleet
71
+ // recipe library. run hands the rendered recipe to the collector's ssh_exec gate (one card).
72
+ 'v1.fleet.recipes.list': {
73
+ method: 'GET',
74
+ service: 'collector',
75
+ path: () => `/api/v1/fleet/recipes`,
76
+ tags: ['live-safe'],
77
+ },
78
+ 'v1.fleet.recipes.get': {
79
+ method: 'GET',
80
+ service: 'collector',
81
+ path: ({ name }) => `/api/v1/fleet/recipes/${encodeURIComponent(name)}`,
82
+ tags: ['live-safe'],
83
+ },
84
+ 'v1.fleet.recipes.create': {
85
+ method: 'POST',
86
+ service: 'collector',
87
+ path: () => `/api/v1/fleet/recipes`,
88
+ tags: ['admin-write'],
89
+ },
90
+ 'v1.fleet.recipes.delete': {
91
+ method: 'DELETE',
92
+ service: 'collector',
93
+ path: ({ name }) => `/api/v1/fleet/recipes/${encodeURIComponent(name)}`,
94
+ tags: ['admin-write'],
95
+ },
96
+ 'v1.fleet.recipes.run': {
97
+ method: 'POST',
98
+ service: 'collector',
99
+ path: () => `/api/v1/fleet/recipes/run`,
100
+ tags: ['live-write'],
101
+ },
70
102
  // DEVSUITE-HUB Phase 2 — `quox dev enable` (see lib/commands/dev.js).
71
103
  // Constructs + signs + delivers a quoxagent dev.CapabilityProfile onto a
72
104
  // target host via Bastion. See services/collector/server.js's
@@ -202,6 +234,31 @@ export const v1 = {
202
234
  path: () => `/api/v1/build-status`,
203
235
  tags: ['live-safe'],
204
236
  },
237
+ // HYPERION-NEXT P10: fleet hardware health (bare-metal IPMI / RAID / SMART), `quox fleet hw`.
238
+ 'v1.fleet.hw.list': {
239
+ method: 'GET',
240
+ service: 'collector',
241
+ path: () => `/api/v1/fleet/hw`,
242
+ tags: ['live-safe'],
243
+ },
244
+ 'v1.fleet.hw.events': {
245
+ method: 'GET',
246
+ service: 'collector',
247
+ path: () => `/api/v1/fleet/hw/events`,
248
+ tags: ['live-safe'],
249
+ },
250
+ 'v1.fleet.hw.get': {
251
+ method: 'GET',
252
+ service: 'collector',
253
+ path: ({ host }) => `/api/v1/fleet/hw/${encodeURIComponent(host)}`,
254
+ tags: ['live-safe'],
255
+ },
256
+ 'v1.fleet.hw.refresh': {
257
+ method: 'POST',
258
+ service: 'collector',
259
+ path: () => `/api/v1/fleet/hw/refresh`,
260
+ tags: ['live-write'],
261
+ },
205
262
  'v1.gpu.nodes.list': {
206
263
  method: 'GET',
207
264
  service: 'collector',
@@ -41,6 +41,39 @@ async function listItems(ctx, { status = 'all', limit = 100, scope } = {}) {
41
41
  throw new QuoxError(`More than ${MAX_PAGES * pageSize} items; narrow with --status or --scope.`);
42
42
  }
43
43
 
44
+ // Short ids, as `quox approvals` shows them: the last 6 characters of the ULID. --json keeps the full id.
45
+ const SHORT_LEN = 6;
46
+ export const shortActionId = (id) => String(id ?? '').slice(-SHORT_LEN);
47
+
48
+ // The width to fit: the terminal when there is one, else COLUMNS (the Quox TUI sets it for ! and /<verb> runs).
49
+ function outputWidth() {
50
+ if (process.stdout.isTTY && process.stdout.columns) return process.stdout.columns;
51
+ const env = Number(process.env.COLUMNS);
52
+ return env > 0 ? env : 0;
53
+ }
54
+
55
+ /** An exact id, or a unique id suffix (the short id the list shows). -> item | null; ambiguous throws. */
56
+ function findByIdOrSuffix(items, given) {
57
+ const want = String(given || '').trim().toUpperCase();
58
+ if (!want) return null;
59
+ const exact = items.find((i) => String(i.id).toUpperCase() === want);
60
+ if (exact) return exact;
61
+ const hits = items.filter((i) => String(i.id).toUpperCase().endsWith(want));
62
+ if (hits.length > 1) {
63
+ throw new QuoxError(`"${given}" matches ${hits.length} action items (${hits.map((h) => h.id).join(', ')}); use more of the id.`, 2);
64
+ }
65
+ return hits[0] || null;
66
+ }
67
+
68
+ // A short id (base32, shorter than a ULID) is resolved against the list; anything else goes to the server as given.
69
+ const looksShort = (id) => /^[0-9A-Z]{4,25}$/i.test(String(id || '').trim());
70
+ async function resolveActionId(ctx, given) {
71
+ if (!looksShort(given)) return given;
72
+ const item = findByIdOrSuffix(await listItems(ctx, { status: 'all', limit: 100 }), given);
73
+ if (!item) throw new QuoxError(`No action item matches ${given}.`, 5);
74
+ return item.id;
75
+ }
76
+
44
77
  export function registerActionsCommands(program, ctx) {
45
78
  const actions = program
46
79
  .command('actions')
@@ -54,20 +87,24 @@ export function registerActionsCommands(program, ctx) {
54
87
  .option('--scope <scopeId>', 'only items in this team/department/division scope')
55
88
  .action(async (options) => {
56
89
  const items = await listItems({ ...ctx, program }, options);
90
+ // Fit the terminal (owner 2026-11-07): short id, title cut with an ellipsis, no all-empty Due column.
91
+ const hasDue = items.some((i) => i.dueDate);
92
+ const columns = ['id', 'status', 'priority', 'title', ...(hasDue ? ['dueDate'] : [])];
93
+ const headers = ['ID', 'Status', 'Priority', 'Title', ...(hasDue ? ['Due'] : [])];
57
94
  formatOutput({
58
- columns: ['id', 'status', 'priority', 'title', 'dueDate'],
59
- headers: ['ID', 'Status', 'Priority', 'Title', 'Due'],
60
- rows: items.map((i) => ({ ...i, dueDate: i.dueDate || '-' })),
61
- }, { raw: items });
95
+ columns,
96
+ headers,
97
+ rows: items.map((i) => ({ ...i, fullId: i.id, id: shortActionId(i.id), dueDate: i.dueDate ? String(i.dueDate).slice(0, 10) : '' })),
98
+ }, { raw: items, quietColumn: 'fullId', fit: { width: outputWidth(), flex: 'title' } });
62
99
  });
63
100
 
64
101
  actions
65
102
  .command('get')
66
103
  .description('Show one action item by id')
67
- .argument('<id>', 'action item id (from `quox actions list`)')
104
+ .argument('<id>', 'action item id, or the short id `quox actions list` shows')
68
105
  .action(async (id) => {
69
106
  const items = await listItems({ ...ctx, program }, { status: 'all', limit: 100 });
70
- const item = items.find((i) => i.id === id);
107
+ const item = findByIdOrSuffix(items, id);
71
108
  if (!item) throw new QuoxError(`No action item ${id} among the latest 100 items.`);
72
109
  formatDetail(item);
73
110
  });
@@ -90,10 +127,10 @@ export function registerActionsCommands(program, ctx) {
90
127
  actions
91
128
  .command('done')
92
129
  .description('Mark an Action Tracker item done')
93
- .argument('<id>', 'action item id (from `quox actions list`)')
130
+ .argument('<id>', 'action item id, or the short id `quox actions list` shows')
94
131
  .action(async (id) => {
95
132
  ctx.auth.requireAuth();
96
- const item = await completeActionItem(ctx.client, id);
133
+ const item = await completeActionItem(ctx.client, await resolveActionId({ ...ctx, program }, id));
97
134
  if (program.opts().output === 'json') return formatDetail(item);
98
135
  printSuccess(`Marked done ${item.id}: ${item.title}`);
99
136
  return undefined;
@@ -102,11 +139,11 @@ export function registerActionsCommands(program, ctx) {
102
139
  actions
103
140
  .command('move')
104
141
  .description('Move an Action Tracker item to todo, doing or done')
105
- .argument('<id>', 'action item id (from `quox actions list`)')
142
+ .argument('<id>', 'action item id, or the short id `quox actions list` shows')
106
143
  .argument('<status>', STATUS_COLUMNS.join(', '))
107
144
  .action(async (id, status) => {
108
145
  ctx.auth.requireAuth();
109
- const item = await moveActionItem(ctx.client, id, status);
146
+ const item = await moveActionItem(ctx.client, await resolveActionId({ ...ctx, program }, id), status);
110
147
  if (program.opts().output === 'json') return formatDetail(item);
111
148
  printSuccess(`Moved ${item.id} to ${item.status}: ${item.title}`);
112
149
  return undefined;
@@ -115,15 +152,16 @@ export function registerActionsCommands(program, ctx) {
115
152
  actions
116
153
  .command('delete')
117
154
  .description('Delete one of your Action Tracker items (asks first; --yes skips, required without a terminal)')
118
- .argument('<id>', 'action item id (from `quox actions list`)')
155
+ .argument('<id>', 'action item id, or the short id `quox actions list` shows')
119
156
  .option('-y, --yes', 'do not ask')
120
157
  .action(async (id, options) => {
121
158
  ctx.auth.requireAuth();
122
- if (!(await confirmDestructive(program, { force: options.yes === true }, `Delete action item ${id}?`))) {
159
+ const fullId = await resolveActionId({ ...ctx, program }, id);
160
+ if (!(await confirmDestructive(program, { force: options.yes === true }, `Delete action item ${fullId}?`))) {
123
161
  console.log('Not deleted.');
124
162
  return undefined;
125
163
  }
126
- const r = await deleteActionItem(ctx.client, id);
164
+ const r = await deleteActionItem(ctx.client, fullId);
127
165
  if (program.opts().output === 'json') return formatDetail(r);
128
166
  printSuccess(`Deleted ${r.id}${r.title ? `: ${r.title}` : ''}`);
129
167
  return undefined;