openclaw-workflowskill 0.3.1 → 0.3.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 +30 -6
- package/index.ts +3 -70
- package/lib/openclaw-context.md +9 -0
- package/openclaw.plugin.json +2 -2
- package/package.json +2 -2
- package/tools/llm.ts +81 -0
package/README.md
CHANGED
|
@@ -24,19 +24,32 @@ Author, validate, run, and review WorkflowSkill workflows — without leaving th
|
|
|
24
24
|
|
|
25
25
|
Requires [OpenClaw](https://openclaw.ai).
|
|
26
26
|
|
|
27
|
-
### 1.
|
|
27
|
+
### 1. Set your profile to Coding
|
|
28
|
+
|
|
29
|
+
In OpenClaw settings, change your profile from `messaging` (the default) to `coding`. The `coding` profile grants the agent filesystem access, which is required to read and write workflow files. For more granular access configuration, refer to the [OpenClaw documentation](https://docs.openclaw.ai/).
|
|
30
|
+
|
|
31
|
+
### 2. Install the plugin
|
|
28
32
|
|
|
29
33
|
```bash
|
|
30
34
|
openclaw plugins install openclaw-workflowskill
|
|
31
35
|
```
|
|
32
36
|
|
|
33
|
-
###
|
|
37
|
+
### 3. Configure
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
openclaw config set plugins.allow '["openclaw-workflowskill"]'
|
|
41
|
+
openclaw config set tools.alsoAllow '["openclaw-workflowskill"]'
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
The first command allowlists the plugin for loading. The second makes its tools available in all sessions (including cron).
|
|
45
|
+
|
|
46
|
+
### 4. Restart the gateway
|
|
34
47
|
|
|
35
48
|
```bash
|
|
36
49
|
openclaw gateway restart
|
|
37
50
|
```
|
|
38
51
|
|
|
39
|
-
###
|
|
52
|
+
### 5. Verify
|
|
40
53
|
|
|
41
54
|
```bash
|
|
42
55
|
openclaw plugins list
|
|
@@ -48,7 +61,7 @@ openclaw skills list
|
|
|
48
61
|
|
|
49
62
|
> **Note:** `workflowskill_llm` uses your Anthropic credentials from the main agent — no separate API key configuration needed.
|
|
50
63
|
|
|
51
|
-
###
|
|
64
|
+
### 6. Create a workflow
|
|
52
65
|
|
|
53
66
|
Just tell the agent what you want to automate:
|
|
54
67
|
|
|
@@ -60,7 +73,7 @@ Just tell the agent what you want to automate:
|
|
|
60
73
|
>
|
|
61
74
|
> Run complete: 4 AI stories found, summary drafted. Ready to schedule — want me to set up a daily cron at 8 AM?
|
|
62
75
|
|
|
63
|
-
###
|
|
76
|
+
### 7. Schedule it
|
|
64
77
|
|
|
65
78
|
Ask the agent to set up a cron job, or add one manually at `~/.openclaw/cron/jobs.json`:
|
|
66
79
|
|
|
@@ -76,6 +89,14 @@ Ask the agent to set up a cron job, or add one manually at `~/.openclaw/cron/job
|
|
|
76
89
|
|
|
77
90
|
Your agent will use `"model": "haiku"` for cron jobs, ensuring execution is cheap and lightweight.
|
|
78
91
|
|
|
92
|
+
> **Important:** Plugin tools are not available in sessions by default. Before scheduling, ensure `tools.alsoAllow` includes `openclaw-workflowskill`:
|
|
93
|
+
>
|
|
94
|
+
> ```bash
|
|
95
|
+
> openclaw config set tools.alsoAllow '["openclaw-workflowskill"]'
|
|
96
|
+
> ```
|
|
97
|
+
>
|
|
98
|
+
> Without this, cron sessions cannot invoke `workflowskill_run` and will fail silently.
|
|
99
|
+
|
|
79
100
|
## Tools
|
|
80
101
|
|
|
81
102
|
Registers four tools with the OpenClaw agent:
|
|
@@ -173,9 +194,12 @@ To test changes, link the plugin locally and restart the OpenClaw gateway:
|
|
|
173
194
|
```bash
|
|
174
195
|
openclaw plugins install --link "$(pwd)"
|
|
175
196
|
openclaw gateway restart
|
|
176
|
-
openclaw tools invoke workflowskill_validate '{"content": "..."}'
|
|
177
197
|
```
|
|
178
198
|
|
|
199
|
+
Then verify tools work by asking the agent:
|
|
200
|
+
|
|
201
|
+
> Validate this workflow: `inputs: { url: { type: string } }`
|
|
202
|
+
|
|
179
203
|
## License
|
|
180
204
|
|
|
181
205
|
MIT
|
package/index.ts
CHANGED
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
// Default-exports an object with id + register(api) per the OpenClaw plugin API.
|
|
5
5
|
|
|
6
6
|
import { mkdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
7
|
-
import { homedir } from 'node:os';
|
|
8
7
|
import { join } from 'node:path';
|
|
9
8
|
import { AUTHORING_SKILL } from 'workflowskill';
|
|
10
9
|
import { validateHandler } from './tools/validate.js';
|
|
11
10
|
import { runHandler } from './tools/run.js';
|
|
12
11
|
import { runsHandler } from './tools/runs.js';
|
|
12
|
+
import { llmHandler } from './tools/llm.js';
|
|
13
13
|
import { createToolAdapter, type GatewayConfig } from './lib/adapters.js';
|
|
14
14
|
|
|
15
15
|
// ─── OpenClaw plugin API types ─────────────────────────────────────────────
|
|
@@ -81,74 +81,10 @@ function toContent(result: unknown): { content: TextContent[] } {
|
|
|
81
81
|
return { content: [{ type: 'text', text: JSON.stringify(result, null, 2) }] };
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
// ─── LLM helpers ───────────────────────────────────────────────────────────
|
|
85
|
-
|
|
86
|
-
const MODEL_ALIASES: Record<string, string> = {
|
|
87
|
-
haiku: 'claude-haiku-4-5-20251001',
|
|
88
|
-
sonnet: 'claude-sonnet-4-6',
|
|
89
|
-
opus: 'claude-opus-4-6',
|
|
90
|
-
};
|
|
91
|
-
const DEFAULT_MODEL = 'claude-haiku-4-5-20251001';
|
|
92
|
-
|
|
93
|
-
function readAnthropicApiKey(): string {
|
|
94
|
-
const profilesPath = join(homedir(), '.openclaw', 'agents', 'main', 'agent', 'auth-profiles.json');
|
|
95
|
-
let parsed: {
|
|
96
|
-
profiles?: Record<string, { provider?: string; key?: string }>;
|
|
97
|
-
lastGood?: Record<string, string>;
|
|
98
|
-
};
|
|
99
|
-
try {
|
|
100
|
-
parsed = JSON.parse(readFileSync(profilesPath, 'utf-8')) as typeof parsed;
|
|
101
|
-
} catch (err) {
|
|
102
|
-
throw new Error(
|
|
103
|
-
`WorkflowSkill: could not read OpenClaw auth profiles from ${profilesPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
104
|
-
);
|
|
105
|
-
}
|
|
106
|
-
const profiles = parsed.profiles ?? {};
|
|
107
|
-
const lastGoodName = parsed.lastGood?.['anthropic'];
|
|
108
|
-
const profile = lastGoodName
|
|
109
|
-
? profiles[lastGoodName]
|
|
110
|
-
: Object.values(profiles).find((p) => p.provider === 'anthropic');
|
|
111
|
-
if (!profile?.key) {
|
|
112
|
-
throw new Error(
|
|
113
|
-
`WorkflowSkill: no anthropic profile found in ${profilesPath}. Add a profile with provider "anthropic" and a key.`,
|
|
114
|
-
);
|
|
115
|
-
}
|
|
116
|
-
return profile.key;
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
const ANTHROPIC_TIMEOUT_MS = 60_000;
|
|
120
|
-
|
|
121
|
-
async function callAnthropic(apiKey: string, model: string, prompt: string): Promise<string> {
|
|
122
|
-
const resolvedModel = MODEL_ALIASES[model] ?? model;
|
|
123
|
-
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
124
|
-
method: 'POST',
|
|
125
|
-
headers: {
|
|
126
|
-
'Content-Type': 'application/json',
|
|
127
|
-
'x-api-key': apiKey,
|
|
128
|
-
'anthropic-version': '2023-06-01',
|
|
129
|
-
},
|
|
130
|
-
body: JSON.stringify({
|
|
131
|
-
model: resolvedModel,
|
|
132
|
-
max_tokens: 8192,
|
|
133
|
-
messages: [{ role: 'user', content: prompt }],
|
|
134
|
-
}),
|
|
135
|
-
signal: AbortSignal.timeout(ANTHROPIC_TIMEOUT_MS),
|
|
136
|
-
});
|
|
137
|
-
if (!response.ok) {
|
|
138
|
-
const body = await response.text().catch(() => '');
|
|
139
|
-
throw new Error(`Anthropic API error ${response.status}: ${body}`);
|
|
140
|
-
}
|
|
141
|
-
const data = (await response.json()) as {
|
|
142
|
-
content: Array<{ type: string; text: string }>;
|
|
143
|
-
};
|
|
144
|
-
const text = data.content.find((b) => b.type === 'text')?.text ?? '';
|
|
145
|
-
return text;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
84
|
// ─── Plugin entry point ────────────────────────────────────────────────────
|
|
149
85
|
|
|
150
86
|
export default {
|
|
151
|
-
id: 'workflowskill',
|
|
87
|
+
id: 'openclaw-workflowskill',
|
|
152
88
|
|
|
153
89
|
register(api: PluginApi): void {
|
|
154
90
|
const workspace = api?.config?.agents?.defaults?.workspace;
|
|
@@ -291,10 +227,7 @@ export default {
|
|
|
291
227
|
required: ['prompt'],
|
|
292
228
|
},
|
|
293
229
|
execute: async (_id, params) => {
|
|
294
|
-
|
|
295
|
-
const apiKey = readAnthropicApiKey();
|
|
296
|
-
const text = await callAnthropic(apiKey, model, prompt);
|
|
297
|
-
return toContent({ text });
|
|
230
|
+
return toContent(await llmHandler(params as { prompt: string; model?: string }));
|
|
298
231
|
},
|
|
299
232
|
});
|
|
300
233
|
|
package/lib/openclaw-context.md
CHANGED
|
@@ -25,3 +25,12 @@ Always set `"model": "haiku"` on cron payloads — cron runs are lightweight orc
|
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
```
|
|
28
|
+
|
|
29
|
+
> **Important:** Plugin tools are not available in sessions by default.
|
|
30
|
+
> Before scheduling, ensure `tools.alsoAllow` includes `openclaw-workflowskill`:
|
|
31
|
+
>
|
|
32
|
+
> ```bash
|
|
33
|
+
> openclaw config set tools.alsoAllow '["openclaw-workflowskill"]'
|
|
34
|
+
> ```
|
|
35
|
+
>
|
|
36
|
+
> Without this, cron sessions cannot invoke `workflowskill_run` and will fail silently.
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
|
-
"id": "workflowskill",
|
|
2
|
+
"id": "openclaw-workflowskill",
|
|
3
3
|
"name": "WorkflowSkill",
|
|
4
4
|
"description": "Author, validate, run, and review WorkflowSkill YAML workflows",
|
|
5
|
-
"version": "0.3.
|
|
5
|
+
"version": "0.3.3",
|
|
6
6
|
"skills": ["skills/workflowskill-author"],
|
|
7
7
|
"configSchema": {
|
|
8
8
|
"type": "object",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openclaw-workflowskill",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.3",
|
|
4
4
|
"description": "WorkflowSkill plugin for OpenClaw — author, validate, run, and review YAML workflows",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "index.ts",
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
]
|
|
36
36
|
},
|
|
37
37
|
"dependencies": {
|
|
38
|
-
"workflowskill": "^0.3.
|
|
38
|
+
"workflowskill": "^0.3.2"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|
|
41
41
|
"@types/node": "^25.3.0",
|
package/tools/llm.ts
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
// workflowskill_llm — call Anthropic LLM using OpenClaw's credential store.
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from 'node:fs';
|
|
4
|
+
import { homedir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
export interface LlmParams {
|
|
8
|
+
prompt: string;
|
|
9
|
+
model?: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface LlmResult {
|
|
13
|
+
text: string;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const MODEL_ALIASES: Record<string, string> = {
|
|
17
|
+
haiku: 'claude-haiku-4-5-20251001',
|
|
18
|
+
sonnet: 'claude-sonnet-4-6',
|
|
19
|
+
opus: 'claude-opus-4-6',
|
|
20
|
+
};
|
|
21
|
+
const DEFAULT_MODEL = 'claude-haiku-4-5-20251001';
|
|
22
|
+
const ANTHROPIC_TIMEOUT_MS = 60_000;
|
|
23
|
+
|
|
24
|
+
function readAnthropicApiKey(): string {
|
|
25
|
+
const profilesPath = join(homedir(), '.openclaw', 'agents', 'main', 'agent', 'auth-profiles.json');
|
|
26
|
+
let parsed: {
|
|
27
|
+
profiles?: Record<string, { provider?: string; key?: string }>;
|
|
28
|
+
lastGood?: Record<string, string>;
|
|
29
|
+
};
|
|
30
|
+
try {
|
|
31
|
+
parsed = JSON.parse(readFileSync(profilesPath, 'utf-8')) as typeof parsed;
|
|
32
|
+
} catch (err) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`WorkflowSkill: could not read OpenClaw auth profiles from ${profilesPath}: ${err instanceof Error ? err.message : String(err)}`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
const profiles = parsed.profiles ?? {};
|
|
38
|
+
const lastGoodName = parsed.lastGood?.['anthropic'];
|
|
39
|
+
const profile = lastGoodName
|
|
40
|
+
? profiles[lastGoodName]
|
|
41
|
+
: Object.values(profiles).find((p) => p.provider === 'anthropic');
|
|
42
|
+
if (!profile?.key) {
|
|
43
|
+
throw new Error(
|
|
44
|
+
`WorkflowSkill: no anthropic profile found in ${profilesPath}. Add a profile with provider "anthropic" and a key.`,
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
return profile.key;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
async function callAnthropic(apiKey: string, model: string, prompt: string): Promise<string> {
|
|
51
|
+
const resolvedModel = MODEL_ALIASES[model] ?? model;
|
|
52
|
+
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
|
53
|
+
method: 'POST',
|
|
54
|
+
headers: {
|
|
55
|
+
'Content-Type': 'application/json',
|
|
56
|
+
'x-api-key': apiKey,
|
|
57
|
+
'anthropic-version': '2023-06-01',
|
|
58
|
+
},
|
|
59
|
+
body: JSON.stringify({
|
|
60
|
+
model: resolvedModel,
|
|
61
|
+
max_tokens: 8192,
|
|
62
|
+
messages: [{ role: 'user', content: prompt }],
|
|
63
|
+
}),
|
|
64
|
+
signal: AbortSignal.timeout(ANTHROPIC_TIMEOUT_MS),
|
|
65
|
+
});
|
|
66
|
+
if (!response.ok) {
|
|
67
|
+
const body = await response.text().catch(() => '');
|
|
68
|
+
throw new Error(`Anthropic API error ${response.status}: ${body}`);
|
|
69
|
+
}
|
|
70
|
+
const data = (await response.json()) as {
|
|
71
|
+
content: Array<{ type: string; text: string }>;
|
|
72
|
+
};
|
|
73
|
+
return data.content.find((b) => b.type === 'text')?.text ?? '';
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export async function llmHandler(params: LlmParams): Promise<LlmResult> {
|
|
77
|
+
const { prompt, model = DEFAULT_MODEL } = params;
|
|
78
|
+
const apiKey = readAnthropicApiKey();
|
|
79
|
+
const text = await callAnthropic(apiKey, model, prompt);
|
|
80
|
+
return { text };
|
|
81
|
+
}
|