dify/cli/AGENTS.md

100 lines
7.3 KiB
Markdown

# AGENTS.md — difyctl (TypeScript CLI)
TypeScript port of difyctl. Stack: oclif 4.x, Node 22+, ESM, ky for HTTP, vitest, eslint via @antfu/eslint-config.
> Architecture patterns, scaffolding recipe, printer chain, strategy pattern, testing conventions, anti-patterns: see **[`ARD.md`]**.
## Code rules
- **Spaces, not tabs.**
- **Minimum comments.** Code speak for self. Comment only non-obvious WHY — hidden constraints, subtle invariants, bug-workaround notes. Never restate code. Never reference tasks, PRs, current callers.
- **No magic strings or numbers.** Enums or named constants for bounded value sets.
- **No long positional arg lists.** Use options objects.
- **No long if/switch ladders on discriminator.** Polymorphism, dispatch tables, or strategy pattern. Name concept, let implementations plug in.
- **No `any`. No `unknown` outside genuine wire boundaries** (HTTP body parse, env vars). Narrow types everywhere else.
- **Avoid `!` non-null assertions.** Narrow instead.
- **`readonly` on inputs not mutated.**
- **Discriminated unions** for variant data (SSE events, run outputs, error shapes), not optional-field bags.
- **No backwards-compat shims.** No re-exports of old names, no `// removed:` markers, no deprecation notes. Delete, update callers.
- **No new dependencies without explicit approval.**
- **No CLI behavior changes in refactor commit.** Same flags, same output, same exit codes.
- **Every leaf command extends `DifyCommand`.** Add `static agentGuide` string when command benefits from agent workflow docs — see `src/commands/AGENTS.md`.
## Layering
| Layer | Path | Role |
| --------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| commands | `src/commands/` | oclif command shells. Only place oclif imports run. |
| domain | `src/run/`, `src/get/`, etc. | Plain TS modules. Take typed deps via options. Testable without oclif. |
| api | `src/api/` | One typed client per resource. Each takes `KyInstance`. |
| http | `src/http/` | `createClient` + middleware (auth, retry, logging, error mapping). Only place ky runs. |
| io | `src/io/` | Streams + spinner. Fence between data-out and progress UI. |
| printers | `src/printers/` | `CompositePrintFlags` + `-o {json,yaml,name,wide,text}` matrix. |
| errors | `src/errors/` | `BaseError`, `ErrorCode` enum, `ExitCode` enum, dispatch table, `formatErrorForCli`. |
| guide | `src/commands/**/<cmd>/guide.ts` | Per-command agent guide string. Export `agentGuide`, assign `static agentGuide = agentGuide` in command class. Surfaced via `--help`. |
| cache | `src/cache/` | On-disk caches (app-info, etc.). |
| auth | `src/auth/` | Hosts file, token store, login flow. |
| config | `src/config/` | XDG dir resolution, config.yml load/save. |
| workspace | `src/workspace/` | Resolver: flag → env → bundle. |
| types | `src/types/` | Pure data + zod schemas for server contracts. No runtime imports outward. |
## Command Structure
Scaffold recipe + checklist: see `ARD.md §New command scaffold`. Full folder convention (subcommands, guide.ts): see `src/commands/AGENTS.md`.
Layer rules:
- Commands thin shells. Use `this.authedCtx(opts)` for bearer context; delegate to domain function.
- Domain receives deps via options; never imports oclif.
- Only `src/http/client.ts` and `src/api/*` import ky at runtime; elsewhere use `import type { KyInstance }`.
- `process.*` lives in `src/io/`, `src/config/dir.ts`, `src/util/browser.ts`. Nowhere else.
- No circular imports. `types/` pure leaf.
## Dev commands
```sh
pnpm install # one-time
pnpm dev <command> [args...] # run CLI from source (no -- separator)
pnpm test # vitest
pnpm test:coverage # with coverage
pnpm type-check # tsc, no emit
pnpm lint # eslint
pnpm lint:fix # eslint --fix
pnpm build # production bundle + oclif manifest
pnpm manifest # regenerate oclif.manifest.json only
```
`make` covers `build` / `test` / `release` / `ci` as no-arg targets. Dev runs use `pnpm dev` directly.
## Tests
- Behavior tests run against real Hono mock at `test/fixtures/dify-mock/`. No `nock`, `msw`, or `fetchMock` — every test exercises real HTTP.
- Test files co-located: `foo.test.ts` next to `foo.ts`.
- Type-check, lint, full test suite must be green before any commit.
## Spec docs (`docs/specs/`)
Behavior contracts. Living tree — amended in place, no version subfolders.
**Keep:** HTTP wire shape (req/resp JSON, headers, status codes), SQL DDL, Redis keys + TTL, state transitions, audit event names + payload, error/exit codes, rate-limit values, JWS/cookie envelope claims.
**Cut:** language type decls, internal helper sigs, decorator snippets, file-path tables, pseudocode mirroring code, "Open items"/"Handler walk"/"CI guard"/"Migration" sections, rationale (`Rejected:`/`Why X not Y`/`Historical note:`/product comparisons), release-pipeline lines, version-pinning (`in v1.0`, `post-v1.0`, milestone codes), frontmatter `date`/`status`/`author`.
**Test:** "rewrite in Rust tomorrow, does spec hold?" HTTP/SQL/Redis stays; type defs go.
**Rules:** behavior, not rationale. One topic per file; cross-refs = `auth.md §Storage`. Tables beat prose. Code wins on drift — update spec.
## Out of scope for unrelated work
Do not modify in passing:
- `test/fixtures/dify-mock/` public surface (endpoints, JSON shapes, status codes, scenario names) — that's the dify-api contract.
- `bin/`, `scripts/`, `Makefile`, `eslint.config.js`, `tsconfig*.json`, `package.json` (unless the change is required by the task).
## Commits
- One concern per commit. Style: `<type>(<scope>): <imperative subject>` lowercase. Body explains why if non-obvious.
- Never push, amend, force-push, or skip hooks (`--no-verify`) without explicit user approval.
[`ARD.md`]: ARD.md