Skip to content
Open
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
416 changes: 208 additions & 208 deletions Terminal49-API.postman_collection.json

Large diffs are not rendered by default.

158 changes: 158 additions & 0 deletions sdks/typescript-sdk-cli/CLI_GA_PLAN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
# Terminal49 CLI GA Plan: Full SDK Parity + Dual Auth (API Token + MCP OAuth)

## Summary

Build `@terminal49/cli` as a production-grade CLI for humans and agents with:

1. Full command parity with the TypeScript SDK.
2. Dual auth modes: API token and OAuth using the same auth flow as hosted MCP.
3. Enforced coverage gate of 90/90/85 (lines/functions/statements at 90, branches at 85).
4. Deterministic machine output plus high-quality human UX.

## Current State Evaluation

1. CLI package exists but is scaffold-only; core modules and all command files are TODO stubs.
2. CLI has zero tests; `npm test` exits with "No test files found."
3. CLI build/type-check currently fail because workspace dependencies are not installed/resolved and lock metadata does not include the new CLI workspace.
4. SDK auth is token-centric and does not cleanly model OAuth bearer tokens for REST-first execution.
5. MCP package has working tests and auth guardrails, but OAuth is not implemented in this repo's server path today.
6. Docs currently state "OAuth not required" while linking to missing OAuth docs, so auth documentation is inconsistent.
7. CI has SDK and MCP jobs but no CLI job.

## Public API / Interface Changes

1. Extend `@terminal49/sdk` config to support explicit auth scheme without breaking `apiToken` callers.
2. Add CLI auth command group and auth-related global options.
3. Define and version the machine-readable command discovery schema returned by `t49 commands --json`.
4. Add CLI config schema versioning to allow safe upgrades.

## Decisions Locked

1. OAuth model: Shared OAuth flow aligned with MCP auth flow.
2. Execution backend in OAuth mode: REST SDK first.
3. Coverage gate: 90/90/85.
4. Token storage default: plain config file.
5. V1 scope: full SDK parity.

## Implementation Plan

### Phase 0: Workspace and Packaging Baseline

1. Wire CLI workspace dependency installation into root lockfile and CI install path.
2. Add missing CLI `README.md` (required by current `files` list in package manifest).
3. Ensure root-level `npm ci` yields resolvable `commander`, `chalk`, and `cli-table3` for CLI workspace.
4. Add workspace scripts for CLI build/test/lint consistency.

### Phase 1: SDK Auth Extension for OAuth Compatibility

1. Update SDK auth construction in `sdks/typescript-sdk/src/client.ts` to accept explicit auth scheme (`Token` or `Bearer`) while preserving existing `apiToken` behavior.
2. Add/adjust exported config types in SDK index/types for backward-compatible migration.
3. Add SDK tests for:
1. Legacy token path still emits `Authorization: Token ...`.
2. Explicit bearer path emits `Authorization: Bearer ...`.
3. Prefixed token passthrough and invalid config behavior.

### Phase 2: CLI Auth Subsystem (API Token + OAuth)

1. Implement config persistence in `sdks/typescript-sdk-cli/src/config.ts` with schema version and file mode hardening (`0600`).
2. Add auth resolver in `sdks/typescript-sdk-cli/src/client-factory.ts` with deterministic precedence:
1. Explicit CLI flags.
2. Environment variables.
3. Stored OAuth session (with refresh).
4. Stored API token.
3. Implement OAuth login/logout/status commands in `sdks/typescript-sdk-cli/src/commands/config.ts` or dedicated `src/commands/auth.ts` and register from `src/index.ts`.
4. OAuth flow implementation:
1. Use MCP SDK OAuth client primitives (PKCE auth code flow).
2. Discover metadata from MCP server URL.
3. Store tokens and token metadata in CLI config.
4. Refresh access token on expiry before REST calls.
5. On scope mismatch/401 in OAuth mode, emit deterministic auth error with remediation.
5. Add non-interactive behavior:
1. `--json` mode never prompts.
2. Missing OAuth session in non-interactive context exits with usage/auth error and next-step hint.

### Phase 3: Command Surface (Full SDK Parity)

1. Implement and register all planned commands:
1. `shipments`: `get`, `list`, `update`, `stop-tracking`, `resume-tracking`.
2. `containers`: `get`, `list`, `events`, `route`, `raw-events`, `refresh`, `demurrage`, `rail`.
3. `tracking-requests`: `list`, `get`, `create`, `update`, `infer`.
4. `track`.
5. `shipping-lines list`.
6. `search`.
7. `config` and `commands`.
2. Implement argument validation/mapping with strict, predictable usage errors.
3. Keep command layer thin; push API semantics to SDK.

### Phase 4: Output, Error Model, and Agent UX

1. Implement JSON/table dispatch in `sdks/typescript-sdk-cli/src/output/formatter.ts` with TTY-aware defaults.
2. Implement JSON envelope in `sdks/typescript-sdk-cli/src/output/json.ts` for both success and errors.
3. Implement field projection and compact output in `sdks/typescript-sdk-cli/src/output/fields.ts`.
4. Implement table rendering by resource in `sdks/typescript-sdk-cli/src/output/table.ts`.
5. Implement stable error mapping and exit codes in `sdks/typescript-sdk-cli/src/errors.ts`, including OAuth-specific failures.

### Phase 5: High-Coverage Test Suite

1. Unit tests for auth/config/output/error utilities with exhaustive branches.
2. Unit tests for each command module validating:
1. flag parsing.
2. argument validation.
3. SDK call mapping.
4. output envelope shape.
5. error translation.
3. Integration tests using `execa` for real process behavior:
1. exit codes.
2. stdout/stderr boundaries.
3. JSON determinism.
4. TTY/non-TTY mode.
5. OAuth missing-session and token-refresh behavior.
4. Contract tests for `t49 commands --json` schema stability.
5. Optional live smoke tests behind env guard for token and OAuth modes.

### Phase 6: CI, Docs, and Release Readiness

1. Add dedicated CLI job to `.github/workflows/ci.yml` with build, lint, tests, and coverage threshold enforcement.
2. Ensure coverage fails CI below 90/90/85.
3. Add user docs for:
1. API token auth.
2. OAuth login flow.
3. agent usage patterns.
4. non-interactive mode.
4. Fix/remove broken OAuth doc links until corresponding docs are added.

## Test Cases and Scenarios (Must-Have)

1. Auth precedence matrix across flags/env/config/oauth-session.
2. OAuth login success, callback timeout, denied consent, invalid state, refresh success, refresh failure.
3. SDK auth header emission for token and bearer paths.
4. Every command happy path plus representative API failures (401/403/404/422/429/5xx/network).
5. Output compatibility:
1. JSON envelope schema.
2. `--compact`.
3. `--fields` nested projection.
4. table output for TTY.
6. Agent determinism:
1. no ANSI in JSON mode.
2. no prompts in non-interactive runs.
3. stable error codes/messages.
7. Pagination and polling edge cases where implemented.

## Acceptance Criteria

1. `npm run build --workspace @terminal49/cli` passes on clean checkout.
2. `npm run test:coverage --workspace @terminal49/cli` passes with 90/90/85 minimums.
3. All planned commands are implemented and registered.
4. Both auth modes work end-to-end:
1. API token mode.
2. OAuth mode with token refresh and REST execution.
5. CI includes CLI checks and blocks merges on failures.
6. Command discovery JSON is versioned and contract-tested.

## Assumptions and Defaults

1. OAuth piggyback means reusing the same OAuth provider/discovery model as hosted MCP, not scraping Claude/Cursor local caches.
2. OAuth tokens issued by that flow are valid for REST SDK calls with `Bearer` auth; if not, CLI will fail with explicit scope remediation.
3. Plain config-file token storage is accepted for V1 despite weaker security posture.
4. Full SDK parity is required for GA, not a reduced MVP command set.
5. Node runtime target remains compatible with current repo toolchain and workspace CI matrix.
196 changes: 196 additions & 0 deletions sdks/typescript-sdk-cli/PLAN-v2.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,196 @@
# Terminal49 CLI — Implementation Plan

## Context

The CLI at `sdks/typescript-sdk-cli/` has been scaffolded (19 stub files, all empty TODOs) with a detailed PLAN.md spec. The TypeScript SDK at `sdks/typescript-sdk/src/client.ts` is mature, covering ~20 of 39 API endpoints. The MCP server at `packages/mcp/` uses token auth only today; OAuth is planned but unbuilt. The goal is a high-quality CLI with full API coverage, dual auth (token + OAuth), usable by both humans and LLM agents.

## Key Architectural Decisions

### 1. SDK-First: Extend the SDK, not the CLI
The CLI stays a thin presentation layer. Missing endpoint methods get added to `Terminal49Client` in the SDK, following existing `execute()` patterns. This keeps business logic in one place and makes new endpoints available to both CLI and MCP server.

### 2. Auth Strategy: Token-Only (OAuth Deferred)
Token auth only for now. OAuth (device code flow) deferred until Terminal49 ships an authorization server.

Token resolution order:
1. `--token <value>` flag
2. `T49_API_TOKEN` env var
3. `~/.config/terminal49/config.json` → `token`
4. Error with actionable message

### 3. Dependency: Use workspace SDK reference
Change `"@terminal49/sdk": "^0.1.0"` → `"@terminal49/sdk": "workspace:*"` (or `file:../typescript-sdk`) so CLI uses the local SDK with new methods during development.

---

## Phase 1: Foundation (Infrastructure + Core Commands)

### 1a. Infrastructure modules

| File | What |
|------|------|
| `src/util/tty.ts` | TTY detection, `NO_COLOR`/`FORCE_COLOR` support |
| `src/config.ts` | Read/write `~/.config/terminal49/config.json` (XDG-compliant, plain `node:fs`, atomic writes) |
| `src/client-factory.ts` | Token resolution chain → creates `Terminal49Client` |
| `src/errors.ts` | SDK error → CLI error mapping, exit codes 0-9, `withErrorHandling()` wrapper |
| `src/output/fields.ts` | `--fields` projection with dot-notation |
| `src/output/json.ts` | JSON envelope `{ok, command, data, pagination?, meta?}`, `--compact` |
| `src/output/table.ts` | Table renderer via `cli-table3`, resource-specific column defs |
| `src/output/formatter.ts` | Dispatcher: selects json/table from flags+TTY, applies fields projection |

### 1b. Core commands (use existing SDK methods)

| File | Commands | SDK Methods |
|------|----------|-------------|
| `src/commands/config.ts` | `set`, `get`, `list`, `path` | None (pure config) |
| `src/commands/containers.ts` | `get`, `list`, `events`, `route`, `raw-events`, `refresh`, `demurrage`, `rail` | `containers.*`, `getDemurrage`, `getRailMilestones` |
| `src/commands/shipments.ts` | `get`, `list`, `update`, `stop-tracking`, `resume-tracking` | `shipments.*` |
| `src/index.ts` | Register commands into Commander program | — |

### 1c. Tests
Unit tests for all modules + integration tests (spawn CLI binary, assert stdout/stderr/exit code). Target: ~80 tests.

---

## Phase 2: Remaining Existing-SDK Commands

| File | Commands | SDK Methods |
|------|----------|-------------|
| `src/commands/tracking-requests.ts` | `list`, `get`, `create`, `update`, `infer` | `trackingRequests.*` |
| `src/commands/track.ts` | `<number> [--scac] [--type]` | `trackingRequests.createFromInfer` |
| `src/commands/shipping-lines.ts` | `list [--search]` | `shippingLines.list` |
| `src/commands/search.ts` | `<query>` | `client.search()` |
| `src/commands/commands.ts` | `[--json]` — machine-readable command discovery | Introspects Commander tree |

Tests: ~40 more tests.

---

## Phase 3: SDK Extension + New CLI Commands

### 3a. Extend SDK (`sdks/typescript-sdk/src/client.ts`)

Add new resource namespaces following existing patterns:

| Namespace | Methods | API Endpoints |
|-----------|---------|---------------|
| `webhooks` | `list`, `get`, `create`, `update`, `delete`, `getIps` | `/webhooks`, `/webhooks/{id}`, `/webhooks/ips` |
| `webhookNotifications` | `list`, `get`, `getExamples` | `/webhook_notifications`, `/webhook_notifications/{id}`, `/webhook_notifications/examples` |
| `vessels` | `get`, `getByImo`, `futurePositions`, `futurePositionsWithCoords` | `/vessels/{id}`, `/vessels/{imo}`, `/vessels/{id}/future_positions*` |
| `ports` | `get` | `/ports/{id}` |
| `terminals` | `get` | `/terminals/{id}` |
| `parties` | `list`, `get` | `/parties`, `/parties/{id}` |
| `metroAreas` | `get` | `/metro_areas/{id}` |
| `customFieldDefinitions` | `list`, `get`, `create`, `update`, `delete` | `/custom_field_definitions*` |
| `customFieldOptions` | `list`, `get`, `create`, `update`, `delete` | `/custom_field_definitions/{id}/options*` |
| `customFields` | `list`, `get`, `create`, `update`, `delete` | `/custom_fields*` |

Also add: `containers.mapGeojson(id)` and entity-scoped custom fields on `shipments`/`containers`.

### 3b. New CLI command files

| File | Commands |
|------|----------|
| `src/commands/webhooks.ts` | `list`, `get`, `create`, `update`, `delete`, `ips` |
| `src/commands/webhook-notifications.ts` | `list`, `get`, `examples` |
| `src/commands/vessels.ts` | `get`, `get-by-imo`, `future-positions`, `future-positions-coords` |
| `src/commands/ports.ts` | `get <id-or-locode>` |
| `src/commands/terminals.ts` | `get <id>` |
| `src/commands/parties.ts` | `list`, `get` |
| `src/commands/metro-areas.ts` | `get <id-or-locode>` |
| `src/commands/custom-fields.ts` | `list`, `get`, `create`, `update`, `delete` |
| `src/commands/custom-field-definitions.ts` | `list`, `get`, `create`, `update`, `delete` |
| `src/commands/custom-field-options.ts` | `list <def-id>`, `get`, `create`, `update`, `delete` |

Update `containers.ts` and `shipments.ts` with `map`, `custom-fields`, `set-custom-field` subcommands.

Tests: ~80 more tests.

---

## Phase 4: Agent & Scale Features

| Feature | File |
|---------|------|
| `--all` pagination with NDJSON streaming | `src/util/pagination.ts` |
| `--poll` support with `--interval` / `--until` | `src/util/polling.ts` |
| Shell completions (bash/zsh/fish) | `src/commands/completions.ts` |

---

## Phase 5: Distribution & Polish

| Feature | File |
|---------|------|
| README with examples for humans + LLM agents | `README.md` |
| CI/CD (GitHub Actions: test, lint, build, publish to npm) | `.github/workflows/test-cli.yml` |
| npx support, Docker image | — |
| Final coverage audit (≥90% lines, ≥85% branches) | — |

---

## Full Command Surface (39 endpoints + composed commands)

```
t49 shipments list|get|update|stop-tracking|resume-tracking|custom-fields|set-custom-field
t49 containers list|get|events|route|raw-events|refresh|demurrage|rail|map|custom-fields|set-custom-field
t49 tracking-requests list|get|create|update|infer
t49 track <number>
t49 shipping-lines list
t49 webhooks list|get|create|update|delete|ips
t49 webhook-notifications list|get|examples
t49 vessels get|get-by-imo|future-positions|future-positions-coords
t49 ports get
t49 terminals get
t49 parties list|get
t49 metro-areas get
t49 custom-fields list|get|create|update|delete
t49 custom-field-definitions list|get|create|update|delete
t49 custom-field-options list|get|create|update|delete
t49 search <query>
t49 config set|get|list|path
t49 commands [--json]
```

---

## Command Pattern (all commands follow this)

```typescript
export function registerXxxCommand(program: Command): void {
const cmd = program.command('xxx').description('...');
cmd.command('get <id>')
.option('--include <resources>')
.action(withErrorHandling('xxx.get', async (id, opts, cmd) => {
const globalOpts = cmd.optsWithGlobals();
const client = createClient(globalOpts);
const formatter = createFormatter(globalOpts);
const data = await client.xxx.get(id, { format: globalOpts.format });
formatter.output('xxx.get', data);
}));
}
```

---

## Verification Plan

1. **Unit tests**: `npm test` — ~200+ tests covering all modules
2. **Integration tests**: `npm run test:integration` — spawn CLI binary with mocked HTTP (msw)
3. **E2E smoke**: `npm run test:e2e` — live API with `T49_API_TOKEN`
4. **Manual verification**: Run each command group against live API
5. **Coverage**: `npm run test:coverage` — enforce ≥90% lines, ≥85% branches
6. **Lint**: `npm run lint` — biome check

---

## Critical Files

| File | Why |
|------|-----|
| `sdks/typescript-sdk/src/client.ts` | SDK client — must be extended for ~19 missing endpoints |
| `sdks/typescript-sdk-cli/src/errors.ts` | Every command depends on `withErrorHandling()` |
| `sdks/typescript-sdk-cli/src/output/formatter.ts` | Every command depends on the output dispatcher |
| `sdks/typescript-sdk-cli/src/client-factory.ts` | Every command needs an authenticated SDK client |
| `sdks/typescript-sdk-cli/src/index.ts` | All commands registered here |
| `sdks/typescript-sdk-cli/package.json` | SDK dependency must be changed to workspace reference |
Loading
Loading