espcli 0.0.1 → 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.
@@ -1,78 +0,0 @@
1
- export function getRootCMakeLists(projectName: string): string {
2
- return `cmake_minimum_required(VERSION 3.16)
3
- include($ENV{IDF_PATH}/tools/cmake/project.cmake)
4
- project(${projectName})
5
- `;
6
- }
7
-
8
- export function getMainCMakeLists(mainFile: string): string {
9
- return `idf_component_register(SRCS "${mainFile}"
10
- INCLUDE_DIRS ".")
11
- `;
12
- }
13
-
14
- export function getCMain(projectName: string): string {
15
- return `#include <stdio.h>
16
- #include "freertos/FreeRTOS.h"
17
- #include "freertos/task.h"
18
-
19
- void app_main(void)
20
- {
21
- while (1) {
22
- printf("Hello from ${projectName}!\\n");
23
- vTaskDelay(pdMS_TO_TICKS(1000));
24
- }
25
- }
26
- `;
27
- }
28
-
29
- export function getCppMain(projectName: string): string {
30
- return `#include <cstdio>
31
- #include "freertos/FreeRTOS.h"
32
- #include "freertos/task.h"
33
-
34
- extern "C" void app_main(void)
35
- {
36
- while (true) {
37
- printf("Hello from ${projectName}!\\n");
38
- vTaskDelay(pdMS_TO_TICKS(1000));
39
- }
40
- }
41
- `;
42
- }
43
-
44
- export function getGitignore(): string {
45
- return `build/
46
- sdkconfig
47
- sdkconfig.old
48
- .vscode/
49
- *.pyc
50
- __pycache__/
51
- .DS_Store
52
- `;
53
- }
54
-
55
- export function getReadme(projectName: string, target: string): string {
56
- return `# ${projectName}
57
-
58
- ESP-IDF project targeting ${target}.
59
-
60
- ## Build
61
-
62
- \`\`\`bash
63
- idf.py build
64
- \`\`\`
65
-
66
- ## Flash
67
-
68
- \`\`\`bash
69
- idf.py -p PORT flash
70
- \`\`\`
71
-
72
- ## Monitor
73
-
74
- \`\`\`bash
75
- idf.py -p PORT monitor
76
- \`\`\`
77
- `;
78
- }
@@ -1,52 +0,0 @@
1
- import type { InitConfig, InitResult, Result, TemplateFile } from '@/core/types';
2
- import { mkdir, writeFile } from 'fs/promises';
3
- import { join } from 'path';
4
- import {
5
- getRootCMakeLists,
6
- getMainCMakeLists,
7
- getCMain,
8
- getCppMain,
9
- getGitignore,
10
- getReadme,
11
- } from '@/core/templates/files';
12
-
13
- export function generateProjectFiles(config: InitConfig): TemplateFile[] {
14
- const { name, language, target } = config;
15
- const mainFile = language === 'cpp' ? 'main.cpp' : 'main.c';
16
- const mainContent = language === 'cpp' ? getCppMain(name) : getCMain(name);
17
-
18
- return [
19
- { path: 'CMakeLists.txt', content: getRootCMakeLists(name) },
20
- { path: 'main/CMakeLists.txt', content: getMainCMakeLists(mainFile) },
21
- { path: `main/${mainFile}`, content: mainContent },
22
- { path: '.gitignore', content: getGitignore() },
23
- { path: 'README.md', content: getReadme(name, target) },
24
- ];
25
- }
26
-
27
- export async function createProject(config: InitConfig): Promise<Result<InitResult>> {
28
- const projectPath = join(config.directory, config.name);
29
- const files = generateProjectFiles(config);
30
-
31
- try {
32
- await mkdir(join(projectPath, 'main'), { recursive: true });
33
-
34
- const createdFiles: string[] = [];
35
-
36
- for (const file of files) {
37
- const filePath = join(projectPath, file.path);
38
- await writeFile(filePath, file.content);
39
- createdFiles.push(file.path);
40
- }
41
-
42
- return {
43
- ok: true,
44
- data: {
45
- projectPath,
46
- files: createdFiles,
47
- },
48
- };
49
- } catch (err) {
50
- return { ok: false, error: `Failed to create project: ${err}` };
51
- }
52
- }
package/src/core/types.ts DELETED
@@ -1,128 +0,0 @@
1
- export type Result<T> =
2
- | { ok: true; data: T }
3
- | { ok: false; error: string; code?: string };
4
-
5
- export type ShellType = 'zsh' | 'bash' | 'fish' | 'unknown';
6
-
7
- export interface ShellInfo {
8
- type: ShellType;
9
- configPath: string;
10
- }
11
-
12
- export interface EspTarget {
13
- id: string;
14
- name: string;
15
- description: string;
16
- stable: boolean;
17
- }
18
-
19
- export type ConnectionType = 'native-usb' | 'uart-bridge' | 'unknown';
20
-
21
- export interface SerialDevice {
22
- port: string;
23
- vendorId?: string;
24
- productId?: string;
25
- manufacturer?: string;
26
- chip?: string;
27
- connectionType: ConnectionType;
28
- espChip?: string; // Actual ESP chip detected via esptool (e.g., "ESP32-S3")
29
- description?: string; // USB device description (e.g., "USB JTAG/serial debug unit")
30
- }
31
-
32
- export interface IdfStatus {
33
- installed: boolean;
34
- path?: string;
35
- version?: string;
36
- }
37
-
38
- export interface InstallConfig {
39
- path: string;
40
- target: string;
41
- addToShell: boolean;
42
- }
43
-
44
- export interface InstallResult {
45
- idfPath: string;
46
- version: string;
47
- addedToShell: boolean;
48
- }
49
-
50
- export interface InitConfig {
51
- name: string;
52
- directory: string;
53
- language: 'c' | 'cpp';
54
- target: string;
55
- }
56
-
57
- export interface InitResult {
58
- projectPath: string;
59
- files: string[];
60
- }
61
-
62
- export interface BuildConfig {
63
- projectDir: string;
64
- target?: string;
65
- clean?: boolean;
66
- }
67
-
68
- export interface FlashConfig {
69
- projectDir: string;
70
- port: string;
71
- baud?: number;
72
- }
73
-
74
- export interface MonitorConfig {
75
- port: string;
76
- baud?: number;
77
- projectDir?: string;
78
- }
79
-
80
- export interface CleanConfig {
81
- projectDir: string;
82
- full?: boolean;
83
- }
84
-
85
- export type EventType =
86
- | 'progress'
87
- | 'log'
88
- | 'stdout'
89
- | 'stderr'
90
- | 'complete'
91
- | 'error';
92
-
93
- export type LogLevel = 'info' | 'warn' | 'error';
94
-
95
- export type EventData =
96
- | { type: 'progress'; message: string; percent?: number }
97
- | { type: 'log'; level: LogLevel; message: string }
98
- | { type: 'stdout'; text: string }
99
- | { type: 'stderr'; text: string }
100
- | { type: 'complete'; result: unknown }
101
- | { type: 'error'; message: string; code?: string };
102
-
103
- export interface CoreEvent {
104
- type: EventType;
105
- timestamp: number;
106
- operationId: string;
107
- data: EventData;
108
- }
109
-
110
- export interface TemplateFile {
111
- path: string;
112
- content: string;
113
- }
114
-
115
- export interface ProjectTemplate {
116
- language: 'c' | 'cpp';
117
- files: TemplateFile[];
118
- }
119
-
120
- export type WsClientMessage =
121
- | { type: 'subscribe'; operationId: string }
122
- | { type: 'unsubscribe'; operationId: string }
123
- | { type: 'input'; operationId: string; data: string };
124
-
125
- export type WsServerMessage =
126
- | { type: 'event'; operationId: string; event: CoreEvent }
127
- | { type: 'subscribed'; operationId: string }
128
- | { type: 'error'; message: string };
package/src/index.ts DELETED
@@ -1,5 +0,0 @@
1
- #!/usr/bin/env bun
2
- import { createCli } from '@/tui';
3
-
4
- const cli = createCli();
5
- cli.parse();
package/src/serve.ts DELETED
@@ -1,8 +0,0 @@
1
- #!/usr/bin/env bun
2
- import { createServer } from '@/server';
3
-
4
- const port = parseInt(process.env.PORT || '3000', 10);
5
- const server = createServer(port);
6
-
7
- console.log(`ESP CLI server running on http://localhost:${server.port}`);
8
- console.log(`WebSocket available at ws://localhost:${server.port}/ws`);
@@ -1,105 +0,0 @@
1
- import type { ServerWebSocket } from 'bun';
2
- import { matchRoute } from '@/server/routes';
3
- import { emitter } from '@/core/emitter';
4
- import type { WsClientMessage, WsServerMessage, CoreEvent } from '@/core/types';
5
-
6
- interface WsData {
7
- subscriptions: Set<string>;
8
- }
9
-
10
- const clients = new Set<ServerWebSocket<WsData>>();
11
-
12
- function broadcast(operationId: string, event: CoreEvent): void {
13
- const message: WsServerMessage = { type: 'event', operationId, event };
14
- const payload = JSON.stringify(message);
15
-
16
- for (const ws of clients) {
17
- if (ws.data.subscriptions.has(operationId)) {
18
- ws.send(payload);
19
- }
20
- }
21
- }
22
-
23
- emitter.subscribeAll((event) => {
24
- broadcast(event.operationId, event);
25
- });
26
-
27
- function corsHeaders(): HeadersInit {
28
- return {
29
- 'Access-Control-Allow-Origin': '*',
30
- 'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
31
- 'Access-Control-Allow-Headers': 'Content-Type',
32
- };
33
- }
34
-
35
- function withCors(response: Response): Response {
36
- const headers = new Headers(response.headers);
37
- Object.entries(corsHeaders()).forEach(([key, value]) => {
38
- headers.set(key, value);
39
- });
40
- return new Response(response.body, {
41
- status: response.status,
42
- statusText: response.statusText,
43
- headers,
44
- });
45
- }
46
-
47
- export function createServer(port: number) {
48
- return Bun.serve<WsData>({
49
- port,
50
- async fetch(req, server) {
51
- const url = new URL(req.url);
52
-
53
- // Handle CORS preflight
54
- if (req.method === 'OPTIONS') {
55
- return new Response(null, { headers: corsHeaders() });
56
- }
57
-
58
- if (url.pathname === '/ws') {
59
- const upgraded = server.upgrade(req, {
60
- data: { subscriptions: new Set<string>() },
61
- });
62
- if (upgraded) return undefined;
63
- return new Response('WebSocket upgrade failed', { status: 400 });
64
- }
65
-
66
- const handler = matchRoute(req.method, url.pathname);
67
-
68
- if (handler) {
69
- const response = await handler(req);
70
- return withCors(response);
71
- }
72
-
73
- return withCors(new Response('Not Found', { status: 404 }));
74
- },
75
- websocket: {
76
- open(ws) {
77
- clients.add(ws);
78
- },
79
- close(ws) {
80
- clients.delete(ws);
81
- },
82
- message(ws, message) {
83
- try {
84
- const data = JSON.parse(message.toString()) as WsClientMessage;
85
-
86
- switch (data.type) {
87
- case 'subscribe':
88
- ws.data.subscriptions.add(data.operationId);
89
- ws.send(JSON.stringify({ type: 'subscribed', operationId: data.operationId }));
90
- break;
91
-
92
- case 'unsubscribe':
93
- ws.data.subscriptions.delete(data.operationId);
94
- break;
95
-
96
- case 'input':
97
- break;
98
- }
99
- } catch {
100
- ws.send(JSON.stringify({ type: 'error', message: 'Invalid message format' }));
101
- }
102
- },
103
- },
104
- });
105
- }
@@ -1,175 +0,0 @@
1
- import { VERSION, ESP_TARGETS } from '@/core/constants';
2
- import { getIdfStatus } from '@/core/services/idf';
3
- import { listDevices } from '@/core/operations/devices';
4
- import { install } from '@/core/operations/install';
5
- import { init } from '@/core/operations/init';
6
- import { build } from '@/core/operations/build';
7
- import { flash } from '@/core/operations/flash';
8
- import { startMonitor, stopMonitor } from '@/core/operations/monitor';
9
- import { clean } from '@/core/operations/clean';
10
- import { createOperationId } from '@/core/emitter';
11
- import type {
12
- HealthResponse,
13
- TargetsResponse,
14
- DevicesResponse,
15
- InstallRequest,
16
- InitRequest,
17
- BuildRequest,
18
- FlashRequest,
19
- MonitorRequest,
20
- MonitorStopRequest,
21
- CleanRequest,
22
- } from '@/server/schema';
23
-
24
- type RouteHandler = (req: Request) => Promise<Response>;
25
-
26
- function json<T>(data: T, status = 200): Response {
27
- return new Response(JSON.stringify(data), {
28
- status,
29
- headers: { 'Content-Type': 'application/json' },
30
- });
31
- }
32
-
33
- function error(message: string, status = 400): Response {
34
- return json({ error: message }, status);
35
- }
36
-
37
- async function parseBody<T>(req: Request): Promise<T | null> {
38
- try {
39
- return (await req.json()) as T;
40
- } catch {
41
- return null;
42
- }
43
- }
44
-
45
- export const routes: Record<string, RouteHandler> = {
46
- 'GET /api/health': async () => {
47
- const response: HealthResponse = { status: 'ok', version: VERSION };
48
- return json(response);
49
- },
50
-
51
- 'GET /api/targets': async () => {
52
- const response: TargetsResponse = { targets: ESP_TARGETS };
53
- return json(response);
54
- },
55
-
56
- 'GET /api/devices': async () => {
57
- const result = await listDevices();
58
- if (!result.ok) {
59
- return error(result.error, 500);
60
- }
61
- const response: DevicesResponse = { devices: result.data };
62
- return json(response);
63
- },
64
-
65
- 'GET /api/idf/status': async () => {
66
- const status = await getIdfStatus();
67
- return json(status);
68
- },
69
-
70
- 'POST /api/install': async (req) => {
71
- const body = await parseBody<InstallRequest>(req);
72
- const operationId = createOperationId();
73
-
74
- install(
75
- {
76
- path: body?.path || '',
77
- target: body?.target || 'all',
78
- addToShell: body?.addToShell ?? true,
79
- },
80
- operationId
81
- );
82
-
83
- return json({ operationId });
84
- },
85
-
86
- 'POST /api/init': async (req) => {
87
- const body = await parseBody<InitRequest>(req);
88
-
89
- if (!body?.name || !body.directory || !body.language || !body.target) {
90
- return error('Missing required fields: name, directory, language, target');
91
- }
92
-
93
- const result = await init(body);
94
-
95
- if (!result.ok) {
96
- return json({ ok: false, error: result.error });
97
- }
98
-
99
- return json({ ok: true, projectPath: result.data.projectPath });
100
- },
101
-
102
- 'POST /api/build': async (req) => {
103
- const body = await parseBody<BuildRequest>(req);
104
-
105
- if (!body?.projectDir) {
106
- return error('Missing required field: projectDir');
107
- }
108
-
109
- const operationId = createOperationId();
110
-
111
- build(body, operationId);
112
-
113
- return json({ operationId });
114
- },
115
-
116
- 'POST /api/flash': async (req) => {
117
- const body = await parseBody<FlashRequest>(req);
118
-
119
- if (!body?.projectDir || !body.port) {
120
- return error('Missing required fields: projectDir, port');
121
- }
122
-
123
- const operationId = createOperationId();
124
-
125
- flash(body, operationId);
126
-
127
- return json({ operationId });
128
- },
129
-
130
- 'POST /api/monitor': async (req) => {
131
- const body = await parseBody<MonitorRequest>(req);
132
-
133
- if (!body?.port) {
134
- return error('Missing required field: port');
135
- }
136
-
137
- const operationId = createOperationId();
138
- const result = startMonitor(body, operationId);
139
-
140
- if (!result.ok) {
141
- return error(result.error, 500);
142
- }
143
-
144
- return json({ operationId });
145
- },
146
-
147
- 'POST /api/monitor/stop': async (req) => {
148
- const body = await parseBody<MonitorStopRequest>(req);
149
-
150
- if (!body?.operationId) {
151
- return error('Missing required field: operationId');
152
- }
153
-
154
- const stopped = stopMonitor(body.operationId);
155
-
156
- return json({ ok: stopped });
157
- },
158
-
159
- 'POST /api/clean': async (req) => {
160
- const body = await parseBody<CleanRequest>(req);
161
-
162
- if (!body?.projectDir) {
163
- return error('Missing required field: projectDir');
164
- }
165
-
166
- const result = await clean(body);
167
-
168
- return json({ ok: result.ok, error: result.ok ? undefined : result.error });
169
- },
170
- };
171
-
172
- export function matchRoute(method: string, path: string): RouteHandler | null {
173
- const key = `${method} ${path}`;
174
- return routes[key] || null;
175
- }
@@ -1,81 +0,0 @@
1
- import type { EspTarget, SerialDevice, IdfStatus, CoreEvent } from '@/core/types';
2
-
3
- export interface HealthResponse {
4
- status: 'ok';
5
- version: string;
6
- }
7
-
8
- export interface TargetsResponse {
9
- targets: EspTarget[];
10
- }
11
-
12
- export interface DevicesResponse {
13
- devices: SerialDevice[];
14
- }
15
-
16
- export interface IdfStatusResponse extends IdfStatus {}
17
-
18
- export interface InstallRequest {
19
- path?: string;
20
- target?: string;
21
- addToShell?: boolean;
22
- }
23
-
24
- export interface OperationResponse {
25
- operationId: string;
26
- }
27
-
28
- export interface InitRequest {
29
- name: string;
30
- directory: string;
31
- language: 'c' | 'cpp';
32
- target: string;
33
- }
34
-
35
- export interface InitResponse {
36
- ok: boolean;
37
- projectPath?: string;
38
- error?: string;
39
- }
40
-
41
- export interface BuildRequest {
42
- projectDir: string;
43
- target?: string;
44
- clean?: boolean;
45
- }
46
-
47
- export interface FlashRequest {
48
- projectDir: string;
49
- port: string;
50
- baud?: number;
51
- }
52
-
53
- export interface MonitorRequest {
54
- port: string;
55
- baud?: number;
56
- projectDir?: string;
57
- }
58
-
59
- export interface MonitorStopRequest {
60
- operationId: string;
61
- }
62
-
63
- export interface CleanRequest {
64
- projectDir: string;
65
- full?: boolean;
66
- }
67
-
68
- export interface CleanResponse {
69
- ok: boolean;
70
- error?: string;
71
- }
72
-
73
- export type WsClientMessage =
74
- | { type: 'subscribe'; operationId: string }
75
- | { type: 'unsubscribe'; operationId: string }
76
- | { type: 'input'; operationId: string; data: string };
77
-
78
- export type WsServerMessage =
79
- | { type: 'event'; operationId: string; event: CoreEvent }
80
- | { type: 'subscribed'; operationId: string }
81
- | { type: 'error'; message: string };
@@ -1,48 +0,0 @@
1
- import { build } from '@/core/operations/build';
2
- import { findProjectRoot } from '@/core/services/idf';
3
- import { emitter, createOperationId } from '@/core/emitter';
4
- import { logger } from '@/tui/logger';
5
-
6
- interface BuildOptions {
7
- target?: string;
8
- clean?: boolean;
9
- }
10
-
11
- export async function buildCommand(options: BuildOptions): Promise<void> {
12
- const projectDir = await findProjectRoot(process.cwd());
13
-
14
- if (!projectDir) {
15
- logger.error('Not in an ESP-IDF project directory');
16
- process.exit(1);
17
- }
18
-
19
- const opId = createOperationId();
20
-
21
- emitter.subscribe(opId, (event) => {
22
- if (event.data.type === 'stdout') {
23
- logger.output(event.data.text);
24
- } else if (event.data.type === 'stderr') {
25
- logger.output(event.data.text);
26
- }
27
- });
28
-
29
- logger.step('Building project...');
30
-
31
- const result = await build(
32
- {
33
- projectDir,
34
- target: options.target,
35
- clean: options.clean,
36
- },
37
- opId
38
- );
39
-
40
- if (!result.ok) {
41
- logger.newline();
42
- logger.error(result.error);
43
- process.exit(1);
44
- }
45
-
46
- logger.newline();
47
- logger.success('Build complete');
48
- }