quoxcli 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (348) hide show
  1. package/LICENSE +35 -0
  2. package/README.md +142 -2
  3. package/bin/quox.js +292 -0
  4. package/bin/quoxon.js +8 -0
  5. package/lib/api/endpoints/admin.js +125 -0
  6. package/lib/api/endpoints/aee.js +38 -0
  7. package/lib/api/endpoints/agent-attach.js +126 -0
  8. package/lib/api/endpoints/agents.js +56 -0
  9. package/lib/api/endpoints/assistants.js +50 -0
  10. package/lib/api/endpoints/audit.js +14 -0
  11. package/lib/api/endpoints/auth.js +50 -0
  12. package/lib/api/endpoints/awareness.js +34 -0
  13. package/lib/api/endpoints/backup-schedules.js +20 -0
  14. package/lib/api/endpoints/backups.js +86 -0
  15. package/lib/api/endpoints/brain.js +140 -0
  16. package/lib/api/endpoints/budgets.js +62 -0
  17. package/lib/api/endpoints/calendarq.js +71 -0
  18. package/lib/api/endpoints/catalog.js +14 -0
  19. package/lib/api/endpoints/chat.js +54 -0
  20. package/lib/api/endpoints/consent.js +26 -0
  21. package/lib/api/endpoints/contactsq.js +66 -0
  22. package/lib/api/endpoints/conversations.js +32 -0
  23. package/lib/api/endpoints/custom-roles.js +92 -0
  24. package/lib/api/endpoints/divisions.js +56 -0
  25. package/lib/api/endpoints/domains.js +38 -0
  26. package/lib/api/endpoints/engagement.js +231 -0
  27. package/lib/api/endpoints/engine.js +11 -0
  28. package/lib/api/endpoints/entity.js +50 -0
  29. package/lib/api/endpoints/executions.js +32 -0
  30. package/lib/api/endpoints/file.js +20 -0
  31. package/lib/api/endpoints/files.js +26 -0
  32. package/lib/api/endpoints/github.js +21 -0
  33. package/lib/api/endpoints/governance.js +43 -0
  34. package/lib/api/endpoints/hat.js +36 -0
  35. package/lib/api/endpoints/inbox.js +32 -0
  36. package/lib/api/endpoints/inboxq.js +120 -0
  37. package/lib/api/endpoints/index.js +228 -0
  38. package/lib/api/endpoints/invites.js +19 -0
  39. package/lib/api/endpoints/llm-policy.js +43 -0
  40. package/lib/api/endpoints/lucid.js +89 -0
  41. package/lib/api/endpoints/members.js +32 -0
  42. package/lib/api/endpoints/memory.js +75 -0
  43. package/lib/api/endpoints/mfa.js +32 -0
  44. package/lib/api/endpoints/mon.js +178 -0
  45. package/lib/api/endpoints/notesq.js +43 -0
  46. package/lib/api/endpoints/objectives.js +62 -0
  47. package/lib/api/endpoints/orgs.js +38 -0
  48. package/lib/api/endpoints/plugins.js +134 -0
  49. package/lib/api/endpoints/powers.js +75 -0
  50. package/lib/api/endpoints/prompts.js +69 -0
  51. package/lib/api/endpoints/qlarity.js +122 -0
  52. package/lib/api/endpoints/quoxbox.js +55 -0
  53. package/lib/api/endpoints/quoxchat.js +54 -0
  54. package/lib/api/endpoints/quoxflow.js +32 -0
  55. package/lib/api/endpoints/quoxplan.js +114 -0
  56. package/lib/api/endpoints/quoxseo.js +108 -0
  57. package/lib/api/endpoints/reach.js +47 -0
  58. package/lib/api/endpoints/runs.js +38 -0
  59. package/lib/api/endpoints/safety.js +46 -0
  60. package/lib/api/endpoints/scim.js +46 -0
  61. package/lib/api/endpoints/scopes.js +14 -0
  62. package/lib/api/endpoints/security.js +69 -0
  63. package/lib/api/endpoints/setup.js +90 -0
  64. package/lib/api/endpoints/skills.js +180 -0
  65. package/lib/api/endpoints/sso.js +67 -0
  66. package/lib/api/endpoints/tags.js +32 -0
  67. package/lib/api/endpoints/teams.js +56 -0
  68. package/lib/api/endpoints/telephony.js +46 -0
  69. package/lib/api/endpoints/tg.js +56 -0
  70. package/lib/api/endpoints/tools.js +50 -0
  71. package/lib/api/endpoints/traces.js +157 -0
  72. package/lib/api/endpoints/triggers.js +70 -0
  73. package/lib/api/endpoints/user-library.js +41 -0
  74. package/lib/api/endpoints/users.js +20 -0
  75. package/lib/api/endpoints/v1.js +499 -0
  76. package/lib/api/endpoints/vault.js +192 -0
  77. package/lib/api/endpoints/voice.js +22 -0
  78. package/lib/api/endpoints/volt.js +84 -0
  79. package/lib/api/endpoints/ward.js +40 -0
  80. package/lib/api/endpoints/webhooks.js +46 -0
  81. package/lib/api/endpoints/workflows.js +162 -0
  82. package/lib/api/endpoints/x.js +24 -0
  83. package/lib/api/endpoints.js +15 -0
  84. package/lib/commands/admin.js +359 -0
  85. package/lib/commands/aee.js +99 -0
  86. package/lib/commands/agent.js +201 -0
  87. package/lib/commands/agentAttach.js +1440 -0
  88. package/lib/commands/approvals.js +47 -0
  89. package/lib/commands/assistant.js +114 -0
  90. package/lib/commands/auth.js +458 -0
  91. package/lib/commands/awareness.js +88 -0
  92. package/lib/commands/backup.js +373 -0
  93. package/lib/commands/brain.js +994 -0
  94. package/lib/commands/budget.js +241 -0
  95. package/lib/commands/build.js +156 -0
  96. package/lib/commands/calendar.js +216 -0
  97. package/lib/commands/capabilities.js +34 -0
  98. package/lib/commands/chat.js +240 -0
  99. package/lib/commands/claude.js +354 -0
  100. package/lib/commands/config-cmd.js +106 -0
  101. package/lib/commands/consent.js +84 -0
  102. package/lib/commands/contacts.js +967 -0
  103. package/lib/commands/contactsSelftest.js +132 -0
  104. package/lib/commands/conversation.js +74 -0
  105. package/lib/commands/department.js +75 -0
  106. package/lib/commands/dev.js +620 -0
  107. package/lib/commands/devResident.js +314 -0
  108. package/lib/commands/devSupervisor.js +1050 -0
  109. package/lib/commands/discord.js +96 -0
  110. package/lib/commands/doctor.js +181 -0
  111. package/lib/commands/domain.js +114 -0
  112. package/lib/commands/done.js +277 -0
  113. package/lib/commands/engagement.js +586 -0
  114. package/lib/commands/entity.js +119 -0
  115. package/lib/commands/facebook.js +212 -0
  116. package/lib/commands/file.js +128 -0
  117. package/lib/commands/fleet.js +882 -0
  118. package/lib/commands/governance.js +638 -0
  119. package/lib/commands/gripe.js +324 -0
  120. package/lib/commands/hat.js +205 -0
  121. package/lib/commands/helpers.js +79 -0
  122. package/lib/commands/inbox.js +122 -0
  123. package/lib/commands/instagram.js +264 -0
  124. package/lib/commands/integration.js +100 -0
  125. package/lib/commands/inventory.js +115 -0
  126. package/lib/commands/invites.js +45 -0
  127. package/lib/commands/licence.js +280 -0
  128. package/lib/commands/linkedin.js +178 -0
  129. package/lib/commands/llm-policy.js +117 -0
  130. package/lib/commands/llm.js +45 -0
  131. package/lib/commands/lucid.js +591 -0
  132. package/lib/commands/mail.js +531 -0
  133. package/lib/commands/memory.js +284 -0
  134. package/lib/commands/mon.js +514 -0
  135. package/lib/commands/monitoring.js +86 -0
  136. package/lib/commands/motion.js +22 -0
  137. package/lib/commands/notes.js +1246 -0
  138. package/lib/commands/notesBatch.js +160 -0
  139. package/lib/commands/notesBridges.js +56 -0
  140. package/lib/commands/notesLinks.js +60 -0
  141. package/lib/commands/notification.js +57 -0
  142. package/lib/commands/nucleus.js +92 -0
  143. package/lib/commands/objective.js +229 -0
  144. package/lib/commands/onboard.js +328 -0
  145. package/lib/commands/open.js +56 -0
  146. package/lib/commands/org.js +266 -0
  147. package/lib/commands/plan.js +906 -0
  148. package/lib/commands/plugin-doctor.js +1091 -0
  149. package/lib/commands/plugin.js +539 -0
  150. package/lib/commands/policy.js +78 -0
  151. package/lib/commands/powers.js +296 -0
  152. package/lib/commands/prompt.js +350 -0
  153. package/lib/commands/qlarity.js +690 -0
  154. package/lib/commands/quoxbox.js +270 -0
  155. package/lib/commands/quoxchat.js +266 -0
  156. package/lib/commands/quoxseo.js +340 -0
  157. package/lib/commands/reach.js +158 -0
  158. package/lib/commands/role.js +286 -0
  159. package/lib/commands/run.js +132 -0
  160. package/lib/commands/safety.js +107 -0
  161. package/lib/commands/scim.js +88 -0
  162. package/lib/commands/scope.js +58 -0
  163. package/lib/commands/security.js +157 -0
  164. package/lib/commands/service.js +89 -0
  165. package/lib/commands/settings.js +99 -0
  166. package/lib/commands/setup.js +543 -0
  167. package/lib/commands/skillManifest.js +113 -0
  168. package/lib/commands/skillRightsFormat.js +38 -0
  169. package/lib/commands/skills.js +644 -0
  170. package/lib/commands/slot.js +1198 -0
  171. package/lib/commands/sso.js +229 -0
  172. package/lib/commands/status.js +137 -0
  173. package/lib/commands/tag.js +91 -0
  174. package/lib/commands/team.js +140 -0
  175. package/lib/commands/tg.js +240 -0
  176. package/lib/commands/tiktok.js +218 -0
  177. package/lib/commands/tool.js +216 -0
  178. package/lib/commands/trace.js +525 -0
  179. package/lib/commands/trigger.js +128 -0
  180. package/lib/commands/vault.js +1424 -0
  181. package/lib/commands/voice.js +182 -0
  182. package/lib/commands/volt.js +528 -0
  183. package/lib/commands/watch.js +115 -0
  184. package/lib/commands/webhook.js +137 -0
  185. package/lib/commands/workflow.js +503 -0
  186. package/lib/commands/x.js +439 -0
  187. package/lib/core/actingAs.js +136 -0
  188. package/lib/core/aee.js +53 -0
  189. package/lib/core/agentAlias.js +163 -0
  190. package/lib/core/approvalContext.js +61 -0
  191. package/lib/core/auth.js +350 -0
  192. package/lib/core/banner.js +143 -0
  193. package/lib/core/brainPrime.js +90 -0
  194. package/lib/core/callerSource.js +129 -0
  195. package/lib/core/claudeLs.js +94 -0
  196. package/lib/core/client.js +584 -0
  197. package/lib/core/completions.js +163 -0
  198. package/lib/core/config.js +551 -0
  199. package/lib/core/confirm.js +37 -0
  200. package/lib/core/deeplinks.js +76 -0
  201. package/lib/core/doneEngineFetch.js +219 -0
  202. package/lib/core/errors.js +132 -0
  203. package/lib/core/global-flags.js +90 -0
  204. package/lib/core/governedSession.js +402 -0
  205. package/lib/core/lazyCommands.js +119 -0
  206. package/lib/core/liveSessionsReport.js +112 -0
  207. package/lib/core/org-override.js +52 -0
  208. package/lib/core/output.js +239 -0
  209. package/lib/core/prodEnv.js +7 -0
  210. package/lib/core/quoxglyph.js +158 -0
  211. package/lib/core/residentAlarm.js +282 -0
  212. package/lib/core/residentAuth.js +243 -0
  213. package/lib/core/residentPump.js +229 -0
  214. package/lib/core/residentSeed.js +104 -0
  215. package/lib/core/sessionHeartbeat.js +192 -0
  216. package/lib/core/sessionsOverview.js +160 -0
  217. package/lib/core/slotKeeper.js +282 -0
  218. package/lib/core/supervisorEngine.js +864 -0
  219. package/lib/core/systemdInstaller.js +256 -0
  220. package/lib/core/version.js +87 -0
  221. package/lib/core/websiteSession.js +148 -0
  222. package/lib/lucid/LucidIntro.jsx +105 -0
  223. package/lib/lucid/blind.js +71 -0
  224. package/lib/lucid/deck.js +32 -0
  225. package/lib/lucid/fmt.js +139 -0
  226. package/lib/lucid/sitrep.js +173 -0
  227. package/lib/plugins/loader.js +93 -0
  228. package/lib/quoxon/activity.js +104 -0
  229. package/lib/quoxon/api.js +473 -0
  230. package/lib/quoxon/app.js +1262 -0
  231. package/lib/quoxon/capabilities.js +75 -0
  232. package/lib/quoxon/commands.js +154 -0
  233. package/lib/quoxon/connect.js +467 -0
  234. package/lib/quoxon/connectCard.js +28 -0
  235. package/lib/quoxon/context.js +68 -0
  236. package/lib/quoxon/copy.js +33 -0
  237. package/lib/quoxon/demo.fixtures.js +24 -0
  238. package/lib/quoxon/demo.js +35 -0
  239. package/lib/quoxon/egg.js +36 -0
  240. package/lib/quoxon/events.js +458 -0
  241. package/lib/quoxon/gallery.js +223 -0
  242. package/lib/quoxon/indicator.js +193 -0
  243. package/lib/quoxon/intent.js +41 -0
  244. package/lib/quoxon/keys.js +121 -0
  245. package/lib/quoxon/main.js +244 -0
  246. package/lib/quoxon/markdown.js +317 -0
  247. package/lib/quoxon/model.js +205 -0
  248. package/lib/quoxon/motion/clock.js +69 -0
  249. package/lib/quoxon/motion/color.js +35 -0
  250. package/lib/quoxon/motion/frame.js +14 -0
  251. package/lib/quoxon/motion/reveal.js +54 -0
  252. package/lib/quoxon/motion/ring.js +79 -0
  253. package/lib/quoxon/objects/agent.js +61 -0
  254. package/lib/quoxon/objects/crystal.js +100 -0
  255. package/lib/quoxon/objects/fetch.js +170 -0
  256. package/lib/quoxon/objects/index.js +85 -0
  257. package/lib/quoxon/objects/ladder.js +71 -0
  258. package/lib/quoxon/objects/memory.js +44 -0
  259. package/lib/quoxon/objects/model.js +118 -0
  260. package/lib/quoxon/objects/proof.js +110 -0
  261. package/lib/quoxon/objects/skill.js +105 -0
  262. package/lib/quoxon/objects/view.js +76 -0
  263. package/lib/quoxon/objects/workflow.js +119 -0
  264. package/lib/quoxon/observers.js +42 -0
  265. package/lib/quoxon/onboarding/copy.js +235 -0
  266. package/lib/quoxon/onboarding/credential.js +95 -0
  267. package/lib/quoxon/onboarding/detect.js +22 -0
  268. package/lib/quoxon/onboarding/finish.js +119 -0
  269. package/lib/quoxon/onboarding/index.js +67 -0
  270. package/lib/quoxon/onboarding/installRunner.js +225 -0
  271. package/lib/quoxon/onboarding/intent.js +53 -0
  272. package/lib/quoxon/onboarding/journey.js +73 -0
  273. package/lib/quoxon/onboarding/preflight.js +142 -0
  274. package/lib/quoxon/onboarding/runner.js +58 -0
  275. package/lib/quoxon/onboarding/view.js +625 -0
  276. package/lib/quoxon/print.js +94 -0
  277. package/lib/quoxon/qart.js +379 -0
  278. package/lib/quoxon/qglyph.js +143 -0
  279. package/lib/quoxon/qhello.js +58 -0
  280. package/lib/quoxon/qmotion.js +111 -0
  281. package/lib/quoxon/render.js +648 -0
  282. package/lib/quoxon/roster.js +221 -0
  283. package/lib/quoxon/route.js +38 -0
  284. package/lib/quoxon/rules.js +130 -0
  285. package/lib/quoxon/session.js +68 -0
  286. package/lib/quoxon/skills.js +30 -0
  287. package/lib/quoxon/slash/compact.js +90 -0
  288. package/lib/quoxon/slash/ledger.js +83 -0
  289. package/lib/quoxon/slash/listView.js +28 -0
  290. package/lib/quoxon/slash/palette.js +104 -0
  291. package/lib/quoxon/slash/paletteController.js +59 -0
  292. package/lib/quoxon/slash/panels.js +101 -0
  293. package/lib/quoxon/slash/picker.js +38 -0
  294. package/lib/quoxon/slash/restart.js +144 -0
  295. package/lib/quoxon/slash/resume.js +59 -0
  296. package/lib/quoxon/slash/settings.js +50 -0
  297. package/lib/quoxon/sound/index.js +186 -0
  298. package/lib/quoxon/sound/mapping.js +119 -0
  299. package/lib/quoxon/sound/output.js +52 -0
  300. package/lib/quoxon/sound/synth.js +122 -0
  301. package/lib/quoxon/term.js +106 -0
  302. package/lib/quoxon/textBuffer.js +242 -0
  303. package/lib/quoxon/theme.js +253 -0
  304. package/lib/quoxon/transcript.js +150 -0
  305. package/lib/quoxon/web/qface.js +81 -0
  306. package/lib/quoxon/welcome.js +52 -0
  307. package/lib/tui/app.jsx +289 -0
  308. package/lib/tui/components/Breadcrumb.jsx +42 -0
  309. package/lib/tui/components/ChatInput.jsx +68 -0
  310. package/lib/tui/components/Confirm.jsx +32 -0
  311. package/lib/tui/components/DataTable.jsx +270 -0
  312. package/lib/tui/components/DetailPanel.jsx +241 -0
  313. package/lib/tui/components/HelpOverlay.jsx +67 -0
  314. package/lib/tui/components/Layout.jsx +129 -0
  315. package/lib/tui/components/Palette.jsx +141 -0
  316. package/lib/tui/components/SearchInput.jsx +125 -0
  317. package/lib/tui/components/Sparkline.jsx +55 -0
  318. package/lib/tui/components/StatusBar.jsx +133 -0
  319. package/lib/tui/components/Toast.jsx +52 -0
  320. package/lib/tui/hooks/useApi.js +44 -0
  321. package/lib/tui/hooks/useHealth.js +90 -0
  322. package/lib/tui/hooks/usePolledApi.js +86 -0
  323. package/lib/tui/hooks/useTerminalSize.js +46 -0
  324. package/lib/tui/index.js +56 -0
  325. package/lib/tui/intro/Intro.jsx +332 -0
  326. package/lib/tui/jsx-loader.js +32 -0
  327. package/lib/tui/keys.js +87 -0
  328. package/lib/tui/registry.js +49 -0
  329. package/lib/tui/theme.js +103 -0
  330. package/lib/tui/views/AdminView.jsx +398 -0
  331. package/lib/tui/views/AgentView.jsx +204 -0
  332. package/lib/tui/views/BackupView.jsx +225 -0
  333. package/lib/tui/views/BrainView.jsx +223 -0
  334. package/lib/tui/views/ChatView.jsx +468 -0
  335. package/lib/tui/views/FilesView.jsx +218 -0
  336. package/lib/tui/views/FleetView.jsx +234 -0
  337. package/lib/tui/views/GovernanceView.jsx +453 -0
  338. package/lib/tui/views/HealthView.jsx +216 -0
  339. package/lib/tui/views/InboxView.jsx +228 -0
  340. package/lib/tui/views/LucidDeck.jsx +727 -0
  341. package/lib/tui/views/MainMenu.jsx +123 -0
  342. package/lib/tui/views/MemoryView.jsx +377 -0
  343. package/lib/tui/views/ObjectivesView.jsx +230 -0
  344. package/lib/tui/views/OrgView.jsx +229 -0
  345. package/lib/tui/views/ToolView.jsx +221 -0
  346. package/lib/tui/views/VaultView.jsx +422 -0
  347. package/lib/tui/views/WorkflowView.jsx +329 -0
  348. package/package.json +72 -4
@@ -0,0 +1,132 @@
1
+ /**
2
+ * `quox contacts selftest [--agent]`: drives the ContactsQ READ path against a dedicated demo
3
+ * contact and prints a pass/fail table. Never dials, sends or leases context (those file
4
+ * approvals). With --agent the same checks run through the attached agent's tool scope
5
+ * (POST /api/v1/tools/execute with the attach token, exactly like `quox agent exec`).
6
+ */
7
+
8
+ import { QuoxApiError, QuoxError } from '../core/errors.js';
9
+ import { line as glyphLine, printEvent } from '../core/quoxglyph.js';
10
+ import { resolveEndpoint } from '../api/endpoints.js';
11
+ import { ensureFreshAttachToken, execResultFailed } from './agentAttach.js';
12
+
13
+ export const DEMO_NAME = 'ContactsQ Selftest (demo)';
14
+ export const DEMO_PHONE = '+44 7911 123000';
15
+ export const DEMO_EMAIL = 'selftest@example.com';
16
+ export const DEMO_TAG = 'selftest';
17
+ const AGENT_ID = 'contactsq';
18
+
19
+ const errText = (e) => (e instanceof QuoxApiError ? `${e.status} ${(e.body && e.body.error) || e.message}` : (e && e.message) || String(e));
20
+
21
+ /** Find the demo contact or create it (human path). Idempotent. */
22
+ export async function ensureDemoContact(client) {
23
+ const list = resolveEndpoint('contactsq.contacts.list');
24
+ const found = await client.get(list.path, { service: list.service, query: { q: 'Selftest', limit: 50 } });
25
+ const hit = (found.contacts || []).find((c) => c.display_name === DEMO_NAME);
26
+ if (hit) return { id: hit.id, created: false };
27
+ const create = resolveEndpoint('contactsq.contacts.create');
28
+ const made = await client.post(create.path, {
29
+ service: create.service,
30
+ body: { display_name: DEMO_NAME, identifiers: [{ channel: 'phone', value: DEMO_PHONE, is_primary: true }, { channel: 'email', value: DEMO_EMAIL, is_primary: true }] },
31
+ });
32
+ const id = made.contact && made.contact.id;
33
+ if (!id) throw new QuoxError('Could not create the selftest demo contact.', 1);
34
+ const upd = resolveEndpoint('contactsq.contacts.update', { id });
35
+ // Tagging is a human edit: an attached agent creates the demo contact but may not tag it (403 human_required).
36
+ // The tag is a convenience for people browsing the book, so a refusal here never fails the selftest.
37
+ try {
38
+ await client.put(upd.path, { service: upd.service, body: { tags: [DEMO_TAG] } });
39
+ } catch (e) {
40
+ if (!(e && (e.status === 403 || e.statusCode === 403))) throw e;
41
+ }
42
+ return { id, created: true };
43
+ }
44
+
45
+ /** Direct (human) transport: the governed HTTP routes. */
46
+ function directOps(client, id) {
47
+ const get = (key, params, query) => { const ep = resolveEndpoint(key, params); return client.get(ep.path, { service: ep.service, query }); };
48
+ const post = (key, params, body) => { const ep = resolveEndpoint(key, params); return client.post(ep.path, { service: ep.service, body }); };
49
+ return {
50
+ search: (q) => get('contactsq.search', {}, { q }),
51
+ find: (q) => get('contactsq.contacts.list', {}, { q }),
52
+ resolve: (channel, value) => post('contactsq.resolve', {}, { channel, value }),
53
+ // No HTTP route for best_channel: the policy check is the same engine, per channel.
54
+ best_channel: async () => {
55
+ const out = [];
56
+ for (const channel of ['voice', 'email']) {
57
+ const r = await post('contactsq.policy', { id }, { purpose: 'service', channel });
58
+ out.push({ channel, decision: r.decision || (r.policy && r.policy.decision) || null });
59
+ }
60
+ return { alternatives: out };
61
+ },
62
+ timeline: () => get('contactsq.timeline', { id }, {}),
63
+ log: () => post('contactsq.touchpoints.add', { id }, { surface: 'manual', direction: 'internal', outcome: 'selftest' }),
64
+ brief: () => get('contactsq.brief', {}, {}),
65
+ };
66
+ }
67
+
68
+ /** Agent transport: the attached agent's own tool scope through /api/v1/tools/execute. */
69
+ async function agentOps(client, id, orgId) {
70
+ const token = await ensureFreshAttachToken(client, AGENT_ID);
71
+ const ep = resolveEndpoint('v1.tools.execute');
72
+ const exec = async (tool, input) => {
73
+ let data;
74
+ try {
75
+ data = await client.post(ep.path, { service: ep.service, noAuth: true, headers: { Authorization: `Bearer ${token}` }, body: { tool_name: tool, input, org_id: orgId } });
76
+ } catch (e) {
77
+ if (e instanceof QuoxApiError && (e.status === 401 || e.status === 403)) {
78
+ throw new Error(`${tool}: attach token for "${AGENT_ID}" refused (${e.status}). Re-attach with \`quox agent attach ${AGENT_ID}\`.`);
79
+ }
80
+ throw e;
81
+ }
82
+ // Tool envelopes vary: unwrap { result } if present, then require success.
83
+ const res = data && typeof data.result === 'object' && data.result ? data.result : data;
84
+ if (execResultFailed(res) || execResultFailed(data)) throw new Error(`${tool}: ${res.error || data.error || 'tool reported failure'}`);
85
+ return res;
86
+ };
87
+ return {
88
+ search: (q) => exec('contacts_search', { q }),
89
+ find: (q) => exec('contacts_find', { q }),
90
+ resolve: (channel, value) => exec('contacts_resolve', { channel, value }),
91
+ best_channel: () => exec('contacts_best_channel', { contact_id: id }),
92
+ timeline: () => exec('contacts_timeline', { contact_id: id }),
93
+ log: () => exec('contacts_log_touchpoint', { contact_id: id, surface: 'manual', direction: 'internal', outcome: 'selftest' }),
94
+ brief: () => exec('contacts_brief', {}),
95
+ };
96
+ }
97
+
98
+ const idsOf = (r) => [...(r.candidates || []), ...(r.contacts || [])].map((c) => c.id);
99
+
100
+ /** The checks, transport-agnostic. Each returns a short result string or throws. */
101
+ export function buildChecks(ops, id) {
102
+ return [
103
+ ['search', async () => { const r = await ops.search('ContactsQ Selftest'); if (!idsOf(r).includes(id)) throw new Error('demo contact not in results'); return `${idsOf(r).length} hit(s)`; }],
104
+ ['search (sound-alike)', async () => { const r = await ops.search('Selftast'); if (!idsOf(r).includes(id)) throw new Error('sound-alike did not reach the demo contact'); return 'Selftast -> Selftest'; }],
105
+ ['find', async () => { const r = await ops.find('Selftest'); if (!idsOf(r).includes(id)) throw new Error('demo contact not found'); return `${idsOf(r).length} hit(s)`; }],
106
+ ['resolve by email', async () => { const r = await ops.resolve('email', DEMO_EMAIL); if (!r.match || r.match.id !== id) throw new Error('email did not resolve to the demo contact'); return 'matched'; }],
107
+ ['best_channel', async () => { const r = await ops.best_channel(); const n = (r.alternatives || []).length; if (!n) throw new Error('no channel evaluation returned'); return r.channel ? `chose ${r.channel}` : `${n} evaluated`; }],
108
+ ['timeline', async () => { const r = await ops.timeline(); if (!Array.isArray(r.timeline)) throw new Error('no timeline array'); return `${r.timeline.length} entr(ies)`; }],
109
+ ['log touchpoint', async () => { const r = await ops.log(); const t = r.touchpoint || r; if (!t.id) throw new Error('no touchpoint id returned'); return t.id; }],
110
+ ['brief', async () => { const r = await ops.brief(); if (typeof r.candidates_to_review !== 'number' || !Array.isArray(r.sync)) throw new Error('brief shape wrong'); return `${r.candidates_to_review} candidates, ${r.duplicates} dupes`; }],
111
+ ];
112
+ }
113
+
114
+ export async function runSelftest({ client, auth, program, options, orgId, print = console.log }) {
115
+ auth.requireAuth();
116
+ const demo = await ensureDemoContact(client);
117
+ const mode = options.agent ? 'agent' : 'direct';
118
+ const ops = options.agent ? await agentOps(client, demo.id, orgId) : directOps(client, demo.id);
119
+ const rows = [];
120
+ for (const [name, fn] of buildChecks(ops, demo.id)) {
121
+ try { rows.push({ check: name, ok: true, detail: await fn() }); } catch (e) { rows.push({ check: name, ok: false, detail: errText(e) }); }
122
+ }
123
+ const failed = rows.filter((r) => !r.ok).length;
124
+ if (program.opts().output === 'json') {
125
+ print(JSON.stringify({ mode, demo_contact: demo.id, created: demo.created, passed: rows.length - failed, failed, checks: rows }, null, 2));
126
+ } else {
127
+ for (const r of rows) print(`${r.ok ? '✔' : '✖'} ${r.check.padEnd(22)} ${r.detail}`);
128
+ printEvent(glyphLine('contactsq', `selftest (${mode})`, { result: `${rows.length - failed}/${rows.length} passed`, status: failed ? 'fail' : 'ok' }));
129
+ }
130
+ if (failed) process.exitCode = 1;
131
+ return { rows, failed };
132
+ }
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Conversation commands
3
+ *
4
+ * quox conversation list|get|search|stats
5
+ */
6
+
7
+ import { formatOutput, formatDetail } from '../core/output.js';
8
+ import { resolveEndpoint } from '../api/endpoints.js';
9
+
10
+ export function registerConversationCommands(program, ctx) {
11
+ const { client, auth } = ctx;
12
+
13
+ const conv = program
14
+ .command('conversation')
15
+ .description('Manage conversations');
16
+
17
+ conv
18
+ .command('list')
19
+ .description('List conversations')
20
+ .option('--limit <n>', 'Max results', '20')
21
+ .option('--offset <n>', 'Skip results', '0')
22
+ .action(async (opts) => {
23
+ auth.requireAuth();
24
+ const query = { limit: opts.limit, offset: opts.offset };
25
+ const data = await client.get(resolveEndpoint('conversations.list').path, { service: resolveEndpoint('conversations.list').service, query });
26
+ const conversations = data.conversations || data || [];
27
+ formatOutput({
28
+ columns: ['id', 'title', 'messages', 'created_at'],
29
+ headers: ['ID', 'Title', 'Messages', 'Created'],
30
+ rows: conversations.map(c => ({
31
+ id: c.id,
32
+ title: (c.title || 'Untitled').slice(0, 50),
33
+ messages: c.message_count || c.messages?.length || 0,
34
+ created_at: c.created_at ? new Date(c.created_at).toLocaleDateString() : '-',
35
+ })),
36
+ }, { format: program.opts().output });
37
+ });
38
+
39
+ conv
40
+ .command('get')
41
+ .argument('<id>', 'conversation ID')
42
+ .description('Get conversation details')
43
+ .action(async (id) => {
44
+ auth.requireAuth();
45
+ const data = await client.get(resolveEndpoint('conversations.get', { id }).path, { service: resolveEndpoint('conversations.get', { id }).service });
46
+ formatDetail(data.conversation || data, { format: program.opts().output, title: 'Conversation' });
47
+ });
48
+
49
+ conv
50
+ .command('search')
51
+ .description('Search conversations')
52
+ .requiredOption('--query <q>', 'Search query')
53
+ .option('--limit <n>', 'Max results', '10')
54
+ .action(async (opts) => {
55
+ auth.requireAuth();
56
+ const query = { query: opts.query, limit: opts.limit };
57
+ const data = await client.get(resolveEndpoint('conversations.search.list').path, { service: resolveEndpoint('conversations.search.list').service, query });
58
+ const results = data.results || data || [];
59
+ formatOutput({
60
+ columns: ['id', 'title', 'score'],
61
+ headers: ['ID', 'Title', 'Score'],
62
+ rows: results.map(r => ({ id: r.id, title: (r.title || 'Untitled').slice(0, 50), score: r.score != null ? r.score.toFixed(2) : '-' })),
63
+ }, { format: program.opts().output });
64
+ });
65
+
66
+ conv
67
+ .command('stats')
68
+ .description('Conversation statistics')
69
+ .action(async () => {
70
+ auth.requireAuth();
71
+ const data = await client.get(resolveEndpoint('conversations.stats.list').path, { service: resolveEndpoint('conversations.stats.list').service });
72
+ formatDetail(data.stats || data, { format: program.opts().output, title: 'Conversation Stats' });
73
+ });
74
+ }
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Department commands: CRUD
3
+ */
4
+
5
+ import { formatOutput, printSuccess } from '../core/output.js';
6
+ import { resolveOrgId } from './helpers.js';
7
+ import { resolveEndpoint } from '../api/endpoints.js';
8
+
9
+ export function registerDepartmentCommands(program, ctx) {
10
+ const { client, auth } = ctx;
11
+
12
+ const dept = program
13
+ .command('department')
14
+ .description('Manage departments within divisions');
15
+
16
+ dept
17
+ .command('list')
18
+ .description('List departments')
19
+ .requiredOption('--division <divId>', 'division ID')
20
+ .action(async (options) => {
21
+ auth.requireAuth();
22
+ const orgId = resolveOrgId(auth, program);
23
+ const data = await client.get(resolveEndpoint('divisions.departments.list', { orgId, division: options.division }).path, { service: resolveEndpoint('divisions.departments.list', { orgId, division: options.division }).service });
24
+ formatOutput({
25
+ columns: ['id', 'name', 'description', 'created_at'],
26
+ headers: ['ID', 'Name', 'Description', 'Created'],
27
+ rows: data.departments || data,
28
+ }, { format: program.opts().output });
29
+ });
30
+
31
+ dept
32
+ .command('create')
33
+ .description('Create a department')
34
+ .requiredOption('--division <divId>', 'division ID')
35
+ .requiredOption('--name <name>', 'department name')
36
+ .option('--description <desc>', 'description')
37
+ .action(async (options) => {
38
+ auth.requireAuth();
39
+ const orgId = resolveOrgId(auth, program);
40
+ await client.post(resolveEndpoint('divisions.departments.create', { orgId, division: options.division }).path, {
41
+ service: resolveEndpoint('divisions.departments.create', { orgId, division: options.division }).service,
42
+ body: { name: options.name, description: options.description },
43
+ });
44
+ printSuccess(`Department "${options.name}" created.`);
45
+ });
46
+
47
+ dept
48
+ .command('update')
49
+ .description('Update a department')
50
+ .argument('<deptId>', 'department ID')
51
+ .requiredOption('--division <divId>', 'division ID')
52
+ .option('--name <name>', 'new name')
53
+ .option('--description <desc>', 'new description')
54
+ .action(async (deptId, options) => {
55
+ auth.requireAuth();
56
+ const orgId = resolveOrgId(auth, program);
57
+ const body = {};
58
+ if (options.name) body.name = options.name;
59
+ if (options.description) body.description = options.description;
60
+ await client.patch(resolveEndpoint('divisions.departments.update', { orgId, division: options.division, deptId }).path, { service: resolveEndpoint('divisions.departments.update', { orgId, division: options.division, deptId }).service, body });
61
+ printSuccess('Department updated.');
62
+ });
63
+
64
+ dept
65
+ .command('delete')
66
+ .description('Delete a department')
67
+ .argument('<deptId>', 'department ID')
68
+ .requiredOption('--division <divId>', 'division ID')
69
+ .action(async (deptId, options) => {
70
+ auth.requireAuth();
71
+ const orgId = resolveOrgId(auth, program);
72
+ await client.delete(resolveEndpoint('divisions.departments.delete', { orgId, division: options.division, deptId }).path, { service: resolveEndpoint('divisions.departments.delete', { orgId, division: options.division, deptId }).service });
73
+ printSuccess('Department deleted.');
74
+ });
75
+ }