@fieldwangai/agentflow 0.1.71 → 0.1.72

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.
@@ -7,7 +7,10 @@ import {
7
7
  expandAgentflowHomePath,
8
8
  getAgentflowDataRoot,
9
9
  getAgentflowDataRootOverride,
10
+ getAgentflowSkillsRoot,
11
+ getAgentflowSkillsRootOverride,
10
12
  writeAgentflowDataRootOverride,
13
+ writeAgentflowSkillsRootOverride,
11
14
  } from "./paths.mjs";
12
15
 
13
16
  function defaultAgentflowDataRoot() {
@@ -28,6 +31,14 @@ function normalizeDataRoot(raw) {
28
31
  return dataRoot;
29
32
  }
30
33
 
34
+ function normalizeSkillsRoot(raw) {
35
+ const skillsRoot = expandAgentflowHomePath(raw);
36
+ if (skillsRoot && !path.isAbsolute(skillsRoot)) {
37
+ throw new Error("skillsRoot must be an absolute path");
38
+ }
39
+ return skillsRoot;
40
+ }
41
+
31
42
  function copyEntry(src, dest) {
32
43
  const stat = fs.lstatSync(src);
33
44
  if (stat.isSymbolicLink()) {
@@ -45,6 +56,12 @@ function copyEntry(src, dest) {
45
56
  fs.copyFileSync(src, dest);
46
57
  }
47
58
 
59
+ function copyEntryIfMissing(src, dest) {
60
+ if (fs.existsSync(dest)) return false;
61
+ copyEntry(src, dest);
62
+ return true;
63
+ }
64
+
48
65
  function migrateDataRoot(oldRoot, newRoot) {
49
66
  const from = path.resolve(oldRoot);
50
67
  const to = path.resolve(newRoot);
@@ -63,9 +80,49 @@ function migrateDataRoot(oldRoot, newRoot) {
63
80
  return { migrated: true, copied: true };
64
81
  }
65
82
 
83
+ function legacyCodexSkillsRoot() {
84
+ return path.join(os.homedir(), ".codex", "skills");
85
+ }
86
+
87
+ function migrateSkillsRoot(oldRoot, newRoot) {
88
+ const from = path.resolve(oldRoot);
89
+ const to = path.resolve(newRoot);
90
+ if (from === to) return { migrated: false, copied: false };
91
+ if (pathInside(from, to)) {
92
+ throw new Error("skillsRoot target cannot be inside the current skills root");
93
+ }
94
+ if (pathInside(to, from)) {
95
+ throw new Error("skillsRoot target cannot be a parent of the current skills root");
96
+ }
97
+ fs.mkdirSync(to, { recursive: true });
98
+ if (!fs.existsSync(from)) return { migrated: true, copied: false };
99
+ for (const name of fs.readdirSync(from)) {
100
+ copyEntry(path.join(from, name), path.join(to, name));
101
+ }
102
+ return { migrated: true, copied: true };
103
+ }
104
+
105
+ function migrateLegacyCodexSkills(targetRoot) {
106
+ const from = legacyCodexSkillsRoot();
107
+ const to = path.resolve(targetRoot);
108
+ const source = path.resolve(from);
109
+ if (source === to || !fs.existsSync(source)) return { migrated: false, copied: 0, source };
110
+ if (pathInside(source, to) || pathInside(to, source)) return { migrated: false, copied: 0, source };
111
+ fs.mkdirSync(to, { recursive: true });
112
+ let copied = 0;
113
+ for (const name of fs.readdirSync(source)) {
114
+ if (copyEntryIfMissing(path.join(source, name), path.join(to, name))) copied += 1;
115
+ }
116
+ return { migrated: copied > 0, copied, source };
117
+ }
118
+
66
119
  export function readAdminStorageConfig() {
67
120
  const envDataRoot = normalizeDataRoot(process.env.AGENTFLOW_HOME || "");
121
+ const envSkillsRoot = normalizeSkillsRoot(process.env.AGENTFLOW_SKILLS_ROOT || "");
68
122
  const configuredDataRoot = getAgentflowDataRootOverride();
123
+ const configuredSkillsRoot = getAgentflowSkillsRootOverride();
124
+ const skillsRoot = getAgentflowSkillsRoot();
125
+ const legacySkillsRoot = legacyCodexSkillsRoot();
69
126
  return {
70
127
  version: 1,
71
128
  dataRoot: getAgentflowDataRoot(),
@@ -73,21 +130,49 @@ export function readAdminStorageConfig() {
73
130
  defaultDataRoot: defaultAgentflowDataRoot(),
74
131
  envDataRoot,
75
132
  envLocked: Boolean(envDataRoot),
133
+ skillsRoot,
134
+ configuredSkillsRoot,
135
+ defaultSkillsRoot: path.join(getAgentflowDataRoot(), "skills"),
136
+ envSkillsRoot,
137
+ skillsEnvLocked: Boolean(envSkillsRoot),
138
+ legacySkillsRoot,
139
+ legacySkillsRootExists: fs.existsSync(legacySkillsRoot),
76
140
  configPath: AGENTFLOW_HOME_CONFIG_PATH,
77
141
  };
78
142
  }
79
143
 
80
144
  export function writeAdminStorageConfig(config = {}) {
81
145
  const current = readAdminStorageConfig();
82
- if (current.envLocked) {
83
- throw new Error("AGENTFLOW_HOME is set; update the environment variable instead of Admin Settings");
84
- }
85
146
  const nextRoot = normalizeDataRoot(config.dataRoot ?? "");
86
147
  if (!nextRoot) throw new Error("dataRoot is required");
87
- const migration = migrateDataRoot(current.dataRoot, nextRoot);
88
- writeAgentflowDataRootOverride(nextRoot);
148
+ let migration = { migrated: false, copied: false };
149
+ if (current.envLocked) {
150
+ if (path.resolve(nextRoot) !== path.resolve(current.dataRoot)) {
151
+ throw new Error("AGENTFLOW_HOME is set; update the environment variable instead of Admin Settings");
152
+ }
153
+ } else {
154
+ migration = migrateDataRoot(current.dataRoot, nextRoot);
155
+ writeAgentflowDataRootOverride(nextRoot);
156
+ }
157
+ const afterDataRoot = readAdminStorageConfig();
158
+ if (afterDataRoot.skillsEnvLocked) {
159
+ return {
160
+ ...afterDataRoot,
161
+ migration,
162
+ skillsMigration: { skipped: true, reason: "AGENTFLOW_SKILLS_ROOT is set" },
163
+ };
164
+ }
165
+ const nextSkillsRoot = normalizeSkillsRoot(config.skillsRoot ?? afterDataRoot.defaultSkillsRoot);
166
+ if (!nextSkillsRoot) throw new Error("skillsRoot is required");
167
+ const skillsMigration = migrateSkillsRoot(current.skillsRoot, nextSkillsRoot);
168
+ writeAgentflowSkillsRootOverride(nextSkillsRoot);
169
+ const legacySkillsMigration = config.migrateLegacySkills === false
170
+ ? { migrated: false, copied: 0, source: legacyCodexSkillsRoot(), skipped: true }
171
+ : migrateLegacyCodexSkills(nextSkillsRoot);
89
172
  return {
90
173
  ...readAdminStorageConfig(),
91
174
  migration,
175
+ skillsMigration,
176
+ legacySkillsMigration,
92
177
  };
93
178
  }
@@ -137,18 +137,26 @@ export function readComposerSkillDetail(packageRoot, workspaceRoot, keyOrName) {
137
137
  return registryReadSkillDetail(packageRoot, workspaceRoot, keyOrName);
138
138
  }
139
139
 
140
+ function skillNameFromKey(keyOrName) {
141
+ const value = String(keyOrName || "").trim();
142
+ if (!value) return "";
143
+ const idx = value.indexOf(":");
144
+ return idx >= 0 ? value.slice(idx + 1).trim() : value;
145
+ }
146
+
140
147
  export function loadResourcesForSkillKeys(skillKeys, packageRoot, workspaceRoot) {
141
148
  if (!Array.isArray(skillKeys) || skillKeys.length === 0) {
142
149
  return { skills: [], references: [], skillsHint: "", hasContext: false };
143
150
  }
144
151
  const wanted = new Set(skillKeys.map((x) => String(x || "").trim()).filter(Boolean));
145
152
  if (wanted.size === 0) return { skills: [], references: [], skillsHint: "", hasContext: false };
153
+ const wantedNames = new Set(Array.from(wanted).map(skillNameFromKey).filter(Boolean));
146
154
 
147
155
  const exactByKey = new Map(registryListSkills(packageRoot, workspaceRoot).map((item) => [item.key, item]));
148
156
  const candidateItems = [];
149
157
  const seenKeys = new Set();
150
158
  for (const item of registryListUniqueSkills(packageRoot, workspaceRoot)) {
151
- if (!wanted.has(item.key) && !wanted.has(item.name)) continue;
159
+ if (!wanted.has(item.key) && !wanted.has(item.name) && !wantedNames.has(item.name)) continue;
152
160
  candidateItems.push(item);
153
161
  seenKeys.add(item.key);
154
162
  }
package/bin/lib/paths.mjs CHANGED
@@ -49,6 +49,11 @@ export function getAgentflowDataRootOverride() {
49
49
  return expandAgentflowHomePath(config.dataRoot || "");
50
50
  }
51
51
 
52
+ export function getAgentflowSkillsRootOverride() {
53
+ const config = readAgentflowHomeConfig();
54
+ return expandAgentflowHomePath(config.skillsRoot || "");
55
+ }
56
+
52
57
  export function writeAgentflowDataRootOverride(dataRoot) {
53
58
  const nextRoot = expandAgentflowHomePath(dataRoot);
54
59
  if (nextRoot && !path.isAbsolute(nextRoot)) {
@@ -62,6 +67,19 @@ export function writeAgentflowDataRootOverride(dataRoot) {
62
67
  return nextRoot;
63
68
  }
64
69
 
70
+ export function writeAgentflowSkillsRootOverride(skillsRoot) {
71
+ const nextRoot = expandAgentflowHomePath(skillsRoot);
72
+ if (nextRoot && !path.isAbsolute(nextRoot)) {
73
+ throw new Error("skillsRoot must be an absolute path");
74
+ }
75
+ const current = readAgentflowHomeConfig();
76
+ const next = { ...current, skillsRoot: nextRoot };
77
+ if (!nextRoot) delete next.skillsRoot;
78
+ fs.mkdirSync(path.dirname(AGENTFLOW_HOME_CONFIG_PATH), { recursive: true });
79
+ fs.writeFileSync(AGENTFLOW_HOME_CONFIG_PATH, JSON.stringify(next, null, 2) + "\n", "utf-8");
80
+ return nextRoot;
81
+ }
82
+
65
83
  export function getAgentflowDataRoot() {
66
84
  const env = process.env.AGENTFLOW_HOME;
67
85
  if (env != null && String(env).trim() !== "") {
@@ -72,6 +90,16 @@ export function getAgentflowDataRoot() {
72
90
  return path.join(os.homedir(), "agentflow");
73
91
  }
74
92
 
93
+ export function getAgentflowSkillsRoot() {
94
+ const env = process.env.AGENTFLOW_SKILLS_ROOT;
95
+ if (env != null && String(env).trim() !== "") {
96
+ return expandAgentflowHomePath(env);
97
+ }
98
+ const configured = getAgentflowSkillsRootOverride();
99
+ if (configured) return configured;
100
+ return path.join(getAgentflowDataRoot(), "skills");
101
+ }
102
+
75
103
  export const AGENTFLOW_DEFAULT_USER_ID = "";
76
104
 
77
105
  export function sanitizeAgentflowUserId(userId) {
@@ -2,6 +2,7 @@ import fs from "fs";
2
2
  import os from "os";
3
3
  import path from "path";
4
4
  import yaml from "js-yaml";
5
+ import { getAgentflowSkillsRoot } from "./paths.mjs";
5
6
 
6
7
  const fileCache = new Map();
7
8
  const CACHE_TTL_MS = 60_000;
@@ -9,10 +10,11 @@ const SOURCE_PRIORITY = new Map([
9
10
  ["workspace-agents", 100],
10
11
  ["workspace-codex", 95],
11
12
  ["workspace-cursor", 90],
13
+ ["agentflow-root", 85],
12
14
  ["builtin", 80],
13
- ["global-agents", 70],
14
- ["global-codex", 65],
15
- ["global-cursor", 60],
15
+ ["global-agents", 55],
16
+ ["global-codex", 50],
17
+ ["global-cursor", 45],
16
18
  ]);
17
19
 
18
20
  function readFileCached(absPath) {
@@ -28,6 +30,10 @@ function readFileCached(absPath) {
28
30
  }
29
31
  }
30
32
 
33
+ export function clearSkillRegistryCache() {
34
+ fileCache.clear();
35
+ }
36
+
31
37
  export function stripSkillFrontmatter(content) {
32
38
  const raw = String(content || "");
33
39
  const match = raw.match(/^---\s*\r?\n[\s\S]*?\r?\n---\s*\r?\n?/);
@@ -109,11 +115,17 @@ export function defaultSkillSources(packageRoot, workspaceRoot) {
109
115
  },
110
116
  ];
111
117
  if (workspaceRoot) sources.push(...workspaceSkillSources(workspaceRoot, "workspace", "工作区"));
118
+ sources.push({
119
+ source: "agentflow-root",
120
+ sourceLabel: "AgentFlow Skills",
121
+ dir: getAgentflowSkillsRoot(),
122
+ installedBy: "agentflow-admin",
123
+ });
112
124
  const home = os.homedir();
113
125
  sources.push(
114
- { source: "global-agents", sourceLabel: "全局 .agents", dir: path.join(home, ".agents", "skills"), installedBy: "global" },
115
- { source: "global-cursor", sourceLabel: "全局 .cursor", dir: path.join(home, ".cursor", "skills"), installedBy: "global" },
116
- { source: "global-codex", sourceLabel: "全局 .codex", dir: path.join(home, ".codex", "skills"), installedBy: "skillhub" },
126
+ { source: "global-agents", sourceLabel: "Legacy ~/.agents", dir: path.join(home, ".agents", "skills"), installedBy: "global" },
127
+ { source: "global-cursor", sourceLabel: "Legacy ~/.cursor", dir: path.join(home, ".cursor", "skills"), installedBy: "global" },
128
+ { source: "global-codex", sourceLabel: "Legacy ~/.codex", dir: path.join(home, ".codex", "skills"), installedBy: "skillhub-legacy" },
117
129
  );
118
130
  return sources;
119
131
  }
@@ -183,9 +195,10 @@ export function listUniqueSkills(packageRoot, workspaceRoot, opts = {}) {
183
195
  export function readSkillDetail(packageRoot, workspaceRoot, keyOrName) {
184
196
  const wanted = String(keyOrName || "").trim();
185
197
  if (!wanted) return null;
198
+ const wantedName = wanted.includes(":") ? wanted.slice(wanted.indexOf(":") + 1).trim() : wanted;
186
199
  const all = listSkills(packageRoot, workspaceRoot);
187
200
  const item = all.find((skill) => skill.key === wanted)
188
- || dedupeSkillsByName(all).find((skill) => skill.name === wanted);
201
+ || dedupeSkillsByName(all).find((skill) => skill.name === wanted || skill.name === wantedName);
189
202
  if (!item) return null;
190
203
  return item;
191
204
  }
@@ -45,6 +45,7 @@ import {
45
45
  PACKAGE_ROOT,
46
46
  ARCHIVED_PIPELINES_DIR_NAME,
47
47
  getAgentflowDataRoot,
48
+ getAgentflowSkillsRoot,
48
49
  getAgentflowUserConfigAbs,
49
50
  getAgentflowUserDataRoot,
50
51
  getUserPipelinesRoot,
@@ -62,6 +63,7 @@ import {
62
63
  buildSkillInjectionBlock,
63
64
  buildSkillCompactInjectionBlock,
64
65
  } from "./composer-skill-router.mjs";
66
+ import { clearSkillRegistryCache } from "./skill-registry.mjs";
65
67
  import { COMPOSER_NODE_SPEC_FILENAME } from "./composer-planner.mjs";
66
68
  import { listRecentRunsFromDisk } from "./recent-runs.mjs";
67
69
  import {
@@ -1134,6 +1136,22 @@ function normalizeSkillhubListPayload(raw) {
1134
1136
  }).filter((x) => x.name);
1135
1137
  }
1136
1138
 
1139
+ function skillhubManagedSkillsDir() {
1140
+ const dir = getAgentflowSkillsRoot();
1141
+ fs.mkdirSync(dir, { recursive: true });
1142
+ return dir;
1143
+ }
1144
+
1145
+ function skillhubListArgs(target, agent) {
1146
+ const normalizedTarget = String(target || "agentflow").trim();
1147
+ const normalizedAgent = String(agent || "codex").trim();
1148
+ const args = ["list", "--json"];
1149
+ if (normalizedTarget === "all") args.push("--all");
1150
+ else if (normalizedTarget === "legacy-global") args.push("--global", "--agent", normalizedAgent);
1151
+ else args.push("--dir", skillhubManagedSkillsDir());
1152
+ return args;
1153
+ }
1154
+
1137
1155
  function skillhubInstallArgs(payload, { uninstall = false } = {}) {
1138
1156
  const slug = String(payload?.slug || payload?.name || "").trim();
1139
1157
  if (!slug && !payload?.collection) return null;
@@ -1144,10 +1162,12 @@ function skillhubInstallArgs(payload, { uninstall = false } = {}) {
1144
1162
  args.push(slug);
1145
1163
  }
1146
1164
  if (payload?.skillId) args.push("--skill-id", String(payload.skillId).trim());
1147
- const target = String(payload?.target || "project").trim();
1165
+ const target = String(payload?.target || "agentflow").trim();
1148
1166
  const agent = String(payload?.agent || "codex").trim();
1149
- if (target === "global") {
1167
+ if (target === "global" || target === "legacy-global") {
1150
1168
  args.push("--global", "--agent", agent);
1169
+ } else if (target === "agentflow") {
1170
+ args.push("--dir", skillhubManagedSkillsDir());
1151
1171
  } else if (payload?.dir) {
1152
1172
  args.push("--dir", String(payload.dir).trim());
1153
1173
  }
@@ -5620,6 +5640,7 @@ export function startUiServer({
5620
5640
  }
5621
5641
  try {
5622
5642
  const config = writeAdminStorageConfig(payload?.config || payload || {});
5643
+ clearSkillRegistryCache();
5623
5644
  json(res, 200, { ok: true, config });
5624
5645
  } catch (e) {
5625
5646
  json(res, 400, { error: (e && e.message) || String(e) });
@@ -7058,17 +7079,19 @@ export function startUiServer({
7058
7079
  }
7059
7080
 
7060
7081
  if (req.method === "GET" && url.pathname === "/api/skillhub/list") {
7061
- const target = url.searchParams.get("target") || "global";
7082
+ const target = url.searchParams.get("target") || "agentflow";
7062
7083
  const agent = url.searchParams.get("agent") || "codex";
7063
- const args = ["list", "--json"];
7064
- if (target === "all") args.push("--all");
7065
- else if (target === "global") args.push("--global", "--agent", agent);
7084
+ const args = skillhubListArgs(target, agent);
7066
7085
  const result = await runSkillhub(args, { cwd: root });
7067
7086
  if (!result.ok) {
7068
7087
  json(res, 500, { error: result.error, stdout: result.stdout });
7069
7088
  return;
7070
7089
  }
7071
- json(res, 200, { skills: normalizeSkillhubListPayload(parseJsonText(result.stdout, [])) });
7090
+ json(res, 200, {
7091
+ skills: normalizeSkillhubListPayload(parseJsonText(result.stdout, [])),
7092
+ target,
7093
+ skillsRoot: target === "agentflow" ? getAgentflowSkillsRoot() : "",
7094
+ });
7072
7095
  return;
7073
7096
  }
7074
7097
 
@@ -7113,6 +7136,10 @@ export function startUiServer({
7113
7136
  }
7114
7137
 
7115
7138
  if (req.method === "POST" && url.pathname === "/api/skillhub/install") {
7139
+ if (!authUser?.isAdmin) {
7140
+ json(res, 403, { error: "Admin required" });
7141
+ return;
7142
+ }
7116
7143
  let payload;
7117
7144
  try {
7118
7145
  payload = JSON.parse(await readBody(req));
@@ -7125,16 +7152,13 @@ export function startUiServer({
7125
7152
  json(res, 400, { error: "Missing skill slug or collection" });
7126
7153
  return;
7127
7154
  }
7128
- if (payload?.collection && !authUser?.isAdmin) {
7129
- json(res, 403, { error: "Admin required" });
7130
- return;
7131
- }
7132
7155
  const beforeSkills = payload?.collection ? listComposerSkills(PACKAGE_ROOT, root) : [];
7133
7156
  const result = await runSkillhub(args, { cwd: root, timeoutMs: 180_000, maxBuffer: 4 * 1024 * 1024 });
7134
7157
  if (!result.ok) {
7135
7158
  json(res, 500, { error: result.error, stdout: result.stdout });
7136
7159
  return;
7137
7160
  }
7161
+ clearSkillRegistryCache();
7138
7162
  let skillCollections = null;
7139
7163
  if (payload?.collection) {
7140
7164
  const afterSkills = listComposerSkills(PACKAGE_ROOT, root);
@@ -7146,6 +7170,10 @@ export function startUiServer({
7146
7170
  }
7147
7171
 
7148
7172
  if (req.method === "POST" && url.pathname === "/api/skillhub/uninstall") {
7173
+ if (!authUser?.isAdmin) {
7174
+ json(res, 403, { error: "Admin required" });
7175
+ return;
7176
+ }
7149
7177
  let payload;
7150
7178
  try {
7151
7179
  payload = JSON.parse(await readBody(req));
@@ -7158,21 +7186,22 @@ export function startUiServer({
7158
7186
  json(res, 400, { error: "Missing skill slug or collection" });
7159
7187
  return;
7160
7188
  }
7161
- if (payload?.collection && !authUser?.isAdmin) {
7162
- json(res, 403, { error: "Admin required" });
7163
- return;
7164
- }
7165
7189
  const result = await runSkillhub(args, { cwd: root, timeoutMs: 120_000, maxBuffer: 4 * 1024 * 1024 });
7166
7190
  if (!result.ok) {
7167
7191
  json(res, 500, { error: result.error, stdout: result.stdout });
7168
7192
  return;
7169
7193
  }
7194
+ clearSkillRegistryCache();
7170
7195
  const skillCollections = payload?.collection ? removeSkillhubCollectionGroup(userCtx, payload.collection, root) : null;
7171
7196
  json(res, 200, { ok: true, stdout: result.stdout, skillCollections });
7172
7197
  return;
7173
7198
  }
7174
7199
 
7175
7200
  if (req.method === "POST" && url.pathname === "/api/skillhub/update") {
7201
+ if (!authUser?.isAdmin) {
7202
+ json(res, 403, { error: "Admin required" });
7203
+ return;
7204
+ }
7176
7205
  const result = await runSkillhub(["update"], { cwd: root, timeoutMs: 180_000, maxBuffer: 4 * 1024 * 1024 });
7177
7206
  if (!result.ok) {
7178
7207
  json(res, 500, { error: result.error, stdout: result.stdout });