@calltelemetry/cli 0.15.1 → 1.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 (46) hide show
  1. package/README.md +47 -0
  2. package/dist/commands/cube.d.ts.map +1 -1
  3. package/dist/commands/cube.js +68 -0
  4. package/dist/commands/cube.js.map +1 -1
  5. package/dist/commands/sftp.d.ts +3 -0
  6. package/dist/commands/sftp.d.ts.map +1 -0
  7. package/dist/commands/sftp.js +1586 -0
  8. package/dist/commands/sftp.js.map +1 -0
  9. package/dist/index.js +9 -4
  10. package/dist/index.js.map +1 -1
  11. package/dist/lib/appliance-api.d.ts +149 -0
  12. package/dist/lib/appliance-api.d.ts.map +1 -0
  13. package/dist/lib/appliance-api.js +492 -0
  14. package/dist/lib/appliance-api.js.map +1 -0
  15. package/dist/lib/cube-diag.d.ts +6 -0
  16. package/dist/lib/cube-diag.d.ts.map +1 -1
  17. package/dist/lib/cube-diag.js +9 -0
  18. package/dist/lib/cube-diag.js.map +1 -1
  19. package/dist/lib/cube-doctor.d.ts +107 -0
  20. package/dist/lib/cube-doctor.d.ts.map +1 -0
  21. package/dist/lib/cube-doctor.js +426 -0
  22. package/dist/lib/cube-doctor.js.map +1 -0
  23. package/dist/lib/openapi.d.ts +129 -0
  24. package/dist/lib/openapi.d.ts.map +1 -0
  25. package/dist/lib/openapi.js +325 -0
  26. package/dist/lib/openapi.js.map +1 -0
  27. package/dist/lib/sftp.d.ts +231 -0
  28. package/dist/lib/sftp.d.ts.map +1 -0
  29. package/dist/lib/sftp.js +439 -0
  30. package/dist/lib/sftp.js.map +1 -0
  31. package/dist/lib/version.d.ts +1 -1
  32. package/dist/lib/version.d.ts.map +1 -1
  33. package/dist/lib/version.js +1 -1
  34. package/dist/lib/version.js.map +1 -1
  35. package/dist/program.d.ts.map +1 -1
  36. package/dist/program.js +2 -0
  37. package/dist/program.js.map +1 -1
  38. package/dist/utils/prompt.d.ts +14 -0
  39. package/dist/utils/prompt.d.ts.map +1 -1
  40. package/dist/utils/prompt.js +28 -0
  41. package/dist/utils/prompt.js.map +1 -1
  42. package/dist/utils/table.d.ts +18 -0
  43. package/dist/utils/table.d.ts.map +1 -0
  44. package/dist/utils/table.js +97 -0
  45. package/dist/utils/table.js.map +1 -0
  46. package/package.json +1 -1
@@ -0,0 +1,231 @@
1
+ /**
2
+ * SFTP Action Account domain helpers.
3
+ *
4
+ * Paths and response envelopes here match the appliance OpenAPI contract
5
+ * (`sftp_action_accounts`, `sftp_monitoring`, `sftp_storage`, ...). Every
6
+ * command checks the live spec first so a missing route on an older release
7
+ * produces an actionable message instead of a raw 404.
8
+ */
9
+ import { ApplianceApi } from './appliance-api.js';
10
+ import { type OpenApiDocument, type ResolvedOperation } from './openapi.js';
11
+ export interface SftpAction {
12
+ type: string;
13
+ target?: string | null;
14
+ params?: Record<string, unknown> | null;
15
+ }
16
+ export interface SftpAccount {
17
+ id: number;
18
+ org_id: number;
19
+ username: string;
20
+ enabled: boolean;
21
+ home?: string;
22
+ description?: string | null;
23
+ on_success_actions?: SftpAction[] | null;
24
+ on_failure_actions?: SftpAction[] | null;
25
+ quota_used_bytes?: number | null;
26
+ quota_limit_gb?: number | string | null;
27
+ last_quota_calculated_at?: string | null;
28
+ upload_monitoring_enabled?: boolean;
29
+ expected_schedule?: string | null;
30
+ expected_next_upload_at?: string | null;
31
+ last_upload_at?: string | null;
32
+ missed_upload_count?: number;
33
+ alert_on_missed_uploads?: boolean;
34
+ inserted_at?: string;
35
+ updated_at?: string;
36
+ [key: string]: unknown;
37
+ }
38
+ export interface SftpQuota {
39
+ quota_used_bytes?: number | null;
40
+ quota_limit_gb?: number | string | null;
41
+ quota_percentage?: number | null;
42
+ over_quota?: boolean;
43
+ last_calculated_at?: string | null;
44
+ }
45
+ export interface SftpStorageStatus {
46
+ total_used_bytes?: number;
47
+ total_limit_gb?: number | string | null;
48
+ account_count?: number;
49
+ accounts_over_quota?: number;
50
+ }
51
+ export interface SftpRun {
52
+ id: number;
53
+ action_account_id?: number;
54
+ username?: string;
55
+ status?: string;
56
+ type?: string;
57
+ event?: string;
58
+ file?: string | null;
59
+ result?: unknown;
60
+ inserted_at?: string;
61
+ updated_at?: string;
62
+ }
63
+ export interface SftpActivity {
64
+ id: number;
65
+ action?: string;
66
+ status?: string;
67
+ actor_ip?: string | null;
68
+ target_type?: string | null;
69
+ target_id?: string | null;
70
+ error_message?: string | null;
71
+ metadata?: Record<string, unknown> | null;
72
+ inserted_at?: string;
73
+ }
74
+ export interface SftpFileEntry {
75
+ name: string;
76
+ path: string;
77
+ type: 'file' | 'directory' | string;
78
+ size?: number | null;
79
+ modified_at?: string | null;
80
+ }
81
+ export interface SftpMonitoringConfig {
82
+ enabled?: boolean;
83
+ schedule?: {
84
+ type?: string;
85
+ hour?: number | null;
86
+ minute?: number | null;
87
+ day_of_week?: number | null;
88
+ day_of_month?: number | null;
89
+ weekdays_only?: boolean;
90
+ };
91
+ config?: {
92
+ alert_on_missed_uploads?: boolean;
93
+ tolerance_minutes?: number;
94
+ alert_threshold?: number;
95
+ alert_config?: Record<string, unknown>;
96
+ };
97
+ expected_next_upload_at?: string | null;
98
+ last_upload_at?: string | null;
99
+ missed_upload_count?: number;
100
+ }
101
+ export interface SftpMonitoredAccount {
102
+ id: number;
103
+ org_id?: number;
104
+ username: string;
105
+ enabled?: boolean;
106
+ monitoring?: SftpMonitoringConfig;
107
+ }
108
+ export interface SftpContact {
109
+ id: number;
110
+ name?: string;
111
+ email?: string;
112
+ phone?: string;
113
+ [key: string]: unknown;
114
+ }
115
+ export interface SftpReportSchedule {
116
+ id: number;
117
+ enabled?: boolean;
118
+ schedule_type?: string;
119
+ next_run_at?: string | null;
120
+ last_run_at?: string | null;
121
+ last_run_status?: string | null;
122
+ last_run_error?: string | null;
123
+ failure_count?: number;
124
+ [key: string]: unknown;
125
+ }
126
+ /** Assert the installed release exposes an operation, with a release-aware error. */
127
+ export declare function requireOperation(spec: OpenApiDocument, ref: string): ResolvedOperation;
128
+ export declare function supportsOperation(spec: OpenApiDocument, ref: string): boolean;
129
+ export declare const ACCOUNT_USERNAME_PATTERN: RegExp;
130
+ export declare function validateUsername(username: string): void;
131
+ export declare function generateSftpPassword(length?: number): string;
132
+ export declare function parseQuotaGb(value: string | undefined): number | null | undefined;
133
+ /**
134
+ * Parse repeated `--on-success` / `--on-failure` values.
135
+ * Accepts a bare action type, an inline JSON object, or `@file` containing a
136
+ * JSON object or array of objects.
137
+ */
138
+ export declare function parseActionOptions(values: string[] | undefined): SftpAction[] | undefined;
139
+ export interface CreateAccountInput {
140
+ username: string;
141
+ password?: string;
142
+ generatePassword?: boolean;
143
+ description?: string;
144
+ enabled?: boolean;
145
+ quotaGb?: string;
146
+ callmanagerId?: string;
147
+ onSuccess: string[];
148
+ onFailure: string[];
149
+ }
150
+ export interface AccountPayloadResult {
151
+ account: Record<string, unknown>;
152
+ generatedPassword?: string;
153
+ }
154
+ export declare function buildCreateAccountPayload(input: CreateAccountInput): AccountPayloadResult;
155
+ export interface UpdateAccountInput {
156
+ description?: string;
157
+ quotaGb?: string;
158
+ callmanagerId?: string;
159
+ enable?: boolean;
160
+ disable?: boolean;
161
+ password?: string;
162
+ generatePassword?: boolean;
163
+ onSuccess: string[];
164
+ onFailure: string[];
165
+ clearSuccess?: boolean;
166
+ clearFailure?: boolean;
167
+ }
168
+ export declare function buildUpdateAccountPayload(input: UpdateAccountInput): AccountPayloadResult;
169
+ export declare function listAccounts(api: ApplianceApi): Promise<SftpAccount[]>;
170
+ export declare function resolveAccount(api: ApplianceApi, ref: string): Promise<SftpAccount>;
171
+ export declare function getAccountQuota(api: ApplianceApi, account: SftpAccount): Promise<SftpQuota>;
172
+ export declare function getAccountRuns(api: ApplianceApi, account: SftpAccount, limit?: number, offset?: number): Promise<SftpRun[]>;
173
+ export declare function getAccountActivity(api: ApplianceApi, account: SftpAccount, limit?: number, offset?: number): Promise<SftpActivity[]>;
174
+ export declare function getAccountFiles(api: ApplianceApi, account: SftpAccount, path?: string, limit?: number): Promise<{
175
+ path?: string;
176
+ total?: number;
177
+ entries: SftpFileEntry[];
178
+ }>;
179
+ export declare function getAccountContacts(api: ApplianceApi, account: SftpAccount): Promise<SftpContact[]>;
180
+ export declare function addAccountContacts(api: ApplianceApi, account: SftpAccount, contactIds: number[]): Promise<unknown>;
181
+ export declare function removeAccountContact(api: ApplianceApi, account: SftpAccount, contactId: number): Promise<unknown>;
182
+ export interface MonitoringInput {
183
+ schedule?: string;
184
+ hour?: string;
185
+ minute?: string;
186
+ dayOfWeek?: string;
187
+ dayOfMonth?: string;
188
+ weekdaysOnly?: boolean;
189
+ tolerance?: string;
190
+ threshold?: string;
191
+ alertOnMissed?: boolean;
192
+ alertConfig?: string;
193
+ }
194
+ export declare function buildMonitoringPayload(input: MonitoringInput, requireSchedule: boolean): Record<string, unknown>;
195
+ export declare function listMonitoredAccounts(api: ApplianceApi): Promise<SftpMonitoredAccount[]>;
196
+ export declare function getAccountMonitoring(api: ApplianceApi, account: SftpAccount): Promise<SftpMonitoringConfig>;
197
+ export interface DiscoverMonitoringInput {
198
+ host?: string;
199
+ username?: string;
200
+ password?: string;
201
+ scheduleName?: string;
202
+ callmanagerId?: string;
203
+ tolerance?: string;
204
+ }
205
+ export declare function buildDiscoverPayload(input: DiscoverMonitoringInput): Record<string, unknown>;
206
+ export interface SftpStatusSnapshot {
207
+ accounts: {
208
+ total: number;
209
+ enabled: number;
210
+ disabled: number;
211
+ };
212
+ storage: SftpStorageStatus | null;
213
+ monitoring: {
214
+ available: boolean;
215
+ monitoredCount: number;
216
+ missingUploads: number | null;
217
+ };
218
+ operations: {
219
+ total: number;
220
+ specOpenApi: string | null;
221
+ };
222
+ }
223
+ export declare function collectSftpStatus(api: ApplianceApi, spec: OpenApiDocument): Promise<SftpStatusSnapshot>;
224
+ /** Read a CSV or JSON file for import. */
225
+ export declare function readImportFile(path: string): {
226
+ filename: string;
227
+ data: Buffer;
228
+ contentType: string;
229
+ };
230
+ export declare function parseContactIds(values: string[] | undefined): number[];
231
+ //# sourceMappingURL=sftp.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sftp.d.ts","sourceRoot":"","sources":["../../src/lib/sftp.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,439 @@
1
+ /**
2
+ * SFTP Action Account domain helpers.
3
+ *
4
+ * Paths and response envelopes here match the appliance OpenAPI contract
5
+ * (`sftp_action_accounts`, `sftp_monitoring`, `sftp_storage`, ...). Every
6
+ * command checks the live spec first so a missing route on an older release
7
+ * produces an actionable message instead of a raw 404.
8
+ */
9
+ import { randomBytes } from 'node:crypto';
10
+ import { readFileSync } from 'node:fs';
11
+ import { ApplianceApiError, parsePositiveInt, readJsonOption } from './appliance-api.js';
12
+ import { getOperation, hasOperation, listOperations, } from './openapi.js';
13
+ function asRecord(value) {
14
+ return typeof value === 'object' && value !== null && !Array.isArray(value)
15
+ ? value
16
+ : {};
17
+ }
18
+ function unwrapList(data, ...keys) {
19
+ if (Array.isArray(data))
20
+ return data;
21
+ const record = asRecord(data);
22
+ for (const key of keys) {
23
+ if (Array.isArray(record[key]))
24
+ return record[key];
25
+ }
26
+ const nested = asRecord(record.data);
27
+ for (const key of keys) {
28
+ if (Array.isArray(nested[key]))
29
+ return nested[key];
30
+ }
31
+ if (Array.isArray(record.data))
32
+ return record.data;
33
+ return [];
34
+ }
35
+ function unwrapRecord(data, key) {
36
+ const record = asRecord(data);
37
+ if (record[key] !== undefined && record[key] !== null)
38
+ return record[key];
39
+ const nested = asRecord(record.data);
40
+ if (nested[key] !== undefined && nested[key] !== null)
41
+ return nested[key];
42
+ return (record[key] ?? record.data ?? data);
43
+ }
44
+ /** Assert the installed release exposes an operation, with a release-aware error. */
45
+ export function requireOperation(spec, ref) {
46
+ try {
47
+ return getOperation(spec, ref);
48
+ }
49
+ catch (err) {
50
+ throw new ApplianceApiError(`This appliance release does not expose the SFTP operation "${ref}". ` +
51
+ `Run "ct sftp api --list" to see the operations this release supports.`, { code: 'UNSUPPORTED_OPERATION', body: err.message });
52
+ }
53
+ }
54
+ export function supportsOperation(spec, ref) {
55
+ return hasOperation(spec, ref);
56
+ }
57
+ // ── Accounts ─────────────────────────────────────────────────────────────
58
+ export const ACCOUNT_USERNAME_PATTERN = /^[A-Za-z0-9_.@-]{3,50}$/;
59
+ export function validateUsername(username) {
60
+ if (!ACCOUNT_USERNAME_PATTERN.test(username)) {
61
+ throw new ApplianceApiError('Username must be 3–50 characters and contain only letters, numbers, dots, underscores, hyphens, or @.');
62
+ }
63
+ }
64
+ export function generateSftpPassword(length = 24) {
65
+ return randomBytes(Math.max(18, Math.ceil(length * 0.75)))
66
+ .toString('base64url')
67
+ .slice(0, length);
68
+ }
69
+ export function parseQuotaGb(value) {
70
+ if (value === undefined)
71
+ return undefined;
72
+ const trimmed = value.trim().toLowerCase();
73
+ if (trimmed === 'none' || trimmed === 'default' || trimmed === 'unlimited')
74
+ return null;
75
+ const parsed = Number(trimmed);
76
+ if (!Number.isFinite(parsed) || parsed <= 0) {
77
+ throw new ApplianceApiError('Quota must be a positive number of GB, or "none" to use the organization default.');
78
+ }
79
+ if (parsed > 10_000) {
80
+ throw new ApplianceApiError('Quota must be 10,000 GB (10 TB) or less.');
81
+ }
82
+ return parsed;
83
+ }
84
+ function validateAction(action, label) {
85
+ const record = asRecord(action);
86
+ const type = record.type;
87
+ if (typeof type !== 'string' || !/^[a-z0-9_]+$/.test(type)) {
88
+ throw new ApplianceApiError(`${label} must include a valid "type" (lowercase letters, numbers, underscores).`);
89
+ }
90
+ const params = record.params;
91
+ if (params !== undefined && params !== null && (typeof params !== 'object' || Array.isArray(params))) {
92
+ throw new ApplianceApiError(`${label} "params" must be a JSON object.`);
93
+ }
94
+ return {
95
+ type,
96
+ target: typeof record.target === 'string' ? record.target : null,
97
+ params: params ?? null,
98
+ };
99
+ }
100
+ /**
101
+ * Parse repeated `--on-success` / `--on-failure` values.
102
+ * Accepts a bare action type, an inline JSON object, or `@file` containing a
103
+ * JSON object or array of objects.
104
+ */
105
+ export function parseActionOptions(values) {
106
+ if (!values || values.length === 0)
107
+ return undefined;
108
+ const actions = [];
109
+ for (const raw of values) {
110
+ const value = raw.trim();
111
+ if (!value)
112
+ continue;
113
+ if (value.startsWith('@') || value.startsWith('{') || value.startsWith('[')) {
114
+ const parsed = readJsonOption(value, 'actions');
115
+ const list = Array.isArray(parsed) ? parsed : [parsed];
116
+ for (const item of list) {
117
+ actions.push(validateAction(item, 'Action'));
118
+ }
119
+ continue;
120
+ }
121
+ actions.push(validateAction({ type: value }, 'Action'));
122
+ }
123
+ return actions;
124
+ }
125
+ export function buildCreateAccountPayload(input) {
126
+ validateUsername(input.username);
127
+ const payload = {
128
+ username: input.username,
129
+ };
130
+ let generatedPassword;
131
+ let password = input.password;
132
+ if (!password && input.generatePassword !== false) {
133
+ generatedPassword = generateSftpPassword();
134
+ password = generatedPassword;
135
+ }
136
+ if (!password) {
137
+ throw new ApplianceApiError('A password is required. Pass --password, --password-stdin, or use --generate.');
138
+ }
139
+ if (password.length < 8 || password.length > 128) {
140
+ throw new ApplianceApiError('Password must be between 8 and 128 characters.');
141
+ }
142
+ payload.password = password;
143
+ if (input.description !== undefined)
144
+ payload.description = input.description;
145
+ if (input.enabled !== undefined)
146
+ payload.enabled = input.enabled;
147
+ const quota = parseQuotaGb(input.quotaGb);
148
+ if (quota !== undefined)
149
+ payload.quota_limit_gb = quota;
150
+ const callmanagerId = parsePositiveInt(input.callmanagerId, '--callmanager-id');
151
+ if (callmanagerId !== undefined)
152
+ payload.callmanager_id = callmanagerId;
153
+ const onSuccess = parseActionOptions(input.onSuccess);
154
+ if (onSuccess)
155
+ payload.on_success_actions = onSuccess;
156
+ const onFailure = parseActionOptions(input.onFailure);
157
+ if (onFailure)
158
+ payload.on_failure_actions = onFailure;
159
+ return { account: payload, generatedPassword };
160
+ }
161
+ export function buildUpdateAccountPayload(input) {
162
+ if (input.enable && input.disable) {
163
+ throw new ApplianceApiError('Use either --enable or --disable, not both.');
164
+ }
165
+ if (input.password && input.generatePassword) {
166
+ throw new ApplianceApiError('Use either --password/--password-stdin or --generate, not both.');
167
+ }
168
+ if (input.clearSuccess && input.onSuccess.length > 0) {
169
+ throw new ApplianceApiError('Use either --on-success or --clear-success-actions, not both.');
170
+ }
171
+ if (input.clearFailure && input.onFailure.length > 0) {
172
+ throw new ApplianceApiError('Use either --on-failure or --clear-failure-actions, not both.');
173
+ }
174
+ const payload = {};
175
+ let generatedPassword;
176
+ if (input.description !== undefined)
177
+ payload.description = input.description;
178
+ const quota = parseQuotaGb(input.quotaGb);
179
+ if (quota !== undefined)
180
+ payload.quota_limit_gb = quota;
181
+ const callmanagerId = parsePositiveInt(input.callmanagerId, '--callmanager-id');
182
+ if (callmanagerId !== undefined)
183
+ payload.callmanager_id = callmanagerId;
184
+ if (input.enable)
185
+ payload.enabled = true;
186
+ if (input.disable)
187
+ payload.enabled = false;
188
+ if (input.password !== undefined && input.password !== '') {
189
+ if (input.password.length < 8 || input.password.length > 128) {
190
+ throw new ApplianceApiError('Password must be between 8 and 128 characters.');
191
+ }
192
+ payload.password = input.password;
193
+ }
194
+ else if (input.generatePassword) {
195
+ generatedPassword = generateSftpPassword();
196
+ payload.password = generatedPassword;
197
+ }
198
+ const onSuccess = parseActionOptions(input.onSuccess);
199
+ if (input.clearSuccess)
200
+ payload.on_success_actions = [];
201
+ else if (onSuccess)
202
+ payload.on_success_actions = onSuccess;
203
+ const onFailure = parseActionOptions(input.onFailure);
204
+ if (input.clearFailure)
205
+ payload.on_failure_actions = [];
206
+ else if (onFailure)
207
+ payload.on_failure_actions = onFailure;
208
+ if (Object.keys(payload).length === 0) {
209
+ throw new ApplianceApiError('Nothing to update. Pass --description, --quota-gb, --enable/--disable, ' +
210
+ '--password/--generate, or an action option.');
211
+ }
212
+ return { account: payload, generatedPassword };
213
+ }
214
+ export async function listAccounts(api) {
215
+ const response = await api.get('sftp_action_accounts');
216
+ return unwrapList(response.data, 'accounts', 'data');
217
+ }
218
+ export async function resolveAccount(api, ref) {
219
+ const trimmed = ref.trim();
220
+ if (!trimmed)
221
+ throw new ApplianceApiError('Account ID or username is required');
222
+ if (/^\d+$/.test(trimmed)) {
223
+ const response = await api.get(`sftp_action_accounts/${trimmed}`);
224
+ return unwrapRecord(response.data, 'account');
225
+ }
226
+ const accounts = await listAccounts(api);
227
+ const exact = accounts.find(account => account.username === trimmed);
228
+ if (exact)
229
+ return exact;
230
+ const caseInsensitive = accounts.find(account => account.username.toLowerCase() === trimmed.toLowerCase());
231
+ if (caseInsensitive)
232
+ return caseInsensitive;
233
+ const suggestions = accounts
234
+ .filter(account => account.username.toLowerCase().includes(trimmed.toLowerCase()))
235
+ .slice(0, 5)
236
+ .map(account => account.username);
237
+ throw new ApplianceApiError(`SFTP account "${trimmed}" was not found.` +
238
+ (suggestions.length > 0 ? ` Similar accounts: ${suggestions.join(', ')}.` : ''));
239
+ }
240
+ export async function getAccountQuota(api, account) {
241
+ const response = await api.get(`sftp_action_accounts/${account.id}/quota`);
242
+ return unwrapRecord(response.data, 'quota');
243
+ }
244
+ export async function getAccountRuns(api, account, limit = 50, offset = 0) {
245
+ const response = await api.get(`sftp_action_accounts/${account.id}/runs`, {
246
+ query: { limit, offset },
247
+ });
248
+ return unwrapList(response.data, 'runs', 'data');
249
+ }
250
+ export async function getAccountActivity(api, account, limit = 100, offset = 0) {
251
+ const response = await api.get(`sftp_action_accounts/${account.id}/activity`, {
252
+ query: { limit, offset },
253
+ });
254
+ return unwrapList(response.data, 'activity', 'data');
255
+ }
256
+ export async function getAccountFiles(api, account, path, limit = 200) {
257
+ const response = await api.get(`sftp_action_accounts/${account.id}/files`, {
258
+ query: { path, limit },
259
+ });
260
+ const record = asRecord(response.data);
261
+ return {
262
+ path: typeof record.path === 'string' ? record.path : path,
263
+ total: typeof record.total === 'number' ? record.total : undefined,
264
+ entries: unwrapList(response.data, 'entries', 'data'),
265
+ };
266
+ }
267
+ export async function getAccountContacts(api, account) {
268
+ const response = await api.get(`sftp_action_accounts/${account.id}/contacts`);
269
+ return unwrapList(response.data, 'contacts', 'data');
270
+ }
271
+ export async function addAccountContacts(api, account, contactIds) {
272
+ const response = await api.post(`sftp_action_accounts/${account.id}/contacts`, {
273
+ contact_ids: contactIds,
274
+ });
275
+ return response.data;
276
+ }
277
+ export async function removeAccountContact(api, account, contactId) {
278
+ const response = await api.delete(`sftp_action_accounts/${account.id}/contacts/${contactId}`);
279
+ return response.data;
280
+ }
281
+ function optionalInt(value, label, min, max) {
282
+ if (value === undefined || value === '')
283
+ return undefined;
284
+ const parsed = Number(value);
285
+ if (!Number.isInteger(parsed) || parsed < min || parsed > max) {
286
+ throw new ApplianceApiError(`${label} must be an integer between ${min} and ${max}.`);
287
+ }
288
+ return parsed;
289
+ }
290
+ export function buildMonitoringPayload(input, requireSchedule) {
291
+ const payload = {};
292
+ if (requireSchedule && !input.schedule) {
293
+ throw new ApplianceApiError('--schedule is required (hourly, daily, weekly, or monthly).');
294
+ }
295
+ if (input.schedule !== undefined) {
296
+ const schedule = input.schedule.toLowerCase();
297
+ if (!['hourly', 'daily', 'weekly', 'monthly'].includes(schedule)) {
298
+ throw new ApplianceApiError('--schedule must be one of: hourly, daily, weekly, monthly.');
299
+ }
300
+ payload.expected_schedule = schedule;
301
+ }
302
+ const hour = optionalInt(input.hour, '--hour', 0, 23);
303
+ if (hour !== undefined)
304
+ payload.expected_hour = hour;
305
+ const minute = optionalInt(input.minute, '--minute', 0, 59);
306
+ if (minute !== undefined)
307
+ payload.expected_minute = minute;
308
+ const dayOfWeek = optionalInt(input.dayOfWeek, '--day-of-week', 1, 7);
309
+ if (dayOfWeek !== undefined)
310
+ payload.expected_day_of_week = dayOfWeek;
311
+ const dayOfMonth = optionalInt(input.dayOfMonth, '--day-of-month', 1, 28);
312
+ if (dayOfMonth !== undefined)
313
+ payload.expected_day_of_month = dayOfMonth;
314
+ const tolerance = optionalInt(input.tolerance, '--tolerance', 1, 10080);
315
+ if (tolerance !== undefined)
316
+ payload.tolerance_minutes = tolerance;
317
+ const threshold = optionalInt(input.threshold, '--threshold', 1, 99);
318
+ if (threshold !== undefined)
319
+ payload.alert_threshold = threshold;
320
+ if (input.weekdaysOnly !== undefined)
321
+ payload.weekdays_only = input.weekdaysOnly;
322
+ if (input.alertOnMissed !== undefined)
323
+ payload.alert_on_missed_uploads = input.alertOnMissed;
324
+ if (input.alertConfig !== undefined) {
325
+ const parsed = readJsonOption(input.alertConfig, '--alert-config');
326
+ if (typeof parsed !== 'object' || parsed === null || Array.isArray(parsed)) {
327
+ throw new ApplianceApiError('--alert-config must be a JSON object.');
328
+ }
329
+ payload.alert_config = parsed;
330
+ }
331
+ if (payload.expected_schedule === 'weekly' && payload.expected_day_of_week === undefined) {
332
+ throw new ApplianceApiError('--day-of-week is required for a weekly schedule.');
333
+ }
334
+ if (payload.expected_schedule === 'monthly' && payload.expected_day_of_month === undefined) {
335
+ throw new ApplianceApiError('--day-of-month is required for a monthly schedule.');
336
+ }
337
+ return payload;
338
+ }
339
+ export async function listMonitoredAccounts(api) {
340
+ const response = await api.get('sftp_monitoring');
341
+ return unwrapList(response.data, 'data', 'accounts');
342
+ }
343
+ export async function getAccountMonitoring(api, account) {
344
+ const response = await api.get(`sftp_action_accounts/${account.id}/monitoring`);
345
+ return unwrapRecord(response.data, 'monitoring');
346
+ }
347
+ export function buildDiscoverPayload(input) {
348
+ const payload = {};
349
+ if (input.scheduleName !== undefined)
350
+ payload.schedule_name = input.scheduleName;
351
+ if (input.host !== undefined)
352
+ payload.host = input.host;
353
+ if (input.username !== undefined)
354
+ payload.username = input.username;
355
+ if (input.password !== undefined)
356
+ payload.password = input.password;
357
+ const callmanagerId = optionalInt(input.callmanagerId, '--callmanager-id', 1, Number.MAX_SAFE_INTEGER);
358
+ if (callmanagerId !== undefined)
359
+ payload.callmanager_id = callmanagerId;
360
+ const tolerance = optionalInt(input.tolerance, '--tolerance', 1, 10080);
361
+ if (tolerance !== undefined)
362
+ payload.tolerance_minutes = tolerance;
363
+ if ((payload.host || payload.username) && !payload.password && payload.callmanager_id === undefined) {
364
+ throw new ApplianceApiError('CUCM discovery needs --password/--password-stdin, or use --callmanager-id with stored credentials.');
365
+ }
366
+ if (Object.keys(payload).length === 0) {
367
+ throw new ApplianceApiError('Pass discovery credentials (--host, --username, --password) or --callmanager-id.');
368
+ }
369
+ return payload;
370
+ }
371
+ export async function collectSftpStatus(api, spec) {
372
+ const accountsPromise = supportsOperation(spec, 'GET sftp_action_accounts')
373
+ ? listAccounts(api)
374
+ : Promise.resolve([]);
375
+ const storagePromise = supportsOperation(spec, 'GET sftp_storage/status')
376
+ ? api
377
+ .get('sftp_storage/status')
378
+ .then(response => unwrapRecord(response.data, 'storage'))
379
+ .catch(() => null)
380
+ : Promise.resolve(null);
381
+ const monitoringPromise = supportsOperation(spec, 'GET sftp_monitoring')
382
+ ? listMonitoredAccounts(api).catch(() => [])
383
+ : Promise.resolve([]);
384
+ const missingPromise = supportsOperation(spec, 'GET sftp_monitoring/missing')
385
+ ? api
386
+ .get('sftp_monitoring/missing')
387
+ .then(response => unwrapList(response.data, 'accounts', 'data').length)
388
+ .catch(() => null)
389
+ : Promise.resolve(null);
390
+ const [accounts, storage, monitored, missingUploads] = await Promise.all([
391
+ accountsPromise,
392
+ storagePromise,
393
+ monitoringPromise,
394
+ missingPromise,
395
+ ]);
396
+ const enabled = accounts.filter(account => account.enabled).length;
397
+ const sftpOperations = listOperations(spec, { contains: 'sftp' }).length;
398
+ return {
399
+ accounts: {
400
+ total: accounts.length,
401
+ enabled,
402
+ disabled: accounts.length - enabled,
403
+ },
404
+ storage,
405
+ monitoring: {
406
+ available: supportsOperation(spec, 'GET sftp_monitoring'),
407
+ monitoredCount: monitored.filter(account => account.monitoring?.enabled).length,
408
+ missingUploads,
409
+ },
410
+ operations: {
411
+ total: sftpOperations,
412
+ specOpenApi: spec.openapi ?? null,
413
+ },
414
+ };
415
+ }
416
+ /** Read a CSV or JSON file for import. */
417
+ export function readImportFile(path) {
418
+ const data = readFileSync(path);
419
+ const filename = path.split(/[\\/]/).pop() ?? 'import.csv';
420
+ const contentType = filename.toLowerCase().endsWith('.json') ? 'application/json' : 'text/csv';
421
+ return { filename, data, contentType };
422
+ }
423
+ export function parseContactIds(values) {
424
+ if (!values || values.length === 0) {
425
+ throw new ApplianceApiError('At least one --contact <id> is required.');
426
+ }
427
+ return values.flatMap(value => value
428
+ .split(',')
429
+ .map(part => part.trim())
430
+ .filter(Boolean)
431
+ .map(part => {
432
+ const id = Number(part);
433
+ if (!Number.isInteger(id) || id <= 0) {
434
+ throw new ApplianceApiError(`Invalid contact ID: ${part}`);
435
+ }
436
+ return id;
437
+ }));
438
+ }
439
+ //# sourceMappingURL=sftp.js.map