feat(cli): add CRUD helper, resource definitions, and help command

Resource-first CLI: `nc groups list`, `nc wirings get <id>`, etc.
Seven resources defined (groups, messaging-groups, wirings, users,
roles, members, sessions) with full column documentation that serves
as the single source of truth for help output and arg validation.

- CRUD helper auto-registers list/get/create/update/delete from
  declarative resource definitions with generic SQL
- Custom operations for composite-PK resources (roles grant/revoke,
  members add/remove)
- Access model: open (reads) / approval (writes) / hidden
- `nc help` lists resources; `nc <resource> help` shows fields
- Positional target IDs: `nc groups get <id>`
- Removed unused priority column from wirings

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
gavrielc
2026-05-06 00:33:10 +03:00
parent 5e2bf1cb54
commit 6865811147
16 changed files with 810 additions and 46 deletions

View File

@@ -1,10 +1,10 @@
/**
* Transport-agnostic dispatcher. Both the socket server (host caller) and
* — once it lands — the per-session DB poller (container caller) call
* dispatch() with the same frame and a transport-supplied CallerContext.
* the per-session DB poller (container caller) call dispatch() with the
* same frame and a transport-supplied CallerContext.
*
* Approval gating for risky calls from the container is the only branch
* that differs by caller. Host callers and `safe` commands run inline.
* that differs by caller. Host callers and `open` commands run inline.
*/
import type { CallerContext, ErrorCode, RequestFrame, ResponseFrame } from './frame.js';
import { lookup } from './registry.js';
@@ -15,13 +15,13 @@ export async function dispatch(req: RequestFrame, ctx: CallerContext): Promise<R
return err(req.id, 'unknown-command', `no command "${req.command}"`);
}
// Agent + risky → approval flow. Wired alongside the first risky command;
// until then, return a clear pending-shaped error so the contract is visible.
if (ctx.caller !== 'host' && cmd.riskClass !== 'safe') {
// Agent + approval-gated → approval flow. Wired alongside the first
// approval-requiring command; until then, return a clear error.
if (ctx.caller !== 'host' && cmd.access === 'approval') {
return err(
req.id,
'approval-pending',
'Approval flow not yet wired. (Will be added when the first risky command lands.)',
'This command requires approval. (Approval flow not yet wired.)',
);
}