Add communication account plugin scaffolds

This commit is contained in:
Anthony Ettinger 2026-06-09 10:02:07 +00:00
parent 5cfeea6b57
commit c23ce42948
48 changed files with 1949 additions and 13 deletions

View file

@ -15,8 +15,10 @@
"@logicsrc/plugin-core": "file:../../packages/plugin-core",
"@logicsrc/plugin-c0mpute": "file:../../plugins/c0mpute",
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
"@logicsrc/plugin-email-accounts": "file:../../plugins/email-accounts",
"@logicsrc/plugin-feed-discovery": "file:../../plugins/feed-discovery",
"@logicsrc/plugin-sh1pt": "file:../../plugins/sh1pt",
"@logicsrc/plugin-social-accounts": "file:../../plugins/social-accounts",
"@logicsrc/plugin-ugig": "file:../../plugins/ugig",
"@logicsrc/validators": "file:../../packages/validators"
},

View file

@ -47,7 +47,7 @@ describe("CommandBoard API contracts", () => {
};
expect(response.status).toBe(200);
expect(body.plugins.map((plugin) => plugin.id)).toEqual(["coinpay", "ugig", "sh1pt", "c0mpute", "feed-discovery"]);
expect(body.plugins.map((plugin) => plugin.id)).toEqual(["coinpay", "ugig", "sh1pt", "c0mpute", "feed-discovery", "social-accounts", "email-accounts"]);
expect(body.plugins.find((plugin) => plugin.id === "sh1pt")).toMatchObject({
enabled: true,
capabilities: expect.arrayContaining(["projects.sync", "actions.publish", "deployments.status"])
@ -59,6 +59,24 @@ describe("CommandBoard API contracts", () => {
expect(body.capabilities["actions.publish"]).toEqual(["sh1pt"]);
expect(body.capabilities["compute.jobs.dispatch"]).toEqual(["c0mpute"]);
expect(body.capabilities["feeds.discover"]).toEqual(["feed-discovery"]);
expect(body.capabilities["social.post.publish"]).toEqual(["social-accounts"]);
expect(body.capabilities["email.send"]).toEqual(["email-accounts"]);
});
it("exposes communication account provider contracts", async () => {
const accountsResponse = await fetch(`${baseUrl}/api/accounts/providers`);
const accountsBody = await accountsResponse.json() as { providers: Array<{ id: string; kind: string }> };
const socialResponse = await fetch(`${baseUrl}/api/social/providers`);
const socialBody = await socialResponse.json() as { providers: Array<{ id: string; kind: string }> };
const emailResponse = await fetch(`${baseUrl}/api/email/providers`);
const emailBody = await emailResponse.json() as { providers: Array<{ id: string; kind: string }> };
expect(accountsResponse.status).toBe(200);
expect(accountsBody.providers.map((provider) => provider.id)).toEqual(expect.arrayContaining(["mastodon", "gmail", "imap-smtp"]));
expect(socialResponse.status).toBe(200);
expect(socialBody.providers[0]).toMatchObject({ id: "mastodon", kind: "social" });
expect(emailResponse.status).toBe(200);
expect(emailBody.providers[0]).toMatchObject({ id: "imap-smtp", kind: "email" });
});
it("exposes feed discovery plugin endpoints", async () => {

View file

@ -3,12 +3,14 @@ import { pathToFileURL } from "node:url";
import { createPluginRegistry } from "@logicsrc/plugin-core";
import { c0mputePlugin } from "@logicsrc/plugin-c0mpute";
import { coinPayPlugin } from "@logicsrc/plugin-coinpay";
import { emailAccountsPlugin, listEmailAccountProviders } from "@logicsrc/plugin-email-accounts";
import { discoverFeeds, feedDiscoveryPlugin, listFeedProviders, renderAtom, renderJsonFeed, renderOpml, renderRss, type FeedKind } from "@logicsrc/plugin-feed-discovery";
import { sh1ptPlugin } from "@logicsrc/plugin-sh1pt";
import { listSocialAccountProviders, socialAccountsPlugin } from "@logicsrc/plugin-social-accounts";
import { uGigPlugin } from "@logicsrc/plugin-ugig";
import { schemas, validate } from "@logicsrc/validators";
const registry = createPluginRegistry([coinPayPlugin, uGigPlugin, sh1ptPlugin, c0mputePlugin, feedDiscoveryPlugin]);
const registry = createPluginRegistry([coinPayPlugin, uGigPlugin, sh1ptPlugin, c0mputePlugin, feedDiscoveryPlugin, socialAccountsPlugin, emailAccountsPlugin]);
const boards = [
{ path: "/general", title: "General", description: "CommandBoard.run general discussion." },
@ -70,7 +72,7 @@ async function route(request: IncomingMessage, response: ServerResponse) {
json(response, 200, {
ok: true,
service: "commandboard-api",
endpoints: ["/health", "/api/boards", "/api/tasks", "/api/plugins", "/api/schemas", "/api/feeds/discover", "/api/feeds/providers", "/rss/discover/:keyword.xml", "/opml/discover/:keyword.xml", "/atom/discover/:keyword.xml", "/json-feed/discover/:keyword.json"]
endpoints: ["/health", "/api/boards", "/api/tasks", "/api/plugins", "/api/schemas", "/api/accounts/providers", "/api/accounts", "/api/social/providers", "/api/email/providers", "/api/feeds/discover", "/api/feeds/providers", "/rss/discover/:keyword.xml", "/opml/discover/:keyword.xml", "/atom/discover/:keyword.xml", "/json-feed/discover/:keyword.json"]
});
return;
}
@ -114,6 +116,38 @@ async function route(request: IncomingMessage, response: ServerResponse) {
return;
}
if (request.method === "GET" && url.pathname === "/api/accounts/providers") {
const kind = url.searchParams.get("kind");
const providers = [...listSocialAccountProviders(), ...listEmailAccountProviders()].filter((provider) => !kind || provider.kind === kind);
json(response, 200, { providers });
return;
}
if (request.method === "GET" && url.pathname === "/api/accounts") {
json(response, 200, { accounts: [] });
return;
}
if (request.method === "GET" && url.pathname === "/api/social/providers") {
json(response, 200, { providers: listSocialAccountProviders() });
return;
}
if (request.method === "GET" && url.pathname === "/api/social/accounts") {
json(response, 200, { accounts: [] });
return;
}
if (request.method === "GET" && url.pathname === "/api/email/providers") {
json(response, 200, { providers: listEmailAccountProviders() });
return;
}
if (request.method === "GET" && url.pathname === "/api/email/accounts") {
json(response, 200, { accounts: [] });
return;
}
if (request.method === "GET" && (url.pathname === "/api/feeds/providers" || url.pathname === "/api/rss/providers")) {
json(response, 200, { providers: listFeedProviders() });
return;

View file

@ -31,6 +31,9 @@ task
wallet
events
agentswarm
accounts
social
email
credentials
openspec
plugins
@ -66,6 +69,19 @@ logicsrc credentials plan --from doppler --to github-secrets
Credential sharing is provider-neutral. External tools can consume LogicSRC credential contracts, but LogicSRC commands do not call proprietary product CLIs.
Communication account commands:
```bash
logicsrc accounts providers
logicsrc accounts list
logicsrc social providers
logicsrc email providers
logicsrc social post <account-id> --text "Launching today" --dry-run
logicsrc email send <draft-id> --dry-run
```
Communication account commands must redact credentials, support dry-run for write-capable actions, and require approval policies for outbound email and social publishing.
When `--openspec` is enabled, AgentSwarm writes OpenSpec.dev-style files under `openspec/changes/<change-id>/`.
Machine-readable output should be available anywhere data is returned:

View file

@ -0,0 +1,139 @@
# Communication Accounts
Status: draft implementation scaffold
Slug: `communication-accounts`
Communication Accounts is the LogicSRC standard for connecting external social and email identities, delegating scoped access to humans, agents, workflows, and plugins, and auditing every read/write action without exposing raw credentials.
## First-Party Plugins
```txt
social-accounts
email-accounts
```
- `@logicsrc/plugin-social-accounts` manages social/network accounts such as Mastodon, Bluesky, GitHub, X/Twitter, Reddit, LinkedIn, YouTube, Discord, Telegram, and future ActivityPub/RSS-style adapters.
- `@logicsrc/plugin-email-accounts` manages email inboxes and outbound sending identities through IMAP/SMTP, Gmail, Microsoft Graph, ForwardEmail.net, local bridges, and custom providers.
Both plugins share contracts from `@logicsrc/account-core`.
## Core Objects
```txt
connected_account
account_provider
account_permission_grant
account_policy
account_audit_event
credential_broker_call
email_message_cache
social_post_cache
```
The shared account model records account kind, provider, account display metadata, granted scopes, declared capabilities, status, credential reference, ownership scope, and sync timestamps.
## Provider Boundary
Provider adapters implement the shared account provider contract:
```txt
provider.id
provider.kind
provider.authMethods
provider.capabilities
provider.getAuthUrl()
provider.completeAuth()
provider.refreshCredential()
provider.testConnection()
provider.revoke()
```
Social providers extend this with profile, draft, publish, media, mentions, comments, and analytics operations.
Email providers extend this with search, read, draft, send, reply, forward, archive, label, and delete operations.
## Permissions
Shared permissions:
```txt
accounts:connect
accounts:list
accounts:read_metadata
accounts:test
accounts:revoke
accounts:sync
accounts:audit:read
```
Social and email permissions use colon-style grant scopes such as `social:post:publish`, `social:mentions:read`, `email:headers:read`, `email:send`, and `email:attachments:read`.
Plugin manifest capabilities use the existing LogicSRC dotted capability convention such as `social.post.publish` and `email.headers.read`.
## Policy Gates
These actions require policy evaluation by default:
```txt
social:post:publish
social:post:delete
social:dm:read
social:dm:send
email:attachments:read
email:send
email:delete
```
Default write behavior is dry-run first, approval required for public publishing and outbound email, and deny for critical risk unless an admin override exists.
## Credential Rules
- Raw secrets must never be printed or exposed to agents by default.
- OAuth refresh tokens and app passwords must be stored through a credential broker.
- Provider calls should execute through a trusted runtime boundary.
- Credential access must create audit events.
- CLI, API, MCP, TUI, and PWA previews must redact secret-like fields.
## CLI Namespaces
```bash
logicsrc accounts providers
logicsrc accounts list
logicsrc accounts audit <account-id>
logicsrc social providers
logicsrc social accounts
logicsrc social post <account-id> --text "Launching today" --dry-run
logicsrc email providers
logicsrc email accounts
logicsrc email send <draft-id> --dry-run
```
The initial scaffold exposes provider listings and dry-run placeholders. Live connect, sync, send, and publish flows require durable credential broker, approval queue, and audit persistence.
## Storage
Deployable database migrations live under `supabase/migrations/`.
The communication account scaffold adds:
```txt
connected_accounts
account_permission_grants
account_audit_events
email_message_cache
social_post_cache
```
## MVP Order
1. Stabilize `packages/account-core`.
2. Validate account schemas through `packages/validators`.
3. Apply Supabase account migrations.
4. Wire provider and account list commands.
5. Add API read/list/dry-run endpoints and contract tests.
6. Add MCP read-only resources.
7. Add TUI/PWA account status panels.
8. Implement IMAP/SMTP provider behind approval and audit gates.
9. Implement Mastodon, Bluesky, or GitHub social provider behind approval and audit gates.
10. Add Gmail OAuth after credential broker storage and restricted-scope handling are complete.

View file

@ -6,6 +6,9 @@ Core tables:
users
dids
oauth_accounts
connected_accounts
account_permission_grants
account_audit_events
profiles
organizations
organization_members
@ -34,6 +37,8 @@ notifications
schemas
schema_versions
plugin_audit_logs
email_message_cache
social_post_cache
```
Important relationships:
@ -50,3 +55,4 @@ Important relationships:
- API keys belong to users, agents, or service accounts.
- Permissions are scoped to resources.
- LogicSRC-compatible objects record their schema version.
- Connected social and email accounts reference credentials through a broker and use account permission grants plus account audit events for agent-safe delegation.

View file

@ -43,6 +43,30 @@ paths:
responses:
"201":
description: Credential sync plan created
/api/accounts/providers:
get:
summary: List social and email account providers
responses:
"200":
description: Account providers returned
/api/accounts:
get:
summary: List connected social and email accounts
responses:
"200":
description: Connected accounts returned
/api/social/providers:
get:
summary: List social account providers
responses:
"200":
description: Social account providers returned
/api/email/providers:
get:
summary: List email account providers
responses:
"200":
description: Email account providers returned
/api/schemas:
get:
summary: List supported LogicSRC schema kinds

View file

@ -40,3 +40,39 @@ payment:spend_limited
```
Spend controls must include per-run, per-day, and per-task limits. Agents must never receive wallet private keys.
Communication account scopes:
```txt
accounts:connect
accounts:list
accounts:read_metadata
accounts:test
accounts:revoke
accounts:sync
accounts:audit:read
social:profile:read
social:post:draft
social:post:publish
social:post:delete
social:media:upload
social:mentions:read
social:comments:read
social:dm:read
social:dm:send
social:analytics:read
email:headers:read
email:body:read
email:attachments:read
email:search
email:draft
email:send
email:reply
email:forward
email:archive
email:labels:modify
email:delete
email:sync
```
Outbound email, social publishing, private-message access, attachment reads, and destructive actions require policy gates and audit records by default.

View file

@ -8,11 +8,14 @@ Default plugins:
- uGig: job import, gig publishing, candidate/agent linking, bid sync, marketplace publishing, and reputation sync.
- c0mpute: compute job dispatch, worker pool sync, usage reporting, quote creation, settlement status, and compute reputation events.
- Credential Sharing: provider-neutral secret sync plans, approvals, rollbacks, and audit events.
- Social Accounts: provider-neutral social profile, drafting, publishing, sync, policy, and audit flows.
- Email Accounts: provider-neutral inbox, sending identity, search, draft, send, sync, policy, and audit flows.
Coming soon plugin specs:
- AgentByte: candidate, contractor, and agent capability screening for AI-era workflows. See `docs/agent-screening.md`.
- Credential Sharing: replacement architecture for .env, Doppler, Railway variables, GitHub Secrets, and future providers. See `docs/credential-sharing.md`.
- Communication Accounts: shared social and email account management contracts. See `docs/communication-accounts.md`.
Runtime requirements:

View file

@ -27,5 +27,6 @@
25. Add SDK contracts for Rust, Bun, Node, Python, and curl.
26. Add MCP server contracts.
27. Add credential sync audit exports.
28. Add docs.
29. Tag v1.0.0.
28. Add communication account contracts, plugins, schemas, and account audit events.
29. Add docs.
30. Tag v1.0.0.

48
package-lock.json generated
View file

@ -26,8 +26,10 @@
"@logicsrc/plugin-c0mpute": "file:../../plugins/c0mpute",
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
"@logicsrc/plugin-core": "file:../../packages/plugin-core",
"@logicsrc/plugin-email-accounts": "file:../../plugins/email-accounts",
"@logicsrc/plugin-feed-discovery": "file:../../plugins/feed-discovery",
"@logicsrc/plugin-sh1pt": "file:../../plugins/sh1pt",
"@logicsrc/plugin-social-accounts": "file:../../plugins/social-accounts",
"@logicsrc/plugin-ugig": "file:../../plugins/ugig",
"@logicsrc/validators": "file:../../packages/validators"
},
@ -1424,6 +1426,10 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@logicsrc/account-core": {
"resolved": "packages/account-core",
"link": true
},
"node_modules/@logicsrc/cli": {
"resolved": "packages/cli",
"link": true
@ -1448,6 +1454,10 @@
"resolved": "packages/plugin-core",
"link": true
},
"node_modules/@logicsrc/plugin-email-accounts": {
"resolved": "plugins/email-accounts",
"link": true
},
"node_modules/@logicsrc/plugin-feed-discovery": {
"resolved": "plugins/feed-discovery",
"link": true
@ -1456,6 +1466,10 @@
"resolved": "plugins/sh1pt",
"link": true
},
"node_modules/@logicsrc/plugin-social-accounts": {
"resolved": "plugins/social-accounts",
"link": true
},
"node_modules/@logicsrc/plugin-ugig": {
"resolved": "plugins/ugig",
"link": true
@ -6042,13 +6056,23 @@
"zod": "^3.25.28 || ^4"
}
},
"packages/account-core": {
"name": "@logicsrc/account-core",
"version": "0.1.0",
"devDependencies": {
"vitest": "^4.0.8"
}
},
"packages/cli": {
"name": "@logicsrc/cli",
"version": "0.1.0",
"dependencies": {
"@logicsrc/account-core": "file:../account-core",
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
"@logicsrc/plugin-core": "file:../plugin-core",
"@logicsrc/plugin-email-accounts": "file:../../plugins/email-accounts",
"@logicsrc/plugin-feed-discovery": "file:../../plugins/feed-discovery",
"@logicsrc/plugin-social-accounts": "file:../../plugins/social-accounts",
"@logicsrc/plugin-ugig": "file:../../plugins/ugig",
"@logicsrc/tui": "file:../tui",
"@logicsrc/validators": "file:../validators",
@ -6104,6 +6128,8 @@
"dependencies": {
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
"@logicsrc/plugin-core": "file:../plugin-core",
"@logicsrc/plugin-email-accounts": "file:../../plugins/email-accounts",
"@logicsrc/plugin-social-accounts": "file:../../plugins/social-accounts",
"@logicsrc/plugin-ugig": "file:../../plugins/ugig"
}
},
@ -6142,6 +6168,17 @@
"vitest": "^4.0.8"
}
},
"plugins/email-accounts": {
"name": "@logicsrc/plugin-email-accounts",
"version": "0.1.0",
"dependencies": {
"@logicsrc/account-core": "file:../../packages/account-core",
"@logicsrc/plugin-core": "file:../../packages/plugin-core"
},
"devDependencies": {
"vitest": "^4.0.8"
}
},
"plugins/feed-discovery": {
"name": "@logicsrc/plugin-feed-discovery",
"version": "0.1.0",
@ -6168,6 +6205,17 @@
"vitest": "^4.0.8"
}
},
"plugins/social-accounts": {
"name": "@logicsrc/plugin-social-accounts",
"version": "0.1.0",
"dependencies": {
"@logicsrc/account-core": "file:../../packages/account-core",
"@logicsrc/plugin-core": "file:../../packages/plugin-core"
},
"devDependencies": {
"vitest": "^4.0.8"
}
},
"plugins/ugig": {
"name": "@logicsrc/plugin-ugig",
"version": "0.1.0",

View file

@ -12,7 +12,7 @@
"apps/*"
],
"scripts": {
"build": "npm --workspace @logicsrc/schemas run build && npm --workspace @logicsrc/validators run build && npm --workspace @logicsrc/sdk run build && npm --workspace @logicsrc/plugin-core run build && npm --workspace @logicsrc/plugin-coinpay run build && npm --workspace @logicsrc/plugin-ugig run build && npm --workspace @logicsrc/plugin-sh1pt run build && npm --workspace @logicsrc/plugin-c0mpute run build && npm --workspace @logicsrc/plugin-feed-discovery run build && npm --workspace @logicsrc/tui run build && npm --workspace @logicsrc/cli run build && npm --workspace @profullstack/logicsrc-mcp run build && npm --workspace @logicsrc/commandboard-api run build && npm --workspace @logicsrc/commandboard-web run build && npm --workspace @logicsrc/web run build",
"build": "npm --workspace @logicsrc/schemas run build && npm --workspace @logicsrc/validators run build && npm --workspace @logicsrc/sdk run build && npm --workspace @logicsrc/plugin-core run build && npm --workspace @logicsrc/account-core run build && npm --workspace @logicsrc/plugin-coinpay run build && npm --workspace @logicsrc/plugin-ugig run build && npm --workspace @logicsrc/plugin-sh1pt run build && npm --workspace @logicsrc/plugin-c0mpute run build && npm --workspace @logicsrc/plugin-feed-discovery run build && npm --workspace @logicsrc/plugin-social-accounts run build && npm --workspace @logicsrc/plugin-email-accounts run build && npm --workspace @logicsrc/tui run build && npm --workspace @logicsrc/cli run build && npm --workspace @profullstack/logicsrc-mcp run build && npm --workspace @logicsrc/commandboard-api run build && npm --workspace @logicsrc/commandboard-web run build && npm --workspace @logicsrc/web run build",
"start": "npm --workspace @logicsrc/web run start",
"test": "npm run test --workspaces --if-present",
"check": "npm run build && npm run test",

View file

@ -0,0 +1,15 @@
{
"name": "@logicsrc/account-core",
"version": "0.1.0",
"description": "LogicSRC shared account provider, permission, policy, credential broker, and audit contracts.",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc -p tsconfig.json",
"test": "vitest run src"
},
"devDependencies": {
"vitest": "^4.0.8"
}
}

View file

@ -0,0 +1,43 @@
import type { LogicSrcAccountAuditEvent, LogicSrcAccountKind, LogicSrcPolicyDecision, LogicSrcPrincipal } from "./types.js";
const REDACTED_KEYS = new Set(["accessToken", "refreshToken", "token", "password", "secret", "clientSecret", "authorization"]);
export function redactedPreview(input: Record<string, unknown>) {
const output: Record<string, unknown> = {};
for (const [key, value] of Object.entries(input)) {
output[key] = REDACTED_KEYS.has(key) ? "[redacted]" : value;
}
return output;
}
export function createAccountAuditEvent(input: {
id?: string;
accountId?: string;
provider: string;
kind: LogicSrcAccountKind;
principal: LogicSrcPrincipal;
action: string;
decision: LogicSrcPolicyDecision;
riskScore?: number;
requestPreview?: Record<string, unknown>;
resultPreview?: Record<string, unknown>;
correlationId?: string;
createdAt?: string;
}): LogicSrcAccountAuditEvent {
return {
id: input.id ?? `acct_audit_${Date.now()}`,
accountId: input.accountId,
provider: input.provider,
kind: input.kind,
principal: input.principal,
action: input.action,
decision: input.decision,
riskScore: input.riskScore ?? 0,
requestPreview: redactedPreview(input.requestPreview ?? {}),
resultPreview: redactedPreview(input.resultPreview ?? {}),
correlationId: input.correlationId,
createdAt: input.createdAt ?? new Date().toISOString()
};
}

View file

@ -0,0 +1,57 @@
import { describe, expect, it } from "vitest";
import { createAccountAuditEvent, createProviderRegistry, evaluateAccountPolicy, isPolicyGatedPermission, riskBandForScore, scoreAccountActionRisk } from "./index.js";
describe("account-core", () => {
it("indexes account providers by kind and id", () => {
const registry = createProviderRegistry([
{ id: "gmail", name: "Gmail", kind: "email", authMethods: ["oauth2"], capabilities: ["email.search"] },
{ id: "mastodon", name: "Mastodon", kind: "social", authMethods: ["oauth2"], capabilities: ["social.post.publish"] }
]);
expect(registry.list("email")).toHaveLength(1);
expect(registry.require("mastodon").name).toBe("Mastodon");
});
it("keeps write and private actions policy-gated", () => {
expect(isPolicyGatedPermission("email:send")).toBe(true);
expect(isPolicyGatedPermission("social:profile:read")).toBe(false);
});
it("scores and bands risky account actions", () => {
const score = scoreAccountActionRisk({ action: "email:send", externalRecipientCount: 1, hasAttachment: true, sensitiveKeywordDetected: true });
expect(score).toBe(0.55);
expect(riskBandForScore(score)).toBe("high");
});
it("requires approval for gated actions with matching grants", () => {
const result = evaluateAccountPolicy({
action: "email:send",
principal: { type: "agent", id: "marketing-agent" },
grant: {
id: "grant_1",
accountId: "account_1",
principal: { type: "agent", id: "marketing-agent" },
permissions: ["email:send"],
policy: [],
createdAt: new Date(0).toISOString()
}
});
expect(result.decision).toBe("approval_required");
});
it("redacts secret-like audit previews", () => {
const event = createAccountAuditEvent({
provider: "gmail",
kind: "email",
principal: { type: "user", id: "user_1" },
action: "accounts:connect",
decision: "allow",
requestPreview: { accessToken: "raw-token", provider: "gmail" }
});
expect(event.requestPreview.accessToken).toBe("[redacted]");
expect(event.requestPreview.provider).toBe("gmail");
});
});

View file

@ -0,0 +1,13 @@
export * from "./types.js";
export {
ACCOUNT_PERMISSIONS,
EMAIL_PERMISSIONS,
POLICY_GATED_PERMISSIONS,
SOCIAL_PERMISSIONS,
SHARED_ACCOUNT_PERMISSIONS,
accountPermissionList,
isPolicyGatedPermission
} from "./permissions.js";
export { createProviderRegistry } from "./provider-registry.js";
export { createAccountAuditEvent, redactedPreview } from "./audit.js";
export { evaluateAccountPolicy, riskBandForScore, scoreAccountActionRisk } from "./policy.js";

View file

@ -0,0 +1,59 @@
export const SHARED_ACCOUNT_PERMISSIONS = [
"accounts:connect",
"accounts:list",
"accounts:read_metadata",
"accounts:test",
"accounts:revoke",
"accounts:sync",
"accounts:audit:read"
] as const;
export const SOCIAL_PERMISSIONS = [
"social:profile:read",
"social:post:draft",
"social:post:publish",
"social:post:delete",
"social:media:upload",
"social:mentions:read",
"social:comments:read",
"social:dm:read",
"social:dm:send",
"social:analytics:read"
] as const;
export const EMAIL_PERMISSIONS = [
"email:headers:read",
"email:body:read",
"email:attachments:read",
"email:search",
"email:draft",
"email:send",
"email:reply",
"email:forward",
"email:archive",
"email:labels:modify",
"email:delete",
"email:sync"
] as const;
export const ACCOUNT_PERMISSIONS = [...SHARED_ACCOUNT_PERMISSIONS, ...SOCIAL_PERMISSIONS, ...EMAIL_PERMISSIONS] as const;
export const POLICY_GATED_PERMISSIONS = [
"social:post:publish",
"social:post:delete",
"social:dm:read",
"social:dm:send",
"email:attachments:read",
"email:send",
"email:delete"
] as const;
export type LogicSrcAccountPermission = (typeof ACCOUNT_PERMISSIONS)[number];
export function accountPermissionList() {
return [...ACCOUNT_PERMISSIONS];
}
export function isPolicyGatedPermission(permission: string) {
return (POLICY_GATED_PERMISSIONS as readonly string[]).includes(permission);
}

View file

@ -0,0 +1,92 @@
import { isPolicyGatedPermission } from "./permissions.js";
import type { LogicSrcPolicyEvaluationInput, LogicSrcPolicyEvaluationResult, LogicSrcRiskBand } from "./types.js";
const WRITE_ACTIONS = new Set([
"social:post:publish",
"social:post:delete",
"social:dm:send",
"email:send",
"email:reply",
"email:forward",
"email:delete",
"email:labels:modify",
"email:archive"
]);
export function riskBandForScore(score: number): LogicSrcRiskBand {
if (score >= 0.75) {
return "critical";
}
if (score >= 0.5) {
return "high";
}
if (score >= 0.25) {
return "medium";
}
return "low";
}
export function scoreAccountActionRisk(input: {
action: string;
externalRecipientCount?: number;
newRecipientOrDomain?: boolean;
hasAttachment?: boolean;
highReachAccount?: boolean;
sensitiveKeywordDetected?: boolean;
rawCredentialAccessAttempted?: boolean;
}) {
let score = 0;
if (input.externalRecipientCount && input.externalRecipientCount > 0) score += 0.1;
if (input.newRecipientOrDomain) score += 0.15;
if (input.hasAttachment) score += 0.15;
if (input.action === "social:post:publish") score += 0.2;
if (input.action.includes("delete")) score += 0.2;
if (input.action.includes("dm:read") || input.action.includes("attachments:read")) score += 0.25;
if (input.highReachAccount) score += 0.25;
if (input.sensitiveKeywordDetected) score += 0.3;
if (input.rawCredentialAccessAttempted) score += 0.4;
return Math.min(1, Number(score.toFixed(2)));
}
export function evaluateAccountPolicy(input: LogicSrcPolicyEvaluationInput): LogicSrcPolicyEvaluationResult {
const riskScore = Math.min(1, Math.max(0, input.riskScore ?? scoreAccountActionRisk({ action: input.action })));
const grantActive = input.grant && !input.grant.revokedAt && (!input.grant.expiresAt || Date.parse(input.grant.expiresAt) > Date.now());
const hasPermission = Boolean(grantActive && input.grant?.permissions.includes(input.action));
if (!hasPermission) {
return { decision: "deny", riskScore, reason: `missing grant for ${input.action}` };
}
if (input.dryRun) {
return { decision: "allow", riskScore, reason: "dry run with matching grant" };
}
const policy = input.grant?.policy.find((entry) => entry.action === input.action);
if (policy?.default === "deny") {
return { decision: "deny", riskScore, reason: `policy ${policy.id} denies ${input.action}` };
}
if (policy?.default === "approval_required") {
return { decision: "approval_required", riskScore, reason: `policy ${policy.id} requires approval` };
}
if (policy?.default === "allow_if_trusted_agent" && input.principal?.trusted) {
return { decision: "allow", riskScore, reason: `policy ${policy.id} allows trusted principal` };
}
if (policy?.default === "allow_if_below_risk_score") {
const maxRiskScore = typeof policy.conditions?.maxRiskScore === "number" ? policy.conditions.maxRiskScore : 0.25;
return riskScore <= maxRiskScore
? { decision: "allow", riskScore, reason: `risk score is within policy ${policy.id}` }
: { decision: "approval_required", riskScore, reason: `risk score exceeds policy ${policy.id}` };
}
const band = riskBandForScore(riskScore);
if (band === "critical") {
return { decision: "deny", riskScore, reason: "critical risk requires admin override" };
}
if (isPolicyGatedPermission(input.action) || (WRITE_ACTIONS.has(input.action) && band !== "low")) {
return { decision: "approval_required", riskScore, reason: "default gate for risky account action" };
}
return { decision: "allow", riskScore, reason: "grant allows account action" };
}

View file

@ -0,0 +1,28 @@
import type { LogicSrcAccountKind, LogicSrcAccountProviderManifest } from "./types.js";
export function createProviderRegistry(providers: LogicSrcAccountProviderManifest[]) {
const byId = new Map<string, LogicSrcAccountProviderManifest>();
for (const provider of providers) {
if (byId.has(provider.id)) {
throw new Error(`Duplicate account provider: ${provider.id}`);
}
byId.set(provider.id, provider);
}
return {
list(kind?: LogicSrcAccountKind) {
return providers.filter((provider) => !kind || provider.kind === kind);
},
get(id: string) {
return byId.get(id);
},
require(id: string) {
const provider = byId.get(id);
if (!provider) {
throw new Error(`Unknown account provider: ${id}`);
}
return provider;
}
};
}

View file

@ -0,0 +1,447 @@
export type LogicSrcAccountKind = "social" | "email";
export type LogicSrcAccountStatus = "connected" | "expired" | "revoked" | "error" | "disabled" | "pending";
export type LogicSrcAccountAuthMethod = "oauth2" | "api_key" | "imap_smtp" | "local_bridge";
export type LogicSrcPrincipalType = "user" | "agent" | "workflow" | "plugin";
export type LogicSrcPolicyMode =
| "allow"
| "approval_required"
| "deny"
| "allow_if_dry_run"
| "allow_if_trusted_agent"
| "allow_if_below_risk_score";
export type LogicSrcPolicyDecision = "allow" | "approval_required" | "deny";
export type LogicSrcRiskBand = "low" | "medium" | "high" | "critical";
export interface LogicSrcConnectedAccount {
id: string;
orgId?: string;
projectId?: string;
boardId?: string;
ownerUserId: string;
kind: LogicSrcAccountKind;
provider: string;
providerAccountId?: string;
displayName: string;
handle?: string;
email?: string;
avatarUrl?: string;
homepageUrl?: string;
status: LogicSrcAccountStatus;
scopes: string[];
capabilities: string[];
credentialRef: string;
metadata: Record<string, unknown>;
createdAt: string;
updatedAt: string;
lastSyncedAt?: string;
}
export interface LogicSrcAccountProviderManifest {
id: string;
name: string;
kind: LogicSrcAccountKind;
authMethods: LogicSrcAccountAuthMethod[];
capabilities: string[];
defaultScopes?: string[];
status?: "available" | "planned" | "disabled";
docsUrl?: string;
}
export interface LogicSrcAccountProvider extends LogicSrcAccountProviderManifest {
getAuthUrl?(input: AuthUrlInput): Promise<AuthUrlResult>;
completeAuth?(input: CompleteAuthInput): Promise<ConnectedAccountResult>;
refreshCredential?(input: RefreshCredentialInput): Promise<CredentialRefreshResult>;
testConnection(input: TestConnectionInput): Promise<TestConnectionResult>;
revoke?(input: RevokeAccountInput): Promise<RevokeAccountResult>;
}
export interface SocialAccountProvider extends LogicSrcAccountProvider {
kind: "social";
getProfile(input: SocialAccountInput): Promise<SocialProfile>;
draftPost(input: DraftSocialPostInput): Promise<SocialDraft>;
publishPost(input: PublishSocialPostInput): Promise<PublishedSocialPost>;
uploadMedia?(input: UploadSocialMediaInput): Promise<UploadedMedia>;
searchMentions?(input: SearchMentionsInput): Promise<SocialMention[]>;
listComments?(input: ListCommentsInput): Promise<SocialComment[]>;
getAnalytics?(input: SocialAnalyticsInput): Promise<SocialAnalyticsResult>;
}
export interface EmailAccountProvider extends LogicSrcAccountProvider {
kind: "email";
searchMessages(input: EmailSearchInput): Promise<EmailSearchResult>;
readMessage(input: ReadEmailMessageInput): Promise<EmailMessage>;
draftMessage(input: DraftEmailInput): Promise<EmailDraft>;
sendMessage(input: SendEmailInput): Promise<SentEmailResult>;
replyToMessage?(input: ReplyEmailInput): Promise<SentEmailResult>;
forwardMessage?(input: ForwardEmailInput): Promise<SentEmailResult>;
archiveMessage?(input: EmailMessageMutationInput): Promise<EmailMutationResult>;
applyLabels?(input: EmailLabelInput): Promise<EmailMutationResult>;
deleteMessage?(input: EmailMessageMutationInput): Promise<EmailMutationResult>;
}
export interface AuthUrlInput {
redirectUri: string;
state: string;
scopes: string[];
metadata?: Record<string, unknown>;
}
export interface AuthUrlResult {
url: string;
state: string;
expiresAt?: string;
}
export interface CompleteAuthInput {
code: string;
redirectUri: string;
state: string;
principal: LogicSrcPrincipal;
}
export interface ConnectedAccountResult {
account: LogicSrcConnectedAccount;
auditEvent: LogicSrcAccountAuditEvent;
}
export interface RefreshCredentialInput {
account: LogicSrcConnectedAccount;
broker: LogicSrcCredentialBroker;
}
export interface CredentialRefreshResult {
credentialRef: string;
scopes: string[];
expiresAt?: string;
}
export interface TestConnectionInput {
account: LogicSrcConnectedAccount;
broker: LogicSrcCredentialBroker;
}
export interface TestConnectionResult {
ok: boolean;
provider: string;
checkedAt: string;
message?: string;
}
export interface RevokeAccountInput {
account: LogicSrcConnectedAccount;
broker: LogicSrcCredentialBroker;
principal: LogicSrcPrincipal;
}
export interface RevokeAccountResult {
ok: boolean;
revokedAt: string;
}
export interface LogicSrcPrincipal {
type: LogicSrcPrincipalType;
id: string;
trusted?: boolean;
}
export interface LogicSrcAccountPermissionGrant {
id: string;
accountId: string;
principal: LogicSrcPrincipal;
permissions: string[];
policy: LogicSrcAccountPolicy[];
expiresAt?: string;
createdBy?: string;
createdAt: string;
revokedAt?: string;
}
export interface LogicSrcAccountPolicy {
id: string;
resource: string;
action: string;
default: LogicSrcPolicyMode;
conditions?: Record<string, unknown>;
}
export interface LogicSrcPolicyEvaluationInput {
action: string;
grant?: LogicSrcAccountPermissionGrant;
riskScore?: number;
dryRun?: boolean;
principal?: LogicSrcPrincipal;
}
export interface LogicSrcPolicyEvaluationResult {
decision: LogicSrcPolicyDecision;
riskScore: number;
reason: string;
}
export interface LogicSrcCredentialBroker {
getCredential(input: CredentialBrokerGetInput): Promise<CredentialBrokerGetResult>;
storeCredential(input: CredentialBrokerStoreInput): Promise<CredentialBrokerStoreResult>;
revokeCredential(input: CredentialBrokerRevokeInput): Promise<CredentialBrokerRevokeResult>;
}
export interface CredentialBrokerGetInput {
credentialRef: string;
accountId: string;
provider: string;
purpose: string;
principal: LogicSrcPrincipal;
}
export interface CredentialBrokerGetResult {
credentialRef: string;
token: string;
expiresAt?: string;
}
export interface CredentialBrokerStoreInput {
provider: string;
kind: LogicSrcAccountKind;
scopes: string[];
secret: string;
metadata?: Record<string, unknown>;
}
export interface CredentialBrokerStoreResult {
credentialRef: string;
}
export interface CredentialBrokerRevokeInput {
credentialRef: string;
accountId: string;
provider: string;
principal: LogicSrcPrincipal;
}
export interface CredentialBrokerRevokeResult {
ok: boolean;
revokedAt: string;
}
export interface LogicSrcAccountAuditEvent {
id: string;
accountId?: string;
provider: string;
kind: LogicSrcAccountKind;
principal: LogicSrcPrincipal;
action: string;
decision: LogicSrcPolicyDecision;
riskScore: number;
requestPreview: Record<string, unknown>;
resultPreview: Record<string, unknown>;
correlationId?: string;
createdAt: string;
}
export interface SocialAccountInput {
account: LogicSrcConnectedAccount;
broker: LogicSrcCredentialBroker;
}
export interface SocialProfile {
providerAccountId: string;
displayName: string;
handle?: string;
avatarUrl?: string;
homepageUrl?: string;
metadata?: Record<string, unknown>;
}
export interface DraftSocialPostInput extends SocialAccountInput {
text: string;
media?: SocialMediaInput[];
principal: LogicSrcPrincipal;
}
export interface PublishSocialPostInput extends DraftSocialPostInput {
dryRun?: boolean;
approvalId?: string;
}
export interface SocialDraft {
id: string;
accountId: string;
text: string;
media: SocialMediaInput[];
createdAt: string;
}
export interface PublishedSocialPost {
providerPostId?: string;
url?: string;
publishedAt: string;
dryRun?: boolean;
}
export interface SocialMediaInput {
url?: string;
fileRef?: string;
altText?: string;
mimeType?: string;
}
export interface UploadSocialMediaInput extends SocialAccountInput {
media: SocialMediaInput;
}
export interface UploadedMedia {
providerMediaId: string;
url?: string;
}
export interface SearchMentionsInput extends SocialAccountInput {
query?: string;
limit?: number;
}
export interface SocialMention {
id: string;
text: string;
authorHandle?: string;
url?: string;
createdAt?: string;
}
export interface ListCommentsInput extends SocialAccountInput {
postId: string;
limit?: number;
}
export interface SocialComment extends SocialMention {
postId: string;
}
export interface SocialAnalyticsInput extends SocialAccountInput {
postId?: string;
since?: string;
until?: string;
}
export interface SocialAnalyticsResult {
metrics: Record<string, number>;
metadata?: Record<string, unknown>;
}
export interface EmailSearchInput {
account: LogicSrcConnectedAccount;
broker: LogicSrcCredentialBroker;
query: string;
limit?: number;
headersOnly?: boolean;
principal: LogicSrcPrincipal;
}
export interface EmailSearchResult {
messages: EmailMessageMetadata[];
nextPageToken?: string;
}
export interface EmailMessageMetadata {
id: string;
providerMessageId: string;
threadId?: string;
subject?: string;
fromAddress?: string;
toAddresses: string[];
ccAddresses: string[];
snippet?: string;
labels: string[];
hasAttachments: boolean;
receivedAt?: string;
sentAt?: string;
}
export interface ReadEmailMessageInput {
account: LogicSrcConnectedAccount;
broker: LogicSrcCredentialBroker;
messageId: string;
includeBody?: boolean;
includeAttachments?: boolean;
principal: LogicSrcPrincipal;
}
export interface EmailMessage extends EmailMessageMetadata {
bodyText?: string;
bodyHtml?: string;
attachments?: EmailAttachment[];
}
export interface EmailAttachment {
id: string;
filename: string;
mimeType: string;
sizeBytes?: number;
contentRef?: string;
}
export interface DraftEmailInput {
account: LogicSrcConnectedAccount;
to: string[];
cc?: string[];
bcc?: string[];
subject: string;
bodyText?: string;
bodyHtml?: string;
attachmentRefs?: string[];
principal: LogicSrcPrincipal;
}
export interface EmailDraft {
id: string;
accountId: string;
to: string[];
cc: string[];
bcc: string[];
subject: string;
bodyPreview: string;
attachmentRefs: string[];
createdAt: string;
}
export interface SendEmailInput extends DraftEmailInput {
draftId?: string;
dryRun?: boolean;
approvalId?: string;
}
export interface SentEmailResult {
providerMessageId?: string;
sentAt: string;
dryRun?: boolean;
}
export interface ReplyEmailInput extends SendEmailInput {
messageId: string;
}
export interface ForwardEmailInput extends SendEmailInput {
messageId: string;
}
export interface EmailMessageMutationInput {
account: LogicSrcConnectedAccount;
broker: LogicSrcCredentialBroker;
messageId: string;
principal: LogicSrcPrincipal;
}
export interface EmailLabelInput extends EmailMessageMutationInput {
add?: string[];
remove?: string[];
}
export interface EmailMutationResult {
ok: boolean;
messageId: string;
changedAt: string;
}

View file

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
},
"include": ["src/**/*.ts"]
}

View file

@ -14,9 +14,12 @@
"test": "vitest run src"
},
"dependencies": {
"@logicsrc/account-core": "file:../account-core",
"@logicsrc/plugin-core": "file:../plugin-core",
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
"@logicsrc/plugin-email-accounts": "file:../../plugins/email-accounts",
"@logicsrc/plugin-feed-discovery": "file:../../plugins/feed-discovery",
"@logicsrc/plugin-social-accounts": "file:../../plugins/social-accounts",
"@logicsrc/plugin-ugig": "file:../../plugins/ugig",
"@logicsrc/tui": "file:../tui",
"@logicsrc/validators": "file:../validators",

View file

@ -1,7 +1,10 @@
#!/usr/bin/env node
import { readFileSync } from "node:fs";
import { evaluateAccountPolicy, scoreAccountActionRisk } from "@logicsrc/account-core";
import { Command } from "commander";
import { listEmailAccountProviders } from "@logicsrc/plugin-email-accounts";
import { discoverFeeds, listFeedProviders, probeSite, renderDiscoveryOutput, validateFeed, type FeedKind, type FeedOutputFormat } from "@logicsrc/plugin-feed-discovery";
import { listSocialAccountProviders } from "@logicsrc/plugin-social-accounts";
import { renderArcadeList, renderPluginStatus, renderTui, runArcadeSession, type TaskSnapshot } from "@logicsrc/tui";
import { assertSchemaKind, parseDocument, validate } from "@logicsrc/validators";
import { getConfigValue, readConfig, setConfigValue, writeConfig } from "./config.js";
@ -399,6 +402,132 @@ credentials.command("plan").option("--from <provider>", "Source provider", "env"
);
});
const accounts = program.command("accounts").description("Manage connected social and email accounts.");
accounts
.command("providers")
.option("--kind <kind>", "social or email")
.option("--format <format>", "table, json, or markdown", "table")
.description("List communication account providers.")
.action((options) => {
const providers = [...listSocialAccountProviders(), ...listEmailAccountProviders()].filter((provider) => !options.kind || provider.kind === options.kind);
print(providers, options.format as OutputFormat);
});
accounts
.command("list")
.alias("accounts")
.option("--kind <kind>", "social or email")
.option("--format <format>", "table, json, or markdown", "table")
.description("List connected accounts.")
.action((options) => {
print([], options.format as OutputFormat);
});
accounts
.command("audit")
.argument("<account-id>", "Connected account id")
.option("--format <format>", "table, json, or markdown", "table")
.description("List account audit events.")
.action((accountId, options) => {
print({ account_id: accountId, events: [], note: "Account audit persistence is not wired yet." }, options.format as OutputFormat);
});
const social = program.command("social").description("Manage social account providers and draft/publish flows.");
social.command("providers").option("--format <format>", "table, json, or markdown", "table").description("List social account providers.").action((options) => {
print(listSocialAccountProviders(), options.format as OutputFormat);
});
social.command("accounts").option("--format <format>", "table, json, or markdown", "table").description("List connected social accounts.").action((options) => {
print([], options.format as OutputFormat);
});
social
.command("post")
.argument("<account-id>", "Connected social account id")
.requiredOption("--text <text>", "Post text")
.option("--dry-run", "Evaluate without publishing", false)
.option("--format <format>", "table, json, or markdown", "table")
.description("Request or dry-run a social post publish.")
.action((accountId, options) => {
const riskScore = scoreAccountActionRisk({ action: "social:post:publish" });
const decision = evaluateAccountPolicy({
action: "social:post:publish",
dryRun: Boolean(options.dryRun),
riskScore,
grant: {
id: "dry_run_grant",
accountId,
principal: { type: "user", id: process.env.COMMANDBOARD_DID || "local-user" },
permissions: ["social:post:publish"],
policy: [],
createdAt: new Date(0).toISOString()
}
});
print(
{
provider: "unknown",
account_id: accountId,
action: "social:post:publish",
dry_run: Boolean(options.dryRun),
scopes_required: ["social:post:publish"],
policy_decision: decision.decision,
risk_score: decision.riskScore,
payload_preview: { text: options.text }
},
options.format as OutputFormat
);
});
const email = program.command("email").description("Manage email account providers and draft/send flows.");
email.command("providers").option("--format <format>", "table, json, or markdown", "table").description("List email account providers.").action((options) => {
print(listEmailAccountProviders(), options.format as OutputFormat);
});
email.command("accounts").option("--format <format>", "table, json, or markdown", "table").description("List connected email accounts.").action((options) => {
print([], options.format as OutputFormat);
});
email
.command("send")
.argument("<draft-id>", "Email draft id")
.option("--dry-run", "Evaluate without sending", false)
.option("--format <format>", "table, json, or markdown", "table")
.description("Request or dry-run an outbound email send.")
.action((draftId, options) => {
const riskScore = scoreAccountActionRisk({ action: "email:send", externalRecipientCount: 1 });
const decision = evaluateAccountPolicy({
action: "email:send",
dryRun: Boolean(options.dryRun),
riskScore,
grant: {
id: "dry_run_grant",
accountId: "unknown",
principal: { type: "user", id: process.env.COMMANDBOARD_DID || "local-user" },
permissions: ["email:send"],
policy: [],
createdAt: new Date(0).toISOString()
}
});
print(
{
provider: "unknown",
draft_id: draftId,
action: "email:send",
dry_run: Boolean(options.dryRun),
scopes_required: ["email:send"],
policy_decision: decision.decision,
risk_score: decision.riskScore,
payload_preview: { draft_id: draftId }
},
options.format as OutputFormat
);
});
program.command("tui").description("Launch the tmux-friendly TUI.").action(() => {
console.log(renderTui());
console.log("\nPlugin status:\n" + renderPluginStatus());

View file

@ -1,8 +1,10 @@
import { createPluginRegistry } from "@logicsrc/plugin-core";
import { coinPayPlugin } from "@logicsrc/plugin-coinpay";
import { emailAccountsPlugin } from "@logicsrc/plugin-email-accounts";
import { feedDiscoveryPlugin } from "@logicsrc/plugin-feed-discovery";
import { socialAccountsPlugin } from "@logicsrc/plugin-social-accounts";
import { uGigPlugin } from "@logicsrc/plugin-ugig";
export function defaultPluginRegistry() {
return createPluginRegistry([coinPayPlugin, uGigPlugin, feedDiscoveryPlugin]);
return createPluginRegistry([coinPayPlugin, uGigPlugin, feedDiscoveryPlugin, socialAccountsPlugin, emailAccountsPlugin]);
}

View file

@ -3,6 +3,7 @@ import { z } from "zod";
import { assertSchemaKind, parseDocument, schemas, validate, type SchemaKind } from "@logicsrc/validators";
const docs = {
"communication-accounts": `LogicSRC Communication Accounts defines shared contracts for connecting social and email identities, granting scoped human/agent/plugin access, evaluating policy gates, brokering credentials, and auditing every account action without exposing raw secrets.`,
positioning: `LogicSRC is an open standards initiative for human and AI agent coordination, maintained by Profullstack, Inc.
CommandBoard.run is a hosted product by Profullstack, Inc., built on LogicSRC. LogicSRC defines identity, boards, posts, tasks, bounties, agents, agent runs, permissions, payments, escrow, reputation, events, webhooks, CLI commands, API schemas, and plugin contracts.`,
@ -80,7 +81,7 @@ export function createLogicSrcMcpServer() {
title: "Validate LogicSRC Document",
description: "Validates a JSON or YAML document against a LogicSRC schema kind.",
inputSchema: {
kind: z.enum(["agent", "event", "plugin", "run", "task"]),
kind: z.enum(["account-audit-event", "account-grant", "account-provider", "agent", "connected-account", "email-message", "event", "plugin", "run", "social-post", "task"]),
document: z.string().describe("JSON or YAML document text."),
fileName: z.string().optional().describe("Optional file name used to select JSON parsing when it ends with .json.")
},
@ -99,7 +100,7 @@ export function createLogicSrcMcpServer() {
title: "Generate Example LogicSRC Document",
description: "Returns a minimal example document for a LogicSRC schema kind.",
inputSchema: {
kind: z.enum(["agent", "event", "plugin", "run", "task"])
kind: z.enum(["account-audit-event", "account-grant", "account-provider", "agent", "connected-account", "email-message", "event", "plugin", "run", "social-post", "task"])
},
annotations: { readOnlyHint: true, openWorldHint: false }
},
@ -163,6 +164,36 @@ function titleCase(value: string) {
function exampleFor(kind: SchemaKind) {
switch (kind) {
case "account-audit-event":
return {
id: "acct_audit_123",
provider: "gmail",
kind: "email",
principal: { type: "agent", id: "marketing-agent" },
action: "email:send",
decision: "approval_required",
riskScore: 0.35,
requestPreview: { draft_id: "draft_123" },
resultPreview: {},
createdAt: new Date(0).toISOString()
};
case "account-grant":
return {
id: "grant_123",
accountId: "account_123",
principal: { type: "agent", id: "marketing-agent" },
permissions: ["email:headers:read", "email:draft"],
policy: [],
createdAt: new Date(0).toISOString()
};
case "account-provider":
return {
id: "gmail",
name: "Gmail",
kind: "email",
authMethods: ["oauth2"],
capabilities: ["email.headers.read", "email.search"]
};
case "agent":
return {
type: "logicsrc.agent",
@ -172,6 +203,32 @@ function exampleFor(kind: SchemaKind) {
capabilities: ["browser.qa", "report.write"],
status: "active"
};
case "connected-account":
return {
id: "account_123",
ownerUserId: "user_123",
kind: "email",
provider: "gmail",
displayName: "Founder Inbox",
email: "founder@example.com",
status: "connected",
scopes: ["gmail.metadata"],
capabilities: ["email.headers.read", "email.search"],
credentialRef: "cred://gmail/account_123",
metadata: {},
createdAt: new Date(0).toISOString(),
updatedAt: new Date(0).toISOString()
};
case "email-message":
return {
id: "email_msg_123",
providerMessageId: "provider_msg_123",
subject: "Hello",
toAddresses: ["founder@example.com"],
ccAddresses: [],
labels: ["inbox"],
hasAttachments: false
};
case "event":
return {
type: "logicsrc.event",
@ -203,6 +260,17 @@ function exampleFor(kind: SchemaKind) {
status: "completed",
started_at: new Date(0).toISOString()
};
case "social-post":
return {
id: "social_post_123",
accountId: "account_123",
status: "draft",
text: "Launching today",
media: [],
metadata: {},
createdAt: new Date(0).toISOString(),
updatedAt: new Date(0).toISOString()
};
case "task":
return {
type: "logicsrc.task",

View file

@ -8,7 +8,13 @@
"./agent": "./schemas/logicsrc-agent.schema.json",
"./run": "./schemas/logicsrc-run.schema.json",
"./event": "./schemas/logicsrc-event.schema.json",
"./plugin": "./schemas/logicsrc-plugin.schema.json"
"./plugin": "./schemas/logicsrc-plugin.schema.json",
"./connected-account": "./schemas/logicsrc-connected-account.schema.json",
"./account-provider": "./schemas/logicsrc-account-provider.schema.json",
"./account-grant": "./schemas/logicsrc-account-grant.schema.json",
"./account-audit-event": "./schemas/logicsrc-account-audit-event.schema.json",
"./email-message": "./schemas/logicsrc-email-message.schema.json",
"./social-post": "./schemas/logicsrc-social-post.schema.json"
},
"files": [
"schemas"

View file

@ -0,0 +1,31 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.logicsrc.com/logicsrc-account-audit-event.schema.json",
"title": "LogicSRC Account Audit Event",
"type": "object",
"required": ["id", "provider", "kind", "principal", "action", "decision", "riskScore", "requestPreview", "resultPreview", "createdAt"],
"additionalProperties": false,
"properties": {
"id": { "type": "string", "minLength": 1 },
"accountId": { "type": "string", "minLength": 1 },
"provider": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
"kind": { "enum": ["social", "email"] },
"principal": {
"type": "object",
"required": ["type", "id"],
"additionalProperties": false,
"properties": {
"type": { "enum": ["user", "agent", "workflow", "plugin"] },
"id": { "type": "string", "minLength": 1 },
"trusted": { "type": "boolean" }
}
},
"action": { "type": "string", "pattern": "^[a-z][a-z0-9_-]*(:[a-z][a-z0-9_-]*)+$" },
"decision": { "enum": ["allow", "approval_required", "deny"] },
"riskScore": { "type": "number", "minimum": 0, "maximum": 1 },
"requestPreview": { "type": "object" },
"resultPreview": { "type": "object" },
"correlationId": { "type": "string", "minLength": 1 },
"createdAt": { "type": "string", "format": "date-time" }
}
}

View file

@ -0,0 +1,48 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.logicsrc.com/logicsrc-account-grant.schema.json",
"title": "LogicSRC Account Permission Grant",
"type": "object",
"required": ["id", "accountId", "principal", "permissions", "policy", "createdAt"],
"additionalProperties": false,
"properties": {
"id": { "type": "string", "minLength": 1 },
"accountId": { "type": "string", "minLength": 1 },
"principal": {
"type": "object",
"required": ["type", "id"],
"additionalProperties": false,
"properties": {
"type": { "enum": ["user", "agent", "workflow", "plugin"] },
"id": { "type": "string", "minLength": 1 },
"trusted": { "type": "boolean" }
}
},
"permissions": {
"type": "array",
"items": { "type": "string", "pattern": "^[a-z][a-z0-9_-]*(:[a-z][a-z0-9_-]*)+$" },
"uniqueItems": true
},
"policy": {
"type": "array",
"items": {
"type": "object",
"required": ["id", "resource", "action", "default"],
"additionalProperties": false,
"properties": {
"id": { "type": "string", "minLength": 1 },
"resource": { "type": "string", "minLength": 1 },
"action": { "type": "string", "pattern": "^[a-z][a-z0-9_-]*(:[a-z][a-z0-9_-]*)+$" },
"default": {
"enum": ["allow", "approval_required", "deny", "allow_if_dry_run", "allow_if_trusted_agent", "allow_if_below_risk_score"]
},
"conditions": { "type": "object" }
}
}
},
"expiresAt": { "type": "string", "format": "date-time" },
"createdBy": { "type": "string", "minLength": 1 },
"createdAt": { "type": "string", "format": "date-time" },
"revokedAt": { "type": "string", "format": "date-time" }
}
}

View file

@ -0,0 +1,31 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.logicsrc.com/logicsrc-account-provider.schema.json",
"title": "LogicSRC Account Provider",
"type": "object",
"required": ["id", "name", "kind", "authMethods", "capabilities"],
"additionalProperties": false,
"properties": {
"id": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
"name": { "type": "string", "minLength": 1 },
"kind": { "enum": ["social", "email"] },
"authMethods": {
"type": "array",
"items": { "enum": ["oauth2", "api_key", "imap_smtp", "local_bridge"] },
"minItems": 1,
"uniqueItems": true
},
"capabilities": {
"type": "array",
"items": { "type": "string", "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z][a-z0-9_-]*)+$" },
"uniqueItems": true
},
"defaultScopes": {
"type": "array",
"items": { "type": "string", "minLength": 1 },
"uniqueItems": true
},
"status": { "enum": ["available", "planned", "disabled"] },
"docsUrl": { "type": "string", "format": "uri" }
}
}

View file

@ -0,0 +1,39 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.logicsrc.com/logicsrc-connected-account.schema.json",
"title": "LogicSRC Connected Account",
"type": "object",
"required": ["id", "ownerUserId", "kind", "provider", "displayName", "status", "scopes", "capabilities", "credentialRef", "metadata", "createdAt", "updatedAt"],
"additionalProperties": false,
"properties": {
"id": { "type": "string", "minLength": 1 },
"orgId": { "type": "string", "minLength": 1 },
"projectId": { "type": "string", "minLength": 1 },
"boardId": { "type": "string", "minLength": 1 },
"ownerUserId": { "type": "string", "minLength": 1 },
"kind": { "enum": ["social", "email"] },
"provider": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" },
"providerAccountId": { "type": "string", "minLength": 1 },
"displayName": { "type": "string", "minLength": 1 },
"handle": { "type": "string", "minLength": 1 },
"email": { "type": "string", "format": "email" },
"avatarUrl": { "type": "string", "format": "uri" },
"homepageUrl": { "type": "string", "format": "uri" },
"status": { "enum": ["connected", "expired", "revoked", "error", "disabled", "pending"] },
"scopes": {
"type": "array",
"items": { "type": "string", "minLength": 1 },
"uniqueItems": true
},
"capabilities": {
"type": "array",
"items": { "type": "string", "pattern": "^[a-z][a-z0-9_-]*(\\.[a-z][a-z0-9_-]*)+$" },
"uniqueItems": true
},
"credentialRef": { "type": "string", "minLength": 1 },
"metadata": { "type": "object" },
"createdAt": { "type": "string", "format": "date-time" },
"updatedAt": { "type": "string", "format": "date-time" },
"lastSyncedAt": { "type": "string", "format": "date-time" }
}
}

View file

@ -0,0 +1,22 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.logicsrc.com/logicsrc-email-message.schema.json",
"title": "LogicSRC Email Message Metadata",
"type": "object",
"required": ["id", "providerMessageId", "toAddresses", "ccAddresses", "labels", "hasAttachments"],
"additionalProperties": false,
"properties": {
"id": { "type": "string", "minLength": 1 },
"providerMessageId": { "type": "string", "minLength": 1 },
"threadId": { "type": "string", "minLength": 1 },
"subject": { "type": "string" },
"fromAddress": { "type": "string" },
"toAddresses": { "type": "array", "items": { "type": "string" } },
"ccAddresses": { "type": "array", "items": { "type": "string" } },
"snippet": { "type": "string" },
"labels": { "type": "array", "items": { "type": "string" } },
"hasAttachments": { "type": "boolean" },
"receivedAt": { "type": "string", "format": "date-time" },
"sentAt": { "type": "string", "format": "date-time" }
}
}

View file

@ -0,0 +1,26 @@
{
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://schemas.logicsrc.com/logicsrc-social-post.schema.json",
"title": "LogicSRC Social Post",
"type": "object",
"required": ["id", "accountId", "status", "media", "metadata", "createdAt", "updatedAt"],
"additionalProperties": false,
"properties": {
"id": { "type": "string", "minLength": 1 },
"accountId": { "type": "string", "minLength": 1 },
"providerPostId": { "type": "string", "minLength": 1 },
"status": { "enum": ["draft", "pending_approval", "published", "deleted", "failed"] },
"text": { "type": "string" },
"media": {
"type": "array",
"items": { "type": "object" }
},
"url": { "type": "string", "format": "uri" },
"publishedAt": { "type": "string", "format": "date-time" },
"createdByPrincipalType": { "enum": ["user", "agent", "workflow", "plugin"] },
"createdByPrincipalId": { "type": "string", "minLength": 1 },
"metadata": { "type": "object" },
"createdAt": { "type": "string", "format": "date-time" },
"updatedAt": { "type": "string", "format": "date-time" }
}
}

View file

@ -11,6 +11,8 @@
"dependencies": {
"@logicsrc/plugin-core": "file:../plugin-core",
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
"@logicsrc/plugin-email-accounts": "file:../../plugins/email-accounts",
"@logicsrc/plugin-social-accounts": "file:../../plugins/social-accounts",
"@logicsrc/plugin-ugig": "file:../../plugins/ugig"
}
}

View file

@ -1,5 +1,7 @@
import { createPluginRegistry } from "@logicsrc/plugin-core";
import { coinPayPlugin } from "@logicsrc/plugin-coinpay";
import { emailAccountsPlugin } from "@logicsrc/plugin-email-accounts";
import { socialAccountsPlugin } from "@logicsrc/plugin-social-accounts";
import { uGigPlugin } from "@logicsrc/plugin-ugig";
export { ArcadeRegistry, createDefaultArcadeRegistry, renderArcadeList, renderArcadeSnapshot, runArcadeSession } from "./arcade/index.js";
export type { ArcadeEvent, GameAction, GameContext, GameControl, KeyEvent, TaskEvent, TaskSnapshot, TerminalFrame, WaitingGame } from "./arcade/index.js";
@ -20,7 +22,7 @@ const defaultState: TuiState = {
export function renderTui(state: Partial<TuiState> = {}) {
const view = { ...defaultState, ...state };
const registry = createPluginRegistry([coinPayPlugin, uGigPlugin]);
const registry = createPluginRegistry([coinPayPlugin, uGigPlugin, socialAccountsPlugin, emailAccountsPlugin]);
const plugins = registry.snapshot().plugins;
return [
@ -34,13 +36,13 @@ export function renderTui(state: Partial<TuiState> = {}) {
"│ /jobs │ [uGig] Senior AI Engineer remote │",
"├───────────────┴─────────────────────────────────────────────┤",
"│ Plugins: " + plugins.map((plugin) => `${plugin.name} ${plugin.enabled ? "enabled" : "disabled"}`).join(" | ").padEnd(50) + " │",
"│ Enter: open p: post t: task a: agents w: wallet q: quit │",
"│ Enter: open p: post t: task a: agents c: accounts q: quit │",
"└─────────────────────────────────────────────────────────────┘"
].join("\n");
}
export function renderPluginStatus() {
const registry = createPluginRegistry([coinPayPlugin, uGigPlugin]);
const registry = createPluginRegistry([coinPayPlugin, uGigPlugin, socialAccountsPlugin, emailAccountsPlugin]);
return registry
.snapshot()
.plugins.map((plugin) => `${plugin.id.padEnd(8)} ${plugin.enabled ? "enabled" : "disabled"} ${plugin.type.join(", ")}`)

View file

@ -1,14 +1,26 @@
import agentSchema from "../../schemas/schemas/logicsrc-agent.schema.json" with { type: "json" };
import accountAuditEventSchema from "../../schemas/schemas/logicsrc-account-audit-event.schema.json" with { type: "json" };
import accountGrantSchema from "../../schemas/schemas/logicsrc-account-grant.schema.json" with { type: "json" };
import accountProviderSchema from "../../schemas/schemas/logicsrc-account-provider.schema.json" with { type: "json" };
import connectedAccountSchema from "../../schemas/schemas/logicsrc-connected-account.schema.json" with { type: "json" };
import emailMessageSchema from "../../schemas/schemas/logicsrc-email-message.schema.json" with { type: "json" };
import eventSchema from "../../schemas/schemas/logicsrc-event.schema.json" with { type: "json" };
import pluginSchema from "../../schemas/schemas/logicsrc-plugin.schema.json" with { type: "json" };
import runSchema from "../../schemas/schemas/logicsrc-run.schema.json" with { type: "json" };
import socialPostSchema from "../../schemas/schemas/logicsrc-social-post.schema.json" with { type: "json" };
import taskSchema from "../../schemas/schemas/logicsrc-task.schema.json" with { type: "json" };
export const schemas = {
agent: agentSchema,
"account-audit-event": accountAuditEventSchema,
"account-grant": accountGrantSchema,
"account-provider": accountProviderSchema,
"connected-account": connectedAccountSchema,
"email-message": emailMessageSchema,
event: eventSchema,
plugin: pluginSchema,
run: runSchema,
"social-post": socialPostSchema,
task: taskSchema
} as const;

View file

@ -0,0 +1,5 @@
# LogicSRC Email Accounts Plugin
First-party LogicSRC plugin for connecting and governing email inboxes and outbound sending identities.
Initial provider manifests are intentionally contract-only. Live IMAP/SMTP, Gmail, and Microsoft Graph providers should be added behind `EmailAccountProvider` from `@logicsrc/account-core` after credential broker, approval, and audit persistence are available.

View file

@ -0,0 +1,19 @@
{
"name": "@logicsrc/plugin-email-accounts",
"version": "0.1.0",
"description": "LogicSRC email account management plugin for inbox, sending identity, provider, policy, and audit flows.",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc -p tsconfig.json",
"test": "vitest run src --passWithNoTests"
},
"dependencies": {
"@logicsrc/account-core": "file:../../packages/account-core",
"@logicsrc/plugin-core": "file:../../packages/plugin-core"
},
"devDependencies": {
"vitest": "^4.0.8"
}
}

View file

@ -0,0 +1,43 @@
import { createProviderRegistry } from "@logicsrc/account-core";
import type { PluginDefinition } from "@logicsrc/plugin-core";
import { emailAccountsManifest } from "./manifest.js";
import { emailAccountProviderManifests } from "./providers/index.js";
export const emailAccountsPlugin: PluginDefinition = {
manifest: emailAccountsManifest,
configDefaults: {
enabled: true,
default_send_policy: "approval_required",
credential_broker: "${LOGICSRC_CREDENTIAL_BROKER}"
},
routes: [
{ method: "GET", path: "/api/email/providers", capability: "email.providers.list" },
{ method: "GET", path: "/api/email/accounts", capability: "accounts.list" },
{ method: "POST", path: "/api/email/accounts/:id/search", capability: "email.search" },
{ method: "GET", path: "/api/email/messages/:messageId", capability: "email.headers.read" },
{ method: "POST", path: "/api/email/accounts/:id/drafts", capability: "email.draft" },
{ method: "POST", path: "/api/email/drafts/:draftId/send", capability: "email.send" },
{ method: "POST", path: "/api/email/messages/:messageId/labels", capability: "email.labels.modify" },
{ method: "DELETE", path: "/api/email/messages/:messageId", capability: "email.delete" }
],
permissions: [
"accounts:list",
"accounts:read_metadata",
"accounts:audit:read",
"email:headers:read",
"email:search",
"email:draft",
"email:send",
"email:labels:modify",
"email:delete"
],
tuiPanels: [{ id: "email-accounts", title: "Email Accounts" }]
};
const registry = createProviderRegistry(emailAccountProviderManifests);
export function listEmailAccountProviders() {
return registry.list("email");
}
export { emailAccountsManifest, emailAccountProviderManifests };

View file

@ -0,0 +1,39 @@
import type { PluginManifest } from "@logicsrc/plugin-core";
export const emailAccountsManifest: PluginManifest = {
id: "email-accounts",
name: "Email Accounts",
version: "0.1.0",
type: ["communication", "accounts", "email"],
default: true,
capabilities: [
"accounts.connect",
"accounts.list",
"accounts.read_metadata",
"accounts.test",
"accounts.revoke",
"accounts.sync",
"accounts.audit.read",
"email.providers.list",
"email.headers.read",
"email.body.read",
"email.attachments.read",
"email.search",
"email.draft",
"email.send",
"email.labels.modify",
"email.delete",
"email.sync"
],
commands: ["accounts", "email"],
env: [
"GMAIL_CLIENT_ID",
"GMAIL_CLIENT_SECRET",
"MICROSOFT_CLIENT_ID",
"MICROSOFT_CLIENT_SECRET",
"IMAP_HOST",
"IMAP_PORT",
"SMTP_HOST",
"SMTP_PORT"
]
};

View file

@ -0,0 +1,40 @@
import type { LogicSrcAccountProviderManifest } from "@logicsrc/account-core";
export const emailAccountProviderManifests: LogicSrcAccountProviderManifest[] = [
{
id: "imap-smtp",
name: "IMAP + SMTP",
kind: "email",
authMethods: ["imap_smtp"],
capabilities: ["email.headers.read", "email.search", "email.draft", "email.send"],
defaultScopes: ["imap.read", "smtp.send"],
status: "planned"
},
{
id: "gmail",
name: "Gmail",
kind: "email",
authMethods: ["oauth2"],
capabilities: ["email.headers.read", "email.search", "email.body.read", "email.draft", "email.send", "email.labels.modify"],
defaultScopes: ["https://www.googleapis.com/auth/gmail.metadata"],
status: "planned"
},
{
id: "microsoft-graph",
name: "Microsoft Graph / Outlook",
kind: "email",
authMethods: ["oauth2"],
capabilities: ["email.headers.read", "email.search", "email.body.read", "email.draft", "email.send"],
defaultScopes: ["Mail.ReadBasic", "Mail.Send", "offline_access"],
status: "planned"
},
{
id: "forwardemail",
name: "ForwardEmail.net",
kind: "email",
authMethods: ["imap_smtp", "api_key"],
capabilities: ["email.headers.read", "email.search", "email.draft", "email.send"],
defaultScopes: ["imap.read", "smtp.send"],
status: "planned"
}
];

View file

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
},
"include": ["src/**/*.ts"]
}

View file

@ -0,0 +1,5 @@
# LogicSRC Social Accounts Plugin
First-party LogicSRC plugin for connecting and governing social/network accounts.
Initial provider manifests are intentionally contract-only. Live provider adapters should be added behind `SocialAccountProvider` from `@logicsrc/account-core` after credential broker, approval, and audit persistence are available.

View file

@ -0,0 +1,19 @@
{
"name": "@logicsrc/plugin-social-accounts",
"version": "0.1.0",
"description": "LogicSRC social account management plugin for provider-agnostic profile, drafting, publishing, sync, policy, and audit flows.",
"type": "module",
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc -p tsconfig.json",
"test": "vitest run src --passWithNoTests"
},
"dependencies": {
"@logicsrc/account-core": "file:../../packages/account-core",
"@logicsrc/plugin-core": "file:../../packages/plugin-core"
},
"devDependencies": {
"vitest": "^4.0.8"
}
}

View file

@ -0,0 +1,41 @@
import { createProviderRegistry } from "@logicsrc/account-core";
import type { PluginDefinition } from "@logicsrc/plugin-core";
import { socialAccountsManifest } from "./manifest.js";
import { socialAccountProviderManifests } from "./providers/index.js";
export const socialAccountsPlugin: PluginDefinition = {
manifest: socialAccountsManifest,
configDefaults: {
enabled: true,
default_publish_policy: "approval_required",
credential_broker: "${LOGICSRC_CREDENTIAL_BROKER}"
},
routes: [
{ method: "GET", path: "/api/social/providers", capability: "social.providers.list" },
{ method: "GET", path: "/api/social/accounts", capability: "accounts.list" },
{ method: "GET", path: "/api/social/accounts/:id/profile", capability: "social.profile.read" },
{ method: "POST", path: "/api/social/accounts/:id/drafts", capability: "social.post.draft" },
{ method: "POST", path: "/api/social/accounts/:id/posts", capability: "social.post.publish" },
{ method: "GET", path: "/api/social/accounts/:id/mentions", capability: "social.mentions.read" },
{ method: "GET", path: "/api/social/accounts/:id/analytics", capability: "social.analytics.read" }
],
permissions: [
"accounts:list",
"accounts:read_metadata",
"accounts:audit:read",
"social:profile:read",
"social:post:draft",
"social:post:publish",
"social:mentions:read",
"social:analytics:read"
],
tuiPanels: [{ id: "social-accounts", title: "Social Accounts" }]
};
const registry = createProviderRegistry(socialAccountProviderManifests);
export function listSocialAccountProviders() {
return registry.list("social");
}
export { socialAccountsManifest, socialAccountProviderManifests };

View file

@ -0,0 +1,37 @@
import type { PluginManifest } from "@logicsrc/plugin-core";
export const socialAccountsManifest: PluginManifest = {
id: "social-accounts",
name: "Social Accounts",
version: "0.1.0",
type: ["communication", "accounts", "social"],
default: true,
capabilities: [
"accounts.connect",
"accounts.list",
"accounts.read_metadata",
"accounts.test",
"accounts.revoke",
"accounts.sync",
"accounts.audit.read",
"social.providers.list",
"social.profile.read",
"social.post.draft",
"social.post.publish",
"social.media.upload",
"social.mentions.read",
"social.analytics.read"
],
commands: ["accounts", "social"],
env: [
"MASTODON_CLIENT_ID",
"MASTODON_CLIENT_SECRET",
"BLUESKY_APP_PASSWORD",
"GITHUB_CLIENT_ID",
"GITHUB_CLIENT_SECRET",
"X_CLIENT_ID",
"X_CLIENT_SECRET",
"REDDIT_CLIENT_ID",
"REDDIT_CLIENT_SECRET"
]
};

View file

@ -0,0 +1,49 @@
import type { LogicSrcAccountProviderManifest } from "@logicsrc/account-core";
export const socialAccountProviderManifests: LogicSrcAccountProviderManifest[] = [
{
id: "mastodon",
name: "Mastodon",
kind: "social",
authMethods: ["oauth2"],
capabilities: ["social.profile.read", "social.post.draft", "social.post.publish", "social.mentions.read"],
defaultScopes: ["read:accounts", "read:statuses", "write:statuses"],
status: "planned"
},
{
id: "bluesky",
name: "Bluesky",
kind: "social",
authMethods: ["api_key"],
capabilities: ["social.profile.read", "social.post.draft", "social.post.publish"],
defaultScopes: ["atproto.session", "atproto.repo.read", "atproto.repo.write"],
status: "planned"
},
{
id: "github",
name: "GitHub",
kind: "social",
authMethods: ["oauth2", "api_key"],
capabilities: ["social.profile.read", "social.post.draft", "social.post.publish"],
defaultScopes: ["read:user", "repo"],
status: "planned"
},
{
id: "x",
name: "X / Twitter",
kind: "social",
authMethods: ["oauth2", "api_key"],
capabilities: ["social.profile.read", "social.post.draft", "social.post.publish", "social.mentions.read", "social.analytics.read"],
defaultScopes: ["tweet.read", "tweet.write", "users.read", "offline.access"],
status: "planned"
},
{
id: "reddit",
name: "Reddit",
kind: "social",
authMethods: ["oauth2"],
capabilities: ["social.profile.read", "social.post.draft", "social.post.publish", "social.comments.read"],
defaultScopes: ["identity", "read", "submit"],
status: "planned"
}
];

View file

@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"rootDir": "src",
"outDir": "dist"
},
"include": ["src/**/*.ts"]
}

View file

@ -0,0 +1,113 @@
create table if not exists connected_accounts (
id uuid primary key default gen_random_uuid(),
org_id uuid,
project_id uuid,
board_id uuid,
owner_user_id uuid not null,
kind text not null check (kind in ('social', 'email')),
provider text not null,
provider_account_id text,
display_name text not null,
handle text,
email text,
avatar_url text,
homepage_url text,
status text not null default 'pending' check (status in ('connected', 'expired', 'revoked', 'error', 'disabled', 'pending')),
scopes text[] not null default '{}',
capabilities text[] not null default '{}',
credential_ref text not null,
metadata jsonb not null default '{}',
last_synced_at timestamptz,
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create unique index if not exists connected_accounts_provider_identity_idx
on connected_accounts(provider, provider_account_id)
where provider_account_id is not null;
create index if not exists connected_accounts_owner_kind_idx
on connected_accounts(owner_user_id, kind);
create table if not exists account_permission_grants (
id uuid primary key default gen_random_uuid(),
account_id uuid not null references connected_accounts(id) on delete cascade,
principal_type text not null check (principal_type in ('user', 'agent', 'workflow', 'plugin')),
principal_id text not null,
permissions text[] not null default '{}',
policy jsonb not null default '[]',
expires_at timestamptz,
created_by uuid,
created_at timestamptz not null default now(),
revoked_at timestamptz
);
create index if not exists account_permission_grants_principal_idx
on account_permission_grants(principal_type, principal_id)
where revoked_at is null;
create table if not exists account_audit_events (
id uuid primary key default gen_random_uuid(),
account_id uuid references connected_accounts(id) on delete set null,
provider text not null,
kind text not null check (kind in ('social', 'email')),
principal_type text not null check (principal_type in ('user', 'agent', 'workflow', 'plugin')),
principal_id text not null,
action text not null,
decision text not null check (decision in ('allow', 'approval_required', 'deny')),
risk_score numeric not null default 0 check (risk_score >= 0 and risk_score <= 1),
request_preview jsonb not null default '{}',
result_preview jsonb not null default '{}',
correlation_id text,
ip_address inet,
user_agent text,
created_at timestamptz not null default now()
);
create index if not exists account_audit_events_account_created_idx
on account_audit_events(account_id, created_at desc);
create index if not exists account_audit_events_principal_created_idx
on account_audit_events(principal_type, principal_id, created_at desc);
create table if not exists email_message_cache (
id uuid primary key default gen_random_uuid(),
account_id uuid not null references connected_accounts(id) on delete cascade,
provider_message_id text not null,
thread_id text,
subject text,
from_address text,
to_addresses text[] not null default '{}',
cc_addresses text[] not null default '{}',
snippet text,
labels text[] not null default '{}',
has_attachments boolean not null default false,
received_at timestamptz,
sent_at timestamptz,
metadata jsonb not null default '{}',
created_at timestamptz not null default now(),
updated_at timestamptz not null default now(),
unique(account_id, provider_message_id)
);
create index if not exists email_message_cache_account_received_idx
on email_message_cache(account_id, received_at desc);
create table if not exists social_post_cache (
id uuid primary key default gen_random_uuid(),
account_id uuid not null references connected_accounts(id) on delete cascade,
provider_post_id text,
status text not null default 'draft' check (status in ('draft', 'pending_approval', 'published', 'deleted', 'failed')),
text text,
media jsonb not null default '[]',
url text,
published_at timestamptz,
created_by_principal_type text check (created_by_principal_type in ('user', 'agent', 'workflow', 'plugin')),
created_by_principal_id text,
metadata jsonb not null default '{}',
created_at timestamptz not null default now(),
updated_at timestamptz not null default now()
);
create index if not exists social_post_cache_account_created_idx
on social_post_cache(account_id, created_at desc);