quoxcli 0.5.0 → 0.6.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/CHANGELOG.md +50 -1
- package/lib/api/endpoints/governance.js +7 -0
- package/lib/api/endpoints/inboxq.js +39 -0
- package/lib/api/endpoints/index.js +2 -0
- package/lib/api/endpoints/lucid.js +17 -0
- package/lib/api/endpoints/mcp.js +35 -0
- package/lib/api/endpoints/notesq.js +2 -0
- package/lib/api/endpoints/quoxplan.js +51 -0
- package/lib/commands/actions.js +51 -13
- package/lib/commands/contacts.js +2 -0
- package/lib/commands/lucid.js +6 -0
- package/lib/commands/lucidGoals.js +389 -0
- package/lib/commands/mail.js +266 -5
- package/lib/commands/mcp.js +190 -0
- package/lib/commands/mon.js +14 -0
- package/lib/commands/monitoring.js +68 -0
- package/lib/commands/notesSeal.js +380 -22
- package/lib/commands/offload.js +51 -10
- package/lib/commands/plan.js +471 -53
- package/lib/commands/volt.js +6 -1
- package/lib/commands/wazuh.js +65 -9
- package/lib/commands/wazuhIntake.js +46 -5
- package/lib/core/commandRegistrars.js +1 -0
- package/lib/core/config.js +14 -1
- package/lib/core/offload.js +83 -0
- package/lib/core/output.js +29 -4
- package/lib/core/selfUpdate.js +2 -0
- package/lib/core/version.js +1 -1
- package/lib/quoxon/api.js +4 -0
- package/lib/quoxon/app.js +257 -48
- package/lib/quoxon/approvalsView.js +3 -1
- package/lib/quoxon/ask.js +4 -2
- package/lib/quoxon/bang.js +7 -1
- package/lib/quoxon/commandShort.js +1 -1
- package/lib/quoxon/commands.js +4 -4
- package/lib/quoxon/delegationDragon.js +49 -0
- package/lib/quoxon/dnd.js +47 -0
- package/lib/quoxon/events.js +12 -1
- package/lib/quoxon/extensions.js +2 -0
- package/lib/quoxon/foreignText.js +49 -0
- package/lib/quoxon/inboxView.js +5 -5
- package/lib/quoxon/launcher.js +2 -1
- package/lib/quoxon/loopCard.js +2 -1
- package/lib/quoxon/main.js +8 -2
- package/lib/quoxon/onboarding/dockerOffer.js +3 -1
- package/lib/quoxon/onboarding/view.js +1 -1
- package/lib/quoxon/render.js +5 -2
- package/lib/quoxon/resend.js +14 -0
- package/lib/quoxon/rows.js +3 -1
- package/lib/quoxon/secretShape.js +2 -0
- package/lib/quoxon/slash/broadcast.js +30 -6
- package/lib/quoxon/slash/broadcastRun.js +12 -6
- package/lib/quoxon/slash/cliLeaves.json +35 -0
- package/lib/quoxon/slash/dashboard.js +2 -0
- package/lib/quoxon/slash/listView.js +2 -1
- package/lib/quoxon/slash/palette.js +41 -0
- package/lib/quoxon/slash/paletteController.js +6 -2
- package/lib/quoxon/slash/restart.js +2 -0
- package/lib/quoxon/slotChat.js +7 -3
- package/lib/quoxon/slotRun.js +2 -0
- package/lib/quoxon/webPty.js +15 -0
- package/lib/quoxon/whatsnew.jsonl +6 -0
- package/lib/render/planBrief.js +614 -0
- package/lib/toolOutputShape.js +6 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -4,7 +4,49 @@ 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.
|
|
7
|
+
## 0.6.0 (2026-11-10)
|
|
8
|
+
|
|
9
|
+
Nothing below has been run end to end from the published 0.6.0 package yet: treat every
|
|
10
|
+
item as *(unproven from the package)*. Items marked *(driven live)* were driven against a
|
|
11
|
+
live instance from the source tree before release.
|
|
12
|
+
|
|
13
|
+
### New commands
|
|
14
|
+
- `quox notes seal convert <id>` and `quox notes seal purge <id>` (human sessions only):
|
|
15
|
+
seal an existing plaintext note in place, then wipe its old plaintext revisions. Convert
|
|
16
|
+
names any attachments and says they are NOT protected. *(driven live)*
|
|
17
|
+
- Sealed notes harden: each sealed note is bound to its note id (another note's envelope
|
|
18
|
+
is refused), the version comes from the authenticated envelope, and a local rollback
|
|
19
|
+
floor fails closed under a lock.
|
|
20
|
+
- `quox notes seal` finds ENQRYPT without setup: `QUOX_ENQRYPT_PATH`, then
|
|
21
|
+
`~/.quox/enqrypt-src` (a clone of quoxai/enqrypt main plus `npm ci`); a copy missing a
|
|
22
|
+
function the CLI calls is refused by name. ENQRYPT is not on npm, and the npm package
|
|
23
|
+
named `enqrypt` is unrelated malware. *(driven live)*
|
|
24
|
+
- `quox mail block | unblock | blocked | junk | trust`.
|
|
25
|
+
- `quox mcp token create | list | revoke`.
|
|
26
|
+
- `quox lucid goals`, `quox lucid insights`, `quox lucid brief`.
|
|
27
|
+
- `quox plan brief`, `quox plan watch`, `quox plan followups` / `followup add | done | status`,
|
|
28
|
+
`quox plan roadmap archive | unarchive`, `quox plan roadmaps --archived`.
|
|
29
|
+
- `quox monitoring incidents claim <id>` and `quox monitoring incidents status <id> <status>`: claim and move a
|
|
30
|
+
governance incident (witnessed, org scoped). `quox wazuh connect --indexer-ca` and
|
|
31
|
+
`--password-stdin` / `--indexer-password-stdin`.
|
|
32
|
+
|
|
33
|
+
### Changes
|
|
34
|
+
- `quox plan add` honours `roadmap_id` in the JSON (streams used to land in the first
|
|
35
|
+
roadmap). `quox plan focus` counts substreams and caps its output; unblockers name the
|
|
36
|
+
whole chain.
|
|
37
|
+
- `quox wazuh vulns` totals are exact or labelled a lower bound (10,000+); `quox wazuh status`
|
|
38
|
+
says its agent count includes the manager; no MAINT? column when the org has no Uptime Kuma.
|
|
39
|
+
- `quox offload` lists every worker lease and warns 12h before one expires; `offload release
|
|
40
|
+
--worker` targets that worker, never the shared default.
|
|
41
|
+
- `quox tool exec --select .` returns the whole result. `quox actions list` fits the terminal.
|
|
42
|
+
- Conversation app (`quox` with no arguments): `/wake`, `/poke` and `/page` take several
|
|
43
|
+
slots; `/inbox show N` reads a whole item; `/morningreport` digests what happened since you
|
|
44
|
+
were last here; a typed `/org switch` or `/org back` no longer asks a second y/n; an inline
|
|
45
|
+
approval says approved only after the server records it and no longer re-opens as a popup;
|
|
46
|
+
decision cards never echo the asker's pick as their own advice; contact-consent approvals ask
|
|
47
|
+
for the lawful basis; slot messages from other sessions never show raw colour codes.
|
|
48
|
+
|
|
49
|
+
## 0.5.0 (2026-11-06)
|
|
8
50
|
|
|
9
51
|
### New commands
|
|
10
52
|
- `quox wazuh`: first-class Wazuh commands. `agents`, `fim`, `sca`, `rootcheck`
|
|
@@ -32,6 +74,13 @@ published package yet.
|
|
|
32
74
|
- `quox notes seal init | status | new | open | edit | print`: ENQRYPT sealed
|
|
33
75
|
notes. Encryption happens on your machine and the server stores only the
|
|
34
76
|
envelope. *(unproven from the package)*
|
|
77
|
+
- `quox notes seal convert <id> --yes [--keep-title]` seals an existing note and
|
|
78
|
+
purges its earlier plaintext from the server (revisions, embeddings, derived
|
|
79
|
+
beliefs; human sessions only); `quox notes seal purge <id> --yes` reruns the
|
|
80
|
+
purge. Both say that backups taken earlier still hold the old text until they
|
|
81
|
+
expire. `seal edit` now takes the version from the sealed envelope, not from a
|
|
82
|
+
server field, and the local rollback floor refuses a damaged or missing floor
|
|
83
|
+
file instead of starting again from zero. *(unproven from the package)*
|
|
35
84
|
- `quox admin users verify-email <email> --reason` (step-up PIN).
|
|
36
85
|
- `quox memory sync`: Claude file memory is saved to Quox memory too
|
|
37
86
|
(idempotent; credential-shaped text is refused).
|
|
@@ -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
|
};
|
|
@@ -96,6 +96,7 @@ import { contactsq } from './contactsq.js';
|
|
|
96
96
|
import { x } from './x.js';
|
|
97
97
|
import { llmPolicy } from './llm-policy.js';
|
|
98
98
|
import { hat } from './hat.js';
|
|
99
|
+
import { mcp } from './mcp.js';
|
|
99
100
|
import { members } from './members.js';
|
|
100
101
|
import { memory } from './memory.js';
|
|
101
102
|
import { mfa } from './mfa.js';
|
|
@@ -189,6 +190,7 @@ export const ENDPOINTS = {
|
|
|
189
190
|
...x,
|
|
190
191
|
...llmPolicy,
|
|
191
192
|
...hat,
|
|
193
|
+
...mcp,
|
|
192
194
|
...members,
|
|
193
195
|
...memory,
|
|
194
196
|
...mfa,
|
|
@@ -86,4 +86,21 @@ 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).
|
|
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.get': { method: 'GET', service: 'collector', path: ({ id }) => `/api/v1/lucid/intelligence/insights/${encodeURIComponent(id)}`, tags: ['live-safe'] },
|
|
104
|
+
// LUCID-INTELLIGENCE LI-3 brief (read-only, never moves a cursor).
|
|
105
|
+
'lucid.brief': { method: 'GET', service: 'collector', path: () => `/api/v1/lucid/intelligence/brief`, tags: ['live-safe'] },
|
|
89
106
|
};
|
|
@@ -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
|
+
};
|
|
@@ -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
|
};
|
package/lib/commands/actions.js
CHANGED
|
@@ -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
|
|
59
|
-
headers
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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,
|
|
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;
|
package/lib/commands/contacts.js
CHANGED
|
@@ -408,6 +408,8 @@ export function registerContactsCommands(program, ctx) {
|
|
|
408
408
|
if (isJson(program)) return printJson(d);
|
|
409
409
|
console.log(`channel: ${d.channel || 'none allowed'}`);
|
|
410
410
|
for (const a of d.alternatives || []) console.log(` ${a.channel}: ${a.decision}${(a.reasons || []).length ? ` (${a.reasons.join(', ')})` : ''}`);
|
|
411
|
+
if (d.evaluated_for) console.log(`for: ${d.evaluated_for === 'agent' ? 'an agent' : 'you (a person)'}`);
|
|
412
|
+
if ((d.quiet_hours_for_agents || []).length) console.log(`quiet hours: an agent reaching them by ${d.quiet_hours_for_agents.join(', ')} now needs approval`);
|
|
411
413
|
announce('contacts best-channel', d.channel || 'none allowed');
|
|
412
414
|
});
|
|
413
415
|
|
package/lib/commands/lucid.js
CHANGED
|
@@ -9,12 +9,14 @@
|
|
|
9
9
|
* quox lucid teach <target> <field>=<value> | lessons | forget <id>
|
|
10
10
|
* quox lucid herald [set key=value... | test] when LUCID speaks first, and what it has said
|
|
11
11
|
* quox lucid blind sources live/silent/failing/never heard, and what doesn't add up
|
|
12
|
+
* quox lucid goals ... your goals and decisions, so LUCID ranks against them (see lucidGoals.js)
|
|
12
13
|
*/
|
|
13
14
|
import { formatOutput, printSuccess } from '../core/output.js';
|
|
14
15
|
import { line as glyphLine, printEvent } from '../core/quoxglyph.js';
|
|
15
16
|
import { resolveEndpoint } from '../api/endpoints.js';
|
|
16
17
|
import { confirmDestructive } from '../core/confirm.js';
|
|
17
18
|
import { resolveOrgId } from './helpers.js';
|
|
19
|
+
import { registerLucidGoalsCommands, registerLucidInsightsCommands, registerLucidBriefCommand } from './lucidGoals.js';
|
|
18
20
|
|
|
19
21
|
const SURFACE = 'cli';
|
|
20
22
|
|
|
@@ -505,6 +507,10 @@ export function registerLucidCommands(program, ctx) {
|
|
|
505
507
|
if (st.length) console.log(`Stats: ${st.map(([k, v]) => `${k}=${Array.isArray(v) ? v.join(',') : v}`).join(' · ')}`);
|
|
506
508
|
});
|
|
507
509
|
|
|
510
|
+
registerLucidGoalsCommands(lu, program, ctx);
|
|
511
|
+
registerLucidInsightsCommands(lu, program, ctx);
|
|
512
|
+
registerLucidBriefCommand(lu, program, ctx);
|
|
513
|
+
|
|
508
514
|
// ---- herald: when LUCID reaches out on its own ----
|
|
509
515
|
const LEVELS = { l1_inbox: 'filed in your Approvals inbox', l2_slot: 'nudged you in your slot', l3_call: 'called you', resolved: 'resolved' };
|
|
510
516
|
const levelWords = (l) => LEVELS[l] || l || 'not yet reached out';
|