Commit Graph
61 Commits
Author SHA1 Message Date
James Wampler ab773615f8 Serve the unified tool registry over MCP Streamable HTTP at /mcp
Adds ModelContextProtocol.AspNetCore and registers AddMcpServer with
WithListToolsHandler/WithCallToolHandler rather than 45 attribute
methods, so both handlers resolve the scoped NovelAgentToolset per
request and reuse its hand-built schemas directly instead of fighting
the SDK's delegate-based schema inference.

NovelMcpTools (src/Novelly.Api/Mcp/) is the adapter: it injects a
required novelId property into the advertised schema for tools that
need one and extracts it back out at call time, since the web agent
gets novelId ambiently from its route but an MCP client has no route
to supply it from.

/mcp inherits auth from the existing fallback policy (cookie or
X-Novelly-Api-Key) by adding no authorization metadata of its own —
chaining .RequireAuthorization() would apply the default,
cookie-only policy instead and break the API key. Verified end to end
against a running instance: initialize advertises capabilities.tools,
tools/list returns all 45 with novelId injected only where needed,
tool errors map to result.isError rather than a JSON-RPC error, and a
write (create_tag) round-trips correctly with the service user's
identity intact.
2026-08-21 10:56:24 -07:00
James Wampler 897fb442a1 Unify MCP and agent tool surfaces onto one registry in NovelAgentToolset
Fixes NovelAgentService continuing a conversation under the wrong
novel's route, since FindConversationAsync matched by id alone. Then
extends NovelAgentToolset to all 45 tools the stdio MCP server offered
(tag/location CRUD, character relationships, arc-stage beat pinning,
question editing, cross-novel novel listing/creation), tagging each
with whether it needs an explicit novel scope so a later MCP adapter
can inject it. Renames the toolset's 33 existing schemas from
snake_case to camelCase to match .NET/REST convention, since nothing
external consumes them.

Lays the groundwork to serve this same registry over MCP at /mcp and
retire the separate stdio Novelly.Mcp project (docs/plans/api/mcp_http_merge_plan.md).
2026-08-21 10:52:50 -07:00
James Wampler bb2a499569 Expose Novelly API port + service-key auth for MCP access to QA deploy
CI / build-and-push (push) Successful in 46s
CI / deploy (push) Successful in 9s
MCP server is a stdio process run outside docker, pointed at the API
over HTTP via NOVELLY_API_URL. The api container previously had no
port mapping, so it was unreachable outside the compose network.
2026-08-21 09:42:10 -07:00
novelly-ci 64fff4f1f7 chore: refresh coverage badge [skip ci] 2026-08-21 00:33:01 +00:00
James Wampler 13fa29e8e9 Pin container names in QA compose to match Caddy's upstream hostnames
CI / build-and-push (push) Successful in 53s
CI / deploy (push) Successful in 9s
Caddy reverse-proxies to novelly-web/novelly-api by DNS name, but compose
only registered aliases web/api (service names) plus novelly-web-1/-api-1
(container-number suffixed). Any Caddy restart re-resolves DNS and 502s
until the alias exists again. Pin container_name so the alias is stable
across every redeploy.
2026-08-20 17:32:30 -07:00
James Wampler aca26588f9 Add soft delete + trash, keyboard-first web overhaul, move chapter tags to bottom
CI / build-and-push (push) Failing after 31s
CI / deploy (push) Has been skipped
Adds SoftDelete/Trash across characters, chapters, locations, beats with a
purge schedule and Trash page. Reworks the web client for keyboard-driven
navigation (focus helpers, help overlay, keyboard.md doc). Moves the
ChapterPage tag editor to the bottom of the page to match CharacterDetailPage.
2026-08-20 16:39:09 -07:00
James Wampler 7df1fffdca Tweaks to Import
CI / build-and-push (push) Failing after 37s
CI / deploy (push) Has been skipped
2026-08-20 14:21:44 -07:00
James Wampler 1423977ed4 Modernize web client: dark phase-driven theme, sidebar shell, global agent panel
Replaces the warm-paper/serif look with a dark palette where the novel's
lifecycle phase drives the accent color app-wide. Sidebar nav replaces the
old header/tab-bar. Dashboard leads with quick actions (new chapter, new
character, continue writing) instead of just showing history. Agent chat
is now a context-aware slide-out panel reachable from any page in a novel,
replacing the buried /agent tab.
2026-08-20 14:21:44 -07:00
James Wampler 661f2917ea Add zip upload and mapped-path picker for outline import
Sandbox source paths under a configured Imports:RootPath, browse it
from the web dialog, upload a zip that extracts into staging, and
import a single markdown file (agent infers chapter vs character).
2026-08-20 14:21:44 -07:00
novelly-ci e3d410da0b chore: refresh coverage badge [skip ci] 2026-08-20 01:13:30 +00:00
James Wampler 3795ddd541 Add chapter kind to the web client
CI / build-and-push (push) Successful in 54s
CI / deploy (push) Successful in 9s
Chapter and its cross-referencing chips (tags, locations, questions,
character arcs) now carry kind/displayNumber/label fields end to end.
Chapter detail page gets a Kind selector; chapter chips across the
app render "Foreword"/"Afterword" instead of a misleading number for
front and back matter.
2026-08-19 18:07:21 -07:00
James Wampler 6b0cdd0d71 Surface chapter kind through the MCP server
create_chapter/update_chapter now pass kind through to the API,
matching the FrontMatter/Body/BackMatter option added to the embedded
agent's toolset.
2026-08-19 18:03:18 -07:00
James Wampler 7f56c79b20 Label chapter chips by kind, not raw number
Beats, tags, locations, open questions, and character/arc-stage
responses that reference a chapter now carry a ChapterLabel/DisplayNumber
alongside the raw Number, computed via the new
ChapterDisplayNumberLookup. Front/back matter chips show their title;
body chapters show "Chapter N: Title".
2026-08-19 18:02:53 -07:00
James Wampler ef5260a111 Add ChapterKind for front/back matter chapters
Chapters can now be marked FrontMatter/Body/BackMatter. Number stays
the manuscript sort key for every chapter; the author-facing display
number is now computed per-request as the chapter's ordinal among
Body chapters only, so a foreword or afterword no longer shifts the
numbering of the rest of the book. Surfaced through the API, agent
toolset, and import toolset.
2026-08-19 17:54:20 -07:00
James Wampler 71953220aa Add GitHub-style activity contribution graph
CI / build-and-push (push) Successful in 52s
CI / deploy (push) Successful in 9s
Record create/update/delete events across novel content (chapters, beats,
characters, arc stages, tags, locations, questions) into an append-only
ActivityEvent log, aggregate by UTC day, and surface as a heatmap on the
novels list and each novel's dashboard. Backfills history from existing
CreatedAt timestamps on first boot after the migration.
2026-08-19 17:39:13 -07:00
novelly-ci 51f3176bd0 chore: refresh coverage badge [skip ci] 2026-08-19 22:47:39 +00:00
James Wampler 45afc980d3 Simplify character dossier fields, add ShowPronouns setting
CI / build-and-push (push) Successful in 51s
CI / deploy (push) Successful in 9s
Collapse Want/Need into Motivation and Internal/External Conflict into
Conflict, drop Arc summary field, and move tags below open questions on
the character page. Gate Pronouns display behind new UiSettings:ShowPronouns
config (default off).
2026-08-19 15:33:14 -07:00
James Wampler bbd5e66777 QA deploy: override Agent model to Sonnet 5 (was Opus default)
CI / build-and-push (push) Successful in 45s
CI / deploy (push) Successful in 9s
2026-08-19 15:01:03 -07:00
James Wampler a45e8a59c7 Gate CI to Gitea only for now
CI / build-and-push (push) Successful in 42s
CI / deploy (push) Successful in 9s
GitHub pushes no longer run build/test/coverage at all — the job-level
condition previously only skipped the docker/deploy steps, so every GitHub
push still burned CI minutes on a build+test that went nowhere. GitHub gets
its own release-triggered workflow later.
2026-08-19 14:51:41 -07:00
James Wampler 2d7c2a93b7 fix: bind mount path is /mnt/storage/apps (typo)
CI / build-and-push (push) Successful in 41s
CI / deploy (push) Successful in 9s
2026-08-19 14:48:15 -07:00
James Wampler 304b8b4c59 Switch novelly-data to a bind mount at /mnt/storage/app/novelly/data
CI / build-and-push (push) Successful in 41s
CI / deploy (push) Successful in 8s
Named docker volume replaced with an explicit host path per user request.
Requires the path to exist on the QA server and any data from the old
novelly-data volume to be copied over manually before the next deploy.
2026-08-19 14:46:35 -07:00
James Wampler 8f93dce065 Guard deploys against a broken migration
CI / build-and-push (push) Successful in 46s
CI / deploy (push) Successful in 9s
Program.cs: wrap the boot-time MigrateAsync in try/catch (was an unhandled
exception into a restart:unless-stopped crash-loop), log critical and exit(1)
on failure, and add a --migrate-only flag that applies migrations then exits
0 without starting the web host.

deploy.sh: run migrations as a preflight via the new --migrate-only image
against the live novelly-data volume, before the running (old-image) stack
is touched. A failing migration now aborts the deploy with the old
containers still serving traffic, instead of swapping to a crash-looping
new container first and finding out from the health-check timeout.
2026-08-19 14:23:34 -07:00
James Wampler 7a1c726af8 chore: trigger CI deploy 2026-08-18 19:08:34 -07:00
James Wampler 44f722019b Mirror mic-check's Gitea/GitHub CI-CD pipeline for novelly
CI / build-and-push (push) Successful in 58s
CI / deploy (push) Successful in 10s
Dual-engine workflow (.github/workflows/ci.yml, read by both GitHub Actions
and Gitea Actions): build, test, coverage badge on every push; on Gitea main
pushes only, build+push API/web images to the Gitea registry and redeploy
the persistent LAN stack via the shared [self-hosted, qa] runner. Replaces
the ad hoc docker-compose.deploy.yml manual workflow with
deploy/qa/docker-compose.qa.yml, pulled by CI — data volume preserved
across deploys (no -v on down), unlike mic-check's throwaway QA stack.
2026-08-18 18:25:01 -07:00
James Wampler 56e64c6f06 Update README for locations, Novel rename, and scene removal
Docs had drifted from the code across several prior renames; refreshes the
data model, API surface table, and tool counts to match, and documents the
new Locations feature and chapter character summary alongside the actual
current commit.
2026-08-18 11:40:39 -07:00
James Wampler c620ddd626 Replace chapter setting with multi-select locations; add chapter character summary
Chapters now carry many Locations (new Tags-style entity with cross-referencing)
instead of a single free-text Setting field, with a Locations tab on the novel
for browsing them and seeing every chapter set at each one. Also surfaces the
distinct characters appearing in a chapter's beats, linked, under the beat/word
count on the outline tab.
2026-08-18 11:36:13 -07:00
James Wampler 4313c8f206 Rename Project concept to Novel across the stack
Renames the domain concept from Project to Novel throughout the backend
(entities, DTOs, services, endpoints, ProjectAccessService/Permission,
ProjectId foreign keys), MCP server (tool names and routes), and the
React/Vite frontend (types, hooks, routes, components). Adds a new EF
Core migration (RenameProjectToNovel) using RenameTable/RenameColumn to
preserve existing data instead of dropping/recreating tables. Updates
CLAUDE.md's structure section to reference Novels/ instead of Projects/.
2026-08-17 23:03:09 -07:00
James Wampler 0ab4f568b5 Drop braces on single-line conditionals in BeatService 2026-08-17 22:54:09 -07:00
James Wampler bc85a36e18 Dashboard: show 10 chapters titled Chapters; bump chapter mtime on beat changes
Beat create/update/delete/reorder/assign/move now touch the parent
chapter's UpdatedAt so the dashboard's recency sort reflects beat edits,
not just chapter-level prose/tag changes.
2026-08-17 22:53:05 -07:00
James Wampler 17facba3b9 Group character beats into arc-stage sections; reciprocal relationship types
Arc stages now group the beats that establish or pay off that stage of a
character's arc (many-to-many via ArcStageBeats), and carry a Result field
(renamed from Description) describing what the stage results in for the
character. Assigning a beat to a stage moves it out of any other stage of
the same character. New endpoint POST /api/arc-stages/{id}/beats, MCP tool
set_arc_stage_beats, and frontend grouping UI in CharacterArc/CharacterBeats.

Also records a relationship's reciprocal type so both characters' dossiers
show the correct direction (e.g. "sister" / "brother") instead of mirroring
the same label.
2026-08-17 22:45:07 -07:00
James Wampler eb1efcf9f8 Stop tracking .mcp.json; it carries a real API key
.mcp.json needs NOVELLY_API_KEY to match the API's Auth:ServiceApiKey
user secret, so it can't be a checked-in file — gitignore it and keep
.mcp.json.example (with the key blanked out) as the template. README
walks through copying the example and setting the matching user secret.
2026-08-17 18:39:56 -07:00
James Wampler c879d9bfce Add publish-mcp.sh so the standalone MCP binary stops going stale
Aspire doesn't run or manage src/Novelly.Mcp — it's a separate stdio
process the MCP client spawns from a published binary that nothing
rebuilds automatically. It had drifted 12 days out of date and was
silently missing the service-API-key auth header, causing confusing
401s. Script wraps the existing dotnet publish command via the shared
ensure_dotnet helper; README points at it instead of the raw command.

Also registers a UserSecretsId on Novelly.Api so Auth:ServiceApiKey
can be set locally without landing in appsettings.
2026-08-17 18:38:48 -07:00
James Wampler cb66ef7343 Add dashboard tag cloud and spectrum tag color picker
- Dashboard's Characters column gains a tag cloud below it, sized by
  usage count, linking into the tags page.
- Tags page color editor replaced with 16 preset swatches across the
  spectrum plus a "Custom..." link to the native hex picker.
- Fix: tag color/name updates weren't invalidating the tag-references
  query, so the reference panel showed a stale color after editing.
- Tag cloud/tag list selection now round-trips through a ?tag= query
  param so clicking a cloud tag selects it on the tags page.
2026-08-17 17:52:43 -07:00
James Wampler 92fe4317e2 Split character table and detail into separate pages
Characters list (/characters) is now just the filterable/sortable
table; clicking a row navigates to /characters/:characterId instead
of expanding an inline sheet below the table.
2026-08-17 17:37:52 -07:00
James Wampler f124b9b4bb Add character aliases/identity links, move-beats, keyboard help overlay
- Characters can carry aliases and be linked as the same underlying
  person (canonical SameCharacterAsId, optional reveal chapter/note),
  surfaced through the API, MCP tools, agent toolset, and web UI.
- Characters page redesigned as a filterable/sortable table (name+
  aliases, role, importance, occupation, tags) instead of a sidebar
  list, to stay usable as the cast grows.
- Beats can be moved between chapters (BeatService.MoveAsync + MCP/
  agent tool + endpoint).
- Add a keyboard-shortcuts help overlay (HelpButton/HelpOverlayContext)
  wired into the project layout.
- CLAUDE.md: require every frontend component to carry a unique id
  attribute; apply it to CharacterMultiSelect and MarkdownEditor.
2026-08-17 17:26:50 -07:00
Claude b4f4b35e3c Gate the Add chapter button behind CreateContent, matching characters/beats
Editors and Reviewers could see and click Add chapter even though the
API rejects the create with 403 for anyone without CreateContent — the
gate existed everywhere else (characters, beats) but was missed here.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JC8Q7Gkv8K7LnJrwkx1xrh
2026-08-17 00:13:53 +00:00
Claude e2a2e69631 Surface project ownership/role on the wire and gate the web UI by it
ProjectResponse now carries OwnerId and a server-resolved MyRole so
Editors/Reviewers see read-only fields and no delete/grant-management
affordances instead of only finding out via a 403 after the fact.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PuBH9QSv66DPXSSBERmPs6
2026-08-16 15:37:50 +00:00
James Wampler e598c18d67 Add users, roles, and per-novel permissions
Introduces accounts (ASP.NET Identity + cookie auth), four global
roles (Admin/Writer/Editor/Reviewer), per-novel ownership and grants
via ProjectMember, and a service-API-key principal for the MCP server
and background import jobs. Enforcement lives in the application
services (not endpoint filters) so the embedded agent and MCP tools,
which call the same services directly, can't bypass it. Web client
gets a login page, session-aware routing, and a People section for
managing per-novel access.

Also includes prior in-flight changes from this branch (CLAUDE.md
compliance pass, dev-deploy docker-compose setup) that were
uncommitted when this feature work started.
2026-08-15 22:29:33 -07:00
James Wampler 7d8dd0c4fd Remove chapter-level POV character
Character assignment now lives on beats only, per batch-assign. Drops
Chapter.PovCharacterId (entity, contracts, service, agent/import/MCP
tools, web UI) and the backing column via migration.
2026-08-15 20:44:10 -07:00
James Wampler df10b1f99b Add batch character assignment for chapter outline beats
Lets a writer select several beats and add one character to all of them
at once, without disturbing each beat's existing characters. Surfaced
through the REST API, the embedded agent, and the MCP server per the
project's rule that all three share the same service methods.
2026-08-15 20:36:25 -07:00
James Wampler 6774a38684 Add Novelly.Web.esproj and wire into solution 2026-08-15 20:24:43 -07:00
James Wampler 27c287b9c8 Add genre feature; extract ConfirmModal for reusable confirm dialogs
Genres seed on boot, are editable per project, and gate agent config
errors more gracefully. ConfirmModal replaces ad-hoc confirm prompts
across chapters, tags, and characters pages.
2026-08-15 11:25:13 -07:00
James Wampler ffb476a81a Add dashboard back-link to chapters list; add character via text selection
Chapters page now links back to the project dashboard. Right-clicking
highlighted text in the chapter summary or a beat's What happened /
What's next fields offers "Add Character", creating a character from
the selection and attaching it to the beat when applicable.
2026-08-12 17:20:56 -07:00
James Wampler 9a83a589c0 Add run.sh to launch AppHost and open the web client
Waits for the Vite dev server to respond on :5173, then opens it in the
default browser via xdg-open/open, falling back to printing the URL.
2026-08-12 17:06:37 -07:00
James Wampler 23348327a9 Remove Scenes, group beats by multiple characters; strip comments repo-wide
Drop the Scene entity/grouping in favor of chapters carrying prose directly
and beats belonging to many characters. Add markdown editor + character
multi-select components to the web client. Remove all XML doc and inline
comments across the touched C#/TS/CSS files in favor of self-documenting
names, and record that convention in CLAUDE.md. Add .mcp.json (local MCP
server config, no secrets) and ignore .idea/.
2026-08-11 21:05:13 -07:00
James Wampler 1ce526019f Add keyboard shortcuts and a help sidebar
Global hotkey registry (src/keyboard) with chord support (g d, g c,
...) and a "?" help sidebar that lists whatever's registered on the
current screen. Wired up nav chords plus the primary create action on
each page (new novel/character/chapter/beat/scene, new conversation),
and moved the agent's mod+Enter send through the same registry.
2026-08-11 20:35:23 -07:00
James Wampler e7d89a6d41 Add novel dashboard with project phases; fix beat/chapter overflow
Dashboard replaces the tabbed overview: Outline + Characters cards
(recent-first), gated by a new Project.Phase (Brainstorming ->
Outlining -> Writing -> Editing -> Complete). Brainstorming shows just
a notes editor; existing projects backfill to Outlining. Old brief
editor moves to a Settings route.

Beat table now renders as plain readable text, turning into editable
fields only for the clicked row. Fixed horizontal overflow on the
chapters list and beat table caused by unconstrained CSS grid/table
tracks sizing to their widest unwrapped child. Widened the project
layout's max width.
2026-08-10 22:46:09 -07:00
James Wampler 2ccebb31eb Remove NotFoundException; services return null on lookup miss
A missing record isn't exceptional — services now return null (logged
at Info) instead of throwing, and endpoints map null to 404. Agent and
import toolsets route not-found through their existing OrNotFound
result pattern rather than a caught exception.
2026-08-06 21:22:40 -07:00
James Wampler 189ebf3237 Add outline import feature; drop Dto naming, map entities at the API boundary
Services now return entities; endpoints (and the agent toolsets) map to
*Response records instead of services building wire DTOs themselves.
Also brings in the outline-import agent, MCP tool, ledger and web dialog
that were already in progress on disk.
2026-08-06 18:36:40 -07:00
James Wampler 40f93e40a8 Stop throwing for not-found; add Guard and request validation
Not-found lookups return null/false instead of throwing NotFoundException
across all services — a missing row is expected control flow, not an
exceptional condition. NotFoundException stays for embedded precondition
checks inside mutations (missing parent, invalid foreign reference).

Guard (copied from mic-check) enforces required arguments at the top of
every service method. A ported IModelValidator<T> framework validates
every request DTO at the API layer via a new ValidationEndpointFilter,
returning a 400 with field-level messages; services re-run the same
validator and throw for direct callers that bypass the API.

Endpoints translate null/false into 404 via a new ToApiResult() helper.
The agent toolset boundary translates the same nullable/bool results
into the tool-error text the model already expected.
2026-08-06 15:13:36 -07:00