@pulse-editor/cli 0.1.3 → 0.1.5

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,3 +1,5 @@
1
+ import type { TypedVariableType } from "@pulse-editor/shared-utils/dist/types/types.js";
2
+ import { JSDoc } from "ts-morph";
1
3
  export declare function loadPulseConfig(): Promise<any>;
2
4
  export declare function getLocalNetworkIP(): string;
3
5
  export declare function readConfigFile(): Promise<any>;
@@ -7,4 +9,13 @@ export declare function discoverServerFunctions(): {
7
9
  export declare function discoverAppSkillActions(): {
8
10
  [x: string]: string;
9
11
  } | null;
12
+ export declare function parseTypeDefs(jsDocs: JSDoc[]): Record<string, Record<string, {
13
+ type: string;
14
+ description: string;
15
+ }>>;
16
+ export declare function normalizeJSDocPropertyName(name: string | undefined): string | undefined;
17
+ export declare function isPromiseLikeType(type: import("ts-morph").Type): boolean;
18
+ export declare function unwrapPromiseLikeType(type: import("ts-morph").Type): import("ts-morph").Type<import("ts-morph").ts.Type>;
19
+ export declare function getActionType(text: string): TypedVariableType;
20
+ export declare function compileAppActionSkills(pulseConfig: any): void;
10
21
  export declare function generateTempTsConfig(): void;
@@ -1,10 +1,9 @@
1
- /* eslint-disable @typescript-eslint/no-explicit-any */
2
1
  import { existsSync, writeFileSync } from "fs";
3
2
  import fs from "fs/promises";
4
3
  import { globSync } from "glob";
5
4
  import { networkInterfaces } from "os";
6
5
  import path from "path";
7
- import { Project, SyntaxKind } from "ts-morph";
6
+ import { Node, Project, SyntaxKind } from "ts-morph";
8
7
  import ts from "typescript";
9
8
  import { pathToFileURL } from "url";
10
9
  export async function loadPulseConfig() {
@@ -143,6 +142,226 @@ export function discoverAppSkillActions() {
143
142
  }, {});
144
143
  return entryPoints;
145
144
  }
145
+ export function parseTypeDefs(jsDocs) {
146
+ const typeDefs = {};
147
+ const typedefRegex = /@typedef\s+{([^}]+)}\s+([^\s-]+)/g;
148
+ const propertyRegex = /@property\s+{([^}]+)}\s+(\[?[^\]\s]+\]?)\s*-?\s*(.*)/g;
149
+ jsDocs.forEach((doc) => {
150
+ const text = doc.getFullText();
151
+ let typedefMatches;
152
+ while ((typedefMatches = typedefRegex.exec(text)) !== null) {
153
+ const typeName = typedefMatches[2];
154
+ if (!typeName)
155
+ continue;
156
+ const properties = {};
157
+ let propertyMatches;
158
+ while ((propertyMatches = propertyRegex.exec(text)) !== null) {
159
+ const propName = normalizeJSDocPropertyName(propertyMatches[2]);
160
+ const propType = propertyMatches[1];
161
+ const propDescription = propertyMatches[3] || "";
162
+ if (propName && propType) {
163
+ properties[propName] = {
164
+ type: propType,
165
+ description: propDescription.trim(),
166
+ };
167
+ }
168
+ }
169
+ typeDefs[typeName.toLowerCase()] = properties;
170
+ }
171
+ });
172
+ return typeDefs;
173
+ }
174
+ export function normalizeJSDocPropertyName(name) {
175
+ if (!name)
176
+ return "";
177
+ return name
178
+ .trim()
179
+ .replace(/^\[/, "")
180
+ .replace(/\]$/, "")
181
+ .split("=")[0]
182
+ ?.trim();
183
+ }
184
+ export function isPromiseLikeType(type) {
185
+ const symbolName = type.getSymbol()?.getName();
186
+ return symbolName === "Promise" || symbolName === "PromiseLike";
187
+ }
188
+ export function unwrapPromiseLikeType(type) {
189
+ const symbolName = type.getSymbol()?.getName();
190
+ if ((symbolName === "Promise" || symbolName === "PromiseLike") &&
191
+ type.getTypeArguments().length > 0) {
192
+ return type.getTypeArguments()[0] ?? type;
193
+ }
194
+ return type;
195
+ }
196
+ export function getActionType(text) {
197
+ if (text === "string")
198
+ return "string";
199
+ if (text === "number")
200
+ return "number";
201
+ if (text === "boolean")
202
+ return "boolean";
203
+ if (text === "any")
204
+ return "object";
205
+ if (text.endsWith("[]"))
206
+ return [getActionType(text.slice(0, -2))];
207
+ if (text.length === 0)
208
+ return "undefined";
209
+ console.warn(`[Type Warning] Unrecognized type "${text}". Consider adding explicit types in your action's JSDoc comments for better type safety and documentation.`);
210
+ return text;
211
+ }
212
+ export function compileAppActionSkills(pulseConfig) {
213
+ const files = globSync("./src/skill/*/action.ts");
214
+ const project = new Project({
215
+ tsConfigFilePath: path.join(process.cwd(), "node_modules/.pulse/tsconfig.server.json"),
216
+ });
217
+ const actions = [];
218
+ files.forEach((file) => {
219
+ const sourceFile = project.addSourceFileAtPath(file);
220
+ const defaultExportSymbol = sourceFile.getDefaultExportSymbol();
221
+ if (!defaultExportSymbol)
222
+ return;
223
+ const defaultExportDeclarations = defaultExportSymbol.getDeclarations();
224
+ defaultExportDeclarations.forEach((declaration) => {
225
+ if (declaration.getKind() !== SyntaxKind.FunctionDeclaration)
226
+ return;
227
+ const funcDecl = declaration.asKindOrThrow(SyntaxKind.FunctionDeclaration);
228
+ const pattern = /src\/skill\/([^\/]+)\/action\.ts$/;
229
+ const match = file.replaceAll("\\", "/").match(pattern);
230
+ if (!match) {
231
+ console.warn(`File path ${file} does not match pattern ${pattern}. Skipping...`);
232
+ return;
233
+ }
234
+ const actionName = match[1];
235
+ if (!actionName) {
236
+ console.warn(`Could not extract action name from file path ${file}. Skipping...`);
237
+ return;
238
+ }
239
+ if (actions.some((action) => action.name === actionName)) {
240
+ throw new Error(`Duplicate action name "${actionName}" detected in file ${file}. Please ensure all actions have unique names to avoid conflicts.`);
241
+ }
242
+ const defaultExportJSDocs = funcDecl.getJsDocs();
243
+ const descriptionText = defaultExportJSDocs
244
+ .map((doc) => doc.getDescription().replace(/^\*+/gm, "").trim())
245
+ .join("\n")
246
+ .trim();
247
+ if (defaultExportJSDocs.length === 0 || !descriptionText) {
248
+ throw new Error(`[Action Validation] Action "${actionName}" in ${file} is missing a JSDoc description. ` +
249
+ `Please add a JSDoc comment block with a description above the function.` +
250
+ `Run \`pulse skill fix ${actionName}\` to automatically add a JSDoc template for this action.`);
251
+ }
252
+ const description = defaultExportJSDocs
253
+ .map((doc) => doc.getFullText())
254
+ .join("\n");
255
+ const allJSDocs = sourceFile.getDescendantsOfKind(SyntaxKind.JSDoc);
256
+ const typeDefs = parseTypeDefs(allJSDocs);
257
+ const funcParam = funcDecl.getParameters()[0];
258
+ const params = {};
259
+ if (funcParam) {
260
+ const defaults = new Map();
261
+ const nameNode = funcParam.getNameNode();
262
+ if (Node.isObjectBindingPattern(nameNode)) {
263
+ nameNode.getElements().forEach((el) => {
264
+ if (!Node.isBindingElement(el))
265
+ return;
266
+ const name = el.getName();
267
+ const initializer = el.getInitializer()?.getText();
268
+ if (initializer) {
269
+ defaults.set(name, initializer);
270
+ }
271
+ });
272
+ }
273
+ const paramProperties = funcParam.getType().getProperties();
274
+ const inputTypeDef = typeDefs["input"] ?? {};
275
+ if (paramProperties.length > 0 && !typeDefs["input"]) {
276
+ throw new Error(`[Action Validation] Action "${actionName}" in ${file} has parameters but is missing an ` +
277
+ `"@typedef {Object} input" JSDoc block. Please document all parameters with ` +
278
+ `@typedef {Object} input and @property tags.` +
279
+ `Run \`pulse skill fix ${actionName}\` to automatically add a JSDoc template for this action.`);
280
+ }
281
+ paramProperties.forEach((prop) => {
282
+ const name = prop.getName();
283
+ if (!inputTypeDef[name]) {
284
+ throw new Error(`[Action Validation] Action "${actionName}" in ${file}: parameter "${name}" is missing ` +
285
+ `a @property entry in the "input" JSDoc typedef. Please add ` +
286
+ `"@property {type} ${name} - description" to the JSDoc.` +
287
+ `Run \`pulse skill fix ${actionName}\` to automatically add a JSDoc template for this action.`);
288
+ }
289
+ if (!inputTypeDef[name]?.description?.trim()) {
290
+ throw new Error(`[Action Validation] Action "${actionName}" in ${file}: parameter "${name}" has an empty ` +
291
+ `description in the JSDoc @property. Please provide a meaningful description.` +
292
+ `Run \`pulse skill fix ${actionName}\` to automatically add a JSDoc template for this action.`);
293
+ }
294
+ const variable = {
295
+ description: inputTypeDef[name]?.description ?? "",
296
+ type: getActionType(inputTypeDef[name]?.type ?? ""),
297
+ optional: prop.isOptional() ? true : undefined,
298
+ defaultValue: defaults.get(name),
299
+ };
300
+ params[name] = variable;
301
+ });
302
+ }
303
+ const rawReturnType = funcDecl.getReturnType();
304
+ const isPromiseLikeReturn = isPromiseLikeType(rawReturnType);
305
+ const returnType = unwrapPromiseLikeType(rawReturnType);
306
+ if (!returnType.isObject()) {
307
+ console.warn(`[Action Registration] Function ${actionName}'s return type should be an object. Skipping...`);
308
+ return;
309
+ }
310
+ const returns = {};
311
+ const returnProperties = returnType.getProperties();
312
+ const outputTypeDef = typeDefs["output"] ?? {};
313
+ const hasOutputTypeDef = !!typeDefs["output"];
314
+ if (returnProperties.length > 0 && !hasOutputTypeDef && !isPromiseLikeReturn) {
315
+ throw new Error(`[Action Validation] Action "${actionName}" in ${file} returns properties but is missing an ` +
316
+ `"@typedef {Output}" JSDoc block. Please document all return values with ` +
317
+ `@typedef {Output} and @property tags.` +
318
+ `Run \`pulse skill fix ${actionName}\` to automatically add a JSDoc template for this action.`);
319
+ }
320
+ if (returnProperties.length > 0 && !hasOutputTypeDef && isPromiseLikeReturn) {
321
+ console.warn(`[Action Validation] Action "${actionName}" in ${file} is missing an "@typedef {Object} output" JSDoc block. ` +
322
+ `Falling back to TypeScript-inferred return metadata because the action returns a Promise.`);
323
+ }
324
+ returnProperties.forEach((prop) => {
325
+ const name = prop.getName();
326
+ if (!hasOutputTypeDef) {
327
+ const variable = {
328
+ description: "",
329
+ type: getActionType(prop.getTypeAtLocation(funcDecl).getText()),
330
+ optional: prop.isOptional() ? true : undefined,
331
+ defaultValue: undefined,
332
+ };
333
+ returns[name] = variable;
334
+ return;
335
+ }
336
+ if (!outputTypeDef[name]) {
337
+ throw new Error(`[Action Validation] Action "${actionName}" in ${file}: return property "${name}" is missing ` +
338
+ `a @property entry in the "output" JSDoc typedef. Please add ` +
339
+ `"@property {type} ${name} - description" to the JSDoc.` +
340
+ `Run \`pulse skill fix ${actionName}\` to automatically add a JSDoc template for this action.`);
341
+ }
342
+ if (!outputTypeDef[name]?.description?.trim()) {
343
+ throw new Error(`[Action Validation] Action "${actionName}" in ${file}: return property "${name}" has an empty ` +
344
+ `description in the JSDoc @property. Please provide a meaningful description.` +
345
+ `Run \`pulse skill fix ${actionName}\` to automatically add a JSDoc template for this action.`);
346
+ }
347
+ const variable = {
348
+ description: outputTypeDef[name]?.description ?? "",
349
+ type: getActionType(outputTypeDef[name]?.type ?? ""),
350
+ optional: prop.isOptional() ? true : undefined,
351
+ defaultValue: undefined,
352
+ };
353
+ returns[name] = variable;
354
+ });
355
+ actions.push({
356
+ name: actionName,
357
+ description,
358
+ parameters: params,
359
+ returns,
360
+ });
361
+ });
362
+ });
363
+ pulseConfig.actions = actions;
364
+ }
146
365
  // Generate tsconfig for server functions
147
366
  export function generateTempTsConfig() {
148
367
  const tempTsConfigPath = path.join(process.cwd(), "node_modules/.pulse/tsconfig.server.json");
@@ -1,12 +1,12 @@
1
1
  /* eslint-disable @typescript-eslint/no-explicit-any */
2
2
  import { makeMFClientConfig } from "./configs/mf-client.js";
3
3
  import { makeMFServerConfig } from "./configs/mf-server.js";
4
- import { makePreviewClientConfig } from "./configs/preview.js";
4
+ import { makeMFPreviewConfig, makePreviewClientConfig } from "./configs/preview.js";
5
5
  export async function createWebpackConfig(isPreview, buildTarget, mode) {
6
6
  if (isPreview) {
7
7
  const previewClientConfig = await makePreviewClientConfig("development");
8
- const mfServerConfig = await makeMFServerConfig("development");
9
- return [previewClientConfig, mfServerConfig];
8
+ const mfPreviewConfig = await makeMFPreviewConfig();
9
+ return [previewClientConfig, mfPreviewConfig];
10
10
  }
11
11
  else if (buildTarget === "server") {
12
12
  const mfServerConfig = await makeMFServerConfig(mode);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pulse-editor/cli",
3
- "version": "0.1.3",
3
+ "version": "0.1.5",
4
4
  "license": "MIT",
5
5
  "bin": {
6
6
  "pulse": "dist/cli.js"
@@ -1 +0,0 @@
1
- export declare function readConfigFile(): Promise<any>;
@@ -1,17 +0,0 @@
1
- import fs from 'fs/promises';
2
- export async function readConfigFile() {
3
- // Read pulse.config.json from dist/client
4
- // Wait until dist/pulse.config.json exists
5
- while (true) {
6
- try {
7
- await fs.access('dist/pulse.config.json');
8
- break;
9
- }
10
- catch (err) {
11
- // Wait for 100ms before trying again
12
- await new Promise(resolve => setTimeout(resolve, 100));
13
- }
14
- }
15
- const data = await fs.readFile('dist/pulse.config.json', 'utf-8');
16
- return JSON.parse(data);
17
- }
@@ -1,2 +0,0 @@
1
- import { Action } from "@pulse-editor/shared-utils";
2
- export declare const preRegisteredActions: Record<string, Action>;
@@ -1,7 +0,0 @@
1
- import { AppConfig } from "@pulse-editor/shared-utils";
2
- /**
3
- * Pulse Editor Extension Config
4
- *
5
- */
6
- declare const config: AppConfig;
7
- export default config;
@@ -1,2 +0,0 @@
1
- import { ChatPromptTemplate } from "@langchain/core/prompts";
2
- export declare const codeModifierAgentPrompt: ChatPromptTemplate<any, any>;
@@ -1,3 +0,0 @@
1
- import { MultiServerMCPClient } from "@langchain/mcp-adapters";
2
- import { MessageStreamController } from "../streaming/message-stream-controller";
3
- export declare function runVibeCoding(mcpClient: MultiServerMCPClient, userPrompt: string, controller: MessageStreamController): Promise<void>;
@@ -1,3 +0,0 @@
1
- import { MultiServerMCPClient } from "@langchain/mcp-adapters";
2
- export declare function writeFileToFS(mcpClient: MultiServerMCPClient, uri: string, content: string): Promise<string>;
3
- export declare function callTerminal(mcpClient: MultiServerMCPClient, command: string): Promise<string>;
@@ -1,10 +0,0 @@
1
- import { AgentTaskMessageData } from "../types";
2
- export declare class MessageStreamController {
3
- private controller;
4
- private msgCounter;
5
- private messages;
6
- private timeCountMap;
7
- constructor(controller: ReadableStreamDefaultController);
8
- enqueueNew(data: AgentTaskMessageData, isFinal: boolean): number;
9
- enqueueUpdate(data: AgentTaskMessageData, isFinal: boolean): void;
10
- }
@@ -1,58 +0,0 @@
1
- export type VibeDevFlowNode = {
2
- id: string;
3
- children: VibeDevFlowNode[];
4
- };
5
- export declare enum AgentTaskMessageType {
6
- Creation = "creation",
7
- Update = "update"
8
- }
9
- export declare enum AgentTaskMessageDataType {
10
- Notification = "notification",
11
- ToolCall = "toolCall",
12
- ArtifactOutput = "artifactOutput"
13
- }
14
- /**
15
- * Data associated with an AgentTaskItem.
16
- * The fields included depend on the type of the task item.
17
- *
18
- * @property type - The type of the task item, defined by the AgentTaskItemType enum.
19
- * @property title - (Optional) A brief title or summary of the task item.
20
- * @property description - (Optional) A detailed description of the task item.
21
- * @property toolName - (Optional) The name of the tool being called (if applicable).
22
- * @property parameters - (Optional) A record of parameters associated with the tool call (if applicable).
23
- * @property error - (Optional) An error message if the task item represents an error.
24
- * @property result - (Optional) The result or output of the task item (if applicable).
25
- */
26
- export type AgentTaskMessageData = {
27
- type?: AgentTaskMessageDataType;
28
- title?: string;
29
- description?: string;
30
- toolName?: string;
31
- parameters?: Record<string, unknown>;
32
- error?: string;
33
- result?: string;
34
- };
35
- /**
36
- * Represents a single task item generated by the agent.
37
- * Each task item can be of different types such as tool calls, notifications, or errors.
38
- *
39
- * @property type - The type of the task item, defined by the AgentTaskItemType enum.
40
- * @property messageId - The unique identifier for the task item.
41
- * This is an incremental number representing the n-th task item.
42
- * @property data - The data associated with the task item, which varies based on the type.
43
- * @property isFinal - (Optional) Indicates if the task item is final and no further updates are expected.
44
- */
45
- export type AgentTaskMessage = {
46
- type: AgentTaskMessageType;
47
- messageId: number;
48
- data: AgentTaskMessageData;
49
- isFinal?: boolean;
50
- };
51
- export type AgentTaskMessageUpdate = {
52
- type: AgentTaskMessageType;
53
- messageId: number;
54
- updateType: "append";
55
- delta: AgentTaskMessageData;
56
- isFinal?: boolean;
57
- timeUsedSec?: string;
58
- };
@@ -1,5 +0,0 @@
1
- /**
2
- * An example function to echo the body of a POST request.
3
- * This route is accessible at /server-function/echo
4
- */
5
- export default function generate(req: Request): Promise<Response>;
@@ -1 +0,0 @@
1
- export default function handler(req: Request): Promise<Response>;
@@ -1,19 +0,0 @@
1
- {
2
- "compilerOptions": {
3
- "target": "ES2020",
4
- "module": "esnext",
5
- "moduleResolution": "bundler",
6
- "strict": true,
7
- "declaration": true,
8
- "outDir": "dist",
9
- },
10
- "include": [
11
- "../../../../../../src/server-function/**/*",
12
- "../../../../../../pulse.config.ts",
13
- "../../../../../../global.d.ts",
14
- ],
15
- "exclude": [
16
- "../../../../../../node_modules",
17
- "../../../../../../dist",
18
- ]
19
- }
@@ -1,2 +0,0 @@
1
- import wp from 'webpack';
2
- export declare function createWebpackConfig(isPreview: boolean, buildTarget: 'client' | 'server' | 'both', mode: 'development' | 'production'): Promise<wp.Configuration[]>;