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 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. Install the plugin
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
- ### 2. Restart the gateway
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
- ### 3. Verify
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
- ### 4. Create a workflow
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
- ### 5. Schedule it
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
- const { prompt, model = DEFAULT_MODEL } = params as { prompt: string; model?: string };
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
 
@@ -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.
@@ -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.1",
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.1",
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.1"
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
+ }