@artflo-ai/artflo-openclaw-plugin 0.0.5 → 0.0.7
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/dist/index.js +17 -10
- package/dist/src/config.js +0 -1
- package/dist/src/constants.js +1 -2
- package/dist/src/core/api/api-base.js +1 -1
- package/dist/src/core/api/api-paths.js +2 -5
- package/dist/src/core/config/fetch-user-status.js +34 -0
- package/dist/src/core/config/model-config-transformer.js +4 -8
- package/dist/src/tools/register-tools.js +41 -11
- package/package.json +7 -2
- package/skills/artflo-canvas/SKILL.md +4 -3
- package/dist/src/core/config/fetch-vip-info.js +0 -31
package/dist/index.js
CHANGED
|
@@ -2,6 +2,7 @@ import { createPluginConfig } from './src/config.js';
|
|
|
2
2
|
import { createSessionRegistryService } from './src/services/canvas-session-registry.js';
|
|
3
3
|
import { registerArtfloTools } from './src/tools/register-tools.js';
|
|
4
4
|
import { fetchClientParams } from './src/core/config/fetch-client-params.js';
|
|
5
|
+
import { fetchUserStatus } from './src/core/config/fetch-user-status.js';
|
|
5
6
|
import { PLUGIN_DESCRIPTION, PLUGIN_ID, PLUGIN_NAME } from './src/constants.js';
|
|
6
7
|
const pluginConfigSchema = {
|
|
7
8
|
type: 'object',
|
|
@@ -69,19 +70,25 @@ const plugin = {
|
|
|
69
70
|
workspaceDir: api.rootDir,
|
|
70
71
|
sessions: sessions.registry,
|
|
71
72
|
});
|
|
72
|
-
// ── 每次 prompt 构建时注入 Artflo
|
|
73
|
-
api.on('before_prompt_build', () => {
|
|
73
|
+
// ── 每次 prompt 构建时注入 Artflo 配置和用户状态 ──────────────
|
|
74
|
+
api.on('before_prompt_build', async () => {
|
|
74
75
|
const masked = config.apiKey
|
|
75
76
|
? `****${config.apiKey.slice(-4)}`
|
|
76
77
|
: '未配置';
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
78
|
+
const lines = [`[Artflo Plugin] env=${config.env}, apiKey=${masked}, tz=${config.timeZone}, country=${config.countryCode}`];
|
|
79
|
+
if (!config.apiKey) {
|
|
80
|
+
lines.push('API Key 未配置,请引导用户通过 artflo_set_api_key 设置。');
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
try {
|
|
84
|
+
const status = await fetchUserStatus(config);
|
|
85
|
+
lines.push(`plan=${status.plan}, subscribed=${status.isSubscribed}, credits=${status.credits}`);
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
lines.push('API Key 已配置,用户状态获取失败。');
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
return { appendSystemContext: lines.join(' | ') };
|
|
85
92
|
});
|
|
86
93
|
},
|
|
87
94
|
};
|
package/dist/src/config.js
CHANGED
|
@@ -41,7 +41,6 @@ export function createPluginConfig(api) {
|
|
|
41
41
|
return {
|
|
42
42
|
env,
|
|
43
43
|
apiBaseUrl: urls.apiBaseUrl,
|
|
44
|
-
vipApiBaseUrl: urls.vipApiBaseUrl,
|
|
45
44
|
webApiBaseUrl: urls.webApiBaseUrl,
|
|
46
45
|
apiKey: readString(pluginConfig.apiKey, ''),
|
|
47
46
|
appKey: readString(pluginConfig.appKey, ''),
|
package/dist/src/constants.js
CHANGED
|
@@ -4,12 +4,10 @@ export const PLUGIN_DESCRIPTION = 'Expose deterministic Artflo canvas tools over
|
|
|
4
4
|
export const ENV_CONFIG = {
|
|
5
5
|
release: {
|
|
6
6
|
apiBaseUrl: 'https://webapi.artflo.ai',
|
|
7
|
-
vipApiBaseUrl: 'https://api-h5-sub.starii.com',
|
|
8
7
|
webApiBaseUrl: 'https://artflo.ai',
|
|
9
8
|
},
|
|
10
9
|
test: {
|
|
11
10
|
apiBaseUrl: 'https://testwebapi.artflo.ai',
|
|
12
|
-
vipApiBaseUrl: 'https://pre-api-h5-sub.starii.com',
|
|
13
11
|
webApiBaseUrl: 'https://test.artflo.ai',
|
|
14
12
|
},
|
|
15
13
|
};
|
|
@@ -33,4 +31,5 @@ export const ARTFLO_TOOL_NAMES = {
|
|
|
33
31
|
listModels: 'artflo_canvas_list_models',
|
|
34
32
|
uploadFile: 'artflo_upload_file',
|
|
35
33
|
setApiKey: 'artflo_set_api_key',
|
|
34
|
+
getUserStatus: 'artflo_get_user_status',
|
|
36
35
|
};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
// Re-export from api-paths for backward compatibility
|
|
2
|
-
export { buildApiUrl, buildWsUrl,
|
|
2
|
+
export { buildApiUrl, buildWsUrl, buildWebUrl } from './api-paths.js';
|
|
@@ -8,8 +8,8 @@ export const WS_CANVAS = '/canvas/ws';
|
|
|
8
8
|
/** apiBaseUrl paths */
|
|
9
9
|
export const API_CREATE_CANVAS = '/canvas';
|
|
10
10
|
export const API_UPLOAD_FILE = '/storage/obs/starii';
|
|
11
|
-
|
|
12
|
-
export const
|
|
11
|
+
export const API_CREDITS_REMAINING = '/user/credits_remaining';
|
|
12
|
+
export const API_SUBSCRIPTION_PLAN = '/user/subscription_plan';
|
|
13
13
|
/** webApiBaseUrl paths */
|
|
14
14
|
export const API_CLIENT_PARAMS = '/api/subscribe-client-params';
|
|
15
15
|
// ── Helper builders ─────────────────────────────────────────────────
|
|
@@ -19,9 +19,6 @@ function strip(url) {
|
|
|
19
19
|
export function buildApiUrl(config, path) {
|
|
20
20
|
return `${strip(config.apiBaseUrl)}${path}`;
|
|
21
21
|
}
|
|
22
|
-
export function buildVipUrl(config, path) {
|
|
23
|
-
return `${strip(config.vipApiBaseUrl)}${path}`;
|
|
24
|
-
}
|
|
25
22
|
export function buildWebUrl(config, path) {
|
|
26
23
|
return `${strip(config.webApiBaseUrl)}${path}`;
|
|
27
24
|
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Fetch user subscription plan and credits from Artflo API.
|
|
3
|
+
* Replaces the old fetch-vip-info.ts which used the starii VIP API.
|
|
4
|
+
*/
|
|
5
|
+
import { buildApiUrl, API_CREDITS_REMAINING, API_SUBSCRIPTION_PLAN } from '../api/api-paths.js';
|
|
6
|
+
export async function fetchUserStatus(config) {
|
|
7
|
+
const headers = {
|
|
8
|
+
'Content-Type': 'application/json',
|
|
9
|
+
'X-API-Key': config.apiKey,
|
|
10
|
+
};
|
|
11
|
+
const [creditsRes, planRes] = await Promise.all([
|
|
12
|
+
fetch(buildApiUrl(config, API_CREDITS_REMAINING), { headers }).catch(() => null),
|
|
13
|
+
fetch(buildApiUrl(config, API_SUBSCRIPTION_PLAN), { headers }).catch(() => null),
|
|
14
|
+
]);
|
|
15
|
+
let credits = 0;
|
|
16
|
+
if (creditsRes?.ok) {
|
|
17
|
+
const body = (await creditsRes.json());
|
|
18
|
+
if (body.code === 0 && body.data) {
|
|
19
|
+
credits = body.data.total_available ?? 0;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
let plan = 'free';
|
|
23
|
+
let isSubscribed = false;
|
|
24
|
+
let expireAt = 0;
|
|
25
|
+
if (planRes?.ok) {
|
|
26
|
+
const body = (await planRes.json());
|
|
27
|
+
if (body.code === 0 && body.data) {
|
|
28
|
+
plan = body.data.plan ?? 'free';
|
|
29
|
+
isSubscribed = body.data.is_valid === true;
|
|
30
|
+
expireAt = body.data.expire_at ?? 0;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return { plan, isSubscribed, credits, expireAt };
|
|
34
|
+
}
|
|
@@ -31,11 +31,9 @@ const OUTPUT_TOOL_DESCRIPTIONS = {
|
|
|
31
31
|
// ---------------------------------------------------------------------------
|
|
32
32
|
// Transform helpers
|
|
33
33
|
// ---------------------------------------------------------------------------
|
|
34
|
-
function transformModelItem(model,
|
|
34
|
+
function transformModelItem(model, isSubscribed) {
|
|
35
35
|
const available = !model.disable &&
|
|
36
|
-
(!model.subscribe_available ||
|
|
37
|
-
(model.subscribe_available && isVip) ||
|
|
38
|
-
model.is_free_limit);
|
|
36
|
+
(!model.subscribe_available || isSubscribed);
|
|
39
37
|
const item = {
|
|
40
38
|
key: model.model_key,
|
|
41
39
|
name: model.model_name,
|
|
@@ -43,7 +41,6 @@ function transformModelItem(model, isVip) {
|
|
|
43
41
|
isDefault: model.is_default,
|
|
44
42
|
available,
|
|
45
43
|
requiresSubscription: model.subscribe_available,
|
|
46
|
-
isFreeLimit: model.is_free_limit,
|
|
47
44
|
};
|
|
48
45
|
if (model.support_ratio?.length)
|
|
49
46
|
item.supportedRatios = model.support_ratio;
|
|
@@ -66,14 +63,14 @@ function transformModelItem(model, isVip) {
|
|
|
66
63
|
// ---------------------------------------------------------------------------
|
|
67
64
|
// Public API
|
|
68
65
|
// ---------------------------------------------------------------------------
|
|
69
|
-
export function transformCanvasConfig(config,
|
|
66
|
+
export function transformCanvasConfig(config, isSubscribed) {
|
|
70
67
|
const categories = [];
|
|
71
68
|
for (const [toolTypeStr, info] of Object.entries(CATEGORY_INFO)) {
|
|
72
69
|
const toolType = Number(toolTypeStr);
|
|
73
70
|
const models = config[info.configKey];
|
|
74
71
|
if (!Array.isArray(models) || models.length === 0)
|
|
75
72
|
continue;
|
|
76
|
-
const transformed = models.map((m) => transformModelItem(m,
|
|
73
|
+
const transformed = models.map((m) => transformModelItem(m, isSubscribed));
|
|
77
74
|
if (transformed.some((m) => m.available)) {
|
|
78
75
|
categories.push({
|
|
79
76
|
name: info.name,
|
|
@@ -90,7 +87,6 @@ export function transformCanvasConfig(config, isVip) {
|
|
|
90
87
|
toolType: tool.tool_type,
|
|
91
88
|
name: tool.name,
|
|
92
89
|
description: OUTPUT_TOOL_DESCRIPTIONS[tool.tool_type] ?? tool.name,
|
|
93
|
-
isFreeLimit: tool.is_free_limit,
|
|
94
90
|
});
|
|
95
91
|
}
|
|
96
92
|
}
|
|
@@ -16,7 +16,7 @@ function jsonResult(payload) {
|
|
|
16
16
|
import { ARTFLO_TOOL_NAMES } from '../constants.js';
|
|
17
17
|
import { createCanvas } from '../core/canvas/create-canvas.js';
|
|
18
18
|
import { uploadFile } from '../core/api/upload-file.js';
|
|
19
|
-
import {
|
|
19
|
+
import { fetchUserStatus } from '../core/config/fetch-user-status.js';
|
|
20
20
|
import { transformCanvasConfig } from '../core/config/model-config-transformer.js';
|
|
21
21
|
import { executePlan as runExecutePlan } from '../core/executor/execute-plan.js';
|
|
22
22
|
import { ExecutionTraceWriter } from '../core/executor/execution-trace.js';
|
|
@@ -153,9 +153,9 @@ export function registerArtfloTools(api, context) {
|
|
|
153
153
|
const session = context.sessions.getOrCreate(canvasId);
|
|
154
154
|
await session.connect({ canvasId });
|
|
155
155
|
const canvasConfig = session.getCanvasConfig();
|
|
156
|
-
const
|
|
156
|
+
const userStatus = await fetchUserStatus(context.config);
|
|
157
157
|
const llmConfig = canvasConfig
|
|
158
|
-
? transformCanvasConfig(canvasConfig,
|
|
158
|
+
? transformCanvasConfig(canvasConfig, userStatus.isSubscribed)
|
|
159
159
|
: null;
|
|
160
160
|
return jsonResult({
|
|
161
161
|
ok: true,
|
|
@@ -163,9 +163,9 @@ export function registerArtfloTools(api, context) {
|
|
|
163
163
|
data: {
|
|
164
164
|
canvasId,
|
|
165
165
|
subscription: {
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
166
|
+
plan: userStatus.plan,
|
|
167
|
+
isSubscribed: userStatus.isSubscribed,
|
|
168
|
+
credits: userStatus.credits,
|
|
169
169
|
},
|
|
170
170
|
models: llmConfig?.categories ?? [],
|
|
171
171
|
outputTools: llmConfig?.outputTools ?? [],
|
|
@@ -470,7 +470,7 @@ export function registerArtfloTools(api, context) {
|
|
|
470
470
|
const session = context.sessions.getOrCreate(payload.canvasId);
|
|
471
471
|
await session.connect({ canvasId: payload.canvasId });
|
|
472
472
|
const canvasConfig = session.getCanvasConfig();
|
|
473
|
-
const
|
|
473
|
+
const userStatus = await fetchUserStatus(context.config);
|
|
474
474
|
if (!canvasConfig) {
|
|
475
475
|
return jsonResult({
|
|
476
476
|
ok: false,
|
|
@@ -499,7 +499,7 @@ export function registerArtfloTools(api, context) {
|
|
|
499
499
|
const isVideo = VIDEO_TOOL_TYPES.has(info.toolType);
|
|
500
500
|
const mapped = models.map((m) => {
|
|
501
501
|
const modelAvailable = !m.disable &&
|
|
502
|
-
(!m.subscribe_available ||
|
|
502
|
+
(!m.subscribe_available || userStatus.isSubscribed);
|
|
503
503
|
// Build per-resolution pricing
|
|
504
504
|
const resolutionPricing = m.multi_config_price && Object.keys(m.multi_config_price).length > 0
|
|
505
505
|
? Object.fromEntries(Object.entries(m.multi_config_price).map(([key, price]) => {
|
|
@@ -544,11 +544,12 @@ export function registerArtfloTools(api, context) {
|
|
|
544
544
|
label: ARTFLO_TOOL_NAMES.listModels,
|
|
545
545
|
data: {
|
|
546
546
|
subscription: {
|
|
547
|
-
|
|
548
|
-
|
|
547
|
+
plan: userStatus.plan,
|
|
548
|
+
isSubscribed: userStatus.isSubscribed,
|
|
549
|
+
credits: userStatus.credits,
|
|
549
550
|
pricingUrl: 'https://artflo.ai/pricing',
|
|
550
551
|
},
|
|
551
|
-
hint:
|
|
552
|
+
hint: userStatus.isSubscribed
|
|
552
553
|
? undefined
|
|
553
554
|
: 'User is not subscribed. Models or resolutions marked requiresSubscription need a subscription. Visit https://artflo.ai/pricing to subscribe, or use free models.',
|
|
554
555
|
categories: result,
|
|
@@ -582,6 +583,35 @@ export function registerArtfloTools(api, context) {
|
|
|
582
583
|
});
|
|
583
584
|
},
|
|
584
585
|
});
|
|
586
|
+
api.registerTool({
|
|
587
|
+
name: ARTFLO_TOOL_NAMES.getUserStatus,
|
|
588
|
+
label: 'Artflo Get User Status',
|
|
589
|
+
description: 'Get the current user subscription plan and remaining credits. Does not require a canvas connection.',
|
|
590
|
+
parameters: Type.Object({}),
|
|
591
|
+
async execute(_id, params) {
|
|
592
|
+
return runWithToolTrace(ARTFLO_TOOL_NAMES.getUserStatus, params, async () => {
|
|
593
|
+
if (!context.config.apiKey) {
|
|
594
|
+
return jsonResult({
|
|
595
|
+
ok: false,
|
|
596
|
+
label: ARTFLO_TOOL_NAMES.getUserStatus,
|
|
597
|
+
error: 'API Key not configured. Use artflo_set_api_key to set it first.',
|
|
598
|
+
});
|
|
599
|
+
}
|
|
600
|
+
const status = await fetchUserStatus(context.config);
|
|
601
|
+
return jsonResult({
|
|
602
|
+
ok: true,
|
|
603
|
+
label: ARTFLO_TOOL_NAMES.getUserStatus,
|
|
604
|
+
data: {
|
|
605
|
+
plan: status.plan,
|
|
606
|
+
isSubscribed: status.isSubscribed,
|
|
607
|
+
credits: status.credits,
|
|
608
|
+
expireAt: status.expireAt,
|
|
609
|
+
pricingUrl: 'https://artflo.ai/pricing',
|
|
610
|
+
},
|
|
611
|
+
});
|
|
612
|
+
});
|
|
613
|
+
},
|
|
614
|
+
});
|
|
585
615
|
api.registerTool({
|
|
586
616
|
name: ARTFLO_TOOL_NAMES.setApiKey,
|
|
587
617
|
label: 'Artflo Set Config',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@artflo-ai/artflo-openclaw-plugin",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "OpenClaw plugin that connects directly to Artflo canvas WebSocket runtime.",
|
|
6
6
|
"keywords": [
|
|
@@ -20,7 +20,12 @@
|
|
|
20
20
|
"openclaw": {
|
|
21
21
|
"extensions": [
|
|
22
22
|
"./dist/index.js"
|
|
23
|
-
]
|
|
23
|
+
],
|
|
24
|
+
"install": {
|
|
25
|
+
"npmSpec": "@artflo-ai/artflo-openclaw-plugin",
|
|
26
|
+
"localPath": "extensions/artflo-openclaw-plugin",
|
|
27
|
+
"defaultChoice": "npm"
|
|
28
|
+
}
|
|
24
29
|
},
|
|
25
30
|
"publishConfig": {
|
|
26
31
|
"access": "public"
|
|
@@ -24,10 +24,10 @@ metadata:
|
|
|
24
24
|
|
|
25
25
|
## API Key 配置检查(首要步骤)
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
**如果 before_prompt_build 注入的上下文显示 `apiKey=未配置`,则:**
|
|
28
28
|
|
|
29
|
-
1.
|
|
30
|
-
2. 引导用户提供 API Key
|
|
29
|
+
1. **禁止调用除 `artflo_set_api_key` 以外的任何 Artflo 工具。** 没有 API Key,所有接口都会返回 401。
|
|
30
|
+
2. 引导用户提供 API Key:
|
|
31
31
|
|
|
32
32
|
> 你还没有配置 Artflo API Key,需要先设置才能使用画布功能。
|
|
33
33
|
>
|
|
@@ -48,6 +48,7 @@ metadata:
|
|
|
48
48
|
- 在假设画布运行时已就绪之前,先读取连接状态。
|
|
49
49
|
- 将插件配置视为 WebSocket 凭证和连接默认值的唯一真实来源。
|
|
50
50
|
- **禁止编造 API URL**:不要自行拼接或猜测 Artflo API 路径。所有 API 交互必须通过插件提供的 tool 完成(如 `artflo_canvas_get_last`、`artflo_canvas_create`、`artflo_upload_file` 等)。不要使用 `fetch` 或 `curl` 直接调用 Artflo API。
|
|
51
|
+
- **model_key 保密**:`model_key`(如 `Praline_2`、`ToffeeV1-Lite`)是内部标识,仅供构建 plan 使用。向用户展示模型时只使用 `model_name`(显示名称),绝不向用户暴露 `model_key`。
|
|
51
52
|
- **资源保存路径**:画布生成完成后需要下载保存的资源(图片、视频等),必须保存到 `/artflo/outbound` 目录。不要使用 `/tmp` 或其他临时目录。
|
|
52
53
|
- 当插件布局引擎可用时,不要手工编造节点坐标。
|
|
53
54
|
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Fetch user VIP / subscription status from the Artflo subscription API.
|
|
3
|
-
*/
|
|
4
|
-
import { buildVipUrl, API_VIP_INFO } from '../api/api-paths.js';
|
|
5
|
-
export async function fetchVipInfo(config) {
|
|
6
|
-
const url = new URL(buildVipUrl(config, API_VIP_INFO));
|
|
7
|
-
url.searchParams.set('app_id', config.vipAppId);
|
|
8
|
-
url.searchParams.set('vip_group', config.vipGroup);
|
|
9
|
-
const response = await fetch(url.toString(), {
|
|
10
|
-
method: 'GET',
|
|
11
|
-
headers: {
|
|
12
|
-
'X-API-Key': config.apiKey,
|
|
13
|
-
'app_id': config.vipAppId,
|
|
14
|
-
'country_code': config.countryCode,
|
|
15
|
-
'platform': '4',
|
|
16
|
-
'system_type': '2',
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
|
-
if (!response.ok) {
|
|
20
|
-
return { isVip: false, membershipLevel: 'Free', membershipName: 'Free' };
|
|
21
|
-
}
|
|
22
|
-
const body = (await response.json());
|
|
23
|
-
if (body.code !== 0 || !body.data) {
|
|
24
|
-
return { isVip: false, membershipLevel: 'Free', membershipName: 'Free' };
|
|
25
|
-
}
|
|
26
|
-
return {
|
|
27
|
-
isVip: body.data.is_vip === true || body.data.use_vip === true,
|
|
28
|
-
membershipLevel: body.data.membership?.level_name ?? 'Free',
|
|
29
|
-
membershipName: body.data.membership?.display_name ?? 'Free',
|
|
30
|
-
};
|
|
31
|
-
}
|