wdyt 0.1.9 → 0.1.11
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/package.json +1 -1
- package/src/cli.ts +39 -32
- package/src/commands/builder.ts +12 -28
- package/src/parseExpression.ts +121 -0
package/package.json
CHANGED
package/src/cli.ts
CHANGED
|
@@ -26,34 +26,33 @@ import {
|
|
|
26
26
|
import { selectGetCommand, selectAddCommand } from "./commands/select";
|
|
27
27
|
import { chatSendCommand } from "./commands/chat";
|
|
28
28
|
import { initCommand, parseInitArgs } from "./commands/init";
|
|
29
|
+
import { parseExpression } from "./parseExpression";
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
|
-
*
|
|
32
|
+
* Execute a parsed expression
|
|
32
33
|
*/
|
|
33
34
|
async function executeExpression(
|
|
34
35
|
expression: string,
|
|
35
36
|
flags: CLIFlags
|
|
36
37
|
): Promise<{ success: boolean; data?: unknown; output?: string; error?: string }> {
|
|
37
|
-
const
|
|
38
|
+
const parsed = parseExpression(expression);
|
|
38
39
|
|
|
39
|
-
|
|
40
|
-
// Expressions can be: "windows", "builder {json}", "prompt get", etc.
|
|
41
|
-
const match = expr.match(/^(\w+)(?:\s+(.*))?$/);
|
|
42
|
-
if (!match) {
|
|
40
|
+
if (!parsed.command) {
|
|
43
41
|
return { success: false, error: `Invalid expression: ${expression}` };
|
|
44
42
|
}
|
|
45
43
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
switch (command) {
|
|
44
|
+
switch (parsed.command) {
|
|
49
45
|
case "windows":
|
|
50
46
|
return await windowsCommand();
|
|
51
47
|
|
|
52
|
-
case "builder":
|
|
48
|
+
case "builder": {
|
|
53
49
|
if (!flags.window) {
|
|
54
50
|
return { success: false, error: "builder requires -w <window>" };
|
|
55
51
|
}
|
|
56
|
-
|
|
52
|
+
// Pass the first positional (summary) and flags
|
|
53
|
+
const summary = parsed.positional[0];
|
|
54
|
+
return await builderCommand(flags.window, summary, parsed.flags);
|
|
55
|
+
}
|
|
57
56
|
|
|
58
57
|
case "prompt": {
|
|
59
58
|
if (!flags.window || !flags.tab) {
|
|
@@ -63,19 +62,21 @@ async function executeExpression(
|
|
|
63
62
|
};
|
|
64
63
|
}
|
|
65
64
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (
|
|
65
|
+
const subcommand = parsed.subcommand || "get";
|
|
66
|
+
|
|
67
|
+
if (subcommand === "get") {
|
|
69
68
|
return await promptGetCommand(flags.window, flags.tab);
|
|
70
69
|
}
|
|
71
70
|
|
|
72
|
-
if (
|
|
73
|
-
|
|
74
|
-
|
|
71
|
+
if (subcommand === "export") {
|
|
72
|
+
const filePath = parsed.positional[0];
|
|
73
|
+
if (!filePath) {
|
|
74
|
+
return { success: false, error: "prompt export requires a file path" };
|
|
75
|
+
}
|
|
75
76
|
return await promptExportCommand(flags.window, flags.tab, filePath);
|
|
76
77
|
}
|
|
77
78
|
|
|
78
|
-
return { success: false, error: `Unknown prompt subcommand: ${
|
|
79
|
+
return { success: false, error: `Unknown prompt subcommand: ${subcommand}` };
|
|
79
80
|
}
|
|
80
81
|
|
|
81
82
|
case "select": {
|
|
@@ -86,49 +87,55 @@ async function executeExpression(
|
|
|
86
87
|
};
|
|
87
88
|
}
|
|
88
89
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
if (
|
|
90
|
+
const subcommand = parsed.subcommand || "get";
|
|
91
|
+
|
|
92
|
+
if (subcommand === "get") {
|
|
92
93
|
return await selectGetCommand(flags.window, flags.tab);
|
|
93
94
|
}
|
|
94
95
|
|
|
95
|
-
if (
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
if (subcommand === "add") {
|
|
97
|
+
// All remaining positionals are paths
|
|
98
|
+
const paths = parsed.positional.join(" ");
|
|
99
|
+
if (!paths) {
|
|
100
|
+
return { success: false, error: "select add requires file paths" };
|
|
101
|
+
}
|
|
102
|
+
return await selectAddCommand(flags.window, flags.tab, paths);
|
|
98
103
|
}
|
|
99
104
|
|
|
100
|
-
return { success: false, error: `Unknown select subcommand: ${
|
|
105
|
+
return { success: false, error: `Unknown select subcommand: ${subcommand}` };
|
|
101
106
|
}
|
|
102
107
|
|
|
103
108
|
case "call": {
|
|
104
109
|
// Handle "call prompt {json}" and "call chat_send {json}"
|
|
105
|
-
|
|
110
|
+
const callTarget = parsed.subcommand || parsed.positional[0];
|
|
111
|
+
|
|
112
|
+
if (callTarget === "prompt") {
|
|
106
113
|
if (!flags.window || !flags.tab) {
|
|
107
114
|
return {
|
|
108
115
|
success: false,
|
|
109
116
|
error: "prompt requires -w <window> -t <tab>",
|
|
110
117
|
};
|
|
111
118
|
}
|
|
112
|
-
const payload =
|
|
119
|
+
const payload = parsed.positional.slice(1).join(" ") || "{}";
|
|
113
120
|
return await promptSetCommand(flags.window, flags.tab, payload);
|
|
114
121
|
}
|
|
115
122
|
|
|
116
|
-
if (
|
|
123
|
+
if (callTarget === "chat_send") {
|
|
117
124
|
if (!flags.window || !flags.tab) {
|
|
118
125
|
return {
|
|
119
126
|
success: false,
|
|
120
127
|
error: "chat_send requires -w <window> -t <tab>",
|
|
121
128
|
};
|
|
122
129
|
}
|
|
123
|
-
|
|
124
|
-
const payload = args.slice(9).trim() || "{}";
|
|
130
|
+
const payload = parsed.positional.slice(1).join(" ") || "{}";
|
|
125
131
|
return await chatSendCommand(flags.window, flags.tab, payload);
|
|
126
132
|
}
|
|
127
|
-
|
|
133
|
+
|
|
134
|
+
return { success: false, error: `Unknown call: ${callTarget}` };
|
|
128
135
|
}
|
|
129
136
|
|
|
130
137
|
default:
|
|
131
|
-
return { success: false, error: `Unknown command: ${command}` };
|
|
138
|
+
return { success: false, error: `Unknown command: ${parsed.command}` };
|
|
132
139
|
}
|
|
133
140
|
}
|
|
134
141
|
|
package/src/commands/builder.ts
CHANGED
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Builder command - create a new tab
|
|
3
3
|
*
|
|
4
|
-
* Parses JSON arg: {summary: string}
|
|
5
4
|
* Returns: Tab: <uuid>
|
|
6
5
|
* Compatible with flowctl.py parsing at line 255-259:
|
|
7
6
|
* match = re.search(r"Tab:\s*([A-Za-z0-9-]+)", output)
|
|
8
7
|
*/
|
|
9
8
|
|
|
10
9
|
import { createTab, getWindow } from "../state";
|
|
11
|
-
import type { BuilderConfig } from "../types";
|
|
12
10
|
|
|
13
11
|
/**
|
|
14
12
|
* Builder command response
|
|
@@ -18,22 +16,11 @@ export interface BuilderResponse {
|
|
|
18
16
|
}
|
|
19
17
|
|
|
20
18
|
/**
|
|
21
|
-
*
|
|
22
|
-
* @param args - JSON string like {"summary": "test"}
|
|
23
|
-
* @returns Parsed BuilderConfig or null if invalid
|
|
19
|
+
* Builder flags from expression parser
|
|
24
20
|
*/
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
return {};
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
try {
|
|
32
|
-
const config = JSON.parse(args.trim()) as BuilderConfig;
|
|
33
|
-
return config;
|
|
34
|
-
} catch {
|
|
35
|
-
return null;
|
|
36
|
-
}
|
|
21
|
+
export interface BuilderFlags {
|
|
22
|
+
"response-type"?: string;
|
|
23
|
+
[key: string]: string | boolean | undefined;
|
|
37
24
|
}
|
|
38
25
|
|
|
39
26
|
/**
|
|
@@ -41,12 +28,14 @@ function parseBuilderArgs(args?: string): BuilderConfig | null {
|
|
|
41
28
|
* Creates a new tab in the specified window
|
|
42
29
|
*
|
|
43
30
|
* @param windowId - The window ID to create the tab in
|
|
44
|
-
* @param
|
|
31
|
+
* @param summary - Optional summary/description for the tab
|
|
32
|
+
* @param flags - Optional flags (e.g., --response-type)
|
|
45
33
|
* @returns Tab: <uuid> on success
|
|
46
34
|
*/
|
|
47
35
|
export async function builderCommand(
|
|
48
36
|
windowId: number,
|
|
49
|
-
|
|
37
|
+
summary?: string,
|
|
38
|
+
flags?: BuilderFlags
|
|
50
39
|
): Promise<{
|
|
51
40
|
success: boolean;
|
|
52
41
|
data?: BuilderResponse;
|
|
@@ -57,18 +46,13 @@ export async function builderCommand(
|
|
|
57
46
|
// Verify window exists
|
|
58
47
|
await getWindow(windowId);
|
|
59
48
|
|
|
60
|
-
// Parse builder config (optional)
|
|
61
|
-
const config = parseBuilderArgs(args);
|
|
62
|
-
if (config === null) {
|
|
63
|
-
return {
|
|
64
|
-
success: false,
|
|
65
|
-
error: `Invalid builder config JSON: ${args}`,
|
|
66
|
-
};
|
|
67
|
-
}
|
|
68
|
-
|
|
69
49
|
// Create the tab
|
|
70
50
|
const tab = await createTab(windowId);
|
|
71
51
|
|
|
52
|
+
// Note: summary and flags like --response-type are accepted but
|
|
53
|
+
// not used in this minimal implementation. The real RepoPrompt
|
|
54
|
+
// uses them for its GUI features.
|
|
55
|
+
|
|
72
56
|
// Return in the format flowctl.py expects: Tab: <uuid>
|
|
73
57
|
return {
|
|
74
58
|
success: true,
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Expression parser for wdyt CLI
|
|
3
|
+
*
|
|
4
|
+
* Properly parses shell-like expressions into command + args + flags
|
|
5
|
+
* Example: 'builder "summary text" --response-type review'
|
|
6
|
+
* -> { command: "builder", positional: ["summary text"], flags: { responseType: "review" } }
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { parseArgs } from "node:util";
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Tokenize a shell-like string, respecting quotes
|
|
13
|
+
* "builder \"hello world\" --flag" -> ["builder", "hello world", "--flag"]
|
|
14
|
+
*/
|
|
15
|
+
export function tokenize(input: string): string[] {
|
|
16
|
+
const tokens: string[] = [];
|
|
17
|
+
let current = "";
|
|
18
|
+
let inQuote: string | null = null;
|
|
19
|
+
let escape = false;
|
|
20
|
+
|
|
21
|
+
for (let i = 0; i < input.length; i++) {
|
|
22
|
+
const char = input[i];
|
|
23
|
+
|
|
24
|
+
if (escape) {
|
|
25
|
+
current += char;
|
|
26
|
+
escape = false;
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (char === "\\") {
|
|
31
|
+
escape = true;
|
|
32
|
+
continue;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (char === '"' || char === "'") {
|
|
36
|
+
if (inQuote === char) {
|
|
37
|
+
// End of quoted string
|
|
38
|
+
inQuote = null;
|
|
39
|
+
} else if (inQuote === null) {
|
|
40
|
+
// Start of quoted string
|
|
41
|
+
inQuote = char;
|
|
42
|
+
} else {
|
|
43
|
+
// Different quote inside a quoted string
|
|
44
|
+
current += char;
|
|
45
|
+
}
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (char === " " && inQuote === null) {
|
|
50
|
+
if (current) {
|
|
51
|
+
tokens.push(current);
|
|
52
|
+
current = "";
|
|
53
|
+
}
|
|
54
|
+
continue;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
current += char;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (current) {
|
|
61
|
+
tokens.push(current);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
return tokens;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Parsed expression result
|
|
69
|
+
*/
|
|
70
|
+
export interface ParsedExpression {
|
|
71
|
+
command: string;
|
|
72
|
+
subcommand?: string;
|
|
73
|
+
positional: string[];
|
|
74
|
+
flags: Record<string, string | boolean>;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Parse an expression string into structured parts
|
|
79
|
+
*/
|
|
80
|
+
export function parseExpression(expression: string): ParsedExpression {
|
|
81
|
+
const tokens = tokenize(expression.trim());
|
|
82
|
+
|
|
83
|
+
if (tokens.length === 0) {
|
|
84
|
+
return { command: "", positional: [], flags: {} };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const command = tokens[0];
|
|
88
|
+
const rest = tokens.slice(1);
|
|
89
|
+
|
|
90
|
+
// Use parseArgs for the remaining tokens
|
|
91
|
+
const { values, positionals } = parseArgs({
|
|
92
|
+
args: rest,
|
|
93
|
+
options: {
|
|
94
|
+
"response-type": { type: "string" },
|
|
95
|
+
"new-chat": { type: "boolean" },
|
|
96
|
+
"chat-name": { type: "string" },
|
|
97
|
+
"chat-id": { type: "string" },
|
|
98
|
+
},
|
|
99
|
+
allowPositionals: true,
|
|
100
|
+
strict: false, // Don't error on unknown flags
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
// Determine subcommand for commands that have them
|
|
104
|
+
let subcommand: string | undefined;
|
|
105
|
+
let finalPositional = positionals;
|
|
106
|
+
|
|
107
|
+
if (command === "prompt" || command === "select") {
|
|
108
|
+
// First positional is the subcommand (get, set, add, export)
|
|
109
|
+
if (positionals.length > 0) {
|
|
110
|
+
subcommand = positionals[0];
|
|
111
|
+
finalPositional = positionals.slice(1);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return {
|
|
116
|
+
command,
|
|
117
|
+
subcommand,
|
|
118
|
+
positional: finalPositional,
|
|
119
|
+
flags: values as Record<string, string | boolean>,
|
|
120
|
+
};
|
|
121
|
+
}
|