@positronic/cli 0.0.2 → 0.0.4

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 (83) hide show
  1. package/dist/src/cli.js +16 -1
  2. package/dist/src/commands/helpers.js +11 -25
  3. package/dist/types/cli.d.ts.map +1 -1
  4. package/dist/types/commands/helpers.d.ts.map +1 -1
  5. package/package.json +11 -4
  6. package/dist/src/commands/brain.test.js +0 -2936
  7. package/dist/src/commands/helpers.test.js +0 -832
  8. package/dist/src/commands/project.test.js +0 -1201
  9. package/dist/src/commands/resources.test.js +0 -2511
  10. package/dist/src/commands/schedule.test.js +0 -1235
  11. package/dist/src/commands/secret.test.d.js +0 -1
  12. package/dist/src/commands/secret.test.js +0 -761
  13. package/dist/src/commands/server.test.js +0 -1237
  14. package/dist/src/commands/test-utils.js +0 -737
  15. package/dist/src/components/secret-sync.js +0 -303
  16. package/dist/src/test/mock-api-client.js +0 -371
  17. package/dist/src/test/test-dev-server.js +0 -1376
  18. package/dist/types/commands/test-utils.d.ts +0 -45
  19. package/dist/types/commands/test-utils.d.ts.map +0 -1
  20. package/dist/types/components/secret-sync.d.ts +0 -9
  21. package/dist/types/components/secret-sync.d.ts.map +0 -1
  22. package/dist/types/test/mock-api-client.d.ts +0 -25
  23. package/dist/types/test/mock-api-client.d.ts.map +0 -1
  24. package/dist/types/test/test-dev-server.d.ts +0 -129
  25. package/dist/types/test/test-dev-server.d.ts.map +0 -1
  26. package/src/cli.ts +0 -981
  27. package/src/commands/backend.ts +0 -63
  28. package/src/commands/brain.test.ts +0 -1004
  29. package/src/commands/brain.ts +0 -215
  30. package/src/commands/helpers.test.ts +0 -487
  31. package/src/commands/helpers.ts +0 -870
  32. package/src/commands/project-config-manager.ts +0 -152
  33. package/src/commands/project.test.ts +0 -502
  34. package/src/commands/project.ts +0 -109
  35. package/src/commands/resources.test.ts +0 -1052
  36. package/src/commands/resources.ts +0 -97
  37. package/src/commands/schedule.test.ts +0 -481
  38. package/src/commands/schedule.ts +0 -65
  39. package/src/commands/secret.test.ts +0 -210
  40. package/src/commands/secret.ts +0 -50
  41. package/src/commands/server.test.ts +0 -493
  42. package/src/commands/server.ts +0 -353
  43. package/src/commands/test-utils.ts +0 -324
  44. package/src/components/brain-history.tsx +0 -198
  45. package/src/components/brain-list.tsx +0 -105
  46. package/src/components/brain-rerun.tsx +0 -111
  47. package/src/components/brain-show.tsx +0 -92
  48. package/src/components/error.tsx +0 -24
  49. package/src/components/project-add.tsx +0 -59
  50. package/src/components/project-create.tsx +0 -83
  51. package/src/components/project-list.tsx +0 -83
  52. package/src/components/project-remove.tsx +0 -55
  53. package/src/components/project-select.tsx +0 -200
  54. package/src/components/project-show.tsx +0 -58
  55. package/src/components/resource-clear.tsx +0 -127
  56. package/src/components/resource-delete.tsx +0 -160
  57. package/src/components/resource-list.tsx +0 -177
  58. package/src/components/resource-sync.tsx +0 -170
  59. package/src/components/resource-types.tsx +0 -55
  60. package/src/components/resource-upload.tsx +0 -182
  61. package/src/components/schedule-create.tsx +0 -90
  62. package/src/components/schedule-delete.tsx +0 -116
  63. package/src/components/schedule-list.tsx +0 -186
  64. package/src/components/schedule-runs.tsx +0 -151
  65. package/src/components/secret-bulk.tsx +0 -79
  66. package/src/components/secret-create.tsx +0 -49
  67. package/src/components/secret-delete.tsx +0 -41
  68. package/src/components/secret-list.tsx +0 -41
  69. package/src/components/watch.tsx +0 -155
  70. package/src/hooks/useApi.ts +0 -183
  71. package/src/positronic.ts +0 -40
  72. package/src/test/data/resources/config.json +0 -1
  73. package/src/test/data/resources/data/config.json +0 -1
  74. package/src/test/data/resources/data/logo.png +0 -2
  75. package/src/test/data/resources/docs/api.md +0 -3
  76. package/src/test/data/resources/docs/readme.md +0 -3
  77. package/src/test/data/resources/example.md +0 -3
  78. package/src/test/data/resources/file with spaces.txt +0 -1
  79. package/src/test/data/resources/readme.md +0 -3
  80. package/src/test/data/resources/test.txt +0 -1
  81. package/src/test/mock-api-client.ts +0 -145
  82. package/src/test/test-dev-server.ts +0 -1003
  83. package/tsconfig.json +0 -11
@@ -1,83 +0,0 @@
1
- import React from 'react';
2
- import { Box, Text } from 'ink';
3
- import type { ProjectConfigManager } from '../commands/project-config-manager.js';
4
-
5
- interface ProjectListProps {
6
- projectConfig: ProjectConfigManager;
7
- }
8
-
9
- export const ProjectList = ({ projectConfig }: ProjectListProps) => {
10
- const { projects, current } = projectConfig.listProjects();
11
-
12
- if (projects.length === 0) {
13
- return (
14
- <Box flexDirection="column">
15
- <Text>No projects configured.</Text>
16
- <Box marginTop={1}>
17
- <Text dimColor>
18
- Add a project with "px project add &lt;name&gt; --url &lt;url&gt;"
19
- </Text>
20
- </Box>
21
- </Box>
22
- );
23
- }
24
-
25
- // Calculate column widths for table display
26
- const maxNameLength = Math.max(...projects.map(p => p.name.length), 4); // min 4 for "Name"
27
- const nameColWidth = Math.min(maxNameLength + 2, 30); // cap at 30
28
-
29
- return (
30
- <Box flexDirection="column" paddingTop={1} paddingBottom={1}>
31
- <Text bold>
32
- Found {projects.length} project{projects.length === 1 ? '' : 's'}:
33
- </Text>
34
-
35
- <Box marginTop={1} flexDirection="column">
36
- {/* Header row */}
37
- <Box>
38
- <Text bold color="cyan">{padRight('Name', nameColWidth)}</Text>
39
- <Text> </Text>
40
- <Text bold color="cyan">URL</Text>
41
- </Box>
42
-
43
- {/* Separator */}
44
- <Box>
45
- <Text dimColor>{'─'.repeat(nameColWidth + 2 + 40)}</Text>
46
- </Box>
47
-
48
- {/* Project rows */}
49
- {projects.map((project) => {
50
- const isCurrent = project.name === current;
51
-
52
- return (
53
- <Box key={project.name}>
54
- <Text color={isCurrent ? 'green' : undefined}>
55
- {padRight(truncate(project.name, nameColWidth - 2), nameColWidth)}
56
- </Text>
57
- <Text> </Text>
58
- <Text dimColor={!isCurrent}>{project.url}</Text>
59
- {isCurrent && <Text color="green"> ← current</Text>}
60
- </Box>
61
- );
62
- })}
63
- </Box>
64
-
65
- {!current && projects.length > 0 && (
66
- <Box marginTop={1}>
67
- <Text dimColor>
68
- No project selected. Use "px project select &lt;name&gt;" to select one.
69
- </Text>
70
- </Box>
71
- )}
72
- </Box>
73
- );
74
- };
75
-
76
- function padRight(text: string, width: number): string {
77
- return text + ' '.repeat(Math.max(0, width - text.length));
78
- }
79
-
80
- function truncate(text: string, maxWidth: number): string {
81
- if (text.length <= maxWidth) return text;
82
- return text.substring(0, maxWidth - 3) + '...';
83
- }
@@ -1,55 +0,0 @@
1
- import React, { useEffect, useState } from 'react';
2
- import { Box, Text } from 'ink';
3
- import type { ProjectConfigManager } from '../commands/project-config-manager.js';
4
-
5
- interface ProjectRemoveProps {
6
- name: string;
7
- projectConfig: ProjectConfigManager;
8
- }
9
-
10
- export const ProjectRemove = ({ name, projectConfig }: ProjectRemoveProps) => {
11
- const [result, setResult] = useState<{ success: boolean; error?: string } | null>(null);
12
-
13
- useEffect(() => {
14
- const removeResult = projectConfig.removeProject(name);
15
- setResult(removeResult);
16
- }, [name, projectConfig]);
17
-
18
- if (!result) {
19
- return <Text>Removing project...</Text>;
20
- }
21
-
22
- if (result.success) {
23
- const config = projectConfig.read();
24
- const currentProject = config.currentProject;
25
-
26
- return (
27
- <Box flexDirection="column">
28
- <Text color="green">✅ Project "{name}" removed successfully!</Text>
29
- {currentProject && (
30
- <Box marginTop={1} paddingLeft={2}>
31
- <Text>
32
- <Text bold>Current project:</Text> <Text color="cyan">{currentProject}</Text>
33
- </Text>
34
- </Box>
35
- )}
36
- <Box marginTop={1}>
37
- <Text dimColor>
38
- {currentProject
39
- ? `Your active project is now "${currentProject}".`
40
- : 'No active project selected. Use "px project select <name>" to choose one.'}
41
- </Text>
42
- </Box>
43
- </Box>
44
- );
45
- }
46
-
47
- return (
48
- <Box flexDirection="column">
49
- <Text color="red">❌ Failed to remove project</Text>
50
- <Box paddingLeft={2}>
51
- <Text color="red">{result.error}</Text>
52
- </Box>
53
- </Box>
54
- );
55
- };
@@ -1,200 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { Box, Text, useInput, useApp, useStdin } from 'ink';
3
- import type { Project, ProjectConfigManager } from '../commands/project-config-manager.js';
4
-
5
- interface ProjectSelectProps {
6
- name?: string;
7
- projectConfig: ProjectConfigManager;
8
- }
9
-
10
- // Separate component for interactive selection that uses useInput
11
- const InteractiveProjectSelect = ({
12
- projects,
13
- currentProject,
14
- projectConfig
15
- }: {
16
- projects: Project[];
17
- currentProject: string | null;
18
- projectConfig: ProjectConfigManager;
19
- }) => {
20
- const [selectedIndex, setSelectedIndex] = useState(() => {
21
- const currentIndex = projects.findIndex(p => p.name === currentProject);
22
- return currentIndex >= 0 ? currentIndex : 0;
23
- });
24
- const [result, setResult] = useState<{ success: boolean; error?: string } | null>(null);
25
- const { exit } = useApp();
26
-
27
- useInput((input, key) => {
28
- if (key.upArrow) {
29
- setSelectedIndex((prev) => (prev - 1 + projects.length) % projects.length);
30
- } else if (key.downArrow) {
31
- setSelectedIndex((prev) => (prev + 1) % projects.length);
32
- } else if (key.return) {
33
- const selectedProject = projects[selectedIndex];
34
- const selectResult = projectConfig.selectProject(selectedProject.name);
35
- setResult(selectResult);
36
- } else if (input === 'q' || key.escape) {
37
- exit();
38
- }
39
- });
40
-
41
- // If selection was made, show success
42
- if (result && result.success) {
43
- const selectedProject = projects[selectedIndex];
44
- return (
45
- <Box flexDirection="column">
46
- <Text color="green">✅ Project switched successfully!</Text>
47
- <Box marginTop={1} paddingLeft={2} flexDirection="column">
48
- <Text>
49
- <Text bold>Current project:</Text> {selectedProject.name}
50
- </Text>
51
- <Text>
52
- <Text bold>URL:</Text> {selectedProject.url}
53
- </Text>
54
- </Box>
55
- </Box>
56
- );
57
- }
58
-
59
- // Show interactive selection UI
60
- return (
61
- <Box flexDirection="column">
62
- <Text bold>Select a project:</Text>
63
- <Box marginTop={1} flexDirection="column">
64
- {projects.map((project, index) => {
65
- const isSelected = index === selectedIndex;
66
- const isCurrent = project.name === currentProject;
67
-
68
- return (
69
- <Box key={project.name}>
70
- <Text color={isSelected ? 'cyan' : undefined}>
71
- {isSelected ? '▶ ' : ' '}
72
- {project.name}
73
- {isCurrent && <Text color="green"> (current)</Text>}
74
- </Text>
75
- </Box>
76
- );
77
- })}
78
- </Box>
79
- <Box marginTop={1}>
80
- <Text dimColor>
81
- Use arrow keys to navigate, Enter to select, q to quit
82
- </Text>
83
- </Box>
84
- </Box>
85
- );
86
- };
87
-
88
- export const ProjectSelect = ({ name, projectConfig }: ProjectSelectProps) => {
89
- const [projects, setProjects] = useState<Project[]>([]);
90
- const [currentProject, setCurrentProject] = useState<string | null>(null);
91
- const [result, setResult] = useState<{ success: boolean; error?: string } | null>(null);
92
- const [isInteractive] = useState(!name);
93
- const { isRawModeSupported } = useStdin();
94
-
95
- useEffect(() => {
96
- const { projects: projectList, current } = projectConfig.listProjects();
97
- setProjects(projectList);
98
- setCurrentProject(current);
99
-
100
- // If name is provided, select it directly
101
- if (name) {
102
- const selectResult = projectConfig.selectProject(name);
103
- setResult(selectResult);
104
- }
105
- }, [name, projectConfig]);
106
-
107
- // Handle no projects case
108
- if (projects.length === 0) {
109
- return (
110
- <Box flexDirection="column">
111
- <Text color="red">❌ No projects configured</Text>
112
- <Box marginTop={1}>
113
- <Text dimColor>
114
- Add a project first with "px project add &lt;name&gt; --url &lt;url&gt;"
115
- </Text>
116
- </Box>
117
- </Box>
118
- );
119
- }
120
-
121
- // Direct selection mode - show result
122
- if (!isInteractive && result) {
123
- if (result.success) {
124
- const selectedProject = projects.find(p => p.name === name);
125
- return (
126
- <Box flexDirection="column">
127
- <Text color="green">✅ Project switched successfully!</Text>
128
- <Box marginTop={1} paddingLeft={2} flexDirection="column">
129
- <Text>
130
- <Text bold>Current project:</Text> {name}
131
- </Text>
132
- {selectedProject && (
133
- <Text>
134
- <Text bold>URL:</Text> {selectedProject.url}
135
- </Text>
136
- )}
137
- </Box>
138
- </Box>
139
- );
140
- } else {
141
- return (
142
- <Box flexDirection="column">
143
- <Text color="red">❌ Failed to select project</Text>
144
- <Box paddingLeft={2}>
145
- <Text color="red">{result.error}</Text>
146
- </Box>
147
- {projects.length > 0 && (
148
- <Box marginTop={1} flexDirection="column">
149
- <Text>Available projects:</Text>
150
- {projects.map(p => (
151
- <Box key={p.name} paddingLeft={2}>
152
- <Text dimColor>• {p.name}</Text>
153
- </Box>
154
- ))}
155
- </Box>
156
- )}
157
- </Box>
158
- );
159
- }
160
- }
161
-
162
- // Interactive selection mode
163
- if (isInteractive) {
164
- // If raw mode is not supported (e.g., in tests), show a non-interactive list
165
- if (!isRawModeSupported) {
166
- return (
167
- <Box flexDirection="column">
168
- <Text bold>Available projects:</Text>
169
- <Box marginTop={1} flexDirection="column">
170
- {projects.map((project) => {
171
- const isCurrent = project.name === currentProject;
172
- return (
173
- <Box key={project.name} paddingLeft={2}>
174
- <Text>
175
- • {project.name}
176
- {isCurrent && <Text color="green"> (current)</Text>}
177
- </Text>
178
- </Box>
179
- );
180
- })}
181
- </Box>
182
- <Box marginTop={1}>
183
- <Text dimColor>
184
- Interactive mode not available. Use "px project select &lt;name&gt;" to select a project.
185
- </Text>
186
- </Box>
187
- </Box>
188
- );
189
- }
190
-
191
- // Use the interactive component that has useInput
192
- return <InteractiveProjectSelect
193
- projects={projects}
194
- currentProject={currentProject}
195
- projectConfig={projectConfig}
196
- />;
197
- }
198
-
199
- return <Text>Processing...</Text>;
200
- };
@@ -1,58 +0,0 @@
1
- import React from 'react';
2
- import { Box, Text } from 'ink';
3
- import type { ProjectConfigManager } from '../commands/project-config-manager.js';
4
-
5
- interface ProjectShowProps {
6
- projectConfig: ProjectConfigManager;
7
- }
8
-
9
- export const ProjectShow = ({ projectConfig }: ProjectShowProps) => {
10
- const currentProject = projectConfig.getCurrentProject();
11
- const { projects } = projectConfig.listProjects();
12
-
13
- if (!currentProject) {
14
- return (
15
- <Box flexDirection="column">
16
- <Text>No project currently selected.</Text>
17
- {projects.length > 0 ? (
18
- <Box marginTop={1}>
19
- <Text dimColor>
20
- Use "px project select" to choose from {projects.length} available project{projects.length === 1 ? '' : 's'}.
21
- </Text>
22
- </Box>
23
- ) : (
24
- <Box marginTop={1}>
25
- <Text dimColor>
26
- Add a project with "px project add &lt;name&gt; --url &lt;url&gt;"
27
- </Text>
28
- </Box>
29
- )}
30
- </Box>
31
- );
32
- }
33
-
34
- return (
35
- <Box flexDirection="column" paddingTop={1} paddingBottom={1}>
36
- <Text bold>Current Project</Text>
37
- <Box marginTop={1} paddingLeft={2} flexDirection="column">
38
- <Text>
39
- <Text bold>Name:</Text> {currentProject.name}
40
- </Text>
41
- <Text>
42
- <Text bold>URL:</Text> {currentProject.url}
43
- </Text>
44
- <Text>
45
- <Text bold>Added:</Text> {new Date(currentProject.addedAt).toLocaleString()}
46
- </Text>
47
- </Box>
48
-
49
- {projects.length > 1 && (
50
- <Box marginTop={1}>
51
- <Text dimColor>
52
- {projects.length - 1} other project{projects.length - 1 === 1 ? '' : 's'} available. Use "px project list" to see all.
53
- </Text>
54
- </Box>
55
- )}
56
- </Box>
57
- );
58
- };
@@ -1,127 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { Box, Text, useApp, useInput } from 'ink';
3
- import { ErrorComponent } from './error.js';
4
- import { useApiGet, useApiDelete } from '../hooks/useApi.js';
5
-
6
- interface ResourcesResponse {
7
- resources: Array<{
8
- key: string;
9
- type: string;
10
- path?: string;
11
- size: number;
12
- lastModified: string;
13
- }>;
14
- truncated: boolean;
15
- count: number;
16
- }
17
-
18
- export const ResourceClear = () => {
19
- const [confirmed, setConfirmed] = useState(false);
20
- const [deleted, setDeleted] = useState(false);
21
- const [selectedOption, setSelectedOption] = useState<'cancel' | 'delete'>('cancel');
22
- const { exit } = useApp();
23
-
24
- const { data: resourcesData, loading: listLoading, error: listError } = useApiGet<ResourcesResponse>('/resources');
25
- const { execute: deleteResources, loading: deleteLoading, error: deleteError } = useApiDelete('resources');
26
-
27
- useInput((input, key) => {
28
- if (!confirmed && !deleted && resourcesData && resourcesData.count > 0) {
29
- if (key.upArrow || key.downArrow) {
30
- setSelectedOption(prev => prev === 'cancel' ? 'delete' : 'cancel');
31
- } else if (key.return) {
32
- if (selectedOption === 'delete') {
33
- setConfirmed(true);
34
- } else {
35
- exit();
36
- }
37
- } else if (key.escape || (key.ctrl && input === 'c')) {
38
- exit();
39
- }
40
- }
41
- });
42
-
43
- useEffect(() => {
44
- if (confirmed && !deleteLoading && !deleteError && !deleted) {
45
- deleteResources('/resources')
46
- .then(() => {
47
- setDeleted(true);
48
- })
49
- }
50
- }, [confirmed, deleteLoading, deleteError, deleted, deleteResources]);
51
-
52
- useEffect(() => {
53
- if (deleted) {
54
- // Exit after showing success message for a moment
55
- const timer = setTimeout(() => {
56
- exit();
57
- }, 1500);
58
-
59
- return () => clearTimeout(timer);
60
- }
61
- }, [deleted, exit]);
62
-
63
- if (listError) {
64
- return <ErrorComponent error={listError} />;
65
- }
66
-
67
- if (deleteError) {
68
- return <ErrorComponent error={deleteError} />;
69
- }
70
-
71
- if (listLoading) {
72
- return (
73
- <Box>
74
- <Text>📋 Loading resources...</Text>
75
- </Box>
76
- );
77
- }
78
-
79
- if (resourcesData && resourcesData.count === 0) {
80
- return (
81
- <Box>
82
- <Text>No resources to delete.</Text>
83
- </Box>
84
- );
85
- }
86
-
87
- if (!confirmed) {
88
- return (
89
- <Box flexDirection="column">
90
- <Text bold color="red">🚨 DANGER: This will permanently delete ALL resources!</Text>
91
- <Box marginTop={1} marginBottom={1} paddingLeft={2} flexDirection="column">
92
- <Text>This action will delete {resourcesData?.count || 0} resource(s).</Text>
93
- <Text dimColor>This cannot be undone.</Text>
94
- </Box>
95
- <Box marginTop={1} flexDirection="column">
96
- <Text>Use arrow keys to select, Enter to confirm:</Text>
97
- <Box marginTop={1} flexDirection="column">
98
- <Text color={selectedOption === 'cancel' ? 'green' : undefined}>
99
- {selectedOption === 'cancel' ? '▶ ' : ' '}Cancel (keep resources)
100
- </Text>
101
- <Text color={selectedOption === 'delete' ? 'red' : undefined}>
102
- {selectedOption === 'delete' ? '▶ ' : ' '}Delete all resources
103
- </Text>
104
- </Box>
105
- </Box>
106
- </Box>
107
- );
108
- }
109
-
110
- if (deleteLoading) {
111
- return (
112
- <Box>
113
- <Text>🗑️ Deleting all resources...</Text>
114
- </Box>
115
- );
116
- }
117
-
118
- if (deleted) {
119
- return (
120
- <Box>
121
- <Text color="green">✅ Successfully deleted all resources</Text>
122
- </Box>
123
- );
124
- }
125
-
126
- return null;
127
- };
@@ -1,160 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { Box, Text, useStdin, useApp } from 'ink';
3
- import { ErrorComponent } from './error.js';
4
- import { useApiDelete, useApiGet } from '../hooks/useApi.js';
5
- import { generateTypes } from '../commands/helpers.js';
6
-
7
- interface ApiResourceEntry {
8
- key: string;
9
- type: 'text' | 'binary';
10
- size: number;
11
- lastModified: string;
12
- local: boolean;
13
- }
14
-
15
- interface ResourcesResponse {
16
- resources: ApiResourceEntry[];
17
- truncated: boolean;
18
- count: number;
19
- }
20
-
21
- interface ResourceDeleteProps {
22
- resourceKey: string;
23
- resourcePath: string;
24
- projectRootPath?: string;
25
- force?: boolean;
26
- }
27
-
28
- export const ResourceDelete = ({ resourceKey, resourcePath, projectRootPath, force = false }: ResourceDeleteProps) => {
29
- const [confirmed, setConfirmed] = useState(force); // Auto-confirm if force is true
30
- const [deleted, setDeleted] = useState(false);
31
- const [input, setInput] = useState('');
32
- const [isLocalResource, setIsLocalResource] = useState<boolean | null>(null);
33
- const { stdin, setRawMode } = useStdin();
34
- const { exit } = useApp();
35
-
36
- const {
37
- data: resourcesData, loading: listLoading, error: listError,
38
- } = useApiGet<ResourcesResponse>('/resources');
39
- const { execute: deleteResource, loading, error } = useApiDelete('resource');
40
-
41
- // Check if the resource is local
42
- useEffect(() => {
43
- if (resourcesData) {
44
- const resource = resourcesData.resources.find(r => r.key === resourceKey);
45
- if (resource) {
46
- setIsLocalResource(resource.local);
47
- } else {
48
- setIsLocalResource(false); // Resource doesn't exist
49
- }
50
- }
51
- }, [resourcesData, resourceKey]);
52
-
53
- useEffect(() => {
54
- if (stdin && !confirmed && !deleted && !force) {
55
- setRawMode(true);
56
-
57
- const handleData = (data: Buffer) => {
58
- const char = data.toString();
59
-
60
- if (char === '\r' || char === '\n') {
61
- if (input.toLowerCase() === 'yes') {
62
- setConfirmed(true);
63
- } else {
64
- exit();
65
- }
66
- } else if (char === '\u0003') { // Ctrl+C
67
- exit();
68
- } else if (char === '\u007F' || char === '\b') { // Backspace
69
- setInput(prev => prev.slice(0, -1));
70
- } else {
71
- setInput(prev => prev + char);
72
- }
73
- };
74
-
75
- stdin.on('data', handleData);
76
-
77
- return () => {
78
- stdin.off('data', handleData);
79
- setRawMode(false);
80
- };
81
- }
82
- }, [stdin, setRawMode, confirmed, deleted, input, exit, force]);
83
-
84
- useEffect(() => {
85
- if (confirmed && !loading && !error && !deleted) {
86
- // URL encode the key for the API endpoint
87
- const encodedKey = encodeURIComponent(resourceKey);
88
- deleteResource(`/resources/${encodedKey}`)
89
- .then(() => {
90
- setDeleted(true);
91
-
92
- // Generate types after successful deletion if in local dev mode
93
- if (projectRootPath) {
94
- generateTypes(projectRootPath)
95
- .catch((typeError) => { // Don't fail the delete if type generation fails
96
- console.error('Failed to generate types:', typeError);
97
- });
98
- }
99
- });
100
- }
101
- }, [confirmed, loading, error, deleted, deleteResource, resourceKey, projectRootPath]);
102
-
103
- if (listError) {
104
- return <ErrorComponent error={listError} />;
105
- }
106
-
107
- if (error) {
108
- return <ErrorComponent error={error} />;
109
- }
110
-
111
- if (listLoading || isLocalResource === null) {
112
- return (
113
- <Box>
114
- <Text>Checking resource...</Text>
115
- </Box>
116
- );
117
- }
118
-
119
- if (isLocalResource) {
120
- return (
121
- <Box flexDirection="column">
122
- <Text color="red" bold>❌ Cannot Delete Local Resource</Text>
123
- <Box marginTop={1} paddingLeft={2} flexDirection="column">
124
- <Text>This resource was synced from your local filesystem.</Text>
125
- <Text dimColor>To remove it, delete the file locally and run 'px resources sync'.</Text>
126
- </Box>
127
- </Box>
128
- );
129
- }
130
-
131
- if (!confirmed && !force) {
132
- return (
133
- <Box flexDirection="column">
134
- <Text bold color="yellow">⚠️ Warning: This will permanently delete the following resource:</Text>
135
- <Box marginTop={1} marginBottom={1} paddingLeft={2}>
136
- <Text>{resourcePath}</Text>
137
- </Box>
138
- <Text>Type "yes" to confirm deletion: {input}</Text>
139
- </Box>
140
- );
141
- }
142
-
143
- if (loading) {
144
- return (
145
- <Box>
146
- <Text>🗑️ Deleting {resourcePath}...</Text>
147
- </Box>
148
- );
149
- }
150
-
151
- if (deleted) {
152
- return (
153
- <Box flexDirection="column">
154
- <Text color="green">✅ Successfully deleted: {resourcePath}</Text>
155
- </Box>
156
- );
157
- }
158
-
159
- return null;
160
- };