@cyberuni/agent-harness

中文文档
0.7.1发布于 2026/10/18MITES Module零依赖构建来源已验证近 30 天下载 2906

A toolkit for working with AI agent harnesses: detect which one is running, query its managed-policy locations, plugin storage, enabled plugins, and skill naming, and resolve layered reference documents.

此软件包已被标记为弃用

Renamed to cyber-agentkit

原始使用文档
文档目录13 节
  1. Detect the harness
  2. Managed policy
  3. Plugin storage
  4. Enabled plugins
  5. Installed plugins
  6. Skill naming and plugin dependencies
  7. Skill directories
  8. Headless runs
  9. Reference documents
  10. The reference command
  11. The reference skill
  12. Why this exists
  13. License

@cyberuni/agent-harness

license: MIT

A toolkit for working with AI agent harnesses. Detect which one is running — Claude Code, Cursor, Codex, GitHub Copilot CLI, OpenCode, Kilo Code, Gemini CLI, Qwen Code, GitHub Copilot in VS Code, Cline, Crush, OpenHands, or the Auggie CLI — query what it holds (its managed-policy locations, plugin storage, enabled plugins, how it names plugin skills, and how to run it headless), and resolve the reference documents an agent reads on demand.

It depends on no other agent tool, so any of them can build on it.

Status: pre-release. Every fact this library encodes rests on .research/harness-detection.

Detect the harness

import { detectHarness } from '@cyberuni/agent-harness'

const result = detectHarness()
// {
//   harness: 'claude-code',
//   candidates: ['claude-code'],
//   evidence: [
//     { harness: 'claude-code', signal: 'CLAUDECODE', research: 'E-CC-D1', description: '…' },
//     { harness: 'claude-code', signal: 'CLAUDE_CODE_CHILD_SESSION', research: 'E-CC-D3', description: '…' },
//   ],
// }

Detection reads environment variables that each harness sets on the processes it spawns:

HarnessDetected from
Claude CodeCLAUDECODE=1 with CLAUDE_CODE_CHILD_SESSION=1 or CLAUDE_CODE_SESSION_ID
CursorCURSOR_AGENT=1, or CURSOR_VERSION with CURSOR_PROJECT_DIR in hooks
CodexCODEX_THREAD_ID
GitHub Copilot CLICOPILOT_CLI=1, COPILOT_AGENT_SESSION_ID, or COPILOT_PLUGIN_ROOT in hooks
OpenCodeOPENCODE=1 with OPENCODE_PID
Kilo CodeKILO=1 with KILO_PID
Gemini CLIGEMINI_CLI=1
Qwen CodeQWEN_CODE=1
GitHub Copilot in VS CodeCOPILOT_AGENT=1
ClineCLINE_ACTIVE=true, set by the VS Code extension only
CrushCRUSH=1
OpenHandsAI_AGENT=openhands, or OPENHANDS_EVENT_TYPE with OPENHANDS_PROJECT_DIR in hooks
Auggie CLIAUGMENT_HOOK_EVENT with AUGMENT_PROJECT_DIR, in hooks only

It reports unknown instead of guessing:

  • when no harness matches, including CLAUDECODE=1 on its own, which IDE extensions also set;
  • when more than one harness matches, as when one harness runs inside another's shell. The matches are listed in candidates;
  • for AGENT and AI_AGENT, which several vendors set (only the exact value AI_AGENT=openhands counts), and for OPENCODE=1 alone, which Kilo Code, an OpenCode fork, also sets;
  • in contexts where a harness sets nothing of its own: Codex, Gemini CLI, and Qwen Code hooks, Qwen Code, Crush, OpenHands, and Auggie MCP servers, Auggie shell commands, OpenHands CLI 1.16.0 shell commands (its SDK predates AI_AGENT=openhands), and the Cline CLI;
  • for harnesses with no verified signal, such as Goose, Antigravity, Rovo Dev, Kiro, Amp, Factory Droid, Devin Desktop, and Warp. The research records why for each one.

Pass { env } to inspect an environment other than process.env.

Managed policy

import { managedPolicyLocations } from '@cyberuni/agent-harness'

managedPolicyLocations('claude-code', { platform: 'linux' })
// [
//   { kind: 'server', location: 'claude.ai admin console or a self-hosted Claude apps gateway', … },
//   { kind: 'file', location: '/etc/claude-code/managed-settings.json', … },
//   { kind: 'directory', location: '/etc/claude-code/managed-settings.d', … },
//   { kind: 'file', location: '/etc/claude-code/managed-mcp.json', … },
// ]

kind is file or directory for what a caller can read from disk, macos-managed-preferences or windows-registry for MDM delivery, and server for policy held by the vendor or the organization. A server entry means the local files are not the whole policy.

Plugin storage

import { pluginStorage } from '@cyberuni/agent-harness'

pluginStorage('codex')
// {
//   harness: 'codex',
//   configDir: '/home/me/.codex',
//   locations: [
//     { kind: 'plugin-cache', path: '/home/me/.codex/plugins/cache', … },
//     { kind: 'enabled-record', path: '/home/me/.codex/config.toml', … },
//   ],
// }

Paths are computed, not checked, so a location may not exist until the first plugin is installed. Cursor has no enabled-record: no file records which of its plugins are enabled.

Enabled plugins

import { enabledPlugins } from '@cyberuni/agent-harness'

const result = await enabledPlugins('claude-code')
// {
//   harness: 'claude-code',
//   supported: true,
//   plugins: [{ id: 'my-plugin@my-marketplace', enabled: true, scope: 'project', source: '/repo/.claude/settings.json' }],
//   sources: [{ scope: 'managed', path: '/etc/claude-code/managed-settings.json', found: false }, …],
//   unread: ['server-managed settings (claude.ai admin console or a self-hosted Claude apps gateway)', …],
//   research: ['E-CC-P3', …],
// }
HarnessFiles read, highest precedence first
Claude Codemanaged managed-settings.json, .claude/settings.local.json, .claude/settings.json, <config dir>/settings.json
Codex<config dir>/config.toml
GitHub Copilot CLImanaged managed-settings.json, .github/copilot/settings.local.json, .github/copilot/settings.json, <config dir>/settings.json
Cursor, OpenCode, Kilo Code, Gemini CLI, Qwen Code, Copilot in VS Code, Cline, Crush, OpenHands, Auggie CLInone: supported is false

Only plugins with an explicit entry are reported. A plugin with no entry falls back to a default this package does not know. When unread is not empty, MDM or server-managed policy can still override the answer.

Installed plugins

import { enabledPlugins, installedPlugins } from '@cyberuni/agent-harness'

const installed = await installedPlugins('claude-code')
// {
//   harness: 'claude-code',
//   supported: true,
//   plugins: [{ id: 'my-plugin@my-marketplace', path: '/home/me/.claude/plugins/cache/my-marketplace/my-plugin/1.0.0', version: '1.0.0', scope: 'user', research: ['E-CC-P2'] }],
//   unresolved: [],
//   sources: [{ path: '/home/me/.claude/plugins/installed_plugins.json', found: true }],
//   unread: [],
//   research: ['E-CC-P2'],
// }

// The folder of each enabled plugin:
const enabled = new Set((await enabledPlugins('claude-code')).plugins.filter((p) => p.enabled).map((p) => p.id))
const folders = installed.plugins.filter((p) => enabled.has(p.id)).map((p) => p.path)
HarnessRead fromFolder
Claude Code<config dir>/plugins/installed_plugins.jsoninstallPath; one entry per scope a plugin is installed at
Codex<config dir>/plugins/cache/<marketplace>/<plugin>/local if present, else the highest version, as Codex picks it
GitHub Copilot CLI<config dir>/config.json installedPluginscache_path; a direct install's id is its bare name
Cursor, OpenCode, Kilo Code, Gemini CLI, Qwen Code, Copilot in VS Code, Cline, Crush, OpenHands, Auggie CLInone: supported is false—

unresolved lists Codex plugins whose cache mixes semver and non-semver version folders, where the folder Codex picks depends on its sort. Copilot CLI loads plugins from a marketplace added by local path in place, without an install record, so they appear in unread instead.

Skill naming and plugin dependencies

import { skillInvocation, supportsPluginDependencies } from '@cyberuni/agent-harness'

skillInvocation('claude-code', { plugin: 'my-plugin', skill: 'review' })?.text // '/my-plugin:review'
skillInvocation('copilot-cli', { plugin: 'my-plugin', skill: 'review' })?.text // '/review'
skillInvocation('codex', { plugin: 'my-plugin', skill: 'review' })?.text // '$review'
skillInvocation('gemini-cli', { plugin: 'my-plugin', skill: 'review' }) // undefined: the model loads skills itself

supportsPluginDependencies('claude-code') // true; false for the others

Skill directories

import { skillsDirectories } from '@cyberuni/agent-harness'

skillsDirectories('claude-code') // { project: ['.claude/skills'], user: ['.claude/skills'], research: ['E-CC-L1'] }
skillsDirectories('codex')?.project.includes('.agents/skills') // true
skillsDirectories('qwen-code') // undefined: not confirmed

Project directories are relative to the project root, user directories to the home directory. Only directories read by default are listed; admin, bundled, and plugin skills are left out.

Headless runs

How to run a harness on one prompt with no user present: the command, the model and permission flags, the output formats and where token usage and cost appear in them, the transcript location, and the documented exit codes. The library only describes the run; starting the process is up to you.

import { headlessCommand, headlessInvocation } from '@cyberuni/agent-harness'

headlessCommand('claude-code', {
  prompt: 'Fix the failing test',
  model: 'sonnet',
  permission: 'unattended',
  outputFormat: 'stream-json',
})
// { executable: 'claude', args: ['-p', '--model', 'sonnet', '--permission-mode', 'bypassPermissions',
//   '--output-format', 'stream-json', '--verbose', 'Fix the failing test'] }

const codex = headlessInvocation('codex')
// codex.outputFormats[1]: { name: 'jsonl', args: ['--json'],
//   usage: { event: 'turn.completed', tokens: 'usage', accumulate: 'sum' } } (no dollar cost)

headlessInvocation('vscode-copilot').supported // false: no headless mode
headlessInvocation('opencode').supported // 'unknown': a one-shot mode exists, but its flags are not verified

Claude Code, Codex, Copilot CLI, Cursor, Gemini CLI, and Qwen Code are covered. permission: 'unattended' lets the run edit files and run commands without asking, so use it only in a throwaway checkout. Exit codes are a weak signal: Claude Code documents only zero and non-zero, and a run stopped by a turn or budget cap is told apart by the result message's subtype.

Reference documents

A reference is a named Markdown document an agent reads on demand, such as a checklist or a set of weights a skill applies. Anyone can override one without editing the plugin that ships it: a project in .agents/references/<name>.md, a person in ~/.agents/references/<name>.md.

import { loadReference } from '@cyberuni/agent-harness'

const resolved = await loadReference('agent-readiness-weights', {
    root: process.cwd(),
    // The plugin calling the resolver: its own `references/` is the first plugin layer.
    plugin: { name: 'my-plugin', root: pluginRoot },
})
if (resolved.status === 'found') console.log(resolved.content)

status is found, missing, or ambiguous (two plugins hold the name; ask for one of resolved.plugins, such as my-plugin/agent-readiness-weights). trace says what each layer held.

Layers are read highest precedence first:

TierFolders
managedthe admin references/ folder, and a references/ beside each detected harness's managed-policy files
project.agents/references/ in the root and each folder above it, up to the repository root
user~/.agents/references/
pluginthe calling plugin's references/, each plugin the harness has enabled, then each package the project's package.json declares

In each folder, <name>.md answers first, then <name>/README.md, <name>/index.md, and <name>/SKILL.md. Legacy governances/ folders are still read, below the references/ beside them.

A document's frontmatter merge says how it combines with the layers below:

  • first-wins (the default): it replaces everything below.
  • combine: it is placed above the layers below, each kept whole.
  • merge-sections: its sections replace the same-titled sections below, matched by heading path, and the sections it does not redefine are kept. Under a heading, <!-- merge: combine --> appends to that section instead, and <!-- merge: remove --> deletes it.

Frontmatter is read as YAML. merge, description, and tags are the keys the resolver reads; resolved.metadata holds every key, the higher layer winning. Frontmatter that is not a YAML mapping is ignored with a warning.

FunctionDoes
loadReference(name, options)Resolves one name for a root, with every tier read
referenceLayers(options)Builds the ordered layers, to resolve several names against
resolveReference(name, layers)Resolves a parsed name (parseReferenceName) against layers
listReferences(layers), referenceNames(layers)Every name the layers hold, with the status of each copy
listReferences(layers, { pattern })Every reference by exact name pattern, with tier, plugin, and ambiguity status
searchReferences(query, layers)Names matching a query by name, description, heading, or body
whereReference(name, layers, options)The project and user files an override can be written to
managedReferencesDir(), projectReferencesDir(), projectReferenceLayers()Single folders and layers
declaredDependencies(), packageDir()A manifest's declared packages, and where each is installed

The reference command

The package ships a reference command with show, list, search, where, create, and delete subcommands:

npx -y @cyberuni/agent-harness reference show <name>... --root <repository root>
npx -y @cyberuni/agent-harness reference list
npx -y @cyberuni/agent-harness reference list 'project-tracker.capabilities.*'
npx -y @cyberuni/agent-harness reference create <name> --scope project
npx -y @cyberuni/agent-harness reference delete <name> --scope project --dry-run

delete removes only the copy in .agents/references/ (--scope project) or ~/.agents/references/ (--scope user), and reports which copy answers the name afterwards: the plugin's copy, once a project override of it is gone. It refuses a plugin-shipped or managed copy.

Output is TOON by default, for an agent to parse; pass --format json or --format text (show, create, and delete default to text). The formats follow clibuilder 12, and any other value is a usage error (exit code 2). A CLI built on clibuilder 12 can host the same command under its own plugin name, imported from the ./commands subpath. --format is the host's global option, so the host's cli({ format }) sets the default for every subcommand; the text default of show, create, and delete belongs to the agent-harness CLI:

import { createReferenceCommand } from '@cyberuni/agent-harness/commands'

app.command(createReferenceCommand({ plugin: { name: 'my-plugin', root: pluginRoot } }))

The reference skill

The package is also an agent plugin, cyber-agent-harness, that ships a reference skill. Type /reference (/cyber-agent-harness:reference in Claude Code) to load, create, update, delete, find, or inspect a reference, or to wire a skill to load one. The skill writes or deletes a file only after you approve it. It runs the command from its bundled scripts/reference.mjs, which needs no node_modules.

/plugin marketplace add cyberuni/cyberplace
/plugin install cyber-agent-harness@cyberplace

A skill that needs a reference names it in one line, as the skill's README.md shows:

Load `skill-design` with the `reference` skill in the `cyber-agent-harness` plugin.

Why this exists

universal-plugin, buddy-agent-harness, repobuddy, and cyberlegion/cyber-mux each need to know which agent harness they are running under, and each was re-implementing that detection separately. This package exists so they share one implementation instead.

License

MIT

版本与发布记录

@cyberuni/agent-harness 共发布 9 个版本,首次发布于 2026/09/27;过去 12 个月发布了 9 个版本。最新版本附带 npm provenance 构建来源证明,可追溯到公开的 CI 构建记录。

版本发布日期距今
0.7.1latest2026/10/1824 天前
0.7.02026/10/1328 天前
0.6.02026/10/061 个月前
0.5.02026/10/051 个月前
0.4.02026/10/051 个月前
0.3.02026/09/291 个月前

关于 @cyberuni/agent-harness 的常见问题

@cyberuni/agent-harness 是什么?

A toolkit for working with AI agent harnesses: detect which one is running, query its managed-policy locations, plugin storage, enabled plugins, and skill naming, and resolve layered reference documents.注意:该包已被作者标记为弃用——Renamed to cyber-agentkit

如何安装 @cyberuni/agent-harness?

在项目目录运行 npm install @cyberuni/agent-harness,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 agent-harness,可以用 npx agent-harness 直接运行。

@cyberuni/agent-harness 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/cyberuni__agent-harness 是否存在。

@cyberuni/agent-harness 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@cyberuni/agent-harness 需要什么版本的 Node.js?

package.json 的 engines 字段要求 Node.js >=22。版本不符时 npm 默认只给出警告,开启 engine-strict 后会拒绝安装。

@cyberuni/agent-harness 可以商用吗?

@cyberuni/agent-harness 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@cyberuni/agent-harness 的下载量有多少?

根据 npm 官方下载统计,@cyberuni/agent-harness 在 2026/10/11 – 2026/11/09 这 30 天内被下载 2,906 次,日均约 97 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@cyberuni/agent-harness 还在维护吗?

最新版本 0.7.1 发布于 2026/10/18(24 天前);至今共发布 9 个版本,其中过去 12 个月发布了 9 个;首次发布于 2026/09/27。作者已将其标记为弃用,新项目建议寻找替代方案。