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
5 changes: 5 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ The root CLI currently exposes these business areas:
- `moment`
- `comment`
- `notification`
- `shop`
- `completion`

## Setup Commands
Expand Down Expand Up @@ -216,6 +217,10 @@ helpers.
reply in console context.
- `notification get` filters the authenticated user's notification list by
`metadata.name`.
- `shop` targets the Halo Pro/Commercial ecommerce Console API group
`console.api.ecommerce.halo.run/v1alpha1`. The published
`@halo-dev/api-client` package does not include these APIs, so the command area
uses manual `axios` requests through `src/commands/shop/api.ts`.

## Pull Request Checklist

Expand Down
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ Commands:
moment Moment management commands
comment Comment management commands
notification Notification management commands
shop Shop management commands (Halo Pro/Commercial)

For more info, run any command with the `--help` flag:
$ halo auth --help
Expand All @@ -118,6 +119,7 @@ For more info, run any command with the `--help` flag:
$ halo moment --help
$ halo comment --help
$ halo notification --help
$ halo shop --help

Options:
-h, --help Display this message
Expand Down Expand Up @@ -174,6 +176,7 @@ Halo CLI currently includes these command groups:
- `moment`
- `comment`
- `notification`
- `shop` — Halo Pro/Commercial shop management

For details on any command, use `--help`.

Expand All @@ -190,6 +193,7 @@ Included skills:
- `halo-cli-search`
- `halo-cli-operations`
- `halo-cli-moderation-notifications`
- `halo-cli-shop`

Add the skills with:

Expand Down
4 changes: 4 additions & 0 deletions README.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -105,6 +105,7 @@ Commands:
moment Moment management commands
comment Comment management commands
notification Notification management commands
shop Shop management commands (Halo Pro/Commercial)

For more info, run any command with the `--help` flag:
$ halo auth --help
Expand All @@ -118,6 +119,7 @@ For more info, run any command with the `--help` flag:
$ halo moment --help
$ halo comment --help
$ halo notification --help
$ halo shop --help

Options:
-h, --help Display this message
Expand Down Expand Up @@ -174,6 +176,7 @@ halo comment reply <TAB>
- `moment`
- `comment`
- `notification`
- `shop` — Halo Pro/Commercial 商城管理

更多细节请使用 `--help` 查看。

Expand All @@ -190,6 +193,7 @@ halo comment reply <TAB>
- `halo-cli-search`
- `halo-cli-operations`
- `halo-cli-moderation-notifications`
- `halo-cli-shop`

可以通过下面的方式添加 skills:

Expand Down
1 change: 1 addition & 0 deletions skills/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Recommended loading order for agents:
- `halo-cli-search`
- `halo-cli-operations`
- `halo-cli-moderation-notifications`
- `halo-cli-shop`

Design goals:

Expand Down
2 changes: 2 additions & 0 deletions skills/halo-cli-shared/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ halo --help
- `moment`
- `comment`
- `notification`
- `shop` — Halo Pro/Commercial shop management
- `completion <shell>` — Generate shell completion script (bash, zsh, fish, powershell)

## Shared Rules
Expand Down Expand Up @@ -86,3 +87,4 @@ Credentials are stored in the system keyring.
- Use `halo-cli-search` for public site search.
- Use `halo-cli-operations` for themes, plugins, attachments, backups, and moments.
- Use `halo-cli-moderation-notifications` for comments, replies, and notifications.
- Use `halo-cli-shop` for products, orders, customers, coupons, discounts, shop statistics, and shop configuration.
164 changes: 164 additions & 0 deletions skills/halo-cli-shop/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
---
name: halo-cli-shop
version: 1.0.0
description: Use when managing a Halo Pro or Halo Commercial shop from the terminal or as an AI agent. Covers products, product categories, orders, customers, coupons, discounts, daily statistics, and shop configuration through `halo shop ...`. Trigger this skill whenever the user mentions Halo shop, Halo 商城, ecommerce products, orders, customers, coupons, discounts, or shop operations.
references:
- ../halo-cli-shared
metadata:
openclaw:
category: developer-tools
requires:
bins: ["halo"]
cliHelp: "halo shop --help"
---

# Halo CLI Shop

Manage the Halo shop area through the `console.api.ecommerce.halo.run/v1alpha1` API.

## Prerequisites

The shop API only exists on Halo Pro / Halo Commercial instances with the shop feature enabled.

```bash
halo shop doctor --json
```

Run `doctor` before a shop workflow. It reports edition, activation, `shopApiAvailable`, `shopApiStatus` (`available`, `denied`, `unavailable`, or `unknown`), per-feature readability, read/write permissions for each shop resource, configuration counts, and actionable `warnings`.

Use `shopApiStatus` rather than the boolean alone: `denied` means the API exists but the current profile lacks permission, while `unavailable` means the instance does not expose the shop API. A `features` entry is true only when the current profile can read that resource; use `warnings` to distinguish `PERMISSION_DENIED` from `RESOURCE_UNAVAILABLE`. If a warning uses `PERMISSION_DENIED`, `PERMISSIONS_UNAVAILABLE`, or `READ_ONLY_ACCESS`, adjust the profile or token instead of retrying.

`halo shop status --json` is a lighter check that only reports edition, activation, and API availability.

All commands accept `--profile <name>` and `--json`. Prefer `--json` for agent workflows.

## Command Map

```bash
halo shop status
halo shop doctor

halo shop product list|get|create|update|publish|unpublish|delete
halo shop category list|get|create|update|delete
halo shop order list|get|create|cancel|mark-paid|fulfill|note
halo shop customer list|get|update|addresses|entitlements
halo shop coupon list|get|create|update|delete|distribute
halo shop discount list|get|create|update|delete
halo shop stats daily
halo shop config get|set
```

Run `halo shop <group> --help` or `halo shop <group> <command> --help` before guessing flags.

## Common Workflows

Inspect products and publish one:

```bash
halo shop product list --status DRAFT --json
halo shop product get 12 --json
halo shop product publish 12
```

Create a simple virtual product with one variant:

```bash
halo shop product create \
--title "Digital license" \
--type VIRTUAL \
--status PUBLISHED \
--price 99 \
--sku DIGITAL-001 \
--variant-status PUBLISHED
```

Create a product with complex variants, specs, or images from a JSON payload:

```bash
halo shop product create --file product.json --json
halo shop product update 12 --file product.json --json
```

`--file -` reads the payload from stdin.

Order operations:

```bash
halo shop order list --payment-status PAID --json
halo shop order get 1001 --json
halo shop order create --username demo --item 5:1:99 --json
halo shop order mark-paid 1001 --force
halo shop order fulfill 1001 --all --force --json
halo shop order note 1001 --message "Called the buyer"
```

`order fulfill --all` fulfills every remaining unfulfilled quantity. Use `--item <orderItemId>:<quantity>` for partial fulfillment.

Marketing:

```bash
halo shop coupon list --json
halo shop coupon create \
--name "Spring sale" \
--calculation-type PERCENTAGE \
--discount-value 10 \
--scope ALL \
--valid-days 30
halo shop coupon distribute 1 --username demo
halo shop coupon get 1 --json # distribution is asynchronous; poll to confirm
halo shop discount list --json
```

`coupon distribute` schedules issuance; a backend scheduler processes recipients asynchronously. Do not report the coupons as issued until `coupon get` shows the distribution as completed.

Statistics and configuration:

```bash
halo shop stats daily --start 2026-01-01 --end 2026-01-31 --json
halo shop config get --json
halo shop config set --guest-checkout true
halo shop config set --group payment --file payment.json
```

`config set --group` merges the file over the current group payload. Nested objects are replaced; omitted top-level groups are preserved.

## JSON Payloads

Payloads follow the Halo console API schemas. Common shapes:

Product request:

```json
{
"title": "Digital license",
"productType": "VIRTUAL",
"status": "PUBLISHED",
"description": "One-year license",
"images": ["https://example.com/cover.png"],
"content": "<p>Details</p>",
"productVariants": [{ "price": 99, "skuCode": "DIGITAL-001", "status": "PUBLISHED" }]
}
```

Order create request:

```json
{
"username": "demo",
"items": [{ "productVariantId": 5, "quantity": 1, "customPrice": 99 }],
"customerNotes": "Created by agent",
"trackInventory": true
}
```

For `product update`, the CLI loads the current product first and merges the provided file and flags over it. For physical and virtual products, basic info, content, status, and specifications are updated through their dedicated endpoints so unrelated variant stock is not resubmitted. Variants are preserved unless the payload replaces `productVariants`. For an existing variant in the payload, fields omitted from the file are inherited from the current variant, so stock and other attributes are not reset. If an update needs confirmation, the CLI re-reads the product before writing specifications; provide explicit stock values if you intentionally want to overwrite concurrent changes.

FIXED variants require a `price`; only `pricingType: "INQUIRY"` variants may omit it. A new variant without an explicit `status` defaults to `DRAFT`; when updating, an existing variant that omits `status` keeps its current status. Set `--variant-status PUBLISHED` or include `status` in the file to make a variant purchasable.

## Safety

- `product delete`, `category delete`, `coupon delete`, `discount delete`, `order cancel`, `order mark-paid`, `order fulfill`, `product unpublish`, `product update --status DRAFT|ARCHIVED`, `product update --variant-status DRAFT|ARCHIVED` on a published product, `coupon update --enabled false`, `discount update --enabled false`, `coupon distribute --all`, and `customer update --status BLOCKED` require interactive confirmation or `--force` in non-interactive mode.
- `product update --file` follows full-array replacement for `productVariants`: removing any variant (including a draft variant, or sending an empty array) requires confirmation, regardless of the product's status.
- `order fulfill --all` skips subscription order lines. Subscription lines are fulfilled by the integrator and cannot be fulfilled manually. Use `--provider MANUAL`; the current backend does not expose a VIRTUAL fulfillment provider. Fulfillment cannot be reversed through the API, so confirm before using `--force`.
- Prefer `--json` and inspect the returned resource after every mutation.
- Do not print or send payment secrets from `shop config get --group payment`; treat them as credentials.
5 changes: 4 additions & 1 deletion skills/halo-cli/SKILL.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
---
name: halo-cli
version: 1.0.0
description: Use when the task involves Halo CLI, Halo CMS terminal operations, or any command-line interaction with Halo. This includes login, profiles, posts, single pages, search, plugins, themes, attachments, backups, moments, comments, notifications, or shell completion. Trigger this skill whenever the user mentions 'halo', 'Halo CLI', or wants to manage Halo from the terminal, even if the exact command area is unclear.
description: Use when the task involves Halo CLI, Halo CMS terminal operations, or any command-line interaction with Halo. This includes login, profiles, posts, single pages, search, plugins, themes, attachments, backups, moments, comments, notifications, shop operations, or shell completion. Trigger this skill whenever the user mentions 'halo', 'Halo CLI', or wants to manage Halo from the terminal, even if the exact command area is unclear.
references:
- ../halo-cli-shared
- ../halo-cli-auth
- ../halo-cli-content
- ../halo-cli-search
- ../halo-cli-operations
- ../halo-cli-moderation-notifications
- ../halo-cli-shop
metadata:
openclaw:
category: developer-tools
Expand All @@ -31,6 +32,7 @@ If the request only says "use Halo CLI" or mixes multiple areas, start here, the
- `halo-cli-search`: public site search
- `halo-cli-operations`: themes, plugins, attachments, backups, moments
- `halo-cli-moderation-notifications`: comments, replies, notifications
- `halo-cli-shop`: Halo Pro/Commercial shop products, orders, customers, coupons, discounts, statistics, and configuration

## Fast Routing

Expand All @@ -49,6 +51,7 @@ halo backup --help
halo moment --help
halo comment --help
halo notification --help
halo shop --help
halo completion --help
```

Expand Down
5 changes: 5 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import {
import { registerPluginCommands, tryRunPluginCommand } from "./commands/plugin/index.js";
import { registerPostCommands, tryRunPostCommand } from "./commands/post/index.js";
import { registerSearchCommands, tryRunSearchCommand } from "./commands/search/index.js";
import { registerShopCommands, tryRunShopCommand } from "./commands/shop/index.js";
import {
registerSinglePageCommands,
tryRunSinglePageCommand,
Expand Down Expand Up @@ -73,6 +74,10 @@ const commandModules = [
register: registerNotificationCommands,
tryRun: tryRunNotificationCommand,
},
{
register: registerShopCommands,
tryRun: tryRunShopCommand,
},
] as const;

for (const commandModule of commandModules) {
Expand Down
52 changes: 52 additions & 0 deletions src/commands/auth/__test__/auth-entry.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ test("tryRunAuthCommand keeps auth login --json free of target banners", async (
metadata: { name: "admin" },
spec: { displayName: "Admin" },
},
roles: [],
passwordSet: true,
},
}),
},
Expand Down Expand Up @@ -164,6 +166,56 @@ test("tryRunAuthCommand keeps auth login --json free of target banners", async (
expect(writeSpy).not.toHaveBeenCalledWith(expect.stringContaining("TARGET"));
});

test("tryRunAuthCommand rejects anonymous sessions instead of saving a broken profile", async () => {
silenceStdout();

const runtimeMock = {
configStore: {
getStoredProfile: vi.fn().mockResolvedValue(undefined),
upsertProfile: vi.fn(),
},
getClientsForResolvedProfile: vi.fn().mockReturnValue({
console: {
user: {
getCurrentUserDetail: vi.fn().mockResolvedValue({
data: {
user: {
metadata: { name: "anonymousUser" },
spec: { displayName: "Anonymous User" },
},
roles: [],
passwordSet: false,
},
}),
},
},
}),
};

await expect(
tryRunAuthCommand(
[
"auth",
"login",
"--profile",
"local",
"--url",
"https://demo.halo.run",
"--auth-type",
"basic",
"--username",
"admin",
"--password",
"wrong",
"--json",
],
runtimeMock as never,
),
).rejects.toThrow("Authentication failed");

expect(runtimeMock.configStore.upsertProfile).not.toHaveBeenCalled();
});

test("tryRunAuthCommand shows help for bare profile subcommands", async () => {
silenceStdout();

Expand Down
Loading
Loading