abelworkflow 0.9.2 → 1.0.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/README.md +54 -182
- package/bin/abelworkflow.mjs +5 -3
- package/extensions/pi-gpt-responses-compat/index.ts +1 -1
- package/lib/cli/args.mjs +76 -0
- package/lib/cli/main.mjs +385 -0
- package/lib/cli/prompts.mjs +106 -0
- package/lib/config/dotenv.mjs +102 -0
- package/lib/config/jsonc.mjs +80 -0
- package/lib/config/store.mjs +310 -0
- package/lib/config/toml.mjs +638 -0
- package/lib/installer/assets.mjs +324 -0
- package/lib/installer/install.mjs +153 -0
- package/lib/installer/links.mjs +427 -0
- package/lib/installer/state.mjs +163 -0
- package/lib/paths.mjs +99 -0
- package/lib/providers/claude.mjs +197 -0
- package/lib/providers/codex.mjs +433 -0
- package/lib/providers/pi.mjs +577 -0
- package/lib/providers/skills.mjs +105 -0
- package/lib/templates/codex/agents/default.toml +7 -0
- package/lib/templates/codex/agents/explorer.toml +7 -0
- package/lib/templates/codex/agents/planner.toml +7 -0
- package/lib/templates/codex/agents/reviewer.toml +7 -0
- package/lib/templates/codex/agents/worker.toml +7 -0
- package/lib/templates/codex/config-base.toml +11 -3
- package/lib/templates/workflow/AGENTS.md +50 -0
- package/lib/templates/workflow/commands/abel-design.md +175 -0
- package/{commands → lib/templates/workflow/commands}/abel-diagnose.md +5 -5
- package/lib/templates/workflow/commands/abel-implement.md +170 -0
- package/{commands → lib/templates/workflow/commands}/abel-init.md +3 -5
- package/{.gitignore → lib/templates/workflow/gitignore.template} +0 -2
- package/lib/tools/cli-installer.mjs +277 -0
- package/package.json +37 -10
- package/skills/context7-auto-research/SKILL.md +5 -5
- package/skills/context7-auto-research/{context7-api.js → context7-api.cjs} +5 -8
- package/skills/dev-browser/SKILL.md +53 -33
- package/skills/dev-browser/dist/scripts/start.d.ts +2 -0
- package/skills/dev-browser/dist/scripts/start.d.ts.map +1 -0
- package/skills/dev-browser/dist/scripts/start.js +118 -0
- package/skills/dev-browser/dist/scripts/start.js.map +1 -0
- package/skills/dev-browser/dist/src/client.d.ts +93 -0
- package/skills/dev-browser/dist/src/client.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/client.js +310 -0
- package/skills/dev-browser/dist/src/client.js.map +1 -0
- package/skills/dev-browser/dist/src/entrypoint.d.ts +29 -0
- package/skills/dev-browser/dist/src/entrypoint.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/entrypoint.js +106 -0
- package/skills/dev-browser/dist/src/entrypoint.js.map +1 -0
- package/skills/dev-browser/dist/src/index.d.ts +4 -0
- package/skills/dev-browser/dist/src/index.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/index.js +2 -0
- package/skills/dev-browser/dist/src/index.js.map +1 -0
- package/skills/dev-browser/dist/src/page-api.d.ts +25 -0
- package/skills/dev-browser/dist/src/page-api.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/page-api.js +104 -0
- package/skills/dev-browser/dist/src/page-api.js.map +1 -0
- package/skills/dev-browser/dist/src/relay.d.ts +27 -0
- package/skills/dev-browser/dist/src/relay.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/relay.js +521 -0
- package/skills/dev-browser/dist/src/relay.js.map +1 -0
- package/skills/dev-browser/dist/src/runtime.d.ts +45 -0
- package/skills/dev-browser/dist/src/runtime.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/runtime.js +54 -0
- package/skills/dev-browser/dist/src/runtime.js.map +1 -0
- package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts +23 -0
- package/skills/dev-browser/dist/src/snapshot/browser-script.d.ts.map +1 -0
- package/skills/dev-browser/{src/snapshot/browser-script.ts → dist/src/snapshot/browser-script.js} +21 -30
- package/skills/dev-browser/dist/src/snapshot/browser-script.js.map +1 -0
- package/skills/dev-browser/dist/src/snapshot/index.d.ts +14 -0
- package/skills/dev-browser/dist/src/snapshot/index.d.ts.map +1 -0
- package/skills/dev-browser/{src/snapshot/index.ts → dist/src/snapshot/index.js} +2 -2
- package/skills/dev-browser/dist/src/snapshot/index.js.map +1 -0
- package/skills/dev-browser/dist/src/snapshot/inject.d.ts +13 -0
- package/skills/dev-browser/dist/src/snapshot/inject.d.ts.map +1 -0
- package/skills/dev-browser/{src/snapshot/inject.ts → dist/src/snapshot/inject.js} +2 -2
- package/skills/dev-browser/dist/src/snapshot/inject.js.map +1 -0
- package/skills/dev-browser/dist/src/standalone.d.ts +32 -0
- package/skills/dev-browser/dist/src/standalone.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/standalone.js +174 -0
- package/skills/dev-browser/dist/src/standalone.js.map +1 -0
- package/skills/dev-browser/dist/src/startup.d.ts +55 -0
- package/skills/dev-browser/dist/src/startup.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/startup.js +81 -0
- package/skills/dev-browser/dist/src/startup.js.map +1 -0
- package/skills/dev-browser/dist/src/target-registry.d.ts +29 -0
- package/skills/dev-browser/dist/src/target-registry.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/target-registry.js +135 -0
- package/skills/dev-browser/dist/src/target-registry.js.map +1 -0
- package/skills/dev-browser/dist/src/types.d.ts +27 -0
- package/skills/dev-browser/dist/src/types.d.ts.map +1 -0
- package/skills/dev-browser/dist/src/types.js +2 -0
- package/skills/dev-browser/dist/src/types.js.map +1 -0
- package/skills/dev-browser/package-lock.json +67 -1510
- package/skills/dev-browser/package.json +18 -14
- package/skills/dev-browser/references/scraping.md +94 -105
- package/skills/grok-search/.env.example +3 -3
- package/skills/grok-search/SKILL.md +1 -1
- package/skills/grok-search/defaults.json +3 -0
- package/skills/grok-search/gitignore.template +4 -0
- package/skills/grok-search/scripts/_dotenv.py +5 -1
- package/skills/grok-search/scripts/groksearch_cli.py +15 -33
- package/skills/prompt-enhancer/SKILL.md +2 -27
- package/skills/prompt-enhancer/TEMPLATE.md +0 -4
- package/.skill-lock.json +0 -29
- package/AGENTS.md +0 -45
- package/commands/abel-implement.md +0 -157
- package/commands/abel-plan.md +0 -88
- package/commands/abel-research.md +0 -126
- package/lib/cli/logic.mjs +0 -213
- package/lib/cli.mjs +0 -3016
- package/skills/confidence-check/SKILL.md +0 -34
- package/skills/confidence-check/confidence.ts +0 -276
- package/skills/dev-browser/bun.lock +0 -460
- package/skills/dev-browser/scripts/start.ts +0 -280
- package/skills/dev-browser/src/client.ts +0 -474
- package/skills/dev-browser/src/entrypoint.ts +0 -157
- package/skills/dev-browser/src/index.ts +0 -289
- package/skills/dev-browser/src/relay.ts +0 -731
- package/skills/dev-browser/src/runtime.test.ts +0 -60
- package/skills/dev-browser/src/runtime.ts +0 -171
- package/skills/dev-browser/src/startup.test.ts +0 -95
- package/skills/dev-browser/src/startup.ts +0 -153
- package/skills/dev-browser/src/types.ts +0 -35
- package/skills/dev-browser/tsconfig.json +0 -36
- package/skills/dev-browser/vitest.config.ts +0 -12
- package/skills/prompt-enhancer/.env.example +0 -12
- package/skills/prompt-enhancer/ADVANCED.md +0 -103
- package/skills/prompt-enhancer/requirements.txt +0 -2
- package/skills/prompt-enhancer/scripts/_dotenv.py +0 -28
- package/skills/prompt-enhancer/scripts/enhance.py +0 -191
- package/skills/prompt-enhancer/scripts/prompt_enhancer_entry.py +0 -253
- package/skills/sequential-think/SKILL.md +0 -198
- package/skills/sequential-think/scripts/.env.example +0 -5
- package/skills/sequential-think/scripts/sequential_think_cli.py +0 -253
|
@@ -0,0 +1,427 @@
|
|
|
1
|
+
import * as fs from "node:fs/promises";
|
|
2
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
3
|
+
import { backupIfNeeded, pathExists, pathTargetExists } from "../config/store.mjs";
|
|
4
|
+
import { isWindows } from "../paths.mjs";
|
|
5
|
+
import { ensureManagedContainerDirectory, hashBytes } from "./assets.mjs";
|
|
6
|
+
|
|
7
|
+
function shouldForceFileSymlinkFailure(kind) {
|
|
8
|
+
return process.env.ABELWORKFLOW_TEST_FORCE_FILE_SYMLINK_EPERM === "1" && isWindows() && kind === "file";
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function createManagedTargetState(targetPath, sourcePath, kind, mode, status) {
|
|
12
|
+
let targetHash;
|
|
13
|
+
if (kind === "file" && await pathTargetExists(targetPath)) {
|
|
14
|
+
targetHash = hashBytes(await fs.readFile(targetPath));
|
|
15
|
+
}
|
|
16
|
+
return { targetPath, sourcePath, kind, mode, status, ...(targetHash ? { targetHash } : {}) };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
async function createSymlink(targetPath, sourcePath, linkType, kind) {
|
|
20
|
+
if (shouldForceFileSymlinkFailure(kind)) {
|
|
21
|
+
const error = new Error("simulated EPERM");
|
|
22
|
+
error.code = "EPERM";
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
await fs.symlink(sourcePath, targetPath, linkType);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function symlinkPointsTo(targetPath, sourcePath) {
|
|
29
|
+
try {
|
|
30
|
+
if (!(await fs.lstat(targetPath)).isSymbolicLink()) return false;
|
|
31
|
+
const existing = await fs.readlink(targetPath);
|
|
32
|
+
return resolve(dirname(targetPath), existing) === resolve(sourcePath);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
if (error?.code === "ENOENT") return false;
|
|
35
|
+
throw error;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function previousStateOwnsSource(previousState, sourcePath, kind) {
|
|
40
|
+
return Boolean(previousState)
|
|
41
|
+
&& resolve(previousState.sourcePath) === resolve(sourcePath)
|
|
42
|
+
&& previousState.kind === kind;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function isUnverifiedWindowsCopy(previousState, kind) {
|
|
46
|
+
return isWindows()
|
|
47
|
+
&& kind === "file"
|
|
48
|
+
&& previousState?.kind === "file"
|
|
49
|
+
&& previousState.mode === "copy"
|
|
50
|
+
&& !previousState.targetHash;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function targetMatchesPreviousState(targetPath, previousState) {
|
|
54
|
+
if (!previousState || !(await pathExists(targetPath))) return false;
|
|
55
|
+
const targetStat = await fs.lstat(targetPath);
|
|
56
|
+
if (previousState.mode === "symlink") {
|
|
57
|
+
return targetStat.isSymbolicLink() && symlinkPointsTo(targetPath, previousState.sourcePath);
|
|
58
|
+
}
|
|
59
|
+
if (previousState.mode === "hardlink") {
|
|
60
|
+
if (!targetStat.isFile()) return false;
|
|
61
|
+
if (previousState.targetHash) {
|
|
62
|
+
return hashBytes(await fs.readFile(targetPath)) === previousState.targetHash;
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
const sourceStat = await fs.stat(previousState.sourcePath);
|
|
66
|
+
if (targetStat.dev === sourceStat.dev && targetStat.ino === sourceStat.ino) return true;
|
|
67
|
+
} catch (error) {
|
|
68
|
+
if (error?.code !== "ENOENT") throw error;
|
|
69
|
+
}
|
|
70
|
+
return false;
|
|
71
|
+
}
|
|
72
|
+
if (previousState.mode === "copy") {
|
|
73
|
+
if (previousState.kind !== "file" || !targetStat.isFile() || !previousState.targetHash) return false;
|
|
74
|
+
return hashBytes(await fs.readFile(targetPath)) === previousState.targetHash;
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function ordinaryFileMatchesSource(targetPath, sourcePath, kind) {
|
|
80
|
+
if (kind !== "file") return false;
|
|
81
|
+
try {
|
|
82
|
+
const targetStat = await fs.lstat(targetPath);
|
|
83
|
+
if (!targetStat.isFile()) return false;
|
|
84
|
+
const [targetContent, sourceContent] = await Promise.all([
|
|
85
|
+
fs.readFile(targetPath),
|
|
86
|
+
fs.readFile(sourcePath)
|
|
87
|
+
]);
|
|
88
|
+
return targetContent.equals(sourceContent);
|
|
89
|
+
} catch (error) {
|
|
90
|
+
if (error?.code === "ENOENT") return false;
|
|
91
|
+
throw error;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
async function moveReplaceableTarget(targetPath) {
|
|
96
|
+
const targetStat = await fs.lstat(targetPath);
|
|
97
|
+
if (targetStat.isDirectory() && !targetStat.isSymbolicLink()) return false;
|
|
98
|
+
await backupIfNeeded(targetPath);
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
async function ensureManagedLink(targetPath, sourcePath, kind, previousLinkedTargets = {}, options = {}) {
|
|
103
|
+
const force = options.force ?? false;
|
|
104
|
+
await fs.mkdir(dirname(targetPath), { recursive: true });
|
|
105
|
+
const sourceExists = await pathTargetExists(sourcePath);
|
|
106
|
+
const targetExists = await pathExists(targetPath);
|
|
107
|
+
const previousState = previousLinkedTargets[targetPath];
|
|
108
|
+
const wasPreviouslyManaged = previousStateOwnsSource(previousState, sourcePath, kind);
|
|
109
|
+
const isUnverifiedCopy = wasPreviouslyManaged && isUnverifiedWindowsCopy(previousState, kind);
|
|
110
|
+
|
|
111
|
+
if (targetExists && await symlinkPointsTo(targetPath, sourcePath)) {
|
|
112
|
+
if (!sourceExists) {
|
|
113
|
+
await fs.rm(targetPath, { recursive: true, force: true });
|
|
114
|
+
return { targetPath, status: "removed" };
|
|
115
|
+
}
|
|
116
|
+
return createManagedTargetState(targetPath, sourcePath, kind, "symlink", "unchanged");
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (!sourceExists) {
|
|
120
|
+
if (!targetExists) return { targetPath, status: "removed" };
|
|
121
|
+
if (!wasPreviouslyManaged) return { targetPath, status: "skipped" };
|
|
122
|
+
if (isUnverifiedCopy) return { targetPath, status: "conflict" };
|
|
123
|
+
if (await targetMatchesPreviousState(targetPath, previousState)) {
|
|
124
|
+
await fs.rm(targetPath, { recursive: true, force: true });
|
|
125
|
+
return { targetPath, status: "removed" };
|
|
126
|
+
}
|
|
127
|
+
if (force && await moveReplaceableTarget(targetPath)) return { targetPath, status: "removed" };
|
|
128
|
+
return { targetPath, status: "conflict" };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
if (targetExists && !wasPreviouslyManaged && await ordinaryFileMatchesSource(targetPath, sourcePath, kind)) {
|
|
132
|
+
return createManagedTargetState(targetPath, sourcePath, kind, "copy", "unchanged");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (targetExists) {
|
|
136
|
+
if (isUnverifiedCopy) {
|
|
137
|
+
if (await ordinaryFileMatchesSource(targetPath, sourcePath, kind)) {
|
|
138
|
+
return createManagedTargetState(targetPath, sourcePath, kind, "copy", "unchanged");
|
|
139
|
+
}
|
|
140
|
+
return { targetPath, status: "conflict" };
|
|
141
|
+
}
|
|
142
|
+
const matchesPrevious = wasPreviouslyManaged && await targetMatchesPreviousState(targetPath, previousState);
|
|
143
|
+
if (matchesPrevious && await ordinaryFileMatchesSource(targetPath, sourcePath, kind)) {
|
|
144
|
+
return createManagedTargetState(targetPath, sourcePath, kind, previousState.mode, "unchanged");
|
|
145
|
+
}
|
|
146
|
+
if ((!matchesPrevious && !force) || !(await moveReplaceableTarget(targetPath))) {
|
|
147
|
+
return { targetPath, status: "conflict" };
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
const linkType = isWindows() ? (kind === "dir" ? "junction" : "file") : kind;
|
|
152
|
+
try {
|
|
153
|
+
await createSymlink(targetPath, sourcePath, linkType, kind);
|
|
154
|
+
return createManagedTargetState(targetPath, sourcePath, kind, "symlink", "linked");
|
|
155
|
+
} catch (error) {
|
|
156
|
+
if (!shouldFallbackToManagedFile(error, kind)) throw error;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
await fs.link(sourcePath, targetPath);
|
|
161
|
+
return createManagedTargetState(targetPath, sourcePath, kind, "hardlink", "linked");
|
|
162
|
+
} catch (error) {
|
|
163
|
+
if (!shouldCopyManagedFile(error)) throw error;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
await fs.cp(sourcePath, targetPath, { recursive: true, force: false, errorOnExist: true });
|
|
167
|
+
return createManagedTargetState(targetPath, sourcePath, kind, "copy", "linked");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
function shouldFallbackToManagedFile(error, kind) {
|
|
171
|
+
return kind === "file" && isWindows() && ["EPERM", "EACCES"].includes(error?.code);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function shouldCopyManagedFile(error) {
|
|
175
|
+
return ["EPERM", "EACCES", "EXDEV", "EINVAL", "UNKNOWN"].includes(error?.code);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async function pathIsDirectory(path) {
|
|
179
|
+
try {
|
|
180
|
+
return (await fs.stat(path)).isDirectory();
|
|
181
|
+
} catch (error) {
|
|
182
|
+
if (["ENOENT", "ELOOP"].includes(error?.code)) return false;
|
|
183
|
+
throw error;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function pathIsFile(path) {
|
|
188
|
+
try {
|
|
189
|
+
return (await fs.stat(path)).isFile();
|
|
190
|
+
} catch (error) {
|
|
191
|
+
if (["ENOENT", "ELOOP"].includes(error?.code)) return false;
|
|
192
|
+
throw error;
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
async function ensureProviderRootDirectory(homeDir, ...segments) {
|
|
197
|
+
await fs.mkdir(homeDir, { recursive: true });
|
|
198
|
+
let directory = homeDir;
|
|
199
|
+
for (const segment of segments) {
|
|
200
|
+
directory = join(directory, segment);
|
|
201
|
+
let stat;
|
|
202
|
+
try {
|
|
203
|
+
stat = await fs.lstat(directory);
|
|
204
|
+
} catch (error) {
|
|
205
|
+
if (error?.code !== "ENOENT") throw error;
|
|
206
|
+
try {
|
|
207
|
+
await fs.mkdir(directory);
|
|
208
|
+
} catch (mkdirError) {
|
|
209
|
+
if (mkdirError?.code === "EEXIST") {
|
|
210
|
+
throw new Error(`Provider root conflict at ${directory}`, { cause: mkdirError });
|
|
211
|
+
}
|
|
212
|
+
throw mkdirError;
|
|
213
|
+
}
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
217
|
+
throw new Error(`Provider root conflict at ${directory}`);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
return directory;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async function getDirectoryNames(root) {
|
|
224
|
+
if (!(await pathIsDirectory(root))) return [];
|
|
225
|
+
const entries = await fs.readdir(root, { withFileTypes: true });
|
|
226
|
+
const names = await Promise.all(entries.map(async (entry) => {
|
|
227
|
+
if (entry.isDirectory()) return entry.name;
|
|
228
|
+
if (entry.isSymbolicLink() && await pathIsDirectory(join(root, entry.name))) return entry.name;
|
|
229
|
+
return null;
|
|
230
|
+
}));
|
|
231
|
+
return names.filter(Boolean);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function getCommandNames(commandsDir) {
|
|
235
|
+
if (!(await pathIsDirectory(commandsDir))) return [];
|
|
236
|
+
const entries = await fs.readdir(commandsDir, { withFileTypes: true });
|
|
237
|
+
const names = await Promise.all(entries.map(async (entry) => {
|
|
238
|
+
if (!entry.name.endsWith(".md")) return null;
|
|
239
|
+
if (entry.isFile()) return entry.name;
|
|
240
|
+
if (entry.isSymbolicLink() && await pathIsFile(join(commandsDir, entry.name))) return entry.name;
|
|
241
|
+
return null;
|
|
242
|
+
}));
|
|
243
|
+
return names.filter(Boolean);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function getPiExtensionNames(extensionsDir) {
|
|
247
|
+
if (!(await pathIsDirectory(extensionsDir))) return [];
|
|
248
|
+
const entries = await fs.readdir(extensionsDir, { withFileTypes: true });
|
|
249
|
+
const names = await Promise.all(entries.map(async (entry) => {
|
|
250
|
+
const entryPath = join(extensionsDir, entry.name);
|
|
251
|
+
if (entry.isDirectory()) return await pathIsFile(join(entryPath, "index.ts")) ? entry.name : null;
|
|
252
|
+
if (entry.isFile()) return entry.name.endsWith(".ts") ? entry.name : null;
|
|
253
|
+
if (!entry.isSymbolicLink()) return null;
|
|
254
|
+
if (entry.name.endsWith(".ts") && await pathIsFile(entryPath)) return entry.name;
|
|
255
|
+
return await pathIsFile(join(entryPath, "index.ts")) ? entry.name : null;
|
|
256
|
+
}));
|
|
257
|
+
return names.filter(Boolean);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function isWithinManagedRoot(targetPath, managedSourceRoot, pathOps = { isAbsolute, relative, sep }) {
|
|
261
|
+
const relativePath = pathOps.relative(managedSourceRoot, targetPath);
|
|
262
|
+
return relativePath !== ""
|
|
263
|
+
&& !pathOps.isAbsolute(relativePath)
|
|
264
|
+
&& relativePath !== ".."
|
|
265
|
+
&& !relativePath.startsWith(`..${pathOps.sep}`);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
async function pruneManagedTargets(targetDir, managedSourceRoot, expectedNames, previousLinkedTargets, options = {}) {
|
|
269
|
+
const resolvedTargetDir = resolve(targetDir);
|
|
270
|
+
const expectedNameSet = new Set(expectedNames);
|
|
271
|
+
const results = [];
|
|
272
|
+
for (const [targetPath, previousState] of Object.entries(previousLinkedTargets)) {
|
|
273
|
+
const resolvedTargetPath = resolve(targetPath);
|
|
274
|
+
if (targetPath !== resolvedTargetPath || dirname(resolvedTargetPath) !== resolvedTargetDir) continue;
|
|
275
|
+
if (!isWithinManagedRoot(resolve(previousState.sourcePath), resolve(managedSourceRoot))) continue;
|
|
276
|
+
if (expectedNameSet.has(basename(resolvedTargetPath)) && await pathTargetExists(previousState.sourcePath)) continue;
|
|
277
|
+
if (!(await pathExists(resolvedTargetPath))) {
|
|
278
|
+
results.push({ targetPath, status: "removed" });
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
const isUnverifiedCopy = isUnverifiedWindowsCopy(previousState, previousState.kind);
|
|
282
|
+
const matchesPrevious = await targetMatchesPreviousState(resolvedTargetPath, previousState)
|
|
283
|
+
|| (isUnverifiedCopy && await ordinaryFileMatchesSource(
|
|
284
|
+
resolvedTargetPath,
|
|
285
|
+
previousState.sourcePath,
|
|
286
|
+
previousState.kind
|
|
287
|
+
));
|
|
288
|
+
if (matchesPrevious) {
|
|
289
|
+
await fs.rm(resolvedTargetPath, { recursive: true, force: true });
|
|
290
|
+
results.push({ targetPath, status: "removed" });
|
|
291
|
+
continue;
|
|
292
|
+
}
|
|
293
|
+
if (isUnverifiedCopy) {
|
|
294
|
+
results.push({ targetPath, status: "conflict" });
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (options.force && await moveReplaceableTarget(resolvedTargetPath)) {
|
|
298
|
+
results.push({ targetPath, status: "removed" });
|
|
299
|
+
continue;
|
|
300
|
+
}
|
|
301
|
+
results.push({ targetPath, status: "conflict" });
|
|
302
|
+
}
|
|
303
|
+
return results;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function linkSkillDirectories(baseDir, paths, previousLinkedTargets, options) {
|
|
307
|
+
const results = [];
|
|
308
|
+
const skillsRoot = join(paths.agentsDir, "skills");
|
|
309
|
+
const skillNames = (await getDirectoryNames(skillsRoot)).filter((skillName) => skillName !== ".system");
|
|
310
|
+
results.push(...await pruneManagedTargets(
|
|
311
|
+
join(baseDir, "skills"),
|
|
312
|
+
skillsRoot,
|
|
313
|
+
skillNames,
|
|
314
|
+
previousLinkedTargets,
|
|
315
|
+
options
|
|
316
|
+
));
|
|
317
|
+
for (const skillName of skillNames) {
|
|
318
|
+
results.push(await ensureManagedLink(
|
|
319
|
+
join(baseDir, "skills", skillName),
|
|
320
|
+
join(skillsRoot, skillName),
|
|
321
|
+
"dir",
|
|
322
|
+
previousLinkedTargets,
|
|
323
|
+
options
|
|
324
|
+
));
|
|
325
|
+
}
|
|
326
|
+
return results;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
async function linkCommands(targetDir, paths, previousLinkedTargets, options) {
|
|
330
|
+
const commandsRoot = join(paths.agentsDir, "commands");
|
|
331
|
+
const commandFiles = await getCommandNames(commandsRoot);
|
|
332
|
+
const results = await pruneManagedTargets(targetDir, commandsRoot, commandFiles, previousLinkedTargets, options);
|
|
333
|
+
for (const fileName of commandFiles) {
|
|
334
|
+
results.push(await ensureManagedLink(
|
|
335
|
+
join(targetDir, fileName),
|
|
336
|
+
join(commandsRoot, fileName),
|
|
337
|
+
"file",
|
|
338
|
+
previousLinkedTargets,
|
|
339
|
+
options
|
|
340
|
+
));
|
|
341
|
+
}
|
|
342
|
+
return results;
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
async function linkClaude(paths, previousLinkedTargets = {}, options = {}) {
|
|
346
|
+
const claudeDir = await ensureProviderRootDirectory(paths.homeDir, ".claude");
|
|
347
|
+
await ensureManagedContainerDirectory(join(claudeDir, "commands"), join(paths.agentsDir, "commands"));
|
|
348
|
+
await ensureManagedContainerDirectory(join(claudeDir, "skills"), join(paths.agentsDir, "skills"));
|
|
349
|
+
return [
|
|
350
|
+
await ensureManagedLink(
|
|
351
|
+
join(claudeDir, "CLAUDE.md"),
|
|
352
|
+
join(paths.agentsDir, "AGENTS.md"),
|
|
353
|
+
"file",
|
|
354
|
+
previousLinkedTargets,
|
|
355
|
+
options
|
|
356
|
+
),
|
|
357
|
+
...await linkCommands(join(claudeDir, "commands"), paths, previousLinkedTargets, options),
|
|
358
|
+
...await linkSkillDirectories(claudeDir, paths, previousLinkedTargets, options)
|
|
359
|
+
];
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
async function linkCodex(paths, previousLinkedTargets = {}, options = {}) {
|
|
363
|
+
const codexDir = await ensureProviderRootDirectory(paths.homeDir, ".codex");
|
|
364
|
+
await ensureManagedContainerDirectory(join(codexDir, "skills"), join(paths.agentsDir, "skills"));
|
|
365
|
+
await ensureManagedContainerDirectory(join(codexDir, "prompts"), join(paths.agentsDir, "commands"));
|
|
366
|
+
return [
|
|
367
|
+
await ensureManagedLink(
|
|
368
|
+
join(codexDir, "AGENTS.md"),
|
|
369
|
+
join(paths.agentsDir, "AGENTS.md"),
|
|
370
|
+
"file",
|
|
371
|
+
previousLinkedTargets,
|
|
372
|
+
options
|
|
373
|
+
),
|
|
374
|
+
...await linkSkillDirectories(codexDir, paths, previousLinkedTargets, options),
|
|
375
|
+
...await linkCommands(join(codexDir, "prompts"), paths, previousLinkedTargets, options)
|
|
376
|
+
];
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
async function linkPi(paths, previousLinkedTargets = {}, options = {}) {
|
|
380
|
+
const piAgentDir = await ensureProviderRootDirectory(paths.homeDir, ".pi", "agent");
|
|
381
|
+
await ensureManagedContainerDirectory(join(piAgentDir, "skills"), join(paths.agentsDir, "skills"));
|
|
382
|
+
await ensureManagedContainerDirectory(join(piAgentDir, "prompts"), join(paths.agentsDir, "commands"));
|
|
383
|
+
await ensureManagedContainerDirectory(join(piAgentDir, "extensions"), join(paths.agentsDir, "extensions"));
|
|
384
|
+
const results = [
|
|
385
|
+
await ensureManagedLink(
|
|
386
|
+
join(piAgentDir, "AGENTS.md"),
|
|
387
|
+
join(paths.agentsDir, "AGENTS.md"),
|
|
388
|
+
"file",
|
|
389
|
+
previousLinkedTargets,
|
|
390
|
+
options
|
|
391
|
+
),
|
|
392
|
+
...await linkSkillDirectories(piAgentDir, paths, previousLinkedTargets, options),
|
|
393
|
+
...await linkCommands(join(piAgentDir, "prompts"), paths, previousLinkedTargets, options)
|
|
394
|
+
];
|
|
395
|
+
const extensionsRoot = join(paths.agentsDir, "extensions");
|
|
396
|
+
const extensionNames = await getPiExtensionNames(extensionsRoot);
|
|
397
|
+
results.push(...await pruneManagedTargets(
|
|
398
|
+
join(piAgentDir, "extensions"),
|
|
399
|
+
extensionsRoot,
|
|
400
|
+
extensionNames,
|
|
401
|
+
previousLinkedTargets,
|
|
402
|
+
options
|
|
403
|
+
));
|
|
404
|
+
for (const entryName of extensionNames) {
|
|
405
|
+
const sourcePath = join(extensionsRoot, entryName);
|
|
406
|
+
results.push(await ensureManagedLink(
|
|
407
|
+
join(piAgentDir, "extensions", entryName),
|
|
408
|
+
sourcePath,
|
|
409
|
+
await pathIsDirectory(sourcePath) ? "dir" : "file",
|
|
410
|
+
previousLinkedTargets,
|
|
411
|
+
options
|
|
412
|
+
));
|
|
413
|
+
}
|
|
414
|
+
return results;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export {
|
|
418
|
+
ensureManagedLink,
|
|
419
|
+
getCommandNames,
|
|
420
|
+
isWithinManagedRoot,
|
|
421
|
+
linkClaude,
|
|
422
|
+
linkCodex,
|
|
423
|
+
linkPi,
|
|
424
|
+
pathIsDirectory,
|
|
425
|
+
pathIsFile,
|
|
426
|
+
pruneManagedTargets
|
|
427
|
+
};
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { join } from "node:path";
|
|
2
|
+
import { readJsonFileSafe, writeJson } from "../config/store.mjs";
|
|
3
|
+
|
|
4
|
+
function metadataPathFor(paths) {
|
|
5
|
+
return join(paths.agentsDir, paths.installMetadataName);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function sortObject(value = {}) {
|
|
9
|
+
return Object.fromEntries(Object.entries(value).sort(([left], [right]) => left.localeCompare(right)));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function isMetadataV2(previousMetadata) {
|
|
13
|
+
return previousMetadata?.schemaVersion === 2;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function objectField(value) {
|
|
17
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : {};
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function stringArray(value) {
|
|
21
|
+
return Array.isArray(value) ? value.filter((entry) => typeof entry === "string") : [];
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function getPreviousManagedCodexAuthKeys(previousMetadata = {}) {
|
|
25
|
+
return isMetadataV2(previousMetadata)
|
|
26
|
+
? stringArray(previousMetadata.managedCodexAuthKeys)
|
|
27
|
+
: [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function normalizeManagedCodexAgentFiles(value = {}) {
|
|
31
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return {};
|
|
32
|
+
return sortObject(Object.fromEntries(Object.entries(value).filter(([name, hash]) => (
|
|
33
|
+
name.endsWith(".toml")
|
|
34
|
+
&& !name.includes("/")
|
|
35
|
+
&& !name.includes("\\")
|
|
36
|
+
&& typeof hash === "string"
|
|
37
|
+
&& /^[a-f0-9]{64}$/u.test(hash)
|
|
38
|
+
))));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function getPreviousManagedCodexAgentFiles(previousMetadata = {}) {
|
|
42
|
+
if (!isMetadataV2(previousMetadata)) return {};
|
|
43
|
+
return normalizeManagedCodexAgentFiles(previousMetadata.managedCodexAgentFiles);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async function readInstallMetadata(paths) {
|
|
47
|
+
const metadata = await readJsonFileSafe(metadataPathFor(paths), {});
|
|
48
|
+
if (!metadata || typeof metadata !== "object" || Array.isArray(metadata)) {
|
|
49
|
+
throw new TypeError(`Install metadata in ${metadataPathFor(paths)} must contain a JSON object`);
|
|
50
|
+
}
|
|
51
|
+
return metadata;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async function writeInstallMetadata(paths, metadata) {
|
|
55
|
+
return writeJson(metadataPathFor(paths), metadata, { backupLimit: 0 });
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function buildInstallMetadata({
|
|
59
|
+
previousMetadata = {},
|
|
60
|
+
packageVersion,
|
|
61
|
+
managedFiles = {},
|
|
62
|
+
managedCodexAuthKeys = getPreviousManagedCodexAuthKeys(previousMetadata),
|
|
63
|
+
managedCodexAgentFiles = getPreviousManagedCodexAgentFiles(previousMetadata),
|
|
64
|
+
linkedTargets = {}
|
|
65
|
+
}) {
|
|
66
|
+
const metadata = {
|
|
67
|
+
schemaVersion: 2,
|
|
68
|
+
packageVersion,
|
|
69
|
+
managedFiles: sortObject(managedFiles),
|
|
70
|
+
managedCodexAuthKeys: [...new Set(managedCodexAuthKeys)].sort(),
|
|
71
|
+
managedCodexAgentFiles: normalizeManagedCodexAgentFiles(managedCodexAgentFiles),
|
|
72
|
+
linkedTargets: sortObject(linkedTargets)
|
|
73
|
+
};
|
|
74
|
+
if (typeof previousMetadata.installedAt === "string") {
|
|
75
|
+
metadata.installedAt = previousMetadata.installedAt;
|
|
76
|
+
}
|
|
77
|
+
return metadata;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function finalizeProviderInstallMetadata({
|
|
81
|
+
previousMetadata = {},
|
|
82
|
+
packageVersion,
|
|
83
|
+
overrides = {}
|
|
84
|
+
}) {
|
|
85
|
+
const isV2 = isMetadataV2(previousMetadata);
|
|
86
|
+
return buildInstallMetadata({
|
|
87
|
+
previousMetadata: isV2 ? previousMetadata : {},
|
|
88
|
+
packageVersion: isV2
|
|
89
|
+
&& typeof previousMetadata.packageVersion === "string"
|
|
90
|
+
&& previousMetadata.packageVersion
|
|
91
|
+
? previousMetadata.packageVersion
|
|
92
|
+
: packageVersion,
|
|
93
|
+
managedFiles: isV2 ? objectField(previousMetadata.managedFiles) : {},
|
|
94
|
+
managedCodexAuthKeys: stringArray(
|
|
95
|
+
overrides.managedCodexAuthKeys ?? getPreviousManagedCodexAuthKeys(previousMetadata)
|
|
96
|
+
),
|
|
97
|
+
managedCodexAgentFiles: overrides.managedCodexAgentFiles
|
|
98
|
+
?? getPreviousManagedCodexAgentFiles(previousMetadata),
|
|
99
|
+
linkedTargets: objectField(previousMetadata.linkedTargets)
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function linkedTargetsFromResults(results) {
|
|
104
|
+
return Object.fromEntries(results
|
|
105
|
+
.filter((result) => result.sourcePath)
|
|
106
|
+
.map((result) => [result.targetPath, {
|
|
107
|
+
sourcePath: result.sourcePath,
|
|
108
|
+
kind: result.kind,
|
|
109
|
+
mode: result.mode,
|
|
110
|
+
...(result.targetHash ? { targetHash: result.targetHash } : {})
|
|
111
|
+
}]));
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function mergeLinkedTargets(previousLinkedTargets = {}, results = []) {
|
|
115
|
+
const nextLinkedTargets = { ...previousLinkedTargets };
|
|
116
|
+
for (const result of results) {
|
|
117
|
+
if (result.sourcePath) {
|
|
118
|
+
nextLinkedTargets[result.targetPath] = {
|
|
119
|
+
sourcePath: result.sourcePath,
|
|
120
|
+
kind: result.kind,
|
|
121
|
+
mode: result.mode,
|
|
122
|
+
...(result.targetHash ? { targetHash: result.targetHash } : {})
|
|
123
|
+
};
|
|
124
|
+
} else if (result.status === "removed") {
|
|
125
|
+
delete nextLinkedTargets[result.targetPath];
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
return sortObject(nextLinkedTargets);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function createInstallReport() {
|
|
132
|
+
return {
|
|
133
|
+
created: [],
|
|
134
|
+
updated: [],
|
|
135
|
+
unchanged: [],
|
|
136
|
+
preserved: [],
|
|
137
|
+
conflicts: [],
|
|
138
|
+
removed: [],
|
|
139
|
+
linked: []
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function mergeInstallReports(...reports) {
|
|
144
|
+
const merged = createInstallReport();
|
|
145
|
+
for (const report of reports) {
|
|
146
|
+
for (const key of Object.keys(merged)) merged[key].push(...(report?.[key] ?? []));
|
|
147
|
+
}
|
|
148
|
+
return merged;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
export {
|
|
152
|
+
buildInstallMetadata,
|
|
153
|
+
createInstallReport,
|
|
154
|
+
finalizeProviderInstallMetadata,
|
|
155
|
+
getPreviousManagedCodexAgentFiles,
|
|
156
|
+
getPreviousManagedCodexAuthKeys,
|
|
157
|
+
linkedTargetsFromResults,
|
|
158
|
+
mergeInstallReports,
|
|
159
|
+
mergeLinkedTargets,
|
|
160
|
+
metadataPathFor,
|
|
161
|
+
readInstallMetadata,
|
|
162
|
+
writeInstallMetadata
|
|
163
|
+
};
|
package/lib/paths.mjs
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
4
|
+
import { fileURLToPath } from "node:url";
|
|
5
|
+
|
|
6
|
+
const defaultPackageRoot = dirname(dirname(fileURLToPath(import.meta.url)));
|
|
7
|
+
|
|
8
|
+
function createPaths({
|
|
9
|
+
homeDir = homedir(),
|
|
10
|
+
packageRoot = defaultPackageRoot,
|
|
11
|
+
agentsDir = join(homeDir, ".agents")
|
|
12
|
+
} = {}) {
|
|
13
|
+
const absoluteHomeDir = resolve(homeDir);
|
|
14
|
+
const absolutePackageRoot = resolve(packageRoot);
|
|
15
|
+
const absoluteAgentsDir = resolve(agentsDir);
|
|
16
|
+
assertSeparateSourceAndDeployment(absolutePackageRoot, absoluteAgentsDir);
|
|
17
|
+
|
|
18
|
+
const codexTemplateRoot = join(absolutePackageRoot, "lib", "templates", "codex");
|
|
19
|
+
const workflowTemplateRoot = join(absolutePackageRoot, "lib", "templates", "workflow");
|
|
20
|
+
const piAgentDir = join(absoluteHomeDir, ".pi", "agent");
|
|
21
|
+
return {
|
|
22
|
+
homeDir: absoluteHomeDir,
|
|
23
|
+
packageRoot: absolutePackageRoot,
|
|
24
|
+
agentsDir: absoluteAgentsDir,
|
|
25
|
+
workflowTemplateRoot,
|
|
26
|
+
installMetadataName: ".abelworkflow-install.json",
|
|
27
|
+
claudeSettingsPath: join(absoluteHomeDir, ".claude", "settings.json"),
|
|
28
|
+
claudeMetaConfigPath: join(absoluteHomeDir, ".claude.json"),
|
|
29
|
+
codexConfigPath: join(absoluteHomeDir, ".codex", "config.toml"),
|
|
30
|
+
codexAuthPath: join(absoluteHomeDir, ".codex", "auth.json"),
|
|
31
|
+
codexTemplateConfigPath: join(codexTemplateRoot, "config-base.toml"),
|
|
32
|
+
codexTemplateAgentsPath: join(codexTemplateRoot, "agents"),
|
|
33
|
+
piAgentDir,
|
|
34
|
+
piAuthPath: join(piAgentDir, "auth.json"),
|
|
35
|
+
piModelsPath: join(piAgentDir, "models.json"),
|
|
36
|
+
piSettingsPath: join(piAgentDir, "settings.json")
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function assertSeparateSourceAndDeployment(packageRoot, agentsDir) {
|
|
41
|
+
const canonicalPackageRoot = canonicalizeExistingPath(packageRoot);
|
|
42
|
+
const canonicalAgentsDir = canonicalizeExistingPath(agentsDir);
|
|
43
|
+
if (!containsPath(canonicalPackageRoot, canonicalAgentsDir)
|
|
44
|
+
&& !containsPath(canonicalAgentsDir, canonicalPackageRoot)) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
throw new Error(
|
|
49
|
+
`源码目录 packageRoot (${packageRoot}) 与部署目录 agentsDir (${agentsDir}) 不能相同或互相嵌套。`
|
|
50
|
+
+ "请将源码克隆到独立目录(例如 ~/src/AbelWorkflow),再用 --agents-dir 指向 ~/.agents;"
|
|
51
|
+
+ "请手动迁移已有源码克隆,安装器不会自动移动或删除数据,--force 也不能绕过此限制。"
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function canonicalizeExistingPath(path) {
|
|
56
|
+
const missingParts = [];
|
|
57
|
+
let candidate = path;
|
|
58
|
+
|
|
59
|
+
while (true) {
|
|
60
|
+
try {
|
|
61
|
+
return resolve(realpathSync.native(candidate), ...missingParts);
|
|
62
|
+
} catch (error) {
|
|
63
|
+
const parent = dirname(candidate);
|
|
64
|
+
if (error?.code !== "ENOENT" || parent === candidate) return path;
|
|
65
|
+
missingParts.unshift(basename(candidate));
|
|
66
|
+
candidate = parent;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function containsPath(parent, child, pathOps = { isAbsolute, relative, sep }) {
|
|
72
|
+
const childRelativePath = pathOps.relative(parent, child);
|
|
73
|
+
return childRelativePath === ""
|
|
74
|
+
|| (childRelativePath !== ".."
|
|
75
|
+
&& !childRelativePath.startsWith(`..${pathOps.sep}`)
|
|
76
|
+
&& !pathOps.isAbsolute(childRelativePath));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
const defaultPaths = createPaths();
|
|
80
|
+
|
|
81
|
+
function getPlatform() {
|
|
82
|
+
return process.env.ABELWORKFLOW_TEST_PLATFORM || process.platform;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
function isWindows() {
|
|
86
|
+
return getPlatform() === "win32";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function pathToLabel(path, homeDir = defaultPaths.homeDir) {
|
|
90
|
+
return path.replace(homeDir, "~");
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function maskSecret(value) {
|
|
94
|
+
if (!value) return "未配置";
|
|
95
|
+
if (value.length <= 4) return "*".repeat(value.length);
|
|
96
|
+
return `${"*".repeat(Math.max(4, value.length - 4))}${value.slice(-4)}`;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export { containsPath, createPaths, defaultPaths, getPlatform, isWindows, maskSecret, pathToLabel };
|