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
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ byte-for-byte unchanged apart from the `cli_version` field.
and `Looked in:` paths now print home-relative (`~/.claude/skills`)
- Fixed `NO_COLOR=''` (empty value) not being treated as "set" — the
no-color.org standard disables color on presence, not truthiness
- Sync messaging: CLI description, error output, and README now say the
hosted service is retired, not paused/rebuilt

## [0.2.0]

Expand Down
18 changes: 9 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ npx devcat-cli --json | jq '.clients[] | {label, total}'
| `devcat` | Scan and print your stack. Same as `devcat report`. |
| `devcat report --markdown` | Print the shareable "My AI stack" snippet instead. |
| `devcat report --json` | Print the scan as one JSON object. |
| `devcat sync` | Push your manifest to devcat.dev. **Paused** — see [Profile sync](#profile-sync). |
| `devcat sync` | Push your manifest to devcat.dev. **Retired** — see [Profile sync](#profile-sync). |
| `devcat logout` | Clear local DevCat credentials. |
| `devcat --version` / `--help` | Version, help. |

Expand Down Expand Up @@ -178,7 +178,7 @@ devcat
|---|---|---|
| `NO_COLOR` | unset | Disable color output ([no-color.org](https://no-color.org/) standard). |

The four other variables the CLI reads take effect only on `devcat sync`, which is paused — they are documented under [Profile sync](#profile-sync).
The four other variables the CLI reads take effect only on `devcat sync`, whose hosted default is retired — they are documented under [Profile sync](#profile-sync).

## Security

Expand All @@ -198,19 +198,19 @@ The four other variables the CLI reads take effect only on `devcat sync`, which

<a id="profile-sync"></a>

## Profile sync — paused while devcat.dev is rebuilt
## Profile sync — retired

Every other command in this README works offline and needs no account. This section covers `devcat sync` — the only command that talks to a server, and the only part of the CLI currently unavailable.
Every other command in this README works offline and needs no account. This section covers `devcat sync` — the only command that talks to a server, and the only one whose hosted default is retired.

`devcat sync` pushes your manifest to a devcat.dev profile. **That site is being rebuilt, so sync is paused** — it stops immediately with one line rather than starting a sign-in it can't finish:
`devcat sync` pushed your manifest to a devcat.dev profile. **devcat.dev no longer hosts profiles — the hosted service is retired.** By default, `sync` now stops immediately with one line rather than starting a sign-in it can't finish:

```
✗ Profile sync is paused while devcat.dev is rebuilt. Your local stack report still works — run `npx devcat-cli`.
✗ Profile sync is retired — devcat.dev no longer hosts profiles. Your local stack report still works — run `npx devcat-cli`.
```

Nothing else is affected: the scan, `--markdown`, and `logout` all work offline as normal. Sync returns with the site.
Nothing else is affected: the scan, `--markdown`, and `logout` all work offline as normal.

The sync path itself is intact — [RFC 8628 device authorization](https://datatracker.ietf.org/doc/html/rfc8628), tokens in your OS keychain via [`@napi-rs/keyring`](https://www.npmjs.com/package/@napi-rs/keyring), no plaintext token on disk. If you run your own instance, point `DEVCAT_API_URL` at it and set `DEVCAT_SYNC_ENABLED=1` to run the full flow.
The sync path itself is intact — [RFC 8628 device authorization](https://datatracker.ietf.org/doc/html/rfc8628), tokens in your OS keychain via [`@napi-rs/keyring`](https://www.npmjs.com/package/@napi-rs/keyring), no plaintext token on disk. The hosted service is retired, but the self-host path remains: if you run your own instance, point `DEVCAT_API_URL` at it and set `DEVCAT_SYNC_ENABLED=1` to run the full flow.

Sync sends MCP servers and plugins only. Skills and subagents are local report detections — they are folders on your machine with no catalog entry behind them, and they never enter the sync payload.

Expand All @@ -220,7 +220,7 @@ These four are read only on the sync and sign-in paths; none of them changes wha

| Variable | Default | Purpose |
|---|---|---|
| `DEVCAT_SYNC_ENABLED` | unset | Set to `1` to run `devcat sync` against a live API instead of stopping at the paused message. |
| `DEVCAT_SYNC_ENABLED` | unset | Set to `1` to run `devcat sync` against a live API instead of stopping at the retired message. |
| `DEVCAT_API_URL` | `https://devcat.dev` | Override the API base URL (staging / self-hosted). HTTPS required except `http://localhost:*`. |
| `DEVCAT_TOKEN` | unset | CI escape hatch — bypass keychain and use this access token directly. |
| `DEVCAT_DEBUG` | unset | Verbose logging without the `--verbose` flag. |
Expand Down
2 changes: 1 addition & 1 deletion src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ export function buildProgram(sink: ExitCodeSink): Command {

program
.command('sync')
.description('Push your AI tool manifest to devcat.dev (paused while the site is rebuilt)')
.description('Push your AI tool manifest to devcat.dev (hosted sync retired)')
.option('--no-open', 'do not auto-open the browser at the verification URL')
.option('--json', 'emit machine-readable JSON event stream (for CI)')
.option('-v, --verbose', 'emit redacted HTTP trace to stderr')
Expand Down
15 changes: 8 additions & 7 deletions src/commands/sync.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,17 +47,18 @@ export interface SyncOptions {
}

/**
* devcat.dev is being rebuilt, so the hosted sync endpoint is not answering.
* Sync stops on the one line below instead of running the device flow and
* failing deep inside an HTTP call — no browser, no polling, no retries.
* The hosted devcat.dev sync endpoint is retired — devcat.dev no longer
* accepts profile syncs. Sync stops on the one line below instead of
* running the device flow and failing deep inside an HTTP call — no
* browser, no polling, no retries.
*
* The whole sync path underneath is untouched and still compiles. Set
* DEVCAT_SYNC_ENABLED=1 to run it anyway (integration tests do this, as does
* anyone pointing DEVCAT_API_URL at their own host). Delete this gate when
* devcat.dev is back.
* DEVCAT_SYNC_ENABLED=1 to run it anyway against a self-hosted instance —
* point DEVCAT_API_URL at it first. This gate is permanent for the hosted
* default, not a flag to delete once devcat.dev "comes back."
*/
const SYNC_PAUSED_MESSAGE =
'Profile sync is paused while devcat.dev is rebuilt. Your local stack report still works — run `npx devcat-cli`.';
'Profile sync is retired — devcat.dev no longer hosts profiles. Your local stack report still works — run `npx devcat-cli`.';

function isSyncPaused(): boolean {
return process.env.DEVCAT_SYNC_ENABLED !== '1';
Expand Down
12 changes: 6 additions & 6 deletions test/integration/sync.paused.test.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';

/**
* devcat.dev is being rebuilt, so `devcat sync` stops on one line instead of
* starting a device flow that cannot finish. This suite deliberately does NOT
* set DEVCAT_SYNC_ENABLED — it covers the paused path the other sync
* integration suites opt out of.
* devcat.dev's hosted sync is retired, so `devcat sync` stops on one line
* instead of starting a device flow that cannot finish. This suite
* deliberately does NOT set DEVCAT_SYNC_ENABLED — it covers the retired
* path the other sync integration suites opt out of.
*/
process.env.NO_COLOR = '1';

Expand All @@ -19,7 +19,7 @@ afterEach(() => {
else process.env.DEVCAT_SYNC_ENABLED = originalFlag;
});

describe('sync — paused while devcat.dev is down', () => {
describe('sync — retired (devcat.dev no longer hosts profiles)', () => {
it('prints exactly one line, opens no browser, makes no request, exits 1', async () => {
const stderr: string[] = [];
const stdout: string[] = [];
Expand Down Expand Up @@ -50,7 +50,7 @@ describe('sync — paused while devcat.dev is down', () => {

const err = stderr.join('');
expect(err.trimEnd().split('\n')).toHaveLength(1);
expect(err).toContain('Profile sync is paused while devcat.dev is rebuilt.');
expect(err).toContain('Profile sync is retired — devcat.dev no longer hosts profiles.');
expect(err).toContain('npx devcat-cli');
// No stack trace, no retry noise.
expect(err).not.toContain('at ');
Expand Down
Loading