@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.
Files changed (59) hide show
  1. package/dist/app.js +4 -1
  2. package/dist/components/commands/build.js +18 -33
  3. package/dist/components/commands/code.d.ts +5 -0
  4. package/dist/components/commands/code.js +342 -0
  5. package/dist/components/commands/create.js +38 -12
  6. package/dist/components/commands/dev.js +35 -8
  7. package/dist/components/commands/login.d.ts +2 -2
  8. package/dist/components/commands/login.js +110 -26
  9. package/dist/components/commands/preview.js +50 -11
  10. package/dist/components/commands/publish.js +23 -37
  11. package/dist/components/commands/{start copy.d.ts → skill.d.ts} +1 -1
  12. package/dist/components/commands/skill.js +230 -0
  13. package/dist/components/commands/{preview copy.d.ts → upgrade.d.ts} +1 -1
  14. package/dist/components/commands/upgrade.js +53 -0
  15. package/dist/lib/backend/publish-app.d.ts +1 -0
  16. package/dist/lib/backend/publish-app.js +26 -0
  17. package/dist/lib/backend-url.d.ts +1 -0
  18. package/dist/lib/backend-url.js +3 -0
  19. package/dist/lib/cli-flags.d.ts +22 -0
  20. package/dist/lib/cli-flags.js +22 -0
  21. package/dist/lib/manual.js +40 -0
  22. package/dist/lib/server/express.js +81 -41
  23. package/dist/lib/server/preview/backend/load-remote.cjs +28 -18
  24. package/dist/lib/server/utils.js +3 -3
  25. package/dist/lib/token.js +2 -3
  26. package/dist/lib/webpack/compile.d.ts +2 -0
  27. package/dist/lib/webpack/compile.js +30 -0
  28. package/dist/lib/webpack/configs/mf-client.d.ts +3 -0
  29. package/dist/lib/webpack/configs/mf-client.js +184 -0
  30. package/dist/lib/webpack/configs/mf-server.d.ts +2 -0
  31. package/dist/lib/webpack/configs/mf-server.js +463 -0
  32. package/dist/lib/webpack/configs/preview.d.ts +3 -0
  33. package/dist/lib/webpack/configs/preview.js +117 -0
  34. package/dist/lib/webpack/configs/utils.d.ts +10 -0
  35. package/dist/lib/webpack/configs/utils.js +172 -0
  36. package/dist/lib/webpack/dist/pregistered-actions.d.ts +2 -0
  37. package/dist/lib/webpack/dist/pulse.config.d.ts +7 -0
  38. package/dist/lib/webpack/dist/src/lib/agents/code-modifier-agent.d.ts +2 -0
  39. package/dist/lib/webpack/dist/src/lib/agents/vibe-coding-agent.d.ts +3 -0
  40. package/dist/lib/webpack/dist/src/lib/mcp/utils.d.ts +3 -0
  41. package/dist/lib/webpack/dist/src/lib/streaming/message-stream-controller.d.ts +10 -0
  42. package/dist/lib/webpack/dist/src/lib/types.d.ts +58 -0
  43. package/dist/lib/webpack/dist/src/server-function/generate-code/v1/generate.d.ts +5 -0
  44. package/dist/lib/webpack/dist/src/server-function/generate-code/v2/generate.d.ts +1 -0
  45. package/dist/lib/webpack/tsconfig.server.json +19 -0
  46. package/dist/lib/webpack/webpack-config.d.ts +1 -0
  47. package/dist/lib/webpack/webpack-config.js +24 -0
  48. package/dist/lib/webpack/webpack.config.d.ts +2 -0
  49. package/dist/lib/webpack/webpack.config.js +527 -0
  50. package/package.json +31 -20
  51. package/readme.md +7 -1
  52. package/dist/components/commands/preview copy.js +0 -14
  53. package/dist/components/commands/start copy.js +0 -14
  54. package/dist/lib/deps.d.ts +0 -1
  55. package/dist/lib/deps.js +0 -5
  56. package/dist/lib/node_module_bin.d.ts +0 -1
  57. package/dist/lib/node_module_bin.js +0 -7
  58. package/dist/lib/server/preview/backend/index.d.ts +0 -1
  59. package/dist/lib/server/preview/backend/index.js +0 -23
@@ -1,5 +1,5 @@
1
- import { Flags } from '../../lib/cli-flags.js';
2
- import { Result } from 'meow';
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 { useEffect, useState } from 'react';
3
- import { Box, Text, useApp } from 'ink';
4
- import SelectInput from 'ink-select-input';
5
- import TextInput from 'ink-text-input';
6
- import Spinner from 'ink-spinner';
7
- import os from 'os';
8
- import path from 'path';
9
- import { checkToken, getToken, isTokenInEnv, saveToken, } from '../../lib/token.js';
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: 'Login using access token',
23
- value: 'token',
50
+ label: "Login in browser",
51
+ value: "flow",
24
52
  },
25
53
  {
26
- label: '(WIP) Login in browser',
27
- value: 'flow',
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('token');
64
+ setLoginMethod("token");
37
65
  setToken(savedToken);
38
66
  return;
39
67
  }
40
68
  else if (cli.flags.token) {
41
- setLoginMethod('token');
69
+ setLoginMethod("token");
42
70
  }
43
71
  else if (cli.flags.flow) {
44
- setLoginMethod('flow');
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 === 'token' && token.length > 0) {
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
- return (_jsxs(_Fragment, { children: [isShowLoginMethod && (_jsxs(_Fragment, { children: [_jsx(Text, { children: "Login to the Pulse Editor Platform" }), _jsx(SelectInput, { items: loginMethodItems, onSelect: item => {
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 === 'token' &&
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)", ' '] }), _jsx(TextInput, { value: saveTokenInput, onChange: setSaveTokenInput, onSubmit: value => {
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 === 'y') {
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(), '.pulse-editor')] }))] })) : (_jsx(Text, { children: "Authentication error: please enter valid credentials." }))), isMethodSelected && loginMethod === 'flow' && (_jsxs(_Fragment, { children: [_jsx(Text, { children: "(WIP) Open the following URL in your browser:" }), _jsx(Text, { children: "https://pulse-editor.com/login" })] }))] }));
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 { jsx as _jsx, Fragment as _Fragment } from "react/jsx-runtime";
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
- await execa('mkdir "node_modules/.pulse/server"', {
26
- shell: true,
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(getDepsBinPath('concurrently'), [
41
- '--prefix',
42
- 'none',
43
- '"npx webpack --mode development --watch"',
44
- '"tsx watch --clear-screen=false node_modules/@pulse-editor/cli/dist/lib/server/express.js"',
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 (_jsx(_Fragment, { children: _jsx(Text, { children: "Starting preview server..." }) }));
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
- setIsBuilding(true);
49
- try {
50
- await $ `npm run build`;
51
- }
52
- catch (error) {
53
- setIsBuildingError(true);
54
- setIsBuilding(false);
55
- setFailureMessage('Build failed. Please run `npm run build` to see the error.');
56
- return;
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
- setIsBuildingError(true);
64
- setIsBuilding(false);
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
- setIsBuilding(false);
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 formData = new FormData();
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." })), 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." }))] })) : (_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."] })) }));
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
  }
@@ -1,5 +1,5 @@
1
1
  import { Result } from 'meow';
2
2
  import { Flags } from '../../lib/cli-flags.js';
3
- export default function Start({ cli }: {
3
+ export default function Skill({ cli }: {
4
4
  cli: Result<Flags>;
5
5
  }): import("react/jsx-runtime").JSX.Element;
@@ -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
+ }
@@ -1,5 +1,5 @@
1
1
  import { Result } from 'meow';
2
2
  import { Flags } from '../../lib/cli-flags.js';
3
- export default function Preview({ cli }: {
3
+ export default function Upgrade({ cli }: {
4
4
  cli: Result<Flags>;
5
5
  }): import("react/jsx-runtime").JSX.Element;