@moxxy/cli 0.0.0 → 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/README.md +228 -0
- package/dist/bin.js +55943 -50844
- package/dist/bin.js.map +1 -1
- package/dist/read-handler.js +1 -0
- package/dist/read-handler.js.map +1 -1
- package/dist/sidecar.js +276 -0
- package/dist/sidecar.js.map +1 -0
- package/dist/skills/create-workflow.md +71 -0
- package/package.json +57 -24
package/dist/read-handler.js
CHANGED
package/dist/read-handler.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../tools-builtin/src/util.ts","../../tools-builtin/src/read-handler.ts"],"names":["fs"],"mappings":"
|
|
1
|
+
{"version":3,"sources":["../../tools-builtin/src/util.ts","../../tools-builtin/src/read-handler.ts"],"names":["fs"],"mappings":";;;;;;AAeO,SAAS,WAAA,CAAY,KAAa,MAAA,EAAwB;AAC/D,EAAA,IAAS,IAAA,CAAA,UAAA,CAAW,MAAM,CAAA,EAAG,OAAY,eAAU,MAAM,CAAA;AACzD,EAAA,OAAY,IAAA,CAAA,OAAA,CAAQ,KAAK,MAAM,CAAA;AACjC;AAuBO,IAAM,WAAA,GAAc,WAAA;AAEpB,SAAS,WAAA,CAAY,GAAW,GAAA,EAAqB;AAC1D,EAAA,IAAI,CAAA,CAAE,MAAA,IAAU,GAAA,EAAK,OAAO,CAAA;AAC5B,EAAA,OAAO,CAAA,CAAE,KAAA,CAAM,CAAA,EAAG,GAAG,CAAA,GAAI;AAAA,eAAA,EAAoB,CAAA,CAAE,SAAS,GAAG,CAAA,OAAA,CAAA;AAC7D;;;ACrBA,eAAsB,WAAA,CAAY,OAAkB,GAAA,EAAmC;AACrF,EAAA,MAAM,EAAE,SAAA,EAAW,MAAA,GAAS,CAAA,EAAG,KAAA,GAAQ,KAAK,GAAI,KAAA;AAChD,EAAA,MAAM,QAAA,GAAW,WAAA,CAAY,GAAA,CAAI,GAAA,EAAK,SAAS,CAAA;AAO/C,EAAA,MAAM,OAAO,GAAA,CAAI,EAAA,GACb,MAAM,GAAA,CAAI,EAAA,CAAG,SAAS,QAAA,EAAU,EAAE,UAAU,MAAA,EAAQ,KACnD,MAAMA,QAAA,CAAG,SAAS,QAAQ,CAAA,EAAG,SAAS,MAAM,CAAA;AACjD,EAAA,MAAM,KAAA,GAAQ,IAAA,CAAK,KAAA,CAAM,IAAI,CAAA;AAC7B,EAAA,MAAM,MAAA,GAAS,KAAA,CAAM,KAAA,CAAM,MAAA,EAAQ,SAAS,KAAK,CAAA;AACjD,EAAA,MAAM,QAAA,GAAW,OACd,GAAA,CAAI,CAAC,MAAM,CAAA,KAAM,CAAA,EAAG,OAAO,MAAA,GAAS,CAAA,GAAI,CAAC,CAAA,CAAE,QAAA,CAAS,GAAG,GAAG,CAAC,IAAK,IAAI,CAAA,CAAE,CAAA,CACtE,IAAA,CAAK,IAAI,CAAA;AACZ,EAAA,OAAO,WAAA,CAAY,UAAU,GAAO,CAAA;AACtC","file":"read-handler.js","sourcesContent":["import * as path from 'node:path';\nimport { MoxxyError } from '@moxxy/sdk';\n\n/**\n * Normalize `target` against `cwd`. Returns an absolute path. **Does not\n * sandbox** — absolute targets and `../` traversal are allowed by design,\n * because the agent often needs to touch paths outside the cwd (`~/.config`,\n * `/etc/...`). Safety against unintended access lives at the permission\n * layer (`PermissionEngine` + the resolver), which prompts the user before\n * any tool runs. Tools that genuinely need to confine to cwd should use\n * `resolveWithinCwd` instead.\n *\n * Renamed from `resolveSafe` to make the contract honest — the old name\n * implied a sandbox it never performed.\n */\nexport function resolvePath(cwd: string, target: string): string {\n if (path.isAbsolute(target)) return path.normalize(target);\n return path.resolve(cwd, target);\n}\n\n/**\n * Like `resolvePath` but throws if the result escapes `cwd`. Use for tools\n * that should be strictly confined (rare).\n */\nexport function resolveWithinCwd(cwd: string, target: string): string {\n const resolved = resolvePath(cwd, target);\n const cwdAbs = path.resolve(cwd);\n const rel = path.relative(cwdAbs, resolved);\n if (rel.startsWith('..') || path.isAbsolute(rel)) {\n throw new MoxxyError({\n code: 'TOOL_ERROR',\n message: `Path escapes cwd: ${target} (resolved to ${resolved}, outside ${cwdAbs})`,\n });\n }\n return resolved;\n}\n\n/**\n * @deprecated Use `resolvePath` (no behavior change — only honest name).\n * Kept as a thin alias so external callers still compile.\n */\nexport const resolveSafe = resolvePath;\n\nexport function clampString(s: string, max: number): string {\n if (s.length <= max) return s;\n return s.slice(0, max) + `\\n... [truncated ${s.length - max} chars]`;\n}\n\n/**\n * Convert a glob pattern (`**`, `*`, `?`) to an anchored RegExp. Shared by\n * the Glob and Grep tools; not exposed externally.\n */\nexport function globToRegExp(pattern: string): RegExp {\n const escaped = pattern\n .replace(/[.+^${}()|[\\]\\\\]/g, '\\\\$&')\n .replace(/\\?/g, '[^/]')\n .replace(/\\*\\*\\//g, '(?:.*/)?')\n .replace(/\\*\\*/g, '.*')\n .replace(/\\*/g, '[^/]*');\n return new RegExp('^' + escaped + '$');\n}\n","import { promises as fs } from 'node:fs';\nimport type { BrokeredFs } from '@moxxy/sdk';\nimport { clampString, resolveSafe } from './util.js';\n\n/**\n * Pure handler module for the Read tool. Lives in its own file so the\n * worker_threads isolator (`@moxxy/isolator-worker`) can re-import it\n * on the worker side via the `handlerModule` reference declared in\n * `read.ts`.\n *\n * Closures can't cross thread boundaries; module exports can.\n */\nexport interface ReadInput {\n readonly file_path: string;\n readonly offset?: number;\n readonly limit?: number;\n}\n\nexport interface ReadCtxLike {\n readonly cwd: string;\n /** Capability-mediated fs. Present when invoked under an isolator that\n * brokers (`@moxxy/isolator-worker`); absent under `none` / `inproc`. */\n readonly fs?: BrokeredFs;\n}\n\nexport async function readHandler(input: ReadInput, ctx: ReadCtxLike): Promise<string> {\n const { file_path, offset = 0, limit = 2000 } = input;\n const resolved = resolveSafe(ctx.cwd, file_path);\n // Use the brokered fs when the isolator provides one. The broker\n // re-validates the path against the tool's declared `caps.fs.read`\n // on the parent side, so reads outside the cap are denied at the\n // boundary regardless of what's in the input. Without a broker\n // (inproc / none), fall back to direct `node:fs` — input-level\n // cap-check already screened the file_path.\n const text = ctx.fs\n ? await ctx.fs.readFile(resolved, { encoding: 'utf8' })\n : (await fs.readFile(resolved)).toString('utf8');\n const lines = text.split('\\n');\n const sliced = lines.slice(offset, offset + limit);\n const numbered = sliced\n .map((line, i) => `${String(offset + i + 1).padStart(6, ' ')}\\t${line}`)\n .join('\\n');\n return clampString(numbered, 200_000);\n}\n"]}
|
package/dist/sidecar.js
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { createRequire } from 'node:module';
|
|
3
|
+
import * as readline from 'readline';
|
|
4
|
+
import { spawn } from 'child_process';
|
|
5
|
+
|
|
6
|
+
createRequire(import.meta.url);
|
|
7
|
+
async function importPlaywright() {
|
|
8
|
+
try {
|
|
9
|
+
return await import('playwright');
|
|
10
|
+
} catch (err) {
|
|
11
|
+
const e = new Error(
|
|
12
|
+
`Playwright is not installed. Run \`pnpm add playwright\` (or \`npm i playwright\`) and then \`npx playwright install\` in the moxxy install dir.
|
|
13
|
+
Underlying: ${err instanceof Error ? err.message : String(err)}`
|
|
14
|
+
);
|
|
15
|
+
e.kind = "init";
|
|
16
|
+
throw e;
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
async function launchWithAutoInstall(browserType, which, headless) {
|
|
20
|
+
try {
|
|
21
|
+
return { handle: await launchOnce(browserType, headless), installNotice: null };
|
|
22
|
+
} catch (err) {
|
|
23
|
+
if (!isMissingBrowserError(err)) throw err;
|
|
24
|
+
process.stderr.write(
|
|
25
|
+
`moxxy-browser: ${which} binary missing, running \`npx playwright install ${which}\` (one-time, ~150MB). This may take a minute\u2026
|
|
26
|
+
`
|
|
27
|
+
);
|
|
28
|
+
try {
|
|
29
|
+
await runPlaywrightInstall(which);
|
|
30
|
+
} catch (installErr) {
|
|
31
|
+
const msg = installErr instanceof Error ? installErr.message : String(installErr);
|
|
32
|
+
const e = new Error(
|
|
33
|
+
`Playwright browser auto-install failed: ${msg}. Run \`npx playwright install ${which}\` manually in the moxxy dir.`
|
|
34
|
+
);
|
|
35
|
+
e.kind = "init";
|
|
36
|
+
throw e;
|
|
37
|
+
}
|
|
38
|
+
process.stderr.write(`moxxy-browser: install complete, retrying launch
|
|
39
|
+
`);
|
|
40
|
+
return {
|
|
41
|
+
handle: await launchOnce(browserType, headless),
|
|
42
|
+
installNotice: `Auto-installed Playwright ${which} browser (~150MB, one-time).`
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async function launchOnce(browserType, headless) {
|
|
47
|
+
const browser = await browserType.launch({ headless });
|
|
48
|
+
const context = await browser.newContext();
|
|
49
|
+
const page = await context.newPage();
|
|
50
|
+
return { browser, context, page };
|
|
51
|
+
}
|
|
52
|
+
function isMissingBrowserError(err) {
|
|
53
|
+
if (!(err instanceof Error)) return false;
|
|
54
|
+
return /Executable doesn'?t exist at/i.test(err.message);
|
|
55
|
+
}
|
|
56
|
+
function runPlaywrightInstall(which) {
|
|
57
|
+
return new Promise((resolve, reject) => {
|
|
58
|
+
const child = spawn("npx", ["playwright", "install", which], {
|
|
59
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
60
|
+
});
|
|
61
|
+
let stderrTail = "";
|
|
62
|
+
child.stdout.on("data", (chunk) => process.stderr.write(chunk));
|
|
63
|
+
child.stderr.on("data", (chunk) => {
|
|
64
|
+
process.stderr.write(chunk);
|
|
65
|
+
stderrTail += chunk.toString("utf8");
|
|
66
|
+
if (stderrTail.length > 4e3) stderrTail = stderrTail.slice(-4e3);
|
|
67
|
+
});
|
|
68
|
+
child.once("error", (err) => reject(err));
|
|
69
|
+
child.once("close", (code) => {
|
|
70
|
+
if (code === 0) resolve();
|
|
71
|
+
else reject(new Error(`exit ${code}: ${stderrTail.trim() || "(no stderr)"}`));
|
|
72
|
+
});
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// ../plugin-browser/src/sidecar/types.ts
|
|
77
|
+
function errMsg(err) {
|
|
78
|
+
return err instanceof Error ? err.message : String(err);
|
|
79
|
+
}
|
|
80
|
+
function badParams(msg) {
|
|
81
|
+
const e = new Error(msg);
|
|
82
|
+
e.kind = "runtime";
|
|
83
|
+
return e;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// ../plugin-browser/src/sidecar/dispatch.ts
|
|
87
|
+
async function ensurePlaywright(state2, opts) {
|
|
88
|
+
if (state2.handle) return state2.handle;
|
|
89
|
+
const pw = await importPlaywright();
|
|
90
|
+
const which = opts.browser ?? "chromium";
|
|
91
|
+
const browserType = pw[which];
|
|
92
|
+
const { handle, installNotice } = await launchWithAutoInstall(browserType, which, opts.headless ?? true);
|
|
93
|
+
state2.handle = handle;
|
|
94
|
+
if (installNotice) state2.pendingInstallNotice = installNotice;
|
|
95
|
+
return state2.handle;
|
|
96
|
+
}
|
|
97
|
+
async function teardown(state2) {
|
|
98
|
+
if (!state2.handle) return;
|
|
99
|
+
try {
|
|
100
|
+
await state2.handle.context.close();
|
|
101
|
+
await state2.handle.browser.close();
|
|
102
|
+
} catch {
|
|
103
|
+
}
|
|
104
|
+
state2.handle = null;
|
|
105
|
+
}
|
|
106
|
+
async function dispatch(state2, req) {
|
|
107
|
+
try {
|
|
108
|
+
return await dispatchInner(state2, req);
|
|
109
|
+
} catch (err) {
|
|
110
|
+
const kind = err.kind;
|
|
111
|
+
return {
|
|
112
|
+
id: req.id,
|
|
113
|
+
ok: false,
|
|
114
|
+
error: { message: errMsg(err), kind: kind ?? "unknown" }
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
async function dispatchInner(state2, req) {
|
|
119
|
+
switch (req.method) {
|
|
120
|
+
case "init": {
|
|
121
|
+
const opts = req.params ?? {};
|
|
122
|
+
await ensurePlaywright(state2, opts);
|
|
123
|
+
return { id: req.id, ok: true, result: { ready: true } };
|
|
124
|
+
}
|
|
125
|
+
case "goto": {
|
|
126
|
+
const h = await ensurePlaywright(state2, {});
|
|
127
|
+
const { url, waitUntil, timeoutMs } = req.params ?? {};
|
|
128
|
+
if (!url) throw badParams("url is required");
|
|
129
|
+
if (!/^https?:\/\//i.test(url)) throw badParams("only http(s) URLs allowed");
|
|
130
|
+
try {
|
|
131
|
+
await h.page.goto(url, { waitUntil: waitUntil ?? "domcontentloaded", timeout: timeoutMs ?? 3e4 });
|
|
132
|
+
} catch (err) {
|
|
133
|
+
return { id: req.id, ok: false, error: { message: errMsg(err), kind: "navigation" } };
|
|
134
|
+
}
|
|
135
|
+
return { id: req.id, ok: true, result: { url: h.page.url() } };
|
|
136
|
+
}
|
|
137
|
+
case "click": {
|
|
138
|
+
const h = await ensurePlaywright(state2, {});
|
|
139
|
+
const { selector, timeoutMs } = req.params ?? {};
|
|
140
|
+
if (!selector) throw badParams("selector is required");
|
|
141
|
+
await h.page.click(selector, { timeout: timeoutMs ?? 1e4 });
|
|
142
|
+
return { id: req.id, ok: true };
|
|
143
|
+
}
|
|
144
|
+
case "fill": {
|
|
145
|
+
const h = await ensurePlaywright(state2, {});
|
|
146
|
+
const { selector, value, timeoutMs } = req.params ?? {};
|
|
147
|
+
if (!selector) throw badParams("selector is required");
|
|
148
|
+
await h.page.fill(selector, value ?? "", { timeout: timeoutMs ?? 1e4 });
|
|
149
|
+
return { id: req.id, ok: true };
|
|
150
|
+
}
|
|
151
|
+
case "text": {
|
|
152
|
+
const h = await ensurePlaywright(state2, {});
|
|
153
|
+
const { selector } = req.params ?? {};
|
|
154
|
+
if (selector) {
|
|
155
|
+
const text2 = await h.page.textContent(selector);
|
|
156
|
+
return { id: req.id, ok: true, result: text2 ?? "" };
|
|
157
|
+
}
|
|
158
|
+
const text = await h.page.evaluate('document.body ? document.body.innerText : ""');
|
|
159
|
+
return { id: req.id, ok: true, result: text };
|
|
160
|
+
}
|
|
161
|
+
case "html": {
|
|
162
|
+
const h = await ensurePlaywright(state2, {});
|
|
163
|
+
const html = await h.page.content();
|
|
164
|
+
return { id: req.id, ok: true, result: html };
|
|
165
|
+
}
|
|
166
|
+
case "screenshot": {
|
|
167
|
+
const h = await ensurePlaywright(state2, {});
|
|
168
|
+
const { fullPage } = req.params ?? {};
|
|
169
|
+
const buf = await h.page.screenshot({ fullPage: fullPage ?? false });
|
|
170
|
+
return { id: req.id, ok: true, result: { mediaType: "image/png", base64: buf.toString("base64") } };
|
|
171
|
+
}
|
|
172
|
+
case "eval": {
|
|
173
|
+
const h = await ensurePlaywright(state2, {});
|
|
174
|
+
const { expression } = req.params ?? {};
|
|
175
|
+
if (!expression) throw badParams("expression is required");
|
|
176
|
+
const value = await h.page.evaluate(expression);
|
|
177
|
+
return { id: req.id, ok: true, result: value };
|
|
178
|
+
}
|
|
179
|
+
case "url": {
|
|
180
|
+
const h = await ensurePlaywright(state2, {});
|
|
181
|
+
return { id: req.id, ok: true, result: h.page.url() };
|
|
182
|
+
}
|
|
183
|
+
case "close": {
|
|
184
|
+
await teardown(state2);
|
|
185
|
+
return { id: req.id, ok: true };
|
|
186
|
+
}
|
|
187
|
+
default:
|
|
188
|
+
return {
|
|
189
|
+
id: req.id,
|
|
190
|
+
ok: false,
|
|
191
|
+
error: { message: `unknown method: ${req.method}`, kind: "runtime" }
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ../plugin-browser/src/sidecar.ts
|
|
197
|
+
var state = { handle: null, pendingInstallNotice: null };
|
|
198
|
+
function write(reply) {
|
|
199
|
+
if (state.pendingInstallNotice) {
|
|
200
|
+
if (reply.ok) {
|
|
201
|
+
reply = { ...reply, notice: state.pendingInstallNotice };
|
|
202
|
+
} else {
|
|
203
|
+
reply = {
|
|
204
|
+
...reply,
|
|
205
|
+
error: {
|
|
206
|
+
...reply.error,
|
|
207
|
+
message: `${state.pendingInstallNotice} Then: ${reply.error.message}`
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
state.pendingInstallNotice = null;
|
|
212
|
+
}
|
|
213
|
+
process.stdout.write(JSON.stringify(reply) + "\n");
|
|
214
|
+
}
|
|
215
|
+
async function shutdownAndExit(code) {
|
|
216
|
+
await teardown(state);
|
|
217
|
+
process.exit(code);
|
|
218
|
+
}
|
|
219
|
+
function startParentWatchdog() {
|
|
220
|
+
const parentPid = process.ppid;
|
|
221
|
+
if (!parentPid || parentPid <= 1) return;
|
|
222
|
+
const interval = setInterval(() => {
|
|
223
|
+
try {
|
|
224
|
+
process.kill(parentPid, 0);
|
|
225
|
+
} catch (err) {
|
|
226
|
+
const code = err.code;
|
|
227
|
+
if (code === "ESRCH") {
|
|
228
|
+
clearInterval(interval);
|
|
229
|
+
void shutdownAndExit(0);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}, 2e3);
|
|
233
|
+
interval.unref?.();
|
|
234
|
+
}
|
|
235
|
+
var queue = Promise.resolve();
|
|
236
|
+
async function main() {
|
|
237
|
+
startParentWatchdog();
|
|
238
|
+
const rl = readline.createInterface({ input: process.stdin });
|
|
239
|
+
rl.on("line", (line) => {
|
|
240
|
+
if (!line.trim()) return;
|
|
241
|
+
let req;
|
|
242
|
+
try {
|
|
243
|
+
req = JSON.parse(line);
|
|
244
|
+
} catch {
|
|
245
|
+
write({ id: "unknown", ok: false, error: { message: "invalid JSON", kind: "runtime" } });
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
if (!req.id || !req.method) {
|
|
249
|
+
write({
|
|
250
|
+
id: req.id ?? "unknown",
|
|
251
|
+
ok: false,
|
|
252
|
+
error: { message: "request requires { id, method }", kind: "runtime" }
|
|
253
|
+
});
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
queue = queue.then(async () => {
|
|
257
|
+
const reply = await dispatch(state, req);
|
|
258
|
+
write(reply);
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
rl.once("close", () => {
|
|
262
|
+
void shutdownAndExit(0);
|
|
263
|
+
});
|
|
264
|
+
process.stdout.on("error", (err) => {
|
|
265
|
+
if (err.code === "EPIPE") {
|
|
266
|
+
void shutdownAndExit(0);
|
|
267
|
+
}
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
main().catch((err) => {
|
|
271
|
+
process.stderr.write(`sidecar fatal: ${errMsg(err)}
|
|
272
|
+
`);
|
|
273
|
+
void shutdownAndExit(1);
|
|
274
|
+
});
|
|
275
|
+
//# sourceMappingURL=sidecar.js.map
|
|
276
|
+
//# sourceMappingURL=sidecar.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../plugin-browser/src/sidecar/install.ts","../../plugin-browser/src/sidecar/types.ts","../../plugin-browser/src/sidecar/dispatch.ts","../../plugin-browser/src/sidecar.ts"],"names":["state","text"],"mappings":"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","file":"sidecar.js","sourcesContent":["/**\n * Playwright lifecycle: import, launch, and one-shot auto-install of the\n * per-browser binary. Keeps the dispatch layer free of node:child_process\n * + Playwright-import noise.\n */\n\nimport { spawn } from 'node:child_process';\nimport type { BrowserKind, BrowserType, PageHandle, PlaywrightHandle } from './types.js';\n\nexport interface LaunchResult {\n handle: PlaywrightHandle;\n /** Set when the browser binary was auto-downloaded during this launch. */\n installNotice: string | null;\n}\n\nexport async function importPlaywright(): Promise<{\n chromium: BrowserType;\n firefox: BrowserType;\n webkit: BrowserType;\n}> {\n try {\n return (await import('playwright')) as never;\n } catch (err) {\n const e = new Error(\n `Playwright is not installed. Run \\`pnpm add playwright\\` (or \\`npm i playwright\\`) and then \\`npx playwright install\\` in the moxxy install dir.\\n` +\n `Underlying: ${err instanceof Error ? err.message : String(err)}`,\n );\n (e as Error & { kind?: string }).kind = 'init';\n throw e;\n }\n}\n\n/**\n * Try to launch the browser. If the binary isn't downloaded yet\n * (Playwright distinguishes the npm install from the per-browser\n * binary download), run `npx playwright install <which>` once and\n * retry. The install can take 30s–2min on the first run depending on\n * connection; we surface progress on stderr (parent forwards to the\n * logger) and return a one-shot notice for the first tool response.\n */\nexport async function launchWithAutoInstall(\n browserType: BrowserType,\n which: BrowserKind,\n headless: boolean,\n): Promise<LaunchResult> {\n try {\n return { handle: await launchOnce(browserType, headless), installNotice: null };\n } catch (err) {\n if (!isMissingBrowserError(err)) throw err;\n process.stderr.write(\n `moxxy-browser: ${which} binary missing, running \\`npx playwright install ${which}\\` ` +\n `(one-time, ~150MB). This may take a minute…\\n`,\n );\n try {\n await runPlaywrightInstall(which);\n } catch (installErr) {\n const msg = installErr instanceof Error ? installErr.message : String(installErr);\n const e = new Error(\n `Playwright browser auto-install failed: ${msg}. ` +\n `Run \\`npx playwright install ${which}\\` manually in the moxxy dir.`,\n );\n (e as Error & { kind?: string }).kind = 'init';\n throw e;\n }\n process.stderr.write(`moxxy-browser: install complete, retrying launch\\n`);\n return {\n handle: await launchOnce(browserType, headless),\n installNotice: `Auto-installed Playwright ${which} browser (~150MB, one-time).`,\n };\n }\n}\n\nasync function launchOnce(browserType: BrowserType, headless: boolean): Promise<PlaywrightHandle> {\n const browser = await browserType.launch({ headless });\n const context = (await browser.newContext()) as PlaywrightHandle['context'];\n const page = (await context.newPage()) as unknown as PageHandle;\n return { browser, context, page };\n}\n\nfunction isMissingBrowserError(err: unknown): boolean {\n if (!(err instanceof Error)) return false;\n // Playwright's \"Executable doesn't exist at …\" launch error fires\n // when the npm package is installed but the per-browser binary\n // hasn't been downloaded. The message stays stable across versions.\n return /Executable doesn'?t exist at/i.test(err.message);\n}\n\n/**\n * Run `npx playwright install <which>` and stream its output to the\n * sidecar's stderr so the operator can watch progress. Resolves on\n * exit-0; rejects with the tail of stderr otherwise.\n */\nfunction runPlaywrightInstall(which: BrowserKind): Promise<void> {\n return new Promise((resolve, reject) => {\n const child = spawn('npx', ['playwright', 'install', which], {\n stdio: ['ignore', 'pipe', 'pipe'],\n });\n let stderrTail = '';\n child.stdout.on('data', (chunk: Buffer) => process.stderr.write(chunk));\n child.stderr.on('data', (chunk: Buffer) => {\n process.stderr.write(chunk);\n stderrTail += chunk.toString('utf8');\n if (stderrTail.length > 4000) stderrTail = stderrTail.slice(-4000);\n });\n child.once('error', (err) => reject(err));\n child.once('close', (code) => {\n if (code === 0) resolve();\n else reject(new Error(`exit ${code}: ${stderrTail.trim() || '(no stderr)'}`));\n });\n });\n}\n","export type BrowserKind = 'chromium' | 'firefox' | 'webkit';\n\nexport type ErrorKind = 'init' | 'navigation' | 'runtime' | 'timeout' | 'unknown';\n\nexport interface Req {\n readonly id: string;\n readonly method: string;\n readonly params?: Record<string, unknown>;\n}\n\nexport interface Ok {\n readonly id: string;\n readonly ok: true;\n readonly result?: unknown;\n /** One-shot human-readable note (e.g. \"Auto-installed Chromium\"). */\n readonly notice?: string;\n}\n\nexport interface Err {\n readonly id: string;\n readonly ok: false;\n readonly error: { message: string; kind: ErrorKind };\n}\n\nexport type Reply = Ok | Err;\n\nexport interface BrowserType {\n launch(opts: {\n headless: boolean;\n }): Promise<{ close(): Promise<void>; newContext(): Promise<unknown> }>;\n}\n\nexport interface PageHandle {\n goto(url: string, opts?: unknown): Promise<unknown>;\n click(selector: string, opts?: unknown): Promise<void>;\n fill(selector: string, value: string, opts?: unknown): Promise<void>;\n textContent(selector: string): Promise<string | null>;\n content(): Promise<string>;\n screenshot(opts?: unknown): Promise<Buffer>;\n evaluate(fn: string): Promise<unknown>;\n url(): string;\n close(): Promise<void>;\n}\n\nexport interface PlaywrightHandle {\n // Loosely typed so we can avoid importing the playwright types at compile time —\n // they're an optional peer dependency.\n readonly browser: { close(): Promise<void> };\n readonly context: {\n newPage(): Promise<unknown>;\n close(): Promise<void>;\n };\n readonly page: PageHandle;\n}\n\nexport function errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err);\n}\n\nexport function badParams(msg: string): Error {\n const e = new Error(msg);\n (e as Error & { kind?: string }).kind = 'runtime';\n return e;\n}\n","/**\n * JSON-RPC dispatch table for the sidecar. Each `method` here corresponds\n * one-to-one with the wire-format methods documented in `sidecar.ts`.\n */\n\nimport { importPlaywright, launchWithAutoInstall } from './install.js';\nimport {\n badParams,\n errMsg,\n type BrowserKind,\n type Err,\n type PlaywrightHandle,\n type Reply,\n type Req,\n} from './types.js';\n\nexport interface SidecarState {\n handle: PlaywrightHandle | null;\n /**\n * Set after a successful auto-install of browser binaries so the next\n * tool result can carry a `notice` letting the user/model know the\n * one-time download happened. Cleared once the notice has been\n * delivered (handed to the reply once, then forgotten).\n */\n pendingInstallNotice: string | null;\n}\n\nasync function ensurePlaywright(\n state: SidecarState,\n opts: { browser?: BrowserKind; headless?: boolean },\n): Promise<PlaywrightHandle> {\n if (state.handle) return state.handle;\n const pw = await importPlaywright();\n const which = opts.browser ?? 'chromium';\n const browserType = pw[which];\n const { handle, installNotice } = await launchWithAutoInstall(browserType, which, opts.headless ?? true);\n state.handle = handle;\n if (installNotice) state.pendingInstallNotice = installNotice;\n return state.handle;\n}\n\nexport async function teardown(state: SidecarState): Promise<void> {\n if (!state.handle) return;\n try {\n await state.handle.context.close();\n await state.handle.browser.close();\n } catch {\n /* ignore */\n }\n state.handle = null;\n}\n\nexport async function dispatch(state: SidecarState, req: Req): Promise<Reply> {\n try {\n return await dispatchInner(state, req);\n } catch (err) {\n const kind = (err as Error & { kind?: string }).kind;\n return {\n id: req.id,\n ok: false,\n error: { message: errMsg(err), kind: (kind as Err['error']['kind']) ?? 'unknown' },\n };\n }\n}\n\nasync function dispatchInner(state: SidecarState, req: Req): Promise<Reply> {\n switch (req.method) {\n case 'init': {\n const opts = (req.params ?? {}) as { browser?: BrowserKind; headless?: boolean };\n await ensurePlaywright(state, opts);\n return { id: req.id, ok: true, result: { ready: true } };\n }\n case 'goto': {\n const h = await ensurePlaywright(state, {});\n const { url, waitUntil, timeoutMs } = (req.params ?? {}) as {\n url: string;\n waitUntil?: 'load' | 'domcontentloaded' | 'networkidle';\n timeoutMs?: number;\n };\n if (!url) throw badParams('url is required');\n // Defence-in-depth: the tool schema already restricts goto to http(s),\n // but the sidecar is a distinct process driven over JSON-RPC, so re-check\n // here rather than trust the caller to have validated. Blocks file:// /\n // javascript: navigation.\n if (!/^https?:\\/\\//i.test(url)) throw badParams('only http(s) URLs allowed');\n try {\n await h.page.goto(url, { waitUntil: waitUntil ?? 'domcontentloaded', timeout: timeoutMs ?? 30_000 });\n } catch (err) {\n return { id: req.id, ok: false, error: { message: errMsg(err), kind: 'navigation' } };\n }\n return { id: req.id, ok: true, result: { url: h.page.url() } };\n }\n case 'click': {\n const h = await ensurePlaywright(state, {});\n const { selector, timeoutMs } = (req.params ?? {}) as { selector: string; timeoutMs?: number };\n if (!selector) throw badParams('selector is required');\n await h.page.click(selector, { timeout: timeoutMs ?? 10_000 });\n return { id: req.id, ok: true };\n }\n case 'fill': {\n const h = await ensurePlaywright(state, {});\n const { selector, value, timeoutMs } = (req.params ?? {}) as {\n selector: string;\n value: string;\n timeoutMs?: number;\n };\n if (!selector) throw badParams('selector is required');\n await h.page.fill(selector, value ?? '', { timeout: timeoutMs ?? 10_000 });\n return { id: req.id, ok: true };\n }\n case 'text': {\n const h = await ensurePlaywright(state, {});\n const { selector } = (req.params ?? {}) as { selector?: string };\n if (selector) {\n const text = await h.page.textContent(selector);\n return { id: req.id, ok: true, result: text ?? '' };\n }\n // Whole-document text via evaluate\n const text = (await h.page.evaluate('document.body ? document.body.innerText : \"\"')) as string;\n return { id: req.id, ok: true, result: text };\n }\n case 'html': {\n const h = await ensurePlaywright(state, {});\n const html = await h.page.content();\n return { id: req.id, ok: true, result: html };\n }\n case 'screenshot': {\n const h = await ensurePlaywright(state, {});\n const { fullPage } = (req.params ?? {}) as { fullPage?: boolean };\n const buf = await h.page.screenshot({ fullPage: fullPage ?? false });\n return { id: req.id, ok: true, result: { mediaType: 'image/png', base64: buf.toString('base64') } };\n }\n case 'eval': {\n const h = await ensurePlaywright(state, {});\n const { expression } = (req.params ?? {}) as { expression: string };\n if (!expression) throw badParams('expression is required');\n const value = await h.page.evaluate(expression);\n return { id: req.id, ok: true, result: value };\n }\n case 'url': {\n const h = await ensurePlaywright(state, {});\n return { id: req.id, ok: true, result: h.page.url() };\n }\n case 'close': {\n await teardown(state);\n return { id: req.id, ok: true };\n }\n default:\n return {\n id: req.id,\n ok: false,\n error: { message: `unknown method: ${req.method}`, kind: 'runtime' },\n };\n }\n}\n","#!/usr/bin/env node\n/**\n * Playwright sidecar — owns a single browser context. Speaks newline-delimited\n * JSON-RPC over stdio so the parent (`browser_session` tool) doesn't load\n * Playwright into its own process. Crash isolation, lazy install: the parent\n * only spawns this when the heavy tier is actually needed.\n *\n * Wire format (one line per message):\n * { \"id\": \"uuid\", \"method\": \"goto\"|\"click\"|\"fill\"|\"text\"|\"html\"|\"screenshot\"|\"eval\"|\"close\", \"params\": {...} }\n * { \"id\": \"uuid\", \"ok\": true, \"result\": ... }\n * { \"id\": \"uuid\", \"ok\": false, \"error\": { \"message\": \"...\", \"kind\": \"init\"|\"navigation\"|\"runtime\"|\"timeout\" } }\n *\n * Run with `node dist/sidecar.js` (the package's `bin` entry is\n * `moxxy-browser-sidecar`). Parent terminates by closing stdin or sending\n * `{method:'close'}`.\n */\n\nimport * as readline from 'node:readline';\nimport { dispatch, teardown, type SidecarState } from './sidecar/dispatch.js';\nimport { errMsg, type Reply, type Req } from './sidecar/types.js';\n\nconst state: SidecarState = { handle: null, pendingInstallNotice: null };\n\nfunction write(reply: Reply): void {\n // Drain the install-notice flag into the first reply that goes out\n // after the install completed, then clear it. Errors get the notice\n // too — sometimes the launch retry surfaces a different problem and\n // the user still wants to know we tried to install.\n if (state.pendingInstallNotice) {\n if (reply.ok) {\n reply = { ...reply, notice: state.pendingInstallNotice };\n } else {\n reply = {\n ...reply,\n error: {\n ...reply.error,\n message: `${state.pendingInstallNotice} Then: ${reply.error.message}`,\n },\n };\n }\n state.pendingInstallNotice = null;\n }\n process.stdout.write(JSON.stringify(reply) + '\\n');\n}\n\n/**\n * Tears down the browser context AND exits the process. Used both by\n * the explicit `close` RPC path and the parent-loss / stdin-close\n * paths so all cleanup goes through one routine.\n */\nasync function shutdownAndExit(code: number): Promise<void> {\n await teardown(state);\n process.exit(code);\n}\n\n/**\n * Parent watchdog: if the moxxy process that spawned this sidecar\n * disappears (crash, SIGKILL, terminal hangup), there's no stdin EOF\n * to rely on — orphan Chromium would keep running and chew CPU/memory\n * until the user notices. Poll the parent PID every few seconds and\n * self-terminate when it goes away.\n *\n * `process.kill(pid, 0)` is the POSIX trick for \"does this PID\n * exist?\" — no signal is actually delivered. Throws ESRCH when the\n * process is gone (or EPERM when it exists but we can't signal it —\n * still alive, so don't treat as gone).\n */\nfunction startParentWatchdog(): void {\n const parentPid = process.ppid;\n if (!parentPid || parentPid <= 1) return; // already orphaned (init), nothing to watch\n const interval = setInterval(() => {\n try {\n process.kill(parentPid, 0);\n } catch (err) {\n const code = (err as NodeJS.ErrnoException).code;\n if (code === 'ESRCH') {\n clearInterval(interval);\n void shutdownAndExit(0);\n }\n // EPERM means the process exists but we can't signal it — still alive.\n }\n }, 2000);\n interval.unref?.(); // never block the event loop from exiting\n}\n\nlet queue: Promise<void> = Promise.resolve();\n\nasync function main(): Promise<void> {\n startParentWatchdog();\n const rl = readline.createInterface({ input: process.stdin });\n rl.on('line', (line) => {\n if (!line.trim()) return;\n let req: Req;\n try {\n req = JSON.parse(line) as Req;\n } catch {\n write({ id: 'unknown', ok: false, error: { message: 'invalid JSON', kind: 'runtime' } });\n return;\n }\n if (!req.id || !req.method) {\n write({\n id: req.id ?? 'unknown',\n ok: false,\n error: { message: 'request requires { id, method }', kind: 'runtime' },\n });\n return;\n }\n // Sequentially serve requests on the single page. Parent can pipeline by\n // sending more requests; we serialize them inside the sidecar so a goto\n // doesn't race a click.\n queue = queue.then(async () => {\n const reply = await dispatch(state, req);\n write(reply);\n });\n });\n rl.once('close', () => {\n void shutdownAndExit(0);\n });\n // Defensive: if our stdout pipe breaks (parent died mid-write), Node\n // would otherwise throw an uncaught EPIPE on the next write. Treat\n // it as a signal to clean up gracefully instead.\n process.stdout.on('error', (err) => {\n if ((err as NodeJS.ErrnoException).code === 'EPIPE') {\n void shutdownAndExit(0);\n }\n });\n}\n\nmain().catch((err) => {\n process.stderr.write(`sidecar fatal: ${errMsg(err)}\\n`);\n void shutdownAndExit(1);\n});\n"]}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-workflow
|
|
3
|
+
description: Design and save a multi-step workflow — a DAG that chains skills, prompts, and tools into a reusable, schedulable pipeline.
|
|
4
|
+
triggers:
|
|
5
|
+
- create a workflow
|
|
6
|
+
- create a flow
|
|
7
|
+
- build a workflow
|
|
8
|
+
- build a pipeline
|
|
9
|
+
- chain skills
|
|
10
|
+
- automate this
|
|
11
|
+
- set up a flow
|
|
12
|
+
- workflow that
|
|
13
|
+
allowed-tools:
|
|
14
|
+
- workflow_create
|
|
15
|
+
- workflow_validate
|
|
16
|
+
- workflow_list
|
|
17
|
+
- workflow_get
|
|
18
|
+
- workflow_run
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
Use this skill when the user wants a reusable, multi-step pipeline — e.g. "create a flow that fetches Dow Jones news, analyzes it, checks my watchlist, then emails me a digest." A workflow is a DAG: each step runs a **skill**, a free-form **prompt**, a **tool**, or a nested **workflow**, and each step's output pipes into the next.
|
|
22
|
+
|
|
23
|
+
## Pattern
|
|
24
|
+
|
|
25
|
+
1. **Clarify the goal** — the steps, their order, any inputs, and a trigger (on-demand, a schedule, on a file change, or after another workflow). Keep questions to a minimum; infer sensible defaults.
|
|
26
|
+
|
|
27
|
+
2. **Map steps to actions:**
|
|
28
|
+
- Prefer a **named skill** when one already fits a step (`workflow_list` shows skills indirectly; the system prompt lists skills). Otherwise use a **prompt** step.
|
|
29
|
+
- Use a **tool** step for a concrete action — sending a message, calling an API, writing a file. For "email/notify me", use a connected tool (e.g. a Gmail MCP tool) if available; otherwise set `delivery: { channel: inbox }` so the result lands in `~/.moxxy/inbox/`.
|
|
30
|
+
- Express ordering and parallelism with `needs`: steps whose `needs` are all satisfied run **in parallel**. Fan-in by listing several ids in `needs`.
|
|
31
|
+
|
|
32
|
+
3. **Call `workflow_create`** with a one- or two-sentence `intent` describing the whole pipeline. It drafts the YAML, validates it (schema + DAG + conditions), writes it to `~/.moxxy/workflows/`, and registers it. Pass `scope: "project"` ONLY if the user asked to scope it to this repo.
|
|
33
|
+
|
|
34
|
+
4. **Show and offer to run** — summarize the steps and triggers. Offer `workflow_run` for a smoke test. The user can also manage flows from the `/workflows` modal (list, enable/disable, run).
|
|
35
|
+
|
|
36
|
+
## Templating (reference earlier results)
|
|
37
|
+
|
|
38
|
+
- `{{ steps.<id>.output }}` — a prior step's output
|
|
39
|
+
- `{{ inputs.<name>}}` — a declared input
|
|
40
|
+
- `{{ trigger }}`, `{{ now }}`
|
|
41
|
+
|
|
42
|
+
## Conditions (`when:`)
|
|
43
|
+
|
|
44
|
+
A step runs only if its `when` is true. Grammar: `<ref> contains "x"`, `<ref> == "x"`, `<ref> != "x"`, `<ref> is empty`, `<ref> is not empty`, joined by `and` / `or`. Example: `when: '{{ steps.check.output }} contains "ALERT"'`.
|
|
45
|
+
|
|
46
|
+
## Example shape
|
|
47
|
+
|
|
48
|
+
```yaml
|
|
49
|
+
name: stock-market-digest
|
|
50
|
+
description: Weekday mornings — Dow Jones news → sentiment → watchlist → email.
|
|
51
|
+
on:
|
|
52
|
+
schedule: { cron: "0 8 * * 1-5", timeZone: "America/New_York" }
|
|
53
|
+
steps:
|
|
54
|
+
- id: fetch_news
|
|
55
|
+
skill: web-research
|
|
56
|
+
input: "Fetch today's market-moving headlines from Dow Jones / WSJ."
|
|
57
|
+
- id: analyze
|
|
58
|
+
needs: [fetch_news]
|
|
59
|
+
prompt: "Analyze sentiment & notable movers:\n{{ steps.fetch_news.output }}"
|
|
60
|
+
- id: email
|
|
61
|
+
needs: [analyze]
|
|
62
|
+
when: '{{ steps.analyze.output }} is not empty'
|
|
63
|
+
tool: gmail_send
|
|
64
|
+
args: { to: "me", subject: "Daily Market Digest", body: "{{ steps.analyze.output }}" }
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Tips
|
|
68
|
+
|
|
69
|
+
- A workflow with no `on:` block is on-demand only (run it with `workflow_run` or `/workflows run <name>`).
|
|
70
|
+
- Set `onError: continue` on a non-critical step so one failure doesn't abort the whole pipeline; use `retries: N` for flaky steps.
|
|
71
|
+
- To edit by hand instead, tell the user to run `/workflows new <name>` for a starter file, or `/workflows edit <name>` to find the path.
|
package/package.json
CHANGED
|
@@ -1,30 +1,56 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@moxxy/cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"description": "moxxy command-line binary. Subcommand dispatcher consuming the moxxy SDK.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"moxxy",
|
|
7
|
+
"agent",
|
|
8
|
+
"agents",
|
|
9
|
+
"agent-framework",
|
|
10
|
+
"ai",
|
|
11
|
+
"llm",
|
|
12
|
+
"anthropic",
|
|
13
|
+
"claude",
|
|
14
|
+
"openai",
|
|
15
|
+
"gpt",
|
|
16
|
+
"cli",
|
|
17
|
+
"tui",
|
|
18
|
+
"terminal",
|
|
19
|
+
"ink",
|
|
20
|
+
"mcp",
|
|
21
|
+
"tool-use",
|
|
22
|
+
"voice",
|
|
23
|
+
"telegram"
|
|
24
|
+
],
|
|
25
|
+
"homepage": "https://moxxy.ai",
|
|
26
|
+
"bugs": {
|
|
27
|
+
"url": "https://github.com/moxxy-ai/moxxy/issues"
|
|
28
|
+
},
|
|
29
|
+
"repository": {
|
|
30
|
+
"type": "git",
|
|
31
|
+
"url": "git+https://github.com/moxxy-ai/moxxy.git",
|
|
32
|
+
"directory": "packages/cli"
|
|
33
|
+
},
|
|
34
|
+
"author": "Michal Makowski <michal.makowski97@gmail.com>",
|
|
35
|
+
"license": "MIT",
|
|
36
|
+
"engines": {
|
|
37
|
+
"node": ">=20.10.0"
|
|
38
|
+
},
|
|
5
39
|
"type": "module",
|
|
6
40
|
"main": "./dist/bin.js",
|
|
7
41
|
"bin": {
|
|
8
|
-
"moxxy": "
|
|
42
|
+
"moxxy": "dist/bin.js"
|
|
9
43
|
},
|
|
10
44
|
"files": [
|
|
11
|
-
"dist"
|
|
45
|
+
"dist",
|
|
46
|
+
"README.md"
|
|
12
47
|
],
|
|
13
48
|
"publishConfig": {
|
|
14
49
|
"access": "public"
|
|
15
50
|
},
|
|
16
|
-
"scripts": {
|
|
17
|
-
"build": "tsx scripts/gen-requirements.mts && tsup",
|
|
18
|
-
"prepack": "node scripts/prepack.mjs",
|
|
19
|
-
"postpack": "node scripts/postpack.mjs",
|
|
20
|
-
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
21
|
-
"test": "vitest run",
|
|
22
|
-
"clean": "rm -rf dist .turbo",
|
|
23
|
-
"start": "node ./dist/bin.js"
|
|
24
|
-
},
|
|
25
51
|
"dependencies": {
|
|
26
|
-
"
|
|
27
|
-
"
|
|
52
|
+
"zod": "^3.24.0",
|
|
53
|
+
"@moxxy/sdk": "0.1.3"
|
|
28
54
|
},
|
|
29
55
|
"optionalDependencies": {
|
|
30
56
|
"@huggingface/transformers": "^3.0.0",
|
|
@@ -32,15 +58,22 @@
|
|
|
32
58
|
"playwright": "^1.40.0"
|
|
33
59
|
},
|
|
34
60
|
"devDependencies": {
|
|
35
|
-
"@clack/prompts": "
|
|
36
|
-
"@types/node": "
|
|
37
|
-
"@types/react": "
|
|
38
|
-
"ink": "
|
|
61
|
+
"@clack/prompts": "^1.4.0",
|
|
62
|
+
"@types/node": "^22.10.0",
|
|
63
|
+
"@types/react": "^18.3.0",
|
|
64
|
+
"ink": "^5.0.0",
|
|
39
65
|
"punycode": "^2.3.1",
|
|
40
|
-
"react": "
|
|
41
|
-
"tsup": "
|
|
42
|
-
"tsx": "
|
|
43
|
-
"typescript": "
|
|
44
|
-
"vitest": "
|
|
66
|
+
"react": "^18.3.0",
|
|
67
|
+
"tsup": "^8.0.0",
|
|
68
|
+
"tsx": "^4.19.2",
|
|
69
|
+
"typescript": "^5.7.3",
|
|
70
|
+
"vitest": "^2.1.8"
|
|
71
|
+
},
|
|
72
|
+
"scripts": {
|
|
73
|
+
"build": "tsx scripts/gen-requirements.mts && tsup",
|
|
74
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
75
|
+
"test": "vitest run",
|
|
76
|
+
"clean": "rm -rf dist .turbo",
|
|
77
|
+
"start": "node ./dist/bin.js"
|
|
45
78
|
}
|
|
46
|
-
}
|
|
79
|
+
}
|