@j-256/ccam 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +209 -0
  3. package/dist/auth/browser-login.d.ts +14 -0
  4. package/dist/auth/browser-login.js +72 -0
  5. package/dist/auth/manual-login.d.ts +10 -0
  6. package/dist/auth/manual-login.js +33 -0
  7. package/dist/auth/paths.d.ts +4 -0
  8. package/dist/auth/paths.js +15 -0
  9. package/dist/auth/profile-resolver.d.ts +26 -0
  10. package/dist/auth/profile-resolver.js +42 -0
  11. package/dist/auth/profile-store.d.ts +38 -0
  12. package/dist/auth/profile-store.js +125 -0
  13. package/dist/auth/prompt.d.ts +9 -0
  14. package/dist/auth/prompt.js +70 -0
  15. package/dist/bin.d.ts +3 -0
  16. package/dist/bin.js +4 -0
  17. package/dist/client-factory.d.ts +6 -0
  18. package/dist/client-factory.js +40 -0
  19. package/dist/commands/auth.d.ts +77 -0
  20. package/dist/commands/auth.js +387 -0
  21. package/dist/commands/client.d.ts +3 -0
  22. package/dist/commands/client.js +365 -0
  23. package/dist/commands/instance.d.ts +11 -0
  24. package/dist/commands/instance.js +128 -0
  25. package/dist/commands/org-config.d.ts +3 -0
  26. package/dist/commands/org-config.js +31 -0
  27. package/dist/commands/org.d.ts +11 -0
  28. package/dist/commands/org.js +234 -0
  29. package/dist/commands/permission.d.ts +3 -0
  30. package/dist/commands/permission.js +60 -0
  31. package/dist/commands/realm.d.ts +3 -0
  32. package/dist/commands/realm.js +58 -0
  33. package/dist/commands/role.d.ts +3 -0
  34. package/dist/commands/role.js +77 -0
  35. package/dist/commands/service-type.d.ts +3 -0
  36. package/dist/commands/service-type.js +57 -0
  37. package/dist/commands/user.d.ts +14 -0
  38. package/dist/commands/user.js +573 -0
  39. package/dist/error-handler.d.ts +2 -0
  40. package/dist/error-handler.js +28 -0
  41. package/dist/index.d.ts +2 -0
  42. package/dist/index.js +2 -0
  43. package/dist/output/csv.d.ts +3 -0
  44. package/dist/output/csv.js +57 -0
  45. package/dist/output/default-columns.d.ts +2 -0
  46. package/dist/output/default-columns.js +16 -0
  47. package/dist/output/detect.d.ts +4 -0
  48. package/dist/output/detect.js +6 -0
  49. package/dist/output/index.d.ts +15 -0
  50. package/dist/output/index.js +34 -0
  51. package/dist/output/json.d.ts +2 -0
  52. package/dist/output/json.js +10 -0
  53. package/dist/output/shared.d.ts +13 -0
  54. package/dist/output/shared.js +41 -0
  55. package/dist/output/table.d.ts +2 -0
  56. package/dist/output/table.js +72 -0
  57. package/dist/output/types.d.ts +2 -0
  58. package/dist/output/types.js +2 -0
  59. package/dist/output/yaml-fmt.d.ts +2 -0
  60. package/dist/output/yaml-fmt.js +11 -0
  61. package/dist/program.d.ts +3 -0
  62. package/dist/program.js +37 -0
  63. package/dist/shared.d.ts +46 -0
  64. package/dist/shared.js +96 -0
  65. package/dist/tui/App.d.ts +7 -0
  66. package/dist/tui/App.js +30 -0
  67. package/dist/tui/components/AuditTab.d.ts +8 -0
  68. package/dist/tui/components/AuditTab.js +80 -0
  69. package/dist/tui/components/FooterBar.d.ts +17 -0
  70. package/dist/tui/components/FooterBar.js +23 -0
  71. package/dist/tui/components/FullScreenLayout.d.ts +6 -0
  72. package/dist/tui/components/FullScreenLayout.js +11 -0
  73. package/dist/tui/components/HeaderBar.d.ts +5 -0
  74. package/dist/tui/components/HeaderBar.js +10 -0
  75. package/dist/tui/components/InfoTab.d.ts +8 -0
  76. package/dist/tui/components/InfoTab.js +70 -0
  77. package/dist/tui/components/ResourcePicker.d.ts +10 -0
  78. package/dist/tui/components/ResourcePicker.js +36 -0
  79. package/dist/tui/components/SubResourceTab.d.ts +7 -0
  80. package/dist/tui/components/SubResourceTab.js +193 -0
  81. package/dist/tui/components/TabBar.d.ts +11 -0
  82. package/dist/tui/components/TabBar.js +13 -0
  83. package/dist/tui/components/Table.d.ts +32 -0
  84. package/dist/tui/components/Table.js +175 -0
  85. package/dist/tui/context/client.d.ts +7 -0
  86. package/dist/tui/context/client.js +14 -0
  87. package/dist/tui/context/navigation.d.ts +14 -0
  88. package/dist/tui/context/navigation.js +25 -0
  89. package/dist/tui/context/terminal-size.d.ts +9 -0
  90. package/dist/tui/context/terminal-size.js +26 -0
  91. package/dist/tui/format.d.ts +20 -0
  92. package/dist/tui/format.js +57 -0
  93. package/dist/tui/hooks/use-audit-log.d.ts +12 -0
  94. package/dist/tui/hooks/use-audit-log.js +71 -0
  95. package/dist/tui/hooks/use-local-collection.d.ts +8 -0
  96. package/dist/tui/hooks/use-local-collection.js +30 -0
  97. package/dist/tui/hooks/use-paginated-resource.d.ts +23 -0
  98. package/dist/tui/hooks/use-paginated-resource.js +115 -0
  99. package/dist/tui/hooks/use-resource-detail.d.ts +7 -0
  100. package/dist/tui/hooks/use-resource-detail.js +30 -0
  101. package/dist/tui/hooks/use-scroll-window.d.ts +7 -0
  102. package/dist/tui/hooks/use-scroll-window.js +29 -0
  103. package/dist/tui/index.d.ts +2 -0
  104. package/dist/tui/index.js +22 -0
  105. package/dist/tui/navigation.d.ts +11 -0
  106. package/dist/tui/navigation.js +29 -0
  107. package/dist/tui/resource-configs/api-clients.d.ts +3 -0
  108. package/dist/tui/resource-configs/api-clients.js +118 -0
  109. package/dist/tui/resource-configs/index.d.ts +14 -0
  110. package/dist/tui/resource-configs/index.js +28 -0
  111. package/dist/tui/resource-configs/instances.d.ts +3 -0
  112. package/dist/tui/resource-configs/instances.js +24 -0
  113. package/dist/tui/resource-configs/org-configuration.d.ts +3 -0
  114. package/dist/tui/resource-configs/org-configuration.js +28 -0
  115. package/dist/tui/resource-configs/organizations.d.ts +3 -0
  116. package/dist/tui/resource-configs/organizations.js +104 -0
  117. package/dist/tui/resource-configs/permissions.d.ts +3 -0
  118. package/dist/tui/resource-configs/permissions.js +25 -0
  119. package/dist/tui/resource-configs/realms.d.ts +3 -0
  120. package/dist/tui/resource-configs/realms.js +36 -0
  121. package/dist/tui/resource-configs/roles.d.ts +3 -0
  122. package/dist/tui/resource-configs/roles.js +56 -0
  123. package/dist/tui/resource-configs/service-types.d.ts +3 -0
  124. package/dist/tui/resource-configs/service-types.js +24 -0
  125. package/dist/tui/resource-configs/users.d.ts +3 -0
  126. package/dist/tui/resource-configs/users.js +126 -0
  127. package/dist/tui/types.d.ts +99 -0
  128. package/dist/tui/types.js +23 -0
  129. package/dist/tui/views/ResourceDetailView.d.ts +7 -0
  130. package/dist/tui/views/ResourceDetailView.js +123 -0
  131. package/dist/tui/views/ResourceListView.d.ts +6 -0
  132. package/dist/tui/views/ResourceListView.js +140 -0
  133. package/dist/tui/views/ViewRouter.d.ts +2 -0
  134. package/dist/tui/views/ViewRouter.js +60 -0
  135. package/package.json +62 -0
@@ -0,0 +1,16 @@
1
+ export const DEFAULT_COLUMNS = {
2
+ user: ['mail', 'firstName', 'lastName', 'userState', 'primaryOrganization', 'lastLoginDate', 'roles'],
3
+ userDetail: ['id', 'mail', 'firstName', 'lastName', 'displayName', 'userState', 'primaryOrganization', 'organizations', 'roles', 'lastLoginDate', 'createdAt', 'lastModified'],
4
+ role: ['description', 'id', 'serviceType', 'scope', 'targetType', 'privileged', 'twoFAEnabled'],
5
+ roleDetail: ['description', 'id', 'serviceType', 'scope', 'targetType', 'privileged', 'twoFAEnabled', 'permissions'],
6
+ org: ['name', 'id', 'type', 'twoFAEnabled'],
7
+ orgDetail: ['name', 'id', 'type', 'twoFAEnabled', 'emailDomains', 'sfAccountIds', 'realms'],
8
+ client: ['id', 'name', 'active', 'organizationCount', 'tokenEndpointAuthMethod', 'lastAuthenticatedDate'],
9
+ clientDetail: ['id', 'name', 'description', 'active', 'organizations', 'roles', 'scopes', 'tokenEndpointAuthMethod', 'lastAuthenticatedDate', 'createdAt'],
10
+ instance: ['id', 'description', 'podId', 'tenantType'],
11
+ realm: ['id', 'description', 'customerName', 'organizationId'],
12
+ permission: ['name', 'adminPermission'],
13
+ serviceType: ['id', 'description'],
14
+ auditLog: ['timestamp', 'eventType', 'eventMessage', 'authorDisplayName', 'authorEmail'],
15
+ };
16
+ //# sourceMappingURL=default-columns.js.map
@@ -0,0 +1,4 @@
1
+ import type { OutputFormat } from './types.js';
2
+ export type { OutputFormat };
3
+ export declare function resolveFormat(explicit: string | undefined, isTTY: boolean): OutputFormat;
4
+ //# sourceMappingURL=detect.d.ts.map
@@ -0,0 +1,6 @@
1
+ export function resolveFormat(explicit, isTTY) {
2
+ if (explicit)
3
+ return explicit;
4
+ return isTTY ? 'table' : 'json';
5
+ }
6
+ //# sourceMappingURL=detect.js.map
@@ -0,0 +1,15 @@
1
+ export type { OutputFormat } from './types.js';
2
+ export { resolveFormat } from './detect.js';
3
+ export { formatJson } from './json.js';
4
+ export { formatCsv, formatTsv } from './csv.js';
5
+ export { formatYaml } from './yaml-fmt.js';
6
+ export { formatTable } from './table.js';
7
+ export { DEFAULT_COLUMNS } from './default-columns.js';
8
+ import type { OutputFormat } from './types.js';
9
+ export interface RenderOptions {
10
+ format: OutputFormat;
11
+ fields?: string[];
12
+ defaultFields?: string[];
13
+ }
14
+ export declare function renderOutput(data: unknown, options: RenderOptions): void;
15
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1,34 @@
1
+ export { resolveFormat } from './detect.js';
2
+ export { formatJson } from './json.js';
3
+ export { formatCsv, formatTsv } from './csv.js';
4
+ export { formatYaml } from './yaml-fmt.js';
5
+ export { formatTable } from './table.js';
6
+ export { DEFAULT_COLUMNS } from './default-columns.js';
7
+ import { formatJson } from './json.js';
8
+ import { formatCsv, formatTsv } from './csv.js';
9
+ import { formatYaml } from './yaml-fmt.js';
10
+ import { formatTable } from './table.js';
11
+ export function renderOutput(data, options) {
12
+ let output;
13
+ switch (options.format) {
14
+ case 'json':
15
+ output = formatJson(data, options.fields);
16
+ break;
17
+ case 'csv':
18
+ output = formatCsv(data, options.fields);
19
+ break;
20
+ case 'tsv':
21
+ output = formatTsv(data, options.fields);
22
+ break;
23
+ case 'yaml':
24
+ output = formatYaml(data, options.fields);
25
+ break;
26
+ case 'table':
27
+ output = formatTable(data, options.fields ?? options.defaultFields);
28
+ break;
29
+ default:
30
+ throw new Error(`Unsupported format: ${options.format}`);
31
+ }
32
+ process.stdout.write(output + '\n');
33
+ }
34
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,2 @@
1
+ export declare function formatJson(data: unknown, fields?: string[]): string;
2
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1,10 @@
1
+ import { applyFieldSelection } from './shared.js';
2
+ export function formatJson(data, fields) {
3
+ if (!fields) {
4
+ return JSON.stringify(data, null, 2);
5
+ }
6
+ // Apply field selection
7
+ const filtered = applyFieldSelection(data, fields);
8
+ return JSON.stringify(filtered, null, 2);
9
+ }
10
+ //# sourceMappingURL=json.js.map
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Shared utilities for output formatters.
3
+ */
4
+ /** Collect the union of top-level keys across a list of objects. */
5
+ export declare function extractColumns(data: unknown[]): string[];
6
+ /** Return the top-level property of an object by key. */
7
+ export declare function getNestedValue(obj: unknown, path: string): unknown;
8
+ /**
9
+ * Apply a field allowlist to a single object or an array of objects.
10
+ * Used by JSON/YAML formatters where the goal is to drop un-requested fields.
11
+ */
12
+ export declare function applyFieldSelection(data: unknown, fields: string[]): unknown;
13
+ //# sourceMappingURL=shared.d.ts.map
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Shared utilities for output formatters.
3
+ */
4
+ /** Collect the union of top-level keys across a list of objects. */
5
+ export function extractColumns(data) {
6
+ const columns = new Set();
7
+ for (const item of data) {
8
+ if (typeof item === 'object' && item !== null) {
9
+ Object.keys(item).forEach((key) => columns.add(key));
10
+ }
11
+ }
12
+ return Array.from(columns);
13
+ }
14
+ /** Return the top-level property of an object by key. */
15
+ export function getNestedValue(obj, path) {
16
+ if (typeof obj !== 'object' || obj === null)
17
+ return undefined;
18
+ return obj[path];
19
+ }
20
+ /**
21
+ * Apply a field allowlist to a single object or an array of objects.
22
+ * Used by JSON/YAML formatters where the goal is to drop un-requested fields.
23
+ */
24
+ export function applyFieldSelection(data, fields) {
25
+ if (Array.isArray(data)) {
26
+ return data.map((item) => pickFields(item, fields));
27
+ }
28
+ return pickFields(data, fields);
29
+ }
30
+ function pickFields(obj, fields) {
31
+ if (typeof obj !== 'object' || obj === null)
32
+ return obj;
33
+ const result = {};
34
+ for (const field of fields) {
35
+ if (field in obj) {
36
+ result[field] = obj[field];
37
+ }
38
+ }
39
+ return result;
40
+ }
41
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1,2 @@
1
+ export declare function formatTable(data: unknown, fields?: string[]): string;
2
+ //# sourceMappingURL=table.d.ts.map
@@ -0,0 +1,72 @@
1
+ import Table from 'cli-table3';
2
+ import chalk from 'chalk';
3
+ import { extractColumns, getNestedValue } from './shared.js';
4
+ const MAX_CELL_LENGTH = 60;
5
+ export function formatTable(data, fields) {
6
+ let items;
7
+ if (!Array.isArray(data)) {
8
+ items = [data];
9
+ }
10
+ else {
11
+ items = data;
12
+ }
13
+ if (items.length === 0) {
14
+ return 'No results.';
15
+ }
16
+ // Determine columns
17
+ const columns = fields || extractColumns(items);
18
+ if (columns.length === 0) {
19
+ return 'No results.';
20
+ }
21
+ // Create table with styled headers
22
+ const table = new Table({
23
+ head: columns.map((col) => chalk.gray(col)),
24
+ style: {
25
+ head: [],
26
+ border: [],
27
+ },
28
+ });
29
+ // Add data rows
30
+ for (const item of items) {
31
+ const row = columns.map((col) => {
32
+ const value = getNestedValue(item, col);
33
+ return formatCell(value);
34
+ });
35
+ table.push(row);
36
+ }
37
+ return table.toString();
38
+ }
39
+ function formatCell(value) {
40
+ if (value === null || value === undefined) {
41
+ return chalk.dim('-');
42
+ }
43
+ let stringValue;
44
+ if (Array.isArray(value)) {
45
+ stringValue = value.join(', ');
46
+ }
47
+ else if (typeof value === 'object') {
48
+ stringValue = JSON.stringify(value);
49
+ }
50
+ else {
51
+ stringValue = String(value);
52
+ }
53
+ // Truncate long values before colorizing so ANSI escapes aren't split.
54
+ if (stringValue.length > MAX_CELL_LENGTH) {
55
+ stringValue = stringValue.slice(0, MAX_CELL_LENGTH - 3) + '...';
56
+ }
57
+ // Apply color coding for known status values after truncation.
58
+ return colorizeStatus(stringValue);
59
+ }
60
+ function colorizeStatus(value) {
61
+ if (value === 'ENABLED' || value === 'true') {
62
+ return chalk.green(value);
63
+ }
64
+ if (value === 'DELETED') {
65
+ return chalk.red(value);
66
+ }
67
+ if (value === 'false') {
68
+ return chalk.dim(value);
69
+ }
70
+ return value;
71
+ }
72
+ //# sourceMappingURL=table.js.map
@@ -0,0 +1,2 @@
1
+ export type OutputFormat = 'table' | 'json' | 'csv' | 'tsv' | 'yaml';
2
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1,2 @@
1
+ export declare function formatYaml(data: unknown, fields?: string[]): string;
2
+ //# sourceMappingURL=yaml-fmt.d.ts.map
@@ -0,0 +1,11 @@
1
+ import { stringify } from 'yaml';
2
+ import { applyFieldSelection } from './shared.js';
3
+ export function formatYaml(data, fields) {
4
+ if (!fields) {
5
+ return stringify(data);
6
+ }
7
+ // Apply field selection
8
+ const filtered = applyFieldSelection(data, fields);
9
+ return stringify(filtered);
10
+ }
11
+ //# sourceMappingURL=yaml-fmt.js.map
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare const program: Command;
3
+ //# sourceMappingURL=program.d.ts.map
@@ -0,0 +1,37 @@
1
+ import { Command } from 'commander';
2
+ import { registerAuthCommands } from './commands/auth.js';
3
+ import { registerUserCommands } from './commands/user.js';
4
+ import { registerOrgCommands } from './commands/org.js';
5
+ import { registerClientCommands } from './commands/client.js';
6
+ import { registerRoleCommands } from './commands/role.js';
7
+ import { registerRealmCommands } from './commands/realm.js';
8
+ import { registerPermissionCommands } from './commands/permission.js';
9
+ import { registerServiceTypeCommands } from './commands/service-type.js';
10
+ import { registerInstanceCommands } from './commands/instance.js';
11
+ import { registerOrgConfigCommands } from './commands/org-config.js';
12
+ export const program = new Command()
13
+ .name('ccam')
14
+ .description('CLI for the Salesforce Commerce Cloud Account Manager API')
15
+ .version('0.1.0')
16
+ .option('-i, --interactive', 'Launch interactive TUI')
17
+ .action(async (options) => {
18
+ if (options.interactive || process.stdout.isTTY) {
19
+ const { startTui } = await import('./tui/index.js');
20
+ await startTui();
21
+ }
22
+ else {
23
+ program.help();
24
+ }
25
+ });
26
+ // Register all command groups
27
+ registerAuthCommands(program);
28
+ registerUserCommands(program);
29
+ registerOrgCommands(program);
30
+ registerClientCommands(program);
31
+ registerRoleCommands(program);
32
+ registerRealmCommands(program);
33
+ registerPermissionCommands(program);
34
+ registerServiceTypeCommands(program);
35
+ registerInstanceCommands(program);
36
+ registerOrgConfigCommands(program);
37
+ //# sourceMappingURL=program.js.map
@@ -0,0 +1,46 @@
1
+ import { Command } from 'commander';
2
+ import type { OutputFormat } from './output/index.js';
3
+ export interface GlobalOptions {
4
+ format?: string;
5
+ fields?: string;
6
+ page?: string;
7
+ size?: string;
8
+ sort?: string;
9
+ profile?: string;
10
+ host?: string;
11
+ json?: boolean;
12
+ }
13
+ export interface ParsedSort {
14
+ field: string;
15
+ direction: 'asc' | 'desc';
16
+ }
17
+ export interface ResolvedGlobalOptions {
18
+ format?: OutputFormat;
19
+ fields?: string[];
20
+ page?: number;
21
+ size?: number;
22
+ sort?: ParsedSort;
23
+ profile?: string;
24
+ host?: string;
25
+ }
26
+ export declare function parseFields(value: string | undefined): string[] | undefined;
27
+ export declare function parseSort(value: string | undefined): ParsedSort | undefined;
28
+ export declare function parsePageSize(value: string | undefined, defaultValue?: number): number | undefined;
29
+ /**
30
+ * Parse and validate a user-provided --expand flag against an allowed set.
31
+ * Throws with a clear message on unknown values, so the user gets a CLI error
32
+ * instead of the AM server silently ignoring the parameter.
33
+ *
34
+ * @param input - raw --expand value (e.g. 'organizations')
35
+ * @param allowed - array of allowed expand tokens for this endpoint
36
+ * @returns the validated expand value, or undefined if input was undefined
37
+ */
38
+ export declare function parseExpand<T extends string>(input: string | undefined, allowed: readonly T[]): T | undefined;
39
+ export declare function addGlobalOptions(cmd: Command): Command;
40
+ /**
41
+ * Write "Page X of Y (N total)" to stderr if the format is 'table' and the
42
+ * result has page metadata. No-op otherwise.
43
+ */
44
+ export declare function writePageInfoIfTable(format: string, result: unknown): void;
45
+ export declare function resolveGlobalOptions(opts: GlobalOptions): ResolvedGlobalOptions;
46
+ //# sourceMappingURL=shared.d.ts.map
package/dist/shared.js ADDED
@@ -0,0 +1,96 @@
1
+ export function parseFields(value) {
2
+ if (!value)
3
+ return undefined;
4
+ return value.split(',').map((field) => field.trim()).filter((field) => field.length > 0);
5
+ }
6
+ export function parseSort(value) {
7
+ if (!value)
8
+ return undefined;
9
+ const [field, direction = 'asc'] = value.split(':');
10
+ if (!field) {
11
+ throw new Error(`Invalid sort field: empty. Use "field:asc" or "field:desc"`);
12
+ }
13
+ if (direction !== 'asc' && direction !== 'desc') {
14
+ throw new Error(`Invalid sort direction: "${direction}". Use "asc" or "desc"`);
15
+ }
16
+ return { field, direction };
17
+ }
18
+ export function parsePageSize(value, defaultValue) {
19
+ if (!value)
20
+ return defaultValue;
21
+ const parsed = parseInt(value, 10);
22
+ if (isNaN(parsed) || parsed < 0) {
23
+ throw new Error(`Invalid number: ${value}`);
24
+ }
25
+ return parsed;
26
+ }
27
+ /**
28
+ * Parse and validate a user-provided --expand flag against an allowed set.
29
+ * Throws with a clear message on unknown values, so the user gets a CLI error
30
+ * instead of the AM server silently ignoring the parameter.
31
+ *
32
+ * @param input - raw --expand value (e.g. 'organizations')
33
+ * @param allowed - array of allowed expand tokens for this endpoint
34
+ * @returns the validated expand value, or undefined if input was undefined
35
+ */
36
+ export function parseExpand(input, allowed) {
37
+ if (input === undefined)
38
+ return undefined;
39
+ if (!allowed.includes(input)) {
40
+ throw new Error(`Invalid --expand value: "${input}". Allowed: ${allowed.join(', ')}`);
41
+ }
42
+ return input;
43
+ }
44
+ export function addGlobalOptions(cmd) {
45
+ return cmd
46
+ .option('--format <format>', 'Output format: table, json, csv, tsv, yaml')
47
+ .option('--fields <fields>', 'Comma-separated list of fields to include')
48
+ .option('--page <page>', 'Page number for pagination', '0')
49
+ .option('--size <size>', 'Page size for pagination', '25')
50
+ .option('--sort <sort>', 'Sort field and direction (e.g., name:asc)')
51
+ .option('--profile <profile>', 'Profile name from config')
52
+ .option('--host <host>', 'API host URL (default: https://account.demandware.com)')
53
+ .option('-j, --json', 'Shorthand for --format json');
54
+ }
55
+ /**
56
+ * Write "Page X of Y (N total)" to stderr if the format is 'table' and the
57
+ * result has page metadata. No-op otherwise.
58
+ */
59
+ export function writePageInfoIfTable(format, result) {
60
+ if (format !== 'table')
61
+ return;
62
+ if (!result || typeof result !== 'object')
63
+ return;
64
+ if (!('page' in result))
65
+ return;
66
+ const page = result.page;
67
+ process.stderr.write(`Page ${page.number + 1} of ${page.totalPages} (${page.totalElements} total)\n`);
68
+ }
69
+ export function resolveGlobalOptions(opts) {
70
+ const resolved = {
71
+ profile: opts.profile,
72
+ };
73
+ // -j flag overrides format
74
+ if (opts.json) {
75
+ resolved.format = 'json';
76
+ }
77
+ else if (opts.format) {
78
+ resolved.format = opts.format;
79
+ }
80
+ // Parse fields
81
+ if (opts.fields) {
82
+ resolved.fields = parseFields(opts.fields);
83
+ }
84
+ // Parse page and size
85
+ resolved.page = parsePageSize(opts.page, 0);
86
+ resolved.size = parsePageSize(opts.size, 25);
87
+ // Parse sort
88
+ if (opts.sort) {
89
+ resolved.sort = parseSort(opts.sort);
90
+ }
91
+ if (opts.host) {
92
+ resolved.host = opts.host;
93
+ }
94
+ return resolved;
95
+ }
96
+ //# sourceMappingURL=shared.js.map
@@ -0,0 +1,7 @@
1
+ import type { CcamClient } from 'ccam-sdk';
2
+ export interface AppProps {
3
+ client: CcamClient;
4
+ host?: string;
5
+ }
6
+ export declare function App({ client, host }: AppProps): import("react/jsx-runtime").JSX.Element;
7
+ //# sourceMappingURL=App.d.ts.map
@@ -0,0 +1,30 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { useApp, useInput } from 'ink';
3
+ import { ClientProvider } from './context/client.js';
4
+ import { TerminalSizeProvider } from './context/terminal-size.js';
5
+ import { NavigationProvider, useNav } from './context/navigation.js';
6
+ import { FullScreenLayout } from './components/FullScreenLayout.js';
7
+ import { ViewRouter } from './views/ViewRouter.js';
8
+ function AppInner({ host }) {
9
+ const nav = useNav();
10
+ const { exit } = useApp();
11
+ useInput((input, key) => {
12
+ if (input === 'q' && nav.current.view === 'resource-picker') {
13
+ exit();
14
+ return;
15
+ }
16
+ if (key.escape || input === 'q') {
17
+ if (nav.canGoBack) {
18
+ nav.pop();
19
+ }
20
+ else {
21
+ exit();
22
+ }
23
+ }
24
+ });
25
+ return (_jsx(FullScreenLayout, { host: host, children: _jsx(ViewRouter, {}) }));
26
+ }
27
+ export function App({ client, host }) {
28
+ return (_jsx(ClientProvider, { client: client, children: _jsx(TerminalSizeProvider, { children: _jsx(NavigationProvider, { initial: { view: 'resource-picker', label: 'Home' }, children: _jsx(AppInner, { host: host }) }) }) }));
29
+ }
30
+ //# sourceMappingURL=App.js.map
@@ -0,0 +1,8 @@
1
+ import type { ColumnDef } from '../types.js';
2
+ import type { AuditLogRecord, ContentResponse } from 'ccam-sdk';
3
+ export interface AuditTabProps {
4
+ fetchFn: (querySize?: number) => Promise<ContentResponse<AuditLogRecord>>;
5
+ columns?: ColumnDef[];
6
+ }
7
+ export declare function AuditTab({ fetchFn, columns }: AuditTabProps): import("react/jsx-runtime").JSX.Element;
8
+ //# sourceMappingURL=AuditTab.d.ts.map
@@ -0,0 +1,80 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { useState, useCallback } from 'react';
3
+ import { Box, Text, useInput } from 'ink';
4
+ import Spinner from 'ink-spinner';
5
+ import { useAuditLog } from '../hooks/use-audit-log.js';
6
+ import { useScrollWindow } from '../hooks/use-scroll-window.js';
7
+ import { useTerminalSize } from '../context/terminal-size.js';
8
+ import { Table } from './Table.js';
9
+ import { HintText } from './FooterBar.js';
10
+ const BORDER_INSET = 2;
11
+ const DEFAULT_COLUMNS = [
12
+ { key: 'timestamp', label: 'Time', width: 2 },
13
+ { key: 'authorDisplayName', label: 'Author', width: 2 },
14
+ { key: 'eventType', label: 'Event', width: 2 },
15
+ { key: 'eventMessage', label: 'Message', width: 4 },
16
+ ];
17
+ export function AuditTab({ fetchFn, columns }) {
18
+ const { cols } = useTerminalSize();
19
+ const [highlight, setHighlight] = useState(0);
20
+ const { data, loading, error, canLoadMore, needsConfirmation, loadMore, confirmLoadAll, retry, } = useAuditLog(fetchFn);
21
+ const scroll = useScrollWindow(highlight, data.length);
22
+ const handleLoadMore = useCallback(() => {
23
+ if (needsConfirmation) {
24
+ confirmLoadAll();
25
+ }
26
+ else {
27
+ loadMore();
28
+ }
29
+ }, [needsConfirmation, confirmLoadAll, loadMore]);
30
+ useInput((input, key) => {
31
+ if (loading)
32
+ return;
33
+ if (error) {
34
+ if (input === 'r')
35
+ retry();
36
+ return;
37
+ }
38
+ // Vertical movement
39
+ if (input === 'j' || key.downArrow) {
40
+ setHighlight((i) => Math.min(i + 1, data.length - 1));
41
+ }
42
+ if (input === 'k' || key.upArrow) {
43
+ setHighlight((i) => Math.max(i - 1, 0));
44
+ }
45
+ // Jump to first/last
46
+ if (input === 'g')
47
+ setHighlight(0);
48
+ if (input === 'G')
49
+ setHighlight(Math.max(0, data.length - 1));
50
+ // Load more
51
+ if (input === 'm' && canLoadMore) {
52
+ handleLoadMore();
53
+ }
54
+ // Scroll
55
+ if (input === 'n') {
56
+ setHighlight((i) => Math.min(i + scroll.visibleRows, data.length - 1));
57
+ }
58
+ if (input === 'p') {
59
+ setHighlight((i) => Math.max(i - scroll.visibleRows, 0));
60
+ }
61
+ // Refresh (resets to initial querySize)
62
+ if (input === 'r')
63
+ retry();
64
+ });
65
+ if (error) {
66
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "red", children: ["Error: ", error.message] }), _jsx(Text, { dimColor: true, children: "r:retry" })] }));
67
+ }
68
+ if (loading && data.length === 0) {
69
+ return (_jsxs(Box, { children: [_jsx(Text, { color: "cyan", children: _jsx(Spinner, { type: "dots" }) }), _jsx(Text, { children: " Loading audit log..." })] }));
70
+ }
71
+ // Footer hints
72
+ const hintParts = ['[j/k:nav]'];
73
+ if (canLoadMore) {
74
+ hintParts.push(needsConfirmation ? '[m:load all]' : '[m:more]');
75
+ }
76
+ hintParts.push('[r:refresh]');
77
+ const hints = hintParts.join(' ');
78
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Table, { columns: columns ?? DEFAULT_COLUMNS, data: data, highlightIndex: highlight, visibleRows: scroll.visibleRows, scrollOffset: scroll.scrollOffset, contentWidth: cols - BORDER_INSET }), _jsxs(Box, { justifyContent: "space-between", children: [_jsx(HintText, { hints: hints }), _jsxs(Text, { dimColor: true, children: [data.length, " records", loading ? ' (loading...)' : ''] })] })] }));
79
+ }
80
+ //# sourceMappingURL=AuditTab.js.map
@@ -0,0 +1,17 @@
1
+ export interface FooterBarProps {
2
+ hints?: string;
3
+ pageInfo?: string;
4
+ loading?: boolean;
5
+ sortLabel?: string;
6
+ statsLabel?: string;
7
+ }
8
+ /**
9
+ * Format hint strings in bracket notation.
10
+ * Input format: "[j/k:nav] [Enter:open] [Esc:back]"
11
+ * Each segment: "[key:description]"
12
+ */
13
+ export declare function HintText({ hints }: {
14
+ hints: string;
15
+ }): import("react/jsx-runtime").JSX.Element;
16
+ export declare function FooterBar({ hints, pageInfo, loading, sortLabel, statsLabel }: FooterBarProps): import("react/jsx-runtime").JSX.Element;
17
+ //# sourceMappingURL=FooterBar.d.ts.map
@@ -0,0 +1,23 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import Spinner from 'ink-spinner';
4
+ /**
5
+ * Format hint strings in bracket notation.
6
+ * Input format: "[j/k:nav] [Enter:open] [Esc:back]"
7
+ * Each segment: "[key:description]"
8
+ */
9
+ export function HintText({ hints }) {
10
+ const segments = hints.match(/\[[^\]]+\]/g) || [];
11
+ return (_jsx(Text, { children: segments.map((seg, i) => {
12
+ // Strip brackets, split on first colon
13
+ const inner = seg.slice(1, -1);
14
+ const colonIdx = inner.indexOf(':');
15
+ const key = colonIdx > 0 ? inner.slice(0, colonIdx) : inner;
16
+ const desc = colonIdx > 0 ? inner.slice(colonIdx) : '';
17
+ return (_jsxs(Text, { children: [i > 0 && _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: "[" }), _jsx(Text, { bold: true, color: "cyan", children: key }), _jsxs(Text, { dimColor: true, children: [desc, "]"] })] }, i));
18
+ }) }));
19
+ }
20
+ export function FooterBar({ hints, pageInfo, loading, sortLabel, statsLabel }) {
21
+ return (_jsxs(Box, { flexDirection: "column", borderStyle: "single", borderColor: "gray", children: [(loading || sortLabel || statsLabel) && (_jsxs(Box, { justifyContent: "space-between", children: [_jsxs(Box, { children: [loading && (_jsxs(Text, { color: "cyan", children: [_jsx(Spinner, { type: "dots" }), ' '] })), sortLabel && _jsx(Text, { dimColor: true, children: sortLabel })] }), statsLabel && _jsx(Text, { dimColor: true, children: statsLabel })] })), _jsxs(Box, { justifyContent: "space-between", children: [hints && _jsx(HintText, { hints: hints }), pageInfo && _jsx(Text, { dimColor: true, children: pageInfo })] })] }));
22
+ }
23
+ //# sourceMappingURL=FooterBar.js.map
@@ -0,0 +1,6 @@
1
+ import type { HeaderBarProps } from './HeaderBar.js';
2
+ export interface FullScreenLayoutProps extends HeaderBarProps {
3
+ children: React.ReactNode;
4
+ }
5
+ export declare function FullScreenLayout({ children, host, }: FullScreenLayoutProps): import("react/jsx-runtime").JSX.Element;
6
+ //# sourceMappingURL=FullScreenLayout.d.ts.map
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box } from 'ink';
3
+ import { useTerminalSize } from '../context/terminal-size.js';
4
+ import { HeaderBar } from './HeaderBar.js';
5
+ export function FullScreenLayout({ children, host, }) {
6
+ const { rows } = useTerminalSize();
7
+ // Header: 1 line top border + 1 line breadcrumb + 1 line bottom border = 3
8
+ const contentHeight = rows - 3;
9
+ return (_jsxs(Box, { flexDirection: "column", height: rows, children: [_jsx(HeaderBar, { host: host }), _jsx(Box, { height: contentHeight, children: children })] }));
10
+ }
11
+ //# sourceMappingURL=FullScreenLayout.js.map
@@ -0,0 +1,5 @@
1
+ export interface HeaderBarProps {
2
+ host?: string;
3
+ }
4
+ export declare function HeaderBar({ host }: HeaderBarProps): import("react/jsx-runtime").JSX.Element;
5
+ //# sourceMappingURL=HeaderBar.d.ts.map
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { useNav } from '../context/navigation.js';
4
+ export function HeaderBar({ host }) {
5
+ const { breadcrumbs } = useNav();
6
+ return (_jsxs(Box, { borderStyle: "single", borderColor: "gray", justifyContent: "space-between", children: [_jsxs(Text, { children: [_jsx(Text, { color: "cyan", bold: true, children: "ccam" }), breadcrumbs.map((crumb, i) => (_jsxs(Text, { children: [_jsxs(Text, { dimColor: true, children: [" ", '>', " "] }), i === breadcrumbs.length - 1
7
+ ? _jsx(Text, { bold: true, color: "white", children: crumb })
8
+ : _jsx(Text, { children: crumb })] }, i)))] }), host && _jsx(Text, { dimColor: true, children: host })] }));
9
+ }
10
+ //# sourceMappingURL=HeaderBar.js.map