@xyteai/cli 0.1.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 (251) hide show
  1. package/LICENSE +176 -0
  2. package/README.md +245 -0
  3. package/dist/bin/xyte-cli.d.ts +3 -0
  4. package/dist/bin/xyte-cli.d.ts.map +1 -0
  5. package/dist/bin/xyte-cli.js +18 -0
  6. package/dist/bin/xyte-cli.js.map +1 -0
  7. package/dist/cli/index.d.ts +24 -0
  8. package/dist/cli/index.d.ts.map +1 -0
  9. package/dist/cli/index.js +1185 -0
  10. package/dist/cli/index.js.map +1 -0
  11. package/dist/client/catalog.d.ts +6 -0
  12. package/dist/client/catalog.d.ts.map +1 -0
  13. package/dist/client/catalog.js +32 -0
  14. package/dist/client/catalog.js.map +1 -0
  15. package/dist/client/create-client.d.ts +3 -0
  16. package/dist/client/create-client.d.ts.map +1 -0
  17. package/dist/client/create-client.js +235 -0
  18. package/dist/client/create-client.js.map +1 -0
  19. package/dist/config/connectivity.d.ts +19 -0
  20. package/dist/config/connectivity.d.ts.map +1 -0
  21. package/dist/config/connectivity.js +166 -0
  22. package/dist/config/connectivity.js.map +1 -0
  23. package/dist/config/readiness.d.ts +32 -0
  24. package/dist/config/readiness.d.ts.map +1 -0
  25. package/dist/config/readiness.js +96 -0
  26. package/dist/config/readiness.js.map +1 -0
  27. package/dist/config/retry-policy.d.ts +16 -0
  28. package/dist/config/retry-policy.d.ts.map +1 -0
  29. package/dist/config/retry-policy.js +24 -0
  30. package/dist/config/retry-policy.js.map +1 -0
  31. package/dist/contracts/call-envelope.d.ts +74 -0
  32. package/dist/contracts/call-envelope.d.ts.map +1 -0
  33. package/dist/contracts/call-envelope.js +72 -0
  34. package/dist/contracts/call-envelope.js.map +1 -0
  35. package/dist/contracts/problem.d.ts +11 -0
  36. package/dist/contracts/problem.d.ts.map +1 -0
  37. package/dist/contracts/problem.js +55 -0
  38. package/dist/contracts/problem.js.map +1 -0
  39. package/dist/contracts/versions.d.ts +6 -0
  40. package/dist/contracts/versions.d.ts.map +1 -0
  41. package/dist/contracts/versions.js +9 -0
  42. package/dist/contracts/versions.js.map +1 -0
  43. package/dist/http/errors.d.ts +24 -0
  44. package/dist/http/errors.d.ts.map +1 -0
  45. package/dist/http/errors.js +39 -0
  46. package/dist/http/errors.js.map +1 -0
  47. package/dist/http/transport.d.ts +35 -0
  48. package/dist/http/transport.d.ts.map +1 -0
  49. package/dist/http/transport.js +129 -0
  50. package/dist/http/transport.js.map +1 -0
  51. package/dist/index.d.ts +10 -0
  52. package/dist/index.d.ts.map +1 -0
  53. package/dist/index.js +23 -0
  54. package/dist/index.js.map +1 -0
  55. package/dist/mcp/server.d.ts +12 -0
  56. package/dist/mcp/server.d.ts.map +1 -0
  57. package/dist/mcp/server.js +404 -0
  58. package/dist/mcp/server.js.map +1 -0
  59. package/dist/namespaces/device.d.ts +38 -0
  60. package/dist/namespaces/device.d.ts.map +1 -0
  61. package/dist/namespaces/device.js +36 -0
  62. package/dist/namespaces/device.js.map +1 -0
  63. package/dist/namespaces/organization.d.ts +27 -0
  64. package/dist/namespaces/organization.d.ts.map +1 -0
  65. package/dist/namespaces/organization.js +30 -0
  66. package/dist/namespaces/organization.js.map +1 -0
  67. package/dist/namespaces/partner.d.ts +18 -0
  68. package/dist/namespaces/partner.d.ts.map +1 -0
  69. package/dist/namespaces/partner.js +21 -0
  70. package/dist/namespaces/partner.js.map +1 -0
  71. package/dist/observability/logger.d.ts +3 -0
  72. package/dist/observability/logger.d.ts.map +1 -0
  73. package/dist/observability/logger.js +21 -0
  74. package/dist/observability/logger.js.map +1 -0
  75. package/dist/observability/tracing.d.ts +3 -0
  76. package/dist/observability/tracing.d.ts.map +1 -0
  77. package/dist/observability/tracing.js +26 -0
  78. package/dist/observability/tracing.js.map +1 -0
  79. package/dist/secure/key-slots.d.ts +8 -0
  80. package/dist/secure/key-slots.d.ts.map +1 -0
  81. package/dist/secure/key-slots.js +47 -0
  82. package/dist/secure/key-slots.js.map +1 -0
  83. package/dist/secure/keychain.d.ts +20 -0
  84. package/dist/secure/keychain.d.ts.map +1 -0
  85. package/dist/secure/keychain.js +170 -0
  86. package/dist/secure/keychain.js.map +1 -0
  87. package/dist/secure/profile-store.d.ts +66 -0
  88. package/dist/secure/profile-store.d.ts.map +1 -0
  89. package/dist/secure/profile-store.js +309 -0
  90. package/dist/secure/profile-store.js.map +1 -0
  91. package/dist/spec/public-endpoints.json +1175 -0
  92. package/dist/tui/animation.d.ts +12 -0
  93. package/dist/tui/animation.d.ts.map +1 -0
  94. package/dist/tui/animation.js +41 -0
  95. package/dist/tui/animation.js.map +1 -0
  96. package/dist/tui/app.d.ts +27 -0
  97. package/dist/tui/app.d.ts.map +1 -0
  98. package/dist/tui/app.js +711 -0
  99. package/dist/tui/app.js.map +1 -0
  100. package/dist/tui/assets/logo.d.ts +5 -0
  101. package/dist/tui/assets/logo.d.ts.map +1 -0
  102. package/dist/tui/assets/logo.js +24 -0
  103. package/dist/tui/assets/logo.js.map +1 -0
  104. package/dist/tui/data-loaders.d.ts +33 -0
  105. package/dist/tui/data-loaders.d.ts.map +1 -0
  106. package/dist/tui/data-loaders.js +250 -0
  107. package/dist/tui/data-loaders.js.map +1 -0
  108. package/dist/tui/dispatch.d.ts +14 -0
  109. package/dist/tui/dispatch.d.ts.map +1 -0
  110. package/dist/tui/dispatch.js +44 -0
  111. package/dist/tui/dispatch.js.map +1 -0
  112. package/dist/tui/headless-renderer.d.ts +20 -0
  113. package/dist/tui/headless-renderer.d.ts.map +1 -0
  114. package/dist/tui/headless-renderer.js +598 -0
  115. package/dist/tui/headless-renderer.js.map +1 -0
  116. package/dist/tui/input-controller.d.ts +29 -0
  117. package/dist/tui/input-controller.d.ts.map +1 -0
  118. package/dist/tui/input-controller.js +76 -0
  119. package/dist/tui/input-controller.js.map +1 -0
  120. package/dist/tui/key-wizard.d.ts +29 -0
  121. package/dist/tui/key-wizard.d.ts.map +1 -0
  122. package/dist/tui/key-wizard.js +177 -0
  123. package/dist/tui/key-wizard.js.map +1 -0
  124. package/dist/tui/keymap.d.ts +9 -0
  125. package/dist/tui/keymap.d.ts.map +1 -0
  126. package/dist/tui/keymap.js +29 -0
  127. package/dist/tui/keymap.js.map +1 -0
  128. package/dist/tui/layout.d.ts +16 -0
  129. package/dist/tui/layout.d.ts.map +1 -0
  130. package/dist/tui/layout.js +99 -0
  131. package/dist/tui/layout.js.map +1 -0
  132. package/dist/tui/logger.d.ts +12 -0
  133. package/dist/tui/logger.d.ts.map +1 -0
  134. package/dist/tui/logger.js +83 -0
  135. package/dist/tui/logger.js.map +1 -0
  136. package/dist/tui/navigation.d.ts +26 -0
  137. package/dist/tui/navigation.d.ts.map +1 -0
  138. package/dist/tui/navigation.js +136 -0
  139. package/dist/tui/navigation.js.map +1 -0
  140. package/dist/tui/panes.d.ts +7 -0
  141. package/dist/tui/panes.d.ts.map +1 -0
  142. package/dist/tui/panes.js +34 -0
  143. package/dist/tui/panes.js.map +1 -0
  144. package/dist/tui/runtime.d.ts +34 -0
  145. package/dist/tui/runtime.d.ts.map +1 -0
  146. package/dist/tui/runtime.js +100 -0
  147. package/dist/tui/runtime.js.map +1 -0
  148. package/dist/tui/scene.d.ts +160 -0
  149. package/dist/tui/scene.d.ts.map +1 -0
  150. package/dist/tui/scene.js +424 -0
  151. package/dist/tui/scene.js.map +1 -0
  152. package/dist/tui/screens/config.d.ts +3 -0
  153. package/dist/tui/screens/config.d.ts.map +1 -0
  154. package/dist/tui/screens/config.js +406 -0
  155. package/dist/tui/screens/config.js.map +1 -0
  156. package/dist/tui/screens/dashboard.d.ts +3 -0
  157. package/dist/tui/screens/dashboard.d.ts.map +1 -0
  158. package/dist/tui/screens/dashboard.js +176 -0
  159. package/dist/tui/screens/dashboard.js.map +1 -0
  160. package/dist/tui/screens/devices.d.ts +3 -0
  161. package/dist/tui/screens/devices.d.ts.map +1 -0
  162. package/dist/tui/screens/devices.js +297 -0
  163. package/dist/tui/screens/devices.js.map +1 -0
  164. package/dist/tui/screens/incidents.d.ts +4 -0
  165. package/dist/tui/screens/incidents.d.ts.map +1 -0
  166. package/dist/tui/screens/incidents.js +304 -0
  167. package/dist/tui/screens/incidents.js.map +1 -0
  168. package/dist/tui/screens/setup.d.ts +3 -0
  169. package/dist/tui/screens/setup.d.ts.map +1 -0
  170. package/dist/tui/screens/setup.js +299 -0
  171. package/dist/tui/screens/setup.js.map +1 -0
  172. package/dist/tui/screens/spaces.d.ts +7 -0
  173. package/dist/tui/screens/spaces.d.ts.map +1 -0
  174. package/dist/tui/screens/spaces.js +422 -0
  175. package/dist/tui/screens/spaces.js.map +1 -0
  176. package/dist/tui/screens/tickets.d.ts +9 -0
  177. package/dist/tui/screens/tickets.d.ts.map +1 -0
  178. package/dist/tui/screens/tickets.js +418 -0
  179. package/dist/tui/screens/tickets.js.map +1 -0
  180. package/dist/tui/serialize.d.ts +31 -0
  181. package/dist/tui/serialize.d.ts.map +1 -0
  182. package/dist/tui/serialize.js +183 -0
  183. package/dist/tui/serialize.js.map +1 -0
  184. package/dist/tui/table-format.d.ts +11 -0
  185. package/dist/tui/table-format.d.ts.map +1 -0
  186. package/dist/tui/table-format.js +77 -0
  187. package/dist/tui/table-format.js.map +1 -0
  188. package/dist/tui/tabs.d.ts +4 -0
  189. package/dist/tui/tabs.d.ts.map +1 -0
  190. package/dist/tui/tabs.js +13 -0
  191. package/dist/tui/tabs.js.map +1 -0
  192. package/dist/tui/types.d.ts +37 -0
  193. package/dist/tui/types.d.ts.map +1 -0
  194. package/dist/tui/types.js +3 -0
  195. package/dist/tui/types.js.map +1 -0
  196. package/dist/types/client.d.ts +54 -0
  197. package/dist/types/client.d.ts.map +1 -0
  198. package/dist/types/client.js +3 -0
  199. package/dist/types/client.js.map +1 -0
  200. package/dist/types/endpoints.d.ts +29 -0
  201. package/dist/types/endpoints.d.ts.map +1 -0
  202. package/dist/types/endpoints.js +3 -0
  203. package/dist/types/endpoints.js.map +1 -0
  204. package/dist/types/profile.d.ts +29 -0
  205. package/dist/types/profile.d.ts.map +1 -0
  206. package/dist/types/profile.js +3 -0
  207. package/dist/types/profile.js.map +1 -0
  208. package/dist/utils/config-dir.d.ts +2 -0
  209. package/dist/utils/config-dir.d.ts.map +1 -0
  210. package/dist/utils/config-dir.js +23 -0
  211. package/dist/utils/config-dir.js.map +1 -0
  212. package/dist/utils/error-format.d.ts +4 -0
  213. package/dist/utils/error-format.d.ts.map +1 -0
  214. package/dist/utils/error-format.js +34 -0
  215. package/dist/utils/error-format.js.map +1 -0
  216. package/dist/utils/install-skills.d.ts +38 -0
  217. package/dist/utils/install-skills.d.ts.map +1 -0
  218. package/dist/utils/install-skills.js +117 -0
  219. package/dist/utils/install-skills.js.map +1 -0
  220. package/dist/utils/json-output.d.ts +6 -0
  221. package/dist/utils/json-output.d.ts.map +1 -0
  222. package/dist/utils/json-output.js +30 -0
  223. package/dist/utils/json-output.js.map +1 -0
  224. package/dist/utils/json.d.ts +4 -0
  225. package/dist/utils/json.d.ts.map +1 -0
  226. package/dist/utils/json.js +36 -0
  227. package/dist/utils/json.js.map +1 -0
  228. package/dist/utils/version.d.ts +2 -0
  229. package/dist/utils/version.d.ts.map +1 -0
  230. package/dist/utils/version.js +28 -0
  231. package/dist/utils/version.js.map +1 -0
  232. package/dist/workflows/fleet-insights.d.ts +122 -0
  233. package/dist/workflows/fleet-insights.d.ts.map +1 -0
  234. package/dist/workflows/fleet-insights.js +938 -0
  235. package/dist/workflows/fleet-insights.js.map +1 -0
  236. package/docs/schemas/call-envelope.v1.schema.json +140 -0
  237. package/docs/schemas/headless-frame.v1.schema.json +159 -0
  238. package/docs/schemas/inspect-deep-dive.v1.schema.json +251 -0
  239. package/docs/schemas/inspect-fleet.v1.schema.json +111 -0
  240. package/docs/schemas/report.v1.schema.json +39 -0
  241. package/package.json +75 -0
  242. package/skills/xyte-cli/SKILL.md +181 -0
  243. package/skills/xyte-cli/agents/openai.yaml +4 -0
  244. package/skills/xyte-cli/references/endpoints.md +106 -0
  245. package/skills/xyte-cli/references/headless-contract.md +96 -0
  246. package/skills/xyte-cli/references/tui-flows.md +126 -0
  247. package/skills/xyte-cli/scripts/check_headless.sh +83 -0
  248. package/skills/xyte-cli/scripts/endpoint_filters_report.sh +33 -0
  249. package/skills/xyte-cli/scripts/run_xyte_cli.sh +12 -0
  250. package/skills/xyte-cli/scripts/validate_agent_contracts.sh +72 -0
  251. package/skills/xyte-cli/scripts/validate_with_schema.js +30 -0
@@ -0,0 +1,1185 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createCli = createCli;
7
+ exports.runCli = runCli;
8
+ const promises_1 = require("node:readline/promises");
9
+ const node_fs_1 = require("node:fs");
10
+ const node_path_1 = require("node:path");
11
+ const node_path_2 = __importDefault(require("node:path"));
12
+ const node_crypto_1 = require("node:crypto");
13
+ const commander_1 = require("commander");
14
+ const create_client_1 = require("../client/create-client");
15
+ const catalog_1 = require("../client/catalog");
16
+ const call_envelope_1 = require("../contracts/call-envelope");
17
+ const problem_1 = require("../contracts/problem");
18
+ const readiness_1 = require("../config/readiness");
19
+ const keychain_1 = require("../secure/keychain");
20
+ const key_slots_1 = require("../secure/key-slots");
21
+ const profile_store_1 = require("../secure/profile-store");
22
+ const json_1 = require("../utils/json");
23
+ const json_output_1 = require("../utils/json-output");
24
+ const version_1 = require("../utils/version");
25
+ const install_skills_1 = require("../utils/install-skills");
26
+ const app_1 = require("../tui/app");
27
+ const fleet_insights_1 = require("../workflows/fleet-insights");
28
+ const server_1 = require("../mcp/server");
29
+ const SIMPLE_SETUP_PROVIDER = 'xyte-org';
30
+ const SIMPLE_SETUP_SLOT_NAME = 'primary';
31
+ const SIMPLE_SETUP_DEFAULT_TENANT = 'default';
32
+ const SKILL_AGENTS = ['claude', 'copilot', 'codex'];
33
+ const SKILL_SCOPES = ['project', 'user', 'both'];
34
+ function printJson(stream, value, options = {}) {
35
+ (0, json_output_1.writeJsonLine)(stream, value, { strictJson: options.strictJson });
36
+ }
37
+ function parseProvider(value) {
38
+ const allowed = ['xyte-org', 'xyte-partner', 'xyte-device'];
39
+ if (!allowed.includes(value)) {
40
+ throw new Error(`Invalid provider: ${value}`);
41
+ }
42
+ return value;
43
+ }
44
+ function parseSkillInstallScope(value) {
45
+ if (!value) {
46
+ return undefined;
47
+ }
48
+ const normalized = value.trim().toLowerCase();
49
+ if (!SKILL_SCOPES.includes(normalized)) {
50
+ throw new Error(`Invalid scope: ${value}. Expected one of: ${SKILL_SCOPES.join(', ')}.`);
51
+ }
52
+ return normalized;
53
+ }
54
+ function parseSkillAgents(value) {
55
+ if (!value) {
56
+ return undefined;
57
+ }
58
+ const tokens = value
59
+ .split(',')
60
+ .map((item) => item.trim().toLowerCase())
61
+ .filter(Boolean);
62
+ if (!tokens.length) {
63
+ throw new Error('Invalid agents: empty value.');
64
+ }
65
+ if (tokens.includes('all')) {
66
+ if (tokens.length > 1) {
67
+ throw new Error('Invalid agents: "all" cannot be combined with specific agents.');
68
+ }
69
+ return [...SKILL_AGENTS];
70
+ }
71
+ const unknown = tokens.filter((item) => !SKILL_AGENTS.includes(item));
72
+ if (unknown.length > 0) {
73
+ throw new Error(`Invalid agents: ${unknown.join(', ')}. Expected "all" or ${SKILL_AGENTS.join(', ')}.`);
74
+ }
75
+ return SKILL_AGENTS.filter((agent) => tokens.includes(agent));
76
+ }
77
+ function formatInstallOutcome(outcome) {
78
+ const prefix = `${outcome.scope}/${outcome.agent}`;
79
+ if (outcome.status === 'failed') {
80
+ return `- ${prefix}: failed -> ${outcome.targetDir} (${outcome.error ?? 'unknown error'})`;
81
+ }
82
+ if (outcome.status === 'skipped') {
83
+ return `- ${prefix}: skipped -> ${outcome.targetDir} (already exists; use --force to overwrite)`;
84
+ }
85
+ return `- ${prefix}: ${outcome.status} -> ${outcome.targetDir}`;
86
+ }
87
+ function parsePathJson(value) {
88
+ const record = (0, json_1.parseJsonObject)(value);
89
+ const out = {};
90
+ for (const [key, item] of Object.entries(record)) {
91
+ if (typeof item === 'string' || typeof item === 'number') {
92
+ out[key] = item;
93
+ continue;
94
+ }
95
+ throw new Error(`Path parameter "${key}" must be string or number.`);
96
+ }
97
+ return out;
98
+ }
99
+ function parseQueryJson(value) {
100
+ const record = (0, json_1.parseJsonObject)(value);
101
+ const out = {};
102
+ for (const [key, item] of Object.entries(record)) {
103
+ if (item === null || item === undefined || typeof item === 'string' || typeof item === 'number' || typeof item === 'boolean') {
104
+ out[key] = item;
105
+ continue;
106
+ }
107
+ throw new Error(`Query parameter "${key}" must be scalar, null, or undefined.`);
108
+ }
109
+ return out;
110
+ }
111
+ function requiresWriteGuard(method) {
112
+ return !['GET', 'HEAD', 'OPTIONS'].includes(method.toUpperCase());
113
+ }
114
+ function requiresDestructiveGuard(method) {
115
+ return method.toUpperCase() === 'DELETE';
116
+ }
117
+ function formatReadinessText(readiness) {
118
+ const lines = [];
119
+ lines.push(`Readiness: ${readiness.state}`);
120
+ lines.push(`Tenant: ${readiness.tenantId ?? 'none'}`);
121
+ lines.push(`Connectivity: ${readiness.connectionState} (${readiness.connectivity.message})`);
122
+ lines.push('');
123
+ lines.push('Providers:');
124
+ for (const provider of readiness.providers) {
125
+ lines.push(`- ${provider.provider}: slots=${provider.slotCount}, active=${provider.activeSlotId ?? 'none'} (${provider.activeSlotName ?? 'n/a'}), hasSecret=${provider.hasActiveSecret}`);
126
+ }
127
+ if (readiness.missingItems.length) {
128
+ lines.push('');
129
+ lines.push('Missing items:');
130
+ readiness.missingItems.forEach((item) => lines.push(`- ${item}`));
131
+ }
132
+ if (readiness.recommendedActions.length) {
133
+ lines.push('');
134
+ lines.push('Recommended actions:');
135
+ readiness.recommendedActions.forEach((item) => lines.push(`- ${item}`));
136
+ }
137
+ return `${lines.join('\n')}\n`;
138
+ }
139
+ function formatSlotListText(slots) {
140
+ if (!slots.length) {
141
+ return 'No key slots found.\n';
142
+ }
143
+ const lines = ['tenant | provider | slotId | name | active | hasSecret | fingerprint | lastValidatedAt'];
144
+ for (const slot of slots) {
145
+ lines.push(`${slot.tenantId} | ${slot.provider} | ${slot.slotId} | ${slot.name} | ${slot.active} | ${slot.hasSecret} | ${slot.fingerprint} | ${slot.lastValidatedAt ?? 'n/a'}`);
146
+ }
147
+ return `${lines.join('\n')}\n`;
148
+ }
149
+ function resolveCommandFromPath(command, envPath = process.env.PATH ?? '') {
150
+ const pathEntries = envPath.split(node_path_1.delimiter).filter(Boolean);
151
+ const extensions = process.platform === 'win32'
152
+ ? (process.env.PATHEXT ?? '.EXE;.CMD;.BAT;.COM')
153
+ .split(';')
154
+ .filter(Boolean)
155
+ .map((ext) => ext.toLowerCase())
156
+ : [''];
157
+ for (const entry of pathEntries) {
158
+ for (const ext of extensions) {
159
+ const candidate = process.platform === 'win32' ? node_path_2.default.join(entry, `${command}${ext}`) : node_path_2.default.join(entry, command);
160
+ if (!(0, node_fs_1.existsSync)(candidate)) {
161
+ continue;
162
+ }
163
+ try {
164
+ (0, node_fs_1.accessSync)(candidate, node_fs_1.constants.X_OK);
165
+ }
166
+ catch {
167
+ continue;
168
+ }
169
+ return candidate;
170
+ }
171
+ }
172
+ return undefined;
173
+ }
174
+ function getRealPath(value) {
175
+ try {
176
+ return (0, node_fs_1.realpathSync)(value);
177
+ }
178
+ catch {
179
+ return node_path_2.default.resolve(value);
180
+ }
181
+ }
182
+ function runInstallDoctor() {
183
+ const expectedPath = node_path_2.default.resolve(__dirname, '../../dist/bin/xyte-cli.js');
184
+ const expectedRealPath = getRealPath(expectedPath);
185
+ const commandPath = resolveCommandFromPath('xyte-cli');
186
+ const commandOnPath = Boolean(commandPath);
187
+ const commandRealPath = commandPath ? getRealPath(commandPath) : undefined;
188
+ const sameTarget = Boolean(commandRealPath && commandRealPath === expectedRealPath);
189
+ const suggestions = [];
190
+ if (!commandOnPath) {
191
+ suggestions.push('Run: npm run install:global');
192
+ suggestions.push('Then verify from a different directory: xyte-cli --help');
193
+ }
194
+ else if (!sameTarget) {
195
+ suggestions.push(`xyte-cli currently points to: ${commandPath}`);
196
+ suggestions.push('Relink this repo globally: npm run reinstall:global');
197
+ }
198
+ else {
199
+ suggestions.push('Global command wiring looks correct.');
200
+ }
201
+ const status = !commandOnPath ? 'missing' : sameTarget ? 'ok' : 'mismatch';
202
+ return {
203
+ status,
204
+ commandOnPath,
205
+ commandPath,
206
+ commandRealPath,
207
+ expectedPath,
208
+ expectedRealPath,
209
+ sameTarget,
210
+ suggestions
211
+ };
212
+ }
213
+ async function promptValue(args) {
214
+ const rl = (0, promises_1.createInterface)({
215
+ input: process.stdin,
216
+ output: process.stdout
217
+ });
218
+ try {
219
+ const suffix = args.initial ? ` [${args.initial}]` : '';
220
+ const answer = (await rl.question(`${args.question}${suffix}: `)).trim();
221
+ return answer || args.initial || '';
222
+ }
223
+ finally {
224
+ rl.close();
225
+ }
226
+ }
227
+ function normalizeTenantId(value) {
228
+ const normalized = value
229
+ .trim()
230
+ .toLowerCase()
231
+ .replace(/\s+/g, '-')
232
+ .replace(/[^a-z0-9_-]/g, '-')
233
+ .replace(/-+/g, '-')
234
+ .replace(/^-|-$/g, '');
235
+ return normalized || SIMPLE_SETUP_DEFAULT_TENANT;
236
+ }
237
+ async function resolveSlotByRef(profileStore, tenantId, provider, slotRef) {
238
+ const slots = await profileStore.listKeySlots(tenantId, provider);
239
+ const slot = slots.find((item) => (0, key_slots_1.matchesSlotRef)(item, slotRef));
240
+ if (!slot) {
241
+ throw new Error(`Unknown slot "${slotRef}" for provider ${provider} in tenant ${tenantId}.`);
242
+ }
243
+ return slot;
244
+ }
245
+ async function collectSlotViews(args) {
246
+ const slots = await args.profileStore.listKeySlots(args.tenantId, args.provider);
247
+ const groupedProviders = new Set(slots.map((slot) => slot.provider));
248
+ const activeByProvider = new Map();
249
+ for (const provider of groupedProviders) {
250
+ const active = await args.profileStore.getActiveKeySlot(args.tenantId, provider);
251
+ activeByProvider.set(provider, active?.slotId);
252
+ }
253
+ const views = [];
254
+ for (const slot of slots) {
255
+ const hasSecret = Boolean(await args.keychain.getSlotSecret(args.tenantId, slot.provider, slot.slotId));
256
+ views.push({
257
+ tenantId: args.tenantId,
258
+ provider: slot.provider,
259
+ slotId: slot.slotId,
260
+ name: slot.name,
261
+ fingerprint: slot.fingerprint,
262
+ hasSecret,
263
+ active: activeByProvider.get(slot.provider) === slot.slotId,
264
+ lastValidatedAt: slot.lastValidatedAt
265
+ });
266
+ }
267
+ return views;
268
+ }
269
+ function requireKeyValue(value) {
270
+ const resolved = value ?? process.env.XYTE_CLI_KEY;
271
+ if (!resolved) {
272
+ throw new Error('Missing key value. Use --key or set XYTE_CLI_KEY environment variable.');
273
+ }
274
+ return resolved;
275
+ }
276
+ async function runSlotConnectivityTest(args) {
277
+ if (args.provider === 'xyte-org') {
278
+ const client = (0, create_client_1.createXyteClient)({
279
+ profileStore: args.profileStore,
280
+ tenantId: args.tenantId,
281
+ auth: { organization: args.key }
282
+ });
283
+ await client.organization.getOrganizationInfo({ tenantId: args.tenantId });
284
+ return {
285
+ strategy: 'organization.getOrganizationInfo',
286
+ ok: true
287
+ };
288
+ }
289
+ if (args.provider === 'xyte-partner') {
290
+ const client = (0, create_client_1.createXyteClient)({
291
+ profileStore: args.profileStore,
292
+ tenantId: args.tenantId,
293
+ auth: { partner: args.key }
294
+ });
295
+ await client.partner.getDevices({ tenantId: args.tenantId });
296
+ return {
297
+ strategy: 'partner.getDevices',
298
+ ok: true
299
+ };
300
+ }
301
+ if (args.provider === 'xyte-device') {
302
+ return {
303
+ strategy: 'local-only',
304
+ ok: true,
305
+ note: 'Device-key probe skipped (requires device-specific path context).'
306
+ };
307
+ }
308
+ return {
309
+ strategy: 'local-only',
310
+ ok: true,
311
+ note: 'Provider key presence verified locally.'
312
+ };
313
+ }
314
+ function createCli(runtime = {}) {
315
+ const stdout = runtime.stdout ?? process.stdout;
316
+ const stderr = runtime.stderr ?? process.stderr;
317
+ const prompt = runtime.promptValue ?? promptValue;
318
+ const isInteractive = runtime.isTTY ?? Boolean(process.stdin.isTTY);
319
+ const profileStore = runtime.profileStore ?? new profile_store_1.FileProfileStore();
320
+ const runTui = runtime.runTui ?? app_1.runTuiApp;
321
+ let keychainPromise;
322
+ const getKeychain = async () => {
323
+ if (runtime.keychain) {
324
+ return runtime.keychain;
325
+ }
326
+ if (!keychainPromise) {
327
+ keychainPromise = (0, keychain_1.createKeychainStore)();
328
+ }
329
+ return keychainPromise;
330
+ };
331
+ const withClient = async (tenantId, retry) => {
332
+ const keychain = await getKeychain();
333
+ return (0, create_client_1.createXyteClient)({
334
+ profileStore,
335
+ keychain,
336
+ tenantId,
337
+ retryAttempts: retry?.attempts,
338
+ retryBackoffMs: retry?.backoffMs
339
+ });
340
+ };
341
+ const runSimpleSetup = async (args) => {
342
+ await profileStore.upsertTenant({
343
+ id: args.tenantId,
344
+ name: args.tenantName
345
+ });
346
+ await profileStore.setActiveTenant(args.tenantId);
347
+ const keychain = await getKeychain();
348
+ const slots = await profileStore.listKeySlots(args.tenantId, SIMPLE_SETUP_PROVIDER);
349
+ const existing = slots.find((slot) => slot.name.toLowerCase() === SIMPLE_SETUP_SLOT_NAME);
350
+ const slot = existing
351
+ ? await profileStore.updateKeySlot(args.tenantId, SIMPLE_SETUP_PROVIDER, existing.slotId, {
352
+ fingerprint: (0, key_slots_1.makeKeyFingerprint)(args.keyValue)
353
+ })
354
+ : await profileStore.addKeySlot(args.tenantId, {
355
+ provider: SIMPLE_SETUP_PROVIDER,
356
+ name: SIMPLE_SETUP_SLOT_NAME,
357
+ fingerprint: (0, key_slots_1.makeKeyFingerprint)(args.keyValue)
358
+ });
359
+ await keychain.setSlotSecret(args.tenantId, SIMPLE_SETUP_PROVIDER, slot.slotId, args.keyValue);
360
+ if (args.setActive !== false) {
361
+ await profileStore.setActiveKeySlot(args.tenantId, SIMPLE_SETUP_PROVIDER, slot.slotId);
362
+ }
363
+ const client = await withClient(args.tenantId);
364
+ const readiness = await (0, readiness_1.evaluateReadiness)({
365
+ profileStore,
366
+ keychain,
367
+ tenantId: args.tenantId,
368
+ client,
369
+ checkConnectivity: true
370
+ });
371
+ return {
372
+ tenantId: args.tenantId,
373
+ provider: SIMPLE_SETUP_PROVIDER,
374
+ slot,
375
+ readiness
376
+ };
377
+ };
378
+ const program = new commander_1.Command();
379
+ program.name('xyte-cli').description('Xyte CLI + TUI').version((0, version_1.getCliVersion)());
380
+ program.option('--error-format <format>', 'text|json', 'text');
381
+ program
382
+ .command('install')
383
+ .description('Initialize workspace')
384
+ .option('--skills', 'install local agent skills')
385
+ .option('--target <path>', 'Workspace directory override')
386
+ .option('--scope <scope>', 'project|user|both')
387
+ .option('--agents <agents>', 'all|claude|copilot|codex[,..]')
388
+ .option('--force', 'Overwrite existing skill install')
389
+ .option('--no-setup', 'Skip guided setup after installing skills')
390
+ .action(async (options) => {
391
+ if (!options.skills) {
392
+ throw new Error('Use "xyte-cli install --skills" to install agent skills.');
393
+ }
394
+ let scope = parseSkillInstallScope(options.scope);
395
+ let agents = parseSkillAgents(options.agents);
396
+ if (isInteractive) {
397
+ if (!scope) {
398
+ scope = parseSkillInstallScope(await prompt({
399
+ question: 'Install scope (project|user|both)',
400
+ initial: 'project',
401
+ stdout
402
+ }));
403
+ }
404
+ if (!agents) {
405
+ agents = parseSkillAgents(await prompt({
406
+ question: 'Agents (all|claude,copilot,codex)',
407
+ initial: 'all',
408
+ stdout
409
+ }));
410
+ }
411
+ }
412
+ scope = scope ?? 'project';
413
+ agents = agents ?? [...SKILL_AGENTS];
414
+ const skillSource = node_path_2.default.resolve(__dirname, '../../skills/xyte-cli');
415
+ const result = await (0, install_skills_1.installSkills)({
416
+ skillName: 'xyte-cli',
417
+ sourceDir: skillSource,
418
+ scope,
419
+ agents,
420
+ targetWorkspace: options.target,
421
+ force: options.force === true
422
+ });
423
+ if (scope === 'project' || scope === 'both') {
424
+ stdout.write(`✅ Workspace target: \`${result.workspaceRoot}\`.\n`);
425
+ }
426
+ if (scope === 'user' || scope === 'both') {
427
+ stdout.write(`✅ User target: \`${result.homeRoot}\`.\n`);
428
+ }
429
+ stdout.write('Skill install summary:\n');
430
+ result.outcomes.forEach((outcome) => stdout.write(`${formatInstallOutcome(outcome)}\n`));
431
+ const failed = result.outcomes.filter((outcome) => outcome.status === 'failed');
432
+ if (failed.length > 0) {
433
+ throw new Error(`Skill installation failed for ${failed.length} target(s).`);
434
+ }
435
+ if (options.setup === false) {
436
+ return;
437
+ }
438
+ let keyValue = process.env.XYTE_CLI_KEY?.trim();
439
+ let tenantLabel = SIMPLE_SETUP_DEFAULT_TENANT;
440
+ if (isInteractive) {
441
+ keyValue = keyValue || (await prompt({ question: 'XYTE API key', stdout })).trim();
442
+ tenantLabel =
443
+ (await prompt({
444
+ question: 'Tenant label (optional)',
445
+ initial: tenantLabel,
446
+ stdout
447
+ })).trim() || SIMPLE_SETUP_DEFAULT_TENANT;
448
+ }
449
+ if (!keyValue) {
450
+ throw new Error('Missing API key. Set XYTE_CLI_KEY or re-run with --no-setup.');
451
+ }
452
+ const tenantId = normalizeTenantId(tenantLabel);
453
+ const setupResult = await runSimpleSetup({
454
+ tenantId,
455
+ tenantName: tenantLabel,
456
+ keyValue,
457
+ setActive: true
458
+ });
459
+ if (setupResult.readiness.state !== 'ready') {
460
+ throw new Error(`Setup did not complete: ${setupResult.readiness.connectivity.message || 'connectivity validation failed'}`);
461
+ }
462
+ stdout.write(`✅ Setup complete for tenant \`${tenantId}\`.\n`);
463
+ });
464
+ program.action(async () => {
465
+ const keychain = await getKeychain();
466
+ const readinessClient = await withClient(undefined);
467
+ const readiness = await (0, readiness_1.evaluateReadiness)({
468
+ profileStore,
469
+ keychain,
470
+ client: readinessClient,
471
+ checkConnectivity: true
472
+ });
473
+ if (readiness.state !== 'ready') {
474
+ if (!isInteractive) {
475
+ throw new Error('Setup required. Run: xyte-cli setup run --non-interactive --tenant default --key "$XYTE_CLI_KEY".');
476
+ }
477
+ const apiKey = await prompt({ question: 'XYTE API key', stdout });
478
+ if (!apiKey.trim()) {
479
+ throw new Error('API key is required to complete first-run setup.');
480
+ }
481
+ const tenantLabelInput = await prompt({
482
+ question: 'Tenant label (optional)',
483
+ initial: SIMPLE_SETUP_DEFAULT_TENANT,
484
+ stdout
485
+ });
486
+ const tenantLabel = tenantLabelInput.trim() || SIMPLE_SETUP_DEFAULT_TENANT;
487
+ const tenantId = normalizeTenantId(tenantLabel);
488
+ const setupResult = await runSimpleSetup({
489
+ tenantId,
490
+ tenantName: tenantLabel,
491
+ keyValue: apiKey.trim(),
492
+ setActive: true
493
+ });
494
+ if (setupResult.readiness.state !== 'ready') {
495
+ throw new Error(`Setup did not complete: ${setupResult.readiness.connectivity.message || 'connectivity validation failed'}`);
496
+ }
497
+ }
498
+ const activeTenantId = readiness.tenantId ?? (await profileStore.getData()).activeTenantId;
499
+ const keychainReady = await getKeychain();
500
+ const client = (0, create_client_1.createXyteClient)({
501
+ profileStore,
502
+ keychain: keychainReady,
503
+ tenantId: activeTenantId
504
+ });
505
+ await runTui({
506
+ client,
507
+ profileStore,
508
+ keychain: keychainReady,
509
+ initialScreen: 'dashboard',
510
+ headless: false,
511
+ tenantId: activeTenantId
512
+ });
513
+ });
514
+ const doctor = program.command('doctor').description('Runtime diagnostics');
515
+ doctor
516
+ .command('install')
517
+ .description('Check global xyte-cli command wiring')
518
+ .option('--format <format>', 'json|text', 'json')
519
+ .action((options) => {
520
+ const report = runInstallDoctor();
521
+ if ((options.format ?? 'json') === 'text') {
522
+ stdout.write([
523
+ `Status: ${report.status}`,
524
+ `Command on PATH: ${report.commandOnPath}`,
525
+ `Command path: ${report.commandPath ?? 'not found'}`,
526
+ `Command real path: ${report.commandRealPath ?? 'n/a'}`,
527
+ `Expected path: ${report.expectedPath}`,
528
+ `Expected real path: ${report.expectedRealPath}`,
529
+ `Same target: ${report.sameTarget}`,
530
+ '',
531
+ 'Suggestions:',
532
+ ...report.suggestions.map((item) => `- ${item}`)
533
+ ].join('\n') + '\n');
534
+ return;
535
+ }
536
+ printJson(stdout, report);
537
+ });
538
+ program
539
+ .command('list-endpoints')
540
+ .description('List endpoint keys')
541
+ .option('--tenant <tenantId>', 'Filter endpoints available for tenant credentials')
542
+ .action(async (options) => {
543
+ if (options.tenant) {
544
+ const client = await withClient(options.tenant);
545
+ printJson(stdout, await client.listTenantEndpoints(options.tenant));
546
+ return;
547
+ }
548
+ printJson(stdout, (0, catalog_1.listEndpoints)());
549
+ });
550
+ program
551
+ .command('describe-endpoint')
552
+ .argument('<key>', 'Endpoint key')
553
+ .description('Describe endpoint metadata')
554
+ .action((key) => {
555
+ printJson(stdout, (0, catalog_1.getEndpoint)(key));
556
+ });
557
+ program
558
+ .command('call')
559
+ .argument('<key>', 'Endpoint key')
560
+ .description('Call endpoint by key')
561
+ .option('--tenant <tenantId>', 'Tenant id')
562
+ .option('--path-json <json>', 'Path params JSON object')
563
+ .option('--query-json <json>', 'Query params JSON object')
564
+ .option('--body-json <json>', 'Body JSON object')
565
+ .option('--allow-write', 'Allow mutation endpoint invocation')
566
+ .option('--confirm <token>', 'Confirm token required for destructive operations')
567
+ .option('--output-mode <mode>', 'raw|envelope', 'raw')
568
+ .option('--strict-json', 'Fail on non-serializable output')
569
+ .action(async (key, options) => {
570
+ const endpoint = (0, catalog_1.getEndpoint)(key);
571
+ const method = endpoint.method.toUpperCase();
572
+ const outputMode = String(options.outputMode ?? 'raw');
573
+ if (!['raw', 'envelope'].includes(outputMode)) {
574
+ throw new Error(`Invalid output mode: ${outputMode}. Use raw|envelope.`);
575
+ }
576
+ const requestId = (0, node_crypto_1.randomUUID)();
577
+ const tenantId = options.tenant;
578
+ const path = parsePathJson(options.pathJson);
579
+ const query = parseQueryJson(options.queryJson);
580
+ const body = options.bodyJson ? JSON.parse(String(options.bodyJson)) : undefined;
581
+ const allowWrite = options.allowWrite === true;
582
+ const confirmToken = options.confirm;
583
+ const strictJson = options.strictJson === true;
584
+ try {
585
+ if (requiresWriteGuard(method) && !allowWrite) {
586
+ throw new Error(`Endpoint ${key} is a write operation (${method}). Re-run with --allow-write.`);
587
+ }
588
+ if (requiresDestructiveGuard(method) && confirmToken !== key) {
589
+ throw new Error(`Endpoint ${key} is destructive. Re-run with --confirm ${key}.`);
590
+ }
591
+ const client = await withClient(tenantId);
592
+ const result = await client.callWithMeta(key, {
593
+ requestId,
594
+ tenantId,
595
+ path,
596
+ query,
597
+ body
598
+ });
599
+ if (outputMode === 'envelope') {
600
+ const envelope = (0, call_envelope_1.buildCallEnvelope)({
601
+ requestId,
602
+ tenantId,
603
+ endpointKey: key,
604
+ method,
605
+ guard: {
606
+ allowWrite,
607
+ confirm: confirmToken
608
+ },
609
+ request: {
610
+ path,
611
+ query,
612
+ body
613
+ },
614
+ response: {
615
+ status: result.status,
616
+ durationMs: result.durationMs,
617
+ retryCount: result.retryCount,
618
+ data: result.data
619
+ }
620
+ });
621
+ printJson(stdout, envelope, { strictJson });
622
+ return;
623
+ }
624
+ printJson(stdout, result.data, { strictJson });
625
+ }
626
+ catch (error) {
627
+ if (outputMode !== 'envelope') {
628
+ throw error;
629
+ }
630
+ const envelope = (0, call_envelope_1.buildCallEnvelope)({
631
+ requestId,
632
+ tenantId,
633
+ endpointKey: key,
634
+ method,
635
+ guard: {
636
+ allowWrite,
637
+ confirm: confirmToken
638
+ },
639
+ request: {
640
+ path,
641
+ query,
642
+ body
643
+ },
644
+ error: (0, problem_1.toProblemDetails)(error, `/call/${key}`)
645
+ });
646
+ printJson(stdout, envelope, { strictJson });
647
+ process.exitCode = 1;
648
+ }
649
+ });
650
+ const inspect = program.command('inspect').description('Deterministic fleet insights');
651
+ inspect
652
+ .command('fleet')
653
+ .description('Build a fleet summary snapshot')
654
+ .requiredOption('--tenant <tenantId>', 'Tenant id')
655
+ .option('--format <format>', 'json|ascii', 'json')
656
+ .option('--strict-json', 'Fail on non-serializable output')
657
+ .action(async (options) => {
658
+ const format = options.format ?? 'json';
659
+ if (!['json', 'ascii'].includes(format)) {
660
+ throw new Error(`Invalid format: ${format}. Use json|ascii.`);
661
+ }
662
+ const client = await withClient(options.tenant);
663
+ const snapshot = await (0, fleet_insights_1.collectFleetSnapshot)(client, options.tenant);
664
+ const result = (0, fleet_insights_1.buildFleetInspect)(snapshot);
665
+ if (format === 'ascii') {
666
+ stdout.write(`${(0, fleet_insights_1.formatFleetInspectAscii)(result)}\n`);
667
+ return;
668
+ }
669
+ printJson(stdout, result, { strictJson: options.strictJson });
670
+ });
671
+ inspect
672
+ .command('deep-dive')
673
+ .description('Build deep-dive operational analytics')
674
+ .requiredOption('--tenant <tenantId>', 'Tenant id')
675
+ .option('--window <hours>', 'Window in hours', '24')
676
+ .option('--format <format>', 'json|ascii|markdown', 'json')
677
+ .option('--strict-json', 'Fail on non-serializable output')
678
+ .action(async (options) => {
679
+ const format = options.format ?? 'json';
680
+ if (!['json', 'ascii', 'markdown'].includes(format)) {
681
+ throw new Error(`Invalid format: ${format}. Use json|ascii|markdown.`);
682
+ }
683
+ const windowHours = Number.parseInt(options.window ?? '24', 10);
684
+ const client = await withClient(options.tenant);
685
+ const snapshot = await (0, fleet_insights_1.collectFleetSnapshot)(client, options.tenant);
686
+ const result = (0, fleet_insights_1.buildDeepDive)(snapshot, Number.isFinite(windowHours) ? windowHours : 24);
687
+ if (format === 'ascii') {
688
+ stdout.write(`${(0, fleet_insights_1.formatDeepDiveAscii)(result)}\n`);
689
+ return;
690
+ }
691
+ if (format === 'markdown') {
692
+ stdout.write(`${(0, fleet_insights_1.formatDeepDiveMarkdown)(result, false)}\n`);
693
+ return;
694
+ }
695
+ printJson(stdout, result, { strictJson: options.strictJson });
696
+ });
697
+ const report = program.command('report').description('Generate fleet findings reports');
698
+ report
699
+ .command('generate')
700
+ .description('Generate report from deep-dive JSON input')
701
+ .requiredOption('--tenant <tenantId>', 'Tenant id')
702
+ .requiredOption('--input <path>', 'Path to deep-dive JSON input')
703
+ .requiredOption('--out <path>', 'Output path')
704
+ .option('--format <format>', 'markdown|pdf', 'pdf')
705
+ .option('--include-sensitive', 'Include full ticket/device IDs in report')
706
+ .option('--strict-json', 'Fail on non-serializable output')
707
+ .action(async (options) => {
708
+ const raw = JSON.parse((0, node_fs_1.readFileSync)(node_path_2.default.resolve(options.input), 'utf8'));
709
+ const format = options.format ?? 'pdf';
710
+ if (!['markdown', 'pdf'].includes(format)) {
711
+ throw new Error(`Invalid format: ${format}. Use markdown|pdf.`);
712
+ }
713
+ if (raw.schemaVersion !== 'xyte.inspect.deep-dive.v1') {
714
+ throw new Error('Input JSON must be produced by `xyte-cli inspect deep-dive --format json`.');
715
+ }
716
+ if (raw.tenantId && raw.tenantId !== options.tenant) {
717
+ throw new Error(`Input tenant mismatch. Expected ${options.tenant}, got ${raw.tenantId}.`);
718
+ }
719
+ const generated = await (0, fleet_insights_1.generateFleetReport)({
720
+ deepDive: raw,
721
+ format: format,
722
+ outPath: options.out,
723
+ includeSensitive: options.includeSensitive === true
724
+ });
725
+ printJson(stdout, generated, { strictJson: options.strictJson });
726
+ });
727
+ const mcp = program.command('mcp').description('Model Context Protocol tools');
728
+ mcp
729
+ .command('serve')
730
+ .description('Run MCP server over stdio')
731
+ .action(async () => {
732
+ const keychain = await getKeychain();
733
+ const server = (0, server_1.createMcpServer)({
734
+ profileStore,
735
+ keychain
736
+ });
737
+ await server.start();
738
+ });
739
+ const tenant = program.command('tenant').description('Manage tenant profiles');
740
+ tenant
741
+ .command('add')
742
+ .argument('<tenantId>', 'Tenant id')
743
+ .description('Create or update a tenant profile')
744
+ .option('--name <name>', 'Display name')
745
+ .option('--hub-url <url>', 'Hub API base URL')
746
+ .option('--entry-url <url>', 'Entry API base URL')
747
+ .action(async (tenantId, options) => {
748
+ const tenantProfile = await profileStore.upsertTenant({
749
+ id: tenantId,
750
+ name: options.name,
751
+ hubBaseUrl: options.hubUrl,
752
+ entryBaseUrl: options.entryUrl
753
+ });
754
+ printJson(stdout, tenantProfile);
755
+ });
756
+ tenant
757
+ .command('list')
758
+ .description('List tenants')
759
+ .action(async () => {
760
+ const data = await profileStore.getData();
761
+ printJson(stdout, {
762
+ activeTenantId: data.activeTenantId,
763
+ tenants: data.tenants
764
+ });
765
+ });
766
+ tenant
767
+ .command('use')
768
+ .argument('<tenantId>', 'Tenant id to set active')
769
+ .description('Set active tenant')
770
+ .action(async (tenantId) => {
771
+ await profileStore.setActiveTenant(tenantId);
772
+ stdout.write(`Active tenant set to ${tenantId}\n`);
773
+ });
774
+ tenant
775
+ .command('remove')
776
+ .argument('<tenantId>', 'Tenant id')
777
+ .description('Remove tenant profile')
778
+ .action(async (tenantId) => {
779
+ await profileStore.removeTenant(tenantId);
780
+ stdout.write(`Removed tenant ${tenantId}\n`);
781
+ });
782
+ const profile = program.command('profile').description('Manage profile settings');
783
+ profile
784
+ .command('set-default')
785
+ .requiredOption('--tenant <tenantId>', 'Tenant id')
786
+ .description('Set active default tenant')
787
+ .action(async (options) => {
788
+ await profileStore.setActiveTenant(options.tenant);
789
+ stdout.write(`Default tenant set to ${options.tenant}\n`);
790
+ });
791
+ const auth = program.command('auth').description('Manage API keys in OS keychain');
792
+ const authKey = auth.command('key').description('Manage named key slots');
793
+ authKey
794
+ .command('add')
795
+ .requiredOption('--tenant <tenantId>', 'Tenant id')
796
+ .requiredOption('--provider <provider>', 'xyte-org|xyte-partner|xyte-device')
797
+ .requiredOption('--name <name>', 'Slot display name')
798
+ .option('--slot-id <slotId>', 'Optional explicit slot id')
799
+ .option('--key <value>', 'API key value')
800
+ .option('--set-active', 'Set as active slot for provider')
801
+ .action(async (options) => {
802
+ const provider = parseProvider(options.provider);
803
+ const value = requireKeyValue(options.key);
804
+ await profileStore.upsertTenant({ id: options.tenant });
805
+ const keychain = await getKeychain();
806
+ const slot = await profileStore.addKeySlot(options.tenant, {
807
+ provider,
808
+ name: options.name,
809
+ slotId: options.slotId,
810
+ fingerprint: (0, key_slots_1.makeKeyFingerprint)(value)
811
+ });
812
+ await keychain.setSlotSecret(options.tenant, provider, slot.slotId, value);
813
+ if (options.setActive) {
814
+ await profileStore.setActiveKeySlot(options.tenant, provider, slot.slotId);
815
+ }
816
+ printJson(stdout, {
817
+ tenantId: options.tenant,
818
+ provider,
819
+ slot
820
+ });
821
+ });
822
+ authKey
823
+ .command('list')
824
+ .requiredOption('--tenant <tenantId>', 'Tenant id')
825
+ .option('--provider <provider>', 'Optional provider filter')
826
+ .option('--format <format>', 'json|text', 'json')
827
+ .action(async (options) => {
828
+ const keychain = await getKeychain();
829
+ const provider = options.provider ? parseProvider(options.provider) : undefined;
830
+ const slots = await collectSlotViews({
831
+ profileStore,
832
+ keychain,
833
+ tenantId: options.tenant,
834
+ provider
835
+ });
836
+ if ((options.format ?? 'json') === 'text') {
837
+ stdout.write(formatSlotListText(slots));
838
+ return;
839
+ }
840
+ printJson(stdout, {
841
+ tenantId: options.tenant,
842
+ slots
843
+ });
844
+ });
845
+ authKey
846
+ .command('use')
847
+ .requiredOption('--tenant <tenantId>', 'Tenant id')
848
+ .requiredOption('--provider <provider>', 'Provider')
849
+ .requiredOption('--slot <slotRef>', 'Slot id or name')
850
+ .action(async (options) => {
851
+ const provider = parseProvider(options.provider);
852
+ const slot = await profileStore.setActiveKeySlot(options.tenant, provider, options.slot);
853
+ printJson(stdout, {
854
+ tenantId: options.tenant,
855
+ provider,
856
+ activeSlot: slot
857
+ });
858
+ });
859
+ authKey
860
+ .command('rename')
861
+ .requiredOption('--tenant <tenantId>', 'Tenant id')
862
+ .requiredOption('--provider <provider>', 'Provider')
863
+ .requiredOption('--slot <slotRef>', 'Slot id or name')
864
+ .requiredOption('--name <name>', 'New slot name')
865
+ .action(async (options) => {
866
+ const provider = parseProvider(options.provider);
867
+ const updated = await profileStore.updateKeySlot(options.tenant, provider, options.slot, {
868
+ name: options.name
869
+ });
870
+ printJson(stdout, {
871
+ tenantId: options.tenant,
872
+ provider,
873
+ slot: updated
874
+ });
875
+ });
876
+ authKey
877
+ .command('update')
878
+ .requiredOption('--tenant <tenantId>', 'Tenant id')
879
+ .requiredOption('--provider <provider>', 'Provider')
880
+ .requiredOption('--slot <slotRef>', 'Slot id or name')
881
+ .option('--key <value>', 'API key value')
882
+ .action(async (options) => {
883
+ const provider = parseProvider(options.provider);
884
+ const slot = await resolveSlotByRef(profileStore, options.tenant, provider, options.slot);
885
+ const value = requireKeyValue(options.key);
886
+ const keychain = await getKeychain();
887
+ await keychain.setSlotSecret(options.tenant, provider, slot.slotId, value);
888
+ const updated = await profileStore.updateKeySlot(options.tenant, provider, slot.slotId, {
889
+ fingerprint: (0, key_slots_1.makeKeyFingerprint)(value)
890
+ });
891
+ printJson(stdout, {
892
+ tenantId: options.tenant,
893
+ provider,
894
+ slot: updated
895
+ });
896
+ });
897
+ authKey
898
+ .command('remove')
899
+ .requiredOption('--tenant <tenantId>', 'Tenant id')
900
+ .requiredOption('--provider <provider>', 'Provider')
901
+ .requiredOption('--slot <slotRef>', 'Slot id or name')
902
+ .option('--confirm', 'Confirm removal')
903
+ .action(async (options) => {
904
+ if (!options.confirm) {
905
+ throw new Error('Key slot removal is destructive. Re-run with --confirm.');
906
+ }
907
+ const provider = parseProvider(options.provider);
908
+ const slot = await resolveSlotByRef(profileStore, options.tenant, provider, options.slot);
909
+ const keychain = await getKeychain();
910
+ await keychain.clearSlotSecret(options.tenant, provider, slot.slotId);
911
+ await profileStore.removeKeySlot(options.tenant, provider, slot.slotId);
912
+ printJson(stdout, {
913
+ tenantId: options.tenant,
914
+ provider,
915
+ removedSlotId: slot.slotId
916
+ });
917
+ });
918
+ authKey
919
+ .command('test')
920
+ .requiredOption('--tenant <tenantId>', 'Tenant id')
921
+ .requiredOption('--provider <provider>', 'Provider')
922
+ .requiredOption('--slot <slotRef>', 'Slot id or name')
923
+ .action(async (options) => {
924
+ const provider = parseProvider(options.provider);
925
+ const slot = await resolveSlotByRef(profileStore, options.tenant, provider, options.slot);
926
+ const keychain = await getKeychain();
927
+ const secret = await keychain.getSlotSecret(options.tenant, provider, slot.slotId);
928
+ if (!secret) {
929
+ throw new Error(`No secret found for slot "${slot.slotId}" (${provider}) in tenant ${options.tenant}.`);
930
+ }
931
+ const probe = await runSlotConnectivityTest({
932
+ provider,
933
+ tenantId: options.tenant,
934
+ key: secret,
935
+ profileStore
936
+ });
937
+ const validatedAt = new Date().toISOString();
938
+ const updated = await profileStore.updateKeySlot(options.tenant, provider, slot.slotId, {
939
+ lastValidatedAt: validatedAt
940
+ });
941
+ printJson(stdout, {
942
+ tenantId: options.tenant,
943
+ provider,
944
+ slot: updated,
945
+ probe
946
+ });
947
+ });
948
+ const setup = program.command('setup').description('Run setup and readiness checks');
949
+ setup
950
+ .command('status')
951
+ .description('Show setup/readiness status')
952
+ .option('--tenant <tenantId>', 'Tenant id override')
953
+ .option('--format <format>', 'json|text', 'json')
954
+ .action(async (options) => {
955
+ const keychain = await getKeychain();
956
+ const client = await withClient(options.tenant);
957
+ const readiness = await (0, readiness_1.evaluateReadiness)({
958
+ profileStore,
959
+ keychain,
960
+ tenantId: options.tenant,
961
+ client,
962
+ checkConnectivity: true
963
+ });
964
+ if ((options.format ?? 'json') === 'text') {
965
+ stdout.write(formatReadinessText(readiness));
966
+ return;
967
+ }
968
+ printJson(stdout, readiness);
969
+ });
970
+ setup
971
+ .command('run')
972
+ .description('Run setup flow (simple first-run by default, advanced with --advanced)')
973
+ .option('--tenant <tenantId>', 'Tenant id')
974
+ .option('--name <name>', 'Tenant display name')
975
+ .option('--advanced', 'Use advanced provider/slot prompts')
976
+ .option('--provider <provider>', 'Primary provider for key setup')
977
+ .option('--slot-name <name>', 'Key slot name', 'primary')
978
+ .option('--key <value>', 'API key value')
979
+ .option('--set-active', 'Set slot active (default true in setup flow)')
980
+ .option('--non-interactive', 'Disable prompts and require needed options')
981
+ .option('--format <format>', 'json|text', 'json')
982
+ .action(async (options) => {
983
+ if (!options.nonInteractive && !isInteractive) {
984
+ throw new Error('Interactive setup requires a TTY. Use --non-interactive with explicit flags.');
985
+ }
986
+ const advanced = options.advanced === true;
987
+ if (!advanced) {
988
+ let tenantLabel = (options.name ?? options.tenant ?? SIMPLE_SETUP_DEFAULT_TENANT).trim() || SIMPLE_SETUP_DEFAULT_TENANT;
989
+ let keyValue = options.key ?? process.env.XYTE_CLI_KEY;
990
+ if (!options.nonInteractive) {
991
+ keyValue = keyValue || (await prompt({ question: 'XYTE API key', stdout }));
992
+ tenantLabel =
993
+ (await prompt({
994
+ question: 'Tenant label (optional)',
995
+ initial: tenantLabel,
996
+ stdout
997
+ })) || tenantLabel;
998
+ }
999
+ if (!keyValue) {
1000
+ throw new Error('Missing API key. Provide --key/XYTE_CLI_KEY (or run interactive setup).');
1001
+ }
1002
+ const tenantId = normalizeTenantId(options.tenant?.trim() || tenantLabel);
1003
+ const tenantName = tenantLabel.trim() || tenantId;
1004
+ const setupResult = await runSimpleSetup({
1005
+ tenantId,
1006
+ tenantName,
1007
+ keyValue,
1008
+ setActive: options.setActive !== false
1009
+ });
1010
+ if ((options.format ?? 'json') === 'text') {
1011
+ stdout.write(formatReadinessText(setupResult.readiness));
1012
+ return;
1013
+ }
1014
+ printJson(stdout, setupResult);
1015
+ return;
1016
+ }
1017
+ let tenantId = options.tenant;
1018
+ let tenantName = options.name;
1019
+ let provider = options.provider ? parseProvider(options.provider) : undefined;
1020
+ let slotName = options.slotName ?? 'primary';
1021
+ let keyValue = options.key ?? process.env.XYTE_CLI_KEY;
1022
+ if (!options.nonInteractive) {
1023
+ tenantId = tenantId || (await prompt({ question: 'Tenant id', stdout }));
1024
+ tenantName = tenantName || (await prompt({ question: 'Tenant display name', initial: tenantId, stdout }));
1025
+ const providerAnswer = provider || parseProvider(await prompt({ question: 'Provider', initial: 'xyte-org', stdout }));
1026
+ provider = providerAnswer;
1027
+ slotName = await prompt({ question: 'Slot name', initial: slotName, stdout });
1028
+ keyValue = keyValue || (await prompt({ question: 'API key', stdout }));
1029
+ }
1030
+ if (!tenantId) {
1031
+ throw new Error('Missing tenant id. Provide --tenant (or run interactive setup).');
1032
+ }
1033
+ if (!provider) {
1034
+ throw new Error('Missing provider. Provide --provider (or run interactive setup).');
1035
+ }
1036
+ if (!keyValue) {
1037
+ throw new Error('Missing API key. Provide --key/XYTE_CLI_KEY (or run interactive setup).');
1038
+ }
1039
+ await profileStore.upsertTenant({
1040
+ id: tenantId,
1041
+ name: tenantName
1042
+ });
1043
+ await profileStore.setActiveTenant(tenantId);
1044
+ const keychain = await getKeychain();
1045
+ let slot;
1046
+ try {
1047
+ slot = await profileStore.addKeySlot(tenantId, {
1048
+ provider,
1049
+ name: slotName,
1050
+ fingerprint: (0, key_slots_1.makeKeyFingerprint)(keyValue)
1051
+ });
1052
+ }
1053
+ catch (error) {
1054
+ const knownSlots = await profileStore.listKeySlots(tenantId, provider);
1055
+ const existing = knownSlots.find((item) => item.name.toLowerCase() === slotName.toLowerCase());
1056
+ if (!existing) {
1057
+ throw error;
1058
+ }
1059
+ slot = await profileStore.updateKeySlot(tenantId, provider, existing.slotId, {
1060
+ fingerprint: (0, key_slots_1.makeKeyFingerprint)(keyValue)
1061
+ });
1062
+ }
1063
+ await keychain.setSlotSecret(tenantId, provider, slot.slotId, keyValue);
1064
+ if (options.setActive !== false) {
1065
+ await profileStore.setActiveKeySlot(tenantId, provider, slot.slotId);
1066
+ }
1067
+ const client = await withClient(tenantId);
1068
+ const readiness = await (0, readiness_1.evaluateReadiness)({
1069
+ profileStore,
1070
+ keychain,
1071
+ tenantId,
1072
+ client,
1073
+ checkConnectivity: true
1074
+ });
1075
+ if ((options.format ?? 'json') === 'text') {
1076
+ stdout.write(formatReadinessText(readiness));
1077
+ return;
1078
+ }
1079
+ printJson(stdout, {
1080
+ tenantId,
1081
+ provider,
1082
+ slot,
1083
+ readiness
1084
+ });
1085
+ });
1086
+ const config = program.command('config').description('Configuration and diagnostics');
1087
+ config
1088
+ .command('doctor')
1089
+ .description('Run connectivity and readiness diagnostics')
1090
+ .option('--tenant <tenantId>', 'Tenant id override')
1091
+ .option('--retry-attempts <n>', 'Retry attempts for HTTP transport', '2')
1092
+ .option('--retry-backoff-ms <n>', 'Retry backoff (ms) for HTTP transport', '250')
1093
+ .option('--format <format>', 'json|text', 'json')
1094
+ .action(async (options) => {
1095
+ const retryAttempts = Number.parseInt(options.retryAttempts ?? '2', 10);
1096
+ const retryBackoffMs = Number.parseInt(options.retryBackoffMs ?? '250', 10);
1097
+ const keychain = await getKeychain();
1098
+ const client = await withClient(options.tenant, {
1099
+ attempts: Number.isFinite(retryAttempts) ? retryAttempts : 2,
1100
+ backoffMs: Number.isFinite(retryBackoffMs) ? retryBackoffMs : 250
1101
+ });
1102
+ const readiness = await (0, readiness_1.evaluateReadiness)({
1103
+ profileStore,
1104
+ keychain,
1105
+ tenantId: options.tenant,
1106
+ client,
1107
+ checkConnectivity: true
1108
+ });
1109
+ if ((options.format ?? 'json') === 'text') {
1110
+ stdout.write(formatReadinessText(readiness));
1111
+ return;
1112
+ }
1113
+ printJson(stdout, {
1114
+ retryAttempts,
1115
+ retryBackoffMs,
1116
+ readiness
1117
+ });
1118
+ });
1119
+ program
1120
+ .command('tui')
1121
+ .description('Launch the full-screen TUI')
1122
+ .option('--headless', 'Run headless visual mode for agents')
1123
+ .option('--screen <screen>', 'setup|config|dashboard|spaces|devices|incidents|tickets', 'dashboard')
1124
+ .option('--format <format>', 'json|text (headless is json-only)', 'json')
1125
+ .option('--once', 'Render one frame and exit (default behavior)')
1126
+ .option('--follow', 'Continuously stream frames')
1127
+ .option('--interval-ms <ms>', 'Polling interval for --follow', '2000')
1128
+ .option('--tenant <tenantId>', 'Tenant id override')
1129
+ .option('--no-motion', 'Disable motion and animation effects')
1130
+ .option('--debug', 'Enable TUI debug logging')
1131
+ .option('--debug-log <path>', 'Write TUI debug logs to this file')
1132
+ .action(async (options) => {
1133
+ const keychain = await getKeychain();
1134
+ const client = (0, create_client_1.createXyteClient)({ profileStore, keychain });
1135
+ const allowedScreens = ['setup', 'config', 'dashboard', 'spaces', 'devices', 'incidents', 'tickets'];
1136
+ const screen = (options.screen ?? 'dashboard');
1137
+ if (!allowedScreens.includes(screen)) {
1138
+ throw new Error(`Invalid screen: ${options.screen}`);
1139
+ }
1140
+ const format = options.format ?? 'json';
1141
+ if (Boolean(options.headless)) {
1142
+ if (format !== 'json') {
1143
+ throw new Error('Headless mode is JSON-only. Use --format json and parse NDJSON frames.');
1144
+ }
1145
+ }
1146
+ else if (!['json', 'text'].includes(format)) {
1147
+ throw new Error(`Invalid format: ${options.format}.`);
1148
+ }
1149
+ const follow = options.once ? false : Boolean(options.follow);
1150
+ const intervalMs = Number.parseInt(options.intervalMs ?? '2000', 10);
1151
+ const motionEnabled = options.motion === false ? false : undefined;
1152
+ await runTui({
1153
+ client,
1154
+ profileStore,
1155
+ keychain,
1156
+ initialScreen: screen,
1157
+ headless: Boolean(options.headless),
1158
+ format: (options.headless ? 'json' : format),
1159
+ motionEnabled,
1160
+ follow,
1161
+ intervalMs: Number.isFinite(intervalMs) ? intervalMs : 2000,
1162
+ tenantId: options.tenant,
1163
+ output: stdout,
1164
+ debug: options.debug,
1165
+ debugLogPath: options.debugLog
1166
+ });
1167
+ });
1168
+ program.exitOverride((error) => {
1169
+ if (error.code === 'commander.helpDisplayed') {
1170
+ return;
1171
+ }
1172
+ throw error;
1173
+ });
1174
+ program.configureOutput({
1175
+ writeErr: (text) => {
1176
+ stderr.write(text);
1177
+ }
1178
+ });
1179
+ return program;
1180
+ }
1181
+ async function runCli(argv = process.argv, runtime = {}) {
1182
+ const program = createCli(runtime);
1183
+ await program.parseAsync(argv);
1184
+ }
1185
+ //# sourceMappingURL=index.js.map