Spaces:
Running
Running
| import fs from "node:fs/promises"; | |
| import path from "node:path"; | |
| import type { IdentityConfig } from "../config/types.js"; | |
| import type { RuntimeEnv } from "../runtime.js"; | |
| import { resolveAgentWorkspaceDir, resolveDefaultAgentId } from "../agents/agent-scope.js"; | |
| import { identityHasValues, parseIdentityMarkdown } from "../agents/identity-file.js"; | |
| import { DEFAULT_IDENTITY_FILENAME } from "../agents/workspace.js"; | |
| import { writeConfigFile } from "../config/config.js"; | |
| import { logConfigUpdated } from "../config/logging.js"; | |
| import { normalizeAgentId } from "../routing/session-key.js"; | |
| import { defaultRuntime } from "../runtime.js"; | |
| import { resolveUserPath, shortenHomePath } from "../utils.js"; | |
| import { requireValidConfig } from "./agents.command-shared.js"; | |
| import { | |
| type AgentIdentity, | |
| findAgentEntryIndex, | |
| listAgentEntries, | |
| loadAgentIdentity, | |
| } from "./agents.config.js"; | |
| type AgentsSetIdentityOptions = { | |
| agent?: string; | |
| workspace?: string; | |
| identityFile?: string; | |
| name?: string; | |
| emoji?: string; | |
| theme?: string; | |
| avatar?: string; | |
| fromIdentity?: boolean; | |
| json?: boolean; | |
| }; | |
| const normalizeWorkspacePath = (input: string) => path.resolve(resolveUserPath(input)); | |
| const coerceTrimmed = (value?: string) => { | |
| const trimmed = value?.trim(); | |
| return trimmed ? trimmed : undefined; | |
| }; | |
| async function loadIdentityFromFile(filePath: string): Promise<AgentIdentity | null> { | |
| try { | |
| const content = await fs.readFile(filePath, "utf-8"); | |
| const parsed = parseIdentityMarkdown(content); | |
| if (!identityHasValues(parsed)) { | |
| return null; | |
| } | |
| return parsed; | |
| } catch { | |
| return null; | |
| } | |
| } | |
| function resolveAgentIdByWorkspace( | |
| cfg: Parameters<typeof resolveAgentWorkspaceDir>[0], | |
| workspaceDir: string, | |
| ): string[] { | |
| const list = listAgentEntries(cfg); | |
| const ids = | |
| list.length > 0 | |
| ? list.map((entry) => normalizeAgentId(entry.id)) | |
| : [resolveDefaultAgentId(cfg)]; | |
| const normalizedTarget = normalizeWorkspacePath(workspaceDir); | |
| return ids.filter( | |
| (id) => normalizeWorkspacePath(resolveAgentWorkspaceDir(cfg, id)) === normalizedTarget, | |
| ); | |
| } | |
| export async function agentsSetIdentityCommand( | |
| opts: AgentsSetIdentityOptions, | |
| runtime: RuntimeEnv = defaultRuntime, | |
| ) { | |
| const cfg = await requireValidConfig(runtime); | |
| if (!cfg) { | |
| return; | |
| } | |
| const agentRaw = coerceTrimmed(opts.agent); | |
| const nameRaw = coerceTrimmed(opts.name); | |
| const emojiRaw = coerceTrimmed(opts.emoji); | |
| const themeRaw = coerceTrimmed(opts.theme); | |
| const avatarRaw = coerceTrimmed(opts.avatar); | |
| const hasExplicitIdentity = Boolean(nameRaw || emojiRaw || themeRaw || avatarRaw); | |
| const identityFileRaw = coerceTrimmed(opts.identityFile); | |
| const workspaceRaw = coerceTrimmed(opts.workspace); | |
| const wantsIdentityFile = Boolean(opts.fromIdentity || identityFileRaw || !hasExplicitIdentity); | |
| let identityFilePath: string | undefined; | |
| let workspaceDir: string | undefined; | |
| if (identityFileRaw) { | |
| identityFilePath = normalizeWorkspacePath(identityFileRaw); | |
| workspaceDir = path.dirname(identityFilePath); | |
| } else if (workspaceRaw) { | |
| workspaceDir = normalizeWorkspacePath(workspaceRaw); | |
| } else if (wantsIdentityFile || !agentRaw) { | |
| workspaceDir = path.resolve(process.cwd()); | |
| } | |
| let agentId = agentRaw ? normalizeAgentId(agentRaw) : undefined; | |
| if (!agentId) { | |
| if (!workspaceDir) { | |
| runtime.error("Select an agent with --agent or provide a workspace via --workspace."); | |
| runtime.exit(1); | |
| return; | |
| } | |
| const matches = resolveAgentIdByWorkspace(cfg, workspaceDir); | |
| if (matches.length === 0) { | |
| runtime.error( | |
| `No agent workspace matches ${shortenHomePath(workspaceDir)}. Pass --agent to target a specific agent.`, | |
| ); | |
| runtime.exit(1); | |
| return; | |
| } | |
| if (matches.length > 1) { | |
| runtime.error( | |
| `Multiple agents match ${shortenHomePath(workspaceDir)}: ${matches.join(", ")}. Pass --agent to choose one.`, | |
| ); | |
| runtime.exit(1); | |
| return; | |
| } | |
| agentId = matches[0]; | |
| } | |
| let identityFromFile: AgentIdentity | null = null; | |
| if (wantsIdentityFile) { | |
| if (identityFilePath) { | |
| identityFromFile = await loadIdentityFromFile(identityFilePath); | |
| } else if (workspaceDir) { | |
| identityFromFile = loadAgentIdentity(workspaceDir); | |
| } | |
| if (!identityFromFile) { | |
| const targetPath = | |
| identityFilePath ?? | |
| (workspaceDir ? path.join(workspaceDir, DEFAULT_IDENTITY_FILENAME) : "IDENTITY.md"); | |
| runtime.error(`No identity data found in ${shortenHomePath(targetPath)}.`); | |
| runtime.exit(1); | |
| return; | |
| } | |
| } | |
| const fileTheme = | |
| identityFromFile?.theme ?? identityFromFile?.creature ?? identityFromFile?.vibe ?? undefined; | |
| const incomingIdentity: IdentityConfig = { | |
| ...(nameRaw || identityFromFile?.name ? { name: nameRaw ?? identityFromFile?.name } : {}), | |
| ...(emojiRaw || identityFromFile?.emoji ? { emoji: emojiRaw ?? identityFromFile?.emoji } : {}), | |
| ...(themeRaw || fileTheme ? { theme: themeRaw ?? fileTheme } : {}), | |
| ...(avatarRaw || identityFromFile?.avatar | |
| ? { avatar: avatarRaw ?? identityFromFile?.avatar } | |
| : {}), | |
| }; | |
| if ( | |
| !incomingIdentity.name && | |
| !incomingIdentity.emoji && | |
| !incomingIdentity.theme && | |
| !incomingIdentity.avatar | |
| ) { | |
| runtime.error( | |
| "No identity fields provided. Use --name/--emoji/--theme/--avatar or --from-identity.", | |
| ); | |
| runtime.exit(1); | |
| return; | |
| } | |
| const list = listAgentEntries(cfg); | |
| const index = findAgentEntryIndex(list, agentId); | |
| const base = index >= 0 ? list[index] : { id: agentId }; | |
| const nextIdentity: IdentityConfig = { | |
| ...base.identity, | |
| ...incomingIdentity, | |
| }; | |
| const nextEntry = { | |
| ...base, | |
| identity: nextIdentity, | |
| }; | |
| const nextList = [...list]; | |
| if (index >= 0) { | |
| nextList[index] = nextEntry; | |
| } else { | |
| const defaultId = normalizeAgentId(resolveDefaultAgentId(cfg)); | |
| if (nextList.length === 0 && agentId !== defaultId) { | |
| nextList.push({ id: defaultId }); | |
| } | |
| nextList.push(nextEntry); | |
| } | |
| const nextConfig = { | |
| ...cfg, | |
| agents: { | |
| ...cfg.agents, | |
| list: nextList, | |
| }, | |
| }; | |
| await writeConfigFile(nextConfig); | |
| if (opts.json) { | |
| runtime.log( | |
| JSON.stringify( | |
| { | |
| agentId, | |
| identity: nextIdentity, | |
| workspace: workspaceDir ?? null, | |
| identityFile: identityFilePath ?? null, | |
| }, | |
| null, | |
| 2, | |
| ), | |
| ); | |
| return; | |
| } | |
| logConfigUpdated(runtime); | |
| runtime.log(`Agent: ${agentId}`); | |
| if (nextIdentity.name) { | |
| runtime.log(`Name: ${nextIdentity.name}`); | |
| } | |
| if (nextIdentity.theme) { | |
| runtime.log(`Theme: ${nextIdentity.theme}`); | |
| } | |
| if (nextIdentity.emoji) { | |
| runtime.log(`Emoji: ${nextIdentity.emoji}`); | |
| } | |
| if (nextIdentity.avatar) { | |
| runtime.log(`Avatar: ${nextIdentity.avatar}`); | |
| } | |
| if (workspaceDir) { | |
| runtime.log(`Workspace: ${shortenHomePath(workspaceDir)}`); | |
| } | |
| } | |