@positronic/cli 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/src/cli.js +739 -0
- package/dist/src/commands/backend.js +199 -0
- package/dist/src/commands/brain.js +446 -0
- package/dist/src/commands/brain.test.js +2936 -0
- package/dist/src/commands/helpers.js +1315 -0
- package/dist/src/commands/helpers.test.js +832 -0
- package/dist/src/commands/project-config-manager.js +197 -0
- package/dist/src/commands/project.js +130 -0
- package/dist/src/commands/project.test.js +1201 -0
- package/dist/src/commands/resources.js +272 -0
- package/dist/src/commands/resources.test.js +2511 -0
- package/dist/src/commands/schedule.js +73 -0
- package/dist/src/commands/schedule.test.js +1235 -0
- package/dist/src/commands/secret.js +87 -0
- package/dist/src/commands/secret.test.d.js +1 -0
- package/dist/src/commands/secret.test.js +761 -0
- package/dist/src/commands/server.js +816 -0
- package/dist/src/commands/server.test.js +1237 -0
- package/dist/src/commands/test-utils.js +737 -0
- package/dist/src/components/brain-history.js +169 -0
- package/dist/src/components/brain-list.js +108 -0
- package/dist/src/components/brain-rerun.js +313 -0
- package/dist/src/components/brain-show.js +65 -0
- package/dist/src/components/error.js +19 -0
- package/dist/src/components/project-add.js +95 -0
- package/dist/src/components/project-create.js +276 -0
- package/dist/src/components/project-list.js +88 -0
- package/dist/src/components/project-remove.js +91 -0
- package/dist/src/components/project-select.js +224 -0
- package/dist/src/components/project-show.js +41 -0
- package/dist/src/components/resource-clear.js +152 -0
- package/dist/src/components/resource-delete.js +189 -0
- package/dist/src/components/resource-list.js +174 -0
- package/dist/src/components/resource-sync.js +386 -0
- package/dist/src/components/resource-types.js +243 -0
- package/dist/src/components/resource-upload.js +366 -0
- package/dist/src/components/schedule-create.js +259 -0
- package/dist/src/components/schedule-delete.js +161 -0
- package/dist/src/components/schedule-list.js +176 -0
- package/dist/src/components/schedule-runs.js +103 -0
- package/dist/src/components/secret-bulk.js +262 -0
- package/dist/src/components/secret-create.js +199 -0
- package/dist/src/components/secret-delete.js +190 -0
- package/dist/src/components/secret-list.js +190 -0
- package/dist/src/components/secret-sync.js +303 -0
- package/dist/src/components/watch.js +184 -0
- package/dist/src/hooks/useApi.js +512 -0
- package/dist/src/positronic.js +33 -0
- package/dist/src/test/mock-api-client.js +371 -0
- package/dist/src/test/test-dev-server.js +1376 -0
- package/dist/types/cli.d.ts +9 -0
- package/dist/types/cli.d.ts.map +1 -0
- package/dist/types/commands/backend.d.ts +6 -0
- package/dist/types/commands/backend.d.ts.map +1 -0
- package/dist/types/commands/brain.d.ts +35 -0
- package/dist/types/commands/brain.d.ts.map +1 -0
- package/dist/types/commands/helpers.d.ts +55 -0
- package/dist/types/commands/helpers.d.ts.map +1 -0
- package/dist/types/commands/project-config-manager.d.ts +37 -0
- package/dist/types/commands/project-config-manager.d.ts.map +1 -0
- package/dist/types/commands/project.d.ts +55 -0
- package/dist/types/commands/project.d.ts.map +1 -0
- package/dist/types/commands/resources.d.ts +13 -0
- package/dist/types/commands/resources.d.ts.map +1 -0
- package/dist/types/commands/schedule.d.ts +27 -0
- package/dist/types/commands/schedule.d.ts.map +1 -0
- package/dist/types/commands/secret.d.ts +23 -0
- package/dist/types/commands/secret.d.ts.map +1 -0
- package/dist/types/commands/server.d.ts +12 -0
- package/dist/types/commands/server.d.ts.map +1 -0
- package/dist/types/commands/test-utils.d.ts +45 -0
- package/dist/types/commands/test-utils.d.ts.map +1 -0
- package/dist/types/components/brain-history.d.ts +7 -0
- package/dist/types/components/brain-history.d.ts.map +1 -0
- package/dist/types/components/brain-list.d.ts +2 -0
- package/dist/types/components/brain-list.d.ts.map +1 -0
- package/dist/types/components/brain-rerun.d.ts +9 -0
- package/dist/types/components/brain-rerun.d.ts.map +1 -0
- package/dist/types/components/brain-show.d.ts +6 -0
- package/dist/types/components/brain-show.d.ts.map +1 -0
- package/dist/types/components/error.d.ts +10 -0
- package/dist/types/components/error.d.ts.map +1 -0
- package/dist/types/components/project-add.d.ts +9 -0
- package/dist/types/components/project-add.d.ts.map +1 -0
- package/dist/types/components/project-create.d.ts +6 -0
- package/dist/types/components/project-create.d.ts.map +1 -0
- package/dist/types/components/project-list.d.ts +7 -0
- package/dist/types/components/project-list.d.ts.map +1 -0
- package/dist/types/components/project-remove.d.ts +8 -0
- package/dist/types/components/project-remove.d.ts.map +1 -0
- package/dist/types/components/project-select.d.ts +8 -0
- package/dist/types/components/project-select.d.ts.map +1 -0
- package/dist/types/components/project-show.d.ts +7 -0
- package/dist/types/components/project-show.d.ts.map +1 -0
- package/dist/types/components/resource-clear.d.ts +2 -0
- package/dist/types/components/resource-clear.d.ts.map +1 -0
- package/dist/types/components/resource-delete.d.ts +9 -0
- package/dist/types/components/resource-delete.d.ts.map +1 -0
- package/dist/types/components/resource-list.d.ts +2 -0
- package/dist/types/components/resource-list.d.ts.map +1 -0
- package/dist/types/components/resource-sync.d.ts +8 -0
- package/dist/types/components/resource-sync.d.ts.map +1 -0
- package/dist/types/components/resource-types.d.ts +7 -0
- package/dist/types/components/resource-types.d.ts.map +1 -0
- package/dist/types/components/resource-upload.d.ts +8 -0
- package/dist/types/components/resource-upload.d.ts.map +1 -0
- package/dist/types/components/schedule-create.d.ts +7 -0
- package/dist/types/components/schedule-create.d.ts.map +1 -0
- package/dist/types/components/schedule-delete.d.ts +7 -0
- package/dist/types/components/schedule-delete.d.ts.map +1 -0
- package/dist/types/components/schedule-list.d.ts +6 -0
- package/dist/types/components/schedule-list.d.ts.map +1 -0
- package/dist/types/components/schedule-runs.d.ts +8 -0
- package/dist/types/components/schedule-runs.d.ts.map +1 -0
- package/dist/types/components/secret-bulk.d.ts +8 -0
- package/dist/types/components/secret-bulk.d.ts.map +1 -0
- package/dist/types/components/secret-create.d.ts +9 -0
- package/dist/types/components/secret-create.d.ts.map +1 -0
- package/dist/types/components/secret-delete.d.ts +8 -0
- package/dist/types/components/secret-delete.d.ts.map +1 -0
- package/dist/types/components/secret-list.d.ts +7 -0
- package/dist/types/components/secret-list.d.ts.map +1 -0
- package/dist/types/components/secret-sync.d.ts +9 -0
- package/dist/types/components/secret-sync.d.ts.map +1 -0
- package/dist/types/components/watch.d.ts +7 -0
- package/dist/types/components/watch.d.ts.map +1 -0
- package/dist/types/hooks/useApi.d.ts +29 -0
- package/dist/types/hooks/useApi.d.ts.map +1 -0
- package/dist/types/positronic.d.ts +3 -0
- package/dist/types/positronic.d.ts.map +1 -0
- package/dist/types/test/mock-api-client.d.ts +25 -0
- package/dist/types/test/mock-api-client.d.ts.map +1 -0
- package/dist/types/test/test-dev-server.d.ts +129 -0
- package/dist/types/test/test-dev-server.d.ts.map +1 -0
- package/package.json +37 -0
- package/src/cli.ts +981 -0
- package/src/commands/backend.ts +63 -0
- package/src/commands/brain.test.ts +1004 -0
- package/src/commands/brain.ts +215 -0
- package/src/commands/helpers.test.ts +487 -0
- package/src/commands/helpers.ts +870 -0
- package/src/commands/project-config-manager.ts +152 -0
- package/src/commands/project.test.ts +502 -0
- package/src/commands/project.ts +109 -0
- package/src/commands/resources.test.ts +1052 -0
- package/src/commands/resources.ts +97 -0
- package/src/commands/schedule.test.ts +481 -0
- package/src/commands/schedule.ts +65 -0
- package/src/commands/secret.test.ts +210 -0
- package/src/commands/secret.ts +50 -0
- package/src/commands/server.test.ts +493 -0
- package/src/commands/server.ts +353 -0
- package/src/commands/test-utils.ts +324 -0
- package/src/components/brain-history.tsx +198 -0
- package/src/components/brain-list.tsx +105 -0
- package/src/components/brain-rerun.tsx +111 -0
- package/src/components/brain-show.tsx +92 -0
- package/src/components/error.tsx +24 -0
- package/src/components/project-add.tsx +59 -0
- package/src/components/project-create.tsx +83 -0
- package/src/components/project-list.tsx +83 -0
- package/src/components/project-remove.tsx +55 -0
- package/src/components/project-select.tsx +200 -0
- package/src/components/project-show.tsx +58 -0
- package/src/components/resource-clear.tsx +127 -0
- package/src/components/resource-delete.tsx +160 -0
- package/src/components/resource-list.tsx +177 -0
- package/src/components/resource-sync.tsx +170 -0
- package/src/components/resource-types.tsx +55 -0
- package/src/components/resource-upload.tsx +182 -0
- package/src/components/schedule-create.tsx +90 -0
- package/src/components/schedule-delete.tsx +116 -0
- package/src/components/schedule-list.tsx +186 -0
- package/src/components/schedule-runs.tsx +151 -0
- package/src/components/secret-bulk.tsx +79 -0
- package/src/components/secret-create.tsx +49 -0
- package/src/components/secret-delete.tsx +41 -0
- package/src/components/secret-list.tsx +41 -0
- package/src/components/watch.tsx +155 -0
- package/src/hooks/useApi.ts +183 -0
- package/src/positronic.ts +40 -0
- package/src/test/data/resources/config.json +1 -0
- package/src/test/data/resources/data/config.json +1 -0
- package/src/test/data/resources/data/logo.png +2 -0
- package/src/test/data/resources/docs/api.md +3 -0
- package/src/test/data/resources/docs/readme.md +3 -0
- package/src/test/data/resources/example.md +3 -0
- package/src/test/data/resources/file with spaces.txt +1 -0
- package/src/test/data/resources/readme.md +3 -0
- package/src/test/data/resources/test.txt +1 -0
- package/src/test/mock-api-client.ts +145 -0
- package/src/test/test-dev-server.ts +1003 -0
- package/tsconfig.json +11 -0
|
@@ -0,0 +1,83 @@
|
|
|
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 <name> --url <url>"
|
|
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 <name>" 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
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,200 @@
|
|
|
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 <name> --url <url>"
|
|
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 <name>" 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
|
+
};
|
|
@@ -0,0 +1,58 @@
|
|
|
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 <name> --url <url>"
|
|
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
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,160 @@
|
|
|
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
|
+
};
|