From 57a8f262f85b6cea06e8b41495b1ecd798642378 Mon Sep 17 00:00:00 2001 From: Jing Sun Date: Tue, 28 Jul 2026 15:00:18 +0200 Subject: [PATCH 1/4] remove list asset-service skills list --- docs/user-guide/asset-registry-commands.md | 23 ------- .../asset-registry/asset-registry-api.ts | 7 --- .../asset-registry.interfaces.ts | 15 ----- .../asset-registry/asset-registry.service.ts | 29 +-------- src/commands/asset-registry/module.ts | 9 --- .../asset-registry-error.spec.ts | 7 --- .../asset-registry-skills-list.spec.ts | 62 ------------------- .../commands/asset-registry.spec.ts | 15 ----- 8 files changed, 1 insertion(+), 166 deletions(-) delete mode 100644 tests/commands/asset-registry/asset-registry-skills-list.spec.ts diff --git a/docs/user-guide/asset-registry-commands.md b/docs/user-guide/asset-registry-commands.md index 7d5e289a..94e623a3 100644 --- a/docs/user-guide/asset-registry-commands.md +++ b/docs/user-guide/asset-registry-commands.md @@ -153,29 +153,6 @@ Options: The asset registry also publishes agent skills (authored guidance for the platform and for specific asset types). Each skill exposes a `SKILL.md` and optional reference files. -### List Skills - -List all skills available on the platform. - -``` -content-cli asset-registry skills list -``` - -Example output: - -``` -content-cli-setup (platform/content-cli-setup) - Install content-cli and create a profile against a Celonis team. -asset-studio-board-v2 (asset/BOARD_V2/asset-studio-board-v2) - Authoring one Celonis Studio view asset of type BOARD_V2. -``` - -Each line is ` ()` followed by ` - ` when the skill provides one. The `` value is what you pass to `skills get --path`. - -Use `--json` to write the full response to a JSON file in the working directory: - -``` -content-cli asset-registry skills list --json -``` - ### Download a Skill File Download a skill's `SKILL.md` (or a specific reference file) to the local filesystem. The Studio MCP server remains the recommended source for live agent use; this command is a fetch/inspect utility for environments without the MCP server, for offline review, or for vendoring a copy into a repo. diff --git a/src/commands/asset-registry/asset-registry-api.ts b/src/commands/asset-registry/asset-registry-api.ts index 1a505240..325db59d 100644 --- a/src/commands/asset-registry/asset-registry-api.ts +++ b/src/commands/asset-registry/asset-registry-api.ts @@ -1,7 +1,6 @@ import { HttpClient } from "../../core/http/http-client"; import { Context } from "../../core/command/cli-context"; import { - AgentSkillsResponse, AssetRegistryDescriptor, AssetRegistryMetadata, } from "./asset-registry.interfaces"; @@ -23,12 +22,6 @@ export class AssetRegistryApi { .catch((e) => handleAssetRegistryApiError("listing asset registry types", e)); } - public async listSkills(): Promise { - return this.httpClient() - .get(AssetRegistryApi.endpointUrl("skills")) - .catch((e) => handleAssetRegistryApiError("listing asset registry skills", e)); - } - public async getType(assetType: string): Promise { return this.httpClient() .get(AssetRegistryApi.endpointUrl("types", encodeURIComponent(assetType))) diff --git a/src/commands/asset-registry/asset-registry.interfaces.ts b/src/commands/asset-registry/asset-registry.interfaces.ts index baf0c547..498f9209 100644 --- a/src/commands/asset-registry/asset-registry.interfaces.ts +++ b/src/commands/asset-registry/asset-registry.interfaces.ts @@ -43,21 +43,6 @@ export interface ValidateOptions { json: boolean; } -export interface AgentSkillMetadata { - version: string; -} - -export interface AgentSkill { - name: string; - description: string; - path: string; - metadata: AgentSkillMetadata; -} - -export interface AgentSkillsResponse { - skills: AgentSkill[]; -} - export interface GetSkillFileOptions { path: string; file?: string; diff --git a/src/commands/asset-registry/asset-registry.service.ts b/src/commands/asset-registry/asset-registry.service.ts index 2890e0d2..9ca4f091 100644 --- a/src/commands/asset-registry/asset-registry.service.ts +++ b/src/commands/asset-registry/asset-registry.service.ts @@ -1,5 +1,5 @@ import { AssetRegistryApi } from "./asset-registry-api"; -import { AgentSkill, AssetRegistryDescriptor, GetSkillFileOptions, ValidateOptions } from "./asset-registry.interfaces"; +import { AssetRegistryDescriptor, GetSkillFileOptions, ValidateOptions } from "./asset-registry.interfaces"; import { Context } from "../../core/command/cli-context"; import { fileService, FileService } from "../../core/utils/file-service"; import { FatalError, logger } from "../../core/utils/logger"; @@ -55,24 +55,6 @@ export class AssetRegistryService { return base; } - public async listSkills(jsonResponse: boolean): Promise { - const response = await this.api.listSkills(); - - if (jsonResponse) { - const filename = uuidv4() + ".json"; - fileService.writeToFileWithGivenName(JSON.stringify(response), filename); - logger.info(FileService.fileDownloadedMessage + filename); - } else { - if (response.skills.length === 0) { - logger.info("No agent skills registered."); - return; - } - response.skills.forEach((skill) => { - this.logSkillSummary(skill); - }); - } - } - public async getType(assetType: string, jsonResponse: boolean): Promise { const descriptor = await this.api.getType(assetType); @@ -176,15 +158,6 @@ export class AssetRegistryService { } } - private logSkillSummary(skill: AgentSkill): void { - const base = `${skill.name} (${skill.path})`; - if (skill.description) { - logger.info(`${base} - ${skill.description}`); - } else { - logger.info(base); - } - } - private logDescriptorDetail(descriptor: AssetRegistryDescriptor): void { logger.info(`Asset Type: ${descriptor.assetType}`); logger.info(`Display Name: ${descriptor.displayName}`); diff --git a/src/commands/asset-registry/module.ts b/src/commands/asset-registry/module.ts index 3de46aa2..7791a54e 100644 --- a/src/commands/asset-registry/module.ts +++ b/src/commands/asset-registry/module.ts @@ -45,11 +45,6 @@ class Module extends IModule { const skillsCommand = assetRegistryCommand.command("skills") .description("Discover agent skills exposed by the asset registry"); - skillsCommand.command("list") - .description("List all available agent skills (name, description, path)") - .option("--json", "Return the response as a JSON file") - .action(this.listSkills); - skillsCommand.command("get") .description("Download a skill file (defaults to SKILL.md)") .requiredOption("--path ", "Skill path from 'skills list' (e.g. platform/ or asset//)") @@ -85,10 +80,6 @@ class Module extends IModule { await new AssetRegistryService(context).getExamples(options.assetType, !!options.json); } - private async listSkills(context: Context, command: Command, options: OptionValues): Promise { - await new AssetRegistryService(context).listSkills(!!options.json); - } - private async getSkillFile(context: Context, command: Command, options: OptionValues): Promise { await new AssetRegistryService(context).getSkillFile({ path: options.path, diff --git a/tests/commands/asset-registry/asset-registry-error.spec.ts b/tests/commands/asset-registry/asset-registry-error.spec.ts index 50decc23..dce10446 100644 --- a/tests/commands/asset-registry/asset-registry-error.spec.ts +++ b/tests/commands/asset-registry/asset-registry-error.spec.ts @@ -59,13 +59,6 @@ describe("Asset registry error handling", () => { .rejects.toThrow(new FatalError(ASSET_REGISTRY_DISABLED_USER_MESSAGE)); }); - it("Should surface the friendly message when listing skills and the feature flag is disabled", async () => { - mockAxiosGetError(SKILLS_URL, 403, { error: ASSET_REGISTRY_DISABLED_ERROR }); - - await expect(new AssetRegistryService(testContext).listSkills(false)) - .rejects.toThrow(new FatalError(ASSET_REGISTRY_DISABLED_USER_MESSAGE)); - }); - it("Should surface a generic error for other 403 responses", async () => { const errorBody = { error: "Access denied" }; mockAxiosGetError(TYPES_URL, 403, errorBody); diff --git a/tests/commands/asset-registry/asset-registry-skills-list.spec.ts b/tests/commands/asset-registry/asset-registry-skills-list.spec.ts deleted file mode 100644 index 8e0a40be..00000000 --- a/tests/commands/asset-registry/asset-registry-skills-list.spec.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { AgentSkillsResponse } from "../../../src/commands/asset-registry/asset-registry.interfaces"; -import { mockAxiosGet } from "../../utls/http-requests-mock"; -import { AssetRegistryService } from "../../../src/commands/asset-registry/asset-registry.service"; -import { testContext } from "../../utls/test-context"; -import { loggingTestTransport } from "../../jest.setup"; -import { getJsonFromDownloadedFile } from "../../utls/fs-utils"; - -describe("Asset registry skills list", () => { - const skillsResponse: AgentSkillsResponse = { - skills: [ - { - name: "skill-one", - description: "First platform skill", - path: "platform/skill-one", - metadata: { version: "1.0.0" }, - }, - { - name: "board-authoring", - description: "", - path: "asset/BOARD_V2/board-authoring", - metadata: { version: "2.0.0" }, - }, - ], - }; - - it("Should list all skills with description when present", async () => { - mockAxiosGet("https://myTeam.celonis.cloud/pacman/api/core/asset-registry/skills", skillsResponse); - - await new AssetRegistryService(testContext).listSkills(false); - - expect(loggingTestTransport.logMessages.length).toBe(2); - - expect(loggingTestTransport.logMessages[0].message).toContain( - "skill-one (platform/skill-one) - First platform skill" - ); - - expect(loggingTestTransport.logMessages[1].message).toContain( - "board-authoring (asset/BOARD_V2/board-authoring)" - ); - expect(loggingTestTransport.logMessages[1].message).not.toMatch(/ - /); - }); - - it("Should list all skills as JSON", async () => { - mockAxiosGet("https://myTeam.celonis.cloud/pacman/api/core/asset-registry/skills", skillsResponse); - - await new AssetRegistryService(testContext).listSkills(true); - - const written = getJsonFromDownloadedFile() as AgentSkillsResponse; - expect(written.skills.length).toBe(2); - expect(written.skills[0].name).toBe("skill-one"); - expect(written.skills[1].path).toBe("asset/BOARD_V2/board-authoring"); - }); - - it("Should handle empty skills list", async () => { - mockAxiosGet("https://myTeam.celonis.cloud/pacman/api/core/asset-registry/skills", { skills: [] }); - - await new AssetRegistryService(testContext).listSkills(false); - - expect(loggingTestTransport.logMessages.length).toBe(1); - expect(loggingTestTransport.logMessages[0].message).toContain("No agent skills registered"); - }); -}); diff --git a/tests/integration/commands/asset-registry.spec.ts b/tests/integration/commands/asset-registry.spec.ts index 0f8c39ee..f3014e1d 100644 --- a/tests/integration/commands/asset-registry.spec.ts +++ b/tests/integration/commands/asset-registry.spec.ts @@ -11,7 +11,6 @@ describe("asset-registry command integration", () => { beforeEach(() => { mockService = { listTypes: jest.fn().mockResolvedValue(undefined), - listSkills: jest.fn().mockResolvedValue(undefined), getType: jest.fn().mockResolvedValue(undefined), getSchema: jest.fn().mockResolvedValue(undefined), validate: jest.fn().mockResolvedValue(undefined), @@ -123,20 +122,6 @@ describe("asset-registry command integration", () => { }); }); - describe("asset-registry skills list", () => { - it("calls listSkills with --json", async () => { - const result = await runCli(["asset-registry", "skills", "list", "--json"]); - - expect(result.exitCode).toBe(0); - expect(mockService.listSkills).toHaveBeenCalledWith(true); - }); - - it("calls listSkills without --json", async () => { - await runCli(["asset-registry", "skills", "list"]); - expect(mockService.listSkills).toHaveBeenCalledWith(false); - }); - }); - describe("asset-registry get", () => { it("calls getType with the requested assetType", async () => { const result = await runCli(["asset-registry", "get", "--assetType", "BOARD_V2"]); From 730885ac8a09fd0b6210c51ee31218dc8574a01e Mon Sep 17 00:00:00 2001 From: Jing Sun Date: Tue, 28 Jul 2026 15:09:18 +0200 Subject: [PATCH 2/4] remove asset-registry skills get --- docs/user-guide/asset-registry-commands.md | 42 ----- .../asset-registry/asset-registry-api.ts | 23 --- .../asset-registry.interfaces.ts | 6 - .../asset-registry/asset-registry.service.ts | 26 +-- src/commands/asset-registry/module.ts | 18 -- src/core/utils/file-service.ts | 8 - src/core/utils/path.ts | 3 - .../asset-registry-skills-get.spec.ts | 164 ------------------ 8 files changed, 1 insertion(+), 289 deletions(-) delete mode 100644 src/core/utils/path.ts delete mode 100644 tests/commands/asset-registry/asset-registry-skills-get.spec.ts diff --git a/docs/user-guide/asset-registry-commands.md b/docs/user-guide/asset-registry-commands.md index 94e623a3..c333a551 100644 --- a/docs/user-guide/asset-registry-commands.md +++ b/docs/user-guide/asset-registry-commands.md @@ -149,48 +149,6 @@ Options: - `--assetType ` (required) – The asset type identifier - `--json` – Write the examples to a JSON file in the working directory -## Skills - -The asset registry also publishes agent skills (authored guidance for the platform and for specific asset types). Each skill exposes a `SKILL.md` and optional reference files. - -### Download a Skill File - -Download a skill's `SKILL.md` (or a specific reference file) to the local filesystem. The Studio MCP server remains the recommended source for live agent use; this command is a fetch/inspect utility for environments without the MCP server, for offline review, or for vendoring a copy into a repo. - -Download the default `SKILL.md` for a platform skill: - -``` -content-cli asset-registry skills get --path platform/content-cli-setup -``` - -Download a `SKILL.md` for an asset skill: - -``` -content-cli asset-registry skills get --path asset/BOARD_V2/asset-studio-board-v2 -``` - -Download a specific reference file and write into a target directory: - -``` -content-cli asset-registry skills get \ - --path asset/BOARD_V2/asset-studio-board-v2 \ - --file refs/example.md \ - --output ./skills -``` - -Options: - -- `--path ` (required) – Skill path from `asset-registry skills list` (e.g. `platform/` or `asset//`). -- `--file ` – Relative path of a reference file within the skill. Defaults to `SKILL.md` when omitted. -- `--output ` – Destination directory. Defaults to the current working directory. Created automatically if it does not exist. - -Behavior: - -- The local filename is the basename of `--file` (or `SKILL.md` when `--file` is omitted). Subdirectories in `--file` are not preserved on the local side. -- Re-running the command overwrites the existing local file without prompting. -- On success the command logs a single confirmation line with the absolute path of the written file. -- A missing skill or file returns a clear error such as `Problem getting SKILL.md for 'platform/missing': ...`. - ## Troubleshooting If the asset registry is disabled on your team, commands fail with: diff --git a/src/commands/asset-registry/asset-registry-api.ts b/src/commands/asset-registry/asset-registry-api.ts index 325db59d..bc797842 100644 --- a/src/commands/asset-registry/asset-registry-api.ts +++ b/src/commands/asset-registry/asset-registry-api.ts @@ -5,7 +5,6 @@ import { AssetRegistryMetadata, } from "./asset-registry.interfaces"; import { handleAssetRegistryApiError } from "./asset-registry-error"; -import { trimSlashes } from "../../core/utils/path"; export class AssetRegistryApi { private static readonly BASE_URL = "/pacman/api/core/asset-registry"; @@ -46,29 +45,7 @@ export class AssetRegistryApi { .catch((e) => handleAssetRegistryApiError(`validating asset type '${assetType}'`, e)); } - public async getSkillFile(skillPath: string, filePath?: string): Promise { - const operation = filePath - ? `getting skill file '${filePath}' for '${skillPath}'` - : `getting SKILL.md for '${skillPath}'`; - const url = this.buildSkillFileUrl(skillPath, filePath); - return this.httpClient() - .getFile(url) - .catch((e) => handleAssetRegistryApiError(operation, e)); - } - - private buildSkillFileUrl(skillPath: string, filePath?: string): string { - const segments = ["skills", encodePathSegments(skillPath)]; - if (filePath) { - segments.push(encodePathSegments(filePath)); - } - return AssetRegistryApi.endpointUrl(...segments); - } - private static endpointUrl(...segments: string[]): string { return `${AssetRegistryApi.BASE_URL}/${segments.join("/")}`; } } - -function encodePathSegments(value: string): string { - return trimSlashes(value).split("/").map(encodeURIComponent).join("/"); -} diff --git a/src/commands/asset-registry/asset-registry.interfaces.ts b/src/commands/asset-registry/asset-registry.interfaces.ts index 498f9209..830570be 100644 --- a/src/commands/asset-registry/asset-registry.interfaces.ts +++ b/src/commands/asset-registry/asset-registry.interfaces.ts @@ -42,9 +42,3 @@ export interface ValidateOptions { file?: string; json: boolean; } - -export interface GetSkillFileOptions { - path: string; - file?: string; - output?: string; -} diff --git a/src/commands/asset-registry/asset-registry.service.ts b/src/commands/asset-registry/asset-registry.service.ts index 9ca4f091..8265d21a 100644 --- a/src/commands/asset-registry/asset-registry.service.ts +++ b/src/commands/asset-registry/asset-registry.service.ts @@ -1,11 +1,9 @@ import { AssetRegistryApi } from "./asset-registry-api"; -import { AssetRegistryDescriptor, GetSkillFileOptions, ValidateOptions } from "./asset-registry.interfaces"; +import { AssetRegistryDescriptor, ValidateOptions } from "./asset-registry.interfaces"; import { Context } from "../../core/command/cli-context"; import { fileService, FileService } from "../../core/utils/file-service"; import { FatalError, logger } from "../../core/utils/logger"; -import { trimSlashes } from "../../core/utils/path"; import { v4 as uuidv4 } from "uuid"; -import * as path from "node:path"; export class AssetRegistryService { private api: AssetRegistryApi; @@ -33,28 +31,6 @@ export class AssetRegistryService { } } - public async getSkillFile(opts: GetSkillFileOptions): Promise { - const filename = this.resolveLocalFilename(opts.file); - const targetDir = opts.output ?? "."; - - const buffer = await this.api.getSkillFile(opts.path, opts.file); - const absolutePath = fileService.writeBufferToPath(targetDir, filename, buffer); - - logger.info(FileService.fileDownloadedMessage + absolutePath); - } - - private resolveLocalFilename(file?: string): string { - if (!file) { - return "SKILL.md"; - } - const trimmed = trimSlashes(file); - const base = trimmed ? path.basename(trimmed) : ""; - if (!base) { - throw new FatalError(`--file must point to a file, got '${file}'.`); - } - return base; - } - public async getType(assetType: string, jsonResponse: boolean): Promise { const descriptor = await this.api.getType(assetType); diff --git a/src/commands/asset-registry/module.ts b/src/commands/asset-registry/module.ts index 7791a54e..2ebcd031 100644 --- a/src/commands/asset-registry/module.ts +++ b/src/commands/asset-registry/module.ts @@ -41,16 +41,6 @@ class Module extends IModule { .option("-f, --file ", "Path to a JSON file containing a full ValidateRequest body. Mutually exclusive with the build-from-options flags.") .option("--json", "Return the response as a JSON file") .action(this.validate); - - const skillsCommand = assetRegistryCommand.command("skills") - .description("Discover agent skills exposed by the asset registry"); - - skillsCommand.command("get") - .description("Download a skill file (defaults to SKILL.md)") - .requiredOption("--path ", "Skill path from 'skills list' (e.g. platform/ or asset//)") - .option("--file ", "Relative path of a reference file within the skill (defaults to SKILL.md)") - .option("--output ", "Destination directory (defaults to current working directory)") - .action(this.getSkillFile); } private async listTypes(context: Context, command: Command, options: OptionValues): Promise { @@ -79,14 +69,6 @@ class Module extends IModule { private async getExamples(context: Context, command: Command, options: OptionValues): Promise { await new AssetRegistryService(context).getExamples(options.assetType, !!options.json); } - - private async getSkillFile(context: Context, command: Command, options: OptionValues): Promise { - await new AssetRegistryService(context).getSkillFile({ - path: options.path, - file: options.file, - output: options.output, - }); - } } export = Module; diff --git a/src/core/utils/file-service.ts b/src/core/utils/file-service.ts index 5b305505..40619c14 100644 --- a/src/core/utils/file-service.ts +++ b/src/core/utils/file-service.ts @@ -22,14 +22,6 @@ export class FileService { }); } - public writeBufferToPath(targetDir: string, filename: string, data: Buffer): string { - const resolvedDir = path.resolve(process.cwd(), targetDir); - const absolutePath = path.join(resolvedDir, filename); - this.mkdirRecursive(resolvedDir); - this.writeBufferToFileWithGivenName(data, absolutePath); - return absolutePath; - } - public extractZipBufferToDirectory(data: Buffer, targetDir: string): void { const targetPath = path.resolve(process.cwd(), targetDir); this.mkdirRecursive(targetPath); diff --git a/src/core/utils/path.ts b/src/core/utils/path.ts deleted file mode 100644 index ad06bde4..00000000 --- a/src/core/utils/path.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function trimSlashes(value: string): string { - return value.replace(/^\/+/, "").replace(/\/+$/, ""); -} diff --git a/tests/commands/asset-registry/asset-registry-skills-get.spec.ts b/tests/commands/asset-registry/asset-registry-skills-get.spec.ts deleted file mode 100644 index 2b2d202d..00000000 --- a/tests/commands/asset-registry/asset-registry-skills-get.spec.ts +++ /dev/null @@ -1,164 +0,0 @@ -import * as fs from "node:fs"; -import * as path from "path"; -import { mockAxiosGet, mockAxiosGetError } from "../../utls/http-requests-mock"; -import { AssetRegistryService } from "../../../src/commands/asset-registry/asset-registry.service"; -import { testContext } from "../../utls/test-context"; -import { loggingTestTransport } from "../../jest.setup"; -import { FatalError } from "../../../src/core/utils/logger"; -import { FileService } from "../../../src/core/utils/file-service"; -import { uniqueDirName } from "../../utls/fs-utils"; - -const SKILLS_BASE_URL = "https://myTeam.celonis.cloud/pacman/api/core/asset-registry/skills"; - -describe("Asset registry skills get", () => { - const skillContent = Buffer.from("# Hello SKILL\n\nLine 2.\n", "utf-8"); - - function absoluteOutputDir(outputDir: string): string { - return path.resolve(process.cwd(), outputDir); - } - - it("Should download SKILL.md by default for a platform skill", async () => { - mockAxiosGet(`${SKILLS_BASE_URL}/platform/foo`, skillContent); - const output = uniqueDirName(); - - await new AssetRegistryService(testContext).getSkillFile({ - path: "platform/foo", - output, - }); - - const written = path.join(absoluteOutputDir(output), "SKILL.md"); - expect(fs.existsSync(written)).toBe(true); - expect(fs.readFileSync(written).equals(skillContent)).toBe(true); - - expect(loggingTestTransport.logMessages).toHaveLength(1); - expect(loggingTestTransport.logMessages[0].message).toContain( - FileService.fileDownloadedMessage + written - ); - }); - - it("Should download SKILL.md by default for an asset skill (multi-segment path)", async () => { - mockAxiosGet(`${SKILLS_BASE_URL}/asset/BOARD_V2/board-authoring`, skillContent); - const output = uniqueDirName(); - - await new AssetRegistryService(testContext).getSkillFile({ - path: "asset/BOARD_V2/board-authoring", - output, - }); - - const written = path.join(absoluteOutputDir(output), "SKILL.md"); - expect(fs.existsSync(written)).toBe(true); - expect(fs.readFileSync(written).equals(skillContent)).toBe(true); - }); - - it("Should write a reference file using only its basename (strips --file subdirs)", async () => { - const refContent = Buffer.from("ref content", "utf-8"); - mockAxiosGet(`${SKILLS_BASE_URL}/platform/foo/refs/style.md`, refContent); - const output = uniqueDirName(); - - await new AssetRegistryService(testContext).getSkillFile({ - path: "platform/foo", - file: "refs/style.md", - output, - }); - - const written = path.join(absoluteOutputDir(output), "style.md"); - expect(fs.existsSync(written)).toBe(true); - expect(fs.readFileSync(written).equals(refContent)).toBe(true); - - const subdirWritten = path.join(absoluteOutputDir(output), "refs", "style.md"); - expect(fs.existsSync(subdirWritten)).toBe(false); - }); - - it("Should create the --output directory if it does not exist", async () => { - mockAxiosGet(`${SKILLS_BASE_URL}/platform/foo`, skillContent); - const output = path.join(uniqueDirName(), "nested", "deep"); - expect(fs.existsSync(absoluteOutputDir(output))).toBe(false); - - await new AssetRegistryService(testContext).getSkillFile({ - path: "platform/foo", - output, - }); - - expect(fs.existsSync(absoluteOutputDir(output))).toBe(true); - const written = path.join(absoluteOutputDir(output), "SKILL.md"); - expect(fs.existsSync(written)).toBe(true); - expect(fs.readFileSync(written).equals(skillContent)).toBe(true); - }); - - it("Should overwrite an existing local file", async () => { - const newContent = Buffer.from("NEW", "utf-8"); - mockAxiosGet(`${SKILLS_BASE_URL}/platform/foo`, newContent); - const output = uniqueDirName(); - fs.mkdirSync(absoluteOutputDir(output), { recursive: true }); - const target = path.join(absoluteOutputDir(output), "SKILL.md"); - fs.writeFileSync(target, "OLD"); - - await new AssetRegistryService(testContext).getSkillFile({ - path: "platform/foo", - output, - }); - - expect(fs.readFileSync(target).equals(newContent)).toBe(true); - }); - - it("Should default --output to the current working directory", async () => { - mockAxiosGet(`${SKILLS_BASE_URL}/platform/cwd-default`, skillContent); - - await new AssetRegistryService(testContext).getSkillFile({ - path: "platform/cwd-default", - }); - - const written = path.join(process.cwd(), "SKILL.md"); - expect(fs.existsSync(written)).toBe(true); - expect(fs.readFileSync(written).equals(skillContent)).toBe(true); - }); - - it("Should URI-encode path segments while preserving slashes", async () => { - const url = `${SKILLS_BASE_URL}/asset/BOARD_V2/${encodeURIComponent("with space")}`; - mockAxiosGet(url, skillContent); - const output = uniqueDirName(); - - await new AssetRegistryService(testContext).getSkillFile({ - path: "asset/BOARD_V2/with space", - output, - }); - - const written = path.join(absoluteOutputDir(output), "SKILL.md"); - expect(fs.existsSync(written)).toBe(true); - }); - - it("Should surface a clear FatalError when the backend returns 404 for SKILL.md", async () => { - mockAxiosGetError(`${SKILLS_BASE_URL}/platform/missing`, 404, { error: "Skill not found" }); - - await expect( - new AssetRegistryService(testContext).getSkillFile({ - path: "platform/missing", - output: uniqueDirName(), - }) - ).rejects.toThrow(/Problem getting SKILL\.md for 'platform\/missing':/); - }); - - it("Should surface a clear FatalError when the backend returns 404 for a reference file", async () => { - mockAxiosGetError(`${SKILLS_BASE_URL}/platform/foo/refs/missing.md`, 404, { - error: "File not found", - }); - - await expect( - new AssetRegistryService(testContext).getSkillFile({ - path: "platform/foo", - file: "refs/missing.md", - output: uniqueDirName(), - }) - ).rejects.toThrow(/Problem getting skill file 'refs\/missing\.md' for 'platform\/foo':/); - }); - - it("Should throw a synchronous FatalError when --file resolves to an empty basename", async () => { - await expect( - new AssetRegistryService(testContext).getSkillFile({ - path: "platform/foo", - file: "/", - output: uniqueDirName(), - }) - ).rejects.toThrow(new FatalError("--file must point to a file, got '/'.")); - }); -}); From b2ff4c0666b8832a47324a083deb7b0ba9da91c4 Mon Sep 17 00:00:00 2001 From: Jing Sun Date: Wed, 29 Jul 2026 10:08:16 +0200 Subject: [PATCH 3/4] make sonar happy --- src/commands/asset-registry/asset-registry-api.ts | 2 +- src/commands/asset-registry/asset-registry.service.ts | 7 ++----- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/src/commands/asset-registry/asset-registry-api.ts b/src/commands/asset-registry/asset-registry-api.ts index bc797842..64b97ba0 100644 --- a/src/commands/asset-registry/asset-registry-api.ts +++ b/src/commands/asset-registry/asset-registry-api.ts @@ -9,7 +9,7 @@ import { handleAssetRegistryApiError } from "./asset-registry-error"; export class AssetRegistryApi { private static readonly BASE_URL = "/pacman/api/core/asset-registry"; - private httpClient: () => HttpClient; + private readonly httpClient: () => HttpClient; constructor(context: Context) { this.httpClient = () => context.httpClient; diff --git a/src/commands/asset-registry/asset-registry.service.ts b/src/commands/asset-registry/asset-registry.service.ts index 8265d21a..d850193a 100644 --- a/src/commands/asset-registry/asset-registry.service.ts +++ b/src/commands/asset-registry/asset-registry.service.ts @@ -6,7 +6,7 @@ import { FatalError, logger } from "../../core/utils/logger"; import { v4 as uuidv4 } from "uuid"; export class AssetRegistryService { - private api: AssetRegistryApi; + private readonly api: AssetRegistryApi; constructor(context: Context) { this.api = new AssetRegistryApi(context); @@ -67,13 +67,10 @@ export class AssetRegistryService { const hasFile = !!opts.file; if (hasFile && (hasNodeKey || hasConfig || !!opts.packageKey)) { - throw new FatalError( - "Option -f is mutually exclusive with --packageKey, --nodeKey and --configuration." - ); + throw new FatalError("Option -f is mutually exclusive with --packageKey, --nodeKey and --configuration."); } if (hasFile) { - return this.parseJson(fileService.readFile(opts.file), `-f ${opts.file}`); } From f60efb4f356de9b7416ceea07d4cdb072c79bc7b Mon Sep 17 00:00:00 2001 From: Jing Sun Date: Wed, 29 Jul 2026 14:01:33 +0200 Subject: [PATCH 4/4] update command graph --- docs/command-graph.html | 8 -------- .../commands/asset-registry/asset-registry-error.spec.ts | 1 - 2 files changed, 9 deletions(-) diff --git a/docs/command-graph.html b/docs/command-graph.html index 2245c415..64714134 100644 --- a/docs/command-graph.html +++ b/docs/command-graph.html @@ -273,13 +273,6 @@ { id: "ar_validate", label: "validate", group: "command", path: "asset-registry validate", description: "Validate asset configuration against the asset service's validate endpoint.", options: ["-p, --profile ", "--assetType (e.g., BOARD_V2)", "--packageKey (required with --nodeKey or --configuration)", "--nodeKey (use with --packageKey)", "--configuration (inline JSON, use with --packageKey)", "-f, --file (full ValidateRequest body; mutually exclusive with build-from-options flags)", "--json", "-h, --help"] }, - { id: "ar_skills_area", label: "skills", group: "subarea", path: "asset-registry skills", - description: "Discover agent skills exposed by the asset registry", options: ["-p, --profile ", "-h, --help"] }, - { id: "ar_skills_list", label: "list", group: "command", path: "asset-registry skills list", - description: "List all available agent skills (name, description, path)", options: ["-p, --profile ", "--json", "-h, --help"] }, - { id: "ar_skills_get", label: "get", group: "command", path: "asset-registry skills get", - description: "Download a skill file (defaults to SKILL.md)", - options: ["-p, --profile ", "--path (Skill path from 'skills list', e.g. platform/ or asset//)", "--file (Relative path of a reference file within the skill, defaults to SKILL.md)", "--output (Destination directory, defaults to current working directory)", "-h, --help"] }, // config direct leaves (deprecated) { id: "config_list", label: "list", group: "command", path: "config list", @@ -464,7 +457,6 @@ ["area_asset_registry","ar_list"],["area_asset_registry","ar_get"],["area_asset_registry","ar_schema"], ["area_asset_registry","ar_examples"],["area_asset_registry","ar_validate"], - ["area_asset_registry","ar_skills_area"],["ar_skills_area","ar_skills_list"],["ar_skills_area","ar_skills_get"], ["area_config","config_list"],["area_config","config_export"],["area_config","config_import"], ["area_config","config_validate"],["area_config","config_diff"], diff --git a/tests/commands/asset-registry/asset-registry-error.spec.ts b/tests/commands/asset-registry/asset-registry-error.spec.ts index dce10446..d7315644 100644 --- a/tests/commands/asset-registry/asset-registry-error.spec.ts +++ b/tests/commands/asset-registry/asset-registry-error.spec.ts @@ -9,7 +9,6 @@ import { AssetRegistryService } from "../../../src/commands/asset-registry/asset import { testContext } from "../../utls/test-context"; const TYPES_URL = "https://myTeam.celonis.cloud/pacman/api/core/asset-registry/types"; -const SKILLS_URL = "https://myTeam.celonis.cloud/pacman/api/core/asset-registry/skills"; describe("Asset registry error handling", () => { describe("handleAssetRegistryApiError", () => {