@pulse-editor/cli 0.1.1-beta.9 → 0.1.3
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/app.js +4 -1
- package/dist/components/commands/build.js +18 -33
- package/dist/components/commands/code.d.ts +5 -0
- package/dist/components/commands/code.js +342 -0
- package/dist/components/commands/create.js +38 -12
- package/dist/components/commands/dev.js +35 -8
- package/dist/components/commands/login.d.ts +2 -2
- package/dist/components/commands/login.js +110 -26
- package/dist/components/commands/preview.js +50 -11
- package/dist/components/commands/publish.js +23 -37
- package/dist/components/commands/{start copy.d.ts → skill.d.ts} +1 -1
- package/dist/components/commands/skill.js +230 -0
- package/dist/components/commands/{preview copy.d.ts → upgrade.d.ts} +1 -1
- package/dist/components/commands/upgrade.js +53 -0
- package/dist/lib/backend/publish-app.d.ts +1 -0
- package/dist/lib/backend/publish-app.js +26 -0
- package/dist/lib/backend-url.d.ts +1 -0
- package/dist/lib/backend-url.js +3 -0
- package/dist/lib/cli-flags.d.ts +22 -0
- package/dist/lib/cli-flags.js +22 -0
- package/dist/lib/manual.js +40 -0
- package/dist/lib/server/express.js +81 -41
- package/dist/lib/server/preview/backend/load-remote.cjs +28 -18
- package/dist/lib/server/utils.js +3 -3
- package/dist/lib/token.js +2 -3
- package/dist/lib/webpack/compile.d.ts +2 -0
- package/dist/lib/webpack/compile.js +30 -0
- package/dist/lib/webpack/configs/mf-client.d.ts +3 -0
- package/dist/lib/webpack/configs/mf-client.js +184 -0
- package/dist/lib/webpack/configs/mf-server.d.ts +2 -0
- package/dist/lib/webpack/configs/mf-server.js +463 -0
- package/dist/lib/webpack/configs/preview.d.ts +3 -0
- package/dist/lib/webpack/configs/preview.js +117 -0
- package/dist/lib/webpack/configs/utils.d.ts +10 -0
- package/dist/lib/webpack/configs/utils.js +172 -0
- package/dist/lib/webpack/dist/pregistered-actions.d.ts +2 -0
- package/dist/lib/webpack/dist/pulse.config.d.ts +7 -0
- package/dist/lib/webpack/dist/src/lib/agents/code-modifier-agent.d.ts +2 -0
- package/dist/lib/webpack/dist/src/lib/agents/vibe-coding-agent.d.ts +3 -0
- package/dist/lib/webpack/dist/src/lib/mcp/utils.d.ts +3 -0
- package/dist/lib/webpack/dist/src/lib/streaming/message-stream-controller.d.ts +10 -0
- package/dist/lib/webpack/dist/src/lib/types.d.ts +58 -0
- package/dist/lib/webpack/dist/src/server-function/generate-code/v1/generate.d.ts +5 -0
- package/dist/lib/webpack/dist/src/server-function/generate-code/v2/generate.d.ts +1 -0
- package/dist/lib/webpack/tsconfig.server.json +19 -0
- package/dist/lib/webpack/webpack-config.d.ts +1 -0
- package/dist/lib/webpack/webpack-config.js +24 -0
- package/dist/lib/webpack/webpack.config.d.ts +2 -0
- package/dist/lib/webpack/webpack.config.js +527 -0
- package/package.json +31 -20
- package/readme.md +7 -1
- package/dist/components/commands/preview copy.js +0 -14
- package/dist/components/commands/start copy.js +0 -14
- package/dist/lib/deps.d.ts +0 -1
- package/dist/lib/deps.js +0 -5
- package/dist/lib/node_module_bin.d.ts +0 -1
- package/dist/lib/node_module_bin.js +0 -7
- package/dist/lib/server/preview/backend/index.d.ts +0 -1
- package/dist/lib/server/preview/backend/index.js +0 -23
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { Result } from "meow";
|
|
2
|
+
import { Flags } from "../../lib/cli-flags.js";
|
|
3
3
|
export default function Login({ cli }: {
|
|
4
4
|
cli: Result<Flags>;
|
|
5
5
|
}): import("react/jsx-runtime").JSX.Element;
|
|
@@ -1,30 +1,58 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import Spinner from
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import
|
|
2
|
+
import { exec } from "child_process";
|
|
3
|
+
import http from "http";
|
|
4
|
+
import { Box, Text, useApp, useInput } from "ink";
|
|
5
|
+
import SelectInput from "ink-select-input";
|
|
6
|
+
import Spinner from "ink-spinner";
|
|
7
|
+
import TextInput from "ink-text-input";
|
|
8
|
+
import os from "os";
|
|
9
|
+
import path from "path";
|
|
10
|
+
import { useEffect, useState } from "react";
|
|
11
|
+
import { getBackendUrl } from "../../lib/backend-url.js";
|
|
12
|
+
import { checkToken, getToken, isTokenInEnv, saveToken, } from "../../lib/token.js";
|
|
13
|
+
function openBrowser(url) {
|
|
14
|
+
const platform = process.platform;
|
|
15
|
+
let cmd;
|
|
16
|
+
if (platform === "win32") {
|
|
17
|
+
cmd = `start "" "${url}"`;
|
|
18
|
+
}
|
|
19
|
+
else if (platform === "darwin") {
|
|
20
|
+
cmd = `open "${url}"`;
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
cmd = `xdg-open "${url}"`;
|
|
24
|
+
}
|
|
25
|
+
exec(cmd);
|
|
26
|
+
}
|
|
27
|
+
// Render a terminal hyperlink using ANSI OSC 8 escape sequences
|
|
28
|
+
function TerminalLink({ url, label }) {
|
|
29
|
+
const text = label ?? url;
|
|
30
|
+
// OSC 8 hyperlink: ESC]8;;URL BEL text ESC]8;; BEL
|
|
31
|
+
const hyperlink = `\u001B]8;;${url}\u0007${text}\u001B]8;;\u0007`;
|
|
32
|
+
return _jsx(Text, { children: hyperlink });
|
|
33
|
+
}
|
|
10
34
|
export default function Login({ cli }) {
|
|
11
35
|
const [loginMethod, setLoginMethod] = useState(undefined);
|
|
12
36
|
const [isShowLoginMethod, setIsShowLoginMethod] = useState(false);
|
|
13
37
|
const [isMethodSelected, setIsMethodSelected] = useState(false);
|
|
14
38
|
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
|
|
15
39
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
16
|
-
const [token, setToken] = useState(
|
|
40
|
+
const [token, setToken] = useState("");
|
|
17
41
|
const [isTokenSaved, setIsTokenSaved] = useState(false);
|
|
18
|
-
const [tokenInput, setTokenInput] = useState(
|
|
19
|
-
const [saveTokenInput, setSaveTokenInput] = useState(
|
|
42
|
+
const [tokenInput, setTokenInput] = useState("");
|
|
43
|
+
const [saveTokenInput, setSaveTokenInput] = useState("");
|
|
44
|
+
// Browser flow state
|
|
45
|
+
const [flowState, setFlowState] = useState("idle");
|
|
46
|
+
const [authUrl, setAuthUrl] = useState("");
|
|
47
|
+
const [flowError, setFlowError] = useState("");
|
|
20
48
|
const loginMethodItems = [
|
|
21
49
|
{
|
|
22
|
-
label:
|
|
23
|
-
value:
|
|
50
|
+
label: "Login in browser",
|
|
51
|
+
value: "flow",
|
|
24
52
|
},
|
|
25
53
|
{
|
|
26
|
-
label:
|
|
27
|
-
value:
|
|
54
|
+
label: "Login using access token",
|
|
55
|
+
value: "token",
|
|
28
56
|
},
|
|
29
57
|
];
|
|
30
58
|
const { exit } = useApp();
|
|
@@ -33,22 +61,22 @@ export default function Login({ cli }) {
|
|
|
33
61
|
const savedToken = getToken(cli.flags.stage);
|
|
34
62
|
setIsShowLoginMethod(!savedToken && !cli.flags.token && !cli.flags.flow);
|
|
35
63
|
if (savedToken) {
|
|
36
|
-
setLoginMethod(
|
|
64
|
+
setLoginMethod("token");
|
|
37
65
|
setToken(savedToken);
|
|
38
66
|
return;
|
|
39
67
|
}
|
|
40
68
|
else if (cli.flags.token) {
|
|
41
|
-
setLoginMethod(
|
|
69
|
+
setLoginMethod("token");
|
|
42
70
|
}
|
|
43
71
|
else if (cli.flags.flow) {
|
|
44
|
-
setLoginMethod(
|
|
72
|
+
setLoginMethod("flow");
|
|
45
73
|
}
|
|
46
74
|
}, [cli]);
|
|
47
75
|
// Check token validity
|
|
48
76
|
useEffect(() => {
|
|
49
77
|
// Only check token validity when it is set
|
|
50
|
-
if (loginMethod ===
|
|
51
|
-
checkToken(token, cli.flags.stage).then(isValid => {
|
|
78
|
+
if (loginMethod === "token" && token.length > 0) {
|
|
79
|
+
checkToken(token, cli.flags.stage).then((isValid) => {
|
|
52
80
|
setIsAuthenticated(isValid);
|
|
53
81
|
setIsCheckingAuth(false);
|
|
54
82
|
});
|
|
@@ -59,21 +87,77 @@ export default function Login({ cli }) {
|
|
|
59
87
|
setIsMethodSelected(loginMethod !== undefined);
|
|
60
88
|
}, 0);
|
|
61
89
|
}, [loginMethod]);
|
|
62
|
-
|
|
90
|
+
// Browser flow: start local server and open browser
|
|
91
|
+
useEffect(() => {
|
|
92
|
+
if (loginMethod !== "flow" || !isMethodSelected)
|
|
93
|
+
return;
|
|
94
|
+
const baseUrl = getBackendUrl(cli.flags.stage);
|
|
95
|
+
const server = http.createServer((req, res) => {
|
|
96
|
+
const reqUrl = new URL(req.url ?? "/", "http://localhost");
|
|
97
|
+
const receivedToken = reqUrl.searchParams.get("token");
|
|
98
|
+
if (receivedToken) {
|
|
99
|
+
res.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
100
|
+
res.end('<html><body style="font-family:sans-serif;text-align:center;padding:60px">' +
|
|
101
|
+
"<h2>Login successful!</h2><p>You can close this tab and return to the terminal.</p>" +
|
|
102
|
+
"</body></html>");
|
|
103
|
+
server.close();
|
|
104
|
+
saveToken(receivedToken, cli.flags.stage);
|
|
105
|
+
setToken(receivedToken);
|
|
106
|
+
setFlowState("success");
|
|
107
|
+
setTimeout(() => {
|
|
108
|
+
exit();
|
|
109
|
+
}, 1500);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
res.writeHead(400, { "Content-Type": "text/plain" });
|
|
113
|
+
res.end("Missing token parameter.");
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
server.listen(0, "127.0.0.1", () => {
|
|
117
|
+
const port = server.address().port;
|
|
118
|
+
const callbackUrl = `http://127.0.0.1:${port}`;
|
|
119
|
+
const url = `${baseUrl}/api/auth/cli?redirect=${encodeURIComponent(callbackUrl)}`;
|
|
120
|
+
setAuthUrl(url);
|
|
121
|
+
setFlowState("idle");
|
|
122
|
+
});
|
|
123
|
+
server.on("error", () => {
|
|
124
|
+
setFlowError("Failed to start local server for authentication.");
|
|
125
|
+
setFlowState("error");
|
|
126
|
+
});
|
|
127
|
+
return () => {
|
|
128
|
+
server.close();
|
|
129
|
+
};
|
|
130
|
+
}, [loginMethod, isMethodSelected]);
|
|
131
|
+
// Handle Enter key to open browser (when in flow mode and idle)
|
|
132
|
+
useInput((_, key) => {
|
|
133
|
+
if (key.return &&
|
|
134
|
+
loginMethod === "flow" &&
|
|
135
|
+
flowState === "idle" &&
|
|
136
|
+
authUrl) {
|
|
137
|
+
setFlowState("opening");
|
|
138
|
+
openBrowser(authUrl);
|
|
139
|
+
setTimeout(() => {
|
|
140
|
+
setFlowState("waiting");
|
|
141
|
+
}, 500);
|
|
142
|
+
}
|
|
143
|
+
}, {
|
|
144
|
+
isActive: loginMethod === "flow" && flowState === "idle" && Boolean(authUrl),
|
|
145
|
+
});
|
|
146
|
+
return (_jsxs(_Fragment, { children: [isShowLoginMethod && (_jsxs(_Fragment, { children: [_jsx(Text, { children: "Login to the Pulse Editor Platform" }), _jsx(SelectInput, { items: loginMethodItems, onSelect: (item) => {
|
|
63
147
|
setLoginMethod(item.value);
|
|
64
148
|
}, isFocused: loginMethod === undefined }), _jsx(Text, { children: " " })] })), isMethodSelected &&
|
|
65
|
-
loginMethod ===
|
|
66
|
-
(token.length === 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: "Enter your Pulse Editor access token:" }), _jsx(TextInput, { mask: "*", value: tokenInput, onChange: setTokenInput, onSubmit: value => {
|
|
149
|
+
loginMethod === "token" &&
|
|
150
|
+
(token.length === 0 ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: "Enter your Pulse Editor access token:" }), _jsx(TextInput, { mask: "*", value: tokenInput, onChange: setTokenInput, onSubmit: (value) => {
|
|
67
151
|
if (value.length === 0) {
|
|
68
152
|
return;
|
|
69
153
|
}
|
|
70
154
|
setToken(value);
|
|
71
155
|
} })] })) : isCheckingAuth ? (_jsxs(Box, { children: [_jsx(Spinner, { type: "dots" }), _jsx(Text, { children: " Checking authentication..." })] })) : isAuthenticated ? (_jsxs(_Fragment, { children: [_jsx(Text, { children: "\u2705 You are signed in successfully." }), !isTokenInEnv(cli.flags.stage) &&
|
|
72
|
-
getToken(cli.flags.stage) !== token && (_jsxs(_Fragment, { children: [_jsx(Text, { children: "\uD83D\uDFE2 It is recommended to save your access token as an environment variable PE_ACCESS_TOKEN." }), _jsxs(Box, { children: [_jsxs(Text, { children: ["\u26A0\uFE0F (NOT recommended) Or, do you want to save access token to user home directory? (y/n)",
|
|
156
|
+
getToken(cli.flags.stage) !== token && (_jsxs(_Fragment, { children: [_jsx(Text, { children: "\uD83D\uDFE2 It is recommended to save your access token as an environment variable PE_ACCESS_TOKEN." }), _jsxs(Box, { children: [_jsxs(Text, { children: ["\u26A0\uFE0F (NOT recommended) Or, do you want to save access token to user home directory? (y/n)", " "] }), _jsx(TextInput, { value: saveTokenInput, onChange: setSaveTokenInput, onSubmit: (value) => {
|
|
73
157
|
if (value.length === 0) {
|
|
74
158
|
return;
|
|
75
159
|
}
|
|
76
|
-
if (value ===
|
|
160
|
+
if (value === "y") {
|
|
77
161
|
saveToken(token, cli.flags.stage);
|
|
78
162
|
setIsTokenSaved(true);
|
|
79
163
|
setTimeout(() => {
|
|
@@ -83,5 +167,5 @@ export default function Login({ cli }) {
|
|
|
83
167
|
else {
|
|
84
168
|
exit();
|
|
85
169
|
}
|
|
86
|
-
} })] })] })), isTokenSaved && (_jsxs(Text, { children: ["Token saved to ", path.join(os.homedir(),
|
|
170
|
+
} })] })] })), isTokenSaved && (_jsxs(Text, { children: ["Token saved to ", path.join(os.homedir(), ".pulse-editor")] }))] })) : (_jsx(Text, { children: "Authentication error: please enter valid credentials." }))), isMethodSelected && loginMethod === "flow" && (_jsx(_Fragment, { children: flowState === "error" ? (_jsxs(Text, { color: "red", children: ["Error: ", flowError] })) : flowState === "success" ? (_jsx(Text, { children: "\u2705 Login successful! Saving credentials..." })) : flowState === "waiting" || flowState === "opening" ? (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsxs(Box, { children: [_jsx(Spinner, { type: "dots" }), _jsx(Text, { children: " Waiting for browser authentication..." })] }), _jsx(Text, { dimColor: true, children: "If the browser did not open, visit: " }), authUrl && _jsx(TerminalLink, { url: authUrl })] })) : authUrl ? (_jsxs(Box, { flexDirection: "column", gap: 1, children: [_jsxs(Text, { children: ["Press ", _jsx(Text, { bold: true, children: "Enter" }), " to open your browser and login:"] }), _jsx(TerminalLink, { url: authUrl, label: "Open browser to login \u2192" })] })) : (_jsxs(Box, { children: [_jsx(Spinner, { type: "dots" }), _jsx(Text, { children: " Preparing login..." })] })) }))] }));
|
|
87
171
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { Text } from 'ink';
|
|
1
|
+
import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
|
|
3
2
|
import { useEffect } from 'react';
|
|
4
3
|
import { execa } from 'execa';
|
|
5
4
|
import fs from 'fs';
|
|
6
5
|
import { getDepsBinPath } from '../../lib/execa-utils/deps.js';
|
|
7
6
|
import { cleanDist } from '../../lib/execa-utils/clean.js';
|
|
7
|
+
import { webpackCompile } from '../../lib/webpack/compile.js';
|
|
8
8
|
export default function Preview({ cli }) {
|
|
9
9
|
useEffect(() => {
|
|
10
10
|
async function startPreviewServer() {
|
|
@@ -22,9 +22,19 @@ export default function Preview({ cli }) {
|
|
|
22
22
|
});
|
|
23
23
|
}
|
|
24
24
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
// Create node_modules/.pulse/server directory
|
|
26
|
+
if (!fs.existsSync('node_modules/.pulse/server')) {
|
|
27
|
+
if (process.platform === 'win32') {
|
|
28
|
+
await execa('mkdir node_modules\\.pulse\\server', {
|
|
29
|
+
shell: true,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
else {
|
|
33
|
+
await execa('mkdir -p node_modules/.pulse/server', {
|
|
34
|
+
shell: true,
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
}
|
|
28
38
|
if (process.platform === 'win32') {
|
|
29
39
|
await execa('xcopy /E /I node_modules\\@pulse-editor\\cli\\dist\\lib\\server node_modules\\.pulse\\server', {
|
|
30
40
|
shell: true,
|
|
@@ -37,11 +47,28 @@ export default function Preview({ cli }) {
|
|
|
37
47
|
}
|
|
38
48
|
// Start preview server
|
|
39
49
|
await cleanDist();
|
|
40
|
-
await execa(
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
// await execa(
|
|
51
|
+
// getDepsBinPath('concurrently'),
|
|
52
|
+
// [
|
|
53
|
+
// '--prefix',
|
|
54
|
+
// 'none',
|
|
55
|
+
// '"npx webpack --mode development --watch"',
|
|
56
|
+
// '"tsx watch --clear-screen=false node_modules/@pulse-editor/cli/dist/lib/server/express.js"',
|
|
57
|
+
// ],
|
|
58
|
+
// {
|
|
59
|
+
// stdio: 'inherit',
|
|
60
|
+
// shell: true,
|
|
61
|
+
// env: {
|
|
62
|
+
// NODE_OPTIONS: '--import=tsx',
|
|
63
|
+
// PREVIEW: 'true',
|
|
64
|
+
// },
|
|
65
|
+
// },
|
|
66
|
+
// );
|
|
67
|
+
const compiler = await webpackCompile('preview', undefined, true);
|
|
68
|
+
await execa(getDepsBinPath('tsx'), [
|
|
69
|
+
'watch',
|
|
70
|
+
'--clear-screen=false',
|
|
71
|
+
'node_modules/@pulse-editor/cli/dist/lib/server/express.js',
|
|
45
72
|
], {
|
|
46
73
|
stdio: 'inherit',
|
|
47
74
|
shell: true,
|
|
@@ -50,8 +77,20 @@ export default function Preview({ cli }) {
|
|
|
50
77
|
PREVIEW: 'true',
|
|
51
78
|
},
|
|
52
79
|
});
|
|
80
|
+
// Handle process exit to close webpack compiler
|
|
81
|
+
process.on('SIGINT', () => {
|
|
82
|
+
if (compiler && typeof compiler.close === 'function') {
|
|
83
|
+
compiler.close(() => {
|
|
84
|
+
process.exit();
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
else {
|
|
88
|
+
process.exit();
|
|
89
|
+
}
|
|
90
|
+
});
|
|
53
91
|
}
|
|
92
|
+
console.log('🚀 Starting preview server...');
|
|
54
93
|
startPreviewServer();
|
|
55
94
|
}, []);
|
|
56
|
-
return
|
|
95
|
+
return _jsx(_Fragment, {});
|
|
57
96
|
}
|
|
@@ -5,12 +5,15 @@ import { checkToken, getToken } from '../../lib/token.js';
|
|
|
5
5
|
import Spinner from 'ink-spinner';
|
|
6
6
|
import fs from 'fs';
|
|
7
7
|
import { $ } from 'execa';
|
|
8
|
+
import { publishApp } from '../../lib/backend/publish-app.js';
|
|
8
9
|
export default function Publish({ cli }) {
|
|
9
10
|
const [isInProjectDir, setIsInProjectDir] = useState(false);
|
|
10
11
|
const [isCheckingAuth, setIsCheckingAuth] = useState(true);
|
|
11
12
|
const [isAuthenticated, setIsAuthenticated] = useState(false);
|
|
12
13
|
const [isBuilding, setIsBuilding] = useState(false);
|
|
13
14
|
const [isBuildingError, setIsBuildingError] = useState(false);
|
|
15
|
+
const [isZipping, setIsZipping] = useState(false);
|
|
16
|
+
const [isZippingError, setIsZippingError] = useState(false);
|
|
14
17
|
const [isPublishing, setIsPublishing] = useState(false);
|
|
15
18
|
const [isPublishingError, setIsPublishingError] = useState(false);
|
|
16
19
|
const [isPublished, setIsPublished] = useState(false);
|
|
@@ -45,56 +48,39 @@ export default function Publish({ cli }) {
|
|
|
45
48
|
// Build the extension
|
|
46
49
|
useEffect(() => {
|
|
47
50
|
async function buildExtension() {
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
51
|
+
if (cli.flags.build) {
|
|
52
|
+
setIsBuilding(true);
|
|
53
|
+
try {
|
|
54
|
+
await $ `npm run build`;
|
|
55
|
+
}
|
|
56
|
+
catch (error) {
|
|
57
|
+
setIsBuildingError(true);
|
|
58
|
+
setFailureMessage('Build failed. Please run `npm run build` to see the error.');
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
setIsBuilding(false);
|
|
63
|
+
}
|
|
57
64
|
}
|
|
65
|
+
setIsZipping(true);
|
|
58
66
|
// Zip the dist folder
|
|
59
67
|
try {
|
|
60
|
-
await $({ cwd: 'dist' }) `zip -r ../node_modules/@pulse-editor/dist.zip
|
|
68
|
+
await $({ cwd: 'dist' }) `zip -r ../node_modules/@pulse-editor/dist.zip .`;
|
|
61
69
|
}
|
|
62
70
|
catch (error) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
setFailureMessage('Failed to zip the build output.');
|
|
71
|
+
setIsZippingError(true);
|
|
72
|
+
setFailureMessage('Failed to zip the build output. ' + error.message);
|
|
66
73
|
return;
|
|
67
74
|
}
|
|
68
75
|
finally {
|
|
69
|
-
|
|
76
|
+
setIsZipping(false);
|
|
70
77
|
}
|
|
71
78
|
await publishExtension();
|
|
72
79
|
}
|
|
73
80
|
async function publishExtension() {
|
|
74
81
|
setIsPublishing(true);
|
|
75
|
-
// Read pulse.config.json for visibility
|
|
76
|
-
const config = JSON.parse(fs.readFileSync('./dist/client/pulse.config.json', 'utf-8'));
|
|
77
|
-
const visibility = config.visibility;
|
|
78
|
-
// Upload the zip file to the server
|
|
79
82
|
try {
|
|
80
|
-
const
|
|
81
|
-
const buffer = fs.readFileSync('./node_modules/@pulse-editor/dist.zip');
|
|
82
|
-
// @ts-ignore Create a Blob from the buffer
|
|
83
|
-
const blob = new Blob([buffer], {
|
|
84
|
-
type: 'application/zip',
|
|
85
|
-
});
|
|
86
|
-
formData.append('file', blob, 'dist.zip');
|
|
87
|
-
formData.append('visibility', visibility);
|
|
88
|
-
// Send the file to the server
|
|
89
|
-
const res = await fetch(cli.flags.stage
|
|
90
|
-
? 'https://localhost:8080/api/app/publish'
|
|
91
|
-
: 'https://pulse-editor.com/api/app/publish', {
|
|
92
|
-
method: 'POST',
|
|
93
|
-
headers: {
|
|
94
|
-
Authorization: `Bearer ${getToken(cli.flags.stage)}`,
|
|
95
|
-
},
|
|
96
|
-
body: formData,
|
|
97
|
-
});
|
|
83
|
+
const res = await publishApp(cli.flags.stage);
|
|
98
84
|
if (res.status === 200) {
|
|
99
85
|
setIsPublished(true);
|
|
100
86
|
}
|
|
@@ -122,5 +108,5 @@ export default function Publish({ cli }) {
|
|
|
122
108
|
buildExtension();
|
|
123
109
|
}
|
|
124
110
|
}, [isAuthenticated]);
|
|
125
|
-
return (_jsx(_Fragment, { children: !isInProjectDir ? (_jsx(Text, { color: 'redBright', children: "\u26D4 The current directory does not contain a Pulse Editor project." })) : isCheckingAuth ? (_jsxs(Box, { children: [_jsx(Spinner, { type: "dots" }), _jsx(Text, { children: " Checking authentication..." })] })) : isAuthenticated ? (_jsxs(_Fragment, { children: [isBuilding && (_jsxs(Box, { children: [_jsx(Spinner, { type: "dots" }), _jsx(Text, { children: " Building..." })] })), isBuildingError && (_jsx(Text, { color: 'redBright', children: "\u274C Error building the extension. Please run `npm run build` to see the error." })),
|
|
111
|
+
return (_jsx(_Fragment, { children: !isInProjectDir ? (_jsx(Text, { color: 'redBright', children: "\u26D4 The current directory does not contain a Pulse Editor project." })) : isCheckingAuth ? (_jsxs(Box, { children: [_jsx(Spinner, { type: "dots" }), _jsx(Text, { children: " Checking authentication..." })] })) : !isAuthenticated ? (_jsxs(Text, { children: ["You are not authenticated or your access token is invalid. Publishing to Extension Marketplace is in Beta access. Please visit", _jsx(Text, { color: 'blueBright', children: " https://pulse-editor.com/beta " }), "to apply for Beta access."] })) : (_jsxs(_Fragment, { children: [isBuilding && (_jsxs(Box, { children: [_jsx(Spinner, { type: "dots" }), _jsx(Text, { children: " Building..." })] })), isBuildingError && (_jsx(Text, { color: 'redBright', children: "\u274C Error building the extension. Please run `npm run build` to see the error." })), isZipping && (_jsxs(Box, { children: [_jsx(Spinner, { type: "dots" }), _jsx(Text, { children: " Compressing build..." })] })), isZippingError && (_jsxs(Text, { color: 'redBright', children: ["\u274C Error zipping the build output. ", failureMessage] })), isPublishing && (_jsxs(Box, { children: [_jsx(Spinner, { type: "dots" }), _jsx(Text, { children: " Publishing..." })] })), isPublishingError && (_jsxs(_Fragment, { children: [_jsx(Text, { color: 'redBright', children: "\u274C Failed to publish extension." }), failureMessage && (_jsxs(Text, { color: 'redBright', children: ["Error: ", failureMessage] }))] })), isPublished && (_jsx(Text, { color: 'greenBright', children: "\u2705 Extension published successfully." }))] })) }));
|
|
126
112
|
}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
+
import { useEffect, useState } from 'react';
|
|
3
|
+
import { Box, Text, useInput } from 'ink';
|
|
4
|
+
import Spinner from 'ink-spinner';
|
|
5
|
+
import fs from 'fs';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { checkToken, getToken } from '../../lib/token.js';
|
|
8
|
+
import { getBackendUrl } from '../../lib/backend-url.js';
|
|
9
|
+
// ---------------------------------------------------------------------------
|
|
10
|
+
// MultilineInput
|
|
11
|
+
// ---------------------------------------------------------------------------
|
|
12
|
+
function MultilineInput({ onSubmit, focus, }) {
|
|
13
|
+
const [lines, setLines] = useState(['']);
|
|
14
|
+
useInput((input, key) => {
|
|
15
|
+
// Regular Enter (\r) → submit
|
|
16
|
+
// Shift+Enter → terminals send \n (0x0A) rather than setting key.shift+key.return
|
|
17
|
+
if (input === '\n') {
|
|
18
|
+
setLines(prev => [...prev, '']);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
if (key.return) {
|
|
22
|
+
const value = lines.join('\n').trim();
|
|
23
|
+
if (value)
|
|
24
|
+
onSubmit(value);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
if (key.backspace || key.delete) {
|
|
28
|
+
setLines(prev => {
|
|
29
|
+
const next = [...prev];
|
|
30
|
+
const last = next[next.length - 1];
|
|
31
|
+
if (last.length > 0) {
|
|
32
|
+
next[next.length - 1] = last.slice(0, -1);
|
|
33
|
+
}
|
|
34
|
+
else if (next.length > 1) {
|
|
35
|
+
next.pop();
|
|
36
|
+
}
|
|
37
|
+
return next;
|
|
38
|
+
});
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
if (input) {
|
|
42
|
+
setLines(prev => {
|
|
43
|
+
const next = [...prev];
|
|
44
|
+
next[next.length - 1] += input;
|
|
45
|
+
return next;
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
}, { isActive: focus });
|
|
49
|
+
return (_jsx(Box, { flexDirection: "column", borderStyle: "round", borderColor: "gray", paddingX: 1, children: lines.map((line, i) => (_jsxs(Text, { children: [line, i === lines.length - 1 ? _jsx(Text, { backgroundColor: "white", children: " " }) : ''] }, i))) }));
|
|
50
|
+
}
|
|
51
|
+
// ---------------------------------------------------------------------------
|
|
52
|
+
// SkillCreate
|
|
53
|
+
// ---------------------------------------------------------------------------
|
|
54
|
+
function SkillCreate({ cli }) {
|
|
55
|
+
const [skillName, setSkillName] = useState(cli.input[2]);
|
|
56
|
+
const [description, setDescription] = useState(cli.flags.description);
|
|
57
|
+
const [status, setStatus] = useState();
|
|
58
|
+
const [errorMessage, setErrorMessage] = useState('');
|
|
59
|
+
const [chunkCount, setChunkCount] = useState(0);
|
|
60
|
+
// Once both fields are collected, authenticate
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
if (!skillName || !description)
|
|
63
|
+
return;
|
|
64
|
+
setStatus('authenticating');
|
|
65
|
+
}, [skillName, description]);
|
|
66
|
+
useEffect(() => {
|
|
67
|
+
if (status !== 'authenticating')
|
|
68
|
+
return;
|
|
69
|
+
async function authenticate() {
|
|
70
|
+
const token = getToken(cli.flags.stage);
|
|
71
|
+
if (token && (await checkToken(token, cli.flags.stage))) {
|
|
72
|
+
setStatus('generating');
|
|
73
|
+
}
|
|
74
|
+
else {
|
|
75
|
+
setErrorMessage('You are not authenticated. Please run pulse login first.');
|
|
76
|
+
setStatus('error');
|
|
77
|
+
setTimeout(() => process.exit(1), 0);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
authenticate();
|
|
81
|
+
}, [status]);
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (status !== 'generating' || !skillName || !description)
|
|
84
|
+
return;
|
|
85
|
+
async function generate() {
|
|
86
|
+
const token = getToken(cli.flags.stage);
|
|
87
|
+
const backendUrl = getBackendUrl(cli.flags.stage);
|
|
88
|
+
try {
|
|
89
|
+
const res = await fetch(`${backendUrl}/api/inference/cli/skill/create`, {
|
|
90
|
+
method: 'POST',
|
|
91
|
+
headers: {
|
|
92
|
+
'Content-Type': 'application/json',
|
|
93
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
94
|
+
},
|
|
95
|
+
body: JSON.stringify({ description }),
|
|
96
|
+
});
|
|
97
|
+
if (!res.ok) {
|
|
98
|
+
setErrorMessage(`Server returned error code ${res.status}.`);
|
|
99
|
+
setStatus('error');
|
|
100
|
+
setTimeout(() => process.exit(), 0);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const reader = res.body?.getReader();
|
|
104
|
+
const decoder = new TextDecoder();
|
|
105
|
+
let code = '';
|
|
106
|
+
if (reader) {
|
|
107
|
+
while (true) {
|
|
108
|
+
const { done, value } = await reader.read();
|
|
109
|
+
if (done)
|
|
110
|
+
break;
|
|
111
|
+
code += decoder.decode(value, { stream: true });
|
|
112
|
+
setChunkCount(n => n + 1);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
const skillDir = path.join(process.cwd(), 'src', 'skill', skillName);
|
|
116
|
+
fs.mkdirSync(skillDir, { recursive: true });
|
|
117
|
+
fs.writeFileSync(path.join(skillDir, 'action.ts'), code, 'utf-8');
|
|
118
|
+
setStatus('done');
|
|
119
|
+
setTimeout(() => process.exit(), 0);
|
|
120
|
+
}
|
|
121
|
+
catch (err) {
|
|
122
|
+
setErrorMessage(err?.message ?? String(err));
|
|
123
|
+
setStatus('error');
|
|
124
|
+
setTimeout(() => process.exit(), 0);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
generate();
|
|
128
|
+
}, [status]);
|
|
129
|
+
return (_jsxs(_Fragment, { children: [!skillName && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "Skill name:" }), _jsx(MultilineInput, { onSubmit: value => setTimeout(() => setSkillName(value), 0), focus: !skillName })] })), skillName && !description && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { children: ["What should this skill do?", ' ', _jsx(Text, { color: "blueBright", children: "(Shift+Enter for newline, Enter to confirm)" })] }), _jsx(MultilineInput, { onSubmit: value => setTimeout(() => setDescription(value), 0), focus: !!skillName && !description })] })), status === 'authenticating' && (_jsxs(Box, { children: [_jsx(Spinner, { type: "dots" }), _jsx(Text, { children: " Checking authentication..." })] })), status === 'generating' && (_jsxs(Box, { children: [_jsx(Spinner, { type: "dots" }), _jsxs(Text, { children: [" Generating skill action for \"", skillName, "\"... "] }), _jsxs(Text, { color: "blueBright", children: ["[", chunkCount, " chunks received]"] })] })), status === 'done' && (_jsxs(Text, { color: "greenBright", children: ["\u2705 Skill action created at src/skill/", skillName, "/action.ts"] })), status === 'error' && (_jsxs(Text, { color: "redBright", children: ["\u274C ", errorMessage] }))] }));
|
|
130
|
+
}
|
|
131
|
+
// ---------------------------------------------------------------------------
|
|
132
|
+
// SkillFix
|
|
133
|
+
// ---------------------------------------------------------------------------
|
|
134
|
+
function SkillFix({ cli }) {
|
|
135
|
+
const [skillName, setSkillName] = useState(cli.input[2]);
|
|
136
|
+
const [status, setStatus] = useState();
|
|
137
|
+
const [errorMessage, setErrorMessage] = useState('');
|
|
138
|
+
const [chunkCount, setChunkCount] = useState(0);
|
|
139
|
+
// Once skill name is collected, authenticate
|
|
140
|
+
useEffect(() => {
|
|
141
|
+
if (!skillName)
|
|
142
|
+
return;
|
|
143
|
+
setStatus('authenticating');
|
|
144
|
+
}, [skillName]);
|
|
145
|
+
useEffect(() => {
|
|
146
|
+
if (status !== 'authenticating')
|
|
147
|
+
return;
|
|
148
|
+
async function authenticate() {
|
|
149
|
+
const token = getToken(cli.flags.stage);
|
|
150
|
+
if (token && (await checkToken(token, cli.flags.stage))) {
|
|
151
|
+
// Validate file exists before fixing
|
|
152
|
+
const actionPath = path.join(process.cwd(), 'src', 'skill', skillName, 'action.ts');
|
|
153
|
+
if (!fs.existsSync(actionPath)) {
|
|
154
|
+
setErrorMessage(`Action file not found: src/skill/${skillName}/action.ts`);
|
|
155
|
+
setStatus('error');
|
|
156
|
+
setTimeout(() => process.exit(), 0);
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
setStatus('fixing');
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
setErrorMessage('You are not authenticated. Please run pulse login first.');
|
|
163
|
+
setStatus('error');
|
|
164
|
+
setTimeout(() => process.exit(1), 0);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
authenticate();
|
|
168
|
+
}, [status]);
|
|
169
|
+
useEffect(() => {
|
|
170
|
+
if (status !== 'fixing' || !skillName)
|
|
171
|
+
return;
|
|
172
|
+
async function fix() {
|
|
173
|
+
const actionPath = path.join(process.cwd(), 'src', 'skill', skillName, 'action.ts');
|
|
174
|
+
const code = fs.readFileSync(actionPath, 'utf-8');
|
|
175
|
+
const token = getToken(cli.flags.stage);
|
|
176
|
+
const backendUrl = getBackendUrl(cli.flags.stage);
|
|
177
|
+
try {
|
|
178
|
+
const res = await fetch(`${backendUrl}/api/inference/cli/skill/fix`, {
|
|
179
|
+
method: 'POST',
|
|
180
|
+
headers: {
|
|
181
|
+
'Content-Type': 'application/json',
|
|
182
|
+
...(token ? { Authorization: `Bearer ${token}` } : {}),
|
|
183
|
+
},
|
|
184
|
+
body: JSON.stringify({ code }),
|
|
185
|
+
});
|
|
186
|
+
if (!res.ok) {
|
|
187
|
+
setErrorMessage(`Server returned error code ${res.status}.`);
|
|
188
|
+
setStatus('error');
|
|
189
|
+
setTimeout(() => process.exit(), 0);
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
const reader = res.body?.getReader();
|
|
193
|
+
const decoder = new TextDecoder();
|
|
194
|
+
let fixed = '';
|
|
195
|
+
if (reader) {
|
|
196
|
+
while (true) {
|
|
197
|
+
const { done, value } = await reader.read();
|
|
198
|
+
if (done)
|
|
199
|
+
break;
|
|
200
|
+
fixed += decoder.decode(value, { stream: true });
|
|
201
|
+
setChunkCount(n => n + 1);
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
fs.writeFileSync(actionPath, fixed, 'utf-8');
|
|
205
|
+
setStatus('done');
|
|
206
|
+
setTimeout(() => process.exit(), 0);
|
|
207
|
+
}
|
|
208
|
+
catch (err) {
|
|
209
|
+
setErrorMessage(err?.message ?? String(err));
|
|
210
|
+
setStatus('error');
|
|
211
|
+
setTimeout(() => process.exit(), 0);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
fix();
|
|
215
|
+
}, [status]);
|
|
216
|
+
return (_jsxs(_Fragment, { children: [!skillName && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: "Action name:" }), _jsx(MultilineInput, { onSubmit: value => setTimeout(() => setSkillName(value), 0), focus: !skillName })] })), status === 'authenticating' && (_jsxs(Box, { children: [_jsx(Spinner, { type: "dots" }), _jsx(Text, { children: " Checking authentication..." })] })), status === 'fixing' && (_jsxs(Box, { children: [_jsx(Spinner, { type: "dots" }), _jsxs(Text, { children: [" Fixing JSDoc for skill \"", skillName, "\"... "] }), _jsxs(Text, { color: "blueBright", children: ["[", chunkCount, " chunks received]"] })] })), status === 'done' && (_jsxs(Text, { color: "greenBright", children: ["\u2705 JSDoc fixed and saved to src/skill/", skillName, "/action.ts"] })), status === 'error' && (_jsxs(Text, { color: "redBright", children: ["\u274C ", errorMessage] }))] }));
|
|
217
|
+
}
|
|
218
|
+
// ---------------------------------------------------------------------------
|
|
219
|
+
// Skill (top-level router)
|
|
220
|
+
// ---------------------------------------------------------------------------
|
|
221
|
+
export default function Skill({ cli }) {
|
|
222
|
+
const subCommand = cli.input[1];
|
|
223
|
+
if (subCommand === 'create') {
|
|
224
|
+
return _jsx(SkillCreate, { cli: cli });
|
|
225
|
+
}
|
|
226
|
+
if (subCommand === 'fix') {
|
|
227
|
+
return _jsx(SkillFix, { cli: cli });
|
|
228
|
+
}
|
|
229
|
+
return (_jsxs(_Fragment, { children: [_jsxs(Text, { color: "redBright", children: ["Unknown subcommand: ", subCommand ?? '(none)'] }), _jsxs(Text, { children: ["Available subcommands:", '\n', ' ', "pulse skill create ", '<skill-name>', " --description \"", '<description>', "\"", '\n', ' ', "pulse skill fix ", '<action-name>'] })] }));
|
|
230
|
+
}
|