quoxcli 0.4.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.
Files changed (199) hide show
  1. package/CHANGELOG.md +110 -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/governance.js +7 -0
  9. package/lib/api/endpoints/inbox.js +6 -0
  10. package/lib/api/endpoints/inboxq.js +58 -0
  11. package/lib/api/endpoints/index.js +4 -0
  12. package/lib/api/endpoints/lucid.js +17 -0
  13. package/lib/api/endpoints/mcp.js +35 -0
  14. package/lib/api/endpoints/mon.js +122 -0
  15. package/lib/api/endpoints/notesq.js +2 -0
  16. package/lib/api/endpoints/quoxplan.js +51 -0
  17. package/lib/api/endpoints/tools.js +13 -0
  18. package/lib/api/endpoints/v1.js +13 -0
  19. package/lib/commands/actions.js +51 -13
  20. package/lib/commands/admin.js +35 -0
  21. package/lib/commands/agentAttach.js +15 -0
  22. package/lib/commands/approvals.js +193 -3
  23. package/lib/commands/ask.js +1 -1
  24. package/lib/commands/auth.js +64 -11
  25. package/lib/commands/backup.js +11 -4
  26. package/lib/commands/capabilities.js +3 -2
  27. package/lib/commands/chat.js +15 -0
  28. package/lib/commands/claude.js +4 -1
  29. package/lib/commands/compliance.js +109 -0
  30. package/lib/commands/connect.js +69 -0
  31. package/lib/commands/contacts.js +2 -0
  32. package/lib/commands/dev.js +6 -8
  33. package/lib/commands/doctor.js +22 -3
  34. package/lib/commands/evidence.js +2 -1
  35. package/lib/commands/gripe.js +24 -13
  36. package/lib/commands/inbox.js +37 -3
  37. package/lib/commands/inboxq.js +147 -0
  38. package/lib/commands/kuma.js +78 -5
  39. package/lib/commands/licenceLinkView.js +311 -0
  40. package/lib/commands/linkedin.js +19 -0
  41. package/lib/commands/loop.js +30 -3
  42. package/lib/commands/lucid.js +7 -0
  43. package/lib/commands/lucidGoals.js +389 -0
  44. package/lib/commands/mail.js +427 -25
  45. package/lib/commands/mcp.js +190 -0
  46. package/lib/commands/memory.js +68 -4
  47. package/lib/commands/memorySync.js +158 -0
  48. package/lib/commands/mon.js +69 -144
  49. package/lib/commands/monitoring.js +68 -0
  50. package/lib/commands/notes.js +29 -7
  51. package/lib/commands/notesSeal.js +617 -0
  52. package/lib/commands/objective.js +5 -2
  53. package/lib/commands/offload.js +52 -12
  54. package/lib/commands/plan.js +487 -52
  55. package/lib/commands/provider.js +29 -8
  56. package/lib/commands/qlarity.js +32 -1
  57. package/lib/commands/run.js +2 -1
  58. package/lib/commands/server.js +37 -0
  59. package/lib/commands/setup.js +127 -75
  60. package/lib/commands/skills.js +4 -1
  61. package/lib/commands/slot.js +49 -9
  62. package/lib/commands/status.js +92 -44
  63. package/lib/commands/tool.js +30 -0
  64. package/lib/commands/vault.js +6 -3
  65. package/lib/commands/volt.js +6 -1
  66. package/lib/commands/walkthrough.js +14 -2
  67. package/lib/commands/watch.js +11 -2
  68. package/lib/commands/wazuh.js +880 -0
  69. package/lib/commands/wazuhIntake.js +358 -0
  70. package/lib/commands/wazuhReport.js +192 -0
  71. package/lib/commands/workflow.js +47 -4
  72. package/lib/commands/x.js +50 -0
  73. package/lib/core/announce.js +86 -0
  74. package/lib/core/auth.js +20 -2
  75. package/lib/core/callMe.js +118 -0
  76. package/lib/core/client.js +27 -1
  77. package/lib/core/commandRegistrars.js +9 -0
  78. package/lib/core/config.js +42 -3
  79. package/lib/core/confirm.js +68 -7
  80. package/lib/core/crossOrgApprovals.js +69 -0
  81. package/lib/core/errors.js +52 -1
  82. package/lib/core/governedSession.js +135 -5
  83. package/lib/core/inboxResolution.js +3 -2
  84. package/lib/core/loginGuard.js +73 -0
  85. package/lib/core/offload.js +120 -0
  86. package/lib/core/org-override.js +5 -1
  87. package/lib/core/output.js +30 -5
  88. package/lib/core/profileTrust.js +182 -0
  89. package/lib/core/quoxglyph.js +22 -1
  90. package/lib/core/router.js +14 -0
  91. package/lib/core/selfUpdate.js +2 -0
  92. package/lib/core/stepUpPrompt.js +99 -0
  93. package/lib/core/version.js +1 -1
  94. package/lib/core/walkthrough.js +9 -5
  95. package/lib/dragon/select.js +2 -2
  96. package/lib/quoxon/EXTENSIONS.md +142 -19
  97. package/lib/quoxon/api.js +189 -34
  98. package/lib/quoxon/app.js +1277 -188
  99. package/lib/quoxon/approvalBatch.js +117 -0
  100. package/lib/quoxon/approvalFacts.js +56 -2
  101. package/lib/quoxon/approvalSentence.js +8 -4
  102. package/lib/quoxon/approvalsView.js +72 -12
  103. package/lib/quoxon/ask.js +4 -2
  104. package/lib/quoxon/autoOpen.js +94 -0
  105. package/lib/quoxon/backgroundWork.js +59 -0
  106. package/lib/quoxon/bang.js +8 -2
  107. package/lib/quoxon/capabilities.js +7 -2
  108. package/lib/quoxon/commandShort.js +8 -1
  109. package/lib/quoxon/commands.js +42 -12
  110. package/lib/quoxon/connect.js +5 -3
  111. package/lib/quoxon/deckLazy.js +58 -0
  112. package/lib/quoxon/delegationDragon.js +49 -0
  113. package/lib/quoxon/destructive.js +35 -0
  114. package/lib/quoxon/dnd.js +118 -0
  115. package/lib/quoxon/dragonCompanion.js +37 -7
  116. package/lib/quoxon/dragonMoments.js +78 -0
  117. package/lib/quoxon/echo.js +1 -0
  118. package/lib/quoxon/events.js +55 -8
  119. package/lib/quoxon/extensions.js +400 -38
  120. package/lib/quoxon/foreignText.js +49 -0
  121. package/lib/quoxon/gripeQueue.js +135 -0
  122. package/lib/quoxon/inboxView.js +125 -22
  123. package/lib/quoxon/keys.js +2 -2
  124. package/lib/quoxon/launcher.js +2 -1
  125. package/lib/quoxon/loopCard.js +7 -2
  126. package/lib/quoxon/loopCards.js +17 -8
  127. package/lib/quoxon/loopStream.js +20 -9
  128. package/lib/quoxon/mailView.js +71 -0
  129. package/lib/quoxon/main.js +27 -6
  130. package/lib/quoxon/markdown.js +1 -1
  131. package/lib/quoxon/model.js +15 -0
  132. package/lib/quoxon/neverAnswered.js +20 -0
  133. package/lib/quoxon/objects/away.js +43 -5
  134. package/lib/quoxon/onboarding/connectServer.js +162 -0
  135. package/lib/quoxon/onboarding/copy.js +6 -0
  136. package/lib/quoxon/onboarding/dockerOffer.js +3 -1
  137. package/lib/quoxon/onboarding/index.js +5 -43
  138. package/lib/quoxon/onboarding/probe.js +20 -1
  139. package/lib/quoxon/onboarding/view.js +16 -3
  140. package/lib/quoxon/orgSwitch.js +22 -0
  141. package/lib/quoxon/print.js +2 -0
  142. package/lib/quoxon/profileSwitch.js +7 -0
  143. package/lib/quoxon/promptHistory.js +3 -1
  144. package/lib/quoxon/render.js +68 -16
  145. package/lib/quoxon/resend.js +14 -0
  146. package/lib/quoxon/roster.js +10 -5
  147. package/lib/quoxon/rosterSessions.js +44 -0
  148. package/lib/quoxon/rows.js +3 -1
  149. package/lib/quoxon/screenContext.js +1 -1
  150. package/lib/quoxon/secretShape.js +88 -0
  151. package/lib/quoxon/slash/agents.js +2 -1
  152. package/lib/quoxon/slash/argSources.js +9 -2
  153. package/lib/quoxon/slash/args.js +4 -2
  154. package/lib/quoxon/slash/broadcast.js +48 -11
  155. package/lib/quoxon/slash/broadcastRun.js +24 -7
  156. package/lib/quoxon/slash/cliLeaves.json +98 -1
  157. package/lib/quoxon/slash/dashboard.js +2 -0
  158. package/lib/quoxon/slash/hatWalk.js +1 -1
  159. package/lib/quoxon/slash/listView.js +2 -1
  160. package/lib/quoxon/slash/palette.js +63 -1
  161. package/lib/quoxon/slash/paletteController.js +15 -5
  162. package/lib/quoxon/slash/restart.js +9 -4
  163. package/lib/quoxon/slash/stepUp.js +5 -5
  164. package/lib/quoxon/slash/stepUpCmds.js +74 -1
  165. package/lib/quoxon/slashSpinner.js +35 -0
  166. package/lib/quoxon/slotAttach.js +8 -0
  167. package/lib/quoxon/slotChat.js +24 -3
  168. package/lib/quoxon/slotFormat.js +1 -1
  169. package/lib/quoxon/slotOutbox.js +113 -0
  170. package/lib/quoxon/slotRun.js +2 -0
  171. package/lib/quoxon/suggestCommand.js +1 -1
  172. package/lib/quoxon/term.js +25 -1
  173. package/lib/quoxon/transcript.js +2 -1
  174. package/lib/quoxon/webPty.js +15 -0
  175. package/lib/quoxon/welcome.js +1 -1
  176. package/lib/quoxon/whatsnew.js +1 -1
  177. package/lib/quoxon/whatsnew.jsonl +42 -0
  178. package/lib/render/planBrief.js +614 -0
  179. package/lib/server/installFlow.js +168 -0
  180. package/lib/server/status.js +15 -1
  181. package/lib/toolOutputShape.js +61 -3
  182. package/package.json +3 -2
  183. package/vendor/dragonlab/SOURCE +3 -3
  184. package/vendor/dragonlab/design/quoxperticus/v2/quoxperticus-master.svg +59 -0
  185. package/vendor/dragonlab/design/quoxperticus/v3/character.mjs +43 -0
  186. package/vendor/dragonlab/design/quoxperticus/v4/character.mjs +57 -0
  187. package/vendor/dragonlab/design/quoxperticus/v4/review.mjs +35 -0
  188. package/vendor/dragonlab/design/quoxperticus/v4/watch.mjs +56 -0
  189. package/vendor/dragonlab/design/vulcan/v1/character.mjs +34 -0
  190. package/vendor/dragonlab/design/vulcan/v1/vulcan-master.svg +57 -0
  191. package/vendor/dragonlab/design/vulcan/v2/character.mjs +39 -0
  192. package/vendor/dragonlab/design/vulcan/v3/character.mjs +41 -0
  193. package/vendor/dragonlab/design/vulcan/v3/review.mjs +35 -0
  194. package/vendor/dragonlab/design/vulcan/v3/watch.mjs +56 -0
  195. package/vendor/dragonlab/factory/svg-rig-contour.mjs +64 -0
  196. package/vendor/dragonlab/integration/companions-v1/characters.mjs +20 -0
  197. package/vendor/dragonlab/integration/companions-v1/quoxperticus.mjs +2 -0
  198. package/vendor/dragonlab/integration/companions-v1/view.mjs +1 -1
  199. package/vendor/dragonlab/integration/companions-v1/vulcan.mjs +2 -0
@@ -29,6 +29,32 @@ function toMs(ts) {
29
29
  return Number.isNaN(n) ? null : n;
30
30
  }
31
31
 
32
+ const LIVE_STATES = new Set(['pending', 'starting', 'decomposing', 'running', 'waiting']);
33
+
34
+ /**
35
+ * One row, one vocabulary. The list route counts turn events, which loops driven by the runner never write, so a
36
+ * finished loop could read 0 there. The server now sends a real 0 (started, ran nothing) and null (no record at
37
+ * all); a number from the server is kept, null stays unknown unless the loop is live. objective_status is the objective row's word ('cancelled' for a
38
+ * human halt); `state` is the loop's, so the row says one thing and the raw word is kept as objective_row_status.
39
+ */
40
+ export function normLoop(l) {
41
+ const n = (v) => (Number.isFinite(Number(v)) && Number(v) > 0 ? Number(v) : 0);
42
+ const count = n(l.turn) || n(l.iteration_count);
43
+ // the server's turn is authoritative: a real 0 (started, ran nothing) stays 0; only null/absent means unknown
44
+ const turn = count || (l.turn === 0 || LIVE_STATES.has(l.state) ? 0 : null);
45
+ const out = { ...l, turn };
46
+ if (l.objective_status !== undefined && l.state && l.objective_status !== l.state) {
47
+ out.objective_row_status = l.objective_status;
48
+ out.objective_status = l.state;
49
+ }
50
+ return out;
51
+ }
52
+
53
+ function turnLabel(l) {
54
+ if (l.turn === null) return '-';
55
+ return l.max_turns ? `${l.turn}/${l.max_turns}` : String(l.turn);
56
+ }
57
+
32
58
  const SETTLE_MAX_MS = 90000;
33
59
  const SETTLE_POLL_MS = 4000;
34
60
 
@@ -149,7 +175,7 @@ function oneLineErr(err) {
149
175
 
150
176
  export function statusLines(s, { diagnostics = false } = {}) {
151
177
  const lines = [];
152
- const turns = `turn ${s.turn}${s.max_turns ? `/${s.max_turns}` : ''}`;
178
+ const turns = `turn ${s.turn === null || s.turn === undefined ? '-' : s.turn}${s.max_turns ? `/${s.max_turns}` : ''}`;
153
179
  lines.push(loopLine(s.title || s.objective_id, `${s.state}, ${turns}`, s.state === 'running' ? 'busy' : (s.state === 'completed' ? 'ok' : (s.state === 'completed_with_errors' || s.state === 'halted' || s.state === 'interrupted' ? 'warn' : (s.finished ? 'fail' : 'busy')))));
154
180
  lines.push(` id ${s.objective_id}`);
155
181
  lines.push(` host ${s.host || '-'} backend ${s.backend || '-'} scope ${(s.scope || []).join(',') || '-'}`);
@@ -240,11 +266,12 @@ export function registerLoopCommands(program, ctx) {
240
266
  auth.requireAuth();
241
267
  const ep = resolveEndpoint('loops.list');
242
268
  const data = await client.get(ep.path, { service: ep.service, query: { limit: options.limit } });
269
+ const loops = (data.loops || []).map(normLoop);
243
270
  formatOutput({
244
271
  columns: ['objective_id', 'title', 'state', 'turn', 'backend', 'created_at'],
245
272
  headers: ['ID', 'Title', 'State', 'Turn', 'Backend', 'Created'],
246
- rows: (data.loops || []).map((l) => ({ ...l, turn: l.max_turns ? `${l.turn}/${l.max_turns}` : String(l.turn) })),
247
- }, { format: program.opts().output, emptyMessage: 'No loops in this org yet.' });
273
+ rows: loops.map((l) => ({ ...l, turn: turnLabel(l) })),
274
+ }, { format: program.opts().output, raw: loops, emptyMessage: 'No loops in this org yet.' });
248
275
  });
249
276
 
250
277
  // SECURITY containment (2026-10-26): which orgs may run loops in-server (core-local) while a security review is open.
@@ -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
 
@@ -105,6 +107,7 @@ export function registerLucidCommands(program, ctx) {
105
107
  if (o.markdown) { const { renderSitrepMarkdown } = await import('../lucid/markdown.js'); console.log(renderSitrepMarkdown(data)); return; }
106
108
  const { renderSitrep } = await import('../lucid/sitrep.js');
107
109
  const tty = Boolean(process.stdout.isTTY);
110
+ if (tty) process.stderr.write('Tip: for a shorter form use --spoken (sentences), --agent (compact) or --markdown (one chat message).\n');
108
111
  process.stdout.write(renderSitrep(data, {
109
112
  width: process.stdout.columns || 100,
110
113
  color: tty && !process.env.NO_COLOR && process.env.QUOX_COLOR !== '0',
@@ -504,6 +507,10 @@ export function registerLucidCommands(program, ctx) {
504
507
  if (st.length) console.log(`Stats: ${st.map(([k, v]) => `${k}=${Array.isArray(v) ? v.join(',') : v}`).join(' · ')}`);
505
508
  });
506
509
 
510
+ registerLucidGoalsCommands(lu, program, ctx);
511
+ registerLucidInsightsCommands(lu, program, ctx);
512
+ registerLucidBriefCommand(lu, program, ctx);
513
+
507
514
  // ---- herald: when LUCID reaches out on its own ----
508
515
  const LEVELS = { l1_inbox: 'filed in your Approvals inbox', l2_slot: 'nudged you in your slot', l3_call: 'called you', resolved: 'resolved' };
509
516
  const levelWords = (l) => LEVELS[l] || l || 'not yet reached out';
@@ -0,0 +1,389 @@
1
+ /**
2
+ * `quox lucid goals`: LUCID's World Model (LUCID-INTELLIGENCE LI-1). What you are trying to do,
3
+ * in your words, so LUCID can rank what matters against it instead of narrating telemetry.
4
+ *
5
+ * quox lucid goals your active goals (and anything an agent proposed)
6
+ * quox lucid goals add "<outcome>" a goal; --priority, --by <date>, --success "<criteria>", --watch <refs>, --shared
7
+ * quox lucid goals add --as decision "<question>" also: initiative "<title>", actor "<name>"
8
+ * quox lucid goals link <from> <to> --relation blocks|requires|related
9
+ * quox lucid goals show|history <id>
10
+ * quox lucid goals change <id> key=value... your correction (it wins, and stays confirmed)
11
+ * quox lucid goals confirm <id> accept something an agent proposed
12
+ * quox lucid goals archive|restore <id>
13
+ * quox lucid goals export [--out <file>]
14
+ * quox lucid goals erase delete your personal World Model (asks first)
15
+ *
16
+ * What you add is CONFIRMED. What an agent adds is only PROPOSED until you confirm it.
17
+ */
18
+ import fs from 'fs';
19
+ import { formatOutput, printSuccess } from '../core/output.js';
20
+ import { line as glyphLine, printEvent } from '../core/quoxglyph.js';
21
+ import { resolveEndpoint } from '../api/endpoints.js';
22
+ import { confirmDestructive } from '../core/confirm.js';
23
+ import { resolveOrgId } from './helpers.js';
24
+
25
+ const KINDS = ['goal', 'initiative', 'decision', 'dependency', 'actor'];
26
+ // Which field the free text fills for each kind.
27
+ const TEXT_FIELD = { goal: 'outcome', initiative: 'title', decision: 'question', actor: 'name' };
28
+
29
+ function coerce(v) {
30
+ if (v === 'true') return true;
31
+ if (v === 'false') return false;
32
+ if (v === 'null') return null;
33
+ return /^-?\d+(\.\d+)?$/.test(v) ? Number(v) : v;
34
+ }
35
+
36
+ /** The one-line, human description of an entity. */
37
+ export function describeEntity(e) {
38
+ const text = e.outcome || e.title || e.question || e.name
39
+ || (e.kind === 'dependency' ? `${e.from} ${e.relation || 'related'} ${e.to}` : '') || e.id;
40
+ return String(text);
41
+ }
42
+
43
+ export function standingOf(e) {
44
+ const s = e.provenance && e.provenance.inference_status;
45
+ return s === 'confirmed' ? 'yours' : s === 'proposed' ? 'proposed, needs you' : s || '';
46
+ }
47
+
48
+ export function parsePairs(pairs) {
49
+ const patch = {};
50
+ for (const p of pairs) {
51
+ const eq = p.indexOf('=');
52
+ if (eq < 1) throw new Error(`expected key=value, got "${p}"`);
53
+ patch[p.slice(0, eq)] = coerce(p.slice(eq + 1));
54
+ }
55
+ return patch;
56
+ }
57
+
58
+ export function registerLucidGoalsCommands(lu, program, ctx) {
59
+ const { client, auth } = ctx;
60
+ const wantJson = (cmd) => {
61
+ const o = cmd.optsWithGlobals ? cmd.optsWithGlobals() : cmd.opts();
62
+ return Boolean(o.json) || program.opts().output === 'json';
63
+ };
64
+ const announce = (verb, result) => printEvent(glyphLine('lucid', verb, { result }).replace('Ⓠ', '◉'));
65
+
66
+ // An error body is not data: report the status and the server's reason, and stop.
67
+ async function call(key, { params, query, body } = {}) {
68
+ const ep = resolveEndpoint(key, params);
69
+ const q = { org_id: resolveOrgId(auth, program), ...query };
70
+ const method = { GET: 'get', POST: 'post', PATCH: 'patch', DELETE: 'delete' }[ep.method] || 'get';
71
+ try {
72
+ return await client[method](ep.path, { service: ep.service, query: q, ...(body !== undefined ? { body } : {}) });
73
+ } catch (err) {
74
+ const status = err && (err.status || err.statusCode);
75
+ if (status) {
76
+ const why = (err.body && (err.body.message || err.body.error)) || err.message || '';
77
+ console.error(`LUCID goals request failed: HTTP ${status}${why ? ` ${why}` : ''}`);
78
+ process.exitCode = 1;
79
+ return null;
80
+ }
81
+ throw err;
82
+ }
83
+ }
84
+
85
+ function table(entities) {
86
+ formatOutput(entities.map((e) => ({
87
+ ID: e.id,
88
+ KIND: e.kind,
89
+ WHAT: describeEntity(e),
90
+ PRIORITY: e.priority || '',
91
+ STANDING: standingOf(e),
92
+ SHARED: e.scope && e.scope.type === 'org' ? 'org' : '',
93
+ })), {
94
+ format: program.opts().output,
95
+ columns: ['ID', 'KIND', 'WHAT', 'PRIORITY', 'STANDING', 'SHARED'],
96
+ emptyMessage: 'Nothing yet. Tell LUCID what you are working towards: quox lucid goals add "<what you want to be true>"',
97
+ raw: entities,
98
+ });
99
+ }
100
+
101
+ const goals = lu
102
+ .command('goals')
103
+ .alias('world')
104
+ .description("LUCID's World Model: your goals, initiatives and decisions, so it ranks what matters against them")
105
+ .option('--kind <kind>', `only one kind (${KINDS.join('|')})`)
106
+ .option('--archived', 'show archived instead of active')
107
+ .option('--all', 'active and archived')
108
+ .option('--proposed', 'only what an agent proposed and you have not confirmed')
109
+ .option('--json', 'print the full JSON response')
110
+ .action(async (options, cmd) => {
111
+ auth.requireAuth();
112
+ const data = await call('lucid.world.list', { query: {
113
+ kind: options.kind,
114
+ lifecycle: options.all ? 'all' : options.archived ? 'archived' : 'active',
115
+ status: options.proposed ? 'proposed' : undefined,
116
+ } });
117
+ if (data === null) return;
118
+ if (wantJson(cmd)) { console.log(JSON.stringify(data, null, 2)); return; }
119
+ const es = data.entities || [];
120
+ const proposed = es.filter((e) => standingOf(e) === 'proposed, needs you').length;
121
+ announce('goals', `${es.length} held${proposed ? ` · ${proposed} proposed` : ''}`);
122
+ table(es);
123
+ });
124
+
125
+ goals
126
+ .command('add <text...>')
127
+ .description('Add a goal (or, with --as, an initiative, decision or actor). Yours, so it is confirmed')
128
+ .option('--as <kind>', 'goal (default), initiative, decision, actor', 'goal')
129
+ .option('--priority <band>', 'critical|high|normal|low')
130
+ .option('--by <date>', 'deadline, e.g. 2026-11-30')
131
+ .option('--success <criteria>', 'how you will know it is done')
132
+ .option('--shared', 'share with the whole org (org admins only)')
133
+ .option('--watch <refs>', 'what it depends on, so LUCID can tie problems to it, e.g. site:shop,unit:billing.service or site:*')
134
+ .option('--json', 'print the full JSON response')
135
+ .action(async (words, options, cmd) => {
136
+ auth.requireAuth();
137
+ const kind = String(options.as || 'goal');
138
+ if (!TEXT_FIELD[kind]) {
139
+ console.error(`--as must be one of ${Object.keys(TEXT_FIELD).join('|')} (use "goals link" for a dependency)`);
140
+ process.exitCode = 1;
141
+ return;
142
+ }
143
+ const body = { kind, [TEXT_FIELD[kind]]: words.join(' '), scope: options.shared ? 'org' : 'user', source_ref: 'cli:quox lucid goals add' };
144
+ if (options.priority) body.priority = options.priority;
145
+ if (options.by) body.deadline = options.by;
146
+ if (options.success) body.success_criteria = options.success;
147
+ if (options.watch) body.watches = String(options.watch).split(',').map((x) => x.trim()).filter(Boolean);
148
+ if (kind === 'decision') body.status = 'open';
149
+ const data = await call('lucid.world.create', { body });
150
+ if (data === null) return;
151
+ if (wantJson(cmd)) { console.log(JSON.stringify(data, null, 2)); return; }
152
+ const e = data.entity;
153
+ announce('goals add', `${e.kind} ${e.id} · ${standingOf(e)}`);
154
+ printSuccess(`${describeEntity(e)} (${e.id})`);
155
+ });
156
+
157
+ goals
158
+ .command('link <from> <to>')
159
+ .description('Record that one thing depends on another')
160
+ .option('--relation <relation>', 'blocks|requires|related', 'blocks')
161
+ .option('--json', 'print the full JSON response')
162
+ .action(async (from, to, options, cmd) => {
163
+ auth.requireAuth();
164
+ const data = await call('lucid.world.create', { body: { kind: 'dependency', from, to, relation: options.relation, source_ref: 'cli:quox lucid goals link' } });
165
+ if (data === null) return;
166
+ if (wantJson(cmd)) { console.log(JSON.stringify(data, null, 2)); return; }
167
+ printSuccess(`Linked: ${describeEntity(data.entity)} (${data.entity.id})`);
168
+ });
169
+
170
+ goals
171
+ .command('show <id>')
172
+ .description('One entity, with who said it and when')
173
+ .option('--json', 'print the full JSON response')
174
+ .action(async (id, _o, cmd) => {
175
+ auth.requireAuth();
176
+ const data = await call('lucid.world.get', { params: { id } });
177
+ if (data === null) return;
178
+ if (wantJson(cmd)) { console.log(JSON.stringify(data, null, 2)); return; }
179
+ const e = data.entity;
180
+ const p = e.provenance || {};
181
+ console.log(`${describeEntity(e)}`);
182
+ console.log(` ${e.kind}, ${standingOf(e)}${e.priority ? `, priority ${e.priority}` : ''}${e.deadline ? `, by ${e.deadline}` : ''}`);
183
+ if (e.success_criteria) console.log(` Done when: ${e.success_criteria}`);
184
+ console.log(` From: ${p.source_type || '?'} (${p.source_ref || '?'}) at ${p.observed_at || '?'}`);
185
+ console.log(` ${e.lifecycle}, version ${e.version}, ${e.scope && e.scope.type === 'org' ? 'shared with the org' : 'private to you'}`);
186
+ });
187
+
188
+ goals
189
+ .command('history <id>')
190
+ .description('Every version of an entity: who changed it and how')
191
+ .option('--json', 'print the full JSON response')
192
+ .action(async (id, _o, cmd) => {
193
+ auth.requireAuth();
194
+ const data = await call('lucid.world.history', { params: { id } });
195
+ if (data === null) return;
196
+ if (wantJson(cmd)) { console.log(JSON.stringify(data, null, 2)); return; }
197
+ formatOutput((data.history || []).map((h) => ({ V: h.version, WHAT: h.op, BY: `${h.actor_kind}${h.actor_id ? ` ${h.actor_id}` : ''}`, AT: h.at, TEXT: describeEntity(h.entity) })), {
198
+ format: program.opts().output, columns: ['V', 'WHAT', 'BY', 'AT', 'TEXT'], emptyMessage: 'No history.', raw: data.history,
199
+ });
200
+ });
201
+
202
+ goals
203
+ .command('change <id> <pairs...>')
204
+ .description('Correct an entity, e.g. quox lucid goals change wm_x outcome="Orders by the 20th" priority=high')
205
+ .option('--json', 'print the full JSON response')
206
+ .action(async (id, pairs, _o, cmd) => {
207
+ auth.requireAuth();
208
+ let patch;
209
+ try { patch = parsePairs(pairs); } catch (e) { console.error(e.message); process.exitCode = 1; return; }
210
+ const data = await call('lucid.world.amend', { params: { id }, body: { patch } });
211
+ if (data === null) return;
212
+ if (wantJson(cmd)) { console.log(JSON.stringify(data, null, 2)); return; }
213
+ printSuccess(`Changed (version ${data.entity.version}): ${describeEntity(data.entity)}`);
214
+ });
215
+
216
+ for (const [verb, key, done] of [['confirm', 'lucid.world.confirm', 'Confirmed'], ['archive', 'lucid.world.archive', 'Archived'], ['restore', 'lucid.world.restore', 'Restored']]) {
217
+ goals
218
+ .command(`${verb} <id>`)
219
+ .description({ confirm: 'Accept something an agent proposed, so LUCID treats it as yours', archive: 'Set aside (kept, with history)', restore: 'Bring back an archived entity' }[verb])
220
+ .option('--json', 'print the full JSON response')
221
+ .action(async (id, _o, cmd) => {
222
+ auth.requireAuth();
223
+ const data = await call(key, { params: { id }, body: {} });
224
+ if (data === null) return;
225
+ if (wantJson(cmd)) { console.log(JSON.stringify(data, null, 2)); return; }
226
+ printSuccess(`${done}: ${describeEntity(data.entity)}`);
227
+ });
228
+ }
229
+
230
+ goals
231
+ .command('export')
232
+ .description('Everything LUCID holds for you, with full history, as JSON')
233
+ .option('--out <file>', 'write to a file instead of stdout')
234
+ .action(async (options) => {
235
+ auth.requireAuth();
236
+ const data = await call('lucid.world.export');
237
+ if (data === null) return;
238
+ const text = JSON.stringify(data, null, 2);
239
+ if (options.out) { fs.writeFileSync(options.out, `${text}\n`, { mode: 0o600 }); printSuccess(`Exported ${(data.entities || []).length} entities to ${options.out}`); return; }
240
+ console.log(text);
241
+ });
242
+
243
+ goals
244
+ .command('erase')
245
+ .description('Delete your personal World Model and its history (shared org entities stay)')
246
+ .option('--yes', 'skip the confirmation prompt')
247
+ .action(async (options) => {
248
+ auth.requireAuth();
249
+ const ok = await confirmDestructive(program, { force: options.yes === true }, 'Erase every personal goal, initiative and decision LUCID holds for you, with their history?');
250
+ if (!ok) return;
251
+ const data = await call('lucid.world.erase', { body: { confirm: 'erase' } });
252
+ if (data === null) return;
253
+ printSuccess(`Erased ${data.erased} personal ${data.erased === 1 ? 'entity' : 'entities'}.`);
254
+ });
255
+ }
256
+
257
+ /** Plain standing for an insight. */
258
+ export function insightStanding(i) {
259
+ if (i.status === 'retracted') return 'resolved';
260
+ if (i.status === 'supported') return 'you linked it';
261
+ return 'a guess, not linked';
262
+ }
263
+
264
+ /**
265
+ * `quox lucid insights` (LUCID-INTELLIGENCE LI-2, SHADOW): what LUCID would tie to your goals.
266
+ * Nothing here reaches you on its own yet; this is how to look at it and compare it with
267
+ * today's brief before LI-3 lets it shape what LUCID says.
268
+ */
269
+ export function registerLucidInsightsCommands(lu, program, ctx) {
270
+ const { client, auth } = ctx;
271
+ const wantJson = (cmd) => {
272
+ const o = cmd.optsWithGlobals ? cmd.optsWithGlobals() : cmd.opts();
273
+ return Boolean(o.json) || program.opts().output === 'json';
274
+ };
275
+ async function get(key, { params, query } = {}) {
276
+ const ep = resolveEndpoint(key, params);
277
+ try {
278
+ return await client.get(ep.path, { service: ep.service, query: { org_id: resolveOrgId(auth, program), ...query } });
279
+ } catch (err) {
280
+ const status = err && (err.status || err.statusCode);
281
+ if (status) {
282
+ console.error(`LUCID insights request failed: HTTP ${status}${err.message ? ` ${err.message}` : ''}`);
283
+ process.exitCode = 1;
284
+ return null;
285
+ }
286
+ throw err;
287
+ }
288
+ }
289
+
290
+ const ins = lu
291
+ .command('insights')
292
+ .description('SHADOW: what LUCID would tie to your goals, compared with what it tells you today')
293
+ .option('--retracted', 'include resolved ones')
294
+ .option('--json', 'print the full JSON response')
295
+ .action(async (options, cmd) => {
296
+ auth.requireAuth();
297
+ const data = await get('lucid.insights.list', { query: { retracted: options.retracted ? '1' : undefined } });
298
+ if (data === null) return;
299
+ if (wantJson(cmd)) { console.log(JSON.stringify(data, null, 2)); return; }
300
+ const list = data.insights || [];
301
+ // The shadow comparison: today's brief beside the goal-linked view.
302
+ const sit = await get('lucid.situation', { query: { since: 'last', surface: 'cli' } });
303
+ const needs = sit && sit.situation ? (sit.situation.needs_you || []).length : null;
304
+ const tied = new Set(list.filter((i) => i.status !== 'retracted').map((i) => i.subject));
305
+ printEvent(glyphLine('lucid', 'insights', { result: `${list.length} shadow · ${needs === null ? '?' : needs} in today's brief` }).replace('Ⓠ', '◉'));
306
+ if (needs !== null) console.log(`Today LUCID says ${needs} ${needs === 1 ? 'thing needs' : 'things need'} you. ${tied.size} of what it sees ${tied.size === 1 ? 'touches' : 'touch'} a goal you set. (Shadow: this does not change what LUCID says yet.)\n`);
307
+ formatOutput(list.map((i) => ({
308
+ ID: i.id,
309
+ WHAT: i.headline,
310
+ WHY: (i.observations && i.observations[0] && i.observations[0].text) || '',
311
+ PRIORITY: (i.priority && i.priority.band) || '',
312
+ LINK: insightStanding(i),
313
+ })), {
314
+ format: program.opts().output,
315
+ columns: ['ID', 'WHAT', 'WHY', 'PRIORITY', 'LINK'],
316
+ emptyMessage: 'Nothing ties to your goals right now. (No goals yet? quox lucid goals add "<what you want to be true>". Link one to what it depends on: quox lucid goals change <id> watches=site:<name>)',
317
+ raw: list,
318
+ });
319
+ });
320
+
321
+ ins
322
+ .command('show <id>')
323
+ .description('One insight: what was seen, what LUCID makes of it, how sure, and the evidence')
324
+ .option('--json', 'print the full JSON response')
325
+ .action(async (id, _o, cmd) => {
326
+ auth.requireAuth();
327
+ const data = await get('lucid.insights.get', { params: { id } });
328
+ if (data === null) return;
329
+ if (wantJson(cmd)) { console.log(JSON.stringify(data, null, 2)); return; }
330
+ const i = data.insight;
331
+ console.log(i.headline);
332
+ for (const o of i.observations || []) console.log(` Seen: ${o.text} (evidence ${o.source_ref}, ${o.observed_at})`);
333
+ if (i.interpretation) console.log(` Reading: ${i.interpretation.text} (${i.interpretation.classification}, ${i.interpretation.confidence} confidence)`);
334
+ for (const l of (i.interpretation && i.interpretation.limitations) || []) console.log(` Limit: ${l}`);
335
+ if (i.priority) console.log(` Why it ranks ${i.priority.band}: ${i.priority.why}`);
336
+ console.log(` ${insightStanding(i)}${i.retracted_at ? `, resolved ${i.retracted_at}` : ''}`);
337
+ });
338
+
339
+ ins
340
+ .command('status')
341
+ .description("The shadow worker's health for your org: runs, skips, failures, last error")
342
+ .option('--json', 'print the full JSON response')
343
+ .action(async (_o, cmd) => {
344
+ auth.requireAuth();
345
+ const data = await get('lucid.insights.status');
346
+ if (data === null) return;
347
+ if (wantJson(cmd)) { console.log(JSON.stringify(data, null, 2)); return; }
348
+ const w = data.worker || {};
349
+ console.log(`Runs ${w.runs}, skipped (nothing new) ${w.skipped}, failures ${w.failures}${w.consecutive_failures ? ` (${w.consecutive_failures} in a row)` : ''}.`);
350
+ console.log(`Last run ${w.last_run_at || 'never'}: found ${w.last_found}, rejected for bad evidence ${w.last_rejected}.`);
351
+ if (w.last_error) console.log(`Last error: ${w.last_error}${w.next_run_at ? ` (next try ${w.next_run_at})` : ''}`);
352
+ });
353
+ }
354
+
355
+ /**
356
+ * `quox lucid brief` (LUCID-INTELLIGENCE LI-3): the short answer to "what's going on". Your goals
357
+ * first, then what waits on you, then what went wrong today, then what LUCID cannot see. Never
358
+ * moves a cursor. --spoken gives exactly what the phone says.
359
+ */
360
+ export function registerLucidBriefCommand(lu, program, ctx) {
361
+ const { client, auth } = ctx;
362
+ lu
363
+ .command('brief')
364
+ .description("The short brief: what matters now, in plain words (your goals first; never moves a cursor)")
365
+ .option('--spoken', 'exactly what the phone would say')
366
+ .option('--detail <n>', '1 headline only, 2 normal, 3 adds reasons and drill-down refs', '2')
367
+ .option('--json', 'print the full JSON response')
368
+ .action(async (options, cmd) => {
369
+ auth.requireAuth();
370
+ // `lucid --spoken` also exists on the parent, so read the merged options (commander hands it to the parent).
371
+ const all = cmd.optsWithGlobals ? cmd.optsWithGlobals() : options;
372
+ const spoken = Boolean(options.spoken || all.spoken);
373
+ const ep = resolveEndpoint('lucid.brief');
374
+ let data;
375
+ try {
376
+ data = await client.get(ep.path, { service: ep.service, query: { org_id: resolveOrgId(auth, program), surface: spoken ? 'voice' : 'cli', detail: options.detail } });
377
+ } catch (err) {
378
+ const status = err && (err.status || err.statusCode);
379
+ if (status) { console.error(`LUCID brief request failed: HTTP ${status}${err.message ? ` ${err.message}` : ''}`); process.exitCode = 1; return; }
380
+ throw err;
381
+ }
382
+ const o = cmd.optsWithGlobals ? cmd.optsWithGlobals() : cmd.opts();
383
+ if (o.json || program.opts().output === 'json') { console.log(JSON.stringify(data, null, 2)); return; }
384
+ const b = (data && data.brief) || {};
385
+ printEvent(glyphLine('lucid', 'brief', { result: `${(b.items || []).length} item${(b.items || []).length === 1 ? '' : 's'}${b.more_available ? ` · ${b.more_available} more` : ''}` }).replace('Ⓠ', '◉'));
386
+ console.log(b.text || '');
387
+ if (b.degraded && b.degraded.length) console.error(`(${b.degraded.join(', ')}: this brief is from the situation alone)`);
388
+ });
389
+ }