openclaw-workflowskill 0.3.1 → 0.3.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +9 -5
- package/index.ts +3 -70
- package/openclaw.plugin.json +1 -1
- package/package.json +1 -1
- package/tools/llm.ts +81 -0
package/README.md
CHANGED
|
@@ -24,19 +24,23 @@ 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. Restart the gateway
|
|
34
38
|
|
|
35
39
|
```bash
|
|
36
40
|
openclaw gateway restart
|
|
37
41
|
```
|
|
38
42
|
|
|
39
|
-
###
|
|
43
|
+
### 4. Verify
|
|
40
44
|
|
|
41
45
|
```bash
|
|
42
46
|
openclaw plugins list
|
|
@@ -48,7 +52,7 @@ openclaw skills list
|
|
|
48
52
|
|
|
49
53
|
> **Note:** `workflowskill_llm` uses your Anthropic credentials from the main agent — no separate API key configuration needed.
|
|
50
54
|
|
|
51
|
-
###
|
|
55
|
+
### 5. Create a workflow
|
|
52
56
|
|
|
53
57
|
Just tell the agent what you want to automate:
|
|
54
58
|
|
|
@@ -60,7 +64,7 @@ Just tell the agent what you want to automate:
|
|
|
60
64
|
>
|
|
61
65
|
> Run complete: 4 AI stories found, summary drafted. Ready to schedule — want me to set up a daily cron at 8 AM?
|
|
62
66
|
|
|
63
|
-
###
|
|
67
|
+
### 6. Schedule it
|
|
64
68
|
|
|
65
69
|
Ask the agent to set up a cron job, or add one manually at `~/.openclaw/cron/jobs.json`:
|
|
66
70
|
|
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/openclaw.plugin.json
CHANGED
package/package.json
CHANGED
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
|
+
}
|