mirror of
https://github.com/profullstack/logicsrc.git
synced 2026-08-15 07:17:30 +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
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"
|
||||
},
|
||||
"dependencies": {
|
||||
"@logicsrc/account-core": "file:../account-core",
|
||||
"@logicsrc/plugin-core": "file:../plugin-core",
|
||||
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
|
||||
"@logicsrc/plugin-email-accounts": "file:../../plugins/email-accounts",
|
||||
"@logicsrc/plugin-feed-discovery": "file:../../plugins/feed-discovery",
|
||||
"@logicsrc/plugin-social-accounts": "file:../../plugins/social-accounts",
|
||||
"@logicsrc/plugin-ugig": "file:../../plugins/ugig",
|
||||
"@logicsrc/tui": "file:../tui",
|
||||
"@logicsrc/validators": "file:../validators",
|
||||
|
|
|
|||
|
|
@ -1,7 +1,10 @@
|
|||
#!/usr/bin/env node
|
||||
import { readFileSync } from "node:fs";
|
||||
import { evaluateAccountPolicy, scoreAccountActionRisk } from "@logicsrc/account-core";
|
||||
import { Command } from "commander";
|
||||
import { listEmailAccountProviders } from "@logicsrc/plugin-email-accounts";
|
||||
import { discoverFeeds, listFeedProviders, probeSite, renderDiscoveryOutput, validateFeed, type FeedKind, type FeedOutputFormat } from "@logicsrc/plugin-feed-discovery";
|
||||
import { listSocialAccountProviders } from "@logicsrc/plugin-social-accounts";
|
||||
import { renderArcadeList, renderPluginStatus, renderTui, runArcadeSession, type TaskSnapshot } from "@logicsrc/tui";
|
||||
import { assertSchemaKind, parseDocument, validate } from "@logicsrc/validators";
|
||||
import { getConfigValue, readConfig, setConfigValue, writeConfig } from "./config.js";
|
||||
|
|
@ -399,6 +402,132 @@ credentials.command("plan").option("--from <provider>", "Source provider", "env"
|
|||
);
|
||||
});
|
||||
|
||||
const accounts = program.command("accounts").description("Manage connected social and email accounts.");
|
||||
|
||||
accounts
|
||||
.command("providers")
|
||||
.option("--kind <kind>", "social or email")
|
||||
.option("--format <format>", "table, json, or markdown", "table")
|
||||
.description("List communication account providers.")
|
||||
.action((options) => {
|
||||
const providers = [...listSocialAccountProviders(), ...listEmailAccountProviders()].filter((provider) => !options.kind || provider.kind === options.kind);
|
||||
print(providers, options.format as OutputFormat);
|
||||
});
|
||||
|
||||
accounts
|
||||
.command("list")
|
||||
.alias("accounts")
|
||||
.option("--kind <kind>", "social or email")
|
||||
.option("--format <format>", "table, json, or markdown", "table")
|
||||
.description("List connected accounts.")
|
||||
.action((options) => {
|
||||
print([], options.format as OutputFormat);
|
||||
});
|
||||
|
||||
accounts
|
||||
.command("audit")
|
||||
.argument("<account-id>", "Connected account id")
|
||||
.option("--format <format>", "table, json, or markdown", "table")
|
||||
.description("List account audit events.")
|
||||
.action((accountId, options) => {
|
||||
print({ account_id: accountId, events: [], note: "Account audit persistence is not wired yet." }, options.format as OutputFormat);
|
||||
});
|
||||
|
||||
const social = program.command("social").description("Manage social account providers and draft/publish flows.");
|
||||
|
||||
social.command("providers").option("--format <format>", "table, json, or markdown", "table").description("List social account providers.").action((options) => {
|
||||
print(listSocialAccountProviders(), options.format as OutputFormat);
|
||||
});
|
||||
|
||||
social.command("accounts").option("--format <format>", "table, json, or markdown", "table").description("List connected social accounts.").action((options) => {
|
||||
print([], options.format as OutputFormat);
|
||||
});
|
||||
|
||||
social
|
||||
.command("post")
|
||||
.argument("<account-id>", "Connected social account id")
|
||||
.requiredOption("--text <text>", "Post text")
|
||||
.option("--dry-run", "Evaluate without publishing", false)
|
||||
.option("--format <format>", "table, json, or markdown", "table")
|
||||
.description("Request or dry-run a social post publish.")
|
||||
.action((accountId, options) => {
|
||||
const riskScore = scoreAccountActionRisk({ action: "social:post:publish" });
|
||||
const decision = evaluateAccountPolicy({
|
||||
action: "social:post:publish",
|
||||
dryRun: Boolean(options.dryRun),
|
||||
riskScore,
|
||||
grant: {
|
||||
id: "dry_run_grant",
|
||||
accountId,
|
||||
principal: { type: "user", id: process.env.COMMANDBOARD_DID || "local-user" },
|
||||
permissions: ["social:post:publish"],
|
||||
policy: [],
|
||||
createdAt: new Date(0).toISOString()
|
||||
}
|
||||
});
|
||||
|
||||
print(
|
||||
{
|
||||
provider: "unknown",
|
||||
account_id: accountId,
|
||||
action: "social:post:publish",
|
||||
dry_run: Boolean(options.dryRun),
|
||||
scopes_required: ["social:post:publish"],
|
||||
policy_decision: decision.decision,
|
||||
risk_score: decision.riskScore,
|
||||
payload_preview: { text: options.text }
|
||||
},
|
||||
options.format as OutputFormat
|
||||
);
|
||||
});
|
||||
|
||||
const email = program.command("email").description("Manage email account providers and draft/send flows.");
|
||||
|
||||
email.command("providers").option("--format <format>", "table, json, or markdown", "table").description("List email account providers.").action((options) => {
|
||||
print(listEmailAccountProviders(), options.format as OutputFormat);
|
||||
});
|
||||
|
||||
email.command("accounts").option("--format <format>", "table, json, or markdown", "table").description("List connected email accounts.").action((options) => {
|
||||
print([], options.format as OutputFormat);
|
||||
});
|
||||
|
||||
email
|
||||
.command("send")
|
||||
.argument("<draft-id>", "Email draft id")
|
||||
.option("--dry-run", "Evaluate without sending", false)
|
||||
.option("--format <format>", "table, json, or markdown", "table")
|
||||
.description("Request or dry-run an outbound email send.")
|
||||
.action((draftId, options) => {
|
||||
const riskScore = scoreAccountActionRisk({ action: "email:send", externalRecipientCount: 1 });
|
||||
const decision = evaluateAccountPolicy({
|
||||
action: "email:send",
|
||||
dryRun: Boolean(options.dryRun),
|
||||
riskScore,
|
||||
grant: {
|
||||
id: "dry_run_grant",
|
||||
accountId: "unknown",
|
||||
principal: { type: "user", id: process.env.COMMANDBOARD_DID || "local-user" },
|
||||
permissions: ["email:send"],
|
||||
policy: [],
|
||||
createdAt: new Date(0).toISOString()
|
||||
}
|
||||
});
|
||||
|
||||
print(
|
||||
{
|
||||
provider: "unknown",
|
||||
draft_id: draftId,
|
||||
action: "email:send",
|
||||
dry_run: Boolean(options.dryRun),
|
||||
scopes_required: ["email:send"],
|
||||
policy_decision: decision.decision,
|
||||
risk_score: decision.riskScore,
|
||||
payload_preview: { draft_id: draftId }
|
||||
},
|
||||
options.format as OutputFormat
|
||||
);
|
||||
});
|
||||
|
||||
program.command("tui").description("Launch the tmux-friendly TUI.").action(() => {
|
||||
console.log(renderTui());
|
||||
console.log("\nPlugin status:\n" + renderPluginStatus());
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
import { createPluginRegistry } from "@logicsrc/plugin-core";
|
||||
import { coinPayPlugin } from "@logicsrc/plugin-coinpay";
|
||||
import { emailAccountsPlugin } from "@logicsrc/plugin-email-accounts";
|
||||
import { feedDiscoveryPlugin } from "@logicsrc/plugin-feed-discovery";
|
||||
import { socialAccountsPlugin } from "@logicsrc/plugin-social-accounts";
|
||||
import { uGigPlugin } from "@logicsrc/plugin-ugig";
|
||||
|
||||
export function defaultPluginRegistry() {
|
||||
return createPluginRegistry([coinPayPlugin, uGigPlugin, feedDiscoveryPlugin]);
|
||||
return createPluginRegistry([coinPayPlugin, uGigPlugin, feedDiscoveryPlugin, socialAccountsPlugin, emailAccountsPlugin]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { z } from "zod";
|
|||
import { assertSchemaKind, parseDocument, schemas, validate, type SchemaKind } from "@logicsrc/validators";
|
||||
|
||||
const docs = {
|
||||
"communication-accounts": `LogicSRC Communication Accounts defines shared contracts for connecting social and email identities, granting scoped human/agent/plugin access, evaluating policy gates, brokering credentials, and auditing every account action without exposing raw secrets.`,
|
||||
positioning: `LogicSRC is an open standards initiative for human and AI agent coordination, maintained by Profullstack, Inc.
|
||||
|
||||
CommandBoard.run is a hosted product by Profullstack, Inc., built on LogicSRC. LogicSRC defines identity, boards, posts, tasks, bounties, agents, agent runs, permissions, payments, escrow, reputation, events, webhooks, CLI commands, API schemas, and plugin contracts.`,
|
||||
|
|
@ -80,7 +81,7 @@ export function createLogicSrcMcpServer() {
|
|||
title: "Validate LogicSRC Document",
|
||||
description: "Validates a JSON or YAML document against a LogicSRC schema kind.",
|
||||
inputSchema: {
|
||||
kind: z.enum(["agent", "event", "plugin", "run", "task"]),
|
||||
kind: z.enum(["account-audit-event", "account-grant", "account-provider", "agent", "connected-account", "email-message", "event", "plugin", "run", "social-post", "task"]),
|
||||
document: z.string().describe("JSON or YAML document text."),
|
||||
fileName: z.string().optional().describe("Optional file name used to select JSON parsing when it ends with .json.")
|
||||
},
|
||||
|
|
@ -99,7 +100,7 @@ export function createLogicSrcMcpServer() {
|
|||
title: "Generate Example LogicSRC Document",
|
||||
description: "Returns a minimal example document for a LogicSRC schema kind.",
|
||||
inputSchema: {
|
||||
kind: z.enum(["agent", "event", "plugin", "run", "task"])
|
||||
kind: z.enum(["account-audit-event", "account-grant", "account-provider", "agent", "connected-account", "email-message", "event", "plugin", "run", "social-post", "task"])
|
||||
},
|
||||
annotations: { readOnlyHint: true, openWorldHint: false }
|
||||
},
|
||||
|
|
@ -163,6 +164,36 @@ function titleCase(value: string) {
|
|||
|
||||
function exampleFor(kind: SchemaKind) {
|
||||
switch (kind) {
|
||||
case "account-audit-event":
|
||||
return {
|
||||
id: "acct_audit_123",
|
||||
provider: "gmail",
|
||||
kind: "email",
|
||||
principal: { type: "agent", id: "marketing-agent" },
|
||||
action: "email:send",
|
||||
decision: "approval_required",
|
||||
riskScore: 0.35,
|
||||
requestPreview: { draft_id: "draft_123" },
|
||||
resultPreview: {},
|
||||
createdAt: new Date(0).toISOString()
|
||||
};
|
||||
case "account-grant":
|
||||
return {
|
||||
id: "grant_123",
|
||||
accountId: "account_123",
|
||||
principal: { type: "agent", id: "marketing-agent" },
|
||||
permissions: ["email:headers:read", "email:draft"],
|
||||
policy: [],
|
||||
createdAt: new Date(0).toISOString()
|
||||
};
|
||||
case "account-provider":
|
||||
return {
|
||||
id: "gmail",
|
||||
name: "Gmail",
|
||||
kind: "email",
|
||||
authMethods: ["oauth2"],
|
||||
capabilities: ["email.headers.read", "email.search"]
|
||||
};
|
||||
case "agent":
|
||||
return {
|
||||
type: "logicsrc.agent",
|
||||
|
|
@ -172,6 +203,32 @@ function exampleFor(kind: SchemaKind) {
|
|||
capabilities: ["browser.qa", "report.write"],
|
||||
status: "active"
|
||||
};
|
||||
case "connected-account":
|
||||
return {
|
||||
id: "account_123",
|
||||
ownerUserId: "user_123",
|
||||
kind: "email",
|
||||
provider: "gmail",
|
||||
displayName: "Founder Inbox",
|
||||
email: "founder@example.com",
|
||||
status: "connected",
|
||||
scopes: ["gmail.metadata"],
|
||||
capabilities: ["email.headers.read", "email.search"],
|
||||
credentialRef: "cred://gmail/account_123",
|
||||
metadata: {},
|
||||
createdAt: new Date(0).toISOString(),
|
||||
updatedAt: new Date(0).toISOString()
|
||||
};
|
||||
case "email-message":
|
||||
return {
|
||||
id: "email_msg_123",
|
||||
providerMessageId: "provider_msg_123",
|
||||
subject: "Hello",
|
||||
toAddresses: ["founder@example.com"],
|
||||
ccAddresses: [],
|
||||
labels: ["inbox"],
|
||||
hasAttachments: false
|
||||
};
|
||||
case "event":
|
||||
return {
|
||||
type: "logicsrc.event",
|
||||
|
|
@ -203,6 +260,17 @@ function exampleFor(kind: SchemaKind) {
|
|||
status: "completed",
|
||||
started_at: new Date(0).toISOString()
|
||||
};
|
||||
case "social-post":
|
||||
return {
|
||||
id: "social_post_123",
|
||||
accountId: "account_123",
|
||||
status: "draft",
|
||||
text: "Launching today",
|
||||
media: [],
|
||||
metadata: {},
|
||||
createdAt: new Date(0).toISOString(),
|
||||
updatedAt: new Date(0).toISOString()
|
||||
};
|
||||
case "task":
|
||||
return {
|
||||
type: "logicsrc.task",
|
||||
|
|
|
|||
|
|
@ -8,7 +8,13 @@
|
|||
"./agent": "./schemas/logicsrc-agent.schema.json",
|
||||
"./run": "./schemas/logicsrc-run.schema.json",
|
||||
"./event": "./schemas/logicsrc-event.schema.json",
|
||||
"./plugin": "./schemas/logicsrc-plugin.schema.json"
|
||||
"./plugin": "./schemas/logicsrc-plugin.schema.json",
|
||||
"./connected-account": "./schemas/logicsrc-connected-account.schema.json",
|
||||
"./account-provider": "./schemas/logicsrc-account-provider.schema.json",
|
||||
"./account-grant": "./schemas/logicsrc-account-grant.schema.json",
|
||||
"./account-audit-event": "./schemas/logicsrc-account-audit-event.schema.json",
|
||||
"./email-message": "./schemas/logicsrc-email-message.schema.json",
|
||||
"./social-post": "./schemas/logicsrc-social-post.schema.json"
|
||||
},
|
||||
"files": [
|
||||
"schemas"
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
"@logicsrc/plugin-core": "file:../plugin-core",
|
||||
"@logicsrc/plugin-coinpay": "file:../../plugins/coinpay",
|
||||
"@logicsrc/plugin-email-accounts": "file:../../plugins/email-accounts",
|
||||
"@logicsrc/plugin-social-accounts": "file:../../plugins/social-accounts",
|
||||
"@logicsrc/plugin-ugig": "file:../../plugins/ugig"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,7 @@
|
|||
import { createPluginRegistry } from "@logicsrc/plugin-core";
|
||||
import { coinPayPlugin } from "@logicsrc/plugin-coinpay";
|
||||
import { emailAccountsPlugin } from "@logicsrc/plugin-email-accounts";
|
||||
import { socialAccountsPlugin } from "@logicsrc/plugin-social-accounts";
|
||||
import { uGigPlugin } from "@logicsrc/plugin-ugig";
|
||||
export { ArcadeRegistry, createDefaultArcadeRegistry, renderArcadeList, renderArcadeSnapshot, runArcadeSession } from "./arcade/index.js";
|
||||
export type { ArcadeEvent, GameAction, GameContext, GameControl, KeyEvent, TaskEvent, TaskSnapshot, TerminalFrame, WaitingGame } from "./arcade/index.js";
|
||||
|
|
@ -20,7 +22,7 @@ const defaultState: TuiState = {
|
|||
|
||||
export function renderTui(state: Partial<TuiState> = {}) {
|
||||
const view = { ...defaultState, ...state };
|
||||
const registry = createPluginRegistry([coinPayPlugin, uGigPlugin]);
|
||||
const registry = createPluginRegistry([coinPayPlugin, uGigPlugin, socialAccountsPlugin, emailAccountsPlugin]);
|
||||
const plugins = registry.snapshot().plugins;
|
||||
|
||||
return [
|
||||
|
|
@ -34,13 +36,13 @@ export function renderTui(state: Partial<TuiState> = {}) {
|
|||
"│ /jobs │ [uGig] Senior AI Engineer remote │",
|
||||
"├───────────────┴─────────────────────────────────────────────┤",
|
||||
"│ Plugins: " + plugins.map((plugin) => `${plugin.name} ${plugin.enabled ? "enabled" : "disabled"}`).join(" | ").padEnd(50) + " │",
|
||||
"│ Enter: open p: post t: task a: agents w: wallet q: quit │",
|
||||
"│ Enter: open p: post t: task a: agents c: accounts q: quit │",
|
||||
"└─────────────────────────────────────────────────────────────┘"
|
||||
].join("\n");
|
||||
}
|
||||
|
||||
export function renderPluginStatus() {
|
||||
const registry = createPluginRegistry([coinPayPlugin, uGigPlugin]);
|
||||
const registry = createPluginRegistry([coinPayPlugin, uGigPlugin, socialAccountsPlugin, emailAccountsPlugin]);
|
||||
return registry
|
||||
.snapshot()
|
||||
.plugins.map((plugin) => `${plugin.id.padEnd(8)} ${plugin.enabled ? "enabled" : "disabled"} ${plugin.type.join(", ")}`)
|
||||
|
|
|
|||
|
|
@ -1,14 +1,26 @@
|
|||
import agentSchema from "../../schemas/schemas/logicsrc-agent.schema.json" with { type: "json" };
|
||||
import accountAuditEventSchema from "../../schemas/schemas/logicsrc-account-audit-event.schema.json" with { type: "json" };
|
||||
import accountGrantSchema from "../../schemas/schemas/logicsrc-account-grant.schema.json" with { type: "json" };
|
||||
import accountProviderSchema from "../../schemas/schemas/logicsrc-account-provider.schema.json" with { type: "json" };
|
||||
import connectedAccountSchema from "../../schemas/schemas/logicsrc-connected-account.schema.json" with { type: "json" };
|
||||
import emailMessageSchema from "../../schemas/schemas/logicsrc-email-message.schema.json" with { type: "json" };
|
||||
import eventSchema from "../../schemas/schemas/logicsrc-event.schema.json" with { type: "json" };
|
||||
import pluginSchema from "../../schemas/schemas/logicsrc-plugin.schema.json" with { type: "json" };
|
||||
import runSchema from "../../schemas/schemas/logicsrc-run.schema.json" with { type: "json" };
|
||||
import socialPostSchema from "../../schemas/schemas/logicsrc-social-post.schema.json" with { type: "json" };
|
||||
import taskSchema from "../../schemas/schemas/logicsrc-task.schema.json" with { type: "json" };
|
||||
|
||||
export const schemas = {
|
||||
agent: agentSchema,
|
||||
"account-audit-event": accountAuditEventSchema,
|
||||
"account-grant": accountGrantSchema,
|
||||
"account-provider": accountProviderSchema,
|
||||
"connected-account": connectedAccountSchema,
|
||||
"email-message": emailMessageSchema,
|
||||
event: eventSchema,
|
||||
plugin: pluginSchema,
|
||||
run: runSchema,
|
||||
"social-post": socialPostSchema,
|
||||
task: taskSchema
|
||||
} as const;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue