@rzx/ohs 1.1.0 → 1.1.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -196887,6 +196887,18 @@ class OpenHarnessClient {
196887
196887
  signal: options.signal
196888
196888
  });
196889
196889
  }
196890
+ async listSkills(options = {}) {
196891
+ return await this.request("/skills", {
196892
+ signal: options.signal
196893
+ });
196894
+ }
196895
+ async removeSkill(id, input, options = {}) {
196896
+ return await this.request(`/skills/${encodeURIComponent(id)}`, {
196897
+ method: "DELETE",
196898
+ body: input,
196899
+ signal: options.signal
196900
+ });
196901
+ }
196890
196902
  async listAgentPersonas(options = {}) {
196891
196903
  const response = await this.request("/agent-personas", {
196892
196904
  signal: options.signal
@@ -276984,6 +276996,8 @@ function buildAgentTranscript(messages, parts) {
276984
276996
  const output = [];
276985
276997
  const attachmentsByMessageId = {};
276986
276998
  for (const message of [...messages].sort((a, b) => a.seq - b.seq)) {
276999
+ if (isPresentationOnlyMessage(message))
277000
+ continue;
276987
277001
  const messageParts = (byMessage.get(message.id) ?? []).sort((a, b) => a.seq - b.seq);
276988
277002
  const attachments = attachmentsFromParts(messageParts);
276989
277003
  if (attachments.length > 0)
@@ -277023,6 +277037,14 @@ function buildAgentTranscript(messages, parts) {
277023
277037
  }
277024
277038
  return { messages: output, attachmentsByMessageId };
277025
277039
  }
277040
+ function isPresentationOnlyMessage(message) {
277041
+ const presentation = message.metadata.presentation;
277042
+ if (!presentation || typeof presentation !== "object" || Array.isArray(presentation)) {
277043
+ return false;
277044
+ }
277045
+ const value = presentation;
277046
+ return value.kind === "model_switch" && typeof value.fromModel === "string" && Boolean(value.fromModel.trim()) && typeof value.toModel === "string" && Boolean(value.toModel.trim());
277047
+ }
277026
277048
  function agentMessagesToTranscript(messages) {
277027
277049
  const output = [];
277028
277050
  for (const message of messages) {
@@ -280509,15 +280531,39 @@ class SessionApplicationService {
280509
280531
  try {
280510
280532
  const before = this.context.events.checkpoint();
280511
280533
  const nextModel = metadata ? readSessionRuntimeConfig({ ...existing, metadata }).model : undefined;
280512
- const session = this.context.store.updateSession(sessionId, {
280513
- title: input.title,
280514
- model: nextModel,
280515
- agent: input.agent,
280516
- metadata
280534
+ const modelChanged = nextModel !== undefined && nextModel !== existing.model;
280535
+ const session = this.context.store.transaction(() => {
280536
+ const updated = this.context.store.updateSession(sessionId, {
280537
+ title: input.title,
280538
+ model: nextModel,
280539
+ agent: input.agent,
280540
+ metadata
280541
+ });
280542
+ if (!modelChanged)
280543
+ return updated;
280544
+ const message = this.context.store.createMessage({
280545
+ sessionId,
280546
+ role: "system",
280547
+ metadata: {
280548
+ presentation: {
280549
+ kind: "model_switch",
280550
+ fromModel: existing.model,
280551
+ toModel: nextModel
280552
+ }
280553
+ }
280554
+ });
280555
+ this.context.store.upsertMessagePart({
280556
+ sessionId,
280557
+ messageId: message.id,
280558
+ type: "text",
280559
+ status: "completed",
280560
+ text: `模型已切换 ${existing.model} → ${nextModel}`
280561
+ });
280562
+ return updated;
280517
280563
  });
280518
280564
  if (runtimeConfigurationChanged)
280519
280565
  await this.context.agentPool.close(sessionId);
280520
- if (nextModel !== undefined && nextModel !== existing.model) {
280566
+ if (modelChanged) {
280521
280567
  this.context.contextUsageCache?.invalidate(sessionId);
280522
280568
  }
280523
280569
  this.context.events.publishSince(before);
@@ -286009,8 +286055,8 @@ function createDaemonImageToTextTool(options) {
286009
286055
  };
286010
286056
  }
286011
286057
  async function recognizeAttachment(input, context, options) {
286012
- if (Object.keys(input).some((key) => key !== "attachment_id")) {
286013
- return errorResult4("attachment_id cannot be combined with image_path, image_url, or prompt", "command");
286058
+ if (Object.keys(input).some((key) => key !== "attachment_id" && key !== "prompt")) {
286059
+ return errorResult4("attachment_id cannot be combined with another image source", "command");
286014
286060
  }
286015
286061
  const assetId = typeof input.attachment_id === "string" ? input.attachment_id.trim() : "";
286016
286062
  if (!assetId || !context.sessionId)
@@ -286913,6 +286959,148 @@ var init_default_node_application = __esm(() => {
286913
286959
  init_daemon_application();
286914
286960
  });
286915
286961
 
286962
+ // ../../packages/server/src/application/skill-management-service.ts
286963
+ import { createHash as createHash12 } from "node:crypto";
286964
+ import { readFile as readFile17, realpath as realpath7, stat as stat9, unlink as unlink4 } from "node:fs/promises";
286965
+ import {
286966
+ basename as basename12,
286967
+ dirname as dirname20,
286968
+ isAbsolute as isAbsolute12,
286969
+ join as join39,
286970
+ relative as relative9,
286971
+ resolve as resolve26,
286972
+ sep as sep4
286973
+ } from "node:path";
286974
+
286975
+ class SkillManagementService {
286976
+ options;
286977
+ personalSkillsDir;
286978
+ configDir;
286979
+ bundledSkills;
286980
+ constructor(options) {
286981
+ this.options = options;
286982
+ const defaultSkillsDir = getSkillsDir();
286983
+ this.configDir = resolve26(options.configDir ?? dirname20(defaultSkillsDir));
286984
+ this.personalSkillsDir = options.configDir ? join39(this.configDir, "skills") : resolve26(defaultSkillsDir);
286985
+ this.bundledSkills = options.bundledSkills ?? BUNDLED_SKILLS;
286986
+ }
286987
+ async list() {
286988
+ const warnings = [];
286989
+ const projects = await validProjects(this.options.projects.list(), warnings);
286990
+ const skills = this.bundledSkills.map((skill) => desktopSkill(skill, "bundled"));
286991
+ const seen = new Set;
286992
+ for (const project of projects) {
286993
+ await this.load(join39(project.root, ".agents", "skills"), "agent", skills, seen, warnings, project);
286994
+ await this.load(join39(project.root, ".claude", "skills"), "agent", skills, seen, warnings, project);
286995
+ const managed = join39(project.root, ".openharness-ts", "skills");
286996
+ if (key(managed) !== key(this.personalSkillsDir)) {
286997
+ await this.load(managed, "project", skills, seen, warnings, project);
286998
+ }
286999
+ }
287000
+ await this.load(this.personalSkillsDir, "personal", skills, seen, warnings);
287001
+ return { skills, projects: projects.map(({ info }) => info), warnings };
287002
+ }
287003
+ async remove(input) {
287004
+ const snapshot = await this.list();
287005
+ const item = snapshot.skills.find((candidate) => candidate.id === input.id);
287006
+ if (!item)
287007
+ throw new Error("找不到指定技能,请刷新后重试。");
287008
+ if (item.readOnly || item.source !== "project" && item.source !== "personal") {
287009
+ throw new Error("此技能来自只读目录,不能删除。");
287010
+ }
287011
+ const target = await this.secureTarget(item);
287012
+ const current = await readFile17(target, "utf8");
287013
+ if (current !== input.expectedContent)
287014
+ throw new Error("技能已在其他位置修改,请刷新后重试。");
287015
+ await unlink4(target);
287016
+ return await this.list();
287017
+ }
287018
+ async secureTarget(item) {
287019
+ const target = await realpath7(item.path);
287020
+ if (item.source === "personal") {
287021
+ await assertTarget(this.configDir, this.personalSkillsDir, target);
287022
+ return target;
287023
+ }
287024
+ if (!item.projectPath)
287025
+ throw new Error("技能缺少所属项目。");
287026
+ const project = await realpath7(item.projectPath);
287027
+ await assertTarget(project, join39(project, ".openharness-ts", "skills"), target);
287028
+ return target;
287029
+ }
287030
+ async load(directory, source, output, seen, warnings, project) {
287031
+ const loader = new SkillLoader(new SkillRegistry);
287032
+ const loaded = await loader.loadFromDirectory(directory, {
287033
+ source: source === "personal" ? "user" : "project",
287034
+ recursive: true
287035
+ });
287036
+ for (const skill of loaded) {
287037
+ try {
287038
+ const path = await realpath7(skill.path);
287039
+ if (seen.has(key(path)))
287040
+ continue;
287041
+ seen.add(key(path));
287042
+ output.push(desktopSkill({ ...skill, path }, source, project?.info));
287043
+ } catch {
287044
+ warnings.push(`无法读取技能文件:${skill.path}`);
287045
+ }
287046
+ }
287047
+ }
287048
+ }
287049
+ function createSkillManagementService(options) {
287050
+ return new SkillManagementService(options);
287051
+ }
287052
+ async function validProjects(input, warnings) {
287053
+ const projects = [];
287054
+ const seen = new Set;
287055
+ for (const project of input) {
287056
+ try {
287057
+ const root = await realpath7(resolve26(project.path));
287058
+ if (!(await stat9(root)).isDirectory() || seen.has(key(root)))
287059
+ continue;
287060
+ seen.add(key(root));
287061
+ projects.push({
287062
+ root,
287063
+ info: { name: project.name.trim() || basename12(root), path: root }
287064
+ });
287065
+ } catch {
287066
+ warnings.push(`项目不存在或不是目录:${project.path}`);
287067
+ }
287068
+ }
287069
+ return projects;
287070
+ }
287071
+ function desktopSkill(skill, source, project) {
287072
+ return {
287073
+ id: `skill_${createHash12("sha256").update(source).update("\x00").update(key(skill.path)).update("\x00").update(skill.name).digest("hex").slice(0, 24)}`,
287074
+ name: skill.name,
287075
+ description: skill.description,
287076
+ content: skill.content,
287077
+ path: skill.path,
287078
+ source,
287079
+ readOnly: source === "bundled" || source === "agent",
287080
+ ...project ? { projectPath: project.path, projectName: project.name } : {}
287081
+ };
287082
+ }
287083
+ async function assertTarget(owner, allowed, target) {
287084
+ const physicalOwner = await realpath7(owner);
287085
+ const physicalAllowed = await realpath7(allowed);
287086
+ if (!within(physicalOwner, physicalAllowed))
287087
+ throw new Error("技能目录通过符号链接指向了允许范围之外。");
287088
+ if (!within(physicalAllowed, target))
287089
+ throw new Error("技能文件位于允许范围之外。");
287090
+ }
287091
+ function within(root, target) {
287092
+ const path = relative9(root, target);
287093
+ return path !== "" && path !== ".." && !path.startsWith(`..${sep4}`) && !isAbsolute12(path);
287094
+ }
287095
+ function key(path) {
287096
+ const normalized = resolve26(path);
287097
+ return process.platform === "win32" ? normalized.toLowerCase() : normalized;
287098
+ }
287099
+ var init_skill_management_service = __esm(() => {
287100
+ init_src();
287101
+ init_src2();
287102
+ });
287103
+
286916
287104
  // ../../packages/server/src/shared/observability.ts
286917
287105
  function writeStructuredLog(event) {
286918
287106
  console.info(JSON.stringify({ timestamp: new Date().toISOString(), ...event }));
@@ -288121,7 +288309,9 @@ function createServiceRoutes(context) {
288121
288309
  if (!context.outputStyleService)
288122
288310
  return errorResponse(501, "Output style service is not configured");
288123
288311
  try {
288124
- return jsonResponse({ styles: await context.outputStyleService.list() });
288312
+ return jsonResponse({
288313
+ styles: await context.outputStyleService.list()
288314
+ });
288125
288315
  } catch (error) {
288126
288316
  return errorResponse(500, error instanceof Error ? error.message : String(error));
288127
288317
  }
@@ -288159,7 +288349,10 @@ function createServiceRoutes(context) {
288159
288349
  if (!lease)
288160
288350
  return errorResponse(409, "Cannot uninstall plugins while session runs are active");
288161
288351
  try {
288162
- const result = await context.pluginService.uninstall({ cwd, id: c.req.param("id") });
288352
+ const result = await context.pluginService.uninstall({
288353
+ cwd,
288354
+ id: c.req.param("id")
288355
+ });
288163
288356
  if (result.restartRuntimes)
288164
288357
  await context.control.closeAllRuntimes();
288165
288358
  return jsonResponse({ message: result.message });
@@ -288191,6 +288384,36 @@ function createServiceRoutes(context) {
288191
288384
  } finally {
288192
288385
  lease.release();
288193
288386
  }
288387
+ }).get("/skills", async () => {
288388
+ if (!context.skillService)
288389
+ return errorResponse(501, "Skill service is not configured");
288390
+ try {
288391
+ return jsonResponse(await context.skillService.list());
288392
+ } catch (error) {
288393
+ return errorResponse(500, error instanceof Error ? error.message : String(error));
288394
+ }
288395
+ }).delete("/skills/:id", async (c) => {
288396
+ if (!context.skillService)
288397
+ return errorResponse(501, "Skill service is not configured");
288398
+ const lease = context.control.acquireGlobalMutation();
288399
+ if (!lease)
288400
+ return errorResponse(409, "Cannot remove skills while session runs are active");
288401
+ try {
288402
+ const body = await readJson(c);
288403
+ if (typeof body.expectedContent !== "string") {
288404
+ return errorResponse(400, "expectedContent is required");
288405
+ }
288406
+ const result = await context.skillService.remove({
288407
+ id: c.req.param("id"),
288408
+ expectedContent: body.expectedContent
288409
+ });
288410
+ await context.control.closeAllRuntimes();
288411
+ return jsonResponse(result);
288412
+ } catch (error) {
288413
+ return errorResponse(400, error instanceof Error ? error.message : String(error));
288414
+ } finally {
288415
+ lease.release();
288416
+ }
288194
288417
  }).get("/agent-personas", async () => {
288195
288418
  if (!context.agentPersonaService)
288196
288419
  return errorResponse(501, "Agent persona service is not configured");
@@ -288207,7 +288430,10 @@ function createServiceRoutes(context) {
288207
288430
  return errorResponse(400, "cwd is required");
288208
288431
  const sessionId = c.req.query("sessionId") ?? undefined;
288209
288432
  try {
288210
- const listed = await context.hooksService.list({ cwd, ...sessionId ? { sessionId } : {} });
288433
+ const listed = await context.hooksService.list({
288434
+ cwd,
288435
+ ...sessionId ? { sessionId } : {}
288436
+ });
288211
288437
  const hooks = [...listed.hooks];
288212
288438
  if (sessionId && context.control.runtimeInspectionAvailable) {
288213
288439
  if (!context.control.sessionExists(sessionId)) {
@@ -288263,7 +288489,13 @@ async function installLocalPlugin(context, c, link) {
288263
288489
  if (!lease)
288264
288490
  return errorResponse(409, "Cannot install plugins while session runs are active");
288265
288491
  try {
288266
- const result = await context.pluginService.installLocal({ cwd, sourcePath, scope, approvedPermissions, link });
288492
+ const result = await context.pluginService.installLocal({
288493
+ cwd,
288494
+ sourcePath,
288495
+ scope,
288496
+ approvedPermissions,
288497
+ link
288498
+ });
288267
288499
  if (result.restartRuntimes)
288268
288500
  await context.control.closeAllRuntimes();
288269
288501
  return jsonResponse({ message: result.message });
@@ -289615,6 +289847,7 @@ class OpenHarnessHttpServer {
289615
289847
  outputStyleService: this.services.outputStyle,
289616
289848
  projectInitService: this.services.projectInit,
289617
289849
  pluginService: this.services.plugin,
289850
+ skillService: this.services.skill ?? createSkillManagementService({ projects: this.application.projects }),
289618
289851
  agentPersonaService: this.services.agentPersona,
289619
289852
  hooksService: this.services.hooks,
289620
289853
  control: this.application.control
@@ -289680,6 +289913,7 @@ var init_server = __esm(() => {
289680
289913
  init_dist2();
289681
289914
  init_dist3();
289682
289915
  init_default_node_application();
289916
+ init_skill_management_service();
289683
289917
  init_support2();
289684
289918
  init_auth();
289685
289919
  init_attachment4();
@@ -289896,10 +290130,10 @@ var init_auth_service = __esm(() => {
289896
290130
 
289897
290131
  // ../../packages/output-styles/src/index.ts
289898
290132
  import { readdirSync as readdirSync6, readFileSync as readFileSync14, mkdirSync as mkdirSync15 } from "node:fs";
289899
- import { join as join39 } from "node:path";
290133
+ import { join as join40 } from "node:path";
289900
290134
  import { homedir as homedir11 } from "node:os";
289901
290135
  function getOutputStylesDir() {
289902
- const dir = join39(homedir11(), ".openharness-ts", "output_styles");
290136
+ const dir = join40(homedir11(), ".openharness-ts", "output_styles");
289903
290137
  try {
289904
290138
  mkdirSync15(dir, { recursive: true });
289905
290139
  } catch {}
@@ -289916,7 +290150,7 @@ function loadOutputStyles() {
289916
290150
  }
289917
290151
  for (const file of files) {
289918
290152
  try {
289919
- const content = readFileSync14(join39(dir, file), "utf-8");
290153
+ const content = readFileSync14(join40(dir, file), "utf-8");
289920
290154
  styles.push({ name: file.replace(/\.md$/, ""), content, source: "user" });
289921
290155
  } catch {}
289922
290156
  }
@@ -290575,14 +290809,14 @@ var init_profile_service = __esm(() => {
290575
290809
  });
290576
290810
 
290577
290811
  // ../../packages/server/src/application/default-services/project-init-service.ts
290578
- import { join as join40 } from "node:path";
290812
+ import { join as join41 } from "node:path";
290579
290813
  function createDefaultProjectInitService() {
290580
290814
  return {
290581
290815
  async init({ cwd }) {
290582
290816
  const { writeFile, mkdir, access } = await import("node:fs/promises");
290583
290817
  const files = [
290584
290818
  {
290585
- path: join40(cwd, "CLAUDE.md"),
290819
+ path: join41(cwd, "CLAUDE.md"),
290586
290820
  content: `# Project Rules
290587
290821
 
290588
290822
  Add your project-specific rules and instructions here.
@@ -290590,7 +290824,7 @@ Add your project-specific rules and instructions here.
290590
290824
  label: "CLAUDE.md"
290591
290825
  },
290592
290826
  {
290593
- path: join40(cwd, PROJECT_CONFIG_DIR_NAME, "README.md"),
290827
+ path: join41(cwd, PROJECT_CONFIG_DIR_NAME, "README.md"),
290594
290828
  content: `# OpenHarness Config
290595
290829
 
290596
290830
  This directory contains OpenHarness project configuration.
@@ -290598,7 +290832,7 @@ This directory contains OpenHarness project configuration.
290598
290832
  label: `${PROJECT_CONFIG_DIR_NAME}/README.md`
290599
290833
  },
290600
290834
  {
290601
- path: join40(cwd, PROJECT_CONFIG_DIR_NAME, "memory", "MEMORY.md"),
290835
+ path: join41(cwd, PROJECT_CONFIG_DIR_NAME, "memory", "MEMORY.md"),
290602
290836
  content: `# Memory
290603
290837
 
290604
290838
  This file stores project memory for the AI assistant.
@@ -290607,10 +290841,10 @@ This file stores project memory for the AI assistant.
290607
290841
  }
290608
290842
  ];
290609
290843
  const dirs = [
290610
- join40(cwd, PROJECT_CONFIG_DIR_NAME),
290611
- join40(cwd, PROJECT_CONFIG_DIR_NAME, "memory"),
290612
- join40(cwd, PROJECT_CONFIG_DIR_NAME, "plugins"),
290613
- join40(cwd, PROJECT_CONFIG_DIR_NAME, "skills")
290844
+ join41(cwd, PROJECT_CONFIG_DIR_NAME),
290845
+ join41(cwd, PROJECT_CONFIG_DIR_NAME, "memory"),
290846
+ join41(cwd, PROJECT_CONFIG_DIR_NAME, "plugins"),
290847
+ join41(cwd, PROJECT_CONFIG_DIR_NAME, "skills")
290614
290848
  ];
290615
290849
  const lines = ["Initializing OpenHarness project...", ""];
290616
290850
  for (const dir of dirs) {
@@ -291041,6 +291275,7 @@ var init_application = __esm(() => {
291041
291275
  init_support();
291042
291276
  init_events2();
291043
291277
  init_project_application_service();
291278
+ init_skill_management_service();
291044
291279
  init_channel3();
291045
291280
  init_application_backup();
291046
291281
  init_application_retention_service();
@@ -291082,7 +291317,7 @@ var init_src23 = __esm(() => {
291082
291317
  });
291083
291318
 
291084
291319
  // src/ensure-daemon.ts
291085
- import { stat as stat9 } from "node:fs/promises";
291320
+ import { stat as stat10 } from "node:fs/promises";
291086
291321
  async function ensureLocalDaemon(options = {}) {
291087
291322
  const cliPath = options.cliPath ?? process.argv[1];
291088
291323
  if (!cliPath)
@@ -291090,7 +291325,7 @@ async function ensureLocalDaemon(options = {}) {
291090
291325
  const expectedVersion = options.expectedVersion ?? VERSION;
291091
291326
  const daemonProbeOptions = {
291092
291327
  expectedVersion,
291093
- minimumStartedAt: (await stat9(cliPath)).mtimeMs
291328
+ minimumStartedAt: (await stat10(cliPath)).mtimeMs
291094
291329
  };
291095
291330
  await Promise.resolve().then(() => init_src23());
291096
291331
  const waitForDaemonRegistry = async (spawned) => {
@@ -370332,13 +370567,13 @@ function createMcpCommand() {
370332
370567
  // src/commands/plugin.ts
370333
370568
  init_src15();
370334
370569
  init_src12();
370335
- import { join as join46, resolve as resolve28 } from "node:path";
370336
- import { mkdtemp as mkdtemp3, rm as rm8 } from "node:fs/promises";
370570
+ import { join as join49, resolve as resolve31 } from "node:path";
370571
+ import { mkdtemp as mkdtemp3, rm as rm9 } from "node:fs/promises";
370337
370572
  import { tmpdir as tmpdir5 } from "node:os";
370338
370573
  // ../../packages/plugin-converters/src/core/digest.ts
370339
- import { createHash as createHash12 } from "node:crypto";
370340
- import { readFile as readFile17, readdir as readdir9 } from "node:fs/promises";
370341
- import { join as join41, relative as relative9 } from "node:path";
370574
+ import { createHash as createHash13 } from "node:crypto";
370575
+ import { lstat as lstat6, readFile as readFile18, readdir as readdir9 } from "node:fs/promises";
370576
+ import { join as join43, relative as relative10 } from "node:path";
370342
370577
  function stable(value) {
370343
370578
  if (Array.isArray(value))
370344
370579
  return `[${value.map(stable).join(",")}]`;
@@ -370347,14 +370582,14 @@ function stable(value) {
370347
370582
  return JSON.stringify(value);
370348
370583
  }
370349
370584
  function digestValue(value) {
370350
- return createHash12("sha256").update(stable(value)).digest("hex");
370585
+ return createHash13("sha256").update(stable(value)).digest("hex");
370351
370586
  }
370352
370587
  async function files(root, dir = root) {
370353
370588
  const out = [];
370354
370589
  for (const entry of (await readdir9(dir, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
370355
- const full = join41(dir, entry.name);
370590
+ const full = join43(dir, entry.name);
370356
370591
  if (entry.isSymbolicLink())
370357
- throw new Error(`Source symlink is not allowed: ${relative9(root, full)}`);
370592
+ throw new Error(`Source symlink is not allowed: ${relative10(root, full)}`);
370358
370593
  if (entry.isDirectory())
370359
370594
  out.push(...await files(root, full));
370360
370595
  else if (entry.isFile())
@@ -370363,12 +370598,20 @@ async function files(root, dir = root) {
370363
370598
  return out;
370364
370599
  }
370365
370600
  async function digestSource(root) {
370366
- const hash = createHash12("sha256");
370601
+ if ((await lstat6(root)).isSymbolicLink())
370602
+ throw new Error("Source root link is not allowed");
370603
+ const hash = createHash13("sha256");
370604
+ hash.update(`openharness-source-v2
370605
+ `);
370367
370606
  for (const file of await files(root)) {
370368
- hash.update(relative9(root, file).replaceAll("\\", "/"));
370369
- hash.update("\x00");
370370
- hash.update(await readFile17(file));
370371
- hash.update("\x00");
370607
+ const content = await readFile18(file);
370608
+ hash.update(JSON.stringify([
370609
+ relative10(root, file).replaceAll("\\", "/"),
370610
+ content.length,
370611
+ createHash13("sha256").update(content).digest("hex")
370612
+ ]));
370613
+ hash.update(`
370614
+ `);
370372
370615
  }
370373
370616
  return hash.digest("hex");
370374
370617
  }
@@ -370411,27 +370654,27 @@ class ConverterRegistry {
370411
370654
  }
370412
370655
  // ../../packages/plugin-converters/src/claude-code/detector.ts
370413
370656
  import { access as access6 } from "node:fs/promises";
370414
- import { join as join43 } from "node:path";
370657
+ import { join as join44 } from "node:path";
370415
370658
  var exists4 = async (path) => access6(path).then(() => true, () => false);
370416
370659
  async function detectClaudeCodePlugin(root) {
370417
370660
  const evidence = [];
370418
- if (await exists4(join43(root, ".claude-plugin", "plugin.json")))
370661
+ if (await exists4(join44(root, ".claude-plugin", "plugin.json")))
370419
370662
  evidence.push(".claude-plugin/plugin.json");
370420
370663
  for (const name of ["skills", "commands", "agents", "hooks", ".mcp.json", "SKILL.md"])
370421
- if (await exists4(join43(root, name)))
370664
+ if (await exists4(join44(root, name)))
370422
370665
  evidence.push(name);
370423
- if (!evidence.length || evidence.length === 1 && evidence[0] === "SKILL.md" && !await exists4(join43(root, "SKILL.md")))
370666
+ if (!evidence.length || evidence.length === 1 && evidence[0] === "SKILL.md" && !await exists4(join44(root, "SKILL.md")))
370424
370667
  return null;
370425
370668
  return { converterId: "claude-code", confidence: evidence.includes(".claude-plugin/plugin.json") ? 1 : 0.7, evidence };
370426
370669
  }
370427
370670
  // ../../packages/plugin-converters/src/claude-code/parser.ts
370428
- import { access as access7, lstat as lstat6, readFile as readFile18, readdir as readdir10 } from "node:fs/promises";
370429
- import { basename as basename12, isAbsolute as isAbsolute12, join as join44, relative as relative10, resolve as resolve26 } from "node:path";
370671
+ import { access as access7, lstat as lstat7, readFile as readFile19, readdir as readdir10 } from "node:fs/promises";
370672
+ import { basename as basename13, isAbsolute as isAbsolute13, join as join45, relative as relative11, resolve as resolve27 } from "node:path";
370430
370673
  var exists5 = async (path) => access7(path).then(() => true, () => false);
370431
370674
  async function collect(root, path, extensions) {
370432
370675
  if (!await exists5(path))
370433
370676
  return [];
370434
- const info = await lstat6(path);
370677
+ const info = await lstat7(path);
370435
370678
  if (info.isSymbolicLink())
370436
370679
  throw new Error(`Claude source symlink is not allowed: ${path}`);
370437
370680
  if (info.isFile())
@@ -370440,7 +370683,7 @@ async function collect(root, path, extensions) {
370440
370683
  for (const entry of (await readdir10(path, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
370441
370684
  if (entry.isSymbolicLink())
370442
370685
  throw new Error(`Claude source symlink is not allowed: ${entry.name}`);
370443
- const full = join44(path, entry.name);
370686
+ const full = join45(path, entry.name);
370444
370687
  if (entry.isDirectory())
370445
370688
  result.push(...await collect(root, full, extensions));
370446
370689
  else if (!extensions || extensions.some((x) => entry.name.toLowerCase().endsWith(x)))
@@ -370450,31 +370693,31 @@ async function collect(root, path, extensions) {
370450
370693
  }
370451
370694
  var asPaths = (value) => typeof value === "string" ? [value] : Array.isArray(value) && value.every((x) => typeof x === "string") ? value : [];
370452
370695
  function safe(root, value) {
370453
- const target = resolve26(root, value);
370454
- const rel = relative10(resolve26(root), target);
370455
- if (rel.startsWith("..") || isAbsolute12(rel) || target === resolve26(root))
370696
+ const target = resolve27(root, value);
370697
+ const rel = relative11(resolve27(root), target);
370698
+ if (rel.startsWith("..") || isAbsolute13(rel) || target === resolve27(root))
370456
370699
  throw new Error(`Claude component path escapes source: ${value}`);
370457
370700
  return target;
370458
370701
  }
370459
370702
  async function inspectClaudeCodePlugin(rootInput) {
370460
- const root = resolve26(rootInput);
370703
+ const root = resolve27(rootInput);
370461
370704
  let manifest = {};
370462
- const manifestPath = join44(root, ".claude-plugin", "plugin.json");
370705
+ const manifestPath = join45(root, ".claude-plugin", "plugin.json");
370463
370706
  if (await exists5(manifestPath))
370464
- manifest = JSON.parse(await readFile18(manifestPath, "utf8"));
370465
- const name = typeof manifest.name === "string" ? manifest.name : basename12(root).toLowerCase().replace(/[^a-z0-9-]+/g, "-");
370707
+ manifest = JSON.parse(await readFile19(manifestPath, "utf8"));
370708
+ const name = typeof manifest.name === "string" ? manifest.name : basename13(root).toLowerCase().replace(/[^a-z0-9-]+/g, "-");
370466
370709
  const inventory = {};
370467
- const skillRoots = [join44(root, "skills"), ...asPaths(manifest.skills).map((x) => safe(root, x))];
370468
- if (await exists5(join44(root, "SKILL.md")))
370469
- skillRoots.push(join44(root, "SKILL.md"));
370710
+ const skillRoots = [join45(root, "skills"), ...asPaths(manifest.skills).map((x) => safe(root, x))];
370711
+ if (await exists5(join45(root, "SKILL.md")))
370712
+ skillRoots.push(join45(root, "SKILL.md"));
370470
370713
  inventory.skills = (await Promise.all(skillRoots.map((x) => collect(root, x, ["skill.md"])))).flat();
370471
370714
  for (const kind of ["commands", "agents"]) {
370472
370715
  const custom = asPaths(manifest[kind]);
370473
- const roots = custom.length ? custom.map((x) => safe(root, x)) : [join44(root, kind)];
370716
+ const roots = custom.length ? custom.map((x) => safe(root, x)) : [join45(root, kind)];
370474
370717
  inventory[kind] = (await Promise.all(roots.map((x) => collect(root, x, [".md"])))).flat();
370475
370718
  }
370476
- const hooks = join44(root, "hooks", "hooks.json");
370477
- const mcp = join44(root, ".mcp.json");
370719
+ const hooks = join45(root, "hooks", "hooks.json");
370720
+ const mcp = join45(root, ".mcp.json");
370478
370721
  inventory.hooks = await exists5(hooks) ? [hooks] : [];
370479
370722
  inventory.mcpServers = await exists5(mcp) ? [mcp] : [];
370480
370723
  return { root, format: "claude-code", identity: { id: `converted.claude.${name}`, name, version: typeof manifest.version === "string" ? manifest.version : "0.0.0" }, inventory, diagnostics: [] };
@@ -370584,34 +370827,34 @@ function convertClaudeAgentMarkdown(content) {
370584
370827
  // ../../packages/plugin-converters/src/claude-code/converter.ts
370585
370828
  init_src12();
370586
370829
  import { randomUUID as randomUUID27 } from "node:crypto";
370587
- import { access as access8, cp as cp3, mkdir as mkdir14, readFile as readFile19, rename as rename4, rm as rm7, writeFile as writeFile11 } from "node:fs/promises";
370588
- import { basename as basename13, dirname as dirname20, join as join45, relative as relative11, resolve as resolve27 } from "node:path";
370589
- var rel = (inspection, path) => relative11(inspection.root, path).replaceAll("\\", "/");
370830
+ import { access as access8, cp as cp3, mkdir as mkdir14, readFile as readFile20, rename as rename4, rm as rm7, writeFile as writeFile11 } from "node:fs/promises";
370831
+ import { basename as basename14, dirname as dirname21, join as join46, relative as relative12, resolve as resolve28 } from "node:path";
370832
+ var rel = (inspection, path) => relative12(inspection.root, path).replaceAll("\\", "/");
370590
370833
  function componentPath(inspection, sourcePath, directory) {
370591
370834
  const parts = rel(inspection, sourcePath).split("/");
370592
370835
  const directoryIndex = parts.indexOf(directory);
370593
370836
  return (directoryIndex >= 0 ? parts.slice(directoryIndex + 1) : parts).join("/");
370594
370837
  }
370595
370838
  async function copySkillBundle(inspection, skillFile, targetRoot) {
370596
- const sourceDirectory = dirname20(skillFile);
370597
- const relativeDirectory = relative11(inspection.root, sourceDirectory);
370598
- const targetRelative = relativeDirectory === "" ? inspection.identity.name : dirname20(componentPath(inspection, skillFile, "skills"));
370599
- const targetDirectory = join45(targetRoot, targetRelative === "." ? inspection.identity.name : targetRelative);
370839
+ const sourceDirectory = dirname21(skillFile);
370840
+ const relativeDirectory = relative12(inspection.root, sourceDirectory);
370841
+ const targetRelative = relativeDirectory === "" ? inspection.identity.name : dirname21(componentPath(inspection, skillFile, "skills"));
370842
+ const targetDirectory = join46(targetRoot, targetRelative === "." ? inspection.identity.name : targetRelative);
370600
370843
  if (sourceDirectory !== inspection.root) {
370601
370844
  await cp3(sourceDirectory, targetDirectory, { recursive: true, errorOnExist: true, force: false });
370602
370845
  return;
370603
370846
  }
370604
370847
  await mkdir14(targetDirectory, { recursive: true });
370605
- await cp3(skillFile, join45(targetDirectory, "SKILL.md"), { errorOnExist: true, force: false });
370848
+ await cp3(skillFile, join46(targetDirectory, "SKILL.md"), { errorOnExist: true, force: false });
370606
370849
  for (const resourceDirectory of ["assets", "references", "scripts"]) {
370607
- const sourceResource = join45(sourceDirectory, resourceDirectory);
370850
+ const sourceResource = join46(sourceDirectory, resourceDirectory);
370608
370851
  if (await access8(sourceResource).then(() => true, () => false)) {
370609
- await cp3(sourceResource, join45(targetDirectory, resourceDirectory), { recursive: true, errorOnExist: true, force: false });
370852
+ await cp3(sourceResource, join46(targetDirectory, resourceDirectory), { recursive: true, errorOnExist: true, force: false });
370610
370853
  }
370611
370854
  }
370612
370855
  }
370613
370856
  async function convertHooks(source, target) {
370614
- const raw = JSON.parse(await readFile19(source, "utf8"));
370857
+ const raw = JSON.parse(await readFile20(source, "utf8"));
370615
370858
  const rows = raw.hooks && typeof raw.hooks === "object" ? raw.hooks : raw;
370616
370859
  const output = {};
370617
370860
  for (const [sourceEvent, entries] of Object.entries(rows)) {
@@ -370628,12 +370871,12 @@ async function convertHooks(source, target) {
370628
370871
  }
370629
370872
  if (!Object.keys(output).length)
370630
370873
  return false;
370631
- await mkdir14(dirname20(target), { recursive: true });
370874
+ await mkdir14(dirname21(target), { recursive: true });
370632
370875
  await writeFile11(target, JSON.stringify(output, null, 2));
370633
370876
  return true;
370634
370877
  }
370635
370878
  async function convertMcp(source, target) {
370636
- const raw = JSON.parse(await readFile19(source, "utf8"));
370879
+ const raw = JSON.parse(await readFile20(source, "utf8"));
370637
370880
  const servers = {};
370638
370881
  for (const [name, value] of Object.entries(raw.mcpServers ?? {})) {
370639
370882
  if (typeof value.command === "string")
@@ -370643,7 +370886,7 @@ async function convertMcp(source, target) {
370643
370886
  else
370644
370887
  throw new Error(`Ambiguous MCP server: ${name}`);
370645
370888
  }
370646
- await mkdir14(dirname20(target), { recursive: true });
370889
+ await mkdir14(dirname21(target), { recursive: true });
370647
370890
  await writeFile11(target, JSON.stringify({ servers }, null, 2));
370648
370891
  }
370649
370892
 
@@ -370668,7 +370911,7 @@ class ClaudeCodePluginConverter {
370668
370911
  });
370669
370912
  }
370670
370913
  for (const hookFile of inspection.inventory.hooks ?? []) {
370671
- const raw = JSON.parse(await readFile19(hookFile, "utf8"));
370914
+ const raw = JSON.parse(await readFile20(hookFile, "utf8"));
370672
370915
  const rows = raw.hooks && typeof raw.hooks === "object" ? raw.hooks : raw;
370673
370916
  if (Object.keys(rows).some((event) => CLAUDE_HOOK_EVENT_MAP[event])) {
370674
370917
  items.push({
@@ -370707,7 +370950,7 @@ class ClaudeCodePluginConverter {
370707
370950
  throw new Error(`Blocked conversion items require approval: ${blocked.map((x) => x.id).join(", ")}`);
370708
370951
  if (await digestSource(input.inspection.root) !== input.plan.sourceDigest)
370709
370952
  throw new Error("Source changed after conversion planning; inspect and plan again");
370710
- const output = resolve27(input.output);
370953
+ const output = resolve28(input.output);
370711
370954
  if (await access8(output).then(() => true, () => false))
370712
370955
  throw new Error(`Conversion output already exists: ${output}`);
370713
370956
  const temporary = `${output}.tmp-${randomUUID27()}`;
@@ -370717,31 +370960,31 @@ class ClaudeCodePluginConverter {
370717
370960
  const components = {};
370718
370961
  const skills = input.inspection.inventory.skills ?? [];
370719
370962
  for (const skill of skills) {
370720
- await copySkillBundle(input.inspection, skill, join45(temporary, "skills"));
370963
+ await copySkillBundle(input.inspection, skill, join46(temporary, "skills"));
370721
370964
  }
370722
370965
  const commands = input.inspection.inventory.commands ?? [];
370723
370966
  for (const command of commands) {
370724
- const name = basename13(command, ".md");
370725
- const target = join45(temporary, "skills", name, "SKILL.md");
370726
- await mkdir14(dirname20(target), { recursive: true });
370727
- await writeFile11(target, await readFile19(command, "utf8"));
370967
+ const name = basename14(command, ".md");
370968
+ const target = join46(temporary, "skills", name, "SKILL.md");
370969
+ await mkdir14(dirname21(target), { recursive: true });
370970
+ await writeFile11(target, await readFile20(command, "utf8"));
370728
370971
  }
370729
370972
  if (skills.length || commands.length)
370730
370973
  components.skills = ["./skills"];
370731
370974
  const agents = input.inspection.inventory.agents ?? [];
370732
370975
  for (const agent of agents) {
370733
- const target = join45(temporary, "agents", componentPath(input.inspection, agent, "agents"));
370734
- await mkdir14(dirname20(target), { recursive: true });
370735
- await writeFile11(target, convertClaudeAgentMarkdown(await readFile19(agent, "utf8")));
370976
+ const target = join46(temporary, "agents", componentPath(input.inspection, agent, "agents"));
370977
+ await mkdir14(dirname21(target), { recursive: true });
370978
+ await writeFile11(target, convertClaudeAgentMarkdown(await readFile20(agent, "utf8")));
370736
370979
  }
370737
370980
  if (agents.length)
370738
370981
  components.agents = ["./agents"];
370739
370982
  const hooks = input.inspection.inventory.hooks?.[0];
370740
- if (hooks && await convertHooks(hooks, join45(temporary, "hooks.json")))
370983
+ if (hooks && await convertHooks(hooks, join46(temporary, "hooks.json")))
370741
370984
  components.hooks = ["./hooks.json"];
370742
370985
  const mcp = input.inspection.inventory.mcpServers?.[0];
370743
370986
  if (mcp) {
370744
- await convertMcp(mcp, join45(temporary, "mcp.json"));
370987
+ await convertMcp(mcp, join46(temporary, "mcp.json"));
370745
370988
  components.mcpServers = ["./mcp.json"];
370746
370989
  }
370747
370990
  if (!Object.keys(components).length)
@@ -370754,13 +370997,13 @@ class ClaudeCodePluginConverter {
370754
370997
  metadata: { origin: "converted", sourceFormat: this.sourceFormat, converterId: this.id, converterVersion: this.version },
370755
370998
  components
370756
370999
  };
370757
- await mkdir14(join45(temporary, ".openharness-plugin"));
370758
- await writeFile11(join45(temporary, ".openharness-plugin", "plugin.json"), JSON.stringify(manifest, null, 2));
371000
+ await mkdir14(join46(temporary, ".openharness-plugin"));
371001
+ await writeFile11(join46(temporary, ".openharness-plugin", "plugin.json"), JSON.stringify(manifest, null, 2));
370759
371002
  const report = { schemaVersion: 1, status: input.plan.items.some((x) => x.fidelity === "unsupported") ? "partial" : "success", items: input.plan.items, diagnostics: input.plan.diagnostics };
370760
- await mkdir14(join45(temporary, ".openharness-conversion"));
370761
- await writeFile11(join45(temporary, ".openharness-conversion", "plan.json"), JSON.stringify(input.plan, null, 2));
370762
- await writeFile11(join45(temporary, ".openharness-conversion", "report.json"), JSON.stringify(report, null, 2));
370763
- await writeFile11(join45(temporary, ".openharness-conversion", "provenance.json"), JSON.stringify({ sourceFormat: this.sourceFormat, sourceDigest: input.plan.sourceDigest, converterId: this.id, converterVersion: this.version, mappingVersion: CLAUDE_MAPPING_VERSION }, null, 2));
371003
+ await mkdir14(join46(temporary, ".openharness-conversion"));
371004
+ await writeFile11(join46(temporary, ".openharness-conversion", "plan.json"), JSON.stringify(input.plan, null, 2));
371005
+ await writeFile11(join46(temporary, ".openharness-conversion", "report.json"), JSON.stringify(report, null, 2));
371006
+ await writeFile11(join46(temporary, ".openharness-conversion", "provenance.json"), JSON.stringify({ sourceFormat: this.sourceFormat, sourceDigest: input.plan.sourceDigest, converterId: this.id, converterVersion: this.version, mappingVersion: CLAUDE_MAPPING_VERSION }, null, 2));
370764
371007
  const validation = await validateNativePlugin(temporary);
370765
371008
  if (validation.status !== "valid")
370766
371009
  throw new Error(validation.diagnostics.map((x) => x.message).join("; "));
@@ -370772,10 +371015,428 @@ class ClaudeCodePluginConverter {
370772
371015
  }
370773
371016
  }
370774
371017
  }
371018
+ // ../../packages/plugin-converters/src/codex/converter.ts
371019
+ init_src12();
371020
+ import { randomUUID as randomUUID28 } from "node:crypto";
371021
+ import { copyFile as copyFile2, lstat as lstat9, mkdir as mkdir15, readFile as readFile23, realpath as realpath8, rename as rename5, rm as rm8, writeFile as writeFile12 } from "node:fs/promises";
371022
+ import { dirname as dirname24, join as join48, relative as relative14, resolve as resolve30 } from "node:path";
371023
+
371024
+ // ../../packages/plugin-converters/src/codex/source.ts
371025
+ import { lstat as lstat8, readFile as readFile21, readdir as readdir11 } from "node:fs/promises";
371026
+ import { dirname as dirname23, isAbsolute as isAbsolute14, join as join47, relative as relative13, resolve as resolve29 } from "node:path";
371027
+ var PORTABLE_PLUGIN_SCHEMA = "https://agent-plugins.org/schemas/1.0.0/plugin.schema.json";
371028
+ var legacyManifest = ".codex-plugin/plugin.json";
371029
+ function object3(value, label) {
371030
+ if (!value || typeof value !== "object" || Array.isArray(value))
371031
+ throw new Error(`${label} must be an object`);
371032
+ return value;
371033
+ }
371034
+ async function exists6(path) {
371035
+ try {
371036
+ await lstat8(path);
371037
+ return true;
371038
+ } catch (error) {
371039
+ if (error.code === "ENOENT")
371040
+ return false;
371041
+ throw error;
371042
+ }
371043
+ }
371044
+ async function sourceFiles(root, directory = root) {
371045
+ const info = await lstat8(directory);
371046
+ if (info.isSymbolicLink())
371047
+ throw new Error(`Codex source link is not allowed: ${directory}`);
371048
+ if (!info.isDirectory())
371049
+ throw new Error(`Codex source must be a directory: ${directory}`);
371050
+ const files = [];
371051
+ for (const entry of (await readdir11(directory, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
371052
+ const full = join47(directory, entry.name);
371053
+ if (entry.isSymbolicLink())
371054
+ throw new Error(`Codex source link is not allowed: ${full}`);
371055
+ if (entry.isDirectory())
371056
+ files.push(...await sourceFiles(root, full));
371057
+ else if (entry.isFile())
371058
+ files.push(relative13(root, full).replaceAll("\\", "/"));
371059
+ else
371060
+ throw new Error(`Unsupported Codex source file type: ${full}`);
371061
+ }
371062
+ return files;
371063
+ }
371064
+ function isWithin3(root, path) {
371065
+ const rel = relative13(resolve29(root), resolve29(path));
371066
+ return rel === "" || !isAbsolute14(rel) && rel !== ".." && !rel.startsWith(`..${process.platform === "win32" ? "\\" : "/"}`);
371067
+ }
371068
+ function pathDeclaration(root, value) {
371069
+ if (!value.startsWith("./") || /[\\:\0]/.test(value) || value.split("/").includes("..")) {
371070
+ throw new Error(`Invalid Codex component path: ${value}`);
371071
+ }
371072
+ const full = resolve29(root, value);
371073
+ if (full === root || !isWithin3(root, full))
371074
+ throw new Error(`Codex component path escapes source: ${value}`);
371075
+ const path = relative13(root, full).replaceAll("\\", "/");
371076
+ if (path.split("/").some((part) => [".git", ".codex-plugin", ".claude-plugin", ".openharness-plugin", ".openharness-conversion"].includes(part))) {
371077
+ throw new Error(`Reserved Codex component path: ${value}`);
371078
+ }
371079
+ return path;
371080
+ }
371081
+ async function declaredPaths(root, value, label, defaults) {
371082
+ if (value === undefined)
371083
+ return (await Promise.all(defaults.map(async (path) => await exists6(join47(root, path)) ? path : undefined))).filter((path) => path !== undefined);
371084
+ const values = typeof value === "string" ? [value] : value;
371085
+ if (!Array.isArray(values) || !values.every((path) => typeof path === "string"))
371086
+ throw new Error(`${label} must be a path or path array`);
371087
+ const paths = values.map((path) => pathDeclaration(root, path));
371088
+ for (const path of paths)
371089
+ if (!await exists6(join47(root, path)))
371090
+ throw new Error(`Codex component path does not exist: ${path}`);
371091
+ return [...new Set(paths)];
371092
+ }
371093
+ async function readJson2(root, path) {
371094
+ return object3(JSON.parse(await readFile21(join47(root, path), "utf8")), path);
371095
+ }
371096
+ async function detectCodexPlugin(rootInput) {
371097
+ const root = resolve29(rootInput);
371098
+ if (await exists6(join47(root, legacyManifest)))
371099
+ return { converterId: "codex", confidence: 1, evidence: [legacyManifest] };
371100
+ if (!await exists6(join47(root, "plugin.json")))
371101
+ return null;
371102
+ await sourceFiles(root);
371103
+ const manifest = await readJson2(root, "plugin.json");
371104
+ return manifest.$schema === PORTABLE_PLUGIN_SCHEMA ? { converterId: "codex", confidence: 1, evidence: [`plugin.json:${PORTABLE_PLUGIN_SCHEMA}`] } : null;
371105
+ }
371106
+ async function readCodexSource(rootInput) {
371107
+ const root = resolve29(rootInput);
371108
+ const files = await sourceFiles(root);
371109
+ const portableCandidate = files.includes("plugin.json") ? await readJson2(root, "plugin.json") : undefined;
371110
+ if (typeof portableCandidate?.$schema === "string" && portableCandidate.$schema.startsWith("https://agent-plugins.org/") && portableCandidate.$schema !== PORTABLE_PLUGIN_SCHEMA) {
371111
+ throw new Error(`Unsupported portable plugin schema: ${portableCandidate.$schema}`);
371112
+ }
371113
+ const portable = portableCandidate?.$schema === PORTABLE_PLUGIN_SCHEMA;
371114
+ const extensions = portable && portableCandidate.extensions !== undefined ? object3(portableCandidate.extensions, "extensions") : {};
371115
+ const inline = extensions["com.openai"];
371116
+ const legacy = (!portable || inline === undefined) && files.includes(legacyManifest) ? await readJson2(root, legacyManifest) : undefined;
371117
+ const manifest = portable ? portableCandidate : legacy;
371118
+ if (!manifest)
371119
+ throw new Error("Codex manifest is missing or the portable schema is not supported");
371120
+ if (typeof manifest.name !== "string" || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(manifest.name))
371121
+ throw new Error("Codex name must use kebab-case");
371122
+ if (manifest.version !== undefined && (typeof manifest.version !== "string" || !manifest.version.trim()))
371123
+ throw new Error("Codex version must be a nonempty string");
371124
+ const settings = portable ? extensions["com.openai"] === undefined ? legacy ?? {} : object3(extensions["com.openai"], "extensions.com.openai") : manifest;
371125
+ const inventory = {};
371126
+ const declaredSkillRoots = await declaredPaths(root, portable ? undefined : settings.skills, "skills", ["skills"]);
371127
+ const skillRoots = declaredSkillRoots.filter((path) => !declaredSkillRoots.some((parent) => parent !== path && path.startsWith(`${parent}/`)));
371128
+ inventory.skills = [];
371129
+ for (const path of skillRoots) {
371130
+ const skills = files.filter((file) => (file === path || file.startsWith(`${path}/`)) && file.split("/").at(-1) === "SKILL.md");
371131
+ if (!skills.length)
371132
+ throw new Error(`Codex skill path contains no SKILL.md: ${path}`);
371133
+ inventory.skills.push(...skills.map((file) => join47(root, file)));
371134
+ }
371135
+ inventory.skills = [...new Set(inventory.skills)];
371136
+ const mcp = [];
371137
+ const mcpDeclaration = portable ? undefined : settings.mcpServers;
371138
+ if (mcpDeclaration && typeof mcpDeclaration === "object" && !Array.isArray(mcpDeclaration)) {
371139
+ const raw = object3(mcpDeclaration, "mcpServers");
371140
+ mcp.push({ path: `${legacyManifest}#mcpServers`, servers: object3(raw.mcpServers ?? raw, "mcpServers"), portable: false });
371141
+ } else {
371142
+ for (const path of await declaredPaths(root, mcpDeclaration, "mcpServers", [portable ? "mcp.json" : ".mcp.json"])) {
371143
+ const raw = await readJson2(root, path);
371144
+ mcp.push({ path, servers: object3(raw.mcpServers, `${path}.mcpServers`), portable });
371145
+ }
371146
+ }
371147
+ inventory.mcpServers = mcp.map((value) => value.path);
371148
+ const unsupported = [];
371149
+ if (portable) {
371150
+ const portableFields = new Set(["$schema", "name", "version", "description", "author", "homepage", "repository", "license", "keywords", "extensions"]);
371151
+ for (const key of Object.keys(manifest).filter((key) => !portableFields.has(key))) {
371152
+ unsupported.push({ kind: "manifest", path: `plugin.json#${key}`, reason: `Unrecognized portable manifest field: ${key}` });
371153
+ }
371154
+ for (const [key, extension] of Object.entries(extensions)) {
371155
+ if (key === "com.openai")
371156
+ continue;
371157
+ object3(extension, `extensions.${key}`);
371158
+ unsupported.push({ kind: "extensions", path: `plugin.json#extensions.${key}`, reason: `Portable extension ${key} has no Native conversion` });
371159
+ }
371160
+ }
371161
+ for (const skill of inventory.skills) {
371162
+ const path = relative13(root, join47(dirname23(skill), "agents/openai.yaml")).replaceAll("\\", "/");
371163
+ if (files.includes(path))
371164
+ unsupported.push({ kind: "skillMetadata", path, reason: "Codex skill invocation policy, tool dependencies and presentation in agents/openai.yaml are not applied by Native" });
371165
+ }
371166
+ for (const kind of ["apps", "hooks", "agents", "commands", "lspServers", "tools", "workflows", "channels", "providers", "ui", "outputStyles", "themes", "monitors", "binaries"]) {
371167
+ const declaration = settings[kind];
371168
+ const defaults = kind === "hooks" ? ["hooks/hooks.json"] : [];
371169
+ let paths;
371170
+ if (declaration !== null && typeof declaration === "object" && (!Array.isArray(declaration) || declaration.some((item) => typeof item !== "string"))) {
371171
+ paths = [`${portable ? "plugin.json" : legacyManifest}#${kind}`];
371172
+ } else
371173
+ paths = await declaredPaths(root, declaration, kind, defaults);
371174
+ for (const path of paths)
371175
+ unsupported.push({ kind, path, reason: `Codex ${kind} has no supported Native conversion in this version` });
371176
+ if (paths.length)
371177
+ inventory[kind] = paths;
371178
+ }
371179
+ const known = new Set(["$schema", "name", "version", "description", "author", "homepage", "repository", "license", "keywords", "skills", "mcpServers", "interface", "extensions", ...unsupported.map((item) => item.kind)]);
371180
+ for (const key of Object.keys(settings).filter((key) => !known.has(key))) {
371181
+ unsupported.push({ kind: key, path: `${portable ? "plugin.json" : legacyManifest}#${key}`, reason: `Unrecognized Codex manifest field: ${key}` });
371182
+ }
371183
+ const ui = settings.interface === undefined ? {} : object3(settings.interface, "interface");
371184
+ return {
371185
+ inspection: { root, format: "codex", identity: { id: `converted.codex.${manifest.name}`, name: manifest.name, version: manifest.version ?? "0.0.0" }, inventory, diagnostics: [] },
371186
+ files,
371187
+ metadataPaths: [...portable ? ["plugin.json"] : [], ...mcp.map((item) => item.path), ...unsupported.map((item) => item.path)],
371188
+ skillRoots,
371189
+ mcp,
371190
+ unsupported,
371191
+ ...typeof manifest.description === "string" ? { description: manifest.description } : {},
371192
+ ...typeof ui.displayName === "string" && ui.displayName.trim() ? { displayName: ui.displayName } : {}
371193
+ };
371194
+ }
371195
+
371196
+ // ../../packages/plugin-converters/src/codex/mcp.ts
371197
+ function stringMap(value) {
371198
+ return !!value && typeof value === "object" && !Array.isArray(value) && Object.values(value).every((item) => typeof item === "string");
371199
+ }
371200
+ function mapCodexMcp(value, portable) {
371201
+ let row;
371202
+ try {
371203
+ row = object3(value, "MCP server");
371204
+ } catch {
371205
+ return { reason: "MCP server must be an object" };
371206
+ }
371207
+ const unknown = Object.keys(row).filter((key) => !["type", "command", "args", "env", "url", "headers"].includes(key));
371208
+ if (unknown.length)
371209
+ return { reason: `MCP fields have no verified Native equivalent: ${unknown.sort().join(", ")}` };
371210
+ if (/\$\{|\$[A-Z_][A-Z0-9_]*|%[A-Z_][A-Z0-9_]*%/.test(JSON.stringify(row)))
371211
+ return { reason: "MCP environment or plugin-root interpolation is not supported" };
371212
+ if (row.command !== undefined && row.url !== undefined)
371213
+ return { reason: "MCP command and url cannot be combined" };
371214
+ if (typeof row.url === "string") {
371215
+ if (row.type !== undefined && row.type !== "http" && row.type !== "streamable-http" && row.type !== "sse")
371216
+ return { reason: "Unsupported MCP transport" };
371217
+ if (portable && row.type !== "streamable-http")
371218
+ return { reason: "Portable HTTP MCP requires streamable-http transport" };
371219
+ if (row.args !== undefined || row.env !== undefined || row.headers !== undefined && !stringMap(row.headers))
371220
+ return { reason: "Unsupported HTTP MCP options" };
371221
+ let url;
371222
+ try {
371223
+ url = new URL(row.url);
371224
+ } catch {
371225
+ return { reason: "Invalid MCP URL" };
371226
+ }
371227
+ if (!["https:", "http:"].includes(url.protocol) || url.username || url.password || url.hash)
371228
+ return { reason: "MCP requires an HTTP(S) URL without credentials or a fragment" };
371229
+ return { config: { type: row.type === "sse" ? "sse" : "http", url: row.url, ...row.headers === undefined ? {} : { headers: row.headers } }, permissions: { network: [url.origin] }, reason: "MCP transport is adapted to Native; connection and authentication happen after installation" };
371230
+ }
371231
+ if (typeof row.command === "string" && /^[a-zA-Z0-9_-]+(?:\.exe)?$/.test(row.command)) {
371232
+ if (row.type !== undefined && row.type !== "stdio" || portable && row.type !== "stdio" || row.headers !== undefined)
371233
+ return { reason: "Unsupported stdio MCP options" };
371234
+ if (row.args !== undefined && (!Array.isArray(row.args) || !row.args.every((item) => typeof item === "string")))
371235
+ return { reason: "MCP args must be a string array" };
371236
+ if (row.env !== undefined && !stringMap(row.env))
371237
+ return { reason: "MCP env must contain strings" };
371238
+ const args = row.args ?? [];
371239
+ if (["npx", "npm", "pnpm", "yarn", "bunx", "uvx", "sh", "bash", "cmd", "powershell", "pwsh"].includes(row.command.toLowerCase().replace(/\.exe$/, "")) || args.some((arg) => /[\\/]|\.(?:[cm]?js|ts|py|sh|exe)$/.test(arg) || ["-e", "--eval", "-c", "--command"].includes(arg)))
371240
+ return { reason: "MCP local paths, shell code or dependency execution require explicit preparation not supported by this converter" };
371241
+ return { config: { type: "stdio", command: row.command, ...row.args === undefined ? {} : { args }, ...row.env === undefined ? {} : { env: row.env } }, permissions: { process: [row.command] }, reason: "MCP runs an already-installed executable from PATH; availability is checked when connecting" };
371242
+ }
371243
+ return { reason: "MCP requires a supported remote URL or an installed executable name" };
371244
+ }
371245
+
371246
+ // ../../packages/plugin-converters/src/codex/converter.ts
371247
+ var VERSION3 = "1.0.0";
371248
+ var MAPPING_VERSION = "1";
371249
+ async function exists7(path) {
371250
+ try {
371251
+ await lstat9(path);
371252
+ return true;
371253
+ } catch (error) {
371254
+ if (error.code === "ENOENT")
371255
+ return false;
371256
+ throw error;
371257
+ }
371258
+ }
371259
+ async function prepare(inspection) {
371260
+ const before = await digestSource(inspection.root);
371261
+ const source = await readCodexSource(inspection.root);
371262
+ if (digestValue(source.inspection) !== digestValue(inspection))
371263
+ throw new Error("Source inspection changed; inspect and plan again");
371264
+ const items = (inspection.inventory.skills ?? []).map((path) => {
371265
+ const sourcePath = relative14(inspection.root, path).replaceAll("\\", "/");
371266
+ return { id: `skills:${sourcePath}`, sourceKind: "skills", sourcePath, targetKind: "skills", fidelity: "exact" };
371267
+ });
371268
+ const servers = Object.create(null);
371269
+ const seenServers = new Set;
371270
+ const permissions = {};
371271
+ for (const contribution of source.mcp) {
371272
+ for (const [name, raw] of Object.entries(contribution.servers).sort(([a], [b]) => a.localeCompare(b))) {
371273
+ if (!name.trim() || ["__proto__", "prototype", "constructor"].includes(name) || seenServers.has(name))
371274
+ throw new Error(`Invalid or duplicate Codex MCP server name: ${name}`);
371275
+ seenServers.add(name);
371276
+ const mapped = mapCodexMcp(raw, contribution.portable);
371277
+ const id = `mcpServers:${contribution.path}#${name}`;
371278
+ const requiredApprovals = [id];
371279
+ if (mapped.config) {
371280
+ servers[name] = mapped.config;
371281
+ for (const category of ["network", "process"]) {
371282
+ for (const permission of mapped.permissions?.[category] ?? []) {
371283
+ permissions[category] = [...new Set([...permissions[category] ?? [], permission])].sort();
371284
+ requiredApprovals.push(`${category}:${permission}`);
371285
+ }
371286
+ }
371287
+ }
371288
+ items.push({
371289
+ id,
371290
+ sourceKind: "mcpServers",
371291
+ sourcePath: contribution.path,
371292
+ ...mapped.config ? { targetKind: "mcpServers" } : {},
371293
+ fidelity: mapped.config ? "adapted" : "unsupported",
371294
+ reason: mapped.reason,
371295
+ requiredApprovals
371296
+ });
371297
+ }
371298
+ }
371299
+ for (const value of source.unsupported) {
371300
+ const id = `${value.kind}:${value.path}`;
371301
+ items.push({ id, sourceKind: value.kind, sourcePath: value.path, fidelity: "unsupported", reason: value.reason, requiredApprovals: [id] });
371302
+ }
371303
+ const sourceDigest = await digestSource(inspection.root);
371304
+ if (before !== sourceDigest)
371305
+ throw new Error("Source changed during conversion planning; inspect and plan again");
371306
+ return {
371307
+ source,
371308
+ servers,
371309
+ permissions,
371310
+ plan: {
371311
+ schemaVersion: 1,
371312
+ converterId: "codex",
371313
+ converterVersion: VERSION3,
371314
+ sourceFormat: "codex",
371315
+ sourceDigest,
371316
+ optionsDigest: digestValue({}),
371317
+ mappingVersion: MAPPING_VERSION,
371318
+ items: items.sort((a, b) => a.id.localeCompare(b.id)),
371319
+ diagnostics: []
371320
+ }
371321
+ };
371322
+ }
371323
+ async function checkOutput(sourceRoot, output) {
371324
+ if (isWithin3(sourceRoot, output))
371325
+ throw new Error("Conversion output cannot be inside the source directory");
371326
+ if (await exists7(output))
371327
+ throw new Error(`Conversion output already exists: ${output}`);
371328
+ let parent = dirname24(output);
371329
+ while (!await exists7(parent))
371330
+ parent = dirname24(parent);
371331
+ const canonicalOutput = resolve30(await realpath8(parent), relative14(parent, output));
371332
+ if (isWithin3(await realpath8(sourceRoot), canonicalOutput))
371333
+ throw new Error("Conversion output resolves inside the source directory");
371334
+ }
371335
+ function isSourceMetadata(path, source) {
371336
+ const lower = path.toLowerCase();
371337
+ const top = lower.split("/")[0];
371338
+ if ([".git", ".codex-plugin", ".claude-plugin", ".openharness-plugin", ".openharness-conversion"].includes(top))
371339
+ return true;
371340
+ return source.metadataPaths.some((item) => item.toLowerCase() === lower);
371341
+ }
371342
+
371343
+ class CodexPluginConverter {
371344
+ id = "codex";
371345
+ version = VERSION3;
371346
+ sourceFormat = "codex";
371347
+ detect = detectCodexPlugin;
371348
+ async inspect(root) {
371349
+ return (await readCodexSource(root)).inspection;
371350
+ }
371351
+ async plan(inspection, options = {}) {
371352
+ if (Object.keys(options).length)
371353
+ throw new Error("Codex conversion options are not supported in this version");
371354
+ return (await prepare(inspection)).plan;
371355
+ }
371356
+ async convert(input) {
371357
+ const output = resolve30(input.output);
371358
+ await checkOutput(input.inspection.root, output);
371359
+ await readCodexSource(input.inspection.root);
371360
+ if (await digestSource(input.inspection.root) !== input.plan.sourceDigest)
371361
+ throw new Error("Source changed after conversion planning; inspect and plan again");
371362
+ const prepared = await prepare(input.inspection);
371363
+ if (digestValue(prepared.plan) !== digestValue(input.plan))
371364
+ throw new Error("Conversion plan does not match the current source and converter; plan again");
371365
+ const missing = [...new Set(prepared.plan.items.flatMap((item) => item.requiredApprovals ?? []))].filter((item) => !input.approvals?.includes(item));
371366
+ if (missing.length)
371367
+ throw new Error(`Explicit conversion approval required: ${missing.join(", ")}`);
371368
+ const components = {};
371369
+ const skillFiles = prepared.plan.items.filter((item) => item.sourceKind === "skills" && item.fidelity === "exact");
371370
+ if (skillFiles.length)
371371
+ components.skills = skillFiles.map((item) => `./${item.sourcePath}`);
371372
+ if (Object.keys(prepared.servers).length)
371373
+ components.mcpServers = ["./mcp/servers.json"];
371374
+ if (!Object.keys(components).length)
371375
+ throw new Error("Codex source contains no convertible components; see unsupported plan items");
371376
+ const manifest = {
371377
+ schemaVersion: 1,
371378
+ ...input.inspection.identity,
371379
+ ...prepared.source.description === undefined ? {} : { description: prepared.source.description },
371380
+ ...prepared.source.displayName === undefined ? {} : { displayName: prepared.source.displayName },
371381
+ components,
371382
+ ...Object.keys(prepared.permissions).length ? { permissions: prepared.permissions } : {},
371383
+ metadata: { origin: "converted", sourceFormat: this.sourceFormat, converterId: this.id, converterVersion: this.version }
371384
+ };
371385
+ const report = { schemaVersion: 1, status: input.plan.items.some((item) => item.fidelity === "unsupported") ? "partial" : "success", items: input.plan.items, diagnostics: input.plan.diagnostics };
371386
+ const temporary = `${output}.tmp-${randomUUID28()}`;
371387
+ const copied = [];
371388
+ try {
371389
+ await mkdir15(temporary, { recursive: true });
371390
+ for (const path of prepared.source.files) {
371391
+ if (isSourceMetadata(path, prepared.source))
371392
+ continue;
371393
+ if (components.mcpServers && path.toLowerCase() === "mcp/servers.json")
371394
+ throw new Error("Source resource conflicts with generated Native MCP path");
371395
+ const sourcePath = join48(input.inspection.root, path);
371396
+ if (!(await lstat9(sourcePath)).isFile())
371397
+ throw new Error(`Source file changed during conversion: ${path}`);
371398
+ const target = join48(temporary, path);
371399
+ await mkdir15(dirname24(target), { recursive: true });
371400
+ await copyFile2(sourcePath, target);
371401
+ copied.push(path);
371402
+ }
371403
+ const writeJson = async (path, value) => {
371404
+ const target = join48(temporary, path);
371405
+ await mkdir15(dirname24(target), { recursive: true });
371406
+ await writeFile12(target, JSON.stringify(value, null, 2));
371407
+ };
371408
+ if (components.mcpServers)
371409
+ await writeJson("mcp/servers.json", { servers: prepared.servers });
371410
+ await writeJson(".openharness-plugin/plugin.json", manifest);
371411
+ await writeJson(".openharness-conversion/plan.json", input.plan);
371412
+ await writeJson(".openharness-conversion/report.json", report);
371413
+ await writeJson(".openharness-conversion/provenance.json", { sourceFormat: this.sourceFormat, sourceDigest: input.plan.sourceDigest, converterId: this.id, converterVersion: this.version, mappingVersion: MAPPING_VERSION });
371414
+ await readCodexSource(input.inspection.root);
371415
+ if (await digestSource(input.inspection.root) !== input.plan.sourceDigest)
371416
+ throw new Error("Source changed during conversion; inspect and plan again");
371417
+ for (const path of copied) {
371418
+ if (!(await readFile23(join48(temporary, path))).equals(await readFile23(join48(input.inspection.root, path))))
371419
+ throw new Error(`Source changed while copying: ${path}`);
371420
+ }
371421
+ const validation = await validateNativePlugin(temporary);
371422
+ if (validation.status !== "valid")
371423
+ throw new Error(validation.diagnostics.map((item) => item.message).join("; "));
371424
+ if (digestValue(requestedPluginPermissions(validation.plugin.manifest)) !== digestValue(requestedPluginPermissions(manifest)))
371425
+ throw new Error("Converted permissions changed during validation");
371426
+ await checkOutput(input.inspection.root, output);
371427
+ await rename5(temporary, output);
371428
+ return report;
371429
+ } catch (error) {
371430
+ await rm8(temporary, { recursive: true, force: true });
371431
+ throw error;
371432
+ }
371433
+ }
371434
+ }
370775
371435
  // ../../packages/plugin-converters/src/index.ts
370776
371436
  function createBuiltinConverterRegistry() {
370777
371437
  const registry = new ConverterRegistry;
370778
371438
  registry.register(new ClaudeCodePluginConverter);
371439
+ registry.register(new CodexPluginConverter);
370779
371440
  return registry;
370780
371441
  }
370781
371442
 
@@ -370787,6 +371448,15 @@ async function client() {
370787
371448
  return new OpenHarnessClient({ baseUrl: daemon.url, token: daemon.token });
370788
371449
  }
370789
371450
  var collect2 = (value, previous) => [...previous, value];
371451
+ function formatConversionPlan(plan) {
371452
+ return plan.items.map((item) => [
371453
+ `${item.fidelity.padEnd(11)} ${item.id}`,
371454
+ ...item.reason ? [` ${item.reason}`] : [],
371455
+ ...(item.requiredApprovals ?? []).map((approval) => ` --approve ${JSON.stringify(approval)}`)
371456
+ ].join(`
371457
+ `)).join(`
371458
+ `);
371459
+ }
370790
371460
  var activationLabels = {
370791
371461
  inactive: "inactive",
370792
371462
  active: "active",
@@ -370856,11 +371526,11 @@ function formatPluginList(result, verbose = false) {
370856
371526
  function createPluginCommand() {
370857
371527
  const cmd = new Command3("plugin").description("Manage Native Plugins");
370858
371528
  cmd.command("list").option("--cwd <path>").option("--verbose", "show permissions, contributions, runtime, and diagnostics").option("--json", "print the complete machine-readable response").action(async (options) => {
370859
- const result = await (await client()).listPlugins({ cwd: resolve28(options.cwd ?? process.cwd()) });
371529
+ const result = await (await client()).listPlugins({ cwd: resolve31(options.cwd ?? process.cwd()) });
370860
371530
  console.log(options.json ? JSON.stringify(result, null, 2) : formatPluginList(result, options.verbose));
370861
371531
  });
370862
371532
  cmd.command("validate").argument("<path>").action(async (path) => {
370863
- const result = await validateNativePlugin(resolve28(path));
371533
+ const result = await validateNativePlugin(resolve31(path));
370864
371534
  if (result.status === "valid")
370865
371535
  console.log(`Valid Native Plugin: ${result.plugin.manifest.id}@${result.plugin.manifest.version}`);
370866
371536
  else {
@@ -370871,7 +371541,7 @@ function createPluginCommand() {
370871
371541
  });
370872
371542
  for (const [name, link] of [["install-local", false], ["link", true]]) {
370873
371543
  cmd.command(name).argument("<path>").option("--cwd <path>").option("--approve <permission>", "approve a requested permission", collect2, []).action(async (path, options) => {
370874
- const sourcePath = resolve28(path);
371544
+ const sourcePath = resolve31(path);
370875
371545
  const validation = await validateNativePlugin(sourcePath);
370876
371546
  if (!validation.plugin)
370877
371547
  throw new Error(validation.diagnostics.map((item) => item.message).join("; "));
@@ -370881,7 +371551,7 @@ function createPluginCommand() {
370881
371551
  if (missing.length)
370882
371552
  throw new Error(`Explicit approval required: ${missing.join(", ")}`);
370883
371553
  const result = await (await client()).installLocalPlugin({
370884
- cwd: resolve28(options.cwd ?? process.cwd()),
371554
+ cwd: resolve31(options.cwd ?? process.cwd()),
370885
371555
  sourcePath,
370886
371556
  scope: "user",
370887
371557
  approvedPermissions: approved,
@@ -370893,25 +371563,25 @@ function createPluginCommand() {
370893
371563
  for (const action of ["enable", "disable"])
370894
371564
  cmd.command(action).argument("<id>").option("--cwd <path>").action(async (id, options) => {
370895
371565
  const api = await client();
370896
- const input = { cwd: resolve28(options.cwd ?? process.cwd()) };
371566
+ const input = { cwd: resolve31(options.cwd ?? process.cwd()) };
370897
371567
  console.log((action === "enable" ? await api.enablePlugin(id, input) : await api.disablePlugin(id, input)).message);
370898
371568
  });
370899
371569
  cmd.command("uninstall").argument("<id>").option("--cwd <path>").action(async (id, options) => {
370900
- console.log((await (await client()).uninstallPlugin(id, { cwd: resolve28(options.cwd ?? process.cwd()) })).message);
371570
+ console.log((await (await client()).uninstallPlugin(id, { cwd: resolve31(options.cwd ?? process.cwd()) })).message);
370901
371571
  });
370902
371572
  cmd.command("details").argument("<id>").option("--cwd <path>").action(async (id, options) => {
370903
- const listed = await (await client()).listPlugins({ cwd: resolve28(options.cwd ?? process.cwd()) });
371573
+ const listed = await (await client()).listPlugins({ cwd: resolve31(options.cwd ?? process.cwd()) });
370904
371574
  const plugin = listed.plugins.find((item) => item.identity.id === id);
370905
371575
  if (!plugin)
370906
371576
  throw new Error(`Plugin not found: ${id}`);
370907
371577
  console.log(JSON.stringify(plugin, null, 2));
370908
371578
  });
370909
- cmd.command("install").argument("<source>").requiredOption("--from <converter>").option("--cwd <path>").option("--approve <item>", "approve conversion item or permission", collect2, []).action(async (source, options) => {
370910
- const temporaryRoot = await mkdtemp3(join46(tmpdir5(), "ohs-plugin-import-"));
370911
- const output = join46(temporaryRoot, "native");
371579
+ cmd.command("install").argument("<source>").requiredOption("--from <converter>", "source converter: claude-code or codex").option("--cwd <path>").option("--approve <item>", "approve conversion item or permission", collect2, []).action(async (source, options) => {
371580
+ const temporaryRoot = await mkdtemp3(join49(tmpdir5(), "ohs-plugin-import-"));
371581
+ const output = join49(temporaryRoot, "native");
370912
371582
  try {
370913
- const { converter } = await createBuiltinConverterRegistry().detect(resolve28(source), options.from);
370914
- const inspection = await converter.inspect(resolve28(source));
371583
+ const { converter } = await createBuiltinConverterRegistry().detect(resolve31(source), options.from);
371584
+ const inspection = await converter.inspect(resolve31(source));
370915
371585
  const plan = await converter.plan(inspection, {});
370916
371586
  await converter.convert({ inspection, plan, output, approvals: options.approve });
370917
371587
  const validation = await validateNativePlugin(output);
@@ -370922,29 +371592,28 @@ function createPluginCommand() {
370922
371592
  if (missing.length)
370923
371593
  throw new Error(`Explicit permission approval required: ${missing.join(", ")}`);
370924
371594
  const result = await (await client()).installLocalPlugin({
370925
- cwd: resolve28(options.cwd ?? process.cwd()),
371595
+ cwd: resolve31(options.cwd ?? process.cwd()),
370926
371596
  sourcePath: output,
370927
371597
  scope: "user",
370928
371598
  approvedPermissions: requested
370929
371599
  });
370930
371600
  console.log(result.message);
370931
371601
  } finally {
370932
- await rm8(temporaryRoot, { recursive: true, force: true });
371602
+ await rm9(temporaryRoot, { recursive: true, force: true });
370933
371603
  }
370934
371604
  });
370935
- cmd.command("convert").argument("<source>").option("--from <converter>").option("--output <path>").option("--dry-run").option("--json").option("--approve <item>", "approve a blocked conversion item", collect2, []).action(async (source, options) => {
371605
+ cmd.command("convert").argument("<source>").option("--from <converter>", "source converter: claude-code or codex").option("--output <path>").option("--dry-run").option("--json").option("--approve <item>", "approve a conversion change, omitted component, or permission shown in the plan", collect2, []).action(async (source, options) => {
370936
371606
  const registry = createBuiltinConverterRegistry();
370937
- const { converter, detection } = await registry.detect(resolve28(source), options.from);
370938
- const inspection = await converter.inspect(resolve28(source));
371607
+ const { converter, detection } = await registry.detect(resolve31(source), options.from);
371608
+ const inspection = await converter.inspect(resolve31(source));
370939
371609
  const plan = await converter.plan(inspection, {});
370940
371610
  if (options.dryRun) {
370941
- console.log(options.json ? JSON.stringify({ detection, inspection, plan }, null, 2) : plan.items.map((item) => `${item.fidelity.padEnd(11)} ${item.id}`).join(`
370942
- `));
371611
+ console.log(options.json ? JSON.stringify({ detection, inspection, plan }, null, 2) : formatConversionPlan(plan));
370943
371612
  return;
370944
371613
  }
370945
371614
  if (!options.output)
370946
371615
  throw new Error("--output is required unless --dry-run is used");
370947
- const report = await converter.convert({ inspection, plan, output: resolve28(options.output), approvals: options.approve });
371616
+ const report = await converter.convert({ inspection, plan, output: resolve31(options.output), approvals: options.approve });
370948
371617
  console.log(options.json ? JSON.stringify(report, null, 2) : `Converted ${inspection.identity.id}: ${report.status}`);
370949
371618
  });
370950
371619
  return cmd;
@@ -371461,7 +372130,7 @@ function createSandboxCommand() {
371461
372130
  init_src3();
371462
372131
  init_src9();
371463
372132
  import { readFileSync as readFileSync15 } from "node:fs";
371464
- import { resolve as resolve29 } from "node:path";
372133
+ import { resolve as resolve33 } from "node:path";
371465
372134
  import { Command as Command8 } from "commander";
371466
372135
  function createWorkflowCommand() {
371467
372136
  const cmd = new Command8("workflow").description("Inspect and manage persisted workflow runs");
@@ -371618,7 +372287,7 @@ function readJsonOption(path, inlineJson) {
371618
372287
  if (inlineJson)
371619
372288
  return JSON.parse(inlineJson);
371620
372289
  if (path)
371621
- return JSON.parse(readFileSync15(resolve29(path), "utf-8"));
372290
+ return JSON.parse(readFileSync15(resolve33(path), "utf-8"));
371622
372291
  return;
371623
372292
  }
371624
372293
  function parseCsv(value) {
@@ -372215,4 +372884,4 @@ program.command("doctor").description("Check environment and dependencies").acti
372215
372884
  });
372216
372885
  program.parse();
372217
372886
 
372218
- //# debugId=2D32AB0B747CF29E64756E2164756E21
372887
+ //# debugId=037007773B000ECC64756E2164756E21