runwork 0.6.0 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -62,8 +62,21 @@ describe('generateIntroSkill', () => {
|
|
|
62
62
|
mcpServerCount: 3,
|
|
63
63
|
skillCount: 5,
|
|
64
64
|
}));
|
|
65
|
-
expect(skill.content).toContain('
|
|
66
|
-
expect(skill.content).toContain('
|
|
65
|
+
expect(skill.content).toContain('MCP Servers');
|
|
66
|
+
expect(skill.content).toContain('3 servers configured');
|
|
67
|
+
expect(skill.content).toContain('5 skills configured');
|
|
68
|
+
});
|
|
69
|
+
it('includes MCP and skill names when provided', () => {
|
|
70
|
+
const skill = generateIntroSkill(makeContext({
|
|
71
|
+
mcpServerCount: 2,
|
|
72
|
+
skillCount: 2,
|
|
73
|
+
mcpServerNames: ['Workspace Tools', 'aTars MCP'],
|
|
74
|
+
skillNames: ['Developer Tools', 'Writing Plans'],
|
|
75
|
+
}));
|
|
76
|
+
expect(skill.content).toContain('MCP Servers (2)');
|
|
77
|
+
expect(skill.content).toContain('Workspace Tools');
|
|
78
|
+
expect(skill.content).toContain('Skills (2)');
|
|
79
|
+
expect(skill.content).toContain('Developer Tools');
|
|
67
80
|
});
|
|
68
81
|
it('includes CLI commands section', () => {
|
|
69
82
|
const skill = generateIntroSkill(makeContext());
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const VERSION = "0.6.
|
|
1
|
+
export declare const VERSION = "0.6.2";
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Auto-generated by scripts/embed-types.ts -- do not edit
|
|
2
|
-
export const VERSION = "0.6.
|
|
2
|
+
export const VERSION = "0.6.2";
|
|
@@ -7,6 +7,6 @@ export interface ParsedRequest {
|
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
* Parse a curl command string into a structured request.
|
|
10
|
-
*
|
|
10
|
+
* Pure JS implementation, no native dependencies.
|
|
11
11
|
*/
|
|
12
12
|
export declare function parseCurlToRequest(curlStr: string): Promise<ParsedRequest>;
|
|
@@ -1,44 +1,162 @@
|
|
|
1
|
-
|
|
2
|
-
/** Headers that should be stripped — the integration proxy handles auth */
|
|
1
|
+
/** Headers that should be stripped -- the integration proxy handles auth */
|
|
3
2
|
const STRIPPED_HEADERS = new Set(['host', 'authorization', 'cookie', 'connection', 'user-agent']);
|
|
3
|
+
/**
|
|
4
|
+
* Tokenize a curl command string, respecting single/double quotes and backslash continuations.
|
|
5
|
+
*/
|
|
6
|
+
function tokenize(input) {
|
|
7
|
+
// Normalize backslash-newline continuations into a single line
|
|
8
|
+
const normalized = input.replace(/\\\s*\n\s*/g, ' ');
|
|
9
|
+
const tokens = [];
|
|
10
|
+
let i = 0;
|
|
11
|
+
while (i < normalized.length) {
|
|
12
|
+
// Skip whitespace
|
|
13
|
+
if (/\s/.test(normalized[i])) {
|
|
14
|
+
i++;
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
// Quoted string
|
|
18
|
+
if (normalized[i] === "'" || normalized[i] === '"') {
|
|
19
|
+
const quote = normalized[i];
|
|
20
|
+
let token = '';
|
|
21
|
+
i++; // skip opening quote
|
|
22
|
+
while (i < normalized.length && normalized[i] !== quote) {
|
|
23
|
+
if (normalized[i] === '\\' && quote === '"' && i + 1 < normalized.length) {
|
|
24
|
+
token += normalized[i + 1];
|
|
25
|
+
i += 2;
|
|
26
|
+
}
|
|
27
|
+
else {
|
|
28
|
+
token += normalized[i];
|
|
29
|
+
i++;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
i++; // skip closing quote
|
|
33
|
+
tokens.push(token);
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
// Unquoted token
|
|
37
|
+
let token = '';
|
|
38
|
+
while (i < normalized.length && !/\s/.test(normalized[i])) {
|
|
39
|
+
token += normalized[i];
|
|
40
|
+
i++;
|
|
41
|
+
}
|
|
42
|
+
tokens.push(token);
|
|
43
|
+
}
|
|
44
|
+
return tokens;
|
|
45
|
+
}
|
|
46
|
+
/** Flags that take no argument (should be skipped, not consumed as URL) */
|
|
47
|
+
const NO_ARG_FLAGS = new Set([
|
|
48
|
+
'--compressed', '-s', '--silent', '-S', '--show-error',
|
|
49
|
+
'-L', '--location', '-v', '--verbose', '-k', '--insecure',
|
|
50
|
+
'-I', '--head', '-i', '--include', '-N', '--no-buffer',
|
|
51
|
+
'--fail', '-f', '--globoff', '-g',
|
|
52
|
+
]);
|
|
53
|
+
/** Flags that take one argument (flag + value should both be consumed) */
|
|
54
|
+
const ONE_ARG_FLAGS = new Set([
|
|
55
|
+
'-o', '--output', '-u', '--user', '-A', '--user-agent',
|
|
56
|
+
'-e', '--referer', '-b', '--cookie', '-c', '--cookie-jar',
|
|
57
|
+
'--connect-timeout', '-m', '--max-time', '--retry',
|
|
58
|
+
'-w', '--write-out', '--resolve', '--proxy', '-x',
|
|
59
|
+
'--cert', '--key', '--cacert', '-T', '--upload-file',
|
|
60
|
+
]);
|
|
4
61
|
/**
|
|
5
62
|
* Parse a curl command string into a structured request.
|
|
6
|
-
*
|
|
63
|
+
* Pure JS implementation, no native dependencies.
|
|
7
64
|
*/
|
|
8
65
|
export async function parseCurlToRequest(curlStr) {
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
66
|
+
const trimmed = curlStr.trim();
|
|
67
|
+
if (!trimmed) {
|
|
68
|
+
throw new Error('Could not parse curl command -- empty input.');
|
|
69
|
+
}
|
|
70
|
+
const tokens = tokenize(trimmed);
|
|
71
|
+
// Skip leading "curl" token
|
|
72
|
+
let start = 0;
|
|
73
|
+
if (tokens[start] === 'curl')
|
|
74
|
+
start++;
|
|
75
|
+
let url;
|
|
76
|
+
let method;
|
|
77
|
+
const headers = {};
|
|
78
|
+
let dataStr;
|
|
79
|
+
for (let i = start; i < tokens.length; i++) {
|
|
80
|
+
const token = tokens[i];
|
|
81
|
+
if (token === '-X' || token === '--request') {
|
|
82
|
+
method = tokens[++i];
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
if (token === '-H' || token === '--header') {
|
|
86
|
+
const headerVal = tokens[++i];
|
|
87
|
+
if (headerVal) {
|
|
88
|
+
const colonIdx = headerVal.indexOf(':');
|
|
89
|
+
if (colonIdx > 0) {
|
|
90
|
+
const key = headerVal.slice(0, colonIdx);
|
|
91
|
+
const value = headerVal.slice(colonIdx + 1).trimStart();
|
|
92
|
+
headers[key] = value;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
if (token === '-d' || token === '--data' || token === '--data-raw' || token === '--data-binary') {
|
|
98
|
+
dataStr = tokens[++i];
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (NO_ARG_FLAGS.has(token)) {
|
|
102
|
+
continue;
|
|
103
|
+
}
|
|
104
|
+
if (ONE_ARG_FLAGS.has(token)) {
|
|
105
|
+
i++; // skip the argument
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
// If it looks like a URL (starts with http or has ://) and we don't have one yet
|
|
109
|
+
if (!url && (token.startsWith('http://') || token.startsWith('https://') || token.includes('://'))) {
|
|
110
|
+
url = token;
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
// If it doesn't start with '-' and we don't have a URL, treat as URL
|
|
114
|
+
if (!url && !token.startsWith('-')) {
|
|
115
|
+
url = token;
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
if (!url) {
|
|
12
120
|
throw new Error('Could not parse curl command -- no URL found.');
|
|
13
121
|
}
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
122
|
+
// Infer method: POST if data is present, otherwise GET
|
|
123
|
+
if (!method) {
|
|
124
|
+
method = dataStr !== undefined ? 'POST' : 'GET';
|
|
125
|
+
}
|
|
126
|
+
// Parse URL
|
|
127
|
+
const parsedUrl = new URL(url);
|
|
17
128
|
const path = parsedUrl.pathname;
|
|
18
129
|
const query = parsedUrl.search ? parsedUrl.search.slice(1) : undefined;
|
|
19
|
-
//
|
|
20
|
-
const
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
headers[key] = val;
|
|
25
|
-
}
|
|
130
|
+
// Strip proxy-managed headers
|
|
131
|
+
const filteredHeaders = {};
|
|
132
|
+
for (const [key, val] of Object.entries(headers)) {
|
|
133
|
+
if (!STRIPPED_HEADERS.has(key.toLowerCase())) {
|
|
134
|
+
filteredHeaders[key] = val;
|
|
26
135
|
}
|
|
27
136
|
}
|
|
28
137
|
// Parse body
|
|
29
138
|
let body;
|
|
30
|
-
if (
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
139
|
+
if (dataStr !== undefined && dataStr !== '') {
|
|
140
|
+
// Try JSON first
|
|
141
|
+
try {
|
|
142
|
+
body = JSON.parse(dataStr);
|
|
143
|
+
}
|
|
144
|
+
catch {
|
|
145
|
+
// Try form-encoded: key=value&key=value
|
|
146
|
+
if (dataStr.includes('=') && !dataStr.includes('{')) {
|
|
147
|
+
const formData = {};
|
|
148
|
+
for (const pair of dataStr.split('&')) {
|
|
149
|
+
const eqIdx = pair.indexOf('=');
|
|
150
|
+
if (eqIdx > 0) {
|
|
151
|
+
formData[decodeURIComponent(pair.slice(0, eqIdx))] = decodeURIComponent(pair.slice(eqIdx + 1));
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
body = formData;
|
|
34
155
|
}
|
|
35
|
-
|
|
36
|
-
body =
|
|
156
|
+
else {
|
|
157
|
+
body = dataStr;
|
|
37
158
|
}
|
|
38
159
|
}
|
|
39
|
-
else {
|
|
40
|
-
body = parsed.data;
|
|
41
|
-
}
|
|
42
160
|
}
|
|
43
|
-
return { method, path, query, headers, body };
|
|
161
|
+
return { method: method.toUpperCase(), path, query, headers: filteredHeaders, body };
|
|
44
162
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "runwork",
|
|
3
|
-
"version": "0.6.
|
|
3
|
+
"version": "0.6.2",
|
|
4
4
|
"description": "CLI for Runwork: develop, preview, and deploy Runwork apps from your local machine.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"author": "Runwork <info@runwork.ai> (https://www.runwork.ai)",
|
|
@@ -46,7 +46,6 @@
|
|
|
46
46
|
"better-sqlite3": "^12.8.0",
|
|
47
47
|
"chokidar": "^4.0.0",
|
|
48
48
|
"commander": "^13.0.0",
|
|
49
|
-
"curlconverter": "^4.12.0",
|
|
50
49
|
"fflate": "^0.8.2",
|
|
51
50
|
"open": "^10.0.0",
|
|
52
51
|
"picocolors": "^1.1.1",
|