@rglabs/butterfly 2.0.1

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 (117) hide show
  1. package/CLAUDE.md +201 -0
  2. package/README.md +371 -0
  3. package/dist/commands/add.d.ts +23 -0
  4. package/dist/commands/add.js +303 -0
  5. package/dist/commands/code.d.ts +11 -0
  6. package/dist/commands/code.js +72 -0
  7. package/dist/commands/create-object.d.ts +6 -0
  8. package/dist/commands/create-object.js +293 -0
  9. package/dist/commands/create-report.d.ts +6 -0
  10. package/dist/commands/create-report.js +154 -0
  11. package/dist/commands/diff.d.ts +4 -0
  12. package/dist/commands/diff.js +238 -0
  13. package/dist/commands/download.d.ts +4 -0
  14. package/dist/commands/download.js +374 -0
  15. package/dist/commands/layout.d.ts +12 -0
  16. package/dist/commands/layout.js +83 -0
  17. package/dist/commands/record.d.ts +21 -0
  18. package/dist/commands/record.js +483 -0
  19. package/dist/commands/run-poc.d.ts +3 -0
  20. package/dist/commands/run-poc.js +18 -0
  21. package/dist/commands/setup.d.ts +3 -0
  22. package/dist/commands/setup.js +66 -0
  23. package/dist/commands/start-poc.d.ts +3 -0
  24. package/dist/commands/start-poc.js +55 -0
  25. package/dist/commands/sync-docs.d.ts +3 -0
  26. package/dist/commands/sync-docs.js +27 -0
  27. package/dist/commands/translate.d.ts +13 -0
  28. package/dist/commands/translate.js +401 -0
  29. package/dist/commands/upload.d.ts +3 -0
  30. package/dist/commands/upload.js +150 -0
  31. package/dist/commands/workflow-info.d.ts +13 -0
  32. package/dist/commands/workflow-info.js +161 -0
  33. package/dist/components/ConflictResolver.d.ts +12 -0
  34. package/dist/components/ConflictResolver.js +77 -0
  35. package/dist/components/DiffView.d.ts +11 -0
  36. package/dist/components/DiffView.js +101 -0
  37. package/dist/components/DownloadProgress.d.ts +11 -0
  38. package/dist/components/DownloadProgress.js +29 -0
  39. package/dist/components/RecordPreview.d.ts +11 -0
  40. package/dist/components/RecordPreview.js +91 -0
  41. package/dist/components/SetupForm.d.ts +8 -0
  42. package/dist/components/SetupForm.js +56 -0
  43. package/dist/components/UploadProgress.d.ts +13 -0
  44. package/dist/components/UploadProgress.js +42 -0
  45. package/dist/diff/adapters/index.d.ts +8 -0
  46. package/dist/diff/adapters/index.js +18 -0
  47. package/dist/diff/adapters/objectsAdapter.d.ts +13 -0
  48. package/dist/diff/adapters/objectsAdapter.js +177 -0
  49. package/dist/diff/adapters/reportsAdapter.d.ts +14 -0
  50. package/dist/diff/adapters/reportsAdapter.js +212 -0
  51. package/dist/diff/adapters/types.d.ts +19 -0
  52. package/dist/diff/adapters/types.js +2 -0
  53. package/dist/diff/engine.d.ts +19 -0
  54. package/dist/diff/engine.js +57 -0
  55. package/dist/diff/types.d.ts +34 -0
  56. package/dist/diff/types.js +110 -0
  57. package/dist/index.d.ts +3 -0
  58. package/dist/index.js +117 -0
  59. package/dist/types/index.d.ts +18 -0
  60. package/dist/types/index.js +2 -0
  61. package/dist/utils/api.d.ts +85 -0
  62. package/dist/utils/api.js +1031 -0
  63. package/dist/utils/auth.d.ts +4 -0
  64. package/dist/utils/auth.js +22 -0
  65. package/dist/utils/bfySplitter.d.ts +12 -0
  66. package/dist/utils/bfySplitter.js +151 -0
  67. package/dist/utils/docs.d.ts +16 -0
  68. package/dist/utils/docs.js +186 -0
  69. package/dist/utils/errorLogger.d.ts +6 -0
  70. package/dist/utils/errorLogger.js +29 -0
  71. package/dist/utils/files.d.ts +14 -0
  72. package/dist/utils/files.js +772 -0
  73. package/dist/utils/lockManager.d.ts +15 -0
  74. package/dist/utils/lockManager.js +126 -0
  75. package/dist/utils/resourceHandlers.d.ts +50 -0
  76. package/dist/utils/resourceHandlers.js +684 -0
  77. package/dist/utils/resourceMapping.d.ts +32 -0
  78. package/dist/utils/resourceMapping.js +210 -0
  79. package/dist/utils/singleResourceDownload.d.ts +14 -0
  80. package/dist/utils/singleResourceDownload.js +261 -0
  81. package/dist/utils/summaryGenerator.d.ts +2 -0
  82. package/dist/utils/summaryGenerator.js +183 -0
  83. package/dist/utils/uploadHandler.d.ts +31 -0
  84. package/dist/utils/uploadHandler.js +263 -0
  85. package/docs/AI_API.md +93 -0
  86. package/docs/CLAUDE.md +216 -0
  87. package/docs/PROJECT_SPECIFIC.md +1 -0
  88. package/docs/RECORD_COMMAND.md +262 -0
  89. package/docs/WORKFLOW_API.md +480 -0
  90. package/docs/bfy-splitting.md +126 -0
  91. package/docs/cli-commands.md +333 -0
  92. package/docs/examples/README.md +95 -0
  93. package/docs/examples/order-system.md +147 -0
  94. package/docs/examples/product-catalog.md +195 -0
  95. package/docs/examples/reports.md +187 -0
  96. package/docs/excel-export.md +216 -0
  97. package/docs/field-types/README.md +29 -0
  98. package/docs/field-types/calculated.md +147 -0
  99. package/docs/field-types/code-mappings.md +84 -0
  100. package/docs/field-types/custom.md +340 -0
  101. package/docs/object-specs/README.md +136 -0
  102. package/docs/object-specs/code-parameters.md +151 -0
  103. package/docs/object-specs/creating.md +203 -0
  104. package/docs/object-specs/js-code-examples.md +208 -0
  105. package/docs/object-specs/js-field-updates.md +168 -0
  106. package/docs/objects/README.md +89 -0
  107. package/docs/objects/creating.md +127 -0
  108. package/docs/page-layout.md +361 -0
  109. package/docs/permissions.md +260 -0
  110. package/docs/reports.md +197 -0
  111. package/docs/state-machines.md +544 -0
  112. package/docs/tasks/create-object.md +81 -0
  113. package/docs/translations.md +346 -0
  114. package/docs/twig-helpers.md +283 -0
  115. package/docs/webservices.md +159 -0
  116. package/docs/workspaces.md +176 -0
  117. package/package.json +59 -0
@@ -0,0 +1,150 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { render, Box, Text } from 'ink';
3
+ import { join, resolve, basename, dirname } from 'path';
4
+ import { readdir, stat, access } from 'fs/promises';
5
+ import { loadAuthConfig } from '../utils/auth.js';
6
+ import { ButterflyAPI } from '../utils/api.js';
7
+ import { UploadProgress } from '../components/UploadProgress.js';
8
+ import { UploadHandler } from '../utils/uploadHandler.js';
9
+ async function isPartFolder(dirPath) {
10
+ const folderName = basename(dirPath);
11
+ const parentDir = dirname(dirPath);
12
+ const potentialContainerPath = join(parentDir, `${folderName}.bfy`);
13
+ try {
14
+ await access(potentialContainerPath);
15
+ return true;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
21
+ async function getFilesFromFolder(folderPath) {
22
+ const files = [];
23
+ async function traverse(currentPath) {
24
+ const entries = await readdir(currentPath, { withFileTypes: true });
25
+ for (const entry of entries) {
26
+ const fullPath = join(currentPath, entry.name);
27
+ if (entry.isDirectory()) {
28
+ if (['node_modules', '.git', 'dist', 'build'].includes(entry.name)) {
29
+ continue;
30
+ }
31
+ if (await isPartFolder(fullPath)) {
32
+ continue;
33
+ }
34
+ await traverse(fullPath);
35
+ }
36
+ else if (entry.isFile()) {
37
+ const supportedExtensions = ['.json', '.bfy', '.yaml', '.yml', '.js', '.css'];
38
+ const supportedFiles = ['object.json', 'spec.json', 'report.json', 'task.json', 'page.json', 'cronjob.json', 'template.json', 'layout.json', 'state_machine.json', 'state.json', 'role.json', 'transition.json', 'action.json', 'query.json'];
39
+ if (supportedExtensions.some(ext => entry.name.endsWith(ext)) || supportedFiles.includes(entry.name)) {
40
+ files.push({
41
+ path: fullPath,
42
+ isFile: true
43
+ });
44
+ }
45
+ }
46
+ }
47
+ }
48
+ await traverse(folderPath);
49
+ return files;
50
+ }
51
+ const UploadCommand = ({ targets, api }) => {
52
+ const [status, setStatus] = useState('loading');
53
+ const [error, setError] = useState('');
54
+ const [progress, setProgress] = useState({ current: 0, total: 0, succeeded: 0, failed: 0 });
55
+ const [currentFile, setCurrentFile] = useState('');
56
+ const [uploadErrors, setUploadErrors] = useState([]);
57
+ useEffect(() => {
58
+ const upload = async () => {
59
+ try {
60
+ setStatus('uploading');
61
+ const allFiles = [];
62
+ for (const target of targets) {
63
+ try {
64
+ const resolvedTarget = resolve(target);
65
+ const targetStat = await stat(resolvedTarget);
66
+ if (targetStat.isDirectory()) {
67
+ const files = await getFilesFromFolder(resolvedTarget);
68
+ allFiles.push(...files);
69
+ }
70
+ else if (targetStat.isFile()) {
71
+ allFiles.push({
72
+ path: resolvedTarget,
73
+ isFile: true
74
+ });
75
+ }
76
+ else {
77
+ throw new Error(`${resolvedTarget} is neither a file nor a directory`);
78
+ }
79
+ }
80
+ catch (err) {
81
+ throw new Error(`Failed to process ${target}: ${err instanceof Error ? err.message : 'Unknown error'}`);
82
+ }
83
+ }
84
+ setProgress({ current: 0, total: allFiles.length, succeeded: 0, failed: 0 });
85
+ const uploadHandler = new UploadHandler(api);
86
+ let succeeded = 0;
87
+ let failed = 0;
88
+ const errors = [];
89
+ for (let i = 0; i < allFiles.length; i++) {
90
+ const file = allFiles[i];
91
+ setCurrentFile(file.path.split('/').pop() || file.path);
92
+ try {
93
+ const result = await uploadHandler.uploadFile(file.path);
94
+ if (!result.success) {
95
+ errors.push({ file: file.path, message: result.message });
96
+ failed++;
97
+ }
98
+ else {
99
+ succeeded++;
100
+ }
101
+ setProgress({ current: i + 1, total: allFiles.length, succeeded, failed });
102
+ }
103
+ catch (err) {
104
+ const errMsg = err instanceof Error ? err.message : String(err);
105
+ errors.push({ file: file.path, message: errMsg });
106
+ failed++;
107
+ setProgress({ current: i + 1, total: allFiles.length, succeeded, failed });
108
+ }
109
+ }
110
+ setUploadErrors(errors);
111
+ setStatus('complete');
112
+ setTimeout(() => process.exit(failed > 0 ? 1 : 0), 2000);
113
+ }
114
+ catch (err) {
115
+ setError(err instanceof Error ? err.message : 'Unknown error');
116
+ setStatus('error');
117
+ setTimeout(() => process.exit(1), 3000);
118
+ }
119
+ };
120
+ upload();
121
+ }, [targets]);
122
+ return (React.createElement(Box, { flexDirection: "column" },
123
+ status === 'loading' && React.createElement(Text, { color: "gray" }, "..."),
124
+ status === 'uploading' && (React.createElement(UploadProgress, { current: progress.current, total: progress.total, currentFile: currentFile, status: "uploading", succeeded: progress.succeeded, failed: progress.failed })),
125
+ status === 'complete' && (React.createElement(React.Fragment, null,
126
+ React.createElement(UploadProgress, { current: progress.total, total: progress.total, currentFile: "", status: "complete", succeeded: progress.succeeded, failed: progress.failed }),
127
+ uploadErrors.length > 0 && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
128
+ React.createElement(Text, { color: "red", bold: true }, "Failed uploads:"),
129
+ uploadErrors.map((err, idx) => (React.createElement(Box, { key: idx, flexDirection: "column", marginLeft: 1, marginTop: 1 },
130
+ React.createElement(Text, { color: "yellow" },
131
+ "\u2022 ",
132
+ err.file),
133
+ React.createElement(Text, { color: "gray", dimColor: true },
134
+ " ",
135
+ err.message)))))))),
136
+ status === 'error' && (React.createElement(UploadProgress, { current: 0, total: 0, currentFile: "", status: "error", error: error }))));
137
+ };
138
+ export default async (targets) => {
139
+ const config = await loadAuthConfig();
140
+ if (!config) {
141
+ console.error('No authentication configured. Please run "butterfly-cli setup" first.');
142
+ process.exit(1);
143
+ }
144
+ const api = new ButterflyAPI(config);
145
+ await api.authenticate();
146
+ if (api.needs2FA)
147
+ await api.complete2FA();
148
+ render(React.createElement(UploadCommand, { targets: targets, api: api }));
149
+ };
150
+ //# sourceMappingURL=upload.js.map
@@ -0,0 +1,13 @@
1
+ import { ButterflyAPI } from '../utils/api.js';
2
+ interface WorkflowInfoProps {
3
+ api: ButterflyAPI;
4
+ options: {
5
+ groups?: boolean;
6
+ nodes?: string;
7
+ details?: string;
8
+ group?: string;
9
+ };
10
+ }
11
+ export default function workflowInfoCommand(options: WorkflowInfoProps['options']): Promise<void>;
12
+ export {};
13
+ //# sourceMappingURL=workflow-info.d.ts.map
@@ -0,0 +1,161 @@
1
+ import React, { useEffect, useState } from 'react';
2
+ import { render, Box, Text } from 'ink';
3
+ import { loadAuthConfig } from '../utils/auth.js';
4
+ import { ButterflyAPI } from '../utils/api.js';
5
+ const WorkflowInfoCommand = ({ options, api }) => {
6
+ const [state, setState] = useState({ status: 'loading', message: 'Initializing...' });
7
+ useEffect(() => {
8
+ const run = async () => {
9
+ try {
10
+ if (options.groups) {
11
+ setState({ status: 'loading', message: 'Fetching node groups...' });
12
+ const response = await api.httpClient.get('/admin/bfy_workflow/getWorkflowNodeGroups');
13
+ if (response.data?.success) {
14
+ setState({
15
+ status: 'complete',
16
+ message: 'Node Groups:',
17
+ data: { type: 'groups', groups: response.data.node_groups }
18
+ });
19
+ }
20
+ else {
21
+ setState({ status: 'error', message: response.data?.error || 'Failed to fetch node groups' });
22
+ }
23
+ }
24
+ else if (options.nodes) {
25
+ const group = options.nodes;
26
+ setState({ status: 'loading', message: `Fetching nodes in "${group}" group...` });
27
+ const response = await api.httpClient.get(`/admin/bfy_workflow/getWorkflowNodes?group=${group}`);
28
+ if (response.data?.success) {
29
+ setState({
30
+ status: 'complete',
31
+ message: `Nodes in "${group}" group:`,
32
+ data: { type: 'nodes', group, nodes: response.data.nodes }
33
+ });
34
+ }
35
+ else {
36
+ setState({ status: 'error', message: response.data?.error || 'Failed to fetch nodes' });
37
+ }
38
+ }
39
+ else if (options.details && options.group) {
40
+ const { group, details: node } = options;
41
+ setState({ status: 'loading', message: `Fetching details for "${group}/${node}"...` });
42
+ const response = await api.httpClient.get(`/admin/bfy_workflow/getWorkflowNodeDetails?group=${group}&node=${node}`);
43
+ if (response.data?.success) {
44
+ setState({
45
+ status: 'complete',
46
+ message: `Node Details: ${group}/${node}`,
47
+ data: { type: 'details', node: response.data.node }
48
+ });
49
+ }
50
+ else {
51
+ setState({ status: 'error', message: response.data?.error || 'Failed to fetch node details' });
52
+ }
53
+ }
54
+ else {
55
+ setState({
56
+ status: 'error',
57
+ message: 'Please specify an option: --groups, --nodes <group>, or --details <node> --group <group>'
58
+ });
59
+ }
60
+ }
61
+ catch (error) {
62
+ setState({
63
+ status: 'error',
64
+ message: error instanceof Error ? error.message : 'Unknown error occurred'
65
+ });
66
+ }
67
+ };
68
+ run();
69
+ }, []);
70
+ const renderGroups = (groups) => {
71
+ return Object.entries(groups).map(([key, group]) => (React.createElement(Box, { key: key, flexDirection: "column", marginBottom: 1 },
72
+ React.createElement(Text, { color: "cyan", bold: true }, group.class_name),
73
+ React.createElement(Text, { color: "gray" },
74
+ " Name: ",
75
+ group.name),
76
+ React.createElement(Text, { color: "gray" },
77
+ " Description: ",
78
+ group.description))));
79
+ };
80
+ const renderNodes = (nodes, group) => {
81
+ return nodes.map((node) => (React.createElement(Box, { key: node.id, flexDirection: "column", marginBottom: 1 },
82
+ React.createElement(Text, { color: "cyan", bold: true }, node.id),
83
+ React.createElement(Text, { color: "gray" },
84
+ " Name: ",
85
+ node.name),
86
+ React.createElement(Text, { color: "gray" },
87
+ " Description: ",
88
+ node.description),
89
+ React.createElement(Text, { color: "yellow" },
90
+ " Usage: --node-group ",
91
+ group,
92
+ " --node-type ",
93
+ node.id))));
94
+ };
95
+ const renderDetails = (node) => {
96
+ return (React.createElement(Box, { flexDirection: "column" },
97
+ React.createElement(Text, { color: "cyan", bold: true },
98
+ node.name,
99
+ " (",
100
+ node.identifier_key,
101
+ ")"),
102
+ React.createElement(Text, { color: "gray" },
103
+ "Description: ",
104
+ node.description),
105
+ node.inputs && Object.keys(node.inputs).length > 0 && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
106
+ React.createElement(Text, { color: "green", bold: true }, "Inputs:"),
107
+ Object.entries(node.inputs).map(([key, desc]) => (React.createElement(Text, { key: key, color: "gray" },
108
+ " - ",
109
+ key,
110
+ ": ",
111
+ desc))))),
112
+ node.outputs && Object.keys(node.outputs).length > 0 && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
113
+ React.createElement(Text, { color: "yellow", bold: true }, "Outputs:"),
114
+ Object.entries(node.outputs).map(([key, desc]) => (React.createElement(Text, { key: key, color: "gray" },
115
+ " - ",
116
+ key,
117
+ ": ",
118
+ desc))))),
119
+ node.preferences && node.preferences.length > 0 && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
120
+ React.createElement(Text, { color: "magenta", bold: true }, "Preferences (params.yaml):"),
121
+ node.preferences.map((pref, idx) => (React.createElement(Box, { key: idx, flexDirection: "column", marginLeft: 2 },
122
+ React.createElement(Text, { color: "white" },
123
+ pref.name,
124
+ ": ",
125
+ React.createElement(Text, { color: "gray" },
126
+ "(",
127
+ pref.type,
128
+ ")")),
129
+ pref.description && React.createElement(Text, { color: "gray" },
130
+ " Description: ",
131
+ pref.description),
132
+ pref.default !== undefined && React.createElement(Text, { color: "gray" },
133
+ " Default: ",
134
+ JSON.stringify(pref.default)),
135
+ pref.options && pref.options.length > 0 && (React.createElement(Text, { color: "gray" },
136
+ " Options: ",
137
+ pref.options.map(o => o.value).join(', '))))))))));
138
+ };
139
+ return (React.createElement(Box, { flexDirection: "column" },
140
+ state.status === 'loading' && React.createElement(Text, { color: "yellow" }, state.message),
141
+ state.status === 'complete' && (React.createElement(Box, { flexDirection: "column" },
142
+ React.createElement(Text, { color: "green", bold: true }, state.message),
143
+ React.createElement(Text, null, " "),
144
+ state.data?.type === 'groups' && renderGroups(state.data.groups),
145
+ state.data?.type === 'nodes' && renderNodes(state.data.nodes, state.data.group),
146
+ state.data?.type === 'details' && renderDetails(state.data.node))),
147
+ state.status === 'error' && React.createElement(Text, { color: "red" }, state.message)));
148
+ };
149
+ export default async function workflowInfoCommand(options) {
150
+ const config = await loadAuthConfig();
151
+ if (!config) {
152
+ console.error('No authentication configured. Run "butterfly-cli setup" first.');
153
+ process.exit(1);
154
+ }
155
+ const api = new ButterflyAPI(config);
156
+ await api.authenticate();
157
+ if (api.needs2FA)
158
+ await api.complete2FA();
159
+ render(React.createElement(WorkflowInfoCommand, { options: options, api: api }));
160
+ }
161
+ //# sourceMappingURL=workflow-info.js.map
@@ -0,0 +1,12 @@
1
+ import React from 'react';
2
+ import { ResourceDiff } from '../diff/types.js';
3
+ export type ResolveAction = 'keep_local' | 'take_remote' | 'skip';
4
+ interface ConflictResolverProps {
5
+ diff: ResourceDiff;
6
+ onResolve: (action: ResolveAction) => void;
7
+ onShowDetails?: () => void;
8
+ isApplying?: boolean;
9
+ }
10
+ export declare const ConflictResolver: React.FC<ConflictResolverProps>;
11
+ export default ConflictResolver;
12
+ //# sourceMappingURL=ConflictResolver.d.ts.map
@@ -0,0 +1,77 @@
1
+ import React, { useState } from 'react';
2
+ import { Box, Text, useInput } from 'ink';
3
+ export const ConflictResolver = ({ diff, onResolve, onShowDetails, isApplying = false }) => {
4
+ const [selectedIndex, setSelectedIndex] = useState(0);
5
+ const allActions = [
6
+ {
7
+ key: 'l',
8
+ action: 'keep_local',
9
+ label: 'Keep Local',
10
+ description: 'Upload local version to server',
11
+ enabled: diff.status !== 'remote_only'
12
+ },
13
+ {
14
+ key: 'r',
15
+ action: 'take_remote',
16
+ label: 'Take Remote',
17
+ description: 'Download remote version to local',
18
+ enabled: diff.status !== 'local_only'
19
+ },
20
+ {
21
+ key: 's',
22
+ action: 'skip',
23
+ label: 'Skip',
24
+ description: 'Do nothing, continue to next',
25
+ enabled: true
26
+ }
27
+ ];
28
+ const actions = allActions.filter(a => a.enabled);
29
+ useInput((input, key) => {
30
+ if (isApplying)
31
+ return;
32
+ if (key.leftArrow) {
33
+ setSelectedIndex(prev => Math.max(0, prev - 1));
34
+ }
35
+ else if (key.rightArrow) {
36
+ setSelectedIndex(prev => Math.min(actions.length - 1, prev + 1));
37
+ }
38
+ if (key.return) {
39
+ onResolve(actions[selectedIndex].action);
40
+ }
41
+ const inputLower = input.toLowerCase();
42
+ if (inputLower === 'd' && diff.status === 'modified' && onShowDetails) {
43
+ onShowDetails();
44
+ return;
45
+ }
46
+ for (const action of actions) {
47
+ if (inputLower === action.key) {
48
+ onResolve(action.action);
49
+ break;
50
+ }
51
+ }
52
+ if (key.escape) {
53
+ onResolve('skip');
54
+ }
55
+ });
56
+ if (isApplying) {
57
+ return (React.createElement(Box, { marginTop: 1, paddingX: 1 },
58
+ React.createElement(Text, { color: "blue" }, "Applying changes...")));
59
+ }
60
+ return (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
61
+ React.createElement(Box, { borderStyle: "single", paddingX: 1 },
62
+ React.createElement(Text, null, "Action: "),
63
+ actions.map((action, idx) => (React.createElement(Box, { key: action.action, marginLeft: idx > 0 ? 2 : 0 },
64
+ React.createElement(Text, { color: idx === selectedIndex ? 'cyan' : 'white', bold: idx === selectedIndex, inverse: idx === selectedIndex },
65
+ "[",
66
+ action.key.toUpperCase(),
67
+ "] ",
68
+ action.label)))),
69
+ diff.status === 'modified' && onShowDetails && (React.createElement(Box, { marginLeft: 2 },
70
+ React.createElement(Text, { color: "white" }, "[D] Details")))),
71
+ React.createElement(Box, { marginTop: 1 },
72
+ React.createElement(Text, { color: "gray" },
73
+ "Use arrow keys to navigate, Enter to confirm. Shortcuts: L=Keep Local, R=Take Remote, S=Skip",
74
+ diff.status === 'modified' ? ', D=Details' : ''))));
75
+ };
76
+ export default ConflictResolver;
77
+ //# sourceMappingURL=ConflictResolver.js.map
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ import { ResourceDiff } from '../diff/types.js';
3
+ interface DiffViewProps {
4
+ diff: ResourceDiff;
5
+ currentIndex: number;
6
+ totalCount: number;
7
+ showDetails?: boolean;
8
+ }
9
+ export declare const DiffView: React.FC<DiffViewProps>;
10
+ export default DiffView;
11
+ //# sourceMappingURL=DiffView.d.ts.map
@@ -0,0 +1,101 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import { formatValue } from '../diff/types.js';
4
+ const statusColors = {
5
+ unchanged: 'gray',
6
+ modified: 'yellow',
7
+ local_only: 'green',
8
+ remote_only: 'red'
9
+ };
10
+ const statusLabels = {
11
+ unchanged: 'unchanged',
12
+ modified: 'modified',
13
+ local_only: 'local only',
14
+ remote_only: 'remote only'
15
+ };
16
+ const statusSymbols = {
17
+ unchanged: '=',
18
+ modified: '~',
19
+ local_only: '+',
20
+ remote_only: '-'
21
+ };
22
+ const FieldDiffRow = ({ fieldDiff, detailed = false }) => {
23
+ const maxLen = detailed ? 500 : 60;
24
+ return (React.createElement(Box, { flexDirection: "column", marginLeft: 2, marginBottom: 1 },
25
+ React.createElement(Text, { bold: true, color: "white" },
26
+ fieldDiff.field,
27
+ ":"),
28
+ React.createElement(Box, { marginLeft: 2 },
29
+ React.createElement(Text, { color: "green" }, "Local: "),
30
+ React.createElement(Text, null, formatValue(fieldDiff.localValue, maxLen))),
31
+ React.createElement(Box, { marginLeft: 2 },
32
+ React.createElement(Text, { color: "red" }, "Remote: "),
33
+ React.createElement(Text, null, formatValue(fieldDiff.remoteValue, maxLen)))));
34
+ };
35
+ const ChildDiffSummary = ({ children, detailed = false }) => {
36
+ if (children.length === 0)
37
+ return null;
38
+ const displayChildren = detailed ? children : children.slice(0, 5);
39
+ return (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
40
+ React.createElement(Text, { bold: true, underline: true },
41
+ "Child Changes (",
42
+ children.length,
43
+ "):"),
44
+ displayChildren.map(child => (React.createElement(Box, { key: child.resourceId, flexDirection: "column", marginLeft: 2 },
45
+ React.createElement(Box, null,
46
+ React.createElement(Text, { color: statusColors[child.status] }, statusSymbols[child.status]),
47
+ React.createElement(Text, null,
48
+ " ",
49
+ child.resourceName,
50
+ " "),
51
+ React.createElement(Text, { color: "gray" },
52
+ "(",
53
+ statusLabels[child.status],
54
+ ")")),
55
+ detailed && child.fieldDiffs && child.fieldDiffs.filter(f => f.status !== 'unchanged').length > 0 && (React.createElement(Box, { flexDirection: "column", marginLeft: 2 }, child.fieldDiffs.filter(f => f.status !== 'unchanged').map(field => (React.createElement(FieldDiffRow, { key: field.field, fieldDiff: field, detailed: true })))))))),
56
+ !detailed && children.length > 5 && (React.createElement(Box, { marginLeft: 2 },
57
+ React.createElement(Text, { color: "gray" },
58
+ "...and ",
59
+ children.length - 5,
60
+ " more")))));
61
+ };
62
+ export const DiffView = ({ diff, currentIndex, totalCount, showDetails = false }) => {
63
+ const changedFields = diff.fieldDiffs.filter(f => f.status !== 'unchanged');
64
+ const displayFields = showDetails ? changedFields : changedFields.slice(0, 10);
65
+ return (React.createElement(Box, { flexDirection: "column", paddingY: 1 },
66
+ React.createElement(Box, { borderStyle: "single", paddingX: 1, marginBottom: 1 },
67
+ React.createElement(Text, { bold: true, color: "cyan" },
68
+ "[",
69
+ currentIndex + 1,
70
+ "/",
71
+ totalCount,
72
+ "] ",
73
+ diff.resourceType.toUpperCase(),
74
+ ": ",
75
+ diff.resourceName)),
76
+ React.createElement(Box, null,
77
+ React.createElement(Text, null, "Status: "),
78
+ React.createElement(Text, { color: statusColors[diff.status], bold: true }, statusLabels[diff.status]),
79
+ diff.localPath && (React.createElement(Text, { color: "gray" },
80
+ " (",
81
+ diff.localPath,
82
+ ")"))),
83
+ diff.status === 'local_only' && (React.createElement(Box, { marginTop: 1 },
84
+ React.createElement(Text, { color: "green" }, "This resource exists locally but not on the remote server."))),
85
+ diff.status === 'remote_only' && (React.createElement(Box, { marginTop: 1 },
86
+ React.createElement(Text, { color: "red" }, "This resource exists on the remote server but not locally."))),
87
+ diff.status === 'modified' && changedFields.length > 0 && (React.createElement(Box, { flexDirection: "column", marginTop: 1 },
88
+ React.createElement(Text, { bold: true, underline: true },
89
+ "Changed Fields (",
90
+ changedFields.length,
91
+ "):"),
92
+ displayFields.map(field => (React.createElement(FieldDiffRow, { key: field.field, fieldDiff: field, detailed: showDetails }))),
93
+ !showDetails && changedFields.length > 10 && (React.createElement(Box, { marginLeft: 2 },
94
+ React.createElement(Text, { color: "gray" },
95
+ "...and ",
96
+ changedFields.length - 10,
97
+ " more fields"))))),
98
+ diff.children && diff.children.length > 0 && (React.createElement(ChildDiffSummary, { children: diff.children, detailed: showDetails }))));
99
+ };
100
+ export default DiffView;
101
+ //# sourceMappingURL=DiffView.js.map
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ interface DownloadProgressProps {
3
+ resourceType: string;
4
+ current: number;
5
+ total: number;
6
+ status: 'downloading' | 'complete' | 'error';
7
+ error?: string;
8
+ }
9
+ declare const DownloadProgress: React.FC<DownloadProgressProps>;
10
+ export { DownloadProgress };
11
+ //# sourceMappingURL=DownloadProgress.d.ts.map
@@ -0,0 +1,29 @@
1
+ import React from 'react';
2
+ import { Box, Text } from 'ink';
3
+ import Spinner from 'ink-spinner';
4
+ const DownloadProgress = ({ resourceType, current, total, status, error, }) => {
5
+ return (React.createElement(Box, { flexDirection: "column" },
6
+ React.createElement(Box, null,
7
+ status === 'downloading' && (React.createElement(React.Fragment, null,
8
+ React.createElement(Text, { color: "cyan" },
9
+ React.createElement(Spinner, { type: "dots" })),
10
+ React.createElement(Text, null,
11
+ " Downloading ",
12
+ resourceType,
13
+ "... (",
14
+ current,
15
+ "/",
16
+ total,
17
+ ")"))),
18
+ status === 'complete' && (React.createElement(Text, { color: "green" },
19
+ "\u2713 Downloaded ",
20
+ total,
21
+ " ",
22
+ resourceType,
23
+ " successfully!")),
24
+ status === 'error' && (React.createElement(Text, { color: "red" },
25
+ "\u2717 Error: ",
26
+ error)))));
27
+ };
28
+ export { DownloadProgress };
29
+ //# sourceMappingURL=DownloadProgress.js.map
@@ -0,0 +1,11 @@
1
+ import React from 'react';
2
+ export type OperationType = 'get' | 'add' | 'edit' | 'delete';
3
+ interface RecordPreviewProps {
4
+ operation: OperationType;
5
+ table: string;
6
+ data: Record<string, any>;
7
+ existingRecord?: Record<string, any>;
8
+ }
9
+ export declare const RecordPreview: React.FC<RecordPreviewProps>;
10
+ export default RecordPreview;
11
+ //# sourceMappingURL=RecordPreview.d.ts.map