64 Commits

Author SHA1 Message Date
metatroncubeswdev
ef574be75f Client page: Competitor analysis tab with its reports and a Find competitors button
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 00:26:36 -04:00
metatroncubeswdev
6317d89be0 Runs: tell the AI the dashboard exports PDFs after the run
Stops summaries that tell the team to export PDFs or delete .data.json.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-28 00:23:12 -04:00
metatroncubeswdev
c8ed9870d0 Runs: check file writes by real path and log every refused tool
Write(clients/**) rules alone let a competitor scan's report write be denied.
Writes inside clients/, team/ and reports/ are now allowed by resolved path;
anything refused is logged with the tool and path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 22:17:43 -04:00
metatroncubeswdev
2e0b28f2a2 Competitor scan always re-runs; report numbers refresh when the report is rewritten
- Skill: a "Find competitors" click always does the full scan and rewrites
  this month's report (the run had skipped because one already existed)
- Frozen <report>.data.json is reused only while the .md is older, so a new
  run's report gets fresh numbers without deleting files by hand

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:49:57 -04:00
metatroncubeswdev
9238b8c77a Competitor and trend reports in Documents, search-trends chart, Open report
- Documents tab lists competitor analyses and trend scans (they were listed
  by the server but hidden in the page)
- Competitors page: "Open report" jumps to the latest competitor report
- New report block mc:search-trends: interest over time per keyword,
  trend %, busiest month and rising searches, from stored Google Trends data

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 19:44:10 -04:00
metatroncubeswdev
c404ea2ef9 Research: Google Places ratings, Google Trends, Instagram hashtag top posts
- Places API (New): competitor scans find local rivals on Google Maps and the
  server reads real ratings/review counts by place id; client's own listing
  fills the "You" row; recent reviews for praise/complaint themes; 30-day cache
- Google Trends via DataForSEO: rising/top related searches and % change per
  keyword in the client's region; used by trend-scan and to check gap demand
- Instagram Hashtag Search: top/recent posts under a niche hashtag (tool +
  Competitors page panel), counts Instagram's 30 tags/week limit
- Server settings → Research: keys plus on/off switches for each source
  (hashtag search off until Meta approves Public Content Access)
- Reports mark website-sourced ratings as unverified; skill takes handles
  only from a business's own site

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 18:51:54 -04:00
metatroncubeswdev
f86bd9ce33 Competitor scan: AI finds rivals, Instagram verifies, visual report
- competitor-scan skill + run type: web search for local rivals and
  niche benchmarks, competitor_suggest checks each on Instagram
- Suggestions inbox on Competitors page (Track / Dismiss)
- Report blocks: leaderboard, size-vs-engagement map, format lift,
  market's best posts, hashtag gap, Google ratings
- Stats: engagement by format, top hashtags; audit adds leaderboard

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 13:44:37 -04:00
metatroncubeswdev
df3dbb63c4 Visual reports and client Screenshots tab
- Reports: mc: blocks (score, KPIs, reach, funnel, followers, formats,
  rhythm, top posts, actions, screenshots, callouts) drawn as SVG from
  stored account data, frozen per report; same page in PDF and preview
- Audit/weekly-report runs refresh account numbers first
- Screenshots tab: monthly shot list, uploads, examples; audit reads them

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-27 00:48:32 -04:00
metatroncubeswdev
d314a7ad7c Model-server runs: 64k window, summarise at ~23k tokens
Matches the laptop Ollama setup (num_ctx 65536 on the model): requests
stay readable within the agent's ~5-minute wait while big tool results
still fit.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 11:20:30 -04:00
metatroncubeswdev
8170133a79 Runs on our own model server: smaller requests and earlier summarising
Local models (Ollama on a laptop GPU) break on requests over ~19k tokens.
Model-server runs now send only the tools and skill the job needs and
summarise the conversation at ~16k tokens. Publish, schedule, reply and
DM tools are removed from every run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 09:46:29 -04:00
metatroncubeswdev
32563e87f9 AI runs on our own model server (Ollama): address and token in Server settings
With a model server address set, runs send every model slot (opus/sonnet/haiku/subagent)
to the chosen model and never send the Anthropic key there. Tested locally with
qwen3.6:35b-a3b through the Agent SDK.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-26 03:22:27 -04:00
metatroncubeswdev
dff014e69a Runs: next month's calendar run type; let plan runs add to the Calendar
Dashboard runs couldn't call plan_add, so weekly plans never reached the Calendar.
Adds the calendar tools (plan items only, nothing is scheduled) and the read tools
audits use (competitors, demographics, mentions, connections, library, brand kit).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 22:49:03 -04:00
metatroncubeswdev
7c596ec6d5 Settings on the site instead of .env; Odoo in Connections
- Server settings (Settings page): SMTP, AI, publishing, Meta, platform keys and,
  on the control centre only, Stripe/billing. Stored sealed in data/settings.json and
  applied over .env in every process; hosted agency workspaces don't see our email,
  platform keys or billing.
- Odoo connection per workspace in Connections (login checked before saving).
- provision-tenants.sh marks agency containers with QUORLA_HOSTED_TENANT=true.
- docs/wildcard-subdomains.html: one-time setup for automatic agency subdomains.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 19:49:15 -04:00
metatroncubeswdev
e905236f41 Email: accept SMTP_FROM as "Name <address>"
The whole string was used as the address, so the mail server saw
"Quorla support"@domain and rejected the sender.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 16:38:42 -04:00
metatroncubeswdev
1e97bbd31f Control centre: don't block the provisioning report on email; fix resend
- The welcome email is sent in the background, so a slow mail server no
  longer makes provision-tenants.sh time out.
- SMTP connection/greeting/socket timeouts, so a bad host fails with a
  reason in the agency log instead of hanging.
- "Resend welcome" never sent anything (report() only emails on the
  change to running); it now sends directly and shows the error if any.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 13:15:45 -04:00
metatroncubeswdev
86f4f2c089 provision-tenants: give agency data folders to the container's node user
The app runs as uid 1000 inside the image; folders created by the deploy
user (another uid) made the container crash with EACCES on /app/data.
Fixed with a one-off root container, so the script still needs no sudo.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 12:42:59 -04:00
metatroncubeswdev
2b667c655a Quota test: use today's YouTube day instead of a fixed date
The fixed 21 Oct timestamps got pruned as stale once that day passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 12:29:55 -04:00
metatroncubeswdev
15bc0b9bf2 YouTube comments: read with an API key instead of the channel token
commentThreads.list rejects youtube.readonly ("insufficient authentication
scopes"); it needs youtube.force-ssl, which we don't want to request.
Public comments are now read with YOUTUBE_API_KEY. Also records the
re-submitted Google verification (video v2, new scope justification).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 12:29:27 -04:00
metatroncubeswdev
ac7d6f3ed8 Quota test: use the current time instead of a fixed date
The rolling-window test broke once the hard-coded date was more than a day old,
because entries older than 24 h are pruned on every write.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 15:13:51 -04:00
metatroncubeswdev
4c78b76586 Merge feature/internal-pages: internal pages, client portal, notifications, reports, hosted Quorla
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 15:13:23 -04:00
metatroncubeswdev
2646475ee0 Handoff: features on feature/internal-pages and deploy notes
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:35:33 -04:00
metatroncubeswdev
075b79f6b2 Hosted Quorla: sign-up, Stripe billing, one workspace per agency
- Control centre (QUORLA_CONTROL=true on app.quorlasocial.com): public /signup → Stripe Checkout,
  verified Stripe webhook (paid, updated, cancelled, payment failed), Admin → Agencies
  (approve without Stripe, suspend, resume, billing portal, resend welcome, remove)
- deploy/provision-tenants.sh (cron): one container per agency with its own .env and data,
  nginx map for <agency>.quorlasocial.com, upgrades agencies after a rebuild, archives removed data
- New workspaces: first admin only via the emailed one-time setup link (SETUP_TOKEN)
- Meta system user token can be saved in Settings (encrypted) for workspaces that can't edit .env
- docs/multi-tenant.md: DNS, wildcard cert, nginx, sudoers, cron, Stripe setup; tests for the control centre

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:35:23 -04:00
metatroncubeswdev
82814f3757 White-label workspace and getting-started checklist
- Settings → Workspace: agency name, logo, brand colour, support email, website
- Logo replaces Quorla's in the app (with 'Powered by Quorla'), sign-in page, client approval page and PDF reports;
  brand colour drives buttons, links and highlights; emails and reports use the agency name
- Getting started checklist on the Clients page for admins, checked against the real state
  (workspace, Meta, first client, team, platforms, email, library, first post, approval link); dismissible

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:28:23 -04:00
metatroncubeswdev
d9c5b8ccdf Sidebar: group links into Work, Setup and Admin
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:25:11 -04:00
metatroncubeswdev
ca42c781fa Admin: audit log and system health
- Every successful change in the dashboard is logged (who, what, when) with passwords, secrets and tokens masked;
  shown with the publish log in plain English, filter by period, client, person, search
- System health: publishing/scheduler switches, overdue or failed posts, stale numbers refreshes,
  connections needing reconnect or expiring, email, AI runs and API key, last backup, data size
- docker-compose mounts ./backups read-only so health can see the last backup

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:24:33 -04:00
metatroncubeswdev
958ec9a1b5 Bulk scheduling from the content library
- Approved tab with a client selected: tick items, 'Schedule N selected'
- Start date, one post every 1/2/3/4/7 days, times used in turn (pre-filled with the client's best times)
- Items with their own planned date keep it; creates drafts only, each still needs approval
- Platforms needing per-post details (YouTube, TikTok, Pinterest, Reddit) stay one at a time

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:22:15 -04:00
metatroncubeswdev
444201e4ec Link-in-bio page per client with click counts
- Public page /l/<address>: Instagram name, picture and bio (or custom), link buttons, latest 6 posts
- Every click counted per link per day through /l/<address>/go/<id>; optional UTM tags for Google Analytics
- Client page → Link in bio: address, links (reorder with buttons), colours, clicks in the last 30 days
- Snapshots now keep the Instagram profile picture

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:20:52 -04:00
metatroncubeswdev
7e780549ea Brand kit and caption checks per client
- Client page → Brand kit: hashtag sets, approved phrases/CTAs, words to avoid, voice notes
- New post: one-click insert of hashtag sets and phrases, live checks while typing
- Checks: agency named in a client post (refused when saving; the agency's own account is exempt),
  avoid list, generic AI-sounding phrases, too many hashtags or emoji, platform length limits
- Brand warnings also appear on drafts; MCP tools brand_kit and caption_check for the AI

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:19:04 -04:00
metatroncubeswdev
9e263cd1f8 Best time to post, learned from each client's own Instagram reach
- Last 120 days of posts, reach vs the client's median, by weekday and 3-hour window (client time zone)
- Needs 8+ posts and 2+ per slot; shown on Published (client filter) and as the New post time hint
- Falls back to general market times when there isn't enough data

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:16:26 -04:00
metatroncubeswdev
21e49b442f Inbox: Google reviews (read and reply) and YouTube comments
- Inbox tabs: Instagram & Facebook / Google reviews / YouTube comments
- Google reviews: average rating, needs-reply filter, reply or edit reply (public on Google), only on the client's location
- YouTube comments: latest on the channel, open the comment to reply on YouTube (reply needs a broader scope)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:15:11 -04:00
metatroncubeswdev
c4d8c6474c Team task flow: assign calendar items, link uploads, move cards through production
- Assigning a plan item notifies the person (bell + email); team names offered when assigning
- Optional Odoo task from a calendar card (project + assignee, deadline = post date, brief in the task)
- Library upload can pick its calendar item: inherits date and Odoo task, card moves to 'in production'
- Approving the upload moves the card to 'ready'; changes requested notify the uploader
- Calendar shows the uploaded work in place of its plan card (no duplicates)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:13:23 -04:00
metatroncubeswdev
aa56d2b531 Monthly client report from real numbers, emailed or automatic
- Posts and results for the month, follower change, best posts, formats that worked, every post,
  competitor comparison and next month's plan; branded PDF via tools/report-pdf (no AI key needed)
- Client page → Documents: create for any month, email the PDF to the client's reviewer
- Optional per client: create on the 1st of each month and email it; admins get a bell notification
- Documents list groups monthly reports separately

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:11:04 -04:00
metatroncubeswdev
3f800d46ad Notifications: bell in the sidebar, email, morning summary, client approval emails
- In-app list with unread count; per-person email and 8:00 summary settings
- Team is told when a client approves or asks for changes, a post fails or only partly publishes,
  or a TikTok post is waiting in the TikTok app; new drafts appear in the bell and the summary
- Clients get one email (batched over 5 minutes) when posts are sent to them, in the agency's name
- SMTP settings in .env; nodemailer pinned; express advisories fixed (npm audit: 0)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:07:20 -04:00
metatroncubeswdev
92ee5c28b2 Client approval portal: a private link per client to approve posts or ask for changes
- Admin creates/replaces/turns off the link and sets the reviewer on the client page (Client approval tab)
- Drafts get 'Send to client'; the client's answer (approved / changes + note + name) shows on the draft
- Editing a reviewed draft asks the client again; the admin's approval is still required to publish
- Client page is mobile-first, no login, noindex, media served only for posts in review

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 10:03:23 -04:00
metatroncubeswdev
8b6e1cf835 Competitors page: track Instagram competitors per client
- Add/remove competitor handles (public Business/Creator accounts); refreshed nightly, one snapshot a day
- Compare with the client: followers, 30-day change, trend, posts/week, typical post (median), engagement rate, format mix
- Engagement-rate chart and each competitor's top recent posts
- MCP tools competitors and competitor_add (audits can add the AI's suggestions)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 09:22:33 -04:00
metatroncubeswdev
e304626986 Content calendar: plan, production and the publishing queue per client
- Plan items (idea → brief → production → ready) with brief, caption, platforms, assignee, pillar
- Calendar merges plan items, library items with a target date and queued/published posts
- Month and week views, drag a card to another day (or change the date in the card), add from any day
- MCP tools content_plan, plan_add, plan_update; weekly-plan skill now fills the calendar

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 09:18:53 -04:00
metatroncubeswdev
999e9fbcec Published page: every client's posts with their numbers
- Feed merges Instagram/Facebook snapshot posts (with insights) and everything Quorla published
- Filters (client, platform, 7/30/90 days), sort by interactions/reach/views, via-Quorla badge
- YouTube view/like/comment counts fetched nightly and on demand (1 quota unit per 50 videos)
- Snapshots now keep post thumbnails (IG media/thumbnail url, FB full_picture)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-23 09:15:34 -04:00
metatroncubeswdev
e5f24799ea TikTok: production version submitted for review
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 22:22:58 -04:00
metatroncubeswdev
014d96ddaf TikTok: link private posts to the profile (TikTok gives no public post id)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 21:47:03 -04:00
metatroncubeswdev
6dc35c8c8b TikTok post screen: media preview, max video length check, can't-post state
- New post shows image/video previews; the TikTok panel shows a preview of what will be posted
- Videos longer than the account's TikTok limit (creator_info) block saving with a clear message
- If TikTok's creator info says the account can't post now, the panel shows TikTok's message and saving is blocked

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 20:50:34 -04:00
metatroncubeswdev
482cf4253e Google: YouTube scopes submitted for verification
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:41:13 -04:00
metatroncubeswdev
5b5a14411f GMB Audit (Business Profile): in production and branding verified
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:19:24 -04:00
metatroncubeswdev
7f1d6fdc74 Google demo: YouTube only; GMB Audit needs branding check only
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:08:59 -04:00
metatroncubeswdev
f8f39aa689 Demo video scripts for Google and TikTok reviews, with test media
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-22 19:04:59 -04:00
metatroncubeswdev
6c65889597 Google verification: branding verified; paste-ready scope justification
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:19:17 -04:00
metatroncubeswdev
a201fdc437 Google verification: scope justifications and demo video script
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:12:52 -04:00
metatroncubeswdev
addf1c7787 Notes: TikTok inbox mode works before review; LinkedIn request status
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:06:16 -04:00
metatroncubeswdev
0a705a5c18 Publishing: say when a TikTok post was sent to the TikTok app inbox
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 20:06:04 -04:00
metatroncubeswdev
894940800b TikTok: option to send posts to the TikTok app's inbox to finish there
Direct Post from an unaudited app only works for private accounts. Inbox upload lets a
person finish and post from the TikTok app.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 19:52:17 -04:00
metatroncubeswdev
fd87ba08aa Retry failed platforms on a post; clearer platform error codes
- Publishing: admins can retry a failed or partly published post; parts that already posted are skipped
- API errors now include the platform's error code, with plain fixes for common TikTok ones

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-21 19:27:21 -04:00