> ## Documentation Index
> Fetch the complete documentation index at: https://docs.fetchski-attract.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Ace commands

> The package's Ace commands: check the site, sweep every read, store or forget the key, and a REPL with the client loaded.

[Ace](https://docs.adonisjs.com/guides/ace/introduction) is AdonisJS's command line, its Artisan. The
package registers its commands in `adonisrc.ts` when you configure it: `node ace list attract` shows
them, and `node ace <name> --help` explains each.

> [card] [attract:check](#check-the-site)
>
> The config, the site answering, its key accepted, and the clocks agreeing.

> [card] [attract:sweep](#sweep-every-read)
>
> Call every read the site offers with this app's key, and report what broke.

> [card] [attract:store-key · attract:forget-key](#store-or-forget-the-key)
>
> The key in the database: store it after the site accepts it, or delete it.

> [card] [REPL: loadAttract()](#try-calls-in-the-repl)
>
> The client in `node ace repl`, to try a call against the real site.

A refusal ends any of them with one line and exit code `1`, so a script or CI step sees it failed.

## Check the site

```bash Terminal theme={"dark"}
node ace attract:check
```

```text Terminal theme={"dark"}
┌────────────────┬──────────┬──────────────────────────────────────┐
│  Check         │  Result  │  Detail                              │
├────────────────┼──────────┼──────────────────────────────────────┤
│  Config        │  ok      │  acme                                │
├────────────────┼──────────┼──────────────────────────────────────┤
│  Reachable     │  ok      │                                      │
├────────────────┼──────────┼──────────────────────────────────────┤
│  Key accepted  │  ok      │                                      │
├────────────────┼──────────┼──────────────────────────────────────┤
│  Clock         │  ok      │  0 s apart (the site's Date header)  │
└────────────────┴──────────┴──────────────────────────────────────┘
[ success ] The site acme is ready.
```

**1. Config**

`config/attract.ts` names the site, and its URL and key are well formed (with the key in the
database: the row is there and opens).

**2. Reachable**

The site answers a public read.

**3. Key accepted**

One call with the key. A key whose scopes can't list pages is a warning, not a fail: the site
still accepted it.

**4. Clock**

This server's clock against the site's. Signed requests allow 5 minutes: past that, a key that
signs fails; past a minute, a warning.

**--site** `string`

Check another configured site, by its slug. Left out: the default site.

## Sweep every read

```bash Terminal theme={"dark"}
node ace attract:sweep
```

It calls every read the site offers through this app's client and reports what each did: a health
check of the key, its scopes and the SDK against the live site.

```text Terminal theme={"dark"}
┌──────────────────────────────────────────────────────────────────────────────────────┐
│  Call                                                                                │
├──────────────────────────────────────────────────────────────────────────────────────┤
│  52 of 62 read calls decoded; 4 skipped (they need arguments the sweep can't fill).  │
└──────────────────────────────────────────────────────────────────────────────────────┘
[ success ] Every read answered as the SDK expects on acme
```

How many decode depends on the key's scopes: a read they don't reach answers `403` (any `4xx` is an
answer), so it isn't broken.
**Broken** is an answer the SDK can't read, a `5xx` or no answer at all: report it.

**--all** `boolean`

List every call with its outcome, not only the broken ones.

**--site** `string`

Sweep another configured site, by its slug.

## Store or forget the key

With the key in the database ([Key in the database](https://docs.fetchski-attract.com/adonisjs/key-in-database.md)):

```bash Terminal theme={"dark"}
node ace attract:store-key --url=https://acme.example
```

It asks for whatever the flags leave out (the key and signing secret hidden as you type), builds a
client, makes one call to prove the site accepts the key, stores it sealed, then runs
`attract:check`. Run it again to replace the key.

> [!WARNING]
>
> Leave `--key` and `--secret` out and type them at the hidden prompts: a flag's value lands in your
> shell history and is visible to other processes on the machine.

**--url** `string`

The site's public origin.

**--key** `string`

The API key, `fsk_…`. Left out, it is asked for, hidden.

**--secret** `string`

The key's signing secret, `fss_…`. Left out, it is asked for, hidden, whenever the signing mode
isn't `none`.

**--signing** `string`

The key's signing mode, as its **Security** tab shows: `none`, `writes` or `all`.

**--no-check** `boolean`

Store the key without running `attract:check` after.

```bash Terminal theme={"dark"}
node ace attract:forget-key acme
```

Deletes the site's row from this app (the slug can be left out while the table holds one key; `--force`
skips the question). The key keeps working on the site until you revoke it there, in the CMS under
**API Keys**.

## Try calls in the REPL

`node ace repl` is a Node REPL with your app booted. The package adds `loadAttract()`, which puts the
default site's client in a `site` variable:

```ts REPL theme={"dark"}
> await loadAttract()
Loaded the site client as the "site" variable
> site.slug
'acme'
> (await site.blogPosts().listBlogs({ perPage: 5 })).results.map((post) => post.title)
```

`.ls` lists every helper the REPL has, `loadAttract` among them.

> [!WARNING]
>
> The REPL calls the real site with the app's key: a write there is a real write. Use a key whose scopes
> match what you mean to try.

## Commands of your own

Any job your app does against the site can be a command of its own:

```bash Terminal theme={"dark"}
node ace make:command latest_posts
```

```ts commands/latest_posts.ts theme={"dark"}
import { inject } from '@adonisjs/core'
import { BaseCommand } from '@adonisjs/core/ace'
import type { CommandOptions } from '@adonisjs/core/types/ace'
import { SiteClient } from '@fetchski/attract-cms-sdk'

export default class LatestPosts extends BaseCommand {
  static commandName = 'blog:latest'
  static description = "Print the titles of the site's newest posts"
  static options: CommandOptions = { startApp: true }

  @inject()
  async run(site: SiteClient) {
    const list = await site.blogPosts().listBlogs({ perPage: 5 })
    for (const post of list.results) {
      this.logger.info(post.title)
    }
  }
}
```

> [card] startApp: true
>
> Boots the app first, so the container and the package's client are there.

> [card] @inject() on run()
>
> Takes the `SiteClient` (or `SiteClients`, or your own services) as arguments.

> [card] exitCode = 1
>
> On a refusal, after `this.logger.error(…)`, so a script or CI step sees it failed.

Ace's [arguments](https://docs.adonisjs.com/guides/ace/arguments),
[flags](https://docs.adonisjs.com/guides/ace/flags), [prompts](https://docs.adonisjs.com/guides/ace/prompts)
and [terminal UI](https://docs.adonisjs.com/guides/ace/terminal-ui) (tables, tasks, stickers) are in
the AdonisJS docs. A command's test is on [Testing](https://docs.fetchski-attract.com/adonisjs/testing.md#an-ace-command-of-your-own).

> [card] [Next: jobs, cache and health](https://docs.fetchski-attract.com/adonisjs/background-work.md)
>
> Queue jobs and schedules, cached answers, and a readiness check.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.