workflowskill 0.1.0
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/LICENSE +21 -0
- package/README.md +242 -0
- package/dist/cli/index.d.mts +1 -0
- package/dist/cli/index.mjs +187 -0
- package/dist/cli/index.mjs.map +1 -0
- package/dist/gmail-CYBJ6Dxa.mjs +209 -0
- package/dist/gmail-CYBJ6Dxa.mjs.map +1 -0
- package/dist/html-select-BZPYAr6k.mjs +104 -0
- package/dist/html-select-BZPYAr6k.mjs.map +1 -0
- package/dist/http-request-k9bp5joL.mjs +91 -0
- package/dist/http-request-k9bp5joL.mjs.map +1 -0
- package/dist/index.d.mts +637 -0
- package/dist/index.d.mts.map +1 -0
- package/dist/index.mjs +61 -0
- package/dist/index.mjs.map +1 -0
- package/dist/runtime-BY1CFnew.mjs +2003 -0
- package/dist/runtime-BY1CFnew.mjs.map +1 -0
- package/dist/sheets-CGy8JvVz.mjs +177 -0
- package/dist/sheets-CGy8JvVz.mjs.map +1 -0
- package/package.json +75 -0
- package/skill/SKILL.md +725 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Matthew Cromer
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
# WorkflowSkill
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/workflowskill)
|
|
4
|
+
[](https://github.com/matthew-h-cromer/workflowskill/blob/main/LICENSE)
|
|
5
|
+
[](https://nodejs.org)
|
|
6
|
+
[](https://github.com/matthew-h-cromer/workflowskill/issues)
|
|
7
|
+
|
|
8
|
+
> [!IMPORTANT]
|
|
9
|
+
> **Pre-release.** The spec and reference runtime are complete and tested, but the API is not yet frozen. This is a good time to influence direction — open an issue if something feels wrong, missing, or over-engineered.
|
|
10
|
+
|
|
11
|
+
A declarative workflow language for AI agents.
|
|
12
|
+
|
|
13
|
+
1. You prompt what you need: "I want to check this website daily"
|
|
14
|
+
2. Your agent writes a WorkflowSkill — an extension to [Agent Skills](https://agentskills.io/home) — that can be executed deterministically by any compatible runtime.
|
|
15
|
+
3. The WorkflowSkill runs reliably and cheaply for repetitive tasks — no agent needed.
|
|
16
|
+
|
|
17
|
+
```yaml
|
|
18
|
+
inputs:
|
|
19
|
+
endpoint:
|
|
20
|
+
type: string
|
|
21
|
+
|
|
22
|
+
outputs:
|
|
23
|
+
summary:
|
|
24
|
+
type: string
|
|
25
|
+
value: $steps.summarize.output.summary
|
|
26
|
+
|
|
27
|
+
steps:
|
|
28
|
+
- id: fetch
|
|
29
|
+
type: tool
|
|
30
|
+
tool: http.request
|
|
31
|
+
inputs:
|
|
32
|
+
url:
|
|
33
|
+
type: string
|
|
34
|
+
value: $inputs.endpoint
|
|
35
|
+
outputs:
|
|
36
|
+
body:
|
|
37
|
+
type: string
|
|
38
|
+
|
|
39
|
+
- id: summarize
|
|
40
|
+
type: llm
|
|
41
|
+
prompt: "Summarize this content: $steps.fetch.output.body"
|
|
42
|
+
outputs:
|
|
43
|
+
summary:
|
|
44
|
+
type: string
|
|
45
|
+
value: $result
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Install
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
npm install workflowskill
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## CLI
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Validate a workflow (no API keys needed)
|
|
58
|
+
workflowskill validate path/to/workflow.md
|
|
59
|
+
|
|
60
|
+
# Run a workflow
|
|
61
|
+
workflowskill run path/to/workflow.md
|
|
62
|
+
|
|
63
|
+
# Pass inputs as JSON
|
|
64
|
+
workflowskill run path/to/workflow.md --input '{"keywords": "rust developer"}'
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The CLI shows live progress on stderr and writes a structured [run log](#run-log) as JSON to stdout and to `runs/<name>-<timestamp>.json`.
|
|
68
|
+
|
|
69
|
+
## Authoring
|
|
70
|
+
|
|
71
|
+
A WorkflowSkill is authored via natural conversation with any agent system that supports the [Agent Skills](https://agentskills.io/home) format.
|
|
72
|
+
|
|
73
|
+
1. Prompt your agent to create a workflow: "I want to check this website daily"
|
|
74
|
+
- For Claude Code, this package ships `skill/SKILL.md` which teaches it to author valid WorkflowSkill YAML. For other agent tools, provide the contents of `node_modules/workflowskill/skill/SKILL.md` as context.
|
|
75
|
+
2. Your agent writes a WorkflowSkill.
|
|
76
|
+
3. Run it deterministically: `workflowskill run <workflow.md>`
|
|
77
|
+
|
|
78
|
+
Your agent will research the task, write the workflow file, and validate it with the CLI. The dev tools available to generated workflows are:
|
|
79
|
+
|
|
80
|
+
| Tool | What it does | Needs credentials |
|
|
81
|
+
| --------------- | ------------------------------------- | ----------------- |
|
|
82
|
+
| `http.request` | HTTP GET/POST/PUT/PATCH/DELETE | No |
|
|
83
|
+
| `html.select` | CSS selector extraction from HTML | No |
|
|
84
|
+
| `gmail.search` | Search Gmail by query | Google OAuth2 |
|
|
85
|
+
| `gmail.read` | Read a Gmail message by ID | Google OAuth2 |
|
|
86
|
+
| `gmail.send` | Send email via Gmail | Google OAuth2 |
|
|
87
|
+
| `sheets.read` | Read a Google Sheets range | Google OAuth2 |
|
|
88
|
+
| `sheets.write` | Write to a Google Sheets range | Google OAuth2 |
|
|
89
|
+
| `sheets.append` | Append rows to a Google Sheets range | Google OAuth2 |
|
|
90
|
+
|
|
91
|
+
`http.request` and `html.select` work with no setup. For Google tools, add credentials to `.env` (see [Configuration](#configuration)).
|
|
92
|
+
|
|
93
|
+
**Evaluate the output:** Check `status` for overall success. If a step failed, its `error` field explains why. For `each` steps, `iterations` shows per-item results. Compare per-step `inputs` and `output` values against your expectations to find where the data flow broke down.
|
|
94
|
+
|
|
95
|
+
## Language overview
|
|
96
|
+
|
|
97
|
+
WorkflowSkill workflows are YAML documents with five step types:
|
|
98
|
+
|
|
99
|
+
| Step type | Description |
|
|
100
|
+
| ------------- | ------------------------------------------------------------------------------------------ |
|
|
101
|
+
| `tool` | Invoke an MCP server endpoint, dev tool (HTTP, Gmail, Sheets), or any registered function |
|
|
102
|
+
| `llm` | Call a language model with a templated prompt |
|
|
103
|
+
| `transform` | Filter, map, or sort data without side effects |
|
|
104
|
+
| `conditional` | Branch execution based on an expression |
|
|
105
|
+
| `exit` | Terminate early with a status and output |
|
|
106
|
+
|
|
107
|
+
Steps are connected by `$steps.<id>.output.<field>` references. Loops use `each`. Error handling uses `on_error: fail | ignore` (retries are a separate `retry:` field).
|
|
108
|
+
|
|
109
|
+
See the [full language specification](https://github.com/matthew-h-cromer/workflowskill/blob/main/SPEC.md) for details.
|
|
110
|
+
|
|
111
|
+
## Library API
|
|
112
|
+
|
|
113
|
+
Two entry points collapse parse → validate → run into single calls that accept raw content and never throw.
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
import {
|
|
117
|
+
runWorkflowSkill,
|
|
118
|
+
validateWorkflowSkill,
|
|
119
|
+
} from "workflowskill";
|
|
120
|
+
|
|
121
|
+
// Validate a workflow (synchronous, never throws)
|
|
122
|
+
const result = validateWorkflowSkill({
|
|
123
|
+
content, // SKILL.md with frontmatter or bare workflow YAML
|
|
124
|
+
toolAdapter, // optional — skips tool availability checks if absent
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (!result.valid) {
|
|
128
|
+
console.error(result.errors);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Run a workflow (async, never throws — always returns a RunLog)
|
|
132
|
+
const log = await runWorkflowSkill({
|
|
133
|
+
content, // SKILL.md with frontmatter or bare workflow YAML
|
|
134
|
+
inputs: { ... },
|
|
135
|
+
toolAdapter, // implements ToolAdapter
|
|
136
|
+
llmAdapter, // implements LLMAdapter
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
if (log.status === "success") {
|
|
140
|
+
console.log(log.outputs);
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Key ergonomic properties:
|
|
145
|
+
|
|
146
|
+
- **Accepts raw content** — SKILL.md with frontmatter or bare workflow YAML, no parsing step needed
|
|
147
|
+
- **Never throws** — parse, validation, and execution failures are encoded in the return value
|
|
148
|
+
- **`RunLog` is always returned**, with `error?: { phase, message, details }` on failure
|
|
149
|
+
|
|
150
|
+
## Adapters
|
|
151
|
+
|
|
152
|
+
`ToolAdapter` and `LLMAdapter` are the integration boundaries:
|
|
153
|
+
|
|
154
|
+
```typescript
|
|
155
|
+
interface ToolAdapter {
|
|
156
|
+
invoke(toolName: string, args: Record<string, unknown>): Promise<ToolResult>;
|
|
157
|
+
has(toolName: string): boolean;
|
|
158
|
+
list?(): ToolDescriptor[];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
interface LLMAdapter {
|
|
162
|
+
call(
|
|
163
|
+
model: string | undefined,
|
|
164
|
+
prompt: string,
|
|
165
|
+
responseFormat?: Record<string, unknown>,
|
|
166
|
+
): Promise<LLMResult>;
|
|
167
|
+
}
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Built-in implementations:
|
|
171
|
+
|
|
172
|
+
- **`DevToolAdapter`** — provides `http.request`, `html.select`, Gmail, and Google Sheets tools for standalone use
|
|
173
|
+
- **`AnthropicLLMAdapter`** — wraps the Anthropic SDK; reads `ANTHROPIC_API_KEY` from the environment
|
|
174
|
+
|
|
175
|
+
For testing, **`MockToolAdapter`** and **`MockLLMAdapter`** let you supply handler functions without any external dependencies.
|
|
176
|
+
|
|
177
|
+
## Run log
|
|
178
|
+
|
|
179
|
+
Every call to `runWorkflowSkill` returns a `RunLog`:
|
|
180
|
+
|
|
181
|
+
```typescript
|
|
182
|
+
interface RunLog {
|
|
183
|
+
id: string;
|
|
184
|
+
workflow: string;
|
|
185
|
+
status: "success" | "failed";
|
|
186
|
+
summary: {
|
|
187
|
+
steps_executed: number;
|
|
188
|
+
steps_skipped: number;
|
|
189
|
+
total_tokens: number;
|
|
190
|
+
total_duration_ms: number;
|
|
191
|
+
};
|
|
192
|
+
started_at: string; // ISO 8601
|
|
193
|
+
completed_at: string; // ISO 8601
|
|
194
|
+
duration_ms: number;
|
|
195
|
+
inputs: Record<string, unknown>;
|
|
196
|
+
steps: StepRecord[];
|
|
197
|
+
outputs: Record<string, unknown>;
|
|
198
|
+
error?: {
|
|
199
|
+
phase: "parse" | "validate" | "execute";
|
|
200
|
+
message: string;
|
|
201
|
+
details?: unknown;
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
`error` is present only when the run failed. `outputs` is populated on success (and on `exit` steps with `status: failed`).
|
|
207
|
+
|
|
208
|
+
## Low-level API
|
|
209
|
+
|
|
210
|
+
`parseWorkflowFromMd`, `validateWorkflow`, and `runWorkflow` are still exported for consumers who need fine-grained control over each phase.
|
|
211
|
+
|
|
212
|
+
## Configuration
|
|
213
|
+
|
|
214
|
+
Create a `.env` file in your project directory (or export env vars in your shell):
|
|
215
|
+
|
|
216
|
+
```
|
|
217
|
+
ANTHROPIC_API_KEY=sk-ant-... # Required for LLM steps in workflows
|
|
218
|
+
GOOGLE_CLIENT_ID=... # Required for Gmail and Sheets tools
|
|
219
|
+
GOOGLE_CLIENT_SECRET=...
|
|
220
|
+
GOOGLE_REFRESH_TOKEN=...
|
|
221
|
+
```
|
|
222
|
+
|
|
223
|
+
Missing `ANTHROPIC_API_KEY` causes LLM steps to fail with a clear error (no silent fallback). Missing Google credentials → Google tools not registered (warning if a workflow references them).
|
|
224
|
+
|
|
225
|
+
### Getting Google OAuth2 credentials
|
|
226
|
+
|
|
227
|
+
1. Go to [Google Cloud Console](https://console.cloud.google.com/) and create or select a project.
|
|
228
|
+
2. Enable the APIs you need: **Gmail API** and/or **Google Sheets API** under _APIs & Services > Library_.
|
|
229
|
+
3. Go to _APIs & Services > OAuth consent screen_. Choose **External** and fill in the app name and your email.
|
|
230
|
+
4. Go to _APIs & Services > Credentials > Create Credentials > OAuth client ID_. Choose **Desktop app**. Copy the **Client ID** and **Client Secret** into your `.env`.
|
|
231
|
+
5. Get a refresh token. The easiest way is [Google's OAuth 2.0 Playground](https://developers.google.com/oauthplayground/):
|
|
232
|
+
- Click the gear icon, check **Use your own OAuth credentials**, and enter your Client ID and Client Secret.
|
|
233
|
+
- In the left panel, select the scopes you need: `https://www.googleapis.com/auth/gmail.modify` (under **Gmail API v1**) and/or `https://www.googleapis.com/auth/spreadsheets` (under **Sheets API v4**). Click **Authorize APIs** and sign in.
|
|
234
|
+
- Click **Exchange authorization code for tokens**. Copy the **Refresh token** into your `.env`.
|
|
235
|
+
|
|
236
|
+
## Documentation
|
|
237
|
+
|
|
238
|
+
Full specification, design rationale, and examples: [github.com/matthew-h-cromer/workflowskill](https://github.com/matthew-h-cromer/workflowskill)
|
|
239
|
+
|
|
240
|
+
## License
|
|
241
|
+
|
|
242
|
+
[MIT](https://github.com/matthew-h-cromer/workflowskill/blob/main/LICENSE)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { };
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { S as ParseError, f as loadConfig, h as validateWorkflow, i as runWorkflowSkill, m as AnthropicLLMAdapter, p as DevToolAdapter, w as parseWorkflowFromMd } from "../runtime-BY1CFnew.mjs";
|
|
3
|
+
import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
4
|
+
import { basename, join } from "node:path";
|
|
5
|
+
import { Command } from "commander";
|
|
6
|
+
import pc from "picocolors";
|
|
7
|
+
|
|
8
|
+
//#region src/cli/validate.ts
|
|
9
|
+
function validateCommand(files) {
|
|
10
|
+
let hasErrors = false;
|
|
11
|
+
for (const file of files) {
|
|
12
|
+
let content;
|
|
13
|
+
try {
|
|
14
|
+
content = readFileSync(file, "utf-8");
|
|
15
|
+
} catch {
|
|
16
|
+
console.error(`✗ ${file}: Cannot read file`);
|
|
17
|
+
hasErrors = true;
|
|
18
|
+
continue;
|
|
19
|
+
}
|
|
20
|
+
let workflow;
|
|
21
|
+
try {
|
|
22
|
+
workflow = parseWorkflowFromMd(content);
|
|
23
|
+
} catch (err) {
|
|
24
|
+
if (err instanceof ParseError) {
|
|
25
|
+
console.error(`✗ ${file}: ${err.message}`);
|
|
26
|
+
for (const detail of err.details) console.error(` ${detail.path}: ${detail.message}`);
|
|
27
|
+
} else console.error(`✗ ${file}: ${err instanceof Error ? err.message : String(err)}`);
|
|
28
|
+
hasErrors = true;
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
const result = validateWorkflow(workflow);
|
|
32
|
+
if (!result.valid) {
|
|
33
|
+
console.error(`✗ ${file}: Validation errors`);
|
|
34
|
+
for (const error of result.errors) console.error(` ${error.path}: ${error.message}`);
|
|
35
|
+
hasErrors = true;
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
console.log(`✓ ${file} (${workflow.steps.length} steps)`);
|
|
39
|
+
}
|
|
40
|
+
if (hasErrors) process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
//#endregion
|
|
44
|
+
//#region src/cli/format.ts
|
|
45
|
+
const runtimeActiveSteps = /* @__PURE__ */ new Map();
|
|
46
|
+
const isTTY = !!process.stderr.isTTY;
|
|
47
|
+
const MOVE_UP = (n) => `\x1b[${n}A`;
|
|
48
|
+
const CLEAR_LINE = "\x1B[2K\r";
|
|
49
|
+
const MOVE_DOWN = (n) => `\x1b[${n}B`;
|
|
50
|
+
let linesSinceStepStart = 0;
|
|
51
|
+
let hasEachProgress = false;
|
|
52
|
+
/**
|
|
53
|
+
* Render a runtime event to stderr with live step-by-step progress output.
|
|
54
|
+
* All CLI live output goes to stderr; stdout is reserved for the JSON run log.
|
|
55
|
+
*
|
|
56
|
+
* On TTY: the yellow "running..." line is overwritten in-place by the green/red
|
|
57
|
+
* completion line, and each_progress updates a single line instead of appending.
|
|
58
|
+
* On non-TTY (pipes, files): append-only fallback — existing behavior.
|
|
59
|
+
*/
|
|
60
|
+
function renderRuntimeEvent(event) {
|
|
61
|
+
switch (event.type) {
|
|
62
|
+
case "workflow_start":
|
|
63
|
+
process.stderr.write(`\n${pc.bold("▶")} Running ${pc.cyan(event.workflow)} (${event.totalSteps} step${event.totalSteps !== 1 ? "s" : ""})\n\n`);
|
|
64
|
+
break;
|
|
65
|
+
case "step_start": {
|
|
66
|
+
linesSinceStepStart = 0;
|
|
67
|
+
hasEachProgress = false;
|
|
68
|
+
const typeLabel = event.tool ? `[tool] (${event.tool})` : `[${event.stepType}]`;
|
|
69
|
+
const line = ` ${pc.yellow("●")} ${event.stepId} ${pc.dim(typeLabel)} ${pc.dim("running...")}`;
|
|
70
|
+
runtimeActiveSteps.set(event.stepId, line);
|
|
71
|
+
process.stderr.write(line + "\n");
|
|
72
|
+
break;
|
|
73
|
+
}
|
|
74
|
+
case "step_complete": {
|
|
75
|
+
runtimeActiveSteps.delete(event.stepId);
|
|
76
|
+
const durationStr = formatDuration(event.duration_ms);
|
|
77
|
+
const extras = [];
|
|
78
|
+
if (event.tokens) extras.push(`${event.tokens.input + event.tokens.output} tokens`);
|
|
79
|
+
if (event.iterations !== void 0) extras.push(`${event.iterations} iterations`);
|
|
80
|
+
const extraStr = extras.length > 0 ? pc.dim(` (${extras.join(", ")})`) : "";
|
|
81
|
+
const completionLine = event.status === "success" ? ` ${pc.green("✓")} ${event.stepId} ${pc.dim(durationStr)}${extraStr}` : ` ${pc.red("✗")} ${event.stepId} ${pc.dim(durationStr)} ${pc.red("failed")}${extraStr}`;
|
|
82
|
+
if (isTTY) {
|
|
83
|
+
const up = linesSinceStepStart + 1;
|
|
84
|
+
process.stderr.write(MOVE_UP(up) + CLEAR_LINE + completionLine + "\n" + (linesSinceStepStart > 0 ? MOVE_DOWN(linesSinceStepStart) : ""));
|
|
85
|
+
} else process.stderr.write(completionLine + "\n");
|
|
86
|
+
break;
|
|
87
|
+
}
|
|
88
|
+
case "step_skip":
|
|
89
|
+
runtimeActiveSteps.delete(event.stepId);
|
|
90
|
+
process.stderr.write(` ${pc.dim("○")} ${pc.dim(event.stepId)} ${pc.dim("skipped")}: ${pc.dim(event.reason)}\n`);
|
|
91
|
+
break;
|
|
92
|
+
case "step_retry":
|
|
93
|
+
process.stderr.write(` ${pc.yellow("↻")} ${event.stepId} retry #${event.attempt}: ${pc.dim(event.error)}\n`);
|
|
94
|
+
linesSinceStepStart++;
|
|
95
|
+
break;
|
|
96
|
+
case "step_error":
|
|
97
|
+
process.stderr.write(` ${pc.red("✗")} ${event.stepId} error (${event.onError}): ${pc.dim(event.error)}\n`);
|
|
98
|
+
linesSinceStepStart++;
|
|
99
|
+
break;
|
|
100
|
+
case "each_progress":
|
|
101
|
+
if (isTTY && hasEachProgress) process.stderr.write(MOVE_UP(1) + CLEAR_LINE + ` ${pc.dim(`${event.current}/${event.total}`)}\n`);
|
|
102
|
+
else {
|
|
103
|
+
process.stderr.write(` ${pc.dim(`${event.current}/${event.total}`)}\n`);
|
|
104
|
+
linesSinceStepStart++;
|
|
105
|
+
hasEachProgress = true;
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
case "workflow_complete": {
|
|
109
|
+
const durationStr = formatDuration(event.duration_ms);
|
|
110
|
+
const { steps_executed, steps_skipped, total_tokens } = event.summary;
|
|
111
|
+
const parts = [`${steps_executed} executed`];
|
|
112
|
+
if (steps_skipped > 0) parts.push(`${steps_skipped} skipped`);
|
|
113
|
+
if (total_tokens > 0) parts.push(`${total_tokens} tokens`);
|
|
114
|
+
const summary = parts.join(", ");
|
|
115
|
+
if (event.status === "success") process.stderr.write(`\n${pc.green(pc.bold("✓"))} ${pc.green("Success")} in ${durationStr} ${pc.dim(`(${summary})`)}\n`);
|
|
116
|
+
else process.stderr.write(`\n${pc.red(pc.bold("✗"))} ${pc.red("Failed")} in ${durationStr} ${pc.dim(`(${summary})`)}\n`);
|
|
117
|
+
break;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/** Format a duration in milliseconds as a human-readable string (e.g. "1.2s", "2m30s"). */
|
|
122
|
+
function formatDuration(ms) {
|
|
123
|
+
if (ms < 1e3) return `${ms}ms`;
|
|
124
|
+
const totalSeconds = ms / 1e3;
|
|
125
|
+
if (totalSeconds < 60) return `${totalSeconds.toFixed(1)}s`;
|
|
126
|
+
return `${Math.floor(totalSeconds / 60)}m${Math.round(totalSeconds % 60)}s`;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
//#endregion
|
|
130
|
+
//#region src/cli/run.ts
|
|
131
|
+
/** Write a run log to stdout and persist it to disk. */
|
|
132
|
+
function writeRunLog(log, logDir) {
|
|
133
|
+
const json = JSON.stringify(log, null, 2);
|
|
134
|
+
mkdirSync(logDir, { recursive: true });
|
|
135
|
+
const safeTimestamp = log.started_at.replace(/:/g, "-");
|
|
136
|
+
const logFile = join(logDir, `${log.workflow}-${safeTimestamp}.json`);
|
|
137
|
+
writeFileSync(logFile, json + "\n", "utf-8");
|
|
138
|
+
console.error(`Run log written to ${logFile}`);
|
|
139
|
+
console.log(json);
|
|
140
|
+
}
|
|
141
|
+
async function runCommand(file, options) {
|
|
142
|
+
const logDir = options.logDir ?? "runs";
|
|
143
|
+
const workflowName = basename(file, ".md");
|
|
144
|
+
let content;
|
|
145
|
+
try {
|
|
146
|
+
content = readFileSync(file, "utf-8");
|
|
147
|
+
} catch (err) {
|
|
148
|
+
console.error(`Error: Cannot read file "${file}": ${err instanceof Error ? err.message : String(err)}`);
|
|
149
|
+
process.exit(1);
|
|
150
|
+
}
|
|
151
|
+
let inputs = {};
|
|
152
|
+
if (options.input) try {
|
|
153
|
+
inputs = JSON.parse(options.input);
|
|
154
|
+
} catch {
|
|
155
|
+
console.error("Error: --input must be valid JSON");
|
|
156
|
+
process.exit(1);
|
|
157
|
+
}
|
|
158
|
+
const config = loadConfig();
|
|
159
|
+
const toolAdapter = await DevToolAdapter.create(config);
|
|
160
|
+
let llmAdapter;
|
|
161
|
+
if (config.anthropicApiKey) llmAdapter = new AnthropicLLMAdapter(config.anthropicApiKey);
|
|
162
|
+
else llmAdapter = { call() {
|
|
163
|
+
throw new Error("ANTHROPIC_API_KEY not set. This workflow has LLM steps that require it.\nSet it in runtime/.env or export it in your shell: export ANTHROPIC_API_KEY=sk-ant-...");
|
|
164
|
+
} };
|
|
165
|
+
const log = await runWorkflowSkill({
|
|
166
|
+
content,
|
|
167
|
+
inputs,
|
|
168
|
+
toolAdapter,
|
|
169
|
+
llmAdapter,
|
|
170
|
+
workflowName,
|
|
171
|
+
onEvent: renderRuntimeEvent
|
|
172
|
+
});
|
|
173
|
+
writeRunLog(log, logDir);
|
|
174
|
+
process.exit(log.status === "success" ? 0 : 1);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
//#endregion
|
|
178
|
+
//#region src/cli/index.ts
|
|
179
|
+
const program = new Command();
|
|
180
|
+
program.name("workflowskill").description("WorkflowSkill runtime CLI").version("0.1.0");
|
|
181
|
+
program.command("validate").description("Validate one or more workflow SKILL.md files without executing").argument("<files...>", "Workflow files to validate").action(validateCommand);
|
|
182
|
+
program.command("run <file>").description("Execute a workflow SKILL.md file").option("-i, --input <json>", "Workflow inputs as JSON string", "{}").option("-l, --log-dir <dir>", "Directory to write run logs", "runs").action(runCommand);
|
|
183
|
+
program.parse();
|
|
184
|
+
|
|
185
|
+
//#endregion
|
|
186
|
+
export { };
|
|
187
|
+
//# sourceMappingURL=index.mjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.mjs","names":[],"sources":["../../src/cli/validate.ts","../../src/cli/format.ts","../../src/cli/run.ts","../../src/cli/index.ts"],"sourcesContent":["// CLI: validate command — check workflows without running them.\n\nimport { readFileSync } from 'node:fs';\nimport { parseWorkflowFromMd } from '../parser/index.js';\nimport { ParseError } from '../parser/index.js';\nimport { validateWorkflow } from '../validator/index.js';\n\nexport function validateCommand(files: string[]): void {\n let hasErrors = false;\n\n for (const file of files) {\n let content: string;\n try {\n content = readFileSync(file, 'utf-8');\n } catch {\n console.error(`✗ ${file}: Cannot read file`);\n hasErrors = true;\n continue;\n }\n\n // Parse\n let workflow;\n try {\n workflow = parseWorkflowFromMd(content);\n } catch (err) {\n if (err instanceof ParseError) {\n console.error(`✗ ${file}: ${err.message}`);\n for (const detail of err.details) {\n console.error(` ${detail.path}: ${detail.message}`);\n }\n } else {\n console.error(`✗ ${file}: ${err instanceof Error ? err.message : String(err)}`);\n }\n hasErrors = true;\n continue;\n }\n\n // Validate\n const result = validateWorkflow(workflow);\n if (!result.valid) {\n console.error(`✗ ${file}: Validation errors`);\n for (const error of result.errors) {\n console.error(` ${error.path}: ${error.message}`);\n }\n hasErrors = true;\n continue;\n }\n\n console.log(`✓ ${file} (${workflow.steps.length} steps)`);\n }\n\n if (hasErrors) {\n process.exit(1);\n }\n}\n","// CLI formatting module — runtime event rendering.\n// Uses picocolors for terminal colors and writes to stderr (keeping stdout clean for output).\n\nimport pc from 'picocolors';\nimport type { RuntimeEvent } from '../types/index.js';\n\n// ─── Runtime event renderer ───────────────────────────────────────────────────\n\n// Track in-progress step names for the \"running...\" line (keyed by stepId).\nconst runtimeActiveSteps = new Map<string, string>();\n\n// Whether stderr is a real TTY (enables in-place cursor movement).\nconst isTTY = !!process.stderr.isTTY;\n\n// ANSI helpers for in-place line updates (only used when isTTY is true).\nconst MOVE_UP = (n: number): string => `\\x1b[${n}A`;\nconst CLEAR_LINE = '\\x1b[2K\\r';\nconst MOVE_DOWN = (n: number): string => `\\x1b[${n}B`;\n\n// Lines printed between step_start and step_complete (retries, each_progress).\nlet linesSinceStepStart = 0;\n// Whether each_progress has been printed at least once for the current step.\nlet hasEachProgress = false;\n\n/**\n * Render a runtime event to stderr with live step-by-step progress output.\n * All CLI live output goes to stderr; stdout is reserved for the JSON run log.\n *\n * On TTY: the yellow \"running...\" line is overwritten in-place by the green/red\n * completion line, and each_progress updates a single line instead of appending.\n * On non-TTY (pipes, files): append-only fallback — existing behavior.\n */\nexport function renderRuntimeEvent(event: RuntimeEvent): void {\n switch (event.type) {\n case 'workflow_start':\n process.stderr.write(`\\n${pc.bold('▶')} Running ${pc.cyan(event.workflow)} (${event.totalSteps} step${event.totalSteps !== 1 ? 's' : ''})\\n\\n`);\n break;\n\n case 'step_start': {\n linesSinceStepStart = 0;\n hasEachProgress = false;\n const typeLabel = event.tool ? `[tool] (${event.tool})` : `[${event.stepType}]`;\n const line = ` ${pc.yellow('●')} ${event.stepId} ${pc.dim(typeLabel)} ${pc.dim('running...')}`;\n runtimeActiveSteps.set(event.stepId, line);\n process.stderr.write(line + '\\n');\n break;\n }\n\n case 'step_complete': {\n runtimeActiveSteps.delete(event.stepId);\n const durationStr = formatDuration(event.duration_ms);\n const extras: string[] = [];\n if (event.tokens) extras.push(`${event.tokens.input + event.tokens.output} tokens`);\n if (event.iterations !== undefined) extras.push(`${event.iterations} iterations`);\n const extraStr = extras.length > 0 ? pc.dim(` (${extras.join(', ')})`) : '';\n const completionLine =\n event.status === 'success'\n ? ` ${pc.green('✓')} ${event.stepId} ${pc.dim(durationStr)}${extraStr}`\n : ` ${pc.red('✗')} ${event.stepId} ${pc.dim(durationStr)} ${pc.red('failed')}${extraStr}`;\n\n if (isTTY) {\n // Move up past any intermediate lines + the \"running...\" line, overwrite it,\n // then move the cursor back down past the intermediate lines.\n const up = linesSinceStepStart + 1;\n process.stderr.write(\n MOVE_UP(up) + CLEAR_LINE + completionLine + '\\n' +\n (linesSinceStepStart > 0 ? MOVE_DOWN(linesSinceStepStart) : ''),\n );\n } else {\n process.stderr.write(completionLine + '\\n');\n }\n break;\n }\n\n case 'step_skip':\n runtimeActiveSteps.delete(event.stepId);\n process.stderr.write(` ${pc.dim('○')} ${pc.dim(event.stepId)} ${pc.dim('skipped')}: ${pc.dim(event.reason)}\\n`);\n break;\n\n case 'step_retry':\n process.stderr.write(` ${pc.yellow('↻')} ${event.stepId} retry #${event.attempt}: ${pc.dim(event.error)}\\n`);\n linesSinceStepStart++;\n break;\n\n case 'step_error':\n process.stderr.write(` ${pc.red('✗')} ${event.stepId} error (${event.onError}): ${pc.dim(event.error)}\\n`);\n linesSinceStepStart++;\n break;\n\n case 'each_progress':\n if (isTTY && hasEachProgress) {\n // Overwrite the previous each_progress line in-place.\n process.stderr.write(MOVE_UP(1) + CLEAR_LINE + ` ${pc.dim(`${event.current}/${event.total}`)}\\n`);\n } else {\n process.stderr.write(` ${pc.dim(`${event.current}/${event.total}`)}\\n`);\n linesSinceStepStart++;\n hasEachProgress = true;\n }\n break;\n\n case 'workflow_complete': {\n const durationStr = formatDuration(event.duration_ms);\n const { steps_executed, steps_skipped, total_tokens } = event.summary;\n const parts: string[] = [`${steps_executed} executed`];\n if (steps_skipped > 0) parts.push(`${steps_skipped} skipped`);\n if (total_tokens > 0) parts.push(`${total_tokens} tokens`);\n const summary = parts.join(', ');\n if (event.status === 'success') {\n process.stderr.write(`\\n${pc.green(pc.bold('✓'))} ${pc.green('Success')} in ${durationStr} ${pc.dim(`(${summary})`)}\\n`);\n } else {\n process.stderr.write(`\\n${pc.red(pc.bold('✗'))} ${pc.red('Failed')} in ${durationStr} ${pc.dim(`(${summary})`)}\\n`);\n }\n break;\n }\n }\n}\n\n/** Reset runtime format state (for testing). */\nexport function resetRuntimeFormatState(): void {\n runtimeActiveSteps.clear();\n linesSinceStepStart = 0;\n hasEachProgress = false;\n}\n\n/** Format a duration in milliseconds as a human-readable string (e.g. \"1.2s\", \"2m30s\"). */\nexport function formatDuration(ms: number): string {\n if (ms < 1000) return `${ms}ms`;\n const totalSeconds = ms / 1000;\n if (totalSeconds < 60) return `${totalSeconds.toFixed(1)}s`;\n const minutes = Math.floor(totalSeconds / 60);\n const seconds = Math.round(totalSeconds % 60);\n return `${minutes}m${seconds}s`;\n}\n","// CLI: run command — execute a workflow and print the run log.\n\nimport { mkdirSync, readFileSync, writeFileSync } from 'node:fs';\nimport { basename, join } from 'node:path';\nimport { runWorkflowSkill } from '../runtime/index.js';\nimport type { RunLog } from '../types/index.js';\nimport { loadConfig } from '../config/index.js';\nimport { AnthropicLLMAdapter } from '../adapters/anthropic-llm-adapter.js';\nimport { DevToolAdapter } from '../dev-tools/dev-tool-adapter.js';\nimport type { LLMAdapter, LLMResult } from '../types/index.js';\nimport { renderRuntimeEvent } from './format.js';\n\n/** Write a run log to stdout and persist it to disk. */\nfunction writeRunLog(log: RunLog, logDir: string): void {\n const json = JSON.stringify(log, null, 2);\n mkdirSync(logDir, { recursive: true });\n const safeTimestamp = log.started_at.replace(/:/g, '-');\n const logFile = join(logDir, `${log.workflow}-${safeTimestamp}.json`);\n writeFileSync(logFile, json + '\\n', 'utf-8');\n console.error(`Run log written to ${logFile}`);\n console.log(json);\n}\n\nexport async function runCommand(\n file: string,\n options: { input?: string; logDir?: string },\n): Promise<void> {\n const logDir = options.logDir ?? 'runs';\n const workflowName = basename(file, '.md');\n\n let content: string;\n try {\n content = readFileSync(file, 'utf-8');\n } catch (err) {\n console.error(`Error: Cannot read file \"${file}\": ${err instanceof Error ? err.message : String(err)}`);\n process.exit(1);\n }\n\n // Parse inputs\n let inputs: Record<string, unknown> = {};\n if (options.input) {\n try {\n inputs = JSON.parse(options.input) as Record<string, unknown>;\n } catch {\n console.error('Error: --input must be valid JSON');\n process.exit(1);\n }\n }\n\n // Load config and create adapters\n const config = loadConfig();\n const toolAdapter = await DevToolAdapter.create(config);\n let llmAdapter: LLMAdapter;\n if (config.anthropicApiKey) {\n llmAdapter = new AnthropicLLMAdapter(config.anthropicApiKey);\n } else {\n // No API key — create an adapter that fails with a clear error on first use.\n // Workflows without LLM steps (e.g., hello-world.md) still work.\n llmAdapter = {\n call(): Promise<LLMResult> {\n throw new Error(\n 'ANTHROPIC_API_KEY not set. This workflow has LLM steps that require it.\\n' +\n 'Set it in runtime/.env or export it in your shell: export ANTHROPIC_API_KEY=sk-ant-...',\n );\n },\n };\n }\n\n const log = await runWorkflowSkill({\n content,\n inputs,\n toolAdapter,\n llmAdapter,\n workflowName,\n onEvent: renderRuntimeEvent,\n });\n\n writeRunLog(log, logDir);\n process.exit(log.status === 'success' ? 0 : 1);\n}\n","#!/usr/bin/env node\n// WorkflowSkill CLI — validate and run workflows.\n\nimport { Command } from 'commander';\nimport { validateCommand } from './validate.js';\nimport { runCommand } from './run.js';\n\nconst program = new Command();\n\nprogram\n .name('workflowskill')\n .description('WorkflowSkill runtime CLI')\n .version('0.1.0');\n\nprogram\n .command('validate')\n .description('Validate one or more workflow SKILL.md files without executing')\n .argument('<files...>', 'Workflow files to validate')\n .action(validateCommand);\n\nprogram\n .command('run <file>')\n .description('Execute a workflow SKILL.md file')\n .option('-i, --input <json>', 'Workflow inputs as JSON string', '{}')\n .option('-l, --log-dir <dir>', 'Directory to write run logs', 'runs')\n .action(runCommand);\n\nprogram.parse();\n"],"mappings":";;;;;;;;AAOA,SAAgB,gBAAgB,OAAuB;CACrD,IAAI,YAAY;AAEhB,MAAK,MAAM,QAAQ,OAAO;EACxB,IAAI;AACJ,MAAI;AACF,aAAU,aAAa,MAAM,QAAQ;UAC/B;AACN,WAAQ,MAAM,KAAK,KAAK,oBAAoB;AAC5C,eAAY;AACZ;;EAIF,IAAI;AACJ,MAAI;AACF,cAAW,oBAAoB,QAAQ;WAChC,KAAK;AACZ,OAAI,eAAe,YAAY;AAC7B,YAAQ,MAAM,KAAK,KAAK,IAAI,IAAI,UAAU;AAC1C,SAAK,MAAM,UAAU,IAAI,QACvB,SAAQ,MAAM,OAAO,OAAO,KAAK,IAAI,OAAO,UAAU;SAGxD,SAAQ,MAAM,KAAK,KAAK,IAAI,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AAEjF,eAAY;AACZ;;EAIF,MAAM,SAAS,iBAAiB,SAAS;AACzC,MAAI,CAAC,OAAO,OAAO;AACjB,WAAQ,MAAM,KAAK,KAAK,qBAAqB;AAC7C,QAAK,MAAM,SAAS,OAAO,OACzB,SAAQ,MAAM,OAAO,MAAM,KAAK,IAAI,MAAM,UAAU;AAEtD,eAAY;AACZ;;AAGF,UAAQ,IAAI,KAAK,KAAK,IAAI,SAAS,MAAM,OAAO,SAAS;;AAG3D,KAAI,UACF,SAAQ,KAAK,EAAE;;;;;AC3CnB,MAAM,qCAAqB,IAAI,KAAqB;AAGpD,MAAM,QAAQ,CAAC,CAAC,QAAQ,OAAO;AAG/B,MAAM,WAAW,MAAsB,QAAQ,EAAE;AACjD,MAAM,aAAa;AACnB,MAAM,aAAa,MAAsB,QAAQ,EAAE;AAGnD,IAAI,sBAAsB;AAE1B,IAAI,kBAAkB;;;;;;;;;AAUtB,SAAgB,mBAAmB,OAA2B;AAC5D,SAAQ,MAAM,MAAd;EACE,KAAK;AACH,WAAQ,OAAO,MAAM,KAAK,GAAG,KAAK,IAAI,CAAC,WAAW,GAAG,KAAK,MAAM,SAAS,CAAC,IAAI,MAAM,WAAW,OAAO,MAAM,eAAe,IAAI,MAAM,GAAG,OAAO;AAC/I;EAEF,KAAK,cAAc;AACjB,yBAAsB;AACtB,qBAAkB;GAClB,MAAM,YAAY,MAAM,OAAO,WAAW,MAAM,KAAK,KAAK,IAAI,MAAM,SAAS;GAC7E,MAAM,OAAO,KAAK,GAAG,OAAO,IAAI,CAAC,GAAG,MAAM,OAAO,GAAG,GAAG,IAAI,UAAU,CAAC,GAAG,GAAG,IAAI,aAAa;AAC7F,sBAAmB,IAAI,MAAM,QAAQ,KAAK;AAC1C,WAAQ,OAAO,MAAM,OAAO,KAAK;AACjC;;EAGF,KAAK,iBAAiB;AACpB,sBAAmB,OAAO,MAAM,OAAO;GACvC,MAAM,cAAc,eAAe,MAAM,YAAY;GACrD,MAAM,SAAmB,EAAE;AAC3B,OAAI,MAAM,OAAQ,QAAO,KAAK,GAAG,MAAM,OAAO,QAAQ,MAAM,OAAO,OAAO,SAAS;AACnF,OAAI,MAAM,eAAe,OAAW,QAAO,KAAK,GAAG,MAAM,WAAW,aAAa;GACjF,MAAM,WAAW,OAAO,SAAS,IAAI,GAAG,IAAI,KAAK,OAAO,KAAK,KAAK,CAAC,GAAG,GAAG;GACzE,MAAM,iBACJ,MAAM,WAAW,YACb,KAAK,GAAG,MAAM,IAAI,CAAC,GAAG,MAAM,OAAO,GAAG,GAAG,IAAI,YAAY,GAAG,aAC5D,KAAK,GAAG,IAAI,IAAI,CAAC,GAAG,MAAM,OAAO,GAAG,GAAG,IAAI,YAAY,CAAC,GAAG,GAAG,IAAI,SAAS,GAAG;AAEpF,OAAI,OAAO;IAGT,MAAM,KAAK,sBAAsB;AACjC,YAAQ,OAAO,MACb,QAAQ,GAAG,GAAG,aAAa,iBAAiB,QAC3C,sBAAsB,IAAI,UAAU,oBAAoB,GAAG,IAC7D;SAED,SAAQ,OAAO,MAAM,iBAAiB,KAAK;AAE7C;;EAGF,KAAK;AACH,sBAAmB,OAAO,MAAM,OAAO;AACvC,WAAQ,OAAO,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,GAAG,GAAG,IAAI,MAAM,OAAO,CAAC,GAAG,GAAG,IAAI,UAAU,CAAC,IAAI,GAAG,IAAI,MAAM,OAAO,CAAC,IAAI;AAChH;EAEF,KAAK;AACH,WAAQ,OAAO,MAAM,KAAK,GAAG,OAAO,IAAI,CAAC,GAAG,MAAM,OAAO,UAAU,MAAM,QAAQ,IAAI,GAAG,IAAI,MAAM,MAAM,CAAC,IAAI;AAC7G;AACA;EAEF,KAAK;AACH,WAAQ,OAAO,MAAM,KAAK,GAAG,IAAI,IAAI,CAAC,GAAG,MAAM,OAAO,UAAU,MAAM,QAAQ,KAAK,GAAG,IAAI,MAAM,MAAM,CAAC,IAAI;AAC3G;AACA;EAEF,KAAK;AACH,OAAI,SAAS,gBAEX,SAAQ,OAAO,MAAM,QAAQ,EAAE,GAAG,aAAa,OAAO,GAAG,IAAI,GAAG,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI;QAC/F;AACL,YAAQ,OAAO,MAAM,OAAO,GAAG,IAAI,GAAG,MAAM,QAAQ,GAAG,MAAM,QAAQ,CAAC,IAAI;AAC1E;AACA,sBAAkB;;AAEpB;EAEF,KAAK,qBAAqB;GACxB,MAAM,cAAc,eAAe,MAAM,YAAY;GACrD,MAAM,EAAE,gBAAgB,eAAe,iBAAiB,MAAM;GAC9D,MAAM,QAAkB,CAAC,GAAG,eAAe,WAAW;AACtD,OAAI,gBAAgB,EAAG,OAAM,KAAK,GAAG,cAAc,UAAU;AAC7D,OAAI,eAAe,EAAG,OAAM,KAAK,GAAG,aAAa,SAAS;GAC1D,MAAM,UAAU,MAAM,KAAK,KAAK;AAChC,OAAI,MAAM,WAAW,UACnB,SAAQ,OAAO,MAAM,KAAK,GAAG,MAAM,GAAG,KAAK,IAAI,CAAC,CAAC,GAAG,GAAG,MAAM,UAAU,CAAC,MAAM,YAAY,GAAG,GAAG,IAAI,IAAI,QAAQ,GAAG,CAAC,IAAI;OAExH,SAAQ,OAAO,MAAM,KAAK,GAAG,IAAI,GAAG,KAAK,IAAI,CAAC,CAAC,GAAG,GAAG,IAAI,SAAS,CAAC,MAAM,YAAY,GAAG,GAAG,IAAI,IAAI,QAAQ,GAAG,CAAC,IAAI;AAErH;;;;;AAaN,SAAgB,eAAe,IAAoB;AACjD,KAAI,KAAK,IAAM,QAAO,GAAG,GAAG;CAC5B,MAAM,eAAe,KAAK;AAC1B,KAAI,eAAe,GAAI,QAAO,GAAG,aAAa,QAAQ,EAAE,CAAC;AAGzD,QAAO,GAFS,KAAK,MAAM,eAAe,GAAG,CAE3B,GADF,KAAK,MAAM,eAAe,GAAG,CAChB;;;;;;ACtH/B,SAAS,YAAY,KAAa,QAAsB;CACtD,MAAM,OAAO,KAAK,UAAU,KAAK,MAAM,EAAE;AACzC,WAAU,QAAQ,EAAE,WAAW,MAAM,CAAC;CACtC,MAAM,gBAAgB,IAAI,WAAW,QAAQ,MAAM,IAAI;CACvD,MAAM,UAAU,KAAK,QAAQ,GAAG,IAAI,SAAS,GAAG,cAAc,OAAO;AACrE,eAAc,SAAS,OAAO,MAAM,QAAQ;AAC5C,SAAQ,MAAM,sBAAsB,UAAU;AAC9C,SAAQ,IAAI,KAAK;;AAGnB,eAAsB,WACpB,MACA,SACe;CACf,MAAM,SAAS,QAAQ,UAAU;CACjC,MAAM,eAAe,SAAS,MAAM,MAAM;CAE1C,IAAI;AACJ,KAAI;AACF,YAAU,aAAa,MAAM,QAAQ;UAC9B,KAAK;AACZ,UAAQ,MAAM,4BAA4B,KAAK,KAAK,eAAe,QAAQ,IAAI,UAAU,OAAO,IAAI,GAAG;AACvG,UAAQ,KAAK,EAAE;;CAIjB,IAAI,SAAkC,EAAE;AACxC,KAAI,QAAQ,MACV,KAAI;AACF,WAAS,KAAK,MAAM,QAAQ,MAAM;SAC5B;AACN,UAAQ,MAAM,oCAAoC;AAClD,UAAQ,KAAK,EAAE;;CAKnB,MAAM,SAAS,YAAY;CAC3B,MAAM,cAAc,MAAM,eAAe,OAAO,OAAO;CACvD,IAAI;AACJ,KAAI,OAAO,gBACT,cAAa,IAAI,oBAAoB,OAAO,gBAAgB;KAI5D,cAAa,EACX,OAA2B;AACzB,QAAM,IAAI,MACR,kKAED;IAEJ;CAGH,MAAM,MAAM,MAAM,iBAAiB;EACjC;EACA;EACA;EACA;EACA;EACA,SAAS;EACV,CAAC;AAEF,aAAY,KAAK,OAAO;AACxB,SAAQ,KAAK,IAAI,WAAW,YAAY,IAAI,EAAE;;;;;ACvEhD,MAAM,UAAU,IAAI,SAAS;AAE7B,QACG,KAAK,gBAAgB,CACrB,YAAY,4BAA4B,CACxC,QAAQ,QAAQ;AAEnB,QACG,QAAQ,WAAW,CACnB,YAAY,iEAAiE,CAC7E,SAAS,cAAc,6BAA6B,CACpD,OAAO,gBAAgB;AAE1B,QACG,QAAQ,aAAa,CACrB,YAAY,mCAAmC,CAC/C,OAAO,sBAAsB,kCAAkC,KAAK,CACpE,OAAO,uBAAuB,+BAA+B,OAAO,CACpE,OAAO,WAAW;AAErB,QAAQ,OAAO"}
|