mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-14 23:07:29 +00:00
Add communication account plugin scaffolds
This commit is contained in:
parent
5cfeea6b57
commit
c23ce42948
48 changed files with 1949 additions and 13 deletions
|
|
@ -15,8 +15,10 @@
|
||||||
"@logicsrc/plugin-core": "file:../../packages/plugin-core",
|
"@logicsrc/plugin-core": "file:../../packages/plugin-core",
|
||||||
"@logicsrc/plugin-c0mpute": "file:../../plugins/c0mpute",
|
"@logicsrc/plugin-c0mpute": "file:../../plugins/c0mpute",
|
||||||
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
|
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
|
||||||
|
"@logicsrc/plugin-email-accounts": "file:../../plugins/email-accounts",
|
||||||
"@logicsrc/plugin-feed-discovery": "file:../../plugins/feed-discovery",
|
"@logicsrc/plugin-feed-discovery": "file:../../plugins/feed-discovery",
|
||||||
"@logicsrc/plugin-sh1pt": "file:../../plugins/sh1pt",
|
"@logicsrc/plugin-sh1pt": "file:../../plugins/sh1pt",
|
||||||
|
"@logicsrc/plugin-social-accounts": "file:../../plugins/social-accounts",
|
||||||
"@logicsrc/plugin-ugig": "file:../../plugins/ugig",
|
"@logicsrc/plugin-ugig": "file:../../plugins/ugig",
|
||||||
"@logicsrc/validators": "file:../../packages/validators"
|
"@logicsrc/validators": "file:../../packages/validators"
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ describe("CommandBoard API contracts", () => {
|
||||||
};
|
};
|
||||||
|
|
||||||
expect(response.status).toBe(200);
|
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({
|
expect(body.plugins.find((plugin) => plugin.id === "sh1pt")).toMatchObject({
|
||||||
enabled: true,
|
enabled: true,
|
||||||
capabilities: expect.arrayContaining(["projects.sync", "actions.publish", "deployments.status"])
|
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["actions.publish"]).toEqual(["sh1pt"]);
|
||||||
expect(body.capabilities["compute.jobs.dispatch"]).toEqual(["c0mpute"]);
|
expect(body.capabilities["compute.jobs.dispatch"]).toEqual(["c0mpute"]);
|
||||||
expect(body.capabilities["feeds.discover"]).toEqual(["feed-discovery"]);
|
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 () => {
|
it("exposes feed discovery plugin endpoints", async () => {
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,14 @@ import { pathToFileURL } from "node:url";
|
||||||
import { createPluginRegistry } from "@logicsrc/plugin-core";
|
import { createPluginRegistry } from "@logicsrc/plugin-core";
|
||||||
import { c0mputePlugin } from "@logicsrc/plugin-c0mpute";
|
import { c0mputePlugin } from "@logicsrc/plugin-c0mpute";
|
||||||
import { coinPayPlugin } from "@logicsrc/plugin-coinpay";
|
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 { discoverFeeds, feedDiscoveryPlugin, listFeedProviders, renderAtom, renderJsonFeed, renderOpml, renderRss, type FeedKind } from "@logicsrc/plugin-feed-discovery";
|
||||||
import { sh1ptPlugin } from "@logicsrc/plugin-sh1pt";
|
import { sh1ptPlugin } from "@logicsrc/plugin-sh1pt";
|
||||||
|
import { listSocialAccountProviders, socialAccountsPlugin } from "@logicsrc/plugin-social-accounts";
|
||||||
import { uGigPlugin } from "@logicsrc/plugin-ugig";
|
import { uGigPlugin } from "@logicsrc/plugin-ugig";
|
||||||
import { schemas, validate } from "@logicsrc/validators";
|
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 = [
|
const boards = [
|
||||||
{ path: "/general", title: "General", description: "CommandBoard.run general discussion." },
|
{ path: "/general", title: "General", description: "CommandBoard.run general discussion." },
|
||||||
|
|
@ -70,7 +72,7 @@ async function route(request: IncomingMessage, response: ServerResponse) {
|
||||||
json(response, 200, {
|
json(response, 200, {
|
||||||
ok: true,
|
ok: true,
|
||||||
service: "commandboard-api",
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
@ -114,6 +116,38 @@ async function route(request: IncomingMessage, response: ServerResponse) {
|
||||||
return;
|
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")) {
|
if (request.method === "GET" && (url.pathname === "/api/feeds/providers" || url.pathname === "/api/rss/providers")) {
|
||||||
json(response, 200, { providers: listFeedProviders() });
|
json(response, 200, { providers: listFeedProviders() });
|
||||||
return;
|
return;
|
||||||
|
|
|
||||||
16
docs/cli.md
16
docs/cli.md
|
|
@ -31,6 +31,9 @@ task
|
||||||
wallet
|
wallet
|
||||||
events
|
events
|
||||||
agentswarm
|
agentswarm
|
||||||
|
accounts
|
||||||
|
social
|
||||||
|
email
|
||||||
credentials
|
credentials
|
||||||
openspec
|
openspec
|
||||||
plugins
|
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.
|
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>/`.
|
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:
|
Machine-readable output should be available anywhere data is returned:
|
||||||
|
|
|
||||||
139
docs/communication-accounts.md
Normal file
139
docs/communication-accounts.md
Normal 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.
|
||||||
|
|
@ -6,6 +6,9 @@ Core tables:
|
||||||
users
|
users
|
||||||
dids
|
dids
|
||||||
oauth_accounts
|
oauth_accounts
|
||||||
|
connected_accounts
|
||||||
|
account_permission_grants
|
||||||
|
account_audit_events
|
||||||
profiles
|
profiles
|
||||||
organizations
|
organizations
|
||||||
organization_members
|
organization_members
|
||||||
|
|
@ -34,6 +37,8 @@ notifications
|
||||||
schemas
|
schemas
|
||||||
schema_versions
|
schema_versions
|
||||||
plugin_audit_logs
|
plugin_audit_logs
|
||||||
|
email_message_cache
|
||||||
|
social_post_cache
|
||||||
```
|
```
|
||||||
|
|
||||||
Important relationships:
|
Important relationships:
|
||||||
|
|
@ -50,3 +55,4 @@ Important relationships:
|
||||||
- API keys belong to users, agents, or service accounts.
|
- API keys belong to users, agents, or service accounts.
|
||||||
- Permissions are scoped to resources.
|
- Permissions are scoped to resources.
|
||||||
- LogicSRC-compatible objects record their schema version.
|
- 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.
|
||||||
|
|
|
||||||
|
|
@ -43,6 +43,30 @@ paths:
|
||||||
responses:
|
responses:
|
||||||
"201":
|
"201":
|
||||||
description: Credential sync plan created
|
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:
|
/api/schemas:
|
||||||
get:
|
get:
|
||||||
summary: List supported LogicSRC schema kinds
|
summary: List supported LogicSRC schema kinds
|
||||||
|
|
|
||||||
|
|
@ -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.
|
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.
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,14 @@ Default plugins:
|
||||||
- uGig: job import, gig publishing, candidate/agent linking, bid sync, marketplace publishing, and reputation sync.
|
- 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.
|
- 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.
|
- 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:
|
Coming soon plugin specs:
|
||||||
|
|
||||||
- AgentByte: candidate, contractor, and agent capability screening for AI-era workflows. See `docs/agent-screening.md`.
|
- 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`.
|
- 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:
|
Runtime requirements:
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -27,5 +27,6 @@
|
||||||
25. Add SDK contracts for Rust, Bun, Node, Python, and curl.
|
25. Add SDK contracts for Rust, Bun, Node, Python, and curl.
|
||||||
26. Add MCP server contracts.
|
26. Add MCP server contracts.
|
||||||
27. Add credential sync audit exports.
|
27. Add credential sync audit exports.
|
||||||
28. Add docs.
|
28. Add communication account contracts, plugins, schemas, and account audit events.
|
||||||
29. Tag v1.0.0.
|
29. Add docs.
|
||||||
|
30. Tag v1.0.0.
|
||||||
|
|
|
||||||
48
package-lock.json
generated
48
package-lock.json
generated
|
|
@ -26,8 +26,10 @@
|
||||||
"@logicsrc/plugin-c0mpute": "file:../../plugins/c0mpute",
|
"@logicsrc/plugin-c0mpute": "file:../../plugins/c0mpute",
|
||||||
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
|
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
|
||||||
"@logicsrc/plugin-core": "file:../../packages/plugin-core",
|
"@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-feed-discovery": "file:../../plugins/feed-discovery",
|
||||||
"@logicsrc/plugin-sh1pt": "file:../../plugins/sh1pt",
|
"@logicsrc/plugin-sh1pt": "file:../../plugins/sh1pt",
|
||||||
|
"@logicsrc/plugin-social-accounts": "file:../../plugins/social-accounts",
|
||||||
"@logicsrc/plugin-ugig": "file:../../plugins/ugig",
|
"@logicsrc/plugin-ugig": "file:../../plugins/ugig",
|
||||||
"@logicsrc/validators": "file:../../packages/validators"
|
"@logicsrc/validators": "file:../../packages/validators"
|
||||||
},
|
},
|
||||||
|
|
@ -1424,6 +1426,10 @@
|
||||||
"@jridgewell/sourcemap-codec": "^1.4.14"
|
"@jridgewell/sourcemap-codec": "^1.4.14"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@logicsrc/account-core": {
|
||||||
|
"resolved": "packages/account-core",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@logicsrc/cli": {
|
"node_modules/@logicsrc/cli": {
|
||||||
"resolved": "packages/cli",
|
"resolved": "packages/cli",
|
||||||
"link": true
|
"link": true
|
||||||
|
|
@ -1448,6 +1454,10 @@
|
||||||
"resolved": "packages/plugin-core",
|
"resolved": "packages/plugin-core",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@logicsrc/plugin-email-accounts": {
|
||||||
|
"resolved": "plugins/email-accounts",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@logicsrc/plugin-feed-discovery": {
|
"node_modules/@logicsrc/plugin-feed-discovery": {
|
||||||
"resolved": "plugins/feed-discovery",
|
"resolved": "plugins/feed-discovery",
|
||||||
"link": true
|
"link": true
|
||||||
|
|
@ -1456,6 +1466,10 @@
|
||||||
"resolved": "plugins/sh1pt",
|
"resolved": "plugins/sh1pt",
|
||||||
"link": true
|
"link": true
|
||||||
},
|
},
|
||||||
|
"node_modules/@logicsrc/plugin-social-accounts": {
|
||||||
|
"resolved": "plugins/social-accounts",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/@logicsrc/plugin-ugig": {
|
"node_modules/@logicsrc/plugin-ugig": {
|
||||||
"resolved": "plugins/ugig",
|
"resolved": "plugins/ugig",
|
||||||
"link": true
|
"link": true
|
||||||
|
|
@ -6042,13 +6056,23 @@
|
||||||
"zod": "^3.25.28 || ^4"
|
"zod": "^3.25.28 || ^4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"packages/account-core": {
|
||||||
|
"name": "@logicsrc/account-core",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"devDependencies": {
|
||||||
|
"vitest": "^4.0.8"
|
||||||
|
}
|
||||||
|
},
|
||||||
"packages/cli": {
|
"packages/cli": {
|
||||||
"name": "@logicsrc/cli",
|
"name": "@logicsrc/cli",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@logicsrc/account-core": "file:../account-core",
|
||||||
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
|
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
|
||||||
"@logicsrc/plugin-core": "file:../plugin-core",
|
"@logicsrc/plugin-core": "file:../plugin-core",
|
||||||
|
"@logicsrc/plugin-email-accounts": "file:../../plugins/email-accounts",
|
||||||
"@logicsrc/plugin-feed-discovery": "file:../../plugins/feed-discovery",
|
"@logicsrc/plugin-feed-discovery": "file:../../plugins/feed-discovery",
|
||||||
|
"@logicsrc/plugin-social-accounts": "file:../../plugins/social-accounts",
|
||||||
"@logicsrc/plugin-ugig": "file:../../plugins/ugig",
|
"@logicsrc/plugin-ugig": "file:../../plugins/ugig",
|
||||||
"@logicsrc/tui": "file:../tui",
|
"@logicsrc/tui": "file:../tui",
|
||||||
"@logicsrc/validators": "file:../validators",
|
"@logicsrc/validators": "file:../validators",
|
||||||
|
|
@ -6104,6 +6128,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
|
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
|
||||||
"@logicsrc/plugin-core": "file:../plugin-core",
|
"@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"
|
"@logicsrc/plugin-ugig": "file:../../plugins/ugig"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
@ -6142,6 +6168,17 @@
|
||||||
"vitest": "^4.0.8"
|
"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": {
|
"plugins/feed-discovery": {
|
||||||
"name": "@logicsrc/plugin-feed-discovery",
|
"name": "@logicsrc/plugin-feed-discovery",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
|
@ -6168,6 +6205,17 @@
|
||||||
"vitest": "^4.0.8"
|
"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": {
|
"plugins/ugig": {
|
||||||
"name": "@logicsrc/plugin-ugig",
|
"name": "@logicsrc/plugin-ugig",
|
||||||
"version": "0.1.0",
|
"version": "0.1.0",
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,7 @@
|
||||||
"apps/*"
|
"apps/*"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"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",
|
"start": "npm --workspace @logicsrc/web run start",
|
||||||
"test": "npm run test --workspaces --if-present",
|
"test": "npm run test --workspaces --if-present",
|
||||||
"check": "npm run build && npm run test",
|
"check": "npm run build && npm run test",
|
||||||
|
|
|
||||||
15
packages/account-core/package.json
Normal file
15
packages/account-core/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
43
packages/account-core/src/audit.ts
Normal file
43
packages/account-core/src/audit.ts
Normal 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()
|
||||||
|
};
|
||||||
|
}
|
||||||
57
packages/account-core/src/index.test.ts
Normal file
57
packages/account-core/src/index.test.ts
Normal 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");
|
||||||
|
});
|
||||||
|
});
|
||||||
13
packages/account-core/src/index.ts
Normal file
13
packages/account-core/src/index.ts
Normal 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";
|
||||||
59
packages/account-core/src/permissions.ts
Normal file
59
packages/account-core/src/permissions.ts
Normal 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);
|
||||||
|
}
|
||||||
92
packages/account-core/src/policy.ts
Normal file
92
packages/account-core/src/policy.ts
Normal 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" };
|
||||||
|
}
|
||||||
28
packages/account-core/src/provider-registry.ts
Normal file
28
packages/account-core/src/provider-registry.ts
Normal 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;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
447
packages/account-core/src/types.ts
Normal file
447
packages/account-core/src/types.ts
Normal 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;
|
||||||
|
}
|
||||||
8
packages/account-core/tsconfig.json
Normal file
8
packages/account-core/tsconfig.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
||||||
|
|
@ -14,9 +14,12 @@
|
||||||
"test": "vitest run src"
|
"test": "vitest run src"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@logicsrc/account-core": "file:../account-core",
|
||||||
"@logicsrc/plugin-core": "file:../plugin-core",
|
"@logicsrc/plugin-core": "file:../plugin-core",
|
||||||
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
|
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
|
||||||
|
"@logicsrc/plugin-email-accounts": "file:../../plugins/email-accounts",
|
||||||
"@logicsrc/plugin-feed-discovery": "file:../../plugins/feed-discovery",
|
"@logicsrc/plugin-feed-discovery": "file:../../plugins/feed-discovery",
|
||||||
|
"@logicsrc/plugin-social-accounts": "file:../../plugins/social-accounts",
|
||||||
"@logicsrc/plugin-ugig": "file:../../plugins/ugig",
|
"@logicsrc/plugin-ugig": "file:../../plugins/ugig",
|
||||||
"@logicsrc/tui": "file:../tui",
|
"@logicsrc/tui": "file:../tui",
|
||||||
"@logicsrc/validators": "file:../validators",
|
"@logicsrc/validators": "file:../validators",
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,10 @@
|
||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import { readFileSync } from "node:fs";
|
import { readFileSync } from "node:fs";
|
||||||
|
import { evaluateAccountPolicy, scoreAccountActionRisk } from "@logicsrc/account-core";
|
||||||
import { Command } from "commander";
|
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 { 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 { renderArcadeList, renderPluginStatus, renderTui, runArcadeSession, type TaskSnapshot } from "@logicsrc/tui";
|
||||||
import { assertSchemaKind, parseDocument, validate } from "@logicsrc/validators";
|
import { assertSchemaKind, parseDocument, validate } from "@logicsrc/validators";
|
||||||
import { getConfigValue, readConfig, setConfigValue, writeConfig } from "./config.js";
|
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(() => {
|
program.command("tui").description("Launch the tmux-friendly TUI.").action(() => {
|
||||||
console.log(renderTui());
|
console.log(renderTui());
|
||||||
console.log("\nPlugin status:\n" + renderPluginStatus());
|
console.log("\nPlugin status:\n" + renderPluginStatus());
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,10 @@
|
||||||
import { createPluginRegistry } from "@logicsrc/plugin-core";
|
import { createPluginRegistry } from "@logicsrc/plugin-core";
|
||||||
import { coinPayPlugin } from "@logicsrc/plugin-coinpay";
|
import { coinPayPlugin } from "@logicsrc/plugin-coinpay";
|
||||||
|
import { emailAccountsPlugin } from "@logicsrc/plugin-email-accounts";
|
||||||
import { feedDiscoveryPlugin } from "@logicsrc/plugin-feed-discovery";
|
import { feedDiscoveryPlugin } from "@logicsrc/plugin-feed-discovery";
|
||||||
|
import { socialAccountsPlugin } from "@logicsrc/plugin-social-accounts";
|
||||||
import { uGigPlugin } from "@logicsrc/plugin-ugig";
|
import { uGigPlugin } from "@logicsrc/plugin-ugig";
|
||||||
|
|
||||||
export function defaultPluginRegistry() {
|
export function defaultPluginRegistry() {
|
||||||
return createPluginRegistry([coinPayPlugin, uGigPlugin, feedDiscoveryPlugin]);
|
return createPluginRegistry([coinPayPlugin, uGigPlugin, feedDiscoveryPlugin, socialAccountsPlugin, emailAccountsPlugin]);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import { z } from "zod";
|
||||||
import { assertSchemaKind, parseDocument, schemas, validate, type SchemaKind } from "@logicsrc/validators";
|
import { assertSchemaKind, parseDocument, schemas, validate, type SchemaKind } from "@logicsrc/validators";
|
||||||
|
|
||||||
const docs = {
|
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.
|
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.`,
|
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",
|
title: "Validate LogicSRC Document",
|
||||||
description: "Validates a JSON or YAML document against a LogicSRC schema kind.",
|
description: "Validates a JSON or YAML document against a LogicSRC schema kind.",
|
||||||
inputSchema: {
|
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."),
|
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.")
|
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",
|
title: "Generate Example LogicSRC Document",
|
||||||
description: "Returns a minimal example document for a LogicSRC schema kind.",
|
description: "Returns a minimal example document for a LogicSRC schema kind.",
|
||||||
inputSchema: {
|
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 }
|
annotations: { readOnlyHint: true, openWorldHint: false }
|
||||||
},
|
},
|
||||||
|
|
@ -163,6 +164,36 @@ function titleCase(value: string) {
|
||||||
|
|
||||||
function exampleFor(kind: SchemaKind) {
|
function exampleFor(kind: SchemaKind) {
|
||||||
switch (kind) {
|
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":
|
case "agent":
|
||||||
return {
|
return {
|
||||||
type: "logicsrc.agent",
|
type: "logicsrc.agent",
|
||||||
|
|
@ -172,6 +203,32 @@ function exampleFor(kind: SchemaKind) {
|
||||||
capabilities: ["browser.qa", "report.write"],
|
capabilities: ["browser.qa", "report.write"],
|
||||||
status: "active"
|
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":
|
case "event":
|
||||||
return {
|
return {
|
||||||
type: "logicsrc.event",
|
type: "logicsrc.event",
|
||||||
|
|
@ -203,6 +260,17 @@ function exampleFor(kind: SchemaKind) {
|
||||||
status: "completed",
|
status: "completed",
|
||||||
started_at: new Date(0).toISOString()
|
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":
|
case "task":
|
||||||
return {
|
return {
|
||||||
type: "logicsrc.task",
|
type: "logicsrc.task",
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,13 @@
|
||||||
"./agent": "./schemas/logicsrc-agent.schema.json",
|
"./agent": "./schemas/logicsrc-agent.schema.json",
|
||||||
"./run": "./schemas/logicsrc-run.schema.json",
|
"./run": "./schemas/logicsrc-run.schema.json",
|
||||||
"./event": "./schemas/logicsrc-event.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": [
|
"files": [
|
||||||
"schemas"
|
"schemas"
|
||||||
|
|
|
||||||
|
|
@ -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" }
|
||||||
|
}
|
||||||
|
}
|
||||||
48
packages/schemas/schemas/logicsrc-account-grant.schema.json
Normal file
48
packages/schemas/schemas/logicsrc-account-grant.schema.json
Normal 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" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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" }
|
||||||
|
}
|
||||||
|
}
|
||||||
22
packages/schemas/schemas/logicsrc-email-message.schema.json
Normal file
22
packages/schemas/schemas/logicsrc-email-message.schema.json
Normal 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" }
|
||||||
|
}
|
||||||
|
}
|
||||||
26
packages/schemas/schemas/logicsrc-social-post.schema.json
Normal file
26
packages/schemas/schemas/logicsrc-social-post.schema.json
Normal 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" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -11,6 +11,8 @@
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@logicsrc/plugin-core": "file:../plugin-core",
|
"@logicsrc/plugin-core": "file:../plugin-core",
|
||||||
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
|
"@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"
|
"@logicsrc/plugin-ugig": "file:../../plugins/ugig"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import { createPluginRegistry } from "@logicsrc/plugin-core";
|
import { createPluginRegistry } from "@logicsrc/plugin-core";
|
||||||
import { coinPayPlugin } from "@logicsrc/plugin-coinpay";
|
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";
|
import { uGigPlugin } from "@logicsrc/plugin-ugig";
|
||||||
export { ArcadeRegistry, createDefaultArcadeRegistry, renderArcadeList, renderArcadeSnapshot, runArcadeSession } from "./arcade/index.js";
|
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";
|
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> = {}) {
|
export function renderTui(state: Partial<TuiState> = {}) {
|
||||||
const view = { ...defaultState, ...state };
|
const view = { ...defaultState, ...state };
|
||||||
const registry = createPluginRegistry([coinPayPlugin, uGigPlugin]);
|
const registry = createPluginRegistry([coinPayPlugin, uGigPlugin, socialAccountsPlugin, emailAccountsPlugin]);
|
||||||
const plugins = registry.snapshot().plugins;
|
const plugins = registry.snapshot().plugins;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
|
|
@ -34,13 +36,13 @@ export function renderTui(state: Partial<TuiState> = {}) {
|
||||||
"│ /jobs │ [uGig] Senior AI Engineer remote │",
|
"│ /jobs │ [uGig] Senior AI Engineer remote │",
|
||||||
"├───────────────┴─────────────────────────────────────────────┤",
|
"├───────────────┴─────────────────────────────────────────────┤",
|
||||||
"│ Plugins: " + plugins.map((plugin) => `${plugin.name} ${plugin.enabled ? "enabled" : "disabled"}`).join(" | ").padEnd(50) + " │",
|
"│ 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");
|
].join("\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
export function renderPluginStatus() {
|
export function renderPluginStatus() {
|
||||||
const registry = createPluginRegistry([coinPayPlugin, uGigPlugin]);
|
const registry = createPluginRegistry([coinPayPlugin, uGigPlugin, socialAccountsPlugin, emailAccountsPlugin]);
|
||||||
return registry
|
return registry
|
||||||
.snapshot()
|
.snapshot()
|
||||||
.plugins.map((plugin) => `${plugin.id.padEnd(8)} ${plugin.enabled ? "enabled" : "disabled"} ${plugin.type.join(", ")}`)
|
.plugins.map((plugin) => `${plugin.id.padEnd(8)} ${plugin.enabled ? "enabled" : "disabled"} ${plugin.type.join(", ")}`)
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,26 @@
|
||||||
import agentSchema from "../../schemas/schemas/logicsrc-agent.schema.json" with { type: "json" };
|
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 eventSchema from "../../schemas/schemas/logicsrc-event.schema.json" with { type: "json" };
|
||||||
import pluginSchema from "../../schemas/schemas/logicsrc-plugin.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 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" };
|
import taskSchema from "../../schemas/schemas/logicsrc-task.schema.json" with { type: "json" };
|
||||||
|
|
||||||
export const schemas = {
|
export const schemas = {
|
||||||
agent: agentSchema,
|
agent: agentSchema,
|
||||||
|
"account-audit-event": accountAuditEventSchema,
|
||||||
|
"account-grant": accountGrantSchema,
|
||||||
|
"account-provider": accountProviderSchema,
|
||||||
|
"connected-account": connectedAccountSchema,
|
||||||
|
"email-message": emailMessageSchema,
|
||||||
event: eventSchema,
|
event: eventSchema,
|
||||||
plugin: pluginSchema,
|
plugin: pluginSchema,
|
||||||
run: runSchema,
|
run: runSchema,
|
||||||
|
"social-post": socialPostSchema,
|
||||||
task: taskSchema
|
task: taskSchema
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
|
|
|
||||||
5
plugins/email-accounts/README.md
Normal file
5
plugins/email-accounts/README.md
Normal 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.
|
||||||
19
plugins/email-accounts/package.json
Normal file
19
plugins/email-accounts/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
43
plugins/email-accounts/src/index.ts
Normal file
43
plugins/email-accounts/src/index.ts
Normal 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 };
|
||||||
39
plugins/email-accounts/src/manifest.ts
Normal file
39
plugins/email-accounts/src/manifest.ts
Normal 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"
|
||||||
|
]
|
||||||
|
};
|
||||||
40
plugins/email-accounts/src/providers/index.ts
Normal file
40
plugins/email-accounts/src/providers/index.ts
Normal 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"
|
||||||
|
}
|
||||||
|
];
|
||||||
8
plugins/email-accounts/tsconfig.json
Normal file
8
plugins/email-accounts/tsconfig.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
||||||
5
plugins/social-accounts/README.md
Normal file
5
plugins/social-accounts/README.md
Normal 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.
|
||||||
19
plugins/social-accounts/package.json
Normal file
19
plugins/social-accounts/package.json
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
||||||
41
plugins/social-accounts/src/index.ts
Normal file
41
plugins/social-accounts/src/index.ts
Normal 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 };
|
||||||
37
plugins/social-accounts/src/manifest.ts
Normal file
37
plugins/social-accounts/src/manifest.ts
Normal 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"
|
||||||
|
]
|
||||||
|
};
|
||||||
49
plugins/social-accounts/src/providers/index.ts
Normal file
49
plugins/social-accounts/src/providers/index.ts
Normal 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"
|
||||||
|
}
|
||||||
|
];
|
||||||
8
plugins/social-accounts/tsconfig.json
Normal file
8
plugins/social-accounts/tsconfig.json
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"rootDir": "src",
|
||||||
|
"outDir": "dist"
|
||||||
|
},
|
||||||
|
"include": ["src/**/*.ts"]
|
||||||
|
}
|
||||||
113
supabase/migrations/20260609010000_communication_accounts.sql
Normal file
113
supabase/migrations/20260609010000_communication_accounts.sql
Normal 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);
|
||||||
Loading…
Add table
Add a link
Reference in a new issue