@joshualelon/clawdbot-skill-flow 0.4.0 → 0.5.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/package.json +1 -1
- package/src/config.ts +20 -1
- package/src/engine/hooks-loader.ts +18 -7
- package/src/state/flow-store.ts +7 -0
- package/src/state/history-store.ts +16 -2
- package/src/types.ts +2 -0
- package/src/validation.ts +2 -1
package/package.json
CHANGED
package/src/config.ts
CHANGED
|
@@ -2,6 +2,11 @@ import { z } from "zod";
|
|
|
2
2
|
|
|
3
3
|
export const SkillFlowConfigSchema = z
|
|
4
4
|
.object({
|
|
5
|
+
flowsDir: z
|
|
6
|
+
.string()
|
|
7
|
+
.optional()
|
|
8
|
+
.describe("Custom flows directory (default: ~/.clawdbot/flows)"),
|
|
9
|
+
|
|
5
10
|
sessionTimeoutMinutes: z
|
|
6
11
|
.number()
|
|
7
12
|
.int()
|
|
@@ -35,11 +40,25 @@ export const SkillFlowConfigSchema = z
|
|
|
35
40
|
|
|
36
41
|
export type SkillFlowConfig = z.infer<typeof SkillFlowConfigSchema>;
|
|
37
42
|
|
|
43
|
+
// Store parsed config for access by other modules
|
|
44
|
+
let pluginConfig: SkillFlowConfig | null = null;
|
|
45
|
+
|
|
38
46
|
/**
|
|
39
47
|
* Parse and validate plugin config with defaults
|
|
40
48
|
*/
|
|
41
49
|
export function parseSkillFlowConfig(
|
|
42
50
|
raw: unknown
|
|
43
51
|
): SkillFlowConfig {
|
|
44
|
-
|
|
52
|
+
pluginConfig = SkillFlowConfigSchema.parse(raw ?? {});
|
|
53
|
+
return pluginConfig;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Get the current plugin config (must call parseSkillFlowConfig first)
|
|
58
|
+
*/
|
|
59
|
+
export function getPluginConfig(): SkillFlowConfig {
|
|
60
|
+
if (!pluginConfig) {
|
|
61
|
+
throw new Error("Plugin config not initialized. Call parseSkillFlowConfig first.");
|
|
62
|
+
}
|
|
63
|
+
return pluginConfig;
|
|
45
64
|
}
|
|
@@ -10,6 +10,20 @@ import {
|
|
|
10
10
|
resolvePathSafely,
|
|
11
11
|
validatePathWithinBase,
|
|
12
12
|
} from "../security/path-validation.js";
|
|
13
|
+
import { getPluginConfig } from "../config.js";
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Get the flows directory path (same logic as flow-store.ts)
|
|
17
|
+
*/
|
|
18
|
+
function getFlowsDir(api: ClawdbotPluginApi): string {
|
|
19
|
+
const config = getPluginConfig();
|
|
20
|
+
if (config.flowsDir) {
|
|
21
|
+
const expandedPath = config.flowsDir.replace(/^~/, process.env.HOME || "~");
|
|
22
|
+
return path.resolve(expandedPath);
|
|
23
|
+
}
|
|
24
|
+
const stateDir = api.runtime.state.resolveStateDir();
|
|
25
|
+
return path.join(stateDir, "flows");
|
|
26
|
+
}
|
|
13
27
|
|
|
14
28
|
/**
|
|
15
29
|
* Load hooks from a file path
|
|
@@ -23,7 +37,7 @@ export async function loadHooks(
|
|
|
23
37
|
): Promise<FlowHooks | null> {
|
|
24
38
|
try {
|
|
25
39
|
// Validate path is within flows directory
|
|
26
|
-
const flowsDir =
|
|
40
|
+
const flowsDir = getFlowsDir(api);
|
|
27
41
|
validatePathWithinBase(hooksPath, flowsDir, "hooks file");
|
|
28
42
|
|
|
29
43
|
// Check if file exists
|
|
@@ -55,7 +69,7 @@ export async function loadStorageBackend(
|
|
|
55
69
|
): Promise<StorageBackend | null> {
|
|
56
70
|
try {
|
|
57
71
|
// Validate path is within flows directory
|
|
58
|
-
const flowsDir =
|
|
72
|
+
const flowsDir = getFlowsDir(api);
|
|
59
73
|
validatePathWithinBase(backendPath, flowsDir, "storage backend");
|
|
60
74
|
|
|
61
75
|
// Check if file exists
|
|
@@ -87,11 +101,8 @@ export function resolveFlowPath(
|
|
|
87
101
|
flowName: string,
|
|
88
102
|
relativePath: string
|
|
89
103
|
): string {
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
"flows",
|
|
93
|
-
flowName
|
|
94
|
-
);
|
|
104
|
+
const flowsDir = getFlowsDir(api);
|
|
105
|
+
const flowDir = path.join(flowsDir, flowName);
|
|
95
106
|
|
|
96
107
|
// Validate that relativePath doesn't escape flowDir
|
|
97
108
|
return resolvePathSafely(flowDir, relativePath, "flow path");
|
package/src/state/flow-store.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { promisify } from "node:util";
|
|
|
9
9
|
import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
|
|
10
10
|
import type { FlowMetadata } from "../types.js";
|
|
11
11
|
import { FlowMetadataSchema } from "../validation.js";
|
|
12
|
+
import { getPluginConfig } from "../config.js";
|
|
12
13
|
|
|
13
14
|
const lock = promisify(lockfile.lock);
|
|
14
15
|
const unlock = promisify(lockfile.unlock);
|
|
@@ -17,6 +18,12 @@ const unlock = promisify(lockfile.unlock);
|
|
|
17
18
|
* Get the flows directory path
|
|
18
19
|
*/
|
|
19
20
|
function getFlowsDir(api: ClawdbotPluginApi): string {
|
|
21
|
+
const config = getPluginConfig();
|
|
22
|
+
if (config.flowsDir) {
|
|
23
|
+
// Expand ~ to home directory
|
|
24
|
+
const expandedPath = config.flowsDir.replace(/^~/, process.env.HOME || "~");
|
|
25
|
+
return path.resolve(expandedPath);
|
|
26
|
+
}
|
|
20
27
|
const stateDir = api.runtime.state.resolveStateDir();
|
|
21
28
|
return path.join(stateDir, "flows");
|
|
22
29
|
}
|
|
@@ -7,17 +7,31 @@ import path from "node:path";
|
|
|
7
7
|
import type { ClawdbotPluginApi } from "clawdbot/plugin-sdk";
|
|
8
8
|
import type { FlowSession, FlowMetadata } from "../types.js";
|
|
9
9
|
import type { SkillFlowConfig } from "../config.js";
|
|
10
|
+
import { getPluginConfig } from "../config.js";
|
|
10
11
|
import {
|
|
11
12
|
loadStorageBackend,
|
|
12
13
|
resolveFlowPath,
|
|
13
14
|
} from "../engine/hooks-loader.js";
|
|
14
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Get the flows directory path (same logic as flow-store.ts)
|
|
18
|
+
*/
|
|
19
|
+
function getFlowsDir(api: ClawdbotPluginApi): string {
|
|
20
|
+
const config = getPluginConfig();
|
|
21
|
+
if (config.flowsDir) {
|
|
22
|
+
const expandedPath = config.flowsDir.replace(/^~/, process.env.HOME || "~");
|
|
23
|
+
return path.resolve(expandedPath);
|
|
24
|
+
}
|
|
25
|
+
const stateDir = api.runtime.state.resolveStateDir();
|
|
26
|
+
return path.join(stateDir, "flows");
|
|
27
|
+
}
|
|
28
|
+
|
|
15
29
|
/**
|
|
16
30
|
* Get history file path for a flow
|
|
17
31
|
*/
|
|
18
32
|
function getHistoryPath(api: ClawdbotPluginApi, flowName: string): string {
|
|
19
|
-
const
|
|
20
|
-
return path.join(
|
|
33
|
+
const flowsDir = getFlowsDir(api);
|
|
34
|
+
return path.join(flowsDir, flowName, "history.jsonl");
|
|
21
35
|
}
|
|
22
36
|
|
|
23
37
|
/**
|
package/src/types.ts
CHANGED
|
@@ -43,6 +43,8 @@ export interface FlowMetadata {
|
|
|
43
43
|
backend?: string; // Path to custom storage backend
|
|
44
44
|
builtin?: boolean; // Also write to JSONL (default: true)
|
|
45
45
|
};
|
|
46
|
+
// Allow extra fields (e.g., job config)
|
|
47
|
+
[key: string]: unknown;
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
export interface FlowSession {
|
package/src/validation.ts
CHANGED
|
@@ -58,6 +58,7 @@ const StorageSchema = z
|
|
|
58
58
|
.optional();
|
|
59
59
|
|
|
60
60
|
// Complete flow metadata schema
|
|
61
|
+
// Use .passthrough() to allow extra fields (e.g., job config)
|
|
61
62
|
export const FlowMetadataSchema = z.object({
|
|
62
63
|
name: z.string(),
|
|
63
64
|
description: z.string(),
|
|
@@ -67,7 +68,7 @@ export const FlowMetadataSchema = z.object({
|
|
|
67
68
|
triggers: TriggerSchema,
|
|
68
69
|
hooks: z.string().optional(),
|
|
69
70
|
storage: StorageSchema,
|
|
70
|
-
});
|
|
71
|
+
}).passthrough();
|
|
71
72
|
|
|
72
73
|
/**
|
|
73
74
|
* Normalize button input to Button object
|