@kyun.si/agent2cursor 0.0.0-stage → 0.1.1
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/CHANGELOG.md +12 -0
- package/CONTRIBUTING.md +92 -0
- package/LICENSE +21 -0
- package/README.md +230 -2
- package/README.zh-CN.md +231 -0
- package/THIRD_PARTY_NOTICES.md +11 -0
- package/demos/full-send/LICENSE-tstack +21 -0
- package/demos/full-send/README.md +47 -0
- package/demos/full-send/TSTACK-PROVENANCE.json +37 -0
- package/demos/full-send/bot/agent.ts +9 -0
- package/demos/full-send/bot/channels/github.ts +16 -0
- package/demos/full-send/bot/instructions.md +11 -0
- package/demos/full-send/bot/lib/merge-policy.ts +9 -0
- package/demos/full-send/bot/lib/settings.ts +2 -0
- package/demos/full-send/bot/skills/babysit-pr/SKILL.md +13 -0
- package/demos/full-send/bot/skills/babysit-pr/scripts/pr-wait +556 -0
- package/demos/full-send/bot/skills/file-pr/SKILL.md +11 -0
- package/demos/full-send/bot/skills/full-send/SKILL.md +19 -0
- package/demos/full-send/bot/skills/leaving-pr-comments/SKILL.md +14 -0
- package/demos/full-send/bot/skills/sol-loop/SKILL.md +11 -0
- package/demos/full-send/bot/subagents/reviewer/agent.ts +2 -0
- package/demos/full-send/bot/subagents/reviewer/instructions.md +3 -0
- package/demos/full-send/bot/tools/merge_pr.ts +23 -0
- package/demos/full-send/bot/tools/pr_wait.ts +24 -0
- package/demos/full-send/bot/tools/pr_wait_script.ts +9 -0
- package/demos/full-send/evals/evals.config.ts +2 -0
- package/demos/full-send/evals/full-send.eval.ts +5 -0
- package/demos/full-send/evals/skills.eval.ts +8 -0
- package/demos/full-send/package.json +18 -0
- package/demos/pr-review/LICENSE-tstack +21 -0
- package/demos/pr-review/README.md +44 -0
- package/demos/pr-review/TSTACK-PROVENANCE.json +37 -0
- package/demos/pr-review/bot/agent.ts +9 -0
- package/demos/pr-review/bot/channels/github.ts +22 -0
- package/demos/pr-review/bot/instructions.md +11 -0
- package/demos/pr-review/bot/lib/pr-snapshot.ts +92 -0
- package/demos/pr-review/bot/lib/settings.ts +2 -0
- package/demos/pr-review/bot/skills/council/SKILL.md +25 -0
- package/demos/pr-review/bot/skills/leaving-pr-comments/SKILL.md +14 -0
- package/demos/pr-review/bot/subagents/reviewer-a/agent.ts +2 -0
- package/demos/pr-review/bot/subagents/reviewer-a/instructions.md +3 -0
- package/demos/pr-review/bot/subagents/reviewer-b/agent.ts +2 -0
- package/demos/pr-review/bot/subagents/reviewer-b/instructions.md +3 -0
- package/demos/pr-review/bot/subagents/reviewer-c/agent.ts +2 -0
- package/demos/pr-review/bot/subagents/reviewer-c/instructions.md +3 -0
- package/demos/pr-review/bot/tools/pr_wait.ts +24 -0
- package/demos/pr-review/evals/evals.config.ts +2 -0
- package/demos/pr-review/evals/pr-review.eval.ts +13 -0
- package/demos/pr-review/evals/skills.eval.ts +5 -0
- package/demos/pr-review/package.json +18 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +142 -0
- package/dist/emit/common.d.ts +4 -0
- package/dist/emit/common.js +15 -0
- package/dist/emit/evals.d.ts +4 -0
- package/dist/emit/evals.js +33 -0
- package/dist/emit/project.d.ts +2 -0
- package/dist/emit/project.js +165 -0
- package/dist/emit/report.d.ts +2 -0
- package/dist/emit/report.js +74 -0
- package/dist/emit/subagent.d.ts +2 -0
- package/dist/emit/subagent.js +15 -0
- package/dist/emit/tool-stub.d.ts +3 -0
- package/dist/emit/tool-stub.js +29 -0
- package/dist/emit/tstack-license.d.ts +8 -0
- package/dist/emit/tstack-license.js +39 -0
- package/dist/ir.d.ts +66 -0
- package/dist/ir.js +1 -0
- package/dist/mapping/codex-agent.d.ts +2 -0
- package/dist/mapping/codex-agent.js +39 -0
- package/dist/mapping/codex-skill.d.ts +5 -0
- package/dist/mapping/codex-skill.js +32 -0
- package/dist/mapping/foreign-tools.d.ts +2 -0
- package/dist/mapping/foreign-tools.js +33 -0
- package/dist/mapping/model.d.ts +5 -0
- package/dist/mapping/model.js +14 -0
- package/dist/mapping/names.d.ts +2 -0
- package/dist/mapping/names.js +9 -0
- package/dist/mapping/permissions.d.ts +2 -0
- package/dist/mapping/permissions.js +21 -0
- package/dist/mapping/skill.d.ts +3 -0
- package/dist/mapping/skill.js +29 -0
- package/dist/mapping/subagent.d.ts +3 -0
- package/dist/mapping/subagent.js +60 -0
- package/dist/mapping/tools-names.d.ts +6 -0
- package/dist/mapping/tools-names.js +18 -0
- package/dist/scan/codex.d.ts +1 -0
- package/dist/scan/codex.js +19 -0
- package/dist/scan/frontmatter.d.ts +11 -0
- package/dist/scan/frontmatter.js +62 -0
- package/dist/scan/index.d.ts +2 -0
- package/dist/scan/index.js +192 -0
- package/dist/scan/safety.d.ts +11 -0
- package/dist/scan/safety.js +105 -0
- package/dist/scan/tstack.d.ts +9 -0
- package/dist/scan/tstack.js +42 -0
- package/dist/validate.d.ts +5 -0
- package/dist/validate.js +135 -0
- package/dist/write.d.ts +19 -0
- package/dist/write.js +134 -0
- package/docs/acceptance.md +102 -0
- package/docs/assets/agent2cursor-demo-poster.png +0 -0
- package/docs/assets/agent2cursor-demo.gif +0 -0
- package/docs/evidence/codex-adapter.json +133 -0
- package/docs/evidence/evaluation-fixes.json +184 -0
- package/docs/evidence/independent-evaluation.json +61 -0
- package/docs/evidence/npm-publication-0.1.0.json +46 -0
- package/docs/evidence/online-2026-10-27.json +112 -0
- package/docs/evidence/readme-audit-2026-10-28.json +248 -0
- package/docs/official-examples.provenance.json +26 -0
- package/docs/upstream-verification.md +67 -0
- package/evaluation/20261027-2216//345/244/215/346/265/213/347/273/223/350/256/272.md +48 -0
- package/evaluation/README.md +16 -0
- package/package.json +30 -4
- package/templates/en/instructions.md +9 -0
- package/templates/zh/instructions.md +9 -0
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
const REPLACEMENTS = {
|
|
2
|
+
WebFetch: ["没有已核实的 SDK 抓取工具映射;请提供页面内容或人工实现受控抓取工具。此处仅报告名称引用,不改写正文。", "No verified SDK fetch-tool mapping; provide page content or implement a controlled fetch tool manually. This reports a name reference without rewriting the body."],
|
|
3
|
+
WebSearch: ["没有已核实的 SDK 搜索工具映射;请提供检索资料或人工配置搜索工具,并复核访问权限。", "No verified SDK search-tool mapping; provide search material or configure a search tool manually and review access permissions."],
|
|
4
|
+
AskUserQuestion: ["没有已验证的直接替代;在最终回复开头提问,等待人工确认。", "No verified direct equivalent; ask at the start of the final reply and await human confirmation."],
|
|
5
|
+
request_user_input: ["没有已验证的直接替代;在最终回复开头提问,等待人工确认。", "No verified direct equivalent; ask at the start of the final reply and await human confirmation."],
|
|
6
|
+
request_user_input_async: ["没有已验证的异步交互替代;改为明确提问并等待人工回复,不能按超时视作批准。", "No verified asynchronous input equivalent; ask explicitly and await the user's response. A timeout is not approval."],
|
|
7
|
+
spawn_agent: ["改为 task 和实际声明的 SDK 子代理;Codex 的 fork、模型覆盖与并发语义需人工适配。", "Use task with declared SDK subagents; adapt Codex fork, model overrides, and concurrency semantics manually."],
|
|
8
|
+
send_input: ["Codex 子代理续传消息没有已核实的直接映射;按 SDK task 的生命周期重写。", "No verified direct mapping for follow-up messages to Codex subagents; rewrite around the SDK task lifecycle."],
|
|
9
|
+
close_agent: ["Codex 子代理关闭没有已核实的直接映射;按 SDK task 的生命周期重写。", "No verified direct mapping for closing Codex subagents; rewrite around the SDK task lifecycle."],
|
|
10
|
+
resume_agent: ["Codex 子代理恢复没有已核实的直接映射;按 SDK task 的生命周期重写。", "No verified direct mapping for resuming Codex subagents; rewrite around the SDK task lifecycle."],
|
|
11
|
+
wait_agent: ["改为 SDK task 结果收集;后台等待和超时语义需人工适配。", "Collect SDK task results; adapt background waiting and timeout semantics manually."],
|
|
12
|
+
wait: ["Codex wait 调用需按 SDK task 的结果收集方式重写;等待和超时语义不能直接透传。", "Rewrite Codex wait calls around SDK task result collection; waiting and timeout semantics are not directly portable."],
|
|
13
|
+
list_agents: ["请用生成项目中实际声明的子代理清单;没有已核实的 Codex 活跃代理列表接口映射。", "Use the subagents declared in the generated project; no verified mapping exists for the Codex live-agent inventory."],
|
|
14
|
+
send_message: ["Codex 代理间消息没有已核实的直接映射;按 SDK task 的生命周期重写。", "No verified direct mapping for Codex inter-agent messages; rewrite around the SDK task lifecycle."],
|
|
15
|
+
followup_task: ["Codex 子代理后续任务没有已核实的直接映射;按 SDK task 的生命周期重写。", "No verified direct mapping for Codex follow-up tasks; rewrite around the SDK task lifecycle."],
|
|
16
|
+
interrupt_agent: ["Codex 子代理中断没有已核实的直接映射;按 SDK task 的生命周期重写。", "No verified direct mapping for interrupting Codex subagents; rewrite around the SDK task lifecycle."],
|
|
17
|
+
exec_command: ["需按 SDK shell 工具重写;会话 ID、PTY、超时和审批参数不能原样迁移,shell 可以写入。", "Rewrite for the SDK shell tool; session IDs, PTYs, timeouts, and approval parameters are not portable. Shell can write."],
|
|
18
|
+
write_stdin: ["Codex 终端会话续写没有已核实的直接映射;按目标 shell 会话接口重写。", "No verified direct mapping for Codex terminal session input; adapt to the target shell-session interface."],
|
|
19
|
+
apply_patch: ["需按 SDK edit 工具重写;补丁格式、路径约束及审批语义需重新验证。", "Rewrite for the SDK edit tool; revalidate patch format, path constraints, and approval semantics."],
|
|
20
|
+
update_plan: ["没有已核实的计划工具映射;可用普通进度文本表达,不能直接调用此工具名。", "No verified planning-tool equivalent; use ordinary progress text rather than calling this tool name."],
|
|
21
|
+
delegate_task: ["改为 task 委派,并声明实际存在的子代理;委派并发等语义需人工复核。", "Use task delegation with declared subagents; review concurrency and delegation semantics manually."],
|
|
22
|
+
orchestrator_capabilities: ["删除运行时能力探测依赖,明确声明可用子代理及能力。", "Replace runtime capability discovery with explicit available subagents and capabilities."],
|
|
23
|
+
watch_pull_request: ["可改为经人工启用的 pr_wait 工具;轮询不是事件订阅的等价实现。", "Consider an operator-enabled pr_wait tool; polling is not equivalent to event subscription."],
|
|
24
|
+
unwatch_pull_request: ["移除订阅取消逻辑;根据新轮询或 GitHub channel 的生命周期重写。", "Rewrite subscription cancellation for the polling or GitHub channel lifecycle."],
|
|
25
|
+
Monitor: ["需要显式实现监控流程或 GitHub channel;不能直接调用该工具名。", "Implement monitoring or a GitHub channel explicitly; this tool name is not directly available."],
|
|
26
|
+
};
|
|
27
|
+
export function scanForeignTools(body, source) {
|
|
28
|
+
return Object.entries(REPLACEMENTS).flatMap(([tool, [message, messageEn]]) => (tool === "wait"
|
|
29
|
+
// "wait" is also ordinary prose; require an explicit tool-like reference.
|
|
30
|
+
? /(?:\bfunctions\.wait\b|(?<![A-Za-z0-9_])wait\s*\(|`wait`)/.test(body)
|
|
31
|
+
: new RegExp(`(?<![A-Za-z0-9_])${tool}(?![A-Za-z0-9_])`).test(body))
|
|
32
|
+
? [{ source, field: `$prompt.${tool}`, status: "degraded", message, messageEn }] : []);
|
|
33
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function mapModel(value, source, options) {
|
|
2
|
+
const base = { source, field: "model" };
|
|
3
|
+
if (value === "inherit")
|
|
4
|
+
return { finding: { ...base, status: "mapped", message: "已省略 model,继承父代理模型。", messageEn: "Omitted model to inherit the parent model." } };
|
|
5
|
+
if (typeof value === "string" && Object.hasOwn(options.modelMap, value) && options.modelMap[value])
|
|
6
|
+
return { model: options.modelMap[value], finding: {
|
|
7
|
+
...base, status: "mapped", target: "agent.ts:model", message: "已按 --model-map 显式映射;可用性需用实际账号验证。", messageEn: "Explicitly mapped with --model-map; verify availability with your account.",
|
|
8
|
+
} };
|
|
9
|
+
if (typeof value === "string" && value.trim() && options.keepModel)
|
|
10
|
+
return { model: value, finding: {
|
|
11
|
+
...base, status: "degraded", target: "agent.ts:model", message: "按 --keep-model 保留原 ID;validate 不能证明该模型可调用。", messageEn: "Kept the original ID with --keep-model; validate does not prove model availability.",
|
|
12
|
+
} };
|
|
13
|
+
return { finding: { ...base, status: "degraded", message: "已省略 model,继承父代理模型;使用 --model-map 显式指定目标模型。", messageEn: "Omitted model to inherit the parent model; use --model-map to select a target model explicitly." } };
|
|
14
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export function slugName(value, fallback) {
|
|
2
|
+
const slug = value.normalize("NFKD").replace(/[\u0300-\u036f]/g, "").replace(/[^A-Za-z0-9_-]+/g, "-")
|
|
3
|
+
.replace(/^[^A-Za-z0-9]+|[-_]+$/g, "").slice(0, 96).replace(/[-_]+$/g, "");
|
|
4
|
+
return slug || fallback;
|
|
5
|
+
}
|
|
6
|
+
export function scriptName(relativePath) {
|
|
7
|
+
const withoutExtension = relativePath.replace(/\.[^.\/]+$/, "");
|
|
8
|
+
return slugName(withoutExtension.replace(/[/\\-]+/g, "_"), "script").replace(/-/g, "_");
|
|
9
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export function permissionWarning(ir, lang) {
|
|
2
|
+
const affected = ir.agents.filter(agent => ["tools", "disallowedTools", "permissionMode"]
|
|
3
|
+
.some(field => Object.hasOwn(agent.frontmatter, field)));
|
|
4
|
+
const warnings = [];
|
|
5
|
+
if (affected.length)
|
|
6
|
+
warnings.push(lang === "zh"
|
|
7
|
+
? `⚠️ 子代理限制未生效(${affected.length} 个):源 tools / disallowedTools / permissionMode 未迁移。SDK 校验通过不代表只读;--root-tools 只设置根代理白名单。`
|
|
8
|
+
: `⚠️ Source subagent restrictions are not enforced (${affected.length} affected): tools / disallowedTools / permissionMode were not migrated. SDK validation does not establish read-only access; --root-tools sets only the root allowlist.`);
|
|
9
|
+
const codex = ir.agents.filter(agent => agent.sourceFormat === "codex" && ["features", "skills", "sandbox_mode", "approval_policy", "sandbox_workspace_write", "permissions"]
|
|
10
|
+
.some(field => Object.hasOwn(agent.frontmatter, field)));
|
|
11
|
+
if (codex.length)
|
|
12
|
+
warnings.push(lang === "zh"
|
|
13
|
+
? `⚠️ Codex 权限/能力配置未迁移(${codex.length} 个):features / skills 限制未生效,父会话权限未导入。SDK 校验通过不代表只读;--root-tools 只设置根代理白名单。`
|
|
14
|
+
: `⚠️ Codex permission/capability configuration was not migrated (${codex.length} affected): features / skills restrictions are not enforced and parent permissions are not imported. SDK validation does not establish read-only access; --root-tools sets only the root allowlist.`);
|
|
15
|
+
const policies = new Set(ir.findings.filter(finding => finding.field === "policy" && /[\\/]agents[\\/]openai\.yaml$/.test(finding.source)).map(finding => finding.source));
|
|
16
|
+
if (policies.size)
|
|
17
|
+
warnings.push(lang === "zh"
|
|
18
|
+
? `⚠️ Codex skill 调用策略未生效(${policies.size} 个):openai.yaml 的 policy 仅保留为元数据;请人工限制触发流程。`
|
|
19
|
+
: `⚠️ Codex skill invocation policy is not enforced (${policies.size} affected): openai.yaml policy is retained as metadata only; constrain routing manually.`);
|
|
20
|
+
return warnings.length ? warnings.join("\n") : undefined;
|
|
21
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fieldPaths } from "../scan/frontmatter.js";
|
|
3
|
+
import { scanForeignTools } from "./foreign-tools.js";
|
|
4
|
+
import { slugName } from "./names.js";
|
|
5
|
+
export function mapSkill(source, parsed, options, dependency) {
|
|
6
|
+
const { body, frontmatter } = parsed;
|
|
7
|
+
const directory = path.dirname(source);
|
|
8
|
+
const name = typeof frontmatter.name === "string" ? frontmatter.name : path.basename(directory);
|
|
9
|
+
const id = slugName(name, "skill");
|
|
10
|
+
const hasDescription = typeof frontmatter.description === "string" && !!frontmatter.description.trim();
|
|
11
|
+
const description = hasDescription ? frontmatter.description.trim()
|
|
12
|
+
: options.lang === "zh" ? `当用户要求使用 ${id} 时加载;请补充技能用途。` : `Load when the user requests ${id}; add the skill purpose.`;
|
|
13
|
+
const findings = fieldPaths(frontmatter).map((field) => {
|
|
14
|
+
const root = field.split(".")[0];
|
|
15
|
+
if (root === "name")
|
|
16
|
+
return { source, field, status: id === frontmatter.name ? "mapped" : "degraded", target: `bot/skills/${id}/SKILL.md`, message: "已统一 skill 目录与 name。", messageEn: "Normalized the skill directory and name." };
|
|
17
|
+
if (root === "description")
|
|
18
|
+
return { source, field, status: hasDescription ? "mapped" : "degraded", target: `bot/skills/${id}/SKILL.md`, message: hasDescription ? "已保留技能描述。" : "无效描述已替换为占位描述;请补充触发条件。", messageEn: hasDescription ? "Skill description preserved." : "Invalid description replaced with a placeholder; add triggers." };
|
|
19
|
+
if (root === "allowed-tools")
|
|
20
|
+
return { source, field, status: "degraded", message: "原字段保留,但不生成运行时权限限制;请人工审查根代理 tools。", messageEn: "Field preserved but no runtime permission restriction generated; review root tools manually." };
|
|
21
|
+
return { source, field, status: "mapped", target: `bot/skills/${id}/SKILL.md`, message: "原字段保留在 SKILL.md;仅保证内容保留,不保证 SDK 读取其语义。", messageEn: "Preserved in SKILL.md; content preservation does not imply SDK runtime support." };
|
|
22
|
+
});
|
|
23
|
+
if (!("name" in frontmatter))
|
|
24
|
+
findings.push({ source, field: "name", status: "degraded", message: "缺少 name,已从目录名生成。", messageEn: "Missing name; generated from the directory name." });
|
|
25
|
+
if (!("description" in frontmatter))
|
|
26
|
+
findings.push({ source, field: "description", status: "degraded", message: "缺少 description,已生成占位描述。", messageEn: "Missing description; generated a placeholder." });
|
|
27
|
+
findings.push({ source, field: "$body", status: "mapped", target: `bot/skills/${id}/SKILL.md`, message: "正文原样保留。", messageEn: "Body preserved unchanged." }, ...scanForeignTools(body, source));
|
|
28
|
+
return { id, source, directory, body, frontmatter: { ...frontmatter, name: id, description }, description, dependency, findings };
|
|
29
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import { fieldPaths } from "../scan/frontmatter.js";
|
|
3
|
+
import { scanForeignTools } from "./foreign-tools.js";
|
|
4
|
+
import { mapModel } from "./model.js";
|
|
5
|
+
import { slugName } from "./names.js";
|
|
6
|
+
import { mapToolNames } from "./tools-names.js";
|
|
7
|
+
export function mapSubagent(source, parsed, options) {
|
|
8
|
+
const { frontmatter, body } = parsed;
|
|
9
|
+
const sourceName = typeof frontmatter.name === "string" ? frontmatter.name : path.basename(source, path.extname(source));
|
|
10
|
+
const id = slugName(sourceName, "agent");
|
|
11
|
+
const hasDescription = typeof frontmatter.description === "string" && !!frontmatter.description.trim();
|
|
12
|
+
const description = hasDescription ? frontmatter.description.trim()
|
|
13
|
+
: options.lang === "zh" ? `迁移的 ${id} 子代理;请补充具体用途和委派时机。` : `Migrated ${id} subagent; describe its purpose and when to delegate.`;
|
|
14
|
+
const findings = [];
|
|
15
|
+
let model;
|
|
16
|
+
const topRules = new Map();
|
|
17
|
+
for (const field of Object.keys(frontmatter)) {
|
|
18
|
+
let finding = { source, field, status: "dropped", message: "目标 SDK 无已核实的等价字段;未写入配置,请人工实现其语义。", messageEn: "No verified SDK equivalent; omitted from configuration. Implement its semantics manually." };
|
|
19
|
+
if (field === "name")
|
|
20
|
+
finding = { source, field, status: id === frontmatter.name ? "mapped" : "degraded", target: `bot/subagents/${id}/`, message: "用于子代理目录 ID;不合法字符已规范化。", messageEn: "Used as the subagent directory ID; unsupported characters are normalized." };
|
|
21
|
+
if (field === "description")
|
|
22
|
+
finding = { source, field, status: hasDescription ? "mapped" : "degraded", target: "agent.ts:description", message: hasDescription ? "已映射为委派描述。" : "原描述无效,已生成占位描述;请补充触发条件。", messageEn: hasDescription ? "Mapped to the delegation description." : "Invalid description replaced with a placeholder; add delegation triggers." };
|
|
23
|
+
if (field === "model") {
|
|
24
|
+
const mapped = mapModel(frontmatter.model, source, options);
|
|
25
|
+
model = mapped.model;
|
|
26
|
+
finding = mapped.finding;
|
|
27
|
+
}
|
|
28
|
+
if (field === "tools" || field === "disallowedTools") {
|
|
29
|
+
const tools = mapToolNames(frontmatter[field]);
|
|
30
|
+
const suggestions = tools.mapped.length ? ` (${tools.mapped.join(", ")})` : "";
|
|
31
|
+
finding = { source, field, status: "degraded", message: `危险:子代理工具限制未生效,task 子代理保留 harness 自己的工具集;不能假定根白名单会约束子代理。工具名参考映射${suggestions};若必须强制只读,需使用独立代理或禁用 task,并另行约束执行环境;shell 可写入。`, messageEn: `Security boundary lost: task children retain the harness task-tool toolset; do not assume root allowlists constrain them. Reference name mapping${suggestions}. For enforced read-only access use separate agents or disable task and constrain execution separately; shell can write.` };
|
|
32
|
+
if (tools.unknown.length)
|
|
33
|
+
finding.message += " 部分工具名无已核实映射。";
|
|
34
|
+
if (tools.unknown.length)
|
|
35
|
+
finding.messageEn += " Some tool names have no verified mapping.";
|
|
36
|
+
}
|
|
37
|
+
if (field === "permissionMode")
|
|
38
|
+
finding = { source, field, status: "dropped", message: "Claude 权限模式未迁移。local.sandbox 和 tool 审批不等价,必须人工设置权限。", messageEn: "Claude permission mode was not migrated. local.sandbox and tool approval are not equivalents; configure permissions manually." };
|
|
39
|
+
if (field === "hooks")
|
|
40
|
+
finding = { source, field, status: "dropped", message: "未迁移 hooks;SDK 观察型 hook 不能复现 Claude 的拦截语义。", messageEn: "Hooks were not migrated; SDK observer hooks cannot reproduce Claude interception semantics." };
|
|
41
|
+
if (field === "mcpServers")
|
|
42
|
+
finding = { source, field, status: "dropped", message: "未创建 MCP connection;请在 bot/mcp-connections/ 人工配置并验证认证。", messageEn: "No MCP connection generated; configure bot/mcp-connections/ and verify authentication manually." };
|
|
43
|
+
if (field === "skills")
|
|
44
|
+
finding = { source, field, status: "degraded", message: "子代理不能单独预加载 skill;请一并转换依赖 skill,并明确提示根代理加载。", messageEn: "Per-subagent skill preloading is unavailable; convert dependencies and explicitly instruct the root agent to load them." };
|
|
45
|
+
if (field === "memory")
|
|
46
|
+
finding = { source, field, status: "dropped", message: "持久记忆未迁移;ctx.host.kv 需要重新设计数据和生命周期。", messageEn: "Persistent memory was not migrated; ctx.host.kv requires explicit data and lifecycle design." };
|
|
47
|
+
topRules.set(field, finding);
|
|
48
|
+
}
|
|
49
|
+
for (const field of fieldPaths(frontmatter)) {
|
|
50
|
+
const root = [...topRules.keys()].sort((a, b) => b.length - a.length).find((key) => field === key || field.startsWith(`${key}.`));
|
|
51
|
+
const rule = topRules.get(root);
|
|
52
|
+
findings.push({ ...rule, field });
|
|
53
|
+
}
|
|
54
|
+
if (!("name" in frontmatter))
|
|
55
|
+
findings.push({ source, field: "name", status: "degraded", target: `bot/subagents/${id}/`, message: "缺少 name,已从文件名生成 ID。", messageEn: "Missing name; generated the ID from the filename." });
|
|
56
|
+
if (!("description" in frontmatter))
|
|
57
|
+
findings.push({ source, field: "description", status: "degraded", target: "agent.ts:description", message: "缺少 description,已生成占位描述;请补充委派时机。", messageEn: "Missing description; generated a placeholder. Add delegation triggers." });
|
|
58
|
+
findings.push({ source, field: "$body", status: "mapped", target: `bot/subagents/${id}/instructions.md`, message: "正文原样迁移;工具和运行时引用需按下列提示复核。", messageEn: "Body preserved; review tool and runtime references below." }, ...scanForeignTools(body, source));
|
|
59
|
+
return { id, source, sourceFormat: "claude", body, frontmatter, description, model, findings };
|
|
60
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export const CLAUDE_TOOL_MAP = {
|
|
2
|
+
Read: "read", Grep: "grep", Glob: "glob", Bash: "shell", Edit: "edit", Write: "edit", Agent: "task", Task: "task",
|
|
3
|
+
};
|
|
4
|
+
export const ROOT_TOOL_NAMES = new Set(["read", "grep", "glob", "ls", "shell", "edit", "delete", "task"]);
|
|
5
|
+
export function mapToolNames(value) {
|
|
6
|
+
const names = typeof value === "string" ? value.split(/[,\s]+/).filter(Boolean)
|
|
7
|
+
: Array.isArray(value) ? value.filter((name) => typeof name === "string") : [];
|
|
8
|
+
const mapped = new Set();
|
|
9
|
+
const unknown = new Set();
|
|
10
|
+
for (const name of names) {
|
|
11
|
+
const target = Object.hasOwn(CLAUDE_TOOL_MAP, name) ? CLAUDE_TOOL_MAP[name] : ROOT_TOOL_NAMES.has(name) ? name : undefined;
|
|
12
|
+
if (target)
|
|
13
|
+
mapped.add(target);
|
|
14
|
+
else
|
|
15
|
+
unknown.add(name);
|
|
16
|
+
}
|
|
17
|
+
return { mapped: [...mapped], unknown: [...unknown] };
|
|
18
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function parseCodexAgent(text: string, file: string): Record<string, unknown>;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { parse } from "smol-toml";
|
|
2
|
+
import { assertNoSensitiveKeys } from "./frontmatter.js";
|
|
3
|
+
export function parseCodexAgent(text, file) {
|
|
4
|
+
let config;
|
|
5
|
+
try {
|
|
6
|
+
config = parse(text.replace(/^\uFEFF/, ""), { maxDepth: 30 });
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
// TOML parser diagnostics contain source excerpts. Never echo them.
|
|
10
|
+
throw new Error(`Invalid Codex agent TOML (or nesting exceeds 30 levels): ${file}`);
|
|
11
|
+
}
|
|
12
|
+
assertNoSensitiveKeys(config, file);
|
|
13
|
+
for (const field of ["name", "description", "developer_instructions"]) {
|
|
14
|
+
if (typeof config[field] !== "string" || !config[field].trim()) {
|
|
15
|
+
throw new Error(`Codex standalone agents require a non-empty string '${field}': ${file}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
return config;
|
|
19
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export interface ParsedMarkdown {
|
|
2
|
+
frontmatter: Record<string, unknown>;
|
|
3
|
+
body: string;
|
|
4
|
+
}
|
|
5
|
+
export declare function assertNoSensitiveKeys(value: unknown, file: string, prefix?: string, depth?: number, budget?: {
|
|
6
|
+
fields: number;
|
|
7
|
+
}): void;
|
|
8
|
+
export declare function parseYamlMapping(text: string, file: string): Record<string, unknown>;
|
|
9
|
+
export declare function parseMarkdown(text: string, file: string): ParsedMarkdown;
|
|
10
|
+
/** Includes container keys and nested object keys; arrays retain their index for object entries. */
|
|
11
|
+
export declare function fieldPaths(value: Record<string, unknown>): string[];
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { parseDocument } from "yaml";
|
|
2
|
+
export function assertNoSensitiveKeys(value, file, prefix = "", depth = 0, budget = { fields: 0 }) {
|
|
3
|
+
if (depth > 30)
|
|
4
|
+
throw new Error(`Frontmatter nesting exceeds 30 levels: ${file}`);
|
|
5
|
+
if (!value || typeof value !== "object")
|
|
6
|
+
return;
|
|
7
|
+
for (const [key, child] of Object.entries(value)) {
|
|
8
|
+
if (++budget.fields > 10_000)
|
|
9
|
+
throw new Error(`Frontmatter exceeds 10000 fields: ${file}`);
|
|
10
|
+
const field = prefix ? `${prefix}.${key}` : key;
|
|
11
|
+
if (/(?:^|[_-])(?:password|passwd|token|secret|credential|private[_-]?key|api[_-]?key)(?:$|[_-])/i.test(key)
|
|
12
|
+
|| /^(?:apiKey|accessKey|accessToken|refreshToken|authToken|clientSecret|privateKey|authorization|bearer)$/i.test(key)) {
|
|
13
|
+
throw new Error(`Sensitive frontmatter field is not permitted (${field}): ${file}`);
|
|
14
|
+
}
|
|
15
|
+
assertNoSensitiveKeys(child, file, field, depth + 1, budget);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
export function parseYamlMapping(text, file) {
|
|
19
|
+
const doc = parseDocument(text, { uniqueKeys: true, customTags: [], strict: true });
|
|
20
|
+
// YAML diagnostics may include source text, so never relay their raw messages.
|
|
21
|
+
if (doc.errors.length || doc.warnings.length)
|
|
22
|
+
throw new Error(`Invalid YAML frontmatter: ${file}`);
|
|
23
|
+
let value;
|
|
24
|
+
try {
|
|
25
|
+
value = doc.toJS({ maxAliasCount: 0 });
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
throw new Error(`YAML aliases are not supported: ${file}`);
|
|
29
|
+
}
|
|
30
|
+
if (value === null)
|
|
31
|
+
value = {};
|
|
32
|
+
if (typeof value !== "object" || Array.isArray(value))
|
|
33
|
+
throw new Error(`Frontmatter must be a mapping: ${file}`);
|
|
34
|
+
assertNoSensitiveKeys(value, file);
|
|
35
|
+
return value;
|
|
36
|
+
}
|
|
37
|
+
export function parseMarkdown(text, file) {
|
|
38
|
+
const normalized = text.replace(/^\uFEFF/, "");
|
|
39
|
+
const opening = /^(---)[ \t]*\r?\n/.exec(normalized);
|
|
40
|
+
if (!opening)
|
|
41
|
+
return { frontmatter: {}, body: normalized };
|
|
42
|
+
const rest = normalized.slice(opening[0].length);
|
|
43
|
+
const closing = /^(?:---|\.\.\.)[ \t]*(?:\r?\n|$)/m.exec(rest);
|
|
44
|
+
if (!closing)
|
|
45
|
+
throw new Error(`Unclosed YAML frontmatter: ${file}`);
|
|
46
|
+
return { frontmatter: parseYamlMapping(rest.slice(0, closing.index), file), body: rest.slice(closing.index + closing[0].length) };
|
|
47
|
+
}
|
|
48
|
+
/** Includes container keys and nested object keys; arrays retain their index for object entries. */
|
|
49
|
+
export function fieldPaths(value) {
|
|
50
|
+
const result = [];
|
|
51
|
+
function visit(item, prefix) {
|
|
52
|
+
if (!item || typeof item !== "object")
|
|
53
|
+
return;
|
|
54
|
+
for (const [key, child] of Object.entries(item)) {
|
|
55
|
+
const field = prefix ? `${prefix}.${key}` : key;
|
|
56
|
+
result.push(field);
|
|
57
|
+
visit(child, field);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
visit(value, "");
|
|
61
|
+
return result;
|
|
62
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import { lstat, readdir } from "node:fs/promises";
|
|
4
|
+
import { mapSubagent } from "../mapping/subagent.js";
|
|
5
|
+
import { mapCodexAgent } from "../mapping/codex-agent.js";
|
|
6
|
+
import { mapCodexSkillMetadata } from "../mapping/codex-skill.js";
|
|
7
|
+
import { mapSkill } from "../mapping/skill.js";
|
|
8
|
+
import { scriptName } from "../mapping/names.js";
|
|
9
|
+
import { scanForeignTools } from "../mapping/foreign-tools.js";
|
|
10
|
+
import { parseMarkdown, parseYamlMapping } from "./frontmatter.js";
|
|
11
|
+
import { parseCodexAgent } from "./codex.js";
|
|
12
|
+
import { assertSafePath, isSensitiveName, listSafeFiles, MAX_FILES, readSafeText, SKIP_DIRECTORIES } from "./safety.js";
|
|
13
|
+
import { dependencyInfo } from "./tstack.js";
|
|
14
|
+
function expandHome(input) { return input === "~" ? os.homedir() : input.startsWith("~/") ? path.join(os.homedir(), input.slice(2)) : input; }
|
|
15
|
+
async function exists(file) {
|
|
16
|
+
try {
|
|
17
|
+
await lstat(file);
|
|
18
|
+
return true;
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
if (error.code === "ENOENT")
|
|
22
|
+
return false;
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function scanInputs(inputs, options) {
|
|
27
|
+
if (!inputs.length)
|
|
28
|
+
throw new Error("At least one input is required.");
|
|
29
|
+
const ir = { agents: [], skills: [], scripts: [], findings: [] };
|
|
30
|
+
const seen = new Map();
|
|
31
|
+
const visitedDirectories = new Set();
|
|
32
|
+
const claimedNames = new Map();
|
|
33
|
+
let visitedEntries = 0;
|
|
34
|
+
let assetFiles = 0;
|
|
35
|
+
function claim(kind, id, source) {
|
|
36
|
+
const key = `${kind}:${id.toLowerCase()}`;
|
|
37
|
+
const existing = claimedNames.get(key);
|
|
38
|
+
if (existing && existing !== source)
|
|
39
|
+
throw new Error(`Name collision after normalization (${key}): ${existing} and ${source}`);
|
|
40
|
+
claimedNames.set(key, source);
|
|
41
|
+
}
|
|
42
|
+
function addSkipped(file, reason) {
|
|
43
|
+
ir.findings.push({ source: file, field: "$files", status: "dropped", message: "已跳过生成文件、依赖、缓存、版本管理或敏感路径,未读取其内容。", messageEn: reason });
|
|
44
|
+
}
|
|
45
|
+
async function addAgent(file, format) {
|
|
46
|
+
const source = await assertSafePath(file);
|
|
47
|
+
if (seen.has(source))
|
|
48
|
+
return;
|
|
49
|
+
const text = await readSafeText(source);
|
|
50
|
+
const asset = format === "codex"
|
|
51
|
+
? mapCodexAgent(source, parseCodexAgent(text, source), options)
|
|
52
|
+
: mapSubagent(source, parseMarkdown(text, source), options);
|
|
53
|
+
claim("agent", asset.id, source);
|
|
54
|
+
seen.set(source, "agent");
|
|
55
|
+
ir.agents.push(asset);
|
|
56
|
+
ir.findings.push(...asset.findings);
|
|
57
|
+
}
|
|
58
|
+
async function addSkill(directory, dependency = false) {
|
|
59
|
+
const source = await assertSafePath(path.join(directory, "SKILL.md"));
|
|
60
|
+
const existing = ir.skills.find((skill) => skill.source === source);
|
|
61
|
+
if (existing) {
|
|
62
|
+
if (!dependency)
|
|
63
|
+
existing.dependency = false;
|
|
64
|
+
return existing;
|
|
65
|
+
}
|
|
66
|
+
const asset = mapSkill(source, parseMarkdown(await readSafeText(source), source), options, dependency);
|
|
67
|
+
claim("skill", asset.id, source);
|
|
68
|
+
seen.set(source, "skill");
|
|
69
|
+
ir.skills.push(asset);
|
|
70
|
+
ir.findings.push(...asset.findings);
|
|
71
|
+
for (const file of await listSafeFiles(directory, addSkipped)) {
|
|
72
|
+
if (++assetFiles > MAX_FILES)
|
|
73
|
+
throw new Error(`Skill assets exceed ${MAX_FILES} files; select narrower inputs.`);
|
|
74
|
+
const relativePath = path.relative(directory, file).split(path.sep).join("/");
|
|
75
|
+
if (relativePath === "SKILL.md")
|
|
76
|
+
continue;
|
|
77
|
+
if (relativePath.startsWith("scripts/")) {
|
|
78
|
+
const id = scriptName(relativePath.slice("scripts/".length));
|
|
79
|
+
claim("script", id, file);
|
|
80
|
+
ir.scripts.push({ id, source: file, skillId: asset.id, relativePath });
|
|
81
|
+
ir.findings.push({ source: file, field: "$script", status: "degraded", target: `bot/tools/${id}.ts`, message: "保留原脚本并生成禁用的 JSON 工具存根;需人工实现 stdin/stdout 契约后启用,转换不会执行脚本。", messageEn: "Original script preserved and a disabled JSON tool stub generated; implement its stdin/stdout contract before enabling. Conversion never executes scripts." });
|
|
82
|
+
if ((await lstat(file)).mode & 0o111)
|
|
83
|
+
ir.findings.push({ source: file, field: "$permissions", status: "degraded", message: "原脚本有可执行位;生成项目仅复制文件内容,不保留执行权限。若需直接运行脚本,请审核后恢复权限或显式调用解释器。", messageEn: "The source script is executable. Generated files preserve content but not executable permission; after review, restore permissions or invoke the interpreter explicitly." });
|
|
84
|
+
}
|
|
85
|
+
if (relativePath === "agents/openai.yaml") {
|
|
86
|
+
const metadata = parseYamlMapping(await readSafeText(file), file);
|
|
87
|
+
const mapped = mapCodexSkillMetadata(file, metadata, asset.id);
|
|
88
|
+
asset.trigger = mapped.trigger;
|
|
89
|
+
asset.findings.push(...mapped.findings);
|
|
90
|
+
ir.findings.push(...mapped.findings);
|
|
91
|
+
}
|
|
92
|
+
if (/\.md$/i.test(file))
|
|
93
|
+
ir.findings.push(...scanForeignTools(await readSafeText(file), file));
|
|
94
|
+
ir.findings.push({ source: file, field: "$file", status: "mapped", target: `bot/skills/${asset.id}/${relativePath}`, message: "附属文件原样保留;保留文件不代表自动赋予工具能力。", messageEn: "Supporting file preserved unchanged; copying does not make it a callable tool." });
|
|
95
|
+
}
|
|
96
|
+
return asset;
|
|
97
|
+
}
|
|
98
|
+
async function discover(file, inAgentTree, explicit, depth) {
|
|
99
|
+
if (depth > 32)
|
|
100
|
+
throw new Error(`Input nesting exceeds 32 directories: ${file}`);
|
|
101
|
+
const absolute = await assertSafePath(file);
|
|
102
|
+
const stat = await lstat(absolute);
|
|
103
|
+
if (stat.isFile()) {
|
|
104
|
+
if (path.basename(absolute) === "SKILL.md")
|
|
105
|
+
await addSkill(path.dirname(absolute));
|
|
106
|
+
else if (/\.md$/i.test(absolute) && (explicit || (inAgentTree && inAgentTree !== "codex")))
|
|
107
|
+
await addAgent(absolute, "claude");
|
|
108
|
+
else if (/\.toml$/i.test(absolute) && (explicit || (inAgentTree && inAgentTree !== "claude"))) {
|
|
109
|
+
if (path.basename(absolute) === "config.toml" && path.basename(path.dirname(absolute)) === ".codex")
|
|
110
|
+
throw new Error(`Session config.toml is not supported; pass standalone Codex agent TOML files with name, description, and developer_instructions: ${absolute}`);
|
|
111
|
+
await addAgent(absolute, "codex");
|
|
112
|
+
}
|
|
113
|
+
else if (explicit)
|
|
114
|
+
throw new Error(`Expected a Markdown agent, standalone Codex TOML agent, or a skill directory: ${absolute}`);
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
if (!stat.isDirectory())
|
|
118
|
+
throw new Error(`Special files are not permitted: ${absolute}`);
|
|
119
|
+
if (visitedDirectories.has(absolute))
|
|
120
|
+
return;
|
|
121
|
+
visitedDirectories.add(absolute);
|
|
122
|
+
if (await exists(path.join(absolute, "SKILL.md"))) {
|
|
123
|
+
await addSkill(absolute);
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
// Also recognize a directly selected subdirectory such as .codex/agents/review/.
|
|
127
|
+
const ancestors = absolute.split(path.sep);
|
|
128
|
+
let agentTree = inAgentTree;
|
|
129
|
+
for (let index = ancestors.length - 1; index > 0; index--) {
|
|
130
|
+
if (ancestors[index] !== "agents")
|
|
131
|
+
continue;
|
|
132
|
+
if (ancestors[index - 1] === ".codex") {
|
|
133
|
+
agentTree = "codex";
|
|
134
|
+
break;
|
|
135
|
+
}
|
|
136
|
+
if (ancestors[index - 1] === ".claude") {
|
|
137
|
+
agentTree = "claude";
|
|
138
|
+
break;
|
|
139
|
+
}
|
|
140
|
+
agentTree ??= "mixed";
|
|
141
|
+
}
|
|
142
|
+
for (const entry of (await readdir(absolute, { withFileTypes: true })).sort((a, b) => a.name.localeCompare(b.name))) {
|
|
143
|
+
const child = path.join(absolute, entry.name);
|
|
144
|
+
if (++visitedEntries > MAX_FILES)
|
|
145
|
+
throw new Error(`Scan exceeds ${MAX_FILES} entries; select narrower inputs.`);
|
|
146
|
+
if (entry.isSymbolicLink())
|
|
147
|
+
throw new Error(`Symbolic links are not permitted: ${child}`);
|
|
148
|
+
if (isSensitiveName(entry.name)) {
|
|
149
|
+
addSkipped(child, "Sensitive path omitted from project discovery without reading it.");
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
if (entry.isDirectory() && SKIP_DIRECTORIES.has(entry.name)) {
|
|
153
|
+
addSkipped(child, "Generated, dependency, cache, or version-control directory omitted.");
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (entry.name === "config.toml" && path.basename(absolute) === ".codex") {
|
|
157
|
+
ir.findings.push({ source: child, field: "$session-config", status: "dropped",
|
|
158
|
+
message: "未读取 Codex 父会话配置;仅支持独立子代理 TOML 和 skill 目录。旧式 agents.<name>.config_file 声明未解析。",
|
|
159
|
+
messageEn: "Codex parent-session configuration was not read. Only standalone agent TOML and skill directories are supported; legacy agents.<name>.config_file declarations are not resolved." });
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (entry.isDirectory() || /\.md$/i.test(entry.name) || (agentTree && agentTree !== "claude" && /\.toml$/i.test(entry.name)))
|
|
163
|
+
await discover(child, agentTree, false, depth + 1);
|
|
164
|
+
else if (!entry.isFile())
|
|
165
|
+
throw new Error(`Special files are not permitted: ${child}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
for (const input of inputs)
|
|
169
|
+
await discover(expandHome(input), undefined, true, 0);
|
|
170
|
+
if (options.withDeps) {
|
|
171
|
+
// The growing queue computes the transitive closure and seen sources break cycles.
|
|
172
|
+
for (let index = 0; index < ir.skills.length; index++) {
|
|
173
|
+
const skill = ir.skills[index];
|
|
174
|
+
const info = await dependencyInfo(skill.directory, skill.id);
|
|
175
|
+
if (!info.names.length)
|
|
176
|
+
continue;
|
|
177
|
+
ir.findings.push({ source: skill.source, field: "$dependencies", status: info.fallback ? "degraded" : "mapped", message: info.fallback ? `按已核实的 tstack 快照补齐同级依赖;源文件未附 README,需确认同名技能的来源。依据:${info.basis}` : `按源 README 补齐同级依赖。依据:${info.basis}`, messageEn: info.fallback ? `Resolved sibling dependencies using the pinned tstack graph; source README unavailable, so verify the provenance of same-named skills. Basis: ${info.basis}` : `Resolved sibling dependencies from the source README. Basis: ${info.basis}` });
|
|
178
|
+
for (const name of info.names) {
|
|
179
|
+
const directory = path.join(path.dirname(skill.directory), name);
|
|
180
|
+
if (!await exists(path.join(directory, "SKILL.md")))
|
|
181
|
+
throw new Error(`Missing dependency '${name}' for '${skill.id}': ${directory}`);
|
|
182
|
+
await addSkill(directory, true);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
if (!ir.agents.length && !ir.skills.length)
|
|
187
|
+
throw new Error("No Claude agents, Codex standalone agents, or SKILL.md directories found. Pass agent .md/.toml files explicitly or select .claude/agents or .codex/agents.");
|
|
188
|
+
ir.agents.sort((a, b) => a.id.localeCompare(b.id));
|
|
189
|
+
ir.skills.sort((a, b) => a.id.localeCompare(b.id));
|
|
190
|
+
ir.scripts.sort((a, b) => a.id.localeCompare(b.id));
|
|
191
|
+
return ir;
|
|
192
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const MAX_TEXT_BYTES: number;
|
|
2
|
+
export declare const MAX_ASSET_BYTES: number;
|
|
3
|
+
export declare const MAX_FILES = 5000;
|
|
4
|
+
export declare const SKIP_DIRECTORIES: Set<string>;
|
|
5
|
+
export declare function isSensitiveName(name: string): boolean;
|
|
6
|
+
/** Never resolve a symlink before deciding whether it is safe to read. */
|
|
7
|
+
export declare function assertSafePath(file: string): Promise<string>;
|
|
8
|
+
export declare function readSafeFile(file: string, maxBytes?: number): Promise<Buffer>;
|
|
9
|
+
export declare function readSafeText(file: string, maxBytes?: number): Promise<string>;
|
|
10
|
+
/** Absolute, sorted regular files. Sensitive files fail closed instead of being copied. */
|
|
11
|
+
export declare function listSafeFiles(directory: string, onSkip?: (file: string, reason: string) => void): Promise<string[]>;
|