Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,9 @@ packages/
│ ├── index.ts # Library entry point (re-exports lib symbols with @deprecated)
│ ├── scope.ts # Project/user scope resolution
│ ├── cli/ # CLI entry point + commands (init, install, add, remove, sync, list, mcp, doctor, trust)
│ ├── agents/ # Agent definitions, MCP/hook config writers
│ ├── targets/ # Target agent definitions plus MCP/hook config writers
│ ├── subagents/ # Subagent identity, store, and runtime writer
│ ├── agents/ # Compatibility re-export barrel for older internal imports
│ ├── config/ # agents.toml schema, loader, writer
│ ├── lockfile/ # agents.lock schema, loader, writer
│ ├── symlinks/ # Symlink creation/management
Expand Down
30 changes: 17 additions & 13 deletions packages/dotagents/src/agents/index.ts
Original file line number Diff line number Diff line change
@@ -1,39 +1,41 @@
export { getAgent, allAgentIds } from "./registry.js";
export { writeMcpConfigs, verifyMcpConfigs, toMcpDeclarations, projectMcpResolver } from "./mcp-writer.js";
export type { McpTargetResolver, McpResolvedTarget } from "./mcp-writer.js";
export { writeHookConfigs, verifyHookConfigs, toHookDeclarations, projectHookResolver } from "./hook-writer.js";
export type { HookTargetResolver, HookResolvedTarget } from "./hook-writer.js";
// Compatibility barrel for older imports. Runtime ownership now lives in
// targets/ for agent targets and subagents/ for subagent handling.
export { getAgent, allAgentIds } from "../targets/registry.js";
export { writeMcpConfigs, verifyMcpConfigs, toMcpDeclarations, projectMcpResolver } from "../targets/mcp-writer.js";
export type { McpTargetResolver, McpResolvedTarget } from "../targets/mcp-writer.js";
export { writeHookConfigs, verifyHookConfigs, toHookDeclarations, projectHookResolver } from "../targets/hook-writer.js";
export type { HookTargetResolver, HookResolvedTarget } from "../targets/hook-writer.js";
export {
writeSubagentConfigs,
pruneSubagentConfigs,
verifySubagentConfigs,
projectSubagentResolver,
userSubagentResolver,
} from "./subagent-writer.js";
} from "../subagents/writer.js";
export {
resolveSubagent,
writeInstalledSubagents,
loadInstalledSubagents,
pruneInstalledSubagents,
lockEntryForSubagent,
} from "./subagent-store.js";
} from "../subagents/store.js";
export type {
SubagentTargetResolver,
SubagentResolvedTarget,
SubagentWriteWarning,
SubagentWriteResult,
SubagentVerifyIssue,
} from "./subagent-writer.js";
} from "../subagents/writer.js";
export type {
SubagentResolveOptions,
ResolvedSubagent,
ResolvedSubagentType,
InstalledSubagentLoadIssue,
} from "./subagent-store.js";
} from "../subagents/store.js";
export type { LockedSubagent } from "../lockfile/schema.js";
export { UnsupportedFeature } from "./errors.js";
export { getUserMcpTarget, userMcpResolver } from "./paths.js";
export type { UserMcpTarget } from "./paths.js";
export { UnsupportedFeature } from "../targets/errors.js";
export { getUserMcpTarget, userMcpResolver } from "../targets/paths.js";
export type { UserMcpTarget } from "../targets/paths.js";
export type {
AgentDefinition,
McpDeclaration,
Expand All @@ -42,11 +44,13 @@ export type {
HookDeclaration,
HookConfigSpec,
HookSerializer,
} from "../targets/types.js";
export type {
SubagentDeclaration,
NativeSubagentConfig,
NativeSubagentContent,
NativeSubagentTarget,
SubagentConfigSpec,
SubagentIdentityStrategy,
SubagentSerializer,
} from "./types.js";
} from "../subagents/types.js";
2 changes: 1 addition & 1 deletion packages/dotagents/src/cli/commands/doctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { isWildcardDep } from "../../config/schema.js";
import { loadLockfile } from "../../lockfile/loader.js";
import { writeLockfile } from "../../lockfile/writer.js";
import { verifySymlinks } from "../../symlinks/manager.js";
import { getAgent } from "../../agents/registry.js";
import { getAgent } from "../../targets/registry.js";
import { resolveScope, resolveDefaultScope, ScopeError, type ScopeRoot } from "../../scope.js";
import { exec } from "@sentry/dotagents-lib";
import { isInPlaceSkill } from "../../utils/fs.js";
Expand Down
2 changes: 1 addition & 1 deletion packages/dotagents/src/cli/commands/init.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import { generateDefaultConfig } from "../../config/writer.js";
import { writeAgentsGitignore, ensureRootGitignoreEntries } from "../../gitignore/writer.js";
import { ensureSkillsSymlink } from "../../symlinks/manager.js";
import { loadConfig } from "../../config/loader.js";
import { getAgent, allAgentIds, allAgents } from "../../agents/registry.js";
import { getAgent, allAgentIds, allAgents } from "../../targets/registry.js";
import { parseArgs } from "node:util";
import * as clack from "@clack/prompts";
import { resolveScope, isInsideGitRepo, findGitDir, type ScopeRoot } from "../../scope.js";
Expand Down
2 changes: 1 addition & 1 deletion packages/dotagents/src/cli/commands/install.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { loadLockfile } from "../../lockfile/loader.js";
import { writeLockfile } from "../../lockfile/writer.js";
import type { Lockfile } from "../../lockfile/schema.js";
import { resolveScope } from "../../scope.js";
import { DOTAGENTS_SUBAGENT_MARKER } from "../../agents/definitions/helpers.js";
import { DOTAGENTS_SUBAGENT_MARKER } from "../../subagents/format.js";

const SKILL_MD = (name: string) => `---
name: ${name}
Expand Down
12 changes: 6 additions & 6 deletions packages/dotagents/src/cli/commands/install/agent-runtime.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,18 @@
import { join } from "node:path";
import type { AgentsConfig } from "../../../config/schema.js";
import type { ScopeRoot } from "../../../scope.js";
import { getAgent } from "../../../agents/registry.js";
import { getAgent } from "../../../targets/registry.js";
import { ensureSkillsSymlink } from "../../../symlinks/manager.js";
import { projectMcpResolver, toMcpDeclarations, writeMcpConfigs } from "../../../agents/mcp-writer.js";
import { projectHookResolver, toHookDeclarations, writeHookConfigs } from "../../../agents/hook-writer.js";
import { userMcpResolver } from "../../../agents/paths.js";
import { projectMcpResolver, toMcpDeclarations, writeMcpConfigs } from "../../../targets/mcp-writer.js";
import { projectHookResolver, toHookDeclarations, writeHookConfigs } from "../../../targets/hook-writer.js";
import { userMcpResolver } from "../../../targets/paths.js";
import {
pruneSubagentConfigs,
projectSubagentResolver,
userSubagentResolver,
writeSubagentConfigs,
} from "../../../agents/subagent-writer.js";
import type { SubagentDeclaration } from "../../../agents/types.js";
} from "../../../subagents/writer.js";
import type { SubagentDeclaration } from "../../../subagents/types.js";

/** Writes agent skill symlinks after canonical install artifacts are ready. */
export async function writeSkillSymlinks(
Expand Down
2 changes: 1 addition & 1 deletion packages/dotagents/src/cli/commands/install/gitignore.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { Lockfile } from "../../../lockfile/schema.js";
import type { ScopeRoot } from "../../../scope.js";
import { checkRootGitignoreEntries, writeAgentsGitignore } from "../../../gitignore/writer.js";
import { isInPlaceSkill } from "../../../utils/fs.js";
import type { SubagentDeclaration } from "../../../agents/types.js";
import type { SubagentDeclaration } from "../../../subagents/types.js";

export interface InstallGitignoreArtifacts {
installedSkillNames: string[];
Expand Down
4 changes: 2 additions & 2 deletions packages/dotagents/src/cli/commands/install/subagents.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ import { join } from "node:path";
import type { AgentsConfig, SubagentConfig } from "../../../config/schema.js";
import type { Lockfile } from "../../../lockfile/schema.js";
import type { ScopeRoot } from "../../../scope.js";
import type { SubagentDeclaration } from "../../../agents/types.js";
import type { SubagentDeclaration } from "../../../subagents/types.js";
import {
InstalledSubagentWriteError,
lockEntryForSubagent,
loadInstalledSubagents,
pruneInstalledSubagents,
resolveSubagent,
writeInstalledSubagents,
} from "../../../agents/subagent-store.js";
} from "../../../subagents/store.js";
import { GitError, TrustError } from "@sentry/dotagents-lib";
import { getCacheStateDir } from "../../cache.js";
import { InstallError } from "./errors.js";
Expand Down
2 changes: 1 addition & 1 deletion packages/dotagents/src/cli/commands/sync.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { writeLockfile } from "../../lockfile/writer.js";
import { loadLockfile } from "../../lockfile/loader.js";
import { loadConfig } from "../../config/loader.js";
import { resolveScope } from "../../scope.js";
import { DOTAGENTS_SUBAGENT_MARKER } from "../../agents/definitions/helpers.js";
import { DOTAGENTS_SUBAGENT_MARKER } from "../../subagents/format.js";
import { exec } from "@sentry/dotagents-lib";

const SKILL_MD = (name: string) => `---
Expand Down
12 changes: 6 additions & 6 deletions packages/dotagents/src/cli/commands/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ import { writeLockfile } from "../../lockfile/writer.js";
import { addSkillToConfig } from "../../config/writer.js";
import { writeAgentsGitignore, checkRootGitignoreEntries } from "../../gitignore/writer.js";
import { ensureSkillsSymlink, verifySymlinks } from "../../symlinks/manager.js";
import { getAgent } from "../../agents/registry.js";
import { verifyMcpConfigs, writeMcpConfigs, toMcpDeclarations, projectMcpResolver } from "../../agents/mcp-writer.js";
import { verifyHookConfigs, writeHookConfigs, toHookDeclarations, projectHookResolver } from "../../agents/hook-writer.js";
import { pruneSubagentConfigs, verifySubagentConfigs, writeSubagentConfigs, projectSubagentResolver, userSubagentResolver } from "../../agents/subagent-writer.js";
import { loadInstalledSubagents, pruneInstalledSubagents } from "../../agents/subagent-store.js";
import { userMcpResolver } from "../../agents/paths.js";
import { getAgent } from "../../targets/registry.js";
import { verifyMcpConfigs, writeMcpConfigs, toMcpDeclarations, projectMcpResolver } from "../../targets/mcp-writer.js";
import { verifyHookConfigs, writeHookConfigs, toHookDeclarations, projectHookResolver } from "../../targets/hook-writer.js";
import { pruneSubagentConfigs, verifySubagentConfigs, writeSubagentConfigs, projectSubagentResolver, userSubagentResolver } from "../../subagents/writer.js";
import { loadInstalledSubagents, pruneInstalledSubagents } from "../../subagents/store.js";
import { userMcpResolver } from "../../targets/paths.js";
import { resolveScope, resolveDefaultScope, ScopeError, type ScopeRoot } from "../../scope.js";
import { ensureUserScopeBootstrapped } from "../ensure-user-scope.js";
import { isInPlaceSkill, managedSkillPath } from "../../utils/fs.js";
Expand Down
2 changes: 1 addition & 1 deletion packages/dotagents/src/cli/ensure-user-scope.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { readFile, rm, mkdtemp } from "node:fs/promises";
import { join } from "node:path";
import { tmpdir } from "node:os";
import { ensureUserScopeBootstrapped } from "./ensure-user-scope.js";
import { allAgentIds } from "../agents/registry.js";
import { allAgentIds } from "../targets/registry.js";
import type { ScopeRoot } from "../scope.js";

describe("ensureUserScopeBootstrapped", () => {
Expand Down
2 changes: 1 addition & 1 deletion packages/dotagents/src/cli/ensure-user-scope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { existsSync } from "node:fs";
import { mkdir, writeFile } from "node:fs/promises";
import chalk from "chalk";
import { generateDefaultConfig } from "../config/writer.js";
import { allAgentIds } from "../agents/registry.js";
import { allAgentIds } from "../targets/registry.js";
import type { ScopeRoot } from "../scope.js";

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/dotagents/src/config/loader.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { readFile } from "node:fs/promises";
import { parse as parseTOML } from "smol-toml";
import { agentsConfigSchema, isWildcardDep, type AgentsConfig } from "./schema.js";
import { allAgentIds } from "../agents/registry.js";
import { allAgentIds } from "../targets/registry.js";
import { applyDefaultRepositorySource, parseSource } from "@sentry/dotagents-lib";

export class ConfigError extends Error {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,104 +1,12 @@
import { describe, it, expect } from "vitest";
import { parseMarkdownFrontmatterContent } from "@sentry/dotagents-lib";
import {
interpolateEnvRefs,
interpolateHeaders,
extractCodexHeaders,
markManagedMarkdownSubagent,
hasDotagentsMarkdownSubagentMarker,
hasDotagentsTomlSubagentMarker,
serializeMarkdownSubagent,
DOTAGENTS_SUBAGENT_MARKER,
} from "./helpers.js";

const cursorTpl = (k: string) => `\${env:${k}}`;

describe("interpolateEnvRefs", () => {

it("passes through strings with no refs", () => {
expect(interpolateEnvRefs("Bearer tok", cursorTpl)).toBe("Bearer tok");
});

it("replaces a single ref", () => {
expect(interpolateEnvRefs("${API_KEY}", cursorTpl)).toBe("${env:API_KEY}");
});

it("replaces ref in a mixed string", () => {
expect(interpolateEnvRefs("Bearer ${TOKEN}", cursorTpl)).toBe("Bearer ${env:TOKEN}");
});

it("replaces multiple refs", () => {
expect(interpolateEnvRefs("${A}:${B}", cursorTpl)).toBe("${env:A}:${env:B}");
});

it("ignores non-matching patterns", () => {
// No braces, empty braces, leading digit, hyphen in name
for (const s of ["$NOBRACES", "${}", "${123}", "${foo-bar}"]) {
expect(interpolateEnvRefs(s, cursorTpl)).toBe(s);
}
});

it("replaces underscore-prefixed and numeric-suffixed vars", () => {
expect(interpolateEnvRefs("${_FOO_2}", cursorTpl)).toBe("${env:_FOO_2}");
});
});

describe("interpolateHeaders", () => {
it("transforms all header values", () => {
const headers = { "X-Key": "${KEY}", "Authorization": "Bearer ${TOKEN}" };
const result = interpolateHeaders(headers, (k) => `{env:${k}}`);
expect(result).toEqual({ "X-Key": "{env:KEY}", "Authorization": "Bearer {env:TOKEN}" });
});

it("returns undefined for undefined input", () => {
const noHeaders: Record<string, string> | undefined = undefined;
expect(interpolateHeaders(noHeaders, (k) => k)).toBeUndefined();
});
});

describe("extractCodexHeaders", () => {
it("maps pure env ref to envHttpHeaders", () => {
const { httpHeaders, envHttpHeaders } = extractCodexHeaders({
"X-Api-Key": "${GOOGLE_API_KEY}",
});
expect(httpHeaders).toBeUndefined();
expect(envHttpHeaders).toEqual({ GOOGLE_API_KEY: "X-Api-Key" });
});

it("keeps static values in httpHeaders", () => {
const { httpHeaders, envHttpHeaders } = extractCodexHeaders({
Authorization: "Bearer tok",
});
expect(httpHeaders).toEqual({ Authorization: "Bearer tok" });
expect(envHttpHeaders).toBeUndefined();
});

it("keeps mixed values in httpHeaders as literal fallback", () => {
const { httpHeaders, envHttpHeaders } = extractCodexHeaders({
Authorization: "Bearer ${TOKEN}",
});
expect(httpHeaders).toEqual({ Authorization: "Bearer ${TOKEN}" });
expect(envHttpHeaders).toBeUndefined();
});

it("splits mixed and pure refs correctly", () => {
const { httpHeaders, envHttpHeaders } = extractCodexHeaders({
"X-Api-Key": "${API_KEY}",
Authorization: "Bearer tok",
"X-Mixed": "prefix ${VAR} suffix",
});
expect(envHttpHeaders).toEqual({ API_KEY: "X-Api-Key" });
expect(httpHeaders).toEqual({
Authorization: "Bearer tok",
"X-Mixed": "prefix ${VAR} suffix",
});
});

it("returns empty object for undefined input", () => {
const noHeaders: Record<string, string> | undefined = undefined;
expect(extractCodexHeaders(noHeaders)).toEqual({});
});
});
} from "./format.js";

describe("serializeMarkdownSubagent", () => {
it("serializes simple frontmatter fields", () => {
Expand Down
Loading
Loading