@artflo-ai/artflo-openclaw-plugin 0.0.4 → 0.0.6

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
@@ -72,6 +72,7 @@ are auto-detected or fetched from the Artflo API at startup.
72
72
  - `artflo_canvas_wait_for_completion` — Wait for node completion
73
73
  - `artflo_canvas_execute_plan` — Execute a structured workflow plan
74
74
  - `artflo_canvas_create` — Create a new canvas
75
+ - `artflo_canvas_get_last` — Get last used canvas id
75
76
  - `artflo_canvas_list_models` — List available models with pricing
76
77
  - `artflo_upload_file` — Upload files to Artflo storage
77
78
  - `artflo_set_api_key` — Save API Key to config (used in chat onboarding)
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',
@@ -36,7 +37,7 @@ const plugin = {
36
37
  console.log(`[artflo] API Key not configured. Skipping initialization.`);
37
38
  const sessions = createSessionRegistryService(config);
38
39
  api.registerService(sessions.service);
39
- registerArtfloTools(api, { config, sessions: sessions.registry });
40
+ registerArtfloTools(api, { config, workspaceDir: api.rootDir, sessions: sessions.registry });
40
41
  return;
41
42
  }
42
43
  // ── 自动获取 appKey / vipAppId / vipGroup ─────────────────────────
@@ -66,8 +67,29 @@ const plugin = {
66
67
  api.registerService(sessions.service);
67
68
  registerArtfloTools(api, {
68
69
  config,
70
+ workspaceDir: api.rootDir,
69
71
  sessions: sessions.registry,
70
72
  });
73
+ // ── 每次 prompt 构建时注入 Artflo 配置和用户状态 ──────────────
74
+ api.on('before_prompt_build', async () => {
75
+ const masked = config.apiKey
76
+ ? `****${config.apiKey.slice(-4)}`
77
+ : '未配置';
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(' | ') };
92
+ });
71
93
  },
72
94
  };
73
95
  export default plugin;
@@ -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, ''),
@@ -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
  };
@@ -29,7 +27,9 @@ export const ARTFLO_TOOL_NAMES = {
29
27
  waitForCompletion: 'artflo_canvas_wait_for_completion',
30
28
  executePlan: 'artflo_canvas_execute_plan',
31
29
  createCanvas: 'artflo_canvas_create',
30
+ getLastCanvas: 'artflo_canvas_get_last',
32
31
  listModels: 'artflo_canvas_list_models',
33
32
  uploadFile: 'artflo_upload_file',
34
33
  setApiKey: 'artflo_set_api_key',
34
+ getUserStatus: 'artflo_get_user_status',
35
35
  };
@@ -1,2 +1,2 @@
1
1
  // Re-export from api-paths for backward compatibility
2
- export { buildApiUrl, buildWsUrl, buildVipUrl, buildWebUrl } from './api-paths.js';
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
- /** vipApiBaseUrl paths */
12
- export const API_VIP_INFO = '/h5/user/vip_info_by_group.json';
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, isVip) {
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, isVip) {
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, isVip));
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
  }
@@ -0,0 +1,37 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ const ARTFLO_DIR = '.artflo';
4
+ const MANIFEST_FILE = 'manifest.json';
5
+ function getManifestPath(workspaceDir) {
6
+ return path.join(workspaceDir, ARTFLO_DIR, MANIFEST_FILE);
7
+ }
8
+ async function ensureArtfloDir(workspaceDir) {
9
+ const artfloDir = path.join(workspaceDir, ARTFLO_DIR);
10
+ try {
11
+ await fs.mkdir(artfloDir, { recursive: true });
12
+ }
13
+ catch {
14
+ // ignore
15
+ }
16
+ }
17
+ export async function readManifest(workspaceDir) {
18
+ const manifestPath = getManifestPath(workspaceDir);
19
+ try {
20
+ const content = await fs.readFile(manifestPath, 'utf-8');
21
+ return JSON.parse(content);
22
+ }
23
+ catch {
24
+ return {};
25
+ }
26
+ }
27
+ export async function writeManifest(workspaceDir, manifest) {
28
+ await ensureArtfloDir(workspaceDir);
29
+ const manifestPath = getManifestPath(workspaceDir);
30
+ await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2), 'utf-8');
31
+ }
32
+ export async function setProjectId(workspaceDir, id) {
33
+ await writeManifest(workspaceDir, { project: id });
34
+ }
35
+ export async function getProjectId(workspaceDir) {
36
+ return (await readManifest(workspaceDir)).project;
37
+ }
@@ -16,12 +16,13 @@ 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 { fetchVipInfo } from '../core/config/fetch-vip-info.js';
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';
23
23
  import { parsePlanV2, validateAndFixPlan } from '../core/plan/validate-plan.js';
24
24
  import { writeToolTrace } from './tool-trace.js';
25
+ import { getProjectId, setProjectId } from '../services/manifest-service.js';
25
26
  export function registerArtfloTools(api, context) {
26
27
  api.registerTool({
27
28
  name: ARTFLO_TOOL_NAMES.getNode,
@@ -152,9 +153,9 @@ export function registerArtfloTools(api, context) {
152
153
  const session = context.sessions.getOrCreate(canvasId);
153
154
  await session.connect({ canvasId });
154
155
  const canvasConfig = session.getCanvasConfig();
155
- const vipInfo = await fetchVipInfo(context.config);
156
+ const userStatus = await fetchUserStatus(context.config);
156
157
  const llmConfig = canvasConfig
157
- ? transformCanvasConfig(canvasConfig, vipInfo.isVip)
158
+ ? transformCanvasConfig(canvasConfig, userStatus.isSubscribed)
158
159
  : null;
159
160
  return jsonResult({
160
161
  ok: true,
@@ -162,9 +163,9 @@ export function registerArtfloTools(api, context) {
162
163
  data: {
163
164
  canvasId,
164
165
  subscription: {
165
- isVip: vipInfo.isVip,
166
- membershipLevel: vipInfo.membershipLevel,
167
- membershipName: vipInfo.membershipName,
166
+ plan: userStatus.plan,
167
+ isSubscribed: userStatus.isSubscribed,
168
+ credits: userStatus.credits,
168
169
  },
169
170
  models: llmConfig?.categories ?? [],
170
171
  outputTools: llmConfig?.outputTools ?? [],
@@ -417,6 +418,9 @@ export function registerArtfloTools(api, context) {
417
418
  return runWithToolTrace(ARTFLO_TOOL_NAMES.createCanvas, params, async () => {
418
419
  const name = params.name || 'Untitled';
419
420
  const result = await createCanvas(context.config, name);
421
+ if (context.workspaceDir) {
422
+ await setProjectId(context.workspaceDir, result.id);
423
+ }
420
424
  return jsonResult({
421
425
  ok: true,
422
426
  label: ARTFLO_TOOL_NAMES.createCanvas,
@@ -429,6 +433,27 @@ export function registerArtfloTools(api, context) {
429
433
  });
430
434
  },
431
435
  });
436
+ api.registerTool({
437
+ name: ARTFLO_TOOL_NAMES.getLastCanvas,
438
+ label: 'Artflo Canvas Get Last',
439
+ description: 'Get the last used canvas id. Use this when user requests last used canvas.',
440
+ parameters: Type.Object({}),
441
+ async execute(_id, params) {
442
+ return runWithToolTrace(ARTFLO_TOOL_NAMES.getLastCanvas, params, async () => {
443
+ let canvasId = '';
444
+ if (context.workspaceDir) {
445
+ canvasId = (await getProjectId(context.workspaceDir)) ?? '';
446
+ }
447
+ return jsonResult({
448
+ ok: true,
449
+ label: ARTFLO_TOOL_NAMES.createCanvas,
450
+ data: {
451
+ canvasId: canvasId,
452
+ },
453
+ });
454
+ });
455
+ },
456
+ });
432
457
  api.registerTool({
433
458
  name: ARTFLO_TOOL_NAMES.listModels,
434
459
  label: 'Artflo Canvas List Models',
@@ -445,7 +470,7 @@ export function registerArtfloTools(api, context) {
445
470
  const session = context.sessions.getOrCreate(payload.canvasId);
446
471
  await session.connect({ canvasId: payload.canvasId });
447
472
  const canvasConfig = session.getCanvasConfig();
448
- const vipInfo = await fetchVipInfo(context.config);
473
+ const userStatus = await fetchUserStatus(context.config);
449
474
  if (!canvasConfig) {
450
475
  return jsonResult({
451
476
  ok: false,
@@ -474,7 +499,7 @@ export function registerArtfloTools(api, context) {
474
499
  const isVideo = VIDEO_TOOL_TYPES.has(info.toolType);
475
500
  const mapped = models.map((m) => {
476
501
  const modelAvailable = !m.disable &&
477
- (!m.subscribe_available || vipInfo.isVip || m.is_free_limit);
502
+ (!m.subscribe_available || userStatus.isSubscribed);
478
503
  // Build per-resolution pricing
479
504
  const resolutionPricing = m.multi_config_price && Object.keys(m.multi_config_price).length > 0
480
505
  ? Object.fromEntries(Object.entries(m.multi_config_price).map(([key, price]) => {
@@ -519,11 +544,12 @@ export function registerArtfloTools(api, context) {
519
544
  label: ARTFLO_TOOL_NAMES.listModels,
520
545
  data: {
521
546
  subscription: {
522
- isVip: vipInfo.isVip,
523
- membershipLevel: vipInfo.membershipLevel,
547
+ plan: userStatus.plan,
548
+ isSubscribed: userStatus.isSubscribed,
549
+ credits: userStatus.credits,
524
550
  pricingUrl: 'https://artflo.ai/pricing',
525
551
  },
526
- hint: vipInfo.isVip
552
+ hint: userStatus.isSubscribed
527
553
  ? undefined
528
554
  : 'User is not subscribed. Models or resolutions marked requiresSubscription need a subscription. Visit https://artflo.ai/pricing to subscribe, or use free models.',
529
555
  categories: result,
@@ -557,19 +583,54 @@ export function registerArtfloTools(api, context) {
557
583
  });
558
584
  },
559
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
+ const status = await fetchUserStatus(context.config);
594
+ return jsonResult({
595
+ ok: true,
596
+ label: ARTFLO_TOOL_NAMES.getUserStatus,
597
+ data: {
598
+ plan: status.plan,
599
+ isSubscribed: status.isSubscribed,
600
+ credits: status.credits,
601
+ expireAt: status.expireAt,
602
+ pricingUrl: 'https://artflo.ai/pricing',
603
+ },
604
+ });
605
+ });
606
+ },
607
+ });
560
608
  api.registerTool({
561
609
  name: ARTFLO_TOOL_NAMES.setApiKey,
562
- label: 'Artflo Set API Key',
563
- description: 'Save the user\'s Artflo API Key to the plugin config and restart the gateway. Call this when the user provides their API Key in chat. After success, the gateway will restart and all Artflo tools will be functional.',
610
+ label: 'Artflo Set Config',
611
+ description: 'Save Artflo plugin config (apiKey and/or env). Call this when the user provides their API Key or wants to switch environment. Both parameters are optional only provided values will be updated.',
564
612
  parameters: Type.Object({
565
- apiKey: Type.String({ minLength: 1, description: 'The Artflo API Key provided by the user.' }),
613
+ apiKey: Type.Optional(Type.String({ minLength: 1, description: 'Artflo API Key.' })),
614
+ env: Type.Optional(Type.String({ enum: ['release', 'test'], description: 'Environment: release or test.' })),
566
615
  }),
567
616
  async execute(_id, params) {
568
- const { apiKey } = params;
617
+ const { apiKey, env } = params;
618
+ if (!apiKey && !env) {
619
+ return jsonResult({
620
+ ok: false,
621
+ label: ARTFLO_TOOL_NAMES.setApiKey,
622
+ error: 'At least one of apiKey or env must be provided.',
623
+ });
624
+ }
569
625
  try {
570
626
  const cfg = api.runtime.config.loadConfig();
571
627
  const pluginEntry = cfg.plugins?.entries?.['artflo-openclaw-plugin'] ?? {};
572
628
  const existingConfig = pluginEntry.config ?? {};
629
+ const updatedConfig = { ...existingConfig };
630
+ if (apiKey)
631
+ updatedConfig.apiKey = apiKey;
632
+ if (env)
633
+ updatedConfig.env = env;
573
634
  const nextConfig = {
574
635
  ...cfg,
575
636
  plugins: {
@@ -579,10 +640,7 @@ export function registerArtfloTools(api, context) {
579
640
  'artflo-openclaw-plugin': {
580
641
  ...pluginEntry,
581
642
  enabled: true,
582
- config: {
583
- ...existingConfig,
584
- apiKey,
585
- },
643
+ config: updatedConfig,
586
644
  },
587
645
  },
588
646
  },
@@ -592,8 +650,8 @@ export function registerArtfloTools(api, context) {
592
650
  ok: true,
593
651
  label: ARTFLO_TOOL_NAMES.setApiKey,
594
652
  data: {
595
- message: 'API Key saved. Please restart gateway with: openclaw gateway restart',
596
- saved: true,
653
+ message: 'Config saved. Gateway will auto-reload.',
654
+ saved: { apiKey: apiKey ? true : false, env: env || null },
597
655
  },
598
656
  });
599
657
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@artflo-ai/artflo-openclaw-plugin",
3
- "version": "0.0.4",
3
+ "version": "0.0.6",
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"
@@ -34,8 +34,7 @@ metadata:
34
34
  > 请登录 [artflo.ai](https://artflo.ai),在设置页面获取 API Key,然后直接发给我,我帮你配置好。
35
35
 
36
36
  3. 当用户发来 API Key 后,调用 `artflo_set_api_key` 工具保存到配置中。
37
- 4. 保存成功后,提示用户运行 `openclaw gateway restart` 重启 gateway 使配置生效。
38
- 5. 重启后即可正常使用所有画布功能。
37
+ 4. 保存成功后,gateway 会自动重新加载配置,稍等片刻即可正常使用所有画布功能。
39
38
 
40
39
  ## 操作策略
41
40
 
@@ -48,7 +47,9 @@ metadata:
48
47
  - 规划时不要臆造底层画布数字节点类型。应使用 plan 中的节点类型,例如 `input`、`refine`、`process`、`selector`、`batch`、`crop`。
49
48
  - 在假设画布运行时已就绪之前,先读取连接状态。
50
49
  - 将插件配置视为 WebSocket 凭证和连接默认值的唯一真实来源。
51
- - **禁止编造 API URL**:不要自行拼接或猜测 Artflo API 路径。所有 API 交互必须通过插件提供的 tool 完成(如 `artflo_canvas_create`、`artflo_upload_file` 等)。不要使用 `fetch` 或 `curl` 直接调用 Artflo API。
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`。
52
+ - **资源保存路径**:画布生成完成后需要下载保存的资源(图片、视频等),必须保存到 `/artflo/outbound` 目录。不要使用 `/tmp` 或其他临时目录。
52
53
  - 当插件布局引擎可用时,不要手工编造节点坐标。
53
54
 
54
55
  ## 默认编辑规则
@@ -64,7 +65,7 @@ metadata:
64
65
 
65
66
  ## 推荐流程
66
67
 
67
- 1. 检查是否有可用的 canvas id。如果没有,调用 `artflo_canvas_create` 创建新画布,并将返回的 URL 分享给用户。
68
+ 1. 检查是否有可用的 canvas id。如果没有,调用 `artflo_canvas_get_last` 获取上次使用的画布,没有的话调用 `artflo_canvas_create` 创建新画布,并将返回的 URL 分享给用户。
68
69
  2. 调用 `artflo_canvas_connection_status` 检查连接状态。
69
70
  3. 如果未连接,使用目标 canvas id 调用 `artflo_canvas_connect`。
70
71
  4. **调用 `artflo_canvas_get_config` 获取可用模型列表、订阅状态和 Prompt Refine 分类。** 或者调用 `artflo_canvas_list_models` 获取更详细的模型信息(含分辨率价格、订阅要求等),适合需要向用户展示模型选择时使用。
@@ -7,8 +7,8 @@ Read `references/node-schema.json` first for node type constraints, then use thi
7
7
 
8
8
  Before executing any workflow, ensure you have a canvas:
9
9
 
10
- 1. **No canvas id available**: Call `artflo_canvas_create` to create a new canvas. Use the returned `canvasId` for all subsequent operations. Share the `url` with the user so they can view the canvas.
11
- 2. **Canvas id available**: Use the existing canvas. On the first task of each day, ask the user whether to create a new canvas or reuse the existing one. The canvas URL is returned by `artflo_canvas_create`.
10
+ 1. **No canvas id available**: First try `artflo_canvas_get_last` to retrieve the user's most recent canvas. If found, use its `canvasId` for subsequent operations. Only call `artflo_canvas_create` if `artflo_canvas_get_last` returns no canvas or fails. Share the canvas `url` with the user.
11
+ 2. **Canvas id available**: Use the existing canvas. On the first task of each day, ask the user whether to create a new canvas or reuse the existing one. The canvas URL is returned by `artflo_canvas_create` or `artflo_canvas_get_last`.
12
12
  3. **User requests new canvas**: Call `artflo_canvas_create` immediately.
13
13
 
14
14
  ## Core Principle
@@ -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
- }