@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,198 +0,0 @@
1
- import React from 'react';
2
- import { Text, Box } from 'ink';
3
- import { useApiGet } from '../hooks/useApi.js';
4
- import { ErrorComponent } from './error.js';
5
-
6
- interface BrainHistoryProps {
7
- brainName: string;
8
- limit: number;
9
- }
10
-
11
- interface BrainRun {
12
- brainRunId: string;
13
- brainTitle: string;
14
- brainDescription?: string;
15
- type: string;
16
- status: 'PENDING' | 'RUNNING' | 'COMPLETE' | 'ERROR';
17
- options?: any;
18
- error?: any;
19
- createdAt: number;
20
- startedAt?: number;
21
- completedAt?: number;
22
- }
23
-
24
- interface BrainHistoryResponse {
25
- runs: BrainRun[];
26
- }
27
-
28
- // Helper to format dates
29
- const formatDate = (timestamp: number): string => {
30
- const date = new Date(timestamp);
31
- return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
32
- };
33
-
34
- // Helper to format relative time
35
- const formatRelativeTime = (timestamp: number): string => {
36
- const now = Date.now();
37
- const diffMs = now - timestamp;
38
- const diffMins = Math.floor(diffMs / (1000 * 60));
39
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
40
- const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
41
-
42
- if (diffMins < 1) {
43
- return 'just now';
44
- } else if (diffMins < 60) {
45
- return `${diffMins} min ago`;
46
- } else if (diffHours < 24) {
47
- return `${diffHours} hr ago`;
48
- } else {
49
- return `${diffDays} day${diffDays === 1 ? '' : 's'} ago`;
50
- }
51
- };
52
-
53
- // Helper to format duration
54
- const formatDuration = (startMs: number, endMs: number): string => {
55
- const durationMs = endMs - startMs;
56
- const seconds = Math.floor(durationMs / 1000);
57
- const minutes = Math.floor(seconds / 60);
58
-
59
- if (seconds < 60) {
60
- return `${seconds}s`;
61
- } else {
62
- const remainingSeconds = seconds % 60;
63
- return `${minutes}m ${remainingSeconds}s`;
64
- }
65
- };
66
-
67
- // Helper to get status color
68
- const getStatusColor = (status: string): string => {
69
- switch (status) {
70
- case 'COMPLETE':
71
- return 'green';
72
- case 'ERROR':
73
- return 'red';
74
- case 'RUNNING':
75
- return 'yellow';
76
- default:
77
- return 'gray';
78
- }
79
- };
80
-
81
- // Helper to pad text to column width
82
- const padRight = (text: string, width: number): string => {
83
- return text + ' '.repeat(Math.max(0, width - text.length));
84
- };
85
-
86
- // Helper to truncate text
87
- const truncate = (text: string, maxWidth: number): string => {
88
- if (text.length <= maxWidth) return text;
89
- return text.substring(0, maxWidth - 3) + '...';
90
- };
91
-
92
- export const BrainHistory = ({ brainName, limit }: BrainHistoryProps) => {
93
- const url = `/brains/${encodeURIComponent(brainName)}/history?limit=${limit}`;
94
- const { data, loading, error } = useApiGet<BrainHistoryResponse>(url);
95
-
96
- if (error) {
97
- return <ErrorComponent error={error} />;
98
- }
99
-
100
- if (loading) {
101
- return (
102
- <Box>
103
- <Text>🧠 Loading brain history...</Text>
104
- </Box>
105
- );
106
- }
107
-
108
- if (!data || data.runs.length === 0) {
109
- return (
110
- <Box flexDirection="column">
111
- <Text>No run history found for brain: {brainName}</Text>
112
- <Box marginTop={1}>
113
- <Text dimColor>
114
- Tip: Run this brain with "px run {brainName}" to create history
115
- </Text>
116
- </Box>
117
- </Box>
118
- );
119
- }
120
-
121
- // Define column widths
122
- const columns = {
123
- runId: { header: 'Run ID', width: 38 },
124
- status: { header: 'Status', width: 10 },
125
- type: { header: 'Type', width: 10 },
126
- when: { header: 'When', width: 12 },
127
- duration: { header: 'Duration', width: 10 },
128
- startedAt: { header: 'Started At', width: 20 },
129
- };
130
-
131
- return (
132
- <Box flexDirection="column" paddingTop={1} paddingBottom={1}>
133
- <Text bold>
134
- Recent runs for brain "{brainName}" ({data.runs.length} shown):
135
- </Text>
136
-
137
- <Box marginTop={1} flexDirection="column">
138
- {/* Header row */}
139
- <Box>
140
- <Text bold color="cyan">{padRight(columns.runId.header, columns.runId.width)}</Text>
141
- <Text> </Text>
142
- <Text bold color="cyan">{padRight(columns.status.header, columns.status.width)}</Text>
143
- <Text> </Text>
144
- <Text bold color="cyan">{padRight(columns.type.header, columns.type.width)}</Text>
145
- <Text> </Text>
146
- <Text bold color="cyan">{padRight(columns.when.header, columns.when.width)}</Text>
147
- <Text> </Text>
148
- <Text bold color="cyan">{padRight(columns.duration.header, columns.duration.width)}</Text>
149
- <Text> </Text>
150
- <Text bold color="cyan">{padRight(columns.startedAt.header, columns.startedAt.width)}</Text>
151
- </Box>
152
-
153
- {/* Separator */}
154
- <Box>
155
- <Text dimColor>{'─'.repeat(112)}</Text>
156
- </Box>
157
-
158
- {/* Data rows */}
159
- {data.runs.map((run) => {
160
- const duration = run.startedAt && run.completedAt
161
- ? formatDuration(run.startedAt, run.completedAt)
162
- : run.status === 'RUNNING' ? 'Running...' : 'N/A';
163
-
164
- return (
165
- <Box key={run.brainRunId}>
166
- <Text>{padRight(truncate(run.brainRunId, columns.runId.width), columns.runId.width)}</Text>
167
- <Text> </Text>
168
- <Text color={getStatusColor(run.status)}>
169
- {padRight(run.status, columns.status.width)}
170
- </Text>
171
- <Text> </Text>
172
- <Text>{padRight(run.type || 'N/A', columns.type.width)}</Text>
173
- <Text> </Text>
174
- <Text dimColor>{padRight(formatRelativeTime(run.createdAt), columns.when.width)}</Text>
175
- <Text> </Text>
176
- <Text>{padRight(duration, columns.duration.width)}</Text>
177
- <Text> </Text>
178
- <Text dimColor>{padRight(run.startedAt ? formatDate(run.startedAt) : 'N/A', columns.startedAt.width)}</Text>
179
- </Box>
180
- );
181
- })}
182
-
183
- {/* Show errors if any */}
184
- {data.runs.filter(r => r.status === 'ERROR' && r.error).length > 0 && (
185
- <Box flexDirection="column" marginTop={1}>
186
- <Text bold color="red">Errors:</Text>
187
- {data.runs.filter(r => r.status === 'ERROR' && r.error).map((run) => (
188
- <Box key={run.brainRunId} marginLeft={2}>
189
- <Text dimColor>{run.brainRunId}: </Text>
190
- <Text color="red">{typeof run.error === 'string' ? run.error : JSON.stringify(run.error)}</Text>
191
- </Box>
192
- ))}
193
- </Box>
194
- )}
195
- </Box>
196
- </Box>
197
- );
198
- };
@@ -1,105 +0,0 @@
1
- import React from 'react';
2
- import { Text, Box } from 'ink';
3
- import { ErrorComponent } from './error.js';
4
- import { useApiGet } from '../hooks/useApi.js';
5
-
6
- interface Brain {
7
- name: string;
8
- title: string;
9
- description: string;
10
- }
11
-
12
- interface BrainsResponse {
13
- brains: Brain[];
14
- count: number;
15
- }
16
-
17
- // Helper to truncate text to fit column width
18
- const truncate = (text: string, maxWidth: number): string => {
19
- if (text.length <= maxWidth) return text;
20
- return text.substring(0, maxWidth - 3) + '...';
21
- };
22
-
23
- // Helper to pad text to column width
24
- const padRight = (text: string, width: number): string => {
25
- return text + ' '.repeat(Math.max(0, width - text.length));
26
- };
27
-
28
- export const BrainList = () => {
29
- const { data, loading, error } = useApiGet<BrainsResponse>('/brains');
30
-
31
- if (error) {
32
- return <ErrorComponent error={error} />;
33
- }
34
-
35
- if (loading) {
36
- return (
37
- <Box>
38
- <Text>🧠 Loading brains...</Text>
39
- </Box>
40
- );
41
- }
42
-
43
- if (!data || data.brains.length === 0) {
44
- return (
45
- <Box flexDirection="column">
46
- <Text>No brains found.</Text>
47
- <Box marginTop={1}>
48
- <Text dimColor>
49
- Tip: Create a brain with "px brain new &lt;name&gt;" or add .ts files to the brains/ directory
50
- </Text>
51
- </Box>
52
- </Box>
53
- );
54
- }
55
-
56
- // Sort brains alphabetically by name
57
- const sortedBrains = [...data.brains].sort((a, b) => a.name.localeCompare(b.name));
58
-
59
- // Define column widths
60
- const columns = {
61
- name: { header: 'Name', width: 25 },
62
- title: { header: 'Title', width: 35 },
63
- description: { header: 'Description', width: 50 },
64
- };
65
-
66
- // Calculate total width for separator
67
- const totalWidth = Object.values(columns).reduce((sum, col) => sum + col.width + 2, 0) - 2;
68
-
69
- return (
70
- <Box flexDirection="column" paddingTop={1} paddingBottom={1}>
71
- <Text bold>
72
- Found {data.count} brain{data.count === 1 ? '' : 's'}:
73
- </Text>
74
-
75
- <Box marginTop={1} flexDirection="column">
76
- {/* Header row */}
77
- <Box>
78
- <Text bold color="cyan">{padRight(columns.name.header, columns.name.width)}</Text>
79
- <Text> </Text>
80
- <Text bold color="cyan">{padRight(columns.title.header, columns.title.width)}</Text>
81
- <Text> </Text>
82
- <Text bold color="cyan">{padRight(columns.description.header, columns.description.width)}</Text>
83
- </Box>
84
-
85
- {/* Separator */}
86
- <Box>
87
- <Text dimColor>{'─'.repeat(totalWidth)}</Text>
88
- </Box>
89
-
90
- {/* Data rows */}
91
- {sortedBrains.map((brain) => {
92
- return (
93
- <Box key={brain.name}>
94
- <Text>{padRight(truncate(brain.name, columns.name.width), columns.name.width)}</Text>
95
- <Text> </Text>
96
- <Text>{padRight(truncate(brain.title, columns.title.width), columns.title.width)}</Text>
97
- <Text> </Text>
98
- <Text dimColor>{padRight(truncate(brain.description, columns.description.width), columns.description.width)}</Text>
99
- </Box>
100
- );
101
- })}
102
- </Box>
103
- </Box>
104
- );
105
- };
@@ -1,111 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { Text, Box } from 'ink';
3
- import { apiClient } from '../commands/helpers.js';
4
- import { ErrorComponent } from './error.js';
5
-
6
- interface BrainRerunProps {
7
- brainName: string;
8
- runId?: string;
9
- startsAt?: number;
10
- stopsAfter?: number;
11
- }
12
-
13
- interface BrainRerunResponse {
14
- brainRunId: string;
15
- }
16
-
17
- export const BrainRerun = ({ brainName, runId, startsAt, stopsAfter }: BrainRerunProps) => {
18
- const [isLoading, setIsLoading] = useState(true);
19
- const [error, setError] = useState<string | null>(null);
20
- const [newRunId, setNewRunId] = useState<string | null>(null);
21
-
22
- useEffect(() => {
23
- const rerunBrain = async () => {
24
- try {
25
- const body: any = { brainName };
26
- if (runId) body.runId = runId;
27
- if (startsAt !== undefined) body.startsAt = startsAt;
28
- if (stopsAfter !== undefined) body.stopsAfter = stopsAfter;
29
-
30
- const response = await apiClient.fetch('/brains/runs/rerun', {
31
- method: 'POST',
32
- headers: {
33
- 'Content-Type': 'application/json',
34
- },
35
- body: JSON.stringify(body),
36
- });
37
-
38
- if (response.status === 201) {
39
- const result = (await response.json()) as BrainRerunResponse;
40
- setNewRunId(result.brainRunId);
41
- } else if (response.status === 404) {
42
- const errorData = await response.json();
43
- setError(errorData.error || `Brain or run not found`);
44
- } else {
45
- const errorText = await response.text();
46
- setError(`Server returned ${response.status}: ${errorText}`);
47
- }
48
- } catch (err: any) {
49
- setError(`Connection error: ${err.message}`);
50
- } finally {
51
- setIsLoading(false);
52
- }
53
- };
54
-
55
- rerunBrain();
56
- }, [brainName, runId, startsAt, stopsAfter]);
57
-
58
- if (isLoading) {
59
- return (
60
- <Box>
61
- <Text>🔄 Starting brain rerun...</Text>
62
- </Box>
63
- );
64
- }
65
-
66
- if (error) {
67
- const errorDetails = runId
68
- ? `Make sure the brain "${brainName}" and run ID "${runId}" exist.\nYou can list brain history with: positronic brain history ${brainName}`
69
- : `Make sure the brain "${brainName}" exists.\nYou can list available brains with: positronic brain list`;
70
-
71
- return (
72
- <ErrorComponent
73
- error={{
74
- title: 'Brain Rerun Failed',
75
- message: error,
76
- details: errorDetails
77
- }}
78
- />
79
- );
80
- }
81
-
82
- if (newRunId) {
83
- const runDetails = runId ? ` from run ${runId}` : '';
84
- const rangeDetails = startsAt || stopsAfter
85
- ? ` (${startsAt ? `starting at step ${startsAt}` : ''}${startsAt && stopsAfter ? ', ' : ''}${stopsAfter ? `stopping after step ${stopsAfter}` : ''})`
86
- : '';
87
-
88
- return (
89
- <Box flexDirection="column">
90
- <Text bold color="green">✅ Brain rerun started successfully!</Text>
91
- <Text>
92
- New run ID: <Text bold>{newRunId}</Text>
93
- </Text>
94
- <Text dimColor>
95
- Rerunning brain "{brainName}"{runDetails}{rangeDetails}
96
- </Text>
97
- <Box marginTop={1}>
98
- <Text dimColor>
99
- Watch the run with: positronic watch --run-id {newRunId}
100
- </Text>
101
- </Box>
102
- </Box>
103
- );
104
- }
105
-
106
- return (
107
- <Box>
108
- <Text color="red">❌ Unexpected error occurred</Text>
109
- </Box>
110
- );
111
- };
@@ -1,92 +0,0 @@
1
- import React from 'react';
2
- import { Text, Box } from 'ink';
3
- import { useApiGet } from '../hooks/useApi.js';
4
- import { ErrorComponent } from './error.js';
5
-
6
- interface BrainStep {
7
- type: 'step' | 'brain';
8
- title: string;
9
- innerBrain?: {
10
- title: string;
11
- description?: string;
12
- steps: BrainStep[];
13
- };
14
- }
15
-
16
- interface BrainDetails {
17
- name: string;
18
- title: string;
19
- description?: string;
20
- steps: BrainStep[];
21
- }
22
-
23
- interface BrainShowProps {
24
- brainName: string;
25
- }
26
-
27
- const StepDisplay: React.FC<{ step: BrainStep; indent?: number }> = ({ step, indent = 0 }) => {
28
- const indentStr = ' '.repeat(indent);
29
-
30
- if (step.type === 'step') {
31
- return (
32
- <Text>
33
- {indentStr}• {step.title}
34
- </Text>
35
- );
36
- } else {
37
- // Nested brain
38
- return (
39
- <Box flexDirection="column">
40
- <Text>
41
- {indentStr}▸ {step.title} <Text dimColor>(nested brain)</Text>
42
- </Text>
43
- {step.innerBrain?.steps.map((innerStep, idx) => (
44
- <StepDisplay key={idx} step={innerStep} indent={indent + 1} />
45
- ))}
46
- </Box>
47
- );
48
- }
49
- };
50
-
51
- export const BrainShow = ({ brainName }: BrainShowProps) => {
52
- const { data, loading, error } = useApiGet<BrainDetails>(`/brains/${encodeURIComponent(brainName)}`);
53
-
54
- if (loading) {
55
- return (
56
- <Box>
57
- <Text>🧠 Loading brain details...</Text>
58
- </Box>
59
- );
60
- }
61
-
62
- if (error) {
63
- return <ErrorComponent error={error} />;
64
- }
65
-
66
- if (!data) {
67
- return (
68
- <Box flexDirection="column">
69
- <Text color="red">Brain '{brainName}' not found</Text>
70
- </Box>
71
- );
72
- }
73
-
74
- return (
75
- <Box flexDirection="column" gap={1}>
76
- <Text bold underline>{data.title}</Text>
77
-
78
- {data.description && (
79
- <Text dimColor>{data.description}</Text>
80
- )}
81
-
82
- <Box flexDirection="column">
83
- <Text bold>Steps:</Text>
84
- <Box flexDirection="column" marginLeft={1}>
85
- {data.steps.map((step: BrainStep, idx: number) => (
86
- <StepDisplay key={idx} step={step} />
87
- ))}
88
- </Box>
89
- </Box>
90
- </Box>
91
- );
92
- };
@@ -1,24 +0,0 @@
1
- import React from 'react';
2
- import { Box, Text } from 'ink';
3
-
4
- interface ErrorComponentProps {
5
- error: {
6
- title: string;
7
- message: string;
8
- details?: string;
9
- };
10
- }
11
-
12
- export const ErrorComponent = ({ error }: ErrorComponentProps) => {
13
- return (
14
- <Box flexDirection="column">
15
- <Text color="red" bold>❌ {error.title}</Text>
16
- <Box paddingLeft={2} flexDirection="column">
17
- <Text color="red">{error.message}</Text>
18
- {error.details && (
19
- <Text color="red" dimColor>{error.details}</Text>
20
- )}
21
- </Box>
22
- </Box>
23
- );
24
- };
@@ -1,59 +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 ProjectAddProps {
6
- name: string;
7
- url: string;
8
- projectConfig: ProjectConfigManager;
9
- }
10
-
11
- export const ProjectAdd = ({ name, url, projectConfig }: ProjectAddProps) => {
12
- const [result, setResult] = useState<{ success: boolean; error?: string } | null>(null);
13
-
14
- useEffect(() => {
15
- const addResult = projectConfig.addProject(name, url);
16
- setResult(addResult);
17
- }, [name, url, projectConfig]);
18
-
19
- if (!result) {
20
- return <Text>Adding project...</Text>;
21
- }
22
-
23
- if (result.success) {
24
- const config = projectConfig.read();
25
- const isCurrentProject = config.currentProject === name;
26
-
27
- return (
28
- <Box flexDirection="column">
29
- <Text color="green">✅ Project "{name}" added successfully!</Text>
30
- <Box marginTop={1} paddingLeft={2} flexDirection="column">
31
- <Text>
32
- <Text bold>URL:</Text> {url}
33
- </Text>
34
- {isCurrentProject && (
35
- <Text>
36
- <Text bold>Status:</Text> <Text color="cyan">Current project</Text>
37
- </Text>
38
- )}
39
- </Box>
40
- <Box marginTop={1}>
41
- <Text dimColor>
42
- {isCurrentProject
43
- ? 'This project is now your active project.'
44
- : `Use "px project select ${name}" to switch to this project.`}
45
- </Text>
46
- </Box>
47
- </Box>
48
- );
49
- }
50
-
51
- return (
52
- <Box flexDirection="column">
53
- <Text color="red">❌ Failed to add project</Text>
54
- <Box paddingLeft={2}>
55
- <Text color="red">{result.error}</Text>
56
- </Box>
57
- </Box>
58
- );
59
- };
@@ -1,83 +0,0 @@
1
- import React, { useState, useEffect } from 'react';
2
- import { Box, Text } from 'ink';
3
- import { generateProject } from '../commands/helpers.js';
4
- import path from 'path';
5
-
6
- interface ProjectCreateProps {
7
- projectPathArg: string;
8
- }
9
-
10
- export const ProjectCreate = ({ projectPathArg }: ProjectCreateProps) => {
11
- const [status, setStatus] = useState<'creating' | 'success' | 'error'>('creating');
12
- const [error, setError] = useState<string | null>(null);
13
- const [projectDir, setProjectDir] = useState<string>('');
14
- const [projectName, setProjectName] = useState<string>('');
15
-
16
- useEffect(() => {
17
- const createProject = async () => {
18
- try {
19
- const resolvedProjectDir = path.resolve(projectPathArg);
20
- const resolvedProjectName = path.basename(resolvedProjectDir);
21
-
22
- setProjectDir(resolvedProjectDir);
23
- setProjectName(resolvedProjectName);
24
-
25
- await generateProject(resolvedProjectName, resolvedProjectDir);
26
- setStatus('success');
27
- } catch (err) {
28
- setError(err instanceof Error ? err.message : 'Unknown error occurred');
29
- setStatus('error');
30
- }
31
- };
32
-
33
- createProject();
34
- }, [projectPathArg]);
35
-
36
- if (status === 'creating') {
37
- return (
38
- <Box flexDirection="column">
39
- <Text>Creating project...</Text>
40
- </Box>
41
- );
42
- }
43
-
44
- if (status === 'error') {
45
- return (
46
- <Box flexDirection="column">
47
- <Text color="red">Error creating project: {error}</Text>
48
- </Box>
49
- );
50
- }
51
-
52
- return (
53
- <Box flexDirection="column" paddingTop={1} paddingBottom={1}>
54
- <Text bold color="green">Project Created Successfully!</Text>
55
- <Box marginTop={1} paddingLeft={2} flexDirection="column">
56
- <Text>
57
- <Text bold>Name:</Text> {projectName}
58
- </Text>
59
- <Text>
60
- <Text bold>Location:</Text> {projectDir}
61
- </Text>
62
- </Box>
63
-
64
- <Box marginTop={1} flexDirection="column">
65
- <Text bold>Next steps:</Text>
66
- <Box paddingLeft={2} flexDirection="column">
67
- <Text>
68
- <Text bold>1.</Text> cd {projectDir}
69
- </Text>
70
- <Text>
71
- <Text bold>2.</Text> Install dependencies if you didn't choose to during setup (e.g., npm install)
72
- </Text>
73
- <Text>
74
- <Text bold>3.</Text> Run the development server: px s or positronic server
75
- </Text>
76
- <Text>
77
- <Text bold>4.</Text> Open a new terminal in '{projectName}' and run a brain: px run example --watch
78
- </Text>
79
- </Box>
80
- </Box>
81
- </Box>
82
- );
83
- };