studiograph 1.3.2 → 1.3.3-next.10

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 (118) hide show
  1. package/dist/agent/orchestrator.d.ts +8 -0
  2. package/dist/agent/orchestrator.js +13 -2
  3. package/dist/agent/orchestrator.js.map +1 -1
  4. package/dist/agent/tools/graph-tools.d.ts +5 -1
  5. package/dist/agent/tools/graph-tools.js +161 -9
  6. package/dist/agent/tools/graph-tools.js.map +1 -1
  7. package/dist/agent/tools/permission-tools.d.ts +15 -14
  8. package/dist/agent/tools/permission-tools.js +65 -128
  9. package/dist/agent/tools/permission-tools.js.map +1 -1
  10. package/dist/cli/commands/join.d.ts +3 -2
  11. package/dist/cli/commands/join.js +93 -98
  12. package/dist/cli/commands/join.js.map +1 -1
  13. package/dist/cli/commands/redeploy.js +14 -22
  14. package/dist/cli/commands/redeploy.js.map +1 -1
  15. package/dist/cli/commands/serve.js +3 -3
  16. package/dist/cli/commands/serve.js.map +1 -1
  17. package/dist/cli/commands/sync.d.ts +4 -2
  18. package/dist/cli/commands/sync.js +21 -22
  19. package/dist/cli/commands/sync.js.map +1 -1
  20. package/dist/cli/commands/user.d.ts +7 -0
  21. package/dist/cli/commands/user.js +153 -0
  22. package/dist/cli/commands/user.js.map +1 -0
  23. package/dist/cli/index.js +3 -0
  24. package/dist/cli/index.js.map +1 -1
  25. package/dist/core/graph.d.ts +8 -2
  26. package/dist/core/graph.js +11 -7
  27. package/dist/core/graph.js.map +1 -1
  28. package/dist/core/types.d.ts +9 -0
  29. package/dist/core/types.js +1 -0
  30. package/dist/core/types.js.map +1 -1
  31. package/dist/core/workspace-manager.js +1 -5
  32. package/dist/core/workspace-manager.js.map +1 -1
  33. package/dist/core/workspace.js +1 -1
  34. package/dist/core/workspace.js.map +1 -1
  35. package/dist/server/chrome/chrome.js +12 -212
  36. package/dist/server/commit-scheduler.d.ts +39 -0
  37. package/dist/server/commit-scheduler.js +99 -0
  38. package/dist/server/commit-scheduler.js.map +1 -0
  39. package/dist/server/index.js +86 -46
  40. package/dist/server/index.js.map +1 -1
  41. package/dist/server/routes/auth-api.d.ts +8 -0
  42. package/dist/server/routes/auth-api.js +163 -0
  43. package/dist/server/routes/auth-api.js.map +1 -0
  44. package/dist/server/routes/chat.js +4 -0
  45. package/dist/server/routes/chat.js.map +1 -1
  46. package/dist/server/routes/git-http.d.ts +23 -0
  47. package/dist/server/routes/git-http.js +213 -0
  48. package/dist/server/routes/git-http.js.map +1 -0
  49. package/dist/server/routes/graph-api.d.ts +6 -2
  50. package/dist/server/routes/graph-api.js +230 -50
  51. package/dist/server/routes/graph-api.js.map +1 -1
  52. package/dist/server/routes/permissions-api.d.ts +5 -4
  53. package/dist/server/routes/permissions-api.js +39 -167
  54. package/dist/server/routes/permissions-api.js.map +1 -1
  55. package/dist/server/routes/ws.d.ts +7 -0
  56. package/dist/server/routes/ws.js +35 -0
  57. package/dist/server/routes/ws.js.map +1 -0
  58. package/dist/server/ws-hub.d.ts +36 -0
  59. package/dist/server/ws-hub.js +63 -0
  60. package/dist/server/ws-hub.js.map +1 -0
  61. package/dist/services/auth-service.d.ts +77 -0
  62. package/dist/services/auth-service.js +302 -0
  63. package/dist/services/auth-service.js.map +1 -0
  64. package/dist/utils/git.d.ts +21 -1
  65. package/dist/utils/git.js +69 -3
  66. package/dist/utils/git.js.map +1 -1
  67. package/dist/web/_app/immutable/assets/{0.CDbX4Cwz.css → 0.CL-hNrKE.css} +1 -1
  68. package/dist/web/_app/immutable/assets/7.Cn2DG-J6.css +1 -0
  69. package/dist/web/_app/immutable/assets/8.Sm6jB3a0.css +1 -0
  70. package/dist/web/_app/immutable/assets/AppShell.RYbgfVr0.css +1 -0
  71. package/dist/web/_app/immutable/chunks/-lhxaNNQ.js +1 -0
  72. package/dist/web/_app/immutable/chunks/BFD-PG4k.js +2 -0
  73. package/dist/web/_app/immutable/chunks/BKhAbhZ4.js +1 -0
  74. package/dist/web/_app/immutable/chunks/BUuoVpOJ.js +1 -0
  75. package/dist/web/_app/immutable/chunks/BuHHk4nP.js +1 -0
  76. package/dist/web/_app/immutable/chunks/Bv8xNJQh.js +1 -0
  77. package/dist/web/_app/immutable/chunks/C0iyiXwO.js +1 -0
  78. package/dist/web/_app/immutable/chunks/CH-raHh7.js +1 -0
  79. package/dist/web/_app/immutable/chunks/{DTUXhwEY.js → CQo_whF8.js} +1 -1
  80. package/dist/web/_app/immutable/chunks/CiIF45lL.js +1 -0
  81. package/dist/web/_app/immutable/chunks/{DEJSHbC3.js → Cs6vwwZC.js} +1 -1
  82. package/dist/web/_app/immutable/chunks/{C1SF7XfX.js → DJO0wVMY.js} +4 -4
  83. package/dist/web/_app/immutable/chunks/DeY0oOW3.js +2 -0
  84. package/dist/web/_app/immutable/chunks/{BHedmkKI.js → Dj2efhG6.js} +18 -18
  85. package/dist/web/_app/immutable/chunks/ew-IdGn0.js +1 -0
  86. package/dist/web/_app/immutable/chunks/yEjjrv_c.js +23 -0
  87. package/dist/web/_app/immutable/entry/app.BNN66g6y.js +2 -0
  88. package/dist/web/_app/immutable/entry/start.CbKqTiwM.js +1 -0
  89. package/dist/web/_app/immutable/nodes/0.DthLeuCh.js +2 -0
  90. package/dist/web/_app/immutable/nodes/1.ZBkeuxO_.js +1 -0
  91. package/dist/web/_app/immutable/nodes/2.CioLRnGy.js +1 -0
  92. package/dist/web/_app/immutable/nodes/3.Df3ut4ji.js +1 -0
  93. package/dist/web/_app/immutable/nodes/4.HFzA-u2O.js +16 -0
  94. package/dist/web/_app/immutable/nodes/5.h31NMedP.js +4 -0
  95. package/dist/web/_app/immutable/nodes/6.MktCIoXa.js +2 -0
  96. package/dist/web/_app/immutable/nodes/7.CZ7lC_rb.js +1 -0
  97. package/dist/web/_app/immutable/nodes/8.CRf2WFmY.js +1 -0
  98. package/dist/web/_app/version.json +1 -1
  99. package/dist/web/index.html +12 -12
  100. package/package.json +11 -2
  101. package/dist/web/_app/immutable/assets/AppShell.D0rmbdqF.css +0 -1
  102. package/dist/web/_app/immutable/chunks/Bopa-Ask.js +0 -1
  103. package/dist/web/_app/immutable/chunks/CEkx7wvp.js +0 -1
  104. package/dist/web/_app/immutable/chunks/COwytaCP.js +0 -1
  105. package/dist/web/_app/immutable/chunks/Dml-u95b.js +0 -2
  106. package/dist/web/_app/immutable/chunks/DvKVaE7M.js +0 -1
  107. package/dist/web/_app/immutable/chunks/J4wxg_sP.js +0 -23
  108. package/dist/web/_app/immutable/chunks/MbiSz-iW.js +0 -2
  109. package/dist/web/_app/immutable/chunks/bSAC733J.js +0 -1
  110. package/dist/web/_app/immutable/entry/app.B0KkA_jR.js +0 -2
  111. package/dist/web/_app/immutable/entry/start.DFSNI2p-.js +0 -1
  112. package/dist/web/_app/immutable/nodes/0.DfbCOBhn.js +0 -2
  113. package/dist/web/_app/immutable/nodes/1.DMtWWiM4.js +0 -1
  114. package/dist/web/_app/immutable/nodes/2.CgKSJOen.js +0 -1
  115. package/dist/web/_app/immutable/nodes/3.CVYHBZE3.js +0 -1
  116. package/dist/web/_app/immutable/nodes/4.CHN1uWec.js +0 -16
  117. package/dist/web/_app/immutable/nodes/5.B4_87Wva.js +0 -4
  118. package/dist/web/_app/immutable/nodes/6.CGZ970f8.js +0 -2
@@ -1,176 +1,48 @@
1
1
  /**
2
2
  * Permissions API routes
3
3
  *
4
- * GitHub-based org, team, and repo access management.
5
- * Only available when GitHub org is configured and user has a valid token.
4
+ * Self-hosted per-collection access management backed by AuthService.
5
+ * All endpoints require admin role.
6
6
  */
7
- import { loadToken, getAuthenticatedOctokit } from '../../auth/github.js';
8
- import { GitHubService } from '../../services/github-service.js';
9
- function getGitHubService(config) {
10
- const org = config.github?.org;
11
- if (!org)
12
- return null;
13
- const stored = loadToken();
14
- if (!stored)
15
- return null;
16
- const octokit = getAuthenticatedOctokit(stored.token);
17
- return new GitHubService(octokit, org);
18
- }
19
- export async function registerPermissionsApiRoutes(fastify, workspaceConfig) {
20
- // GET /api/permissions/role — current user's org role and login
21
- fastify.get('/api/permissions/role', async (_req, reply) => {
22
- const stored = loadToken();
23
- const service = getGitHubService(workspaceConfig);
24
- if (!service) {
25
- return reply.send({ role: null, login: null, reason: 'not_configured' });
26
- }
27
- try {
28
- const role = await service.getCurrentUserRole();
29
- return reply.send({ role, login: stored?.user?.login ?? null });
30
- }
31
- catch (err) {
32
- return reply.status(500).send({ error: err.message });
33
- }
34
- });
35
- // GET /api/permissions/members — list org members
36
- fastify.get('/api/permissions/members', async (_req, reply) => {
37
- const service = getGitHubService(workspaceConfig);
38
- if (!service) {
39
- return reply.status(400).send({ error: 'GitHub not configured' });
40
- }
41
- try {
42
- const members = await service.listMembers();
43
- return reply.send(members);
44
- }
45
- catch (err) {
46
- return reply.status(500).send({ error: err.message });
47
- }
48
- });
49
- // POST /api/permissions/members/invite — invite user to org
50
- fastify.post('/api/permissions/members/invite', async (req, reply) => {
51
- const service = getGitHubService(workspaceConfig);
52
- if (!service) {
53
- return reply.status(400).send({ error: 'GitHub not configured' });
54
- }
55
- const { username } = req.body ?? {};
56
- if (!username) {
57
- return reply.status(400).send({ error: 'username is required' });
58
- }
59
- try {
60
- const isMember = await service.isOrgMember(username);
61
- if (isMember) {
62
- return reply.send({ status: 'already_member' });
63
- }
64
- await service.inviteUser(username);
65
- return reply.send({ status: 'invited' });
66
- }
67
- catch (err) {
68
- if (err?.status === 422) {
69
- return reply.send({ status: 'already_invited' });
70
- }
71
- return reply.status(err?.status ?? 500).send({ error: err.message });
72
- }
73
- });
74
- // DELETE /api/permissions/members/:username — remove user from org
75
- fastify.delete('/api/permissions/members/:username', async (req, reply) => {
76
- const service = getGitHubService(workspaceConfig);
77
- if (!service) {
78
- return reply.status(400).send({ error: 'GitHub not configured' });
79
- }
80
- try {
81
- await service.removeUser(req.params.username);
82
- return reply.send({ ok: true });
83
- }
84
- catch (err) {
85
- return reply.status(err?.status ?? 500).send({ error: err.message });
86
- }
87
- });
88
- // GET /api/permissions/collections — access info for all collections
89
- fastify.get('/api/permissions/collections', async (_req, reply) => {
90
- const service = getGitHubService(workspaceConfig);
91
- if (!service) {
92
- return reply.status(400).send({ error: 'GitHub not configured' });
93
- }
94
- try {
95
- const repos = workspaceConfig.repos ?? [];
96
- const results = await Promise.all(repos
97
- .filter(r => !r.private)
98
- .map(async (repo) => {
99
- const repoName = extractRepoName(repo.github_url) ?? repo.name;
100
- try {
101
- const access = await service.getRepoAccess(repoName);
102
- return { collection: repo.name, repoName, ...access };
103
- }
104
- catch {
105
- return { collection: repo.name, repoName, teams: [], collaborators: [] };
106
- }
107
- }));
108
- return reply.send(results);
109
- }
110
- catch (err) {
111
- return reply.status(500).send({ error: err.message });
112
- }
7
+ export async function registerPermissionsApiRoutes(fastify, authService, workspaceManager) {
8
+ // GET /api/permissions/collections all collections with their granted users
9
+ fastify.get('/api/permissions/collections', async (req, reply) => {
10
+ const user = req.user;
11
+ if (user && user.role !== 'admin') {
12
+ return reply.status(403).send({ error: 'Admin access required' });
13
+ }
14
+ const repos = workspaceManager.getAllRepoConfigs();
15
+ const result = repos.map(repo => ({
16
+ collection: repo.name,
17
+ users: authService.getCollectionUsers(repo.name),
18
+ }));
19
+ return reply.send(result);
113
20
  });
114
- // POST /api/permissions/collections/:collection/grant — grant user access via auto-team
115
- fastify.post('/api/permissions/collections/:collection/grant', async (req, reply) => {
116
- const service = getGitHubService(workspaceConfig);
117
- if (!service) {
118
- return reply.status(400).send({ error: 'GitHub not configured' });
119
- }
120
- const { username, permission } = req.body ?? {};
121
- if (!username) {
122
- return reply.status(400).send({ error: 'username is required' });
123
- }
124
- const repo = findRepo(workspaceConfig, req.params.collection);
125
- if (!repo) {
126
- return reply.status(404).send({ error: 'Collection not found' });
127
- }
128
- const repoName = extractRepoName(repo.github_url) ?? repo.name;
129
- try {
130
- const result = await service.grantAccessViaTeam(repoName, username, permission ?? 'push');
131
- return reply.send(result);
132
- }
133
- catch (err) {
134
- return reply.status(err?.status ?? 500).send({ error: err.message });
135
- }
21
+ // POST /api/permissions/grant — grant user access to a collection
22
+ fastify.post('/api/permissions/grant', async (req, reply) => {
23
+ const user = req.user;
24
+ if (user && user.role !== 'admin') {
25
+ return reply.status(403).send({ error: 'Admin access required' });
26
+ }
27
+ const { userId, collection } = req.body ?? {};
28
+ if (!userId || !collection) {
29
+ return reply.status(400).send({ error: 'userId and collection are required' });
30
+ }
31
+ authService.grantCollectionAccess(userId, collection);
32
+ return reply.send({ ok: true });
136
33
  });
137
- // DELETE /api/permissions/collections/:collection/revoke/:username — revoke user access
138
- fastify.delete('/api/permissions/collections/:collection/revoke/:username', async (req, reply) => {
139
- const service = getGitHubService(workspaceConfig);
140
- if (!service) {
141
- return reply.status(400).send({ error: 'GitHub not configured' });
142
- }
143
- const repo = findRepo(workspaceConfig, req.params.collection);
144
- if (!repo) {
145
- return reply.status(404).send({ error: 'Collection not found' });
146
- }
147
- const repoName = extractRepoName(repo.github_url) ?? repo.name;
148
- const { username } = req.params;
149
- try {
150
- try {
151
- await service.removeFromTeam(repoName, username);
152
- }
153
- catch { /* not on team */ }
154
- try {
155
- await service.revokeUserAccess(repoName, username);
156
- }
157
- catch { /* not a collaborator */ }
158
- return reply.send({ ok: true });
159
- }
160
- catch (err) {
161
- return reply.status(err?.status ?? 500).send({ error: err.message });
162
- }
34
+ // POST /api/permissions/revoke — revoke user access to a collection
35
+ fastify.post('/api/permissions/revoke', async (req, reply) => {
36
+ const user = req.user;
37
+ if (user && user.role !== 'admin') {
38
+ return reply.status(403).send({ error: 'Admin access required' });
39
+ }
40
+ const { userId, collection } = req.body ?? {};
41
+ if (!userId || !collection) {
42
+ return reply.status(400).send({ error: 'userId and collection are required' });
43
+ }
44
+ authService.revokeCollectionAccess(userId, collection);
45
+ return reply.send({ ok: true });
163
46
  });
164
47
  }
165
- /** Extract repo name from a GitHub URL like https://github.com/org/repo-name.git */
166
- function extractRepoName(url) {
167
- if (!url)
168
- return undefined;
169
- const match = url.match(/\/([^/]+?)(?:\.git)?$/);
170
- return match?.[1];
171
- }
172
- /** Find a repo config by collection name */
173
- function findRepo(config, collection) {
174
- return config.repos?.find(r => r.name === collection);
175
- }
176
48
  //# sourceMappingURL=permissions-api.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"permissions-api.js","sourceRoot":"","sources":["../../../src/server/routes/permissions-api.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"permissions-api.js","sourceRoot":"","sources":["../../../src/server/routes/permissions-api.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,MAAM,CAAC,KAAK,UAAU,4BAA4B,CAChD,OAAwB,EACxB,WAAwB,EACxB,gBAAkC;IAGlC,8EAA8E;IAC9E,OAAO,CAAC,GAAG,CAAC,8BAA8B,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;QAC/D,MAAM,IAAI,GAAI,GAAW,CAAC,IAA4B,CAAC;QACvD,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAClC,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC,CAAC;QACpE,CAAC;QAED,MAAM,KAAK,GAAG,gBAAgB,CAAC,iBAAiB,EAAE,CAAC;QACnD,MAAM,MAAM,GAAG,KAAK,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAChC,UAAU,EAAE,IAAI,CAAC,IAAI;YACrB,KAAK,EAAE,WAAW,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;SACjD,CAAC,CAAC,CAAC;QAEJ,OAAO,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC5B,CAAC,CAAC,CAAC;IAEH,kEAAkE;IAClE,OAAO,CAAC,IAAI,CACV,wBAAwB,EACxB,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;QACnB,MAAM,IAAI,GAAI,GAAW,CAAC,IAA4B,CAAC;QACvD,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAClC,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC,CAAC;QACpE,CAAC;QAED,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;QAC9C,IAAI,CAAC,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;YAC3B,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,oCAAoC,EAAE,CAAC,CAAC;QACjF,CAAC;QAED,WAAW,CAAC,qBAAqB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QACtD,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IAClC,CAAC,CACF,CAAC;IAEF,oEAAoE;IACpE,OAAO,CAAC,IAAI,CACV,yBAAyB,EACzB,KAAK,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;QACnB,MAAM,IAAI,GAAI,GAAW,CAAC,IAA4B,CAAC;QACvD,IAAI,IAAI,IAAI,IAAI,CAAC,IAAI,KAAK,OAAO,EAAE,CAAC;YAClC,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,uBAAuB,EAAE,CAAC,CAAC;QACpE,CAAC;QAED,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,GAAG,GAAG,CAAC,IAAI,IAAI,EAAE,CAAC;QAC9C,IAAI,CAAC,MAAM,IAAI,CAAC,UAAU,EAAE,CAAC;YAC3B,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,oCAAoC,EAAE,CAAC,CAAC;QACjF,CAAC;QAED,WAAW,CAAC,sBAAsB,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;QACvD,OAAO,KAAK,CAAC,IAAI,CAAC,EAAE,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC;IAClC,CAAC,CACF,CAAC;AACJ,CAAC"}
@@ -0,0 +1,7 @@
1
+ /**
2
+ * WebSocket endpoint — authenticates connections and registers them with the hub.
3
+ */
4
+ import type { FastifyInstance } from 'fastify';
5
+ import type { AuthService } from '../../services/auth-service.js';
6
+ import type { WsHub } from '../ws-hub.js';
7
+ export declare function registerWsRoute(fastify: FastifyInstance, wsHub: WsHub, authService: AuthService): Promise<void>;
@@ -0,0 +1,35 @@
1
+ /**
2
+ * WebSocket endpoint — authenticates connections and registers them with the hub.
3
+ */
4
+ export async function registerWsRoute(fastify, wsHub, authService) {
5
+ fastify.get('/ws', { websocket: true }, (socket, req) => {
6
+ // Authenticate via JWT cookie or ?token= query param
7
+ const cookies = req.cookies;
8
+ const jwtFromCookie = cookies?.['__sg_token'];
9
+ const jwtFromQuery = req.query?.token;
10
+ const token = jwtFromCookie || jwtFromQuery;
11
+ if (authService.hasUsers()) {
12
+ if (!token) {
13
+ socket.send(JSON.stringify({ error: 'Unauthorized' }));
14
+ socket.close(4001, 'Unauthorized');
15
+ return;
16
+ }
17
+ const user = authService.verifyToken(token);
18
+ if (!user) {
19
+ socket.send(JSON.stringify({ error: 'Invalid token' }));
20
+ socket.close(4001, 'Invalid token');
21
+ return;
22
+ }
23
+ // Determine accessible collections for this user
24
+ const collections = user.role === 'admin'
25
+ ? null // admin sees all
26
+ : authService.getUserCollections(user.id);
27
+ wsHub.addClient(socket, user, collections);
28
+ }
29
+ else {
30
+ // Open mode — no auth, all collections
31
+ wsHub.addClient(socket, null, null);
32
+ }
33
+ });
34
+ }
35
+ //# sourceMappingURL=ws.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws.js","sourceRoot":"","sources":["../../../src/server/routes/ws.ts"],"names":[],"mappings":"AAAA;;GAEG;AAMH,MAAM,CAAC,KAAK,UAAU,eAAe,CACnC,OAAwB,EACxB,KAAY,EACZ,WAAwB;IAExB,OAAO,CAAC,GAAG,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;QACtD,qDAAqD;QACrD,MAAM,OAAO,GAAG,GAAG,CAAC,OAA6C,CAAC;QAClE,MAAM,aAAa,GAAG,OAAO,EAAE,CAAC,YAAY,CAAC,CAAC;QAC9C,MAAM,YAAY,GAAI,GAAG,CAAC,KAAgC,EAAE,KAAK,CAAC;QAClE,MAAM,KAAK,GAAG,aAAa,IAAI,YAAY,CAAC;QAE5C,IAAI,WAAW,CAAC,QAAQ,EAAE,EAAE,CAAC;YAC3B,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,CAAC,CAAC,CAAC;gBACvD,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC;gBACnC,OAAO;YACT,CAAC;YACD,MAAM,IAAI,GAAG,WAAW,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YAC5C,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,MAAM,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC,CAAC;gBACxD,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,eAAe,CAAC,CAAC;gBACpC,OAAO;YACT,CAAC;YAED,iDAAiD;YACjD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,KAAK,OAAO;gBACvC,CAAC,CAAC,IAAI,CAAE,iBAAiB;gBACzB,CAAC,CAAC,WAAW,CAAC,kBAAkB,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAE5C,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,WAAW,CAAC,CAAC;QAC7C,CAAC;aAAM,CAAC;YACN,uCAAuC;YACvC,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,IAAI,CAAC,CAAC;QACtC,CAAC;IACH,CAAC,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * WebSocket Hub — event bus for real-time change notifications.
3
+ *
4
+ * Tracks authenticated WebSocket connections and broadcasts entity/repo
5
+ * change events scoped to each client's collection access.
6
+ */
7
+ import type { WebSocket } from 'ws';
8
+ import type { AuthUser } from '../services/auth-service.js';
9
+ export interface WsChangeEvent {
10
+ type: 'entity_change';
11
+ action: 'created' | 'updated' | 'deleted';
12
+ repo: string;
13
+ entityType: string;
14
+ entityId: string;
15
+ actor: string;
16
+ source: 'api' | 'git-push';
17
+ timestamp: string;
18
+ }
19
+ export interface WsRepoSyncEvent {
20
+ type: 'repo_sync';
21
+ repo: string;
22
+ source: 'git-push';
23
+ actor: string;
24
+ timestamp: string;
25
+ }
26
+ export type WsEvent = WsChangeEvent | WsRepoSyncEvent;
27
+ export declare class WsHub {
28
+ private clients;
29
+ private heartbeatTimer;
30
+ constructor();
31
+ addClient(ws: WebSocket, user: AuthUser | null, collections: string[] | null): void;
32
+ broadcast(event: WsEvent): void;
33
+ get connectionCount(): number;
34
+ destroy(): void;
35
+ private canReceive;
36
+ }
@@ -0,0 +1,63 @@
1
+ /**
2
+ * WebSocket Hub — event bus for real-time change notifications.
3
+ *
4
+ * Tracks authenticated WebSocket connections and broadcasts entity/repo
5
+ * change events scoped to each client's collection access.
6
+ */
7
+ export class WsHub {
8
+ clients = new Set();
9
+ heartbeatTimer = null;
10
+ constructor() {
11
+ // Ping all clients every 30s, drop unresponsive ones
12
+ this.heartbeatTimer = setInterval(() => {
13
+ for (const client of this.clients) {
14
+ if (client.ws.readyState === 1 /* OPEN */) {
15
+ client.ws.ping();
16
+ }
17
+ else {
18
+ this.clients.delete(client);
19
+ }
20
+ }
21
+ }, 30_000);
22
+ }
23
+ addClient(ws, user, collections) {
24
+ const client = {
25
+ ws,
26
+ user,
27
+ collections: collections ? new Set(collections) : null,
28
+ };
29
+ this.clients.add(client);
30
+ ws.on('close', () => this.clients.delete(client));
31
+ ws.on('error', () => this.clients.delete(client));
32
+ }
33
+ broadcast(event) {
34
+ const payload = JSON.stringify(event);
35
+ for (const client of this.clients) {
36
+ if (client.ws.readyState !== 1 /* OPEN */)
37
+ continue;
38
+ if (!this.canReceive(client, event.repo))
39
+ continue;
40
+ client.ws.send(payload);
41
+ }
42
+ }
43
+ get connectionCount() {
44
+ return this.clients.size;
45
+ }
46
+ destroy() {
47
+ if (this.heartbeatTimer) {
48
+ clearInterval(this.heartbeatTimer);
49
+ this.heartbeatTimer = null;
50
+ }
51
+ for (const client of this.clients) {
52
+ client.ws.close();
53
+ }
54
+ this.clients.clear();
55
+ }
56
+ canReceive(client, repo) {
57
+ // Open mode or null collections = receives everything
58
+ if (!client.collections)
59
+ return true;
60
+ return client.collections.has(repo);
61
+ }
62
+ }
63
+ //# sourceMappingURL=ws-hub.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ws-hub.js","sourceRoot":"","sources":["../../src/server/ws-hub.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAgCH,MAAM,OAAO,KAAK;IACR,OAAO,GAAG,IAAI,GAAG,EAAY,CAAC;IAC9B,cAAc,GAA0C,IAAI,CAAC;IAErE;QACE,qDAAqD;QACrD,IAAI,CAAC,cAAc,GAAG,WAAW,CAAC,GAAG,EAAE;YACrC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;gBAClC,IAAI,MAAM,CAAC,EAAE,CAAC,UAAU,KAAK,CAAC,CAAC,UAAU,EAAE,CAAC;oBAC1C,MAAM,CAAC,EAAE,CAAC,IAAI,EAAE,CAAC;gBACnB,CAAC;qBAAM,CAAC;oBACN,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC;gBAC9B,CAAC;YACH,CAAC;QACH,CAAC,EAAE,MAAM,CAAC,CAAC;IACb,CAAC;IAED,SAAS,CAAC,EAAa,EAAE,IAAqB,EAAE,WAA4B;QAC1E,MAAM,MAAM,GAAa;YACvB,EAAE;YACF,IAAI;YACJ,WAAW,EAAE,WAAW,CAAC,CAAC,CAAC,IAAI,GAAG,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,IAAI;SACvD,CAAC;QACF,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC;QAEzB,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;QAClD,EAAE,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC;IACpD,CAAC;IAED,SAAS,CAAC,KAAc;QACtB,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC;QACtC,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,IAAI,MAAM,CAAC,EAAE,CAAC,UAAU,KAAK,CAAC,CAAC,UAAU;gBAAE,SAAS;YACpD,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC;gBAAE,SAAS;YACnD,MAAM,CAAC,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC;QAC1B,CAAC;IACH,CAAC;IAED,IAAI,eAAe;QACjB,OAAO,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC;IAC3B,CAAC;IAED,OAAO;QACL,IAAI,IAAI,CAAC,cAAc,EAAE,CAAC;YACxB,aAAa,CAAC,IAAI,CAAC,cAAc,CAAC,CAAC;YACnC,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC;QAC7B,CAAC;QACD,KAAK,MAAM,MAAM,IAAI,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC;QACpB,CAAC;QACD,IAAI,CAAC,OAAO,CAAC,KAAK,EAAE,CAAC;IACvB,CAAC;IAEO,UAAU,CAAC,MAAgB,EAAE,IAAY;QAC/C,sDAAsD;QACtD,IAAI,CAAC,MAAM,CAAC,WAAW;YAAE,OAAO,IAAI,CAAC;QACrC,OAAO,MAAM,CAAC,WAAW,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACtC,CAAC;CACF"}
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Authentication service
3
+ *
4
+ * SQLite-backed user store with bcrypt password hashing and JWT sessions.
5
+ * Database lives at .studiograph/auth.db inside the workspace.
6
+ *
7
+ * Auth state is portable via .studiograph/auth-seed.json — a JSON export of
8
+ * all users (with bcrypt hashes) and the JWT secret. The seed file is
9
+ * committed to the config repo so it syncs between local and Railway:
10
+ * local setup → commit → push → Railway redeploy → users restored from seed
11
+ */
12
+ export interface AuthUser {
13
+ id: number;
14
+ email: string;
15
+ displayName: string;
16
+ role: 'admin' | 'member';
17
+ createdAt: string;
18
+ }
19
+ export interface AuthSeed {
20
+ jwtSecret: string;
21
+ users: Array<{
22
+ email: string;
23
+ password_hash: string;
24
+ display_name: string;
25
+ role: string;
26
+ created_at: string;
27
+ }>;
28
+ collection_access?: Array<{
29
+ user_email: string;
30
+ collection_name: string;
31
+ }>;
32
+ }
33
+ export declare class AuthService {
34
+ private db;
35
+ private jwtSecret;
36
+ private sgDir;
37
+ constructor(workspacePath: string);
38
+ createUser(email: string, password: string, displayName: string, role?: 'admin' | 'member'): AuthUser;
39
+ authenticate(email: string, password: string): {
40
+ user: AuthUser;
41
+ token: string;
42
+ } | null;
43
+ verifyToken(token: string): AuthUser | null;
44
+ listUsers(): AuthUser[];
45
+ deleteUser(email: string): boolean;
46
+ updatePassword(email: string, newPassword: string): boolean;
47
+ grantCollectionAccess(userId: number, collectionName: string): void;
48
+ revokeCollectionAccess(userId: number, collectionName: string): void;
49
+ getUserCollections(userId: number): string[];
50
+ getCollectionUsers(collectionName: string): AuthUser[];
51
+ renameCollection(oldName: string, newName: string): void;
52
+ hasUsers(): boolean;
53
+ getUserCount(): number;
54
+ /**
55
+ * Ensure auth.db and auth-secret are in .studiograph/.gitignore.
56
+ * Only the seed JSON file should be committed to the config repo.
57
+ */
58
+ private ensureGitignore;
59
+ /**
60
+ * Export all users and JWT secret to .studiograph/auth-seed.json.
61
+ * If .studiograph/ is a git repo with a remote, auto-commits and pushes
62
+ * the seed file so it syncs between environments.
63
+ */
64
+ exportSeed(): void;
65
+ /**
66
+ * Commit and push auth-seed.json if .studiograph/ is a git repo with a remote.
67
+ * Non-fatal — logs warnings on failure but never throws.
68
+ */
69
+ private pushSeed;
70
+ /**
71
+ * Import users and JWT secret from .studiograph/auth-seed.json.
72
+ * Only runs when the database is empty (fresh boot / volume wipe).
73
+ */
74
+ private importFromSeed;
75
+ close(): void;
76
+ private toAuthUser;
77
+ }