@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
@@ -9,7 +9,7 @@ async function handleCreateComment(c, commentService, target) {
9
9
  return c.json({ error: 'invalid_argument' }, 400);
10
10
  }
11
11
  try {
12
- const comment = commentService.create(target, {
12
+ const comment = await commentService.create(target, {
13
13
  body: body.body,
14
14
  passage: body.passage,
15
15
  anchor: body.anchor,
@@ -26,9 +26,9 @@ async function handleCreateComment(c, commentService, target) {
26
26
  throw error;
27
27
  }
28
28
  }
29
- function handleListComments(c, commentService, target) {
29
+ async function handleListComments(c, commentService, target) {
30
30
  const includeResolved = parseIncludeResolved(c.req.query('include_resolved'));
31
- const comments = commentService.listByTarget(target, { includeResolved });
31
+ const comments = await commentService.listByTarget(target, { includeResolved });
32
32
  if (!comments) {
33
33
  return c.json({ error: 'not_found' }, 404);
34
34
  }
@@ -44,9 +44,9 @@ export function createCommentsRouter(commentService) {
44
44
  router.get('/notes/:id/comments', (c) => handleListComments(c, commentService, { type: 'note', id: c.req.param('id') }));
45
45
  router.post('/submissions/:id/comments', (c) => handleCreateComment(c, commentService, { type: 'submission', id: c.req.param('id') }));
46
46
  router.get('/submissions/:id/comments', (c) => handleListComments(c, commentService, { type: 'submission', id: c.req.param('id') }));
47
- router.get('/projects/:id/comments', (c) => {
47
+ router.get('/projects/:id/comments', async (c) => {
48
48
  const includeResolved = parseIncludeResolved(c.req.query('include_resolved'));
49
- const comments = commentService.listByProject(c.req.param('id'), { includeResolved });
49
+ const comments = await commentService.listByProject(c.req.param('id'), { includeResolved });
50
50
  if (!comments) {
51
51
  return c.json({ error: 'not_found' }, 404);
52
52
  }
@@ -61,7 +61,7 @@ export function createCommentsRouter(commentService) {
61
61
  return c.json({ error: 'invalid_argument' }, 400);
62
62
  }
63
63
  try {
64
- const comment = commentService.createForArtifact(c.req.param('id'), body.artifact_id, {
64
+ const comment = await commentService.createForArtifact(c.req.param('id'), body.artifact_id, {
65
65
  body: body.body,
66
66
  passage: body.passage,
67
67
  anchor: body.anchor,
@@ -78,13 +78,13 @@ export function createCommentsRouter(commentService) {
78
78
  throw error;
79
79
  }
80
80
  });
81
- router.get('/projects/:id/artifact-comments', (c) => {
81
+ router.get('/projects/:id/artifact-comments', async (c) => {
82
82
  const artifactId = c.req.query('artifact_id');
83
83
  if (artifactId === undefined || artifactId === '') {
84
84
  return c.json({ error: 'invalid_argument' }, 400);
85
85
  }
86
86
  const includeResolved = parseIncludeResolved(c.req.query('include_resolved'));
87
- const comments = commentService.listForArtifact(c.req.param('id'), artifactId, {
87
+ const comments = await commentService.listForArtifact(c.req.param('id'), artifactId, {
88
88
  includeResolved,
89
89
  });
90
90
  if (!comments) {
@@ -95,7 +95,7 @@ export function createCommentsRouter(commentService) {
95
95
  router.patch('/comments/:id', async (c) => {
96
96
  const body = await c.req.json();
97
97
  try {
98
- const comment = commentService.update(c.req.param('id'), {
98
+ const comment = await commentService.update(c.req.param('id'), {
99
99
  ...(body.body !== undefined ? { body: body.body } : {}),
100
100
  ...(body.resolved !== undefined ? { resolved: body.resolved } : {}),
101
101
  });
@@ -111,8 +111,8 @@ export function createCommentsRouter(commentService) {
111
111
  throw error;
112
112
  }
113
113
  });
114
- router.delete('/comments/:id', (c) => {
115
- const deleted = commentService.delete(c.req.param('id'));
114
+ router.delete('/comments/:id', async (c) => {
115
+ const deleted = await commentService.delete(c.req.param('id'));
116
116
  if (!deleted) {
117
117
  return c.json({ error: 'not_found' }, 404);
118
118
  }
@@ -15,12 +15,12 @@ function resolveLinkedTaskId(body) {
15
15
  }
16
16
  export function createDocumentsRouter(documentService) {
17
17
  const router = new Hono();
18
- router.get('/projects/:id/documents', (c) => {
18
+ router.get('/projects/:id/documents', async (c) => {
19
19
  const pagination = parsePaginationParams(c.req.query('limit'), c.req.query('offset'));
20
20
  if (pagination === 'invalid') {
21
21
  return c.json({ error: 'invalid_argument' }, 400);
22
22
  }
23
- const tree = documentService.listTree(c.req.param('id'), pagination);
23
+ const tree = await documentService.listTree(c.req.param('id'), pagination);
24
24
  if (!tree) {
25
25
  return c.json({ error: 'not_found' }, 404);
26
26
  }
@@ -32,7 +32,7 @@ export function createDocumentsRouter(documentService) {
32
32
  return c.json({ error: 'invalid_argument' }, 400);
33
33
  }
34
34
  try {
35
- const document = documentService.create(c.req.param('id'), {
35
+ const document = await documentService.create(c.req.param('id'), {
36
36
  title: body.title,
37
37
  body: body.body,
38
38
  statusLine: body.status_line,
@@ -52,8 +52,8 @@ export function createDocumentsRouter(documentService) {
52
52
  throw error;
53
53
  }
54
54
  });
55
- router.get('/documents/:id', (c) => {
56
- const document = documentService.get(c.req.param('id'));
55
+ router.get('/documents/:id', async (c) => {
56
+ const document = await documentService.get(c.req.param('id'));
57
57
  if (!document) {
58
58
  return c.json({ error: 'not_found' }, 404);
59
59
  }
@@ -63,7 +63,7 @@ export function createDocumentsRouter(documentService) {
63
63
  const body = await c.req.json();
64
64
  const linkedTaskId = resolveLinkedTaskId(body);
65
65
  try {
66
- const document = documentService.update(c.req.param('id'), {
66
+ const document = await documentService.update(c.req.param('id'), {
67
67
  ...(body.title !== undefined ? { title: body.title } : {}),
68
68
  ...(body.body !== undefined ? { body: body.body } : {}),
69
69
  ...(body.status_line !== undefined ? { statusLine: body.status_line } : {}),
@@ -83,15 +83,15 @@ export function createDocumentsRouter(documentService) {
83
83
  throw error;
84
84
  }
85
85
  });
86
- router.delete('/documents/:id', (c) => {
87
- const deleted = documentService.delete(c.req.param('id'));
86
+ router.delete('/documents/:id', async (c) => {
87
+ const deleted = await documentService.delete(c.req.param('id'));
88
88
  if (!deleted) {
89
89
  return c.json({ error: 'not_found' }, 404);
90
90
  }
91
91
  return c.body(null, 204);
92
92
  });
93
- router.get('/tasks/:id/document', (c) => {
94
- const document = documentService.getByTask(c.req.param('id'));
93
+ router.get('/tasks/:id/document', async (c) => {
94
+ const document = await documentService.getByTask(c.req.param('id'));
95
95
  if (!document) {
96
96
  return c.json({ error: 'not_found' }, 404);
97
97
  }
@@ -2,8 +2,8 @@ import { Hono } from 'hono';
2
2
  import { InvalidFolderError } from '../services/folders.js';
3
3
  export function createFoldersRouter(folderService) {
4
4
  const router = new Hono();
5
- router.get('/projects/:id/folders', (c) => {
6
- const folders = folderService.list(c.req.param('id'));
5
+ router.get('/projects/:id/folders', async (c) => {
6
+ const folders = await folderService.list(c.req.param('id'));
7
7
  if (!folders) {
8
8
  return c.json({ error: 'not_found' }, 404);
9
9
  }
@@ -15,7 +15,7 @@ export function createFoldersRouter(folderService) {
15
15
  return c.json({ error: 'invalid_argument' }, 400);
16
16
  }
17
17
  try {
18
- const folder = folderService.create(c.req.param('id'), {
18
+ const folder = await folderService.create(c.req.param('id'), {
19
19
  name: body.name,
20
20
  parentFolderId: body.parent_folder_id,
21
21
  });
@@ -31,8 +31,8 @@ export function createFoldersRouter(folderService) {
31
31
  throw error;
32
32
  }
33
33
  });
34
- router.get('/folders/:id', (c) => {
35
- const folder = folderService.get(c.req.param('id'));
34
+ router.get('/folders/:id', async (c) => {
35
+ const folder = await folderService.get(c.req.param('id'));
36
36
  if (!folder) {
37
37
  return c.json({ error: 'not_found' }, 404);
38
38
  }
@@ -41,7 +41,7 @@ export function createFoldersRouter(folderService) {
41
41
  router.patch('/folders/:id', async (c) => {
42
42
  const body = await c.req.json();
43
43
  try {
44
- const folder = folderService.update(c.req.param('id'), {
44
+ const folder = await folderService.update(c.req.param('id'), {
45
45
  ...(body.name !== undefined ? { name: body.name } : {}),
46
46
  ...(body.parent_folder_id !== undefined ? { parentFolderId: body.parent_folder_id } : {}),
47
47
  });
@@ -57,8 +57,8 @@ export function createFoldersRouter(folderService) {
57
57
  throw error;
58
58
  }
59
59
  });
60
- router.delete('/folders/:id', (c) => {
61
- const deleted = folderService.delete(c.req.param('id'));
60
+ router.delete('/folders/:id', async (c) => {
61
+ const deleted = await folderService.delete(c.req.param('id'));
62
62
  if (!deleted) {
63
63
  return c.json({ error: 'not_found' }, 404);
64
64
  }
@@ -46,7 +46,7 @@ export function createGoalsRouter(goalService) {
46
46
  return c.json({ error: 'invalid_argument' }, 400);
47
47
  }
48
48
  try {
49
- const goal = goalService.create(c.req.param('id'), {
49
+ const goal = await goalService.create(c.req.param('id'), {
50
50
  objective: body.objective,
51
51
  ...(body.status !== undefined ? { status: body.status } : {}),
52
52
  ...mapGoalInput(body),
@@ -60,15 +60,15 @@ export function createGoalsRouter(goalService) {
60
60
  return handleGoalError(c, error);
61
61
  }
62
62
  });
63
- router.get('/projects/:id/goals', (c) => {
64
- const goals = goalService.listByProject(c.req.param('id'));
63
+ router.get('/projects/:id/goals', async (c) => {
64
+ const goals = await goalService.listByProject(c.req.param('id'));
65
65
  if (!goals) {
66
66
  return c.json({ error: 'not_found' }, 404);
67
67
  }
68
68
  return c.json(goals);
69
69
  });
70
- router.get('/goals/:id', (c) => {
71
- const goal = goalService.get(c.req.param('id'));
70
+ router.get('/goals/:id', async (c) => {
71
+ const goal = await goalService.get(c.req.param('id'));
72
72
  if (!goal) {
73
73
  return c.json({ error: 'not_found' }, 404);
74
74
  }
@@ -80,7 +80,7 @@ export function createGoalsRouter(goalService) {
80
80
  return c.json({ error: 'invalid_argument' }, 400);
81
81
  }
82
82
  try {
83
- const goal = goalService.update(c.req.param('id'), mapGoalInput(body));
83
+ const goal = await goalService.update(c.req.param('id'), mapGoalInput(body));
84
84
  if (!goal) {
85
85
  return c.json({ error: 'not_found' }, 404);
86
86
  }
@@ -90,9 +90,9 @@ export function createGoalsRouter(goalService) {
90
90
  return handleGoalError(c, error);
91
91
  }
92
92
  });
93
- router.post('/goals/:id/pause', (c) => {
93
+ router.post('/goals/:id/pause', async (c) => {
94
94
  try {
95
- const goal = goalService.pause(c.req.param('id'));
95
+ const goal = await goalService.pause(c.req.param('id'));
96
96
  if (!goal) {
97
97
  return c.json({ error: 'not_found' }, 404);
98
98
  }
@@ -102,9 +102,9 @@ export function createGoalsRouter(goalService) {
102
102
  return handleGoalError(c, error);
103
103
  }
104
104
  });
105
- router.post('/goals/:id/resume', (c) => {
105
+ router.post('/goals/:id/resume', async (c) => {
106
106
  try {
107
- const goal = goalService.resume(c.req.param('id'));
107
+ const goal = await goalService.resume(c.req.param('id'));
108
108
  if (!goal) {
109
109
  return c.json({ error: 'not_found' }, 404);
110
110
  }
@@ -123,7 +123,7 @@ export function createGoalsRouter(goalService) {
123
123
  // empty body is valid when the goal has no verification_surface
124
124
  }
125
125
  try {
126
- const goal = goalService.complete(c.req.param('id'), body.evidence);
126
+ const goal = await goalService.complete(c.req.param('id'), body.evidence);
127
127
  if (!goal) {
128
128
  return c.json({ error: 'not_found' }, 404);
129
129
  }
@@ -3,12 +3,12 @@ import { InvalidNoteError } from '../services/notes.js';
3
3
  import { parsePaginationParams } from '../serialize.js';
4
4
  export function createNotesRouter(noteService) {
5
5
  const router = new Hono();
6
- router.get('/projects/:id/notes', (c) => {
6
+ router.get('/projects/:id/notes', async (c) => {
7
7
  const pagination = parsePaginationParams(c.req.query('limit'), c.req.query('offset'));
8
8
  if (pagination === 'invalid') {
9
9
  return c.json({ error: 'invalid_argument' }, 400);
10
10
  }
11
- const notes = noteService.list(c.req.param('id'), pagination);
11
+ const notes = await noteService.list(c.req.param('id'), pagination);
12
12
  if (!notes) {
13
13
  return c.json({ error: 'not_found' }, 404);
14
14
  }
@@ -20,7 +20,7 @@ export function createNotesRouter(noteService) {
20
20
  return c.json({ error: 'invalid_argument' }, 400);
21
21
  }
22
22
  try {
23
- const note = noteService.create(c.req.param('id'), {
23
+ const note = await noteService.create(c.req.param('id'), {
24
24
  title: body.title,
25
25
  body: body.body,
26
26
  });
@@ -36,8 +36,8 @@ export function createNotesRouter(noteService) {
36
36
  throw error;
37
37
  }
38
38
  });
39
- router.get('/notes/:id', (c) => {
40
- const note = noteService.get(c.req.param('id'));
39
+ router.get('/notes/:id', async (c) => {
40
+ const note = await noteService.get(c.req.param('id'));
41
41
  if (!note) {
42
42
  return c.json({ error: 'not_found' }, 404);
43
43
  }
@@ -46,7 +46,7 @@ export function createNotesRouter(noteService) {
46
46
  router.patch('/notes/:id', async (c) => {
47
47
  const body = await c.req.json();
48
48
  try {
49
- const note = noteService.update(c.req.param('id'), {
49
+ const note = await noteService.update(c.req.param('id'), {
50
50
  ...(body.title !== undefined ? { title: body.title } : {}),
51
51
  ...(body.body !== undefined ? { body: body.body } : {}),
52
52
  });
@@ -62,8 +62,8 @@ export function createNotesRouter(noteService) {
62
62
  throw error;
63
63
  }
64
64
  });
65
- router.delete('/notes/:id', (c) => {
66
- const deleted = noteService.delete(c.req.param('id'));
65
+ router.delete('/notes/:id', async (c) => {
66
+ const deleted = await noteService.delete(c.req.param('id'));
67
67
  if (!deleted) {
68
68
  return c.json({ error: 'not_found' }, 404);
69
69
  }
@@ -0,0 +1,17 @@
1
+ import { Hono } from 'hono';
2
+ import { type Db } from '@plandesk/db';
3
+ import type { BetterAuthInstance } from '../better-auth.js';
4
+ export type OrgsRouterOptions = {
5
+ betterAuth?: BetterAuthInstance;
6
+ /** Base URL for claim links returned from invitations (no trailing slash required). */
7
+ baseURL?: string;
8
+ };
9
+ /**
10
+ * There is deliberately no `POST /orgs`.
11
+ *
12
+ * An org is only ever created by better-auth identity provisioning (BA4c) or by
13
+ * `ensureLocalBetterAuthOrganization` at `serve` boot for the local single-org case.
14
+ * Both bound creation by construction, which is why no quota is needed to bound it.
15
+ */
16
+ export declare function createOrgsRouter(db: Db, options?: OrgsRouterOptions): Hono;
17
+ //# sourceMappingURL=orgs.d.ts.map
@@ -0,0 +1,241 @@
1
+ import { Hono } from 'hono';
2
+ import { getProjectInOrg, importProject, InvalidExportVersionError, PLANDESK_EXPORT_VERSION, } from '@plandesk/db';
3
+ import { createScopedAgentKey } from '../agent-keys.js';
4
+ import { getAuthContext, getOrgAuthContext } from '../auth-context.js';
5
+ import { acceptOrganizationInvitation, createOrganizationInvitation, isAuthApiError, isInvitationRole, } from '../invitations.js';
6
+ import { getOrganizationById } from '../organizations.js';
7
+ import { requirePermission } from '../permissions.js';
8
+ function isPermissionSet(value) {
9
+ if (value === null || typeof value !== 'object' || Array.isArray(value)) {
10
+ return false;
11
+ }
12
+ for (const actions of Object.values(value)) {
13
+ if (!Array.isArray(actions) ||
14
+ !actions.every((action) => typeof action === 'string')) {
15
+ return false;
16
+ }
17
+ }
18
+ return true;
19
+ }
20
+ /**
21
+ * Resolve the better-auth user id that will own the minted agent key.
22
+ * Apikey (owner key) carries userId; session needs a better-auth session cookie.
23
+ */
24
+ async function resolveMintUserId(betterAuth, headers) {
25
+ const ctx = getAuthContext();
26
+ if (ctx.kind === 'apikey') {
27
+ return ctx.userId;
28
+ }
29
+ if (ctx.kind === 'session') {
30
+ const baSession = await betterAuth.api.getSession({ headers });
31
+ if (baSession === null) {
32
+ return undefined;
33
+ }
34
+ return baSession.user.id;
35
+ }
36
+ return undefined;
37
+ }
38
+ /**
39
+ * There is deliberately no `POST /orgs`.
40
+ *
41
+ * An org is only ever created by better-auth identity provisioning (BA4c) or by
42
+ * `ensureLocalBetterAuthOrganization` at `serve` boot for the local single-org case.
43
+ * Both bound creation by construction, which is why no quota is needed to bound it.
44
+ */
45
+ export function createOrgsRouter(db, options = {}) {
46
+ const router = new Hono();
47
+ const betterAuth = options.betterAuth;
48
+ const baseURL = options.baseURL ?? 'http://127.0.0.1';
49
+ async function requireKnownOrg(orgId) {
50
+ if (getOrgAuthContext().orgId !== orgId) {
51
+ return false;
52
+ }
53
+ if (betterAuth === undefined) {
54
+ return true;
55
+ }
56
+ return (await getOrganizationById(betterAuth, orgId)) !== undefined;
57
+ }
58
+ /**
59
+ * BA4b-3: mint a project-scoped agent key for `plandesk connect --to`.
60
+ * Caller must hold apiKey:create (owner key or session owner) — agent keys cannot.
61
+ * Raw key returned once; metadata is { projectId, orgId } (agent profile).
62
+ */
63
+ router.post('/orgs/:orgId/agent-keys', async (c) => {
64
+ if (betterAuth === undefined) {
65
+ return c.json({ error: 'unavailable' }, 503);
66
+ }
67
+ const orgId = c.req.param('orgId');
68
+ if (!(await requireKnownOrg(orgId))) {
69
+ return c.json({ error: 'not_found' }, 404);
70
+ }
71
+ requirePermission(getOrgAuthContext(), 'apiKey', 'create');
72
+ const body = await c.req.json();
73
+ if (typeof body.project_id !== 'string' || body.project_id.trim() === '') {
74
+ return c.json({ error: 'invalid_argument' }, 400);
75
+ }
76
+ const projectId = body.project_id.trim();
77
+ const project = await getProjectInOrg(db, projectId, orgId);
78
+ if (project === undefined) {
79
+ return c.json({ error: 'not_found' }, 404);
80
+ }
81
+ let permissions;
82
+ if (body.permissions !== undefined) {
83
+ if (!isPermissionSet(body.permissions)) {
84
+ return c.json({ error: 'invalid_argument' }, 400);
85
+ }
86
+ permissions = body.permissions;
87
+ }
88
+ let name;
89
+ if (body.name !== undefined) {
90
+ if (typeof body.name !== 'string' || body.name.trim() === '') {
91
+ return c.json({ error: 'invalid_argument' }, 400);
92
+ }
93
+ name = body.name.trim();
94
+ }
95
+ const userId = await resolveMintUserId(betterAuth, c.req.raw.headers);
96
+ if (userId === undefined) {
97
+ return c.json({ error: 'unauthorized' }, 401);
98
+ }
99
+ const mintInput = {
100
+ auth: betterAuth,
101
+ userId,
102
+ orgId,
103
+ projectId,
104
+ ...(permissions !== undefined ? { permissions } : {}),
105
+ ...(name !== undefined ? { name } : {}),
106
+ };
107
+ const minted = await createScopedAgentKey(mintInput);
108
+ return c.json({ token: minted.key, project_id: projectId }, 200);
109
+ });
110
+ /**
111
+ * BA3c: invite by email (link-only, no mailer). Session owner only
112
+ * (member:create). Returns claimUrl for the inviter to deliver by hand.
113
+ */
114
+ router.post('/orgs/:id/invitations', async (c) => {
115
+ const orgId = c.req.param('id');
116
+ if (!(await requireKnownOrg(orgId))) {
117
+ return c.json({ error: 'not_found' }, 404);
118
+ }
119
+ const authCtx = getOrgAuthContext();
120
+ // Session owner only — token/loopback cannot drive better-auth createInvitation.
121
+ if (authCtx.kind !== 'session') {
122
+ return c.json({ error: 'forbidden' }, 403);
123
+ }
124
+ requirePermission(authCtx, 'member', 'create');
125
+ if (betterAuth === undefined) {
126
+ return c.json({ error: 'unavailable' }, 503);
127
+ }
128
+ const body = await c.req.json();
129
+ if (typeof body.email !== 'string' || body.email.trim() === '') {
130
+ return c.json({ error: 'invalid_argument' }, 400);
131
+ }
132
+ if (typeof body.role !== 'string' || !isInvitationRole(body.role)) {
133
+ return c.json({ error: 'invalid_argument' }, 400);
134
+ }
135
+ try {
136
+ const created = await createOrganizationInvitation(betterAuth, {
137
+ email: body.email,
138
+ role: body.role,
139
+ organizationId: orgId,
140
+ headers: c.req.raw.headers,
141
+ baseURL,
142
+ });
143
+ return c.json({
144
+ invitationId: created.invitationId,
145
+ claimUrl: created.claimUrl,
146
+ }, 201);
147
+ }
148
+ catch (err) {
149
+ if (isAuthApiError(err)) {
150
+ if (err.statusCode === 403 || err.status === 'FORBIDDEN') {
151
+ return c.json({ error: 'forbidden' }, 403);
152
+ }
153
+ return c.json({ error: 'invalid_argument', message: err.message }, 400);
154
+ }
155
+ throw err;
156
+ }
157
+ });
158
+ /**
159
+ * BA3c: accept invitation. Session-gated at the handler (public path so
160
+ * org-less invitees can reach it); not org-gated. Single-use → 410 on retry.
161
+ */
162
+ router.post('/invitations/:invitationId/accept', async (c) => {
163
+ if (betterAuth === undefined) {
164
+ return c.json({ error: 'unavailable' }, 503);
165
+ }
166
+ const invitationId = c.req.param('invitationId');
167
+ if (invitationId.trim() === '') {
168
+ return c.json({ error: 'invalid_argument' }, 400);
169
+ }
170
+ const session = await betterAuth.api.getSession({ headers: c.req.raw.headers });
171
+ if (session === null) {
172
+ return c.json({ error: 'unauthorized' }, 401);
173
+ }
174
+ try {
175
+ const result = await acceptOrganizationInvitation(betterAuth, {
176
+ invitationId,
177
+ headers: c.req.raw.headers,
178
+ });
179
+ return c.json({
180
+ invitationId: result.invitation.id,
181
+ organizationId: result.member.organizationId,
182
+ role: result.member.role,
183
+ userId: result.member.userId,
184
+ }, 200);
185
+ }
186
+ catch (err) {
187
+ if (isAuthApiError(err)) {
188
+ // Already accepted / expired / missing → single-use CAS failure.
189
+ if (err.statusCode === 400 ||
190
+ err.status === 'BAD_REQUEST' ||
191
+ err.message.toLowerCase().includes('invitation')) {
192
+ return c.json({ error: 'gone', message: err.message }, 410);
193
+ }
194
+ if (err.statusCode === 403 || err.status === 'FORBIDDEN') {
195
+ return c.json({ error: 'forbidden' }, 403);
196
+ }
197
+ return c.json({ error: 'invalid_argument', message: err.message }, 400);
198
+ }
199
+ throw err;
200
+ }
201
+ });
202
+ // Promote a portable export into this org (one-way authority handoff).
203
+ router.post('/orgs/:id/import', async (c) => {
204
+ const orgId = c.req.param('id');
205
+ // Org-scoped: token for org-B cannot import into org-A.
206
+ if (!(await requireKnownOrg(orgId))) {
207
+ return c.json({ error: 'not_found' }, 404);
208
+ }
209
+ requirePermission(getOrgAuthContext(), 'organization', 'update');
210
+ let body;
211
+ try {
212
+ body = await c.req.json();
213
+ }
214
+ catch {
215
+ return c.json({ error: 'invalid_argument' }, 400);
216
+ }
217
+ if (body === null || typeof body !== 'object') {
218
+ return c.json({ error: 'invalid_argument' }, 400);
219
+ }
220
+ const data = body;
221
+ if (typeof data.version !== 'string' || data.version !== PLANDESK_EXPORT_VERSION) {
222
+ return c.json({ error: 'invalid_argument' }, 400);
223
+ }
224
+ if (data.project === undefined || typeof data.project !== 'object') {
225
+ return c.json({ error: 'invalid_argument' }, 400);
226
+ }
227
+ try {
228
+ // orgId always from authenticated path/context — never from the body.
229
+ const { projectId } = await importProject(db, data, { orgId });
230
+ return c.json({ globalProjectId: projectId }, 201);
231
+ }
232
+ catch (err) {
233
+ if (err instanceof InvalidExportVersionError) {
234
+ return c.json({ error: 'invalid_argument' }, 400);
235
+ }
236
+ throw err;
237
+ }
238
+ });
239
+ return router;
240
+ }
241
+ //# sourceMappingURL=orgs.js.map
@@ -6,26 +6,26 @@ import { isStringArray } from './tasks.js';
6
6
  import { parsePaginationParams } from '../serialize.js';
7
7
  export function createProjectsRouter(projectService, taskService) {
8
8
  const router = new Hono();
9
- router.get('/projects', (c) => {
9
+ router.get('/projects', async (c) => {
10
10
  const pagination = parsePaginationParams(c.req.query('limit'), c.req.query('offset'));
11
11
  if (pagination === 'invalid') {
12
12
  return c.json({ error: 'invalid_argument' }, 400);
13
13
  }
14
- return c.json(projectService.list(pagination));
14
+ return c.json(await projectService.list(pagination));
15
15
  });
16
16
  router.post('/projects', async (c) => {
17
17
  const body = await c.req.json();
18
18
  if (typeof body.name !== 'string' || body.name.trim() === '') {
19
19
  return c.json({ error: 'invalid_argument' }, 400);
20
20
  }
21
- const project = projectService.create({
21
+ const project = await projectService.create({
22
22
  name: body.name,
23
23
  description: body.description,
24
24
  });
25
25
  return c.json(project, 201);
26
26
  });
27
- router.get('/projects/:id', (c) => {
28
- const project = projectService.get(c.req.param('id'));
27
+ router.get('/projects/:id', async (c) => {
28
+ const project = await projectService.get(c.req.param('id'));
29
29
  if (!project) {
30
30
  return c.json({ error: 'not_found' }, 404);
31
31
  }
@@ -36,7 +36,7 @@ export function createProjectsRouter(projectService, taskService) {
36
36
  if (body.name !== undefined && (typeof body.name !== 'string' || body.name.trim() === '')) {
37
37
  return c.json({ error: 'invalid_argument' }, 400);
38
38
  }
39
- const project = projectService.update(c.req.param('id'), {
39
+ const project = await projectService.update(c.req.param('id'), {
40
40
  ...(body.name !== undefined ? { name: body.name } : {}),
41
41
  ...(body.description !== undefined ? { description: body.description } : {}),
42
42
  });
@@ -45,8 +45,8 @@ export function createProjectsRouter(projectService, taskService) {
45
45
  }
46
46
  return c.json(project);
47
47
  });
48
- router.delete('/projects/:id', (c) => {
49
- const deleted = projectService.delete(c.req.param('id'));
48
+ router.delete('/projects/:id', async (c) => {
49
+ const deleted = await projectService.delete(c.req.param('id'));
50
50
  if (!deleted) {
51
51
  return c.json({ error: 'not_found' }, 404);
52
52
  }
@@ -74,7 +74,7 @@ export function createProjectsRouter(projectService, taskService) {
74
74
  dueDate = null;
75
75
  }
76
76
  try {
77
- const task = taskService.create(c.req.param('id'), {
77
+ const task = await taskService.create(c.req.param('id'), {
78
78
  label: body.label,
79
79
  ...(body.status !== undefined ? { status: body.status } : {}),
80
80
  ...(body.description !== undefined ? { description: body.description } : {}),
@@ -99,7 +99,7 @@ export function createProjectsRouter(projectService, taskService) {
99
99
  throw error;
100
100
  }
101
101
  });
102
- router.get('/projects/:id/tasks', (c) => {
102
+ router.get('/projects/:id/tasks', async (c) => {
103
103
  try {
104
104
  const pagination = parsePaginationParams(c.req.query('limit'), c.req.query('offset'));
105
105
  if (pagination === 'invalid') {
@@ -109,7 +109,7 @@ export function createProjectsRouter(projectService, taskService) {
109
109
  // Repeated ?tag= params filter with OR semantics (task matches if it has
110
110
  // ANY of the given tags).
111
111
  const tags = c.req.queries('tag');
112
- const tasks = taskService.listByProject(c.req.param('id'), { status, ...(tags !== undefined && tags.length > 0 ? { tags } : {}) }, pagination);
112
+ const tasks = await taskService.listByProject(c.req.param('id'), { status, ...(tags !== undefined && tags.length > 0 ? { tags } : {}) }, pagination);
113
113
  if (!tasks) {
114
114
  return c.json({ error: 'not_found' }, 404);
115
115
  }
@@ -122,8 +122,8 @@ export function createProjectsRouter(projectService, taskService) {
122
122
  throw error;
123
123
  }
124
124
  });
125
- router.get('/projects/:id/next-task', (c) => {
126
- const result = taskService.nextActionable(c.req.param('id'));
125
+ router.get('/projects/:id/next-task', async (c) => {
126
+ const result = await taskService.nextActionable(c.req.param('id'));
127
127
  if (!result) {
128
128
  return c.json({ error: 'not_found' }, 404);
129
129
  }