@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.
- package/LICENSE +176 -0
- package/README.md +245 -0
- package/dist/bin/xyte-cli.d.ts +3 -0
- package/dist/bin/xyte-cli.d.ts.map +1 -0
- package/dist/bin/xyte-cli.js +18 -0
- package/dist/bin/xyte-cli.js.map +1 -0
- package/dist/cli/index.d.ts +24 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +1185 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/client/catalog.d.ts +6 -0
- package/dist/client/catalog.d.ts.map +1 -0
- package/dist/client/catalog.js +32 -0
- package/dist/client/catalog.js.map +1 -0
- package/dist/client/create-client.d.ts +3 -0
- package/dist/client/create-client.d.ts.map +1 -0
- package/dist/client/create-client.js +235 -0
- package/dist/client/create-client.js.map +1 -0
- package/dist/config/connectivity.d.ts +19 -0
- package/dist/config/connectivity.d.ts.map +1 -0
- package/dist/config/connectivity.js +166 -0
- package/dist/config/connectivity.js.map +1 -0
- package/dist/config/readiness.d.ts +32 -0
- package/dist/config/readiness.d.ts.map +1 -0
- package/dist/config/readiness.js +96 -0
- package/dist/config/readiness.js.map +1 -0
- package/dist/config/retry-policy.d.ts +16 -0
- package/dist/config/retry-policy.d.ts.map +1 -0
- package/dist/config/retry-policy.js +24 -0
- package/dist/config/retry-policy.js.map +1 -0
- package/dist/contracts/call-envelope.d.ts +74 -0
- package/dist/contracts/call-envelope.d.ts.map +1 -0
- package/dist/contracts/call-envelope.js +72 -0
- package/dist/contracts/call-envelope.js.map +1 -0
- package/dist/contracts/problem.d.ts +11 -0
- package/dist/contracts/problem.d.ts.map +1 -0
- package/dist/contracts/problem.js +55 -0
- package/dist/contracts/problem.js.map +1 -0
- package/dist/contracts/versions.d.ts +6 -0
- package/dist/contracts/versions.d.ts.map +1 -0
- package/dist/contracts/versions.js +9 -0
- package/dist/contracts/versions.js.map +1 -0
- package/dist/http/errors.d.ts +24 -0
- package/dist/http/errors.d.ts.map +1 -0
- package/dist/http/errors.js +39 -0
- package/dist/http/errors.js.map +1 -0
- package/dist/http/transport.d.ts +35 -0
- package/dist/http/transport.d.ts.map +1 -0
- package/dist/http/transport.js +129 -0
- package/dist/http/transport.js.map +1 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp/server.d.ts +12 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +404 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/namespaces/device.d.ts +38 -0
- package/dist/namespaces/device.d.ts.map +1 -0
- package/dist/namespaces/device.js +36 -0
- package/dist/namespaces/device.js.map +1 -0
- package/dist/namespaces/organization.d.ts +27 -0
- package/dist/namespaces/organization.d.ts.map +1 -0
- package/dist/namespaces/organization.js +30 -0
- package/dist/namespaces/organization.js.map +1 -0
- package/dist/namespaces/partner.d.ts +18 -0
- package/dist/namespaces/partner.d.ts.map +1 -0
- package/dist/namespaces/partner.js +21 -0
- package/dist/namespaces/partner.js.map +1 -0
- package/dist/observability/logger.d.ts +3 -0
- package/dist/observability/logger.d.ts.map +1 -0
- package/dist/observability/logger.js +21 -0
- package/dist/observability/logger.js.map +1 -0
- package/dist/observability/tracing.d.ts +3 -0
- package/dist/observability/tracing.d.ts.map +1 -0
- package/dist/observability/tracing.js +26 -0
- package/dist/observability/tracing.js.map +1 -0
- package/dist/secure/key-slots.d.ts +8 -0
- package/dist/secure/key-slots.d.ts.map +1 -0
- package/dist/secure/key-slots.js +47 -0
- package/dist/secure/key-slots.js.map +1 -0
- package/dist/secure/keychain.d.ts +20 -0
- package/dist/secure/keychain.d.ts.map +1 -0
- package/dist/secure/keychain.js +170 -0
- package/dist/secure/keychain.js.map +1 -0
- package/dist/secure/profile-store.d.ts +66 -0
- package/dist/secure/profile-store.d.ts.map +1 -0
- package/dist/secure/profile-store.js +309 -0
- package/dist/secure/profile-store.js.map +1 -0
- package/dist/spec/public-endpoints.json +1175 -0
- package/dist/tui/animation.d.ts +12 -0
- package/dist/tui/animation.d.ts.map +1 -0
- package/dist/tui/animation.js +41 -0
- package/dist/tui/animation.js.map +1 -0
- package/dist/tui/app.d.ts +27 -0
- package/dist/tui/app.d.ts.map +1 -0
- package/dist/tui/app.js +711 -0
- package/dist/tui/app.js.map +1 -0
- package/dist/tui/assets/logo.d.ts +5 -0
- package/dist/tui/assets/logo.d.ts.map +1 -0
- package/dist/tui/assets/logo.js +24 -0
- package/dist/tui/assets/logo.js.map +1 -0
- package/dist/tui/data-loaders.d.ts +33 -0
- package/dist/tui/data-loaders.d.ts.map +1 -0
- package/dist/tui/data-loaders.js +250 -0
- package/dist/tui/data-loaders.js.map +1 -0
- package/dist/tui/dispatch.d.ts +14 -0
- package/dist/tui/dispatch.d.ts.map +1 -0
- package/dist/tui/dispatch.js +44 -0
- package/dist/tui/dispatch.js.map +1 -0
- package/dist/tui/headless-renderer.d.ts +20 -0
- package/dist/tui/headless-renderer.d.ts.map +1 -0
- package/dist/tui/headless-renderer.js +598 -0
- package/dist/tui/headless-renderer.js.map +1 -0
- package/dist/tui/input-controller.d.ts +29 -0
- package/dist/tui/input-controller.d.ts.map +1 -0
- package/dist/tui/input-controller.js +76 -0
- package/dist/tui/input-controller.js.map +1 -0
- package/dist/tui/key-wizard.d.ts +29 -0
- package/dist/tui/key-wizard.d.ts.map +1 -0
- package/dist/tui/key-wizard.js +177 -0
- package/dist/tui/key-wizard.js.map +1 -0
- package/dist/tui/keymap.d.ts +9 -0
- package/dist/tui/keymap.d.ts.map +1 -0
- package/dist/tui/keymap.js +29 -0
- package/dist/tui/keymap.js.map +1 -0
- package/dist/tui/layout.d.ts +16 -0
- package/dist/tui/layout.d.ts.map +1 -0
- package/dist/tui/layout.js +99 -0
- package/dist/tui/layout.js.map +1 -0
- package/dist/tui/logger.d.ts +12 -0
- package/dist/tui/logger.d.ts.map +1 -0
- package/dist/tui/logger.js +83 -0
- package/dist/tui/logger.js.map +1 -0
- package/dist/tui/navigation.d.ts +26 -0
- package/dist/tui/navigation.d.ts.map +1 -0
- package/dist/tui/navigation.js +136 -0
- package/dist/tui/navigation.js.map +1 -0
- package/dist/tui/panes.d.ts +7 -0
- package/dist/tui/panes.d.ts.map +1 -0
- package/dist/tui/panes.js +34 -0
- package/dist/tui/panes.js.map +1 -0
- package/dist/tui/runtime.d.ts +34 -0
- package/dist/tui/runtime.d.ts.map +1 -0
- package/dist/tui/runtime.js +100 -0
- package/dist/tui/runtime.js.map +1 -0
- package/dist/tui/scene.d.ts +160 -0
- package/dist/tui/scene.d.ts.map +1 -0
- package/dist/tui/scene.js +424 -0
- package/dist/tui/scene.js.map +1 -0
- package/dist/tui/screens/config.d.ts +3 -0
- package/dist/tui/screens/config.d.ts.map +1 -0
- package/dist/tui/screens/config.js +406 -0
- package/dist/tui/screens/config.js.map +1 -0
- package/dist/tui/screens/dashboard.d.ts +3 -0
- package/dist/tui/screens/dashboard.d.ts.map +1 -0
- package/dist/tui/screens/dashboard.js +176 -0
- package/dist/tui/screens/dashboard.js.map +1 -0
- package/dist/tui/screens/devices.d.ts +3 -0
- package/dist/tui/screens/devices.d.ts.map +1 -0
- package/dist/tui/screens/devices.js +297 -0
- package/dist/tui/screens/devices.js.map +1 -0
- package/dist/tui/screens/incidents.d.ts +4 -0
- package/dist/tui/screens/incidents.d.ts.map +1 -0
- package/dist/tui/screens/incidents.js +304 -0
- package/dist/tui/screens/incidents.js.map +1 -0
- package/dist/tui/screens/setup.d.ts +3 -0
- package/dist/tui/screens/setup.d.ts.map +1 -0
- package/dist/tui/screens/setup.js +299 -0
- package/dist/tui/screens/setup.js.map +1 -0
- package/dist/tui/screens/spaces.d.ts +7 -0
- package/dist/tui/screens/spaces.d.ts.map +1 -0
- package/dist/tui/screens/spaces.js +422 -0
- package/dist/tui/screens/spaces.js.map +1 -0
- package/dist/tui/screens/tickets.d.ts +9 -0
- package/dist/tui/screens/tickets.d.ts.map +1 -0
- package/dist/tui/screens/tickets.js +418 -0
- package/dist/tui/screens/tickets.js.map +1 -0
- package/dist/tui/serialize.d.ts +31 -0
- package/dist/tui/serialize.d.ts.map +1 -0
- package/dist/tui/serialize.js +183 -0
- package/dist/tui/serialize.js.map +1 -0
- package/dist/tui/table-format.d.ts +11 -0
- package/dist/tui/table-format.d.ts.map +1 -0
- package/dist/tui/table-format.js +77 -0
- package/dist/tui/table-format.js.map +1 -0
- package/dist/tui/tabs.d.ts +4 -0
- package/dist/tui/tabs.d.ts.map +1 -0
- package/dist/tui/tabs.js +13 -0
- package/dist/tui/tabs.js.map +1 -0
- package/dist/tui/types.d.ts +37 -0
- package/dist/tui/types.d.ts.map +1 -0
- package/dist/tui/types.js +3 -0
- package/dist/tui/types.js.map +1 -0
- package/dist/types/client.d.ts +54 -0
- package/dist/types/client.d.ts.map +1 -0
- package/dist/types/client.js +3 -0
- package/dist/types/client.js.map +1 -0
- package/dist/types/endpoints.d.ts +29 -0
- package/dist/types/endpoints.d.ts.map +1 -0
- package/dist/types/endpoints.js +3 -0
- package/dist/types/endpoints.js.map +1 -0
- package/dist/types/profile.d.ts +29 -0
- package/dist/types/profile.d.ts.map +1 -0
- package/dist/types/profile.js +3 -0
- package/dist/types/profile.js.map +1 -0
- package/dist/utils/config-dir.d.ts +2 -0
- package/dist/utils/config-dir.d.ts.map +1 -0
- package/dist/utils/config-dir.js +23 -0
- package/dist/utils/config-dir.js.map +1 -0
- package/dist/utils/error-format.d.ts +4 -0
- package/dist/utils/error-format.d.ts.map +1 -0
- package/dist/utils/error-format.js +34 -0
- package/dist/utils/error-format.js.map +1 -0
- package/dist/utils/install-skills.d.ts +38 -0
- package/dist/utils/install-skills.d.ts.map +1 -0
- package/dist/utils/install-skills.js +117 -0
- package/dist/utils/install-skills.js.map +1 -0
- package/dist/utils/json-output.d.ts +6 -0
- package/dist/utils/json-output.d.ts.map +1 -0
- package/dist/utils/json-output.js +30 -0
- package/dist/utils/json-output.js.map +1 -0
- package/dist/utils/json.d.ts +4 -0
- package/dist/utils/json.d.ts.map +1 -0
- package/dist/utils/json.js +36 -0
- package/dist/utils/json.js.map +1 -0
- package/dist/utils/version.d.ts +2 -0
- package/dist/utils/version.d.ts.map +1 -0
- package/dist/utils/version.js +28 -0
- package/dist/utils/version.js.map +1 -0
- package/dist/workflows/fleet-insights.d.ts +122 -0
- package/dist/workflows/fleet-insights.d.ts.map +1 -0
- package/dist/workflows/fleet-insights.js +938 -0
- package/dist/workflows/fleet-insights.js.map +1 -0
- package/docs/schemas/call-envelope.v1.schema.json +140 -0
- package/docs/schemas/headless-frame.v1.schema.json +159 -0
- package/docs/schemas/inspect-deep-dive.v1.schema.json +251 -0
- package/docs/schemas/inspect-fleet.v1.schema.json +111 -0
- package/docs/schemas/report.v1.schema.json +39 -0
- package/package.json +75 -0
- package/skills/xyte-cli/SKILL.md +181 -0
- package/skills/xyte-cli/agents/openai.yaml +4 -0
- package/skills/xyte-cli/references/endpoints.md +106 -0
- package/skills/xyte-cli/references/headless-contract.md +96 -0
- package/skills/xyte-cli/references/tui-flows.md +126 -0
- package/skills/xyte-cli/scripts/check_headless.sh +83 -0
- package/skills/xyte-cli/scripts/endpoint_filters_report.sh +33 -0
- package/skills/xyte-cli/scripts/run_xyte_cli.sh +12 -0
- package/skills/xyte-cli/scripts/validate_agent_contracts.sh +72 -0
- 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
|