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
8 changes: 0 additions & 8 deletions docs/command-graph.html
Original file line number Diff line number Diff line change
Expand Up @@ -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 <profile>", "--assetType <assetType> (e.g., BOARD_V2)", "--packageKey <packageKey> (required with --nodeKey or --configuration)", "--nodeKey <nodeKey> (use with --packageKey)", "--configuration <configuration> (inline JSON, use with --packageKey)", "-f, --file <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 <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 <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 <profile>", "--path <path> (Skill path from 'skills list', e.g. platform/<skill> or asset/<assetType>/<skill>)", "--file <file> (Relative path of a reference file within the skill, defaults to SKILL.md)", "--output <output> (Destination directory, defaults to current working directory)", "-h, --help"] },

// config direct leaves (deprecated)
{ id: "config_list", label: "list", group: "command", path: "config list",
Expand Down Expand Up @@ -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"],
Expand Down
65 changes: 0 additions & 65 deletions docs/user-guide/asset-registry-commands.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,71 +149,6 @@ Options:
- `--assetType <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.

### 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 `<name> (<path>)` followed by ` - <description>` when the skill provides one. The `<path>` 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.

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 <path>` (required) – Skill path from `asset-registry skills list` (e.g. `platform/<skill>` or `asset/<assetType>/<skill>`).
- `--file <file>` – Relative path of a reference file within the skill. Defaults to `SKILL.md` when omitted.
- `--output <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:
Expand Down
32 changes: 1 addition & 31 deletions src/commands/asset-registry/asset-registry-api.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { HttpClient } from "../../core/http/http-client";
import { Context } from "../../core/command/cli-context";
import {
AgentSkillsResponse,
AssetRegistryDescriptor,
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";

private httpClient: () => HttpClient;
private readonly httpClient: () => HttpClient;

constructor(context: Context) {
this.httpClient = () => context.httpClient;
Expand All @@ -23,12 +21,6 @@ export class AssetRegistryApi {
.catch((e) => handleAssetRegistryApiError("listing asset registry types", e));
}

public async listSkills(): Promise<AgentSkillsResponse> {
return this.httpClient()
.get(AssetRegistryApi.endpointUrl("skills"))
.catch((e) => handleAssetRegistryApiError("listing asset registry skills", e));
}

public async getType(assetType: string): Promise<AssetRegistryDescriptor> {
return this.httpClient()
.get(AssetRegistryApi.endpointUrl("types", encodeURIComponent(assetType)))
Expand All @@ -53,29 +45,7 @@ export class AssetRegistryApi {
.catch((e) => handleAssetRegistryApiError(`validating asset type '${assetType}'`, e));
}

public async getSkillFile(skillPath: string, filePath?: string): Promise<Buffer> {
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("/");
}
21 changes: 0 additions & 21 deletions src/commands/asset-registry/asset-registry.interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,24 +42,3 @@ export interface ValidateOptions {
file?: string;
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;
output?: string;
}
60 changes: 3 additions & 57 deletions src/commands/asset-registry/asset-registry.service.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,12 @@
import { AssetRegistryApi } from "./asset-registry-api";
import { AgentSkill, 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;
private readonly api: AssetRegistryApi;

constructor(context: Context) {
this.api = new AssetRegistryApi(context);
Expand All @@ -33,46 +31,6 @@ export class AssetRegistryService {
}
}

public async getSkillFile(opts: GetSkillFileOptions): Promise<void> {
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 listSkills(jsonResponse: boolean): Promise<void> {
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<void> {
const descriptor = await this.api.getType(assetType);

Expand Down Expand Up @@ -109,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}`);
}

Expand Down Expand Up @@ -176,15 +131,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}`);
Expand Down
27 changes: 0 additions & 27 deletions src/commands/asset-registry/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,21 +41,6 @@ class Module extends IModule {
.option("-f, --file <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("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 <path>", "Skill path from 'skills list' (e.g. platform/<skill> or asset/<assetType>/<skill>)")
.option("--file <file>", "Relative path of a reference file within the skill (defaults to SKILL.md)")
.option("--output <output>", "Destination directory (defaults to current working directory)")
.action(this.getSkillFile);
}

private async listTypes(context: Context, command: Command, options: OptionValues): Promise<void> {
Expand Down Expand Up @@ -84,18 +69,6 @@ class Module extends IModule {
private async getExamples(context: Context, command: Command, options: OptionValues): Promise<void> {
await new AssetRegistryService(context).getExamples(options.assetType, !!options.json);
}

private async listSkills(context: Context, command: Command, options: OptionValues): Promise<void> {
await new AssetRegistryService(context).listSkills(!!options.json);
}

private async getSkillFile(context: Context, command: Command, options: OptionValues): Promise<void> {
await new AssetRegistryService(context).getSkillFile({
path: options.path,
file: options.file,
output: options.output,
});
}
}

export = Module;
8 changes: 0 additions & 8 deletions src/core/utils/file-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
3 changes: 0 additions & 3 deletions src/core/utils/path.ts

This file was deleted.

8 changes: 0 additions & 8 deletions tests/commands/asset-registry/asset-registry-error.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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", () => {
Expand Down Expand Up @@ -59,13 +58,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);
Expand Down
Loading
Loading