@plandesk/api 0.20.0 → 1.0.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/dist/access-control.d.ts +150 -0
  2. package/dist/access-control.js +63 -0
  3. package/dist/agent-keys.d.ts +81 -0
  4. package/dist/agent-keys.js +181 -0
  5. package/dist/auth-context.d.ts +60 -0
  6. package/dist/auth-context.js +41 -0
  7. package/dist/auth.d.ts +33 -0
  8. package/dist/auth.js +357 -0
  9. package/dist/better-auth.d.ts +66 -0
  10. package/dist/better-auth.js +106 -0
  11. package/dist/github.d.ts +57 -0
  12. package/dist/github.js +115 -0
  13. package/dist/hosted-auth.d.ts +23 -0
  14. package/dist/hosted-auth.js +49 -0
  15. package/dist/identity.d.ts +38 -0
  16. package/dist/identity.js +163 -0
  17. package/dist/index.d.ts +13 -2
  18. package/dist/index.js +12 -2
  19. package/dist/invitations.d.ts +94 -0
  20. package/dist/invitations.js +211 -0
  21. package/dist/organizations.d.ts +32 -0
  22. package/dist/organizations.js +63 -0
  23. package/dist/permissions.d.ts +19 -0
  24. package/dist/permissions.js +42 -0
  25. package/dist/projection.d.ts +1 -1
  26. package/dist/projection.js +5 -5
  27. package/dist/routes/agent-runs.js +3 -3
  28. package/dist/routes/artifacts.js +6 -6
  29. package/dist/routes/auth.d.ts +13 -0
  30. package/dist/routes/auth.js +83 -0
  31. package/dist/routes/canvas.js +5 -5
  32. package/dist/routes/comments.js +11 -11
  33. package/dist/routes/documents.js +10 -10
  34. package/dist/routes/folders.js +8 -8
  35. package/dist/routes/goals.js +11 -11
  36. package/dist/routes/notes.js +8 -8
  37. package/dist/routes/orgs.d.ts +17 -0
  38. package/dist/routes/orgs.js +241 -0
  39. package/dist/routes/projects.js +13 -13
  40. package/dist/routes/shares.js +92 -3
  41. package/dist/routes/submissions.js +7 -8
  42. package/dist/routes/tags.js +6 -6
  43. package/dist/routes/tasks.js +14 -3
  44. package/dist/routes/tokens.js +4 -4
  45. package/dist/serialize.d.ts +3 -3
  46. package/dist/server.d.ts +20 -2
  47. package/dist/server.js +57 -9
  48. package/dist/services/agent-runs.d.ts +13 -14
  49. package/dist/services/agent-runs.js +39 -41
  50. package/dist/services/artifacts.d.ts +6 -7
  51. package/dist/services/artifacts.js +30 -26
  52. package/dist/services/canvas.d.ts +11 -12
  53. package/dist/services/canvas.js +58 -41
  54. package/dist/services/comments.d.ts +11 -12
  55. package/dist/services/comments.js +44 -64
  56. package/dist/services/documents.d.ts +10 -11
  57. package/dist/services/documents.js +74 -55
  58. package/dist/services/files.d.ts +2 -1
  59. package/dist/services/files.js +13 -5
  60. package/dist/services/folders.d.ts +7 -8
  61. package/dist/services/folders.js +45 -33
  62. package/dist/services/goals.d.ts +24 -25
  63. package/dist/services/goals.js +60 -53
  64. package/dist/services/index.d.ts +2 -5
  65. package/dist/services/index.js +15 -20
  66. package/dist/services/notes.d.ts +7 -8
  67. package/dist/services/notes.js +35 -23
  68. package/dist/services/org-scope.d.ts +22 -0
  69. package/dist/services/org-scope.js +38 -0
  70. package/dist/services/projects.d.ts +12 -13
  71. package/dist/services/projects.js +90 -58
  72. package/dist/services/scope.d.ts +12 -0
  73. package/dist/services/scope.js +29 -0
  74. package/dist/services/share.d.ts +74 -10
  75. package/dist/services/share.js +213 -23
  76. package/dist/services/sync.d.ts +7 -29
  77. package/dist/services/sync.js +37 -142
  78. package/dist/services/tags.d.ts +6 -7
  79. package/dist/services/tags.js +34 -22
  80. package/dist/services/tasks.d.ts +24 -17
  81. package/dist/services/tasks.js +97 -58
  82. package/dist/services/tokens.d.ts +6 -5
  83. package/dist/services/tokens.js +13 -6
  84. package/dist/session.d.ts +5 -0
  85. package/dist/session.js +8 -0
  86. package/dist/storage/local.js +16 -10
  87. package/dist/storage/s3.js +11 -6
  88. package/dist/test-ba-org.d.ts +16 -0
  89. package/dist/test-ba-org.js +35 -0
  90. package/dist/test-helpers.d.ts +21 -5
  91. package/dist/test-helpers.js +45 -8
  92. package/dist/vercel.d.ts +3 -0
  93. package/dist/vercel.js +62 -0
  94. package/dist/worker.d.ts +44 -0
  95. package/dist/worker.js +101 -0
  96. package/package.json +8 -5
  97. package/web/assets/index-D29SyYFS.css +2 -0
  98. package/web/assets/index-tBJ_K3ur.js +358 -0
  99. package/web/index.html +2 -2
  100. package/web/assets/index-CeBvZ-aq.css +0 -2
  101. package/web/assets/index-CuRAxImC.js +0 -358
package/dist/github.js ADDED
@@ -0,0 +1,115 @@
1
+ /**
2
+ * GitHub OAuth helpers (authorize URL + code exchange for identity).
3
+ * Browser sign-in is owned by better-auth social. GitHub is optional (REQ-20).
4
+ */
5
+ const AUTHORIZE_URL = 'https://github.com/login/oauth/authorize';
6
+ const ACCESS_TOKEN_URL = 'https://github.com/login/oauth/access_token';
7
+ const USER_URL = 'https://api.github.com/user';
8
+ export class GithubOAuthError extends Error {
9
+ constructor(message) {
10
+ super(message);
11
+ this.name = 'GithubOAuthError';
12
+ }
13
+ }
14
+ function present(value) {
15
+ return value !== undefined && value.trim() !== '';
16
+ }
17
+ /**
18
+ * Read the GitHub app from the environment. Every runtime entry uses this, so
19
+ * "is GitHub sign-in on?" has exactly one answer everywhere.
20
+ *
21
+ * No client id/secret → undefined → the instance runs without GitHub sign-in.
22
+ * That is the supported self-host path, not a degraded one (REQ-20).
23
+ *
24
+ * A half-configured app throws instead of silently disabling: someone who set
25
+ * a client id meant to turn this on and should be told what is missing.
26
+ */
27
+ export function githubConfigFromEnv(env) {
28
+ const clientId = env.PLANDESK_GITHUB_CLIENT_ID;
29
+ const clientSecret = env.PLANDESK_GITHUB_CLIENT_SECRET;
30
+ const callbackUrl = env.PLANDESK_GITHUB_CALLBACK_URL;
31
+ if (!present(clientId) && !present(clientSecret) && !present(callbackUrl)) {
32
+ return undefined;
33
+ }
34
+ if (!present(clientId) || !present(clientSecret) || !present(callbackUrl)) {
35
+ throw new Error('GitHub sign-in needs PLANDESK_GITHUB_CLIENT_ID, PLANDESK_GITHUB_CLIENT_SECRET and ' +
36
+ 'PLANDESK_GITHUB_CALLBACK_URL together. Unset all three to run without GitHub sign-in.');
37
+ }
38
+ return {
39
+ clientId: clientId.trim(),
40
+ clientSecret: clientSecret.trim(),
41
+ callbackUrl: callbackUrl.trim(),
42
+ dashboardUrl: present(env.PLANDESK_DASHBOARD_URL) ? env.PLANDESK_DASHBOARD_URL.trim() : '/',
43
+ };
44
+ }
45
+ /** `github:<numeric id>` — never the login, which a rename would orphan. */
46
+ export function userRefFromGithubId(id) {
47
+ return `github:${String(id)}`;
48
+ }
49
+ export function authorizeUrl(config, state) {
50
+ const url = new URL(AUTHORIZE_URL);
51
+ url.searchParams.set('client_id', config.clientId);
52
+ url.searchParams.set('redirect_uri', config.callbackUrl);
53
+ url.searchParams.set('state', state);
54
+ // Identity only: we never write to the user's GitHub account.
55
+ url.searchParams.set('scope', 'read:user');
56
+ url.searchParams.set('allow_signup', 'true');
57
+ return url.toString();
58
+ }
59
+ async function exchangeCodeForToken(config, code) {
60
+ const doFetch = config.fetch ?? fetch;
61
+ const response = await doFetch(ACCESS_TOKEN_URL, {
62
+ method: 'POST',
63
+ headers: { Accept: 'application/json', 'Content-Type': 'application/json' },
64
+ body: JSON.stringify({
65
+ client_id: config.clientId,
66
+ // Confidential client: the secret stays server-side.
67
+ client_secret: config.clientSecret,
68
+ code,
69
+ redirect_uri: config.callbackUrl,
70
+ }),
71
+ });
72
+ if (!response.ok) {
73
+ throw new GithubOAuthError(`token exchange failed with status ${String(response.status)}`);
74
+ }
75
+ const body = (await response.json());
76
+ if (typeof body.error === 'string') {
77
+ throw new GithubOAuthError(`token exchange rejected: ${body.error}`);
78
+ }
79
+ if (typeof body.access_token !== 'string' || body.access_token.length === 0) {
80
+ throw new GithubOAuthError('token exchange returned no access_token');
81
+ }
82
+ return body.access_token;
83
+ }
84
+ async function fetchIdentity(config, accessToken) {
85
+ const doFetch = config.fetch ?? fetch;
86
+ const response = await doFetch(USER_URL, {
87
+ headers: {
88
+ Accept: 'application/vnd.github+json',
89
+ Authorization: `Bearer ${accessToken}`,
90
+ 'User-Agent': 'plandesk',
91
+ },
92
+ });
93
+ if (!response.ok) {
94
+ throw new GithubOAuthError(`user lookup failed with status ${String(response.status)}`);
95
+ }
96
+ const body = (await response.json());
97
+ if (typeof body.id !== 'number' || typeof body.login !== 'string') {
98
+ throw new GithubOAuthError('user lookup returned no id/login');
99
+ }
100
+ return {
101
+ id: body.id,
102
+ login: body.login,
103
+ name: typeof body.name === 'string' ? body.name : null,
104
+ };
105
+ }
106
+ /**
107
+ * Exchange the callback code for the caller's identity.
108
+ * The access token is used once, here, and then dropped: storing it would make
109
+ * Plan Desk a breach target for no product benefit.
110
+ */
111
+ export async function resolveGithubIdentity(config, code) {
112
+ const accessToken = await exchangeCodeForToken(config, code);
113
+ return fetchIdentity(config, accessToken);
114
+ }
115
+ //# sourceMappingURL=github.js.map
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Hosted (Workers / Vercel) better-auth wiring.
3
+ * Local `serve` uses sessionSecret + auto-generated local secret; edge entries
4
+ * require an explicit PLANDESK_BETTER_AUTH_SECRET so a misdeploy fails loud
5
+ * instead of 401-ing every request on a non-loopback bind.
6
+ */
7
+ export type HostedAuthEnv = {
8
+ PLANDESK_BETTER_AUTH_SECRET?: string;
9
+ PLANDESK_BASE_URL?: string;
10
+ };
11
+ export declare const MISSING_BETTER_AUTH_SECRET_MESSAGE: string;
12
+ export declare const MISSING_BASE_URL_MESSAGE: string;
13
+ /**
14
+ * Resolve better-auth config for a hosted (non-loopback) entry.
15
+ * Throws a clear Error when the secret is missing — callers return 500, not silent 401.
16
+ * baseURL prefers PLANDESK_BASE_URL; falls back to requestOrigin (Workers per-request).
17
+ */
18
+ export declare function resolveHostedBetterAuth(env: HostedAuthEnv, requestOrigin?: string): {
19
+ secret: string;
20
+ baseURL: string;
21
+ };
22
+ export declare function hostedMisconfigResponse(err: unknown): Response | undefined;
23
+ //# sourceMappingURL=hosted-auth.d.ts.map
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Hosted (Workers / Vercel) better-auth wiring.
3
+ * Local `serve` uses sessionSecret + auto-generated local secret; edge entries
4
+ * require an explicit PLANDESK_BETTER_AUTH_SECRET so a misdeploy fails loud
5
+ * instead of 401-ing every request on a non-loopback bind.
6
+ */
7
+ export const MISSING_BETTER_AUTH_SECRET_MESSAGE = 'Hosted Plan Desk requires PLANDESK_BETTER_AUTH_SECRET. ' +
8
+ 'Set it via `wrangler secret put PLANDESK_BETTER_AUTH_SECRET` (Workers) or the platform env (Vercel). ' +
9
+ 'Without it every request would 401 on a non-loopback bind.';
10
+ export const MISSING_BASE_URL_MESSAGE = 'Hosted Plan Desk requires PLANDESK_BASE_URL (public origin, e.g. https://plandesk-api.example.workers.dev) ' +
11
+ 'when the request origin is not available at construction time.';
12
+ function present(value) {
13
+ return value !== undefined && value.trim() !== '';
14
+ }
15
+ /**
16
+ * Resolve better-auth config for a hosted (non-loopback) entry.
17
+ * Throws a clear Error when the secret is missing — callers return 500, not silent 401.
18
+ * baseURL prefers PLANDESK_BASE_URL; falls back to requestOrigin (Workers per-request).
19
+ */
20
+ export function resolveHostedBetterAuth(env, requestOrigin) {
21
+ if (!present(env.PLANDESK_BETTER_AUTH_SECRET)) {
22
+ throw new Error(MISSING_BETTER_AUTH_SECRET_MESSAGE);
23
+ }
24
+ const fromEnv = present(env.PLANDESK_BASE_URL) ? env.PLANDESK_BASE_URL.trim() : undefined;
25
+ const fromRequest = requestOrigin !== undefined && requestOrigin.trim() !== ''
26
+ ? requestOrigin.trim().replace(/\/$/, '')
27
+ : undefined;
28
+ const baseURL = fromEnv ?? fromRequest;
29
+ if (baseURL === undefined) {
30
+ throw new Error(MISSING_BASE_URL_MESSAGE);
31
+ }
32
+ return {
33
+ secret: env.PLANDESK_BETTER_AUTH_SECRET.trim(),
34
+ baseURL: baseURL.replace(/\/$/, ''),
35
+ };
36
+ }
37
+ export function hostedMisconfigResponse(err) {
38
+ if (!(err instanceof Error))
39
+ return undefined;
40
+ if (err.message !== MISSING_BETTER_AUTH_SECRET_MESSAGE &&
41
+ err.message !== MISSING_BASE_URL_MESSAGE) {
42
+ return undefined;
43
+ }
44
+ return new Response(JSON.stringify({ error: 'misconfigured', message: err.message }), {
45
+ status: 500,
46
+ headers: { 'Content-Type': 'application/json' },
47
+ });
48
+ }
49
+ //# sourceMappingURL=hosted-auth.js.map
@@ -0,0 +1,38 @@
1
+ import { type Db } from '@plandesk/db';
2
+ import type { BetterAuthInstance } from './better-auth.js';
3
+ import type { GithubIdentity } from './github.js';
4
+ import { type OrganizationSummary } from './organizations.js';
5
+ type OrganizationRow = {
6
+ id: string;
7
+ name: string;
8
+ slug: string;
9
+ createdAt: Date;
10
+ };
11
+ export type IdentityOrganization = Pick<OrganizationRow, 'id' | 'name' | 'slug'> & {
12
+ role: string;
13
+ };
14
+ export declare function githubAccountIdFromUserRef(userRef: string): string | undefined;
15
+ export declare function userRefFromGithubAccountId(accountId: string): string;
16
+ export declare function resolveOrganizationsForGithubIdentity(auth: BetterAuthInstance, identity: GithubIdentity): Promise<IdentityOrganization[]>;
17
+ /**
18
+ * Ensure the well-known local better-auth organization exists (user-less).
19
+ * Idempotent. Used at serve boot for loopback owner-by-bind.
20
+ */
21
+ export declare function ensureLocalBetterAuthOrganization(_db: Db, auth: BetterAuthInstance): Promise<OrganizationSummary>;
22
+ export type ProvisionPersonalOrgResult = {
23
+ created: true;
24
+ orgId: string;
25
+ role: 'owner';
26
+ } | {
27
+ created: false;
28
+ reason: 'not_github' | 'already_member';
29
+ };
30
+ /**
31
+ * BA4c: first better-auth GitHub session with zero memberships gets a personal
32
+ * org + owner member. Invited users who already hold a member row are left alone.
33
+ */
34
+ export declare function provisionPersonalOrgIfNeeded(auth: BetterAuthInstance, _db: Db, userId: string): Promise<ProvisionPersonalOrgResult>;
35
+ /** Persist the best active organization for a newly-created session. */
36
+ export declare function setDefaultActiveOrganization(auth: BetterAuthInstance, userId: string, sessionToken: string): Promise<string | undefined>;
37
+ export {};
38
+ //# sourceMappingURL=identity.d.ts.map
@@ -0,0 +1,163 @@
1
+ import { DEFAULT_ORG_ID } from '@plandesk/db';
2
+ import { getOrganizationById } from './organizations.js';
3
+ const GITHUB_PROVIDER_ID = 'github';
4
+ const GITHUB_USER_REF_PREFIX = 'github:';
5
+ const NUMERIC_GITHUB_ID = /^[1-9]\d*$/;
6
+ export function githubAccountIdFromUserRef(userRef) {
7
+ if (!userRef.startsWith(GITHUB_USER_REF_PREFIX))
8
+ return undefined;
9
+ const accountId = userRef.slice(GITHUB_USER_REF_PREFIX.length);
10
+ return NUMERIC_GITHUB_ID.test(accountId) ? accountId : undefined;
11
+ }
12
+ export function userRefFromGithubAccountId(accountId) {
13
+ if (!NUMERIC_GITHUB_ID.test(accountId)) {
14
+ throw new Error('GitHub account id must be numeric');
15
+ }
16
+ return `${GITHUB_USER_REF_PREFIX}${accountId}`;
17
+ }
18
+ export async function resolveOrganizationsForGithubIdentity(auth, identity) {
19
+ const adapter = (await auth.$context).adapter;
20
+ const account = await adapter.findOne({
21
+ model: 'account',
22
+ where: [
23
+ { field: 'providerId', value: GITHUB_PROVIDER_ID },
24
+ { field: 'accountId', value: String(identity.id) },
25
+ ],
26
+ });
27
+ if (account === null)
28
+ return [];
29
+ const user = await adapter.findOne({
30
+ model: 'user',
31
+ where: [{ field: 'id', value: account.userId }],
32
+ });
33
+ if (user === null)
34
+ throw new Error('GitHub account points to a missing user');
35
+ const members = await adapter.findMany({
36
+ model: 'member',
37
+ where: [{ field: 'userId', value: user.id }],
38
+ sortBy: { field: 'createdAt', direction: 'asc' },
39
+ });
40
+ return Promise.all(members.map(async (member) => {
41
+ const organization = await adapter.findOne({
42
+ model: 'organization',
43
+ where: [{ field: 'id', value: member.organizationId }],
44
+ });
45
+ if (organization === null)
46
+ throw new Error('Membership points to a missing organization');
47
+ return {
48
+ id: organization.id,
49
+ name: organization.name,
50
+ slug: organization.slug,
51
+ role: member.role,
52
+ };
53
+ }));
54
+ }
55
+ /**
56
+ * Ensure the well-known local better-auth organization exists (user-less).
57
+ * Idempotent. Used at serve boot for loopback owner-by-bind.
58
+ */
59
+ export async function ensureLocalBetterAuthOrganization(_db, auth) {
60
+ const existing = await getOrganizationById(auth, DEFAULT_ORG_ID);
61
+ if (existing !== undefined) {
62
+ return existing;
63
+ }
64
+ const now = new Date();
65
+ const adapter = (await auth.$context).adapter;
66
+ const data = {
67
+ id: DEFAULT_ORG_ID,
68
+ name: 'Personal',
69
+ slug: 'local',
70
+ createdAt: now,
71
+ };
72
+ const created = await adapter.create({
73
+ model: 'organization',
74
+ data,
75
+ forceAllowId: true,
76
+ });
77
+ return {
78
+ id: created.id,
79
+ name: created.name,
80
+ slug: created.slug,
81
+ createdAt: created.createdAt,
82
+ };
83
+ }
84
+ /**
85
+ * BA4c: first better-auth GitHub session with zero memberships gets a personal
86
+ * org + owner member. Invited users who already hold a member row are left alone.
87
+ */
88
+ export async function provisionPersonalOrgIfNeeded(auth, _db, userId) {
89
+ const adapter = (await auth.$context).adapter;
90
+ const account = await adapter.findOne({
91
+ model: 'account',
92
+ where: [
93
+ { field: 'userId', value: userId },
94
+ { field: 'providerId', value: GITHUB_PROVIDER_ID },
95
+ ],
96
+ });
97
+ if (account === null) {
98
+ return { created: false, reason: 'not_github' };
99
+ }
100
+ const members = await adapter.findMany({
101
+ model: 'member',
102
+ where: [{ field: 'userId', value: userId }],
103
+ });
104
+ if (members.length > 0) {
105
+ return { created: false, reason: 'already_member' };
106
+ }
107
+ const user = await adapter.findOne({
108
+ model: 'user',
109
+ where: [{ field: 'id', value: userId }],
110
+ });
111
+ if (user === null) {
112
+ throw new Error('session user missing during personal org provision');
113
+ }
114
+ const orgName = user.name.trim().length > 0 ? user.name.trim() : 'Personal';
115
+ const now = new Date();
116
+ const organization = await adapter.create({
117
+ model: 'organization',
118
+ data: {
119
+ name: orgName,
120
+ slug: `u-${userId}`,
121
+ createdAt: now,
122
+ },
123
+ });
124
+ await adapter.create({
125
+ model: 'member',
126
+ data: {
127
+ organizationId: organization.id,
128
+ userId,
129
+ role: 'owner',
130
+ createdAt: now,
131
+ },
132
+ });
133
+ return { created: true, orgId: organization.id, role: 'owner' };
134
+ }
135
+ /** Persist the best active organization for a newly-created session. */
136
+ export async function setDefaultActiveOrganization(auth, userId, sessionToken) {
137
+ const context = await auth.$context;
138
+ const members = await context.adapter.findMany({
139
+ model: 'member',
140
+ where: [{ field: 'userId', value: userId }],
141
+ sortBy: { field: 'createdAt', direction: 'asc' },
142
+ });
143
+ if (members.length === 0) {
144
+ return undefined;
145
+ }
146
+ const memberOrgIds = new Set(members.map((member) => member.organizationId));
147
+ const sessions = await context.adapter.findMany({
148
+ model: 'session',
149
+ where: [{ field: 'userId', value: userId }],
150
+ sortBy: { field: 'updatedAt', direction: 'desc' },
151
+ });
152
+ const priorActiveOrganizationId = sessions.find((session) => session.token !== sessionToken &&
153
+ session.activeOrganizationId !== undefined &&
154
+ session.activeOrganizationId !== null &&
155
+ memberOrgIds.has(session.activeOrganizationId))?.activeOrganizationId;
156
+ const activeOrganizationId = priorActiveOrganizationId ?? members[0]?.organizationId;
157
+ if (activeOrganizationId === undefined) {
158
+ return undefined;
159
+ }
160
+ await context.internalAdapter.updateSession(sessionToken, { activeOrganizationId });
161
+ return activeOrganizationId;
162
+ }
163
+ //# sourceMappingURL=identity.js.map
package/dist/index.d.ts CHANGED
@@ -1,9 +1,20 @@
1
1
  export { createApp, type AppDeps } from './server.js';
2
- export { createAuthMiddleware } from './auth.js';
2
+ export { createBetterAuth, runBetterAuthMigrations, type BetterAuthDeps, type BetterAuthInstance, } from './better-auth.js';
3
+ export { ensureLocalBetterAuthOrganization } from './identity.js';
4
+ export { createAuthMiddleware, createOrgAuthMiddleware, createWriteGuardMiddleware, isLoopbackBind, isPublicAuthPath, isInvitationAcceptPath, type OrgAuthOptions, } from './auth.js';
5
+ export { acceptOrganizationInvitation, createOrganizationInvitation, ensureShellOwner, invitationClaimUrl, isAuthApiError, isInvitationRole, mintOwnerInvitation, mintSessionCookieHeader, removeOrganizationMember, updateOrganizationMemberRole, INVITATION_ROLES, type InvitationRole, } from './invitations.js';
6
+ export { authorizeUrl, githubConfigFromEnv, resolveGithubIdentity, userRefFromGithubId, GithubOAuthError, type FetchLike, type GithubConfig, type GithubEnv, type GithubIdentity, } from './github.js';
7
+ export { GUEST_SESSION_COOKIE, readGuestSessionCookie } from './session.js';
8
+ export { createAuthRouter, type AuthRouterDeps } from './routes/auth.js';
9
+ export { runWithAuthContext, tryGetAuthContext, getAuthContext, ReadOnlyTokenError, type AuthContext, } from './auth-context.js';
10
+ export { applyAgentKeyPermissionCeiling, createScopedAgentKey, createOrgOwnerKey, verifyBetterAuthApiKey, DEFAULT_AGENT_KEY_PERMISSIONS, DEFAULT_OWNER_KEY_PERMISSIONS, AGENT_FORBIDDEN_RESOURCES, type ApiKeyKind, type CreateScopedAgentKeyInput, type CreatedScopedAgentKey, type CreateOrgOwnerKeyInput, type CreatedOrgOwnerKey, type VerifiedApiKey, } from './agent-keys.js';
11
+ export { requirePermission, hasPermission, hasAnyWritePermission, orgRoleToPermissionSet, PermissionDeniedError, type PermissionSet, } from './permissions.js';
3
12
  export { healthRouter } from './routes/health.js';
4
13
  export { mountStatic } from './static.js';
14
+ export { createS3Adapter, type S3AdapterConfig } from './storage/s3.js';
15
+ export type { StorageAdapter } from './storage/adapter.js';
5
16
  export { createServices, type Services, type ServicesDeps } from './services/index.js';
6
- export { createEventBus, type EventBus, type PlankDeskEvent } from './events.js';
17
+ export { assertProjectInOrg, ProjectNotInOrgError } from './services/scope.js';
7
18
  export type { ProjectService } from './services/projects.js';
8
19
  export type { GoalService, VerificationEvidence } from './services/goals.js';
9
20
  export { GoalCompletionBlockedError, GoalVerificationRequiredError, InvalidGoalTransitionError, InvalidVerificationSurfaceError, } from './services/goals.js';
package/dist/index.js CHANGED
@@ -1,9 +1,19 @@
1
1
  export { createApp } from './server.js';
2
- export { createAuthMiddleware } from './auth.js';
2
+ export { createBetterAuth, runBetterAuthMigrations, } from './better-auth.js';
3
+ export { ensureLocalBetterAuthOrganization } from './identity.js';
4
+ export { createAuthMiddleware, createOrgAuthMiddleware, createWriteGuardMiddleware, isLoopbackBind, isPublicAuthPath, isInvitationAcceptPath, } from './auth.js';
5
+ export { acceptOrganizationInvitation, createOrganizationInvitation, ensureShellOwner, invitationClaimUrl, isAuthApiError, isInvitationRole, mintOwnerInvitation, mintSessionCookieHeader, removeOrganizationMember, updateOrganizationMemberRole, INVITATION_ROLES, } from './invitations.js';
6
+ export { authorizeUrl, githubConfigFromEnv, resolveGithubIdentity, userRefFromGithubId, GithubOAuthError, } from './github.js';
7
+ export { GUEST_SESSION_COOKIE, readGuestSessionCookie } from './session.js';
8
+ export { createAuthRouter } from './routes/auth.js';
9
+ export { runWithAuthContext, tryGetAuthContext, getAuthContext, ReadOnlyTokenError, } from './auth-context.js';
10
+ export { applyAgentKeyPermissionCeiling, createScopedAgentKey, createOrgOwnerKey, verifyBetterAuthApiKey, DEFAULT_AGENT_KEY_PERMISSIONS, DEFAULT_OWNER_KEY_PERMISSIONS, AGENT_FORBIDDEN_RESOURCES, } from './agent-keys.js';
11
+ export { requirePermission, hasPermission, hasAnyWritePermission, orgRoleToPermissionSet, PermissionDeniedError, } from './permissions.js';
3
12
  export { healthRouter } from './routes/health.js';
4
13
  export { mountStatic } from './static.js';
14
+ export { createS3Adapter } from './storage/s3.js';
5
15
  export { createServices } from './services/index.js';
6
- export { createEventBus } from './events.js';
16
+ export { assertProjectInOrg, ProjectNotInOrgError } from './services/scope.js';
7
17
  export { GoalCompletionBlockedError, GoalVerificationRequiredError, InvalidGoalTransitionError, InvalidVerificationSurfaceError, } from './services/goals.js';
8
18
  export { InvalidGoalReferenceError } from './services/tasks.js';
9
19
  export { InvalidDocumentError } from './services/documents.js';
@@ -0,0 +1,94 @@
1
+ import type { BetterAuthInstance } from './better-auth.js';
2
+ export declare const INVITATION_ROLES: readonly ["owner", "admin", "member"];
3
+ export type InvitationRole = (typeof INVITATION_ROLES)[number];
4
+ export declare function isInvitationRole(value: string): value is InvitationRole;
5
+ export declare function invitationClaimUrl(baseURL: string, invitationId: string): string;
6
+ type InvitationRow = {
7
+ id: string;
8
+ email: string;
9
+ role: string;
10
+ organizationId: string;
11
+ inviterId: string;
12
+ status: string;
13
+ expiresAt: Date;
14
+ createdAt: Date;
15
+ };
16
+ type AcceptResult = {
17
+ invitation: InvitationRow;
18
+ member: {
19
+ id: string;
20
+ organizationId: string;
21
+ userId: string;
22
+ role: string;
23
+ };
24
+ };
25
+ /** better-auth APIError shape (runtime; not imported — version-stable fields only). */
26
+ export type AuthApiErrorShape = {
27
+ statusCode: number;
28
+ status: string;
29
+ message: string;
30
+ };
31
+ export declare function isAuthApiError(err: unknown): err is AuthApiErrorShape;
32
+ /**
33
+ * Create an invitation using the caller's better-auth session (Cookie headers).
34
+ * No mailer: sendInvitationEmail is unset; caller delivers claimUrl by hand.
35
+ */
36
+ export declare function createOrganizationInvitation(auth: BetterAuthInstance, opts: {
37
+ email: string;
38
+ role: InvitationRole;
39
+ organizationId: string;
40
+ headers: Headers;
41
+ baseURL: string;
42
+ }): Promise<{
43
+ invitationId: string;
44
+ claimUrl: string;
45
+ invitation: InvitationRow;
46
+ }>;
47
+ /**
48
+ * Accept a pending invitation as the signed-in better-auth user (email must match).
49
+ * Single-use CAS: a second accept fails (maps to 410 at the route).
50
+ */
51
+ export declare function acceptOrganizationInvitation(auth: BetterAuthInstance, opts: {
52
+ invitationId: string;
53
+ headers: Headers;
54
+ }): Promise<AcceptResult>;
55
+ /**
56
+ * Remove a member via better-auth (enforces ≥1 owner). Used by tests / future routes.
57
+ */
58
+ export declare function removeOrganizationMember(auth: BetterAuthInstance, opts: {
59
+ memberIdOrEmail: string;
60
+ organizationId: string;
61
+ headers: Headers;
62
+ }): Promise<unknown>;
63
+ /**
64
+ * Update a member role via better-auth (enforces ≥1 owner on demote).
65
+ */
66
+ export declare function updateOrganizationMemberRole(auth: BetterAuthInstance, opts: {
67
+ memberId: string;
68
+ role: InvitationRole;
69
+ organizationId: string;
70
+ headers: Headers;
71
+ }): Promise<unknown>;
72
+ /** Mint a signed better-auth session cookie header for an existing user. */
73
+ export declare function mintSessionCookieHeader(auth: BetterAuthInstance, userId: string): Promise<Headers>;
74
+ /**
75
+ * Ensure a shell inviter user exists as owner of organizationId (REQ-3 bootstrap).
76
+ * Shell authority owns the DB — no interactive session required from the operator.
77
+ */
78
+ export declare function ensureShellOwner(auth: BetterAuthInstance, organizationId: string): Promise<{
79
+ userId: string;
80
+ headers: Headers;
81
+ }>;
82
+ /**
83
+ * Shell-side owner invitation for `plandesk admin invite-owner` (no GitHub, no mailer).
84
+ */
85
+ export declare function mintOwnerInvitation(auth: BetterAuthInstance, opts: {
86
+ email: string;
87
+ organizationId: string;
88
+ baseURL: string;
89
+ }): Promise<{
90
+ invitationId: string;
91
+ claimUrl: string;
92
+ }>;
93
+ export {};
94
+ //# sourceMappingURL=invitations.d.ts.map