abelworkflow 1.1.0 → 1.1.2

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.
@@ -1,23 +1,6 @@
1
1
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
- import { createNodeInsecureDispatcher, installProviderTlsFetch } from "./tls-fetch.mjs";
3
-
4
- let nodeInsecureDispatcher: unknown;
5
-
6
- function getNodeInsecureDispatcher() {
7
- if (nodeInsecureDispatcher) return nodeInsecureDispatcher;
8
- nodeInsecureDispatcher = createNodeInsecureDispatcher();
9
- return nodeInsecureDispatcher;
10
- }
11
2
 
12
3
  export default function (pi: ExtensionAPI) {
13
- if (typeof globalThis.fetch === "function") {
14
- const runtime = typeof (globalThis as any).Bun === "undefined" ? "node" : "bun";
15
- installProviderTlsFetch({
16
- runtime,
17
- insecureDispatcher: runtime === "node" ? getNodeInsecureDispatcher : undefined
18
- });
19
- }
20
-
21
4
  pi.on("before_provider_request", (event, ctx) => {
22
5
  const payload = event.payload as any;
23
6
  if (!payload || typeof payload !== "object" || Array.isArray(payload)) return;
package/lib/cli/main.mjs CHANGED
@@ -126,31 +126,32 @@ ${c.bold("Default behavior:")}
126
126
  `);
127
127
  }
128
128
 
129
- async function configureCodexForPaths(paths) {
129
+ async function configureProviderWithMetadata(paths, configure) {
130
130
  const metadata = await readInstallMetadata(paths);
131
131
  const packageVersion = await packageVersionFor(paths, metadata);
132
- const result = await configureCodexApi(paths, promptApi, {
133
- managedAuthKeys: getPreviousManagedCodexAuthKeys(metadata),
134
- managedCodexAgentFiles: getPreviousManagedCodexAgentFiles(metadata)
135
- });
132
+ const overrides = await configure(metadata);
136
133
  await writeInstallMetadata(paths, finalizeProviderInstallMetadata({
137
134
  previousMetadata: metadata,
138
135
  packageVersion,
139
- overrides: {
136
+ overrides
137
+ }));
138
+ }
139
+
140
+ async function configureCodexForPaths(paths) {
141
+ await configureProviderWithMetadata(paths, async (metadata) => {
142
+ const result = await configureCodexApi(paths, promptApi, {
143
+ managedAuthKeys: getPreviousManagedCodexAuthKeys(metadata),
144
+ managedCodexAgentFiles: getPreviousManagedCodexAgentFiles(metadata)
145
+ });
146
+ return {
140
147
  managedCodexAuthKeys: result.managedAuthKeys,
141
148
  managedCodexAgentFiles: result.managedCodexAgentFiles
142
- }
143
- }));
149
+ };
150
+ });
144
151
  }
145
152
 
146
153
  async function configureClaudeForPaths(paths) {
147
- const metadata = await readInstallMetadata(paths);
148
- const packageVersion = await packageVersionFor(paths, metadata);
149
- await configureClaudeApi(paths, promptApi);
150
- await writeInstallMetadata(paths, finalizeProviderInstallMetadata({
151
- previousMetadata: metadata,
152
- packageVersion
153
- }));
154
+ await configureProviderWithMetadata(paths, () => configureClaudeApi(paths, promptApi));
154
155
  }
155
156
 
156
157
  async function runFullInit(options) {
@@ -176,13 +177,13 @@ async function runFullInit(options) {
176
177
  await installCliTool("pi", promptApi);
177
178
  }
178
179
  if (await confirmOrCancel({ message: "是否配置 Pi 当前有效 Provider API?", initialValue: commandExists("pi") })) {
179
- await configurePiApi(options.paths, ensurePiResourcesLinked, promptApi);
180
+ await configurePiApi(options.paths, ensurePiResourcesLinkedWithReport, promptApi);
180
181
  }
181
182
  if (await confirmOrCancel({ message: "是否填写 grok-search 环境变量?", initialValue: false })) {
182
- await configureGrokSearchEnv(options.paths, (paths) => ensureSkillPresent(paths, "grok-search"), promptApi);
183
+ await configureGrokSearchEnv(options.paths, (paths) => ensureSkillPresentWithReport(paths, "grok-search"), promptApi);
183
184
  }
184
185
  if (await confirmOrCancel({ message: "是否填写 context7-auto-research 环境变量?", initialValue: false })) {
185
- await configureContext7Env(options.paths, (paths) => ensureSkillPresent(paths, "context7-auto-research"), promptApi);
186
+ await configureContext7Env(options.paths, (paths) => ensureSkillPresentWithReport(paths, "context7-auto-research"), promptApi);
186
187
  }
187
188
 
188
189
  const conflictCount = uniqueReportPaths(installReport.conflicts).length;
@@ -205,35 +206,31 @@ async function runInteractiveMenu(options) {
205
206
  return opt;
206
207
  };
207
208
  const cliToolMenus = {
208
- "pi-cli": {
209
- tool: "pi",
209
+ pi: {
210
210
  title: "Pi",
211
- actions: {
212
- "pi-install": async () => installCliTool("pi", promptApi),
213
- "pi-api": async () => configurePiApi(options.paths, ensurePiResourcesLinkedWithReport, promptApi)
214
- }
211
+ install: () => installCliTool("pi", promptApi),
212
+ configure: () => configurePiApi(options.paths, ensurePiResourcesLinkedWithReport, promptApi)
215
213
  },
216
- "codex-cli": {
217
- tool: "codex",
214
+ codex: {
218
215
  title: "Codex",
219
- actions: {
220
- "codex-install": async () => installCliTool("codex", promptApi),
221
- "codex-api": async () => configureCodexForPaths(options.paths)
222
- }
216
+ install: () => installCliTool("codex", promptApi),
217
+ configure: () => configureCodexForPaths(options.paths)
223
218
  },
224
- "claude-cli": {
225
- tool: "claude",
219
+ claude: {
226
220
  title: "Claude Code",
227
- actions: {
228
- "claude-install": async () => installCliTool("claude", promptApi),
229
- "claude-api": async () => configureClaudeForPaths(options.paths)
230
- }
221
+ install: () => installCliTool("claude", promptApi),
222
+ configure: () => configureClaudeForPaths(options.paths)
231
223
  }
232
224
  };
233
- const runCliToolMenu = async ({ tool, title, actions }) => {
225
+ const runCliToolMenu = async (tool) => {
226
+ const menu = cliToolMenus[tool];
227
+ const actions = {
228
+ [`${tool}-api`]: menu.configure,
229
+ [`${tool}-install`]: menu.install
230
+ };
234
231
  while (true) {
235
232
  const choice = await p.select({
236
- message: `请选择 ${title} 操作`,
233
+ message: `请选择 ${menu.title} 操作`,
237
234
  options: buildCliToolMenuDescriptors(tool).map(buildOption),
238
235
  initialValue: `${tool}-api`
239
236
  });
@@ -259,9 +256,9 @@ async function runInteractiveMenu(options) {
259
256
  }),
260
257
  "grok-search": async () => configureGrokSearchEnv(options.paths, (paths) => ensureSkillPresentWithReport(paths, "grok-search"), promptApi),
261
258
  context7: async () => configureContext7Env(options.paths, (paths) => ensureSkillPresentWithReport(paths, "context7-auto-research"), promptApi),
262
- "pi-cli": async () => runCliToolMenu(cliToolMenus["pi-cli"]),
263
- "codex-cli": async () => runCliToolMenu(cliToolMenus["codex-cli"]),
264
- "claude-cli": async () => runCliToolMenu(cliToolMenus["claude-cli"])
259
+ "pi-cli": async () => runCliToolMenu("pi"),
260
+ "codex-cli": async () => runCliToolMenu("codex"),
261
+ "claude-cli": async () => runCliToolMenu("claude")
265
262
  };
266
263
 
267
264
  while (true) {
@@ -376,10 +373,4 @@ async function main(argv, runtime = {}) {
376
373
  }
377
374
  }
378
375
 
379
- export {
380
- main,
381
- presentInstallReport,
382
- printHelp,
383
- runFullInit,
384
- runInteractiveMenu
385
- };
376
+ export { main, presentInstallReport };
@@ -11,22 +11,10 @@ const interactiveMenuDescriptors = [
11
11
  { value: "exit", label: "退出", group: "exit" }
12
12
  ];
13
13
  const interactiveMenuDefaultValue = "full-init";
14
- const cliToolMenuDescriptorMap = {
15
- pi: [
16
- { value: "pi-api", label: "配置 Pi API" },
17
- { value: "pi-install", label: "安装/更新 Pi" },
18
- { value: "back", label: "返回上一级" }
19
- ],
20
- codex: [
21
- { value: "codex-api", label: "配置 Codex API" },
22
- { value: "codex-install", label: "安装/更新 Codex" },
23
- { value: "back", label: "返回上一级" }
24
- ],
25
- claude: [
26
- { value: "claude-api", label: "配置 Claude Code API" },
27
- { value: "claude-install", label: "安装/更新 Claude Code" },
28
- { value: "back", label: "返回上一级" }
29
- ]
14
+ const cliToolLabels = {
15
+ pi: "Pi",
16
+ codex: "Codex",
17
+ claude: "Claude Code"
30
18
  };
31
19
  class CancelledError extends Error {
32
20
  constructor(message = "用户取消") {
@@ -36,9 +24,13 @@ class CancelledError extends Error {
36
24
  }
37
25
 
38
26
  function buildCliToolMenuDescriptors(tool) {
39
- const descriptors = cliToolMenuDescriptorMap[tool];
40
- if (!descriptors) throw new Error(`Unknown CLI tool: ${tool}`);
41
- return descriptors.map((descriptor) => ({ ...descriptor }));
27
+ const label = cliToolLabels[tool];
28
+ if (!label) throw new Error(`Unknown CLI tool: ${tool}`);
29
+ return [
30
+ { value: `${tool}-api`, label: `配置 ${label} API` },
31
+ { value: `${tool}-install`, label: `安装/更新 ${label}` },
32
+ { value: "back", label: "返回上一级" }
33
+ ];
42
34
  }
43
35
 
44
36
  function required(message = "此项不能为空") {
@@ -79,6 +71,16 @@ function assertNotCancelled(value) {
79
71
  if (p.isCancel(value)) throw new CancelledError();
80
72
  }
81
73
 
74
+ async function passwordOrExisting({ message, existingValue, requiredMessage = "API Key 不能为空" }) {
75
+ const input = await p.password(passwordPromptOptions(
76
+ message,
77
+ existingValue,
78
+ requiredMessage ? requiredUnlessExisting(existingValue, requiredMessage) : undefined
79
+ ));
80
+ assertNotCancelled(input);
81
+ return resolvePasswordValue(input, existingValue);
82
+ }
83
+
82
84
  async function confirmOrCancel({ message, initialValue = false }) {
83
85
  const value = await p.confirm({ message, initialValue, active: "是", inactive: "否" });
84
86
  assertNotCancelled(value);
@@ -98,6 +100,7 @@ export {
98
100
  confirmOrCancel,
99
101
  interactiveMenuDefaultValue,
100
102
  interactiveMenuDescriptors,
103
+ passwordOrExisting,
101
104
  passwordPromptOptions,
102
105
  required,
103
106
  requiredUnlessExisting,
@@ -34,14 +34,6 @@ function quoteEnvValue(value) {
34
34
  throw new Error("Environment value cannot be represented without changing its meaning");
35
35
  }
36
36
 
37
- function renderDotenv(values) {
38
- const lines = Object.entries(values)
39
- .filter(([, value]) => value !== undefined && value !== null && value !== "")
40
- .sort(([left], [right]) => left.localeCompare(right))
41
- .map(([key, value]) => `${key}=${quoteEnvValue(String(value))}`);
42
- return lines.length ? `${lines.join("\n")}\n` : "";
43
- }
44
-
45
37
  function getDotenvLines(content) {
46
38
  const lines = [];
47
39
  for (let start = 0; start < content.length;) {
@@ -99,4 +91,4 @@ function updateDotenvContent(content, updates) {
99
91
  return next;
100
92
  }
101
93
 
102
- export { parseDotenv, quoteEnvValue, renderDotenv, updateDotenvContent };
94
+ export { parseDotenv, quoteEnvValue, updateDotenvContent };
@@ -6,7 +6,6 @@ import { stripJsonComments } from "./jsonc.mjs";
6
6
 
7
7
  const newBackupMarker = ".abelworkflow.bak.";
8
8
  const newBackupSuffixPattern = /^\d+-\d+-\d{10,}$/u;
9
- const chmod = fs.chmod;
10
9
  let uniqueFileIndex = 0;
11
10
 
12
11
  function nextUniqueSuffix() {
@@ -225,21 +224,6 @@ async function updateLockedJson(path, updater, options = {}) {
225
224
  }
226
225
  }
227
226
 
228
- async function backupExistingPath(targetPath, options = {}) {
229
- return copyBackup(targetPath, options);
230
- }
231
-
232
- async function backupPrivateFile(targetPath, content, options = {}) {
233
- if (!(await pathExists(targetPath))) return null;
234
- const backupLimit = options.backupLimit ?? 3;
235
- if (backupLimit === 0) return null;
236
- const backupPath = await createBackupPath(targetPath);
237
- await fs.writeFile(backupPath, content, { encoding: "utf8", flag: "wx", mode: 0o600 });
238
- if (isPosix()) await fs.chmod(backupPath, 0o600);
239
- await pruneNewBackups(targetPath, backupLimit);
240
- return backupPath;
241
- }
242
-
243
227
  async function backupIfNeeded(targetPath, options = {}) {
244
228
  if (!(await pathExists(targetPath))) return null;
245
229
  const backupLimit = options.backupLimit ?? 3;
@@ -281,19 +265,8 @@ async function updateDotenvFile(path, updates, options = {}) {
281
265
  return writeText(path, updateDotenvContent(content, updates), options);
282
266
  }
283
267
 
284
- async function writeJsonFileSafe(path, data, options = {}) {
285
- return writeJson(path, data, { ...options, backupLimit: options.backupLimit ?? 0 });
286
- }
287
-
288
- async function writeJsonFileWithBackup(path, data, options = {}) {
289
- return writeJson(path, data, options);
290
- }
291
-
292
268
  export {
293
- backupExistingPath,
294
269
  backupIfNeeded,
295
- backupPrivateFile,
296
- chmod,
297
270
  ensurePrivateJsonFile,
298
271
  pathExists,
299
272
  pathTargetExists,
@@ -304,7 +277,5 @@ export {
304
277
  updateDotenvFile,
305
278
  updateLockedJson,
306
279
  writeJson,
307
- writeJsonFileSafe,
308
- writeJsonFileWithBackup,
309
280
  writeText
310
281
  };
@@ -46,6 +46,15 @@ function removeTomlSection(content, sectionName) {
46
46
  return `${content.slice(0, section.start)}${content.slice(section.end)}`;
47
47
  }
48
48
 
49
+ function removeTomlSectionField(content, sectionName, field) {
50
+ const document = parseTomlDocument(content);
51
+ const section = findTomlSection(document, sectionName);
52
+ const entry = section && findTomlAssignment(document.assignments, field, section);
53
+ return entry
54
+ ? `${content.slice(0, entry.start)}${content.slice(entry.lineEnd)}`
55
+ : content;
56
+ }
57
+
49
58
  function buildTomlSection(sectionName, values, lineEnding = "\n") {
50
59
  const lines = [`[${sectionName}]`];
51
60
  for (const [key, value] of Object.entries(values)) {
@@ -399,14 +408,6 @@ function findTomlAssignment(assignments, field, section) {
399
408
  ));
400
409
  }
401
410
 
402
- function splitTopLevelTomlContent(content) {
403
- const { topLevelEnd } = parseTomlDocument(content);
404
- return {
405
- topLevel: topLevelEnd === -1 ? content : content.slice(0, topLevelEnd),
406
- rest: topLevelEnd === -1 ? "" : content.slice(topLevelEnd)
407
- };
408
- }
409
-
410
411
  function extractTopLevelTomlEntries(content) {
411
412
  const document = parseTomlDocument(content);
412
413
  return document.assignments
@@ -631,8 +632,8 @@ export {
631
632
  parseTomlSection,
632
633
  readTopLevelTomlString,
633
634
  removeTomlSection,
635
+ removeTomlSectionField,
634
636
  removeTopLevelTomlField,
635
- splitTopLevelTomlContent,
636
637
  updateTomlSectionFields,
637
638
  updateTopLevelTomlField
638
639
  };
@@ -1,4 +1,3 @@
1
- import { createHash } from "node:crypto";
2
1
  import * as fs from "node:fs/promises";
3
2
  import { join, relative, resolve } from "node:path";
4
3
  import {
@@ -8,6 +7,7 @@ import {
8
7
  readJsonFileSafe,
9
8
  writeText
10
9
  } from "../config/store.mjs";
10
+ import { hashBytes } from "../utils.mjs";
11
11
  import { createInstallReport, readInstallMetadata } from "./state.mjs";
12
12
 
13
13
  const ignoredSkillPathPatterns = [
@@ -69,10 +69,6 @@ function mapGitignoreTemplate(relativePath) {
69
69
  return relativePath.replace(/(^|\/)gitignore\.template$/u, "$1.gitignore");
70
70
  }
71
71
 
72
- function hashBytes(content) {
73
- return createHash("sha256").update(content).digest("hex");
74
- }
75
-
76
72
  function shouldCopySkillPath(skillsRoot, sourcePath) {
77
73
  const relativePath = normalizeRelativePath(relative(skillsRoot, sourcePath));
78
74
  if (!relativePath) return true;
@@ -316,9 +312,9 @@ async function ensureManagedContainerDirectory(targetPath, sourcePath) {
316
312
  export {
317
313
  collectManagedAssets,
318
314
  ensureManagedContainerDirectory,
319
- hashBytes,
320
315
  pathsReferToSameEntry,
321
316
  readManagedFiles,
317
+ readPackageVersion,
322
318
  shouldCopySkillPath,
323
319
  syncManagedFiles
324
320
  };
@@ -1,7 +1,6 @@
1
- import { join } from "node:path";
2
- import { pathExists, readJsonFileSafe } from "../config/store.mjs";
3
- import { createPaths, defaultPaths } from "../paths.mjs";
4
- import { readManagedFiles, syncManagedFiles } from "./assets.mjs";
1
+ import { pathExists } from "../config/store.mjs";
2
+ import { createPaths } from "../paths.mjs";
3
+ import { readManagedFiles, readPackageVersion, syncManagedFiles } from "./assets.mjs";
5
4
  import { linkClaude, linkCodex, linkPi } from "./links.mjs";
6
5
  import {
7
6
  buildInstallMetadata,
@@ -27,15 +26,6 @@ function validatedPaths(paths) {
27
26
  });
28
27
  }
29
28
 
30
- function resolveInstallPaths(options = {}) {
31
- if (options.paths) return validatedPaths(options.paths);
32
- return createPaths({
33
- homeDir: options.homeDir ?? defaultPaths.homeDir,
34
- packageRoot: options.packageRoot ?? defaultPaths.packageRoot,
35
- agentsDir: options.agentsDir ?? defaultPaths.agentsDir
36
- });
37
- }
38
-
39
29
  function reportFromLinkResults(results) {
40
30
  const report = createInstallReport();
41
31
  for (const result of results) {
@@ -58,12 +48,17 @@ async function packageVersionFor(paths, previousMetadata) {
58
48
  && previousMetadata.packageVersion) {
59
49
  return previousMetadata.packageVersion;
60
50
  }
61
- const packagePath = join(paths.packageRoot, "package.json");
62
- const packageData = await readJsonFileSafe(packagePath);
63
- if (typeof packageData.version !== "string" || !packageData.version) {
64
- throw new Error(`Missing package version in ${packagePath}`);
65
- }
66
- return packageData.version;
51
+ return readPackageVersion(paths);
52
+ }
53
+
54
+ async function writeInstallState(paths, assetResult, linkedTargets) {
55
+ const metadata = buildInstallMetadata({
56
+ previousMetadata: assetResult.previousMetadata,
57
+ packageVersion: assetResult.packageVersion,
58
+ managedFiles: assetResult.managedFiles,
59
+ linkedTargets
60
+ });
61
+ await writeInstallMetadata(paths, metadata);
67
62
  }
68
63
 
69
64
  async function installWorkflow(options) {
@@ -94,20 +89,12 @@ async function installWorkflow(options) {
94
89
  const codexResults = await linkCodex(paths, previousLinkedTargets, linkOptions);
95
90
  const piResults = await linkPi(paths, previousLinkedTargets, linkOptions);
96
91
  const linkResults = [...claudeResults, ...codexResults, ...piResults];
97
- const linkedTargets = mergeLinkedTargets(previousLinkedTargets, linkResults);
98
-
99
- const metadata = buildInstallMetadata({
100
- previousMetadata,
101
- packageVersion: assetResult.packageVersion,
102
- managedFiles: assetResult.managedFiles,
103
- linkedTargets
104
- });
105
- await writeInstallMetadata(paths, metadata);
92
+ await writeInstallState(paths, assetResult, mergeLinkedTargets(previousLinkedTargets, linkResults));
106
93
  return mergeInstallReports(assetResult.report, reportFromLinkResults(linkResults));
107
94
  }
108
95
 
109
96
  async function installManagedWorkflow(options = {}) {
110
- return installWorkflow({ ...options, paths: resolveInstallPaths(options) });
97
+ return installWorkflow({ ...options, paths: validatedPaths(options.paths) });
111
98
  }
112
99
 
113
100
  async function ensureSkillPresent(inputPaths, skillName) {
@@ -118,13 +105,7 @@ async function ensureSkillPresent(inputPaths, skillName) {
118
105
  force: false,
119
106
  pathPrefix: `skills/${skillName}/`
120
107
  });
121
- const metadata = buildInstallMetadata({
122
- previousMetadata,
123
- packageVersion: assetResult.packageVersion,
124
- managedFiles: assetResult.managedFiles,
125
- linkedTargets: previousMetadata.linkedTargets ?? {}
126
- });
127
- await writeInstallMetadata(paths, metadata);
108
+ await writeInstallState(paths, assetResult, previousMetadata.linkedTargets ?? {});
128
109
  return assetResult.report;
129
110
  }
130
111
 
@@ -134,13 +115,7 @@ async function ensurePiResourcesLinked(inputPaths) {
134
115
  const assetResult = await syncManagedFiles({ paths, force: false });
135
116
  const previousLinkedTargets = previousMetadata.linkedTargets ?? {};
136
117
  const piResults = await linkPi(paths, previousLinkedTargets, { force: false });
137
- const metadata = buildInstallMetadata({
138
- previousMetadata,
139
- packageVersion: assetResult.packageVersion,
140
- managedFiles: assetResult.managedFiles,
141
- linkedTargets: mergeLinkedTargets(previousLinkedTargets, piResults)
142
- });
143
- await writeInstallMetadata(paths, metadata);
118
+ await writeInstallState(paths, assetResult, mergeLinkedTargets(previousLinkedTargets, piResults));
144
119
  return mergeInstallReports(assetResult.report, reportFromLinkResults(piResults));
145
120
  }
146
121
 
@@ -1,12 +1,9 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
3
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
- }
4
+ import { containsPath, isWindows } from "../paths.mjs";
5
+ import { hashBytes } from "../utils.mjs";
6
+ import { ensureManagedContainerDirectory } from "./assets.mjs";
10
7
 
11
8
  async function createManagedTargetState(targetPath, sourcePath, kind, mode, status) {
12
9
  let targetHash;
@@ -16,12 +13,7 @@ async function createManagedTargetState(targetPath, sourcePath, kind, mode, stat
16
13
  return { targetPath, sourcePath, kind, mode, status, ...(targetHash ? { targetHash } : {}) };
17
14
  }
18
15
 
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
- }
16
+ async function createSymlink(targetPath, sourcePath, linkType) {
25
17
  await fs.symlink(sourcePath, targetPath, linkType);
26
18
  }
27
19
 
@@ -150,7 +142,7 @@ async function ensureManagedLink(targetPath, sourcePath, kind, previousLinkedTar
150
142
 
151
143
  const linkType = isWindows() ? (kind === "dir" ? "junction" : "file") : kind;
152
144
  try {
153
- await createSymlink(targetPath, sourcePath, linkType, kind);
145
+ await createSymlink(targetPath, sourcePath, linkType);
154
146
  return createManagedTargetState(targetPath, sourcePath, kind, "symlink", "linked");
155
147
  } catch (error) {
156
148
  if (!shouldFallbackToManagedFile(error, kind)) throw error;
@@ -258,11 +250,8 @@ async function getPiExtensionNames(extensionsDir) {
258
250
  }
259
251
 
260
252
  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}`);
253
+ return pathOps.relative(managedSourceRoot, targetPath) !== ""
254
+ && containsPath(managedSourceRoot, targetPath, pathOps);
266
255
  }
267
256
 
268
257
  async function pruneManagedTargets(targetDir, managedSourceRoot, expectedNames, previousLinkedTargets, options = {}) {
@@ -1,5 +1,6 @@
1
1
  import { join } from "node:path";
2
2
  import { readJsonFileSafe, writeJson } from "../config/store.mjs";
3
+ import { isManagedCodexAgentFileEntry } from "../utils.mjs";
3
4
 
4
5
  function metadataPathFor(paths) {
5
6
  return join(paths.agentsDir, paths.installMetadataName);
@@ -30,11 +31,7 @@ function getPreviousManagedCodexAuthKeys(previousMetadata = {}) {
30
31
  function normalizeManagedCodexAgentFiles(value = {}) {
31
32
  if (!value || typeof value !== "object" || Array.isArray(value)) return {};
32
33
  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)
34
+ isManagedCodexAgentFileEntry(name, hash)
38
35
  ))));
39
36
  }
40
37
 
@@ -63,7 +60,7 @@ function buildInstallMetadata({
63
60
  managedCodexAgentFiles = getPreviousManagedCodexAgentFiles(previousMetadata),
64
61
  linkedTargets = {}
65
62
  }) {
66
- const metadata = {
63
+ return {
67
64
  schemaVersion: 2,
68
65
  packageVersion,
69
66
  managedFiles: sortObject(managedFiles),
@@ -71,10 +68,6 @@ function buildInstallMetadata({
71
68
  managedCodexAgentFiles: normalizeManagedCodexAgentFiles(managedCodexAgentFiles),
72
69
  linkedTargets: sortObject(linkedTargets)
73
70
  };
74
- if (typeof previousMetadata.installedAt === "string") {
75
- metadata.installedAt = previousMetadata.installedAt;
76
- }
77
- return metadata;
78
71
  }
79
72
 
80
73
  function finalizeProviderInstallMetadata({
@@ -100,17 +93,6 @@ function finalizeProviderInstallMetadata({
100
93
  });
101
94
  }
102
95
 
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
96
  function mergeLinkedTargets(previousLinkedTargets = {}, results = []) {
115
97
  const nextLinkedTargets = { ...previousLinkedTargets };
116
98
  for (const result of results) {
@@ -154,7 +136,6 @@ export {
154
136
  finalizeProviderInstallMetadata,
155
137
  getPreviousManagedCodexAgentFiles,
156
138
  getPreviousManagedCodexAuthKeys,
157
- linkedTargetsFromResults,
158
139
  mergeInstallReports,
159
140
  mergeLinkedTargets,
160
141
  metadataPathFor,