@positronic/cli 0.0.3 → 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 (81) hide show
  1. package/dist/src/commands/helpers.js +11 -25
  2. package/dist/types/commands/helpers.d.ts.map +1 -1
  3. package/package.json +5 -1
  4. package/dist/src/commands/brain.test.js +0 -2936
  5. package/dist/src/commands/helpers.test.js +0 -832
  6. package/dist/src/commands/project.test.js +0 -1201
  7. package/dist/src/commands/resources.test.js +0 -2511
  8. package/dist/src/commands/schedule.test.js +0 -1235
  9. package/dist/src/commands/secret.test.d.js +0 -1
  10. package/dist/src/commands/secret.test.js +0 -761
  11. package/dist/src/commands/server.test.js +0 -1237
  12. package/dist/src/commands/test-utils.js +0 -737
  13. package/dist/src/components/secret-sync.js +0 -303
  14. package/dist/src/test/mock-api-client.js +0 -371
  15. package/dist/src/test/test-dev-server.js +0 -1376
  16. package/dist/types/commands/test-utils.d.ts +0 -45
  17. package/dist/types/commands/test-utils.d.ts.map +0 -1
  18. package/dist/types/components/secret-sync.d.ts +0 -9
  19. package/dist/types/components/secret-sync.d.ts.map +0 -1
  20. package/dist/types/test/mock-api-client.d.ts +0 -25
  21. package/dist/types/test/mock-api-client.d.ts.map +0 -1
  22. package/dist/types/test/test-dev-server.d.ts +0 -129
  23. package/dist/types/test/test-dev-server.d.ts.map +0 -1
  24. package/src/cli.ts +0 -997
  25. package/src/commands/backend.ts +0 -63
  26. package/src/commands/brain.test.ts +0 -1004
  27. package/src/commands/brain.ts +0 -215
  28. package/src/commands/helpers.test.ts +0 -487
  29. package/src/commands/helpers.ts +0 -870
  30. package/src/commands/project-config-manager.ts +0 -152
  31. package/src/commands/project.test.ts +0 -502
  32. package/src/commands/project.ts +0 -109
  33. package/src/commands/resources.test.ts +0 -1052
  34. package/src/commands/resources.ts +0 -97
  35. package/src/commands/schedule.test.ts +0 -481
  36. package/src/commands/schedule.ts +0 -65
  37. package/src/commands/secret.test.ts +0 -210
  38. package/src/commands/secret.ts +0 -50
  39. package/src/commands/server.test.ts +0 -493
  40. package/src/commands/server.ts +0 -353
  41. package/src/commands/test-utils.ts +0 -324
  42. package/src/components/brain-history.tsx +0 -198
  43. package/src/components/brain-list.tsx +0 -105
  44. package/src/components/brain-rerun.tsx +0 -111
  45. package/src/components/brain-show.tsx +0 -92
  46. package/src/components/error.tsx +0 -24
  47. package/src/components/project-add.tsx +0 -59
  48. package/src/components/project-create.tsx +0 -83
  49. package/src/components/project-list.tsx +0 -83
  50. package/src/components/project-remove.tsx +0 -55
  51. package/src/components/project-select.tsx +0 -200
  52. package/src/components/project-show.tsx +0 -58
  53. package/src/components/resource-clear.tsx +0 -127
  54. package/src/components/resource-delete.tsx +0 -160
  55. package/src/components/resource-list.tsx +0 -177
  56. package/src/components/resource-sync.tsx +0 -170
  57. package/src/components/resource-types.tsx +0 -55
  58. package/src/components/resource-upload.tsx +0 -182
  59. package/src/components/schedule-create.tsx +0 -90
  60. package/src/components/schedule-delete.tsx +0 -116
  61. package/src/components/schedule-list.tsx +0 -186
  62. package/src/components/schedule-runs.tsx +0 -151
  63. package/src/components/secret-bulk.tsx +0 -79
  64. package/src/components/secret-create.tsx +0 -49
  65. package/src/components/secret-delete.tsx +0 -41
  66. package/src/components/secret-list.tsx +0 -41
  67. package/src/components/watch.tsx +0 -155
  68. package/src/hooks/useApi.ts +0 -183
  69. package/src/positronic.ts +0 -40
  70. package/src/test/data/resources/config.json +0 -1
  71. package/src/test/data/resources/data/config.json +0 -1
  72. package/src/test/data/resources/data/logo.png +0 -2
  73. package/src/test/data/resources/docs/api.md +0 -3
  74. package/src/test/data/resources/docs/readme.md +0 -3
  75. package/src/test/data/resources/example.md +0 -3
  76. package/src/test/data/resources/file with spaces.txt +0 -1
  77. package/src/test/data/resources/readme.md +0 -3
  78. package/src/test/data/resources/test.txt +0 -1
  79. package/src/test/mock-api-client.ts +0 -145
  80. package/src/test/test-dev-server.ts +0 -1003
  81. package/tsconfig.json +0 -11
@@ -1,116 +0,0 @@
1
- import React, { useState, useEffect, useRef } from 'react';
2
- import { Box, Text, useStdin, useApp } from 'ink';
3
- import { ErrorComponent } from './error.js';
4
- import { useApiDelete } from '../hooks/useApi.js';
5
-
6
- interface ScheduleDeleteProps {
7
- scheduleId: string;
8
- force: boolean;
9
- }
10
-
11
- export const ScheduleDelete = ({ scheduleId, force }: ScheduleDeleteProps) => {
12
- const [confirmed, setConfirmed] = useState(force);
13
- const [deleted, setDeleted] = useState(false);
14
- const [input, setInput] = useState('');
15
- const { stdin, setRawMode } = useStdin();
16
- const { exit } = useApp();
17
- const isDeleting = useRef(false);
18
-
19
- const { execute: deleteSchedule, loading, error } = useApiDelete('schedule');
20
-
21
- useEffect(() => {
22
- if (stdin && !confirmed && !deleted) {
23
- setRawMode(true);
24
-
25
- const handleData = (data: Buffer) => {
26
- const char = data.toString();
27
-
28
- if (char === '\r' || char === '\n') {
29
- if (input.toLowerCase() === 'yes') {
30
- setConfirmed(true);
31
- } else {
32
- exit();
33
- }
34
- } else if (char === '\u0003') { // Ctrl+C
35
- exit();
36
- } else if (char === '\u007F' || char === '\b') { // Backspace
37
- setInput(prev => prev.slice(0, -1));
38
- } else {
39
- setInput(prev => prev + char);
40
- }
41
- };
42
-
43
- stdin.on('data', handleData);
44
-
45
- return () => {
46
- stdin.off('data', handleData);
47
- setRawMode(false);
48
- };
49
- }
50
- }, [stdin, setRawMode, confirmed, deleted, input, exit]);
51
-
52
- useEffect(() => {
53
- if (confirmed && !deleted && !isDeleting.current) {
54
- isDeleting.current = true;
55
- deleteSchedule(`/brains/schedules/${scheduleId}`)
56
- .then(() => {
57
- setDeleted(true);
58
- })
59
- .catch(() => {
60
- // Error is already handled by useApiDelete
61
- })
62
- .finally(() => {
63
- isDeleting.current = false;
64
- });
65
- }
66
- }, [confirmed, deleted, scheduleId]);
67
-
68
- if (error) {
69
- // Check if it's a 404 error
70
- if (error.details?.includes('404')) {
71
- return (
72
- <Box flexDirection="column">
73
- <Text color="red">❌ Schedule not found</Text>
74
- <Box marginTop={1} paddingLeft={2}>
75
- <Text>No schedule found with ID: {scheduleId}</Text>
76
- </Box>
77
- </Box>
78
- );
79
- }
80
- return <ErrorComponent error={error} />;
81
- }
82
-
83
- if (loading) {
84
- return (
85
- <Box>
86
- <Text>🗑️ Deleting schedule...</Text>
87
- </Box>
88
- );
89
- }
90
-
91
- if (deleted) {
92
- return (
93
- <Box flexDirection="column">
94
- <Text color="green">✅ Schedule deleted successfully!</Text>
95
- <Box marginTop={1} paddingLeft={2}>
96
- <Text dimColor>Schedule ID: {scheduleId}</Text>
97
- </Box>
98
- </Box>
99
- );
100
- }
101
-
102
- if (!confirmed) {
103
- return (
104
- <Box flexDirection="column">
105
- <Text bold color="yellow">⚠️ Warning: This will permanently delete the schedule</Text>
106
- <Box marginTop={1} marginBottom={1} paddingLeft={2} flexDirection="column">
107
- <Text>Schedule ID: {scheduleId}</Text>
108
- <Text dimColor>All future runs for this schedule will be cancelled.</Text>
109
- </Box>
110
- <Text>Type "yes" to confirm deletion: {input}</Text>
111
- </Box>
112
- );
113
- }
114
-
115
- return null;
116
- };
@@ -1,186 +0,0 @@
1
- import React from 'react';
2
- import { Box, Text } from 'ink';
3
- import { ErrorComponent } from './error.js';
4
- import { useApiGet } from '../hooks/useApi.js';
5
-
6
- interface ScheduleListProps {
7
- brainFilter?: string;
8
- }
9
-
10
- interface Schedule {
11
- id: string;
12
- brainName: string;
13
- cronExpression: string;
14
- enabled: boolean;
15
- createdAt: number;
16
- nextRunAt?: number;
17
- }
18
-
19
- interface SchedulesResponse {
20
- schedules: Schedule[];
21
- count: number;
22
- }
23
-
24
- // Helper to format dates consistently
25
- const formatDate = (timestamp: number): string => {
26
- const date = new Date(timestamp);
27
- return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
28
- };
29
-
30
- // Helper to format relative time
31
- const formatRelativeTime = (date: Date): string => {
32
- const now = new Date();
33
- const diffMs = date.getTime() - now.getTime();
34
- const diffMins = Math.floor(diffMs / (1000 * 60));
35
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
36
- const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
37
-
38
- if (diffMs < 0) {
39
- return '(overdue)';
40
- } else if (diffMins < 1) {
41
- return '< 1 min';
42
- } else if (diffMins < 60) {
43
- return `${diffMins} min`;
44
- } else if (diffHours < 24) {
45
- return `${diffHours} hr`;
46
- } else {
47
- return `${diffDays} day${diffDays === 1 ? '' : 's'}`;
48
- }
49
- };
50
-
51
- // Helper to truncate text to fit column width
52
- const truncate = (text: string, maxWidth: number): string => {
53
- if (text.length <= maxWidth) return text;
54
- return text.substring(0, maxWidth - 3) + '...';
55
- };
56
-
57
- // Helper to pad text to column width
58
- const padRight = (text: string, width: number): string => {
59
- return text + ' '.repeat(Math.max(0, width - text.length));
60
- };
61
-
62
- export const ScheduleList = ({ brainFilter }: ScheduleListProps) => {
63
- const { data, loading, error } = useApiGet<SchedulesResponse>('/brains/schedules');
64
-
65
- if (error) {
66
- return <ErrorComponent error={error} />;
67
- }
68
-
69
- if (loading) {
70
- return (
71
- <Box>
72
- <Text>📋 Loading schedules...</Text>
73
- </Box>
74
- );
75
- }
76
-
77
- if (!data || data.schedules.length === 0) {
78
- return (
79
- <Box flexDirection="column">
80
- <Text>No schedules found.</Text>
81
- <Box marginTop={1}>
82
- <Text dimColor>
83
- Tip: Create a schedule with "px schedule create &lt;brain-name&gt; &lt;cron-expression&gt;"
84
- </Text>
85
- </Box>
86
- </Box>
87
- );
88
- }
89
-
90
- // Filter schedules if brain filter is provided
91
- const filteredSchedules = brainFilter
92
- ? data.schedules.filter(s => s.brainName === brainFilter)
93
- : data.schedules;
94
-
95
- if (brainFilter && filteredSchedules.length === 0) {
96
- return (
97
- <Box flexDirection="column">
98
- <Text>No schedules found for brain: {brainFilter}</Text>
99
- <Box marginTop={1}>
100
- <Text dimColor>
101
- Tip: Create a schedule with "px schedule create {brainFilter} &lt;cron-expression&gt;"
102
- </Text>
103
- </Box>
104
- </Box>
105
- );
106
- }
107
-
108
- // Sort schedules by creation date (newest first)
109
- const sortedSchedules = [...filteredSchedules].sort((a, b) => b.createdAt - a.createdAt);
110
-
111
- // Define column widths
112
- const columns = {
113
- brainName: { header: 'Brain Name', width: 20 },
114
- schedule: { header: 'Schedule', width: 15 },
115
- status: { header: 'Status', width: 10 },
116
- nextRun: { header: 'Next Run', width: 12 },
117
- created: { header: 'Created', width: 20 },
118
- id: { header: 'ID', width: 36 },
119
- };
120
-
121
- // Calculate total width for separator
122
- const totalWidth = Object.values(columns).reduce((sum, col) => sum + col.width + 2, 0) - 2;
123
-
124
- return (
125
- <Box flexDirection="column" paddingTop={1} paddingBottom={1}>
126
- <Text bold>
127
- {brainFilter
128
- ? `Found ${filteredSchedules.length} schedule${filteredSchedules.length === 1 ? '' : 's'} for brain "${brainFilter}"`
129
- : `Found ${data.count} schedule${data.count === 1 ? '' : 's'}`
130
- }:
131
- </Text>
132
-
133
- <Box marginTop={1} flexDirection="column">
134
- {/* Header row */}
135
- <Box>
136
- <Text bold color="cyan">{padRight(columns.brainName.header, columns.brainName.width)}</Text>
137
- <Text> </Text>
138
- <Text bold color="cyan">{padRight(columns.schedule.header, columns.schedule.width)}</Text>
139
- <Text> </Text>
140
- <Text bold color="cyan">{padRight(columns.status.header, columns.status.width)}</Text>
141
- <Text> </Text>
142
- <Text bold color="cyan">{padRight(columns.nextRun.header, columns.nextRun.width)}</Text>
143
- <Text> </Text>
144
- <Text bold color="cyan">{padRight(columns.created.header, columns.created.width)}</Text>
145
- <Text> </Text>
146
- <Text bold color="cyan">{padRight(columns.id.header, columns.id.width)}</Text>
147
- </Box>
148
-
149
- {/* Separator */}
150
- <Box>
151
- <Text dimColor>{'─'.repeat(totalWidth)}</Text>
152
- </Box>
153
-
154
- {/* Data rows */}
155
- {sortedSchedules.map((schedule) => {
156
- const nextRunDate = schedule.nextRunAt ? new Date(schedule.nextRunAt) : null;
157
- const createdDate = new Date(schedule.createdAt);
158
- const isOverdue = nextRunDate && nextRunDate.getTime() < Date.now();
159
-
160
- return (
161
- <Box key={schedule.id}>
162
- <Text>{padRight(truncate(schedule.brainName, columns.brainName.width), columns.brainName.width)}</Text>
163
- <Text> </Text>
164
- <Text>{padRight(truncate(schedule.cronExpression, columns.schedule.width), columns.schedule.width)}</Text>
165
- <Text> </Text>
166
- <Text color={schedule.enabled ? 'green' : 'red'}>
167
- {padRight(schedule.enabled ? 'Enabled' : 'Disabled', columns.status.width)}
168
- </Text>
169
- <Text> </Text>
170
- <Text color={isOverdue ? 'red' : undefined}>
171
- {padRight(
172
- nextRunDate ? formatRelativeTime(nextRunDate) : 'N/A',
173
- columns.nextRun.width
174
- )}
175
- </Text>
176
- <Text> </Text>
177
- <Text dimColor>{padRight(truncate(formatDate(schedule.createdAt), columns.created.width), columns.created.width)}</Text>
178
- <Text> </Text>
179
- <Text dimColor>{padRight(schedule.id, columns.id.width)}</Text>
180
- </Box>
181
- );
182
- })}
183
- </Box>
184
- </Box>
185
- );
186
- };
@@ -1,151 +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 ScheduleRunsProps {
7
- scheduleId?: string;
8
- limit: number;
9
- status?: 'triggered' | 'failed' | 'complete';
10
- }
11
-
12
- interface ScheduledRun {
13
- id: string;
14
- scheduleId: string;
15
- status: 'triggered' | 'failed';
16
- ranAt: number;
17
- brainRunId?: string;
18
- error?: string;
19
- }
20
-
21
- interface ScheduleRunsResponse {
22
- runs: ScheduledRun[];
23
- count: number;
24
- }
25
-
26
- // Helper to format dates
27
- const formatDate = (timestamp: number): string => {
28
- const date = new Date(timestamp);
29
- return date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
30
- };
31
-
32
- // Helper to format relative time
33
- const formatRelativeTime = (timestamp: number): string => {
34
- const now = Date.now();
35
- const diffMs = now - timestamp;
36
- const diffMins = Math.floor(diffMs / (1000 * 60));
37
- const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
38
- const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
39
-
40
- if (diffMins < 1) {
41
- return 'just now';
42
- } else if (diffMins < 60) {
43
- return `${diffMins} min ago`;
44
- } else if (diffHours < 24) {
45
- return `${diffHours} hr ago`;
46
- } else {
47
- return `${diffDays} day${diffDays === 1 ? '' : 's'} ago`;
48
- }
49
- };
50
-
51
- export const ScheduleRuns = ({ scheduleId, limit, status }: ScheduleRunsProps) => {
52
- // Build query params
53
- const params = new URLSearchParams();
54
- if (scheduleId) params.set('scheduleId', scheduleId);
55
- params.set('limit', limit.toString());
56
-
57
- const queryString = params.toString();
58
- const url = `/brains/schedules/runs${queryString ? `?${queryString}` : ''}`;
59
-
60
- const { data, loading, error } = useApiGet<ScheduleRunsResponse>(url);
61
-
62
- if (error) {
63
- return <ErrorComponent error={error} />;
64
- }
65
-
66
- if (loading) {
67
- return (
68
- <Box>
69
- <Text>📋 Loading scheduled runs...</Text>
70
- </Box>
71
- );
72
- }
73
-
74
- if (!data || data.runs.length === 0) {
75
- return (
76
- <Box flexDirection="column">
77
- <Text>No scheduled runs found.</Text>
78
- {scheduleId && (
79
- <Box marginTop={1}>
80
- <Text dimColor>No runs found for schedule ID: {scheduleId}</Text>
81
- </Box>
82
- )}
83
- </Box>
84
- );
85
- }
86
-
87
- // Filter by status if provided (client-side filtering since API doesn't support it)
88
- const filteredRuns = status
89
- ? data.runs.filter(run => run.status === status)
90
- : data.runs;
91
-
92
- if (status && filteredRuns.length === 0) {
93
- return (
94
- <Box flexDirection="column">
95
- <Text>No runs found with status: {status}</Text>
96
- </Box>
97
- );
98
- }
99
-
100
- return (
101
- <Box flexDirection="column" paddingTop={1} paddingBottom={1}>
102
- <Text bold>
103
- Found {filteredRuns.length} scheduled run{filteredRuns.length === 1 ? '' : 's'}
104
- {scheduleId && ` for schedule ${scheduleId}`}
105
- {status && ` with status ${status}`}:
106
- </Text>
107
-
108
- <Box marginTop={1} flexDirection="column">
109
- {/* Header row */}
110
- <Box>
111
- <Text bold color="cyan">{padRight('Run ID', 38)}</Text>
112
- <Text> </Text>
113
- <Text bold color="cyan">{padRight('Schedule ID', 38)}</Text>
114
- <Text> </Text>
115
- <Text bold color="cyan">{padRight('Status', 10)}</Text>
116
- <Text> </Text>
117
- <Text bold color="cyan">{padRight('Ran At', 20)}</Text>
118
- <Text> </Text>
119
- <Text bold color="cyan">{padRight('When', 12)}</Text>
120
- </Box>
121
-
122
- {/* Separator */}
123
- <Box>
124
- <Text dimColor>{'─'.repeat(130)}</Text>
125
- </Box>
126
-
127
- {/* Data rows */}
128
- {filteredRuns.map((run) => (
129
- <Box key={run.id}>
130
- <Text>{padRight(run.id, 38)}</Text>
131
- <Text> </Text>
132
- <Text>{padRight(run.scheduleId, 38)}</Text>
133
- <Text> </Text>
134
- <Text color={run.status === 'triggered' ? 'green' : 'red'}>
135
- {padRight(run.status, 10)}
136
- </Text>
137
- <Text> </Text>
138
- <Text dimColor>{padRight(formatDate(run.ranAt), 20)}</Text>
139
- <Text> </Text>
140
- <Text dimColor>{padRight(formatRelativeTime(run.ranAt), 12)}</Text>
141
- </Box>
142
- ))}
143
- </Box>
144
- </Box>
145
- );
146
- };
147
-
148
- // Helper to pad text to column width
149
- const padRight = (text: string, width: number): string => {
150
- return text + ' '.repeat(Math.max(0, width - text.length));
151
- };
@@ -1,79 +0,0 @@
1
- import React, { useEffect, useState } from 'react';
2
- import { Text, Box } from 'ink';
3
- import type { PositronicDevServer } from '@positronic/spec';
4
- import * as path from 'path';
5
- import * as fs from 'fs';
6
-
7
- interface SecretBulkProps {
8
- file?: string;
9
- server?: PositronicDevServer;
10
- }
11
-
12
- export const SecretBulk = ({ file = '.env', server }: SecretBulkProps) => {
13
- const [loading, setLoading] = useState(true);
14
- const [error, setError] = useState<string | null>(null);
15
- const [completed, setCompleted] = useState(false);
16
-
17
- useEffect(() => {
18
- const bulkUploadSecrets = async () => {
19
- if (!server) {
20
- setError('No server connection available');
21
- setLoading(false);
22
- return;
23
- }
24
-
25
-
26
- try {
27
- setLoading(true);
28
- setError(null);
29
-
30
- // Always read from .env file
31
- const filePath = path.resolve(server.projectRootDir, file);
32
-
33
- if (!fs.existsSync(filePath)) {
34
- throw new Error(
35
- `No .env file found at ${filePath}\n\n` +
36
- 'To use this command, create a .env file in your project root with your secrets:\n' +
37
- ' ANTHROPIC_API_KEY=sk-ant-...\n' +
38
- ' DATABASE_URL=postgres://...\n' +
39
- ' REDIS_URL=redis://...'
40
- );
41
- }
42
-
43
- // Pass the file path to the backend
44
- await server.bulkSecrets(filePath);
45
- setCompleted(true);
46
- setLoading(false);
47
- } catch (error) {
48
- setError(error instanceof Error ? error.message : 'Failed to bulk upload secrets');
49
- setLoading(false);
50
- }
51
- };
52
-
53
- bulkUploadSecrets();
54
- }, [file, server]);
55
-
56
- if (error) {
57
- return (
58
- <Box flexDirection="column">
59
- <Text color="red">❌ Error: {error}</Text>
60
- </Box>
61
- );
62
- }
63
-
64
- if (loading) {
65
- return (
66
- <Box>
67
- <Text>🔄 Uploading secrets{file ? ` from ${file}` : ' from stdin'}...</Text>
68
- </Box>
69
- );
70
- }
71
-
72
- if (completed) {
73
- // The bulk command itself outputs success message to console
74
- // We just need to ensure the component renders something to prevent React errors
75
- return <Box />;
76
- }
77
-
78
- return null;
79
- };
@@ -1,49 +0,0 @@
1
- import React, { useEffect } from 'react';
2
- import { useApp } from 'ink';
3
- import type { PositronicDevServer } from '@positronic/spec';
4
-
5
- interface SecretCreateProps {
6
- name: string;
7
- value?: string;
8
- server?: PositronicDevServer;
9
- }
10
-
11
- export const SecretCreate = ({ name, value: providedValue, server }: SecretCreateProps) => {
12
- const { exit } = useApp();
13
-
14
- useEffect(() => {
15
- const createSecret = async () => {
16
- if (!server) {
17
- console.error('No project found. Please run this command from within a Positronic project directory.');
18
- exit();
19
- return;
20
- }
21
-
22
- if (!server.setSecret) {
23
- console.error('Secret management not supported for this backend');
24
- exit();
25
- return;
26
- }
27
-
28
- // If no value provided, backend will prompt for it
29
- if (!providedValue) {
30
- console.error('Please provide a value using --value flag');
31
- exit();
32
- return;
33
- }
34
-
35
- try {
36
- await server.setSecret(name, providedValue);
37
- exit();
38
- } catch (err) {
39
- // Error was already printed by backend
40
- exit();
41
- }
42
- };
43
-
44
- createSecret();
45
- }, [name, providedValue, server, exit]);
46
-
47
- // This won't be shown because backend output is printed directly
48
- return null;
49
- };
@@ -1,41 +0,0 @@
1
- import React, { useEffect } from 'react';
2
- import { useApp } from 'ink';
3
- import type { PositronicDevServer } from '@positronic/spec';
4
-
5
- interface SecretDeleteProps {
6
- name: string;
7
- server?: PositronicDevServer;
8
- }
9
-
10
- export const SecretDelete = ({ name, server }: SecretDeleteProps) => {
11
- const { exit } = useApp();
12
-
13
- useEffect(() => {
14
- const deleteSecret = async () => {
15
- if (!server) {
16
- console.error('No project found. Please run this command from within a Positronic project directory.');
17
- exit();
18
- return;
19
- }
20
-
21
- if (!server.deleteSecret) {
22
- console.error('Secret management not supported for this backend');
23
- exit();
24
- return;
25
- }
26
-
27
- try {
28
- await server.deleteSecret(name);
29
- exit();
30
- } catch (err) {
31
- // Error was already printed by backend
32
- exit();
33
- }
34
- };
35
-
36
- deleteSecret();
37
- }, [name, server, exit]);
38
-
39
- // This won't be shown because backend output is printed directly
40
- return null;
41
- };
@@ -1,41 +0,0 @@
1
- import React, { useEffect } from 'react';
2
- import { useApp } from 'ink';
3
- import type { PositronicDevServer } from '@positronic/spec';
4
-
5
- interface SecretListProps {
6
- server?: PositronicDevServer;
7
- }
8
-
9
- export const SecretList = ({ server }: SecretListProps) => {
10
- const { exit } = useApp();
11
-
12
- useEffect(() => {
13
- const loadSecrets = async () => {
14
- if (!server) {
15
- console.error('No project found. Please run this command from within a Positronic project directory.');
16
- exit();
17
- return;
18
- }
19
-
20
- if (!server.listSecrets) {
21
- console.error('Secret management not supported for this backend');
22
- exit();
23
- return;
24
- }
25
-
26
- try {
27
- // listSecrets will print output directly via stdio: 'inherit'
28
- await server.listSecrets();
29
- exit();
30
- } catch (err) {
31
- // Error was already printed by backend
32
- exit();
33
- }
34
- };
35
-
36
- loadSecrets();
37
- }, [server, exit]);
38
-
39
- // This won't be shown because backend output is printed directly
40
- return null;
41
- };