@intx/hub-api 0.1.2
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.
- package/README.md +29 -0
- package/package.json +28 -0
- package/src/app.test.ts +225 -0
- package/src/app.ts +382 -0
- package/src/auth.ts +21 -0
- package/src/context.ts +38 -0
- package/src/format.ts +9 -0
- package/src/git-http/advertise-refs.test.ts +459 -0
- package/src/git-http/advertise-refs.ts +226 -0
- package/src/git-http/pkt-line.test.ts +220 -0
- package/src/git-http/pkt-line.ts +235 -0
- package/src/git-http/receive-pack.test.ts +397 -0
- package/src/git-http/receive-pack.ts +261 -0
- package/src/git-http/side-band-64k.test.ts +181 -0
- package/src/git-http/side-band-64k.ts +134 -0
- package/src/git-http/upload-pack.test.ts +545 -0
- package/src/git-http/upload-pack.ts +396 -0
- package/src/index.ts +23 -0
- package/src/middleware/git-token-auth.test.ts +587 -0
- package/src/middleware/git-token-auth.ts +315 -0
- package/src/middleware/grant.ts +106 -0
- package/src/middleware/session.ts +13 -0
- package/src/middleware/tenant.test.ts +192 -0
- package/src/middleware/tenant.ts +101 -0
- package/src/openapi.ts +66 -0
- package/src/pagination.ts +117 -0
- package/src/routes/agent-data.ts +179 -0
- package/src/routes/agent-state-git.ts +562 -0
- package/src/routes/agents.test.ts +337 -0
- package/src/routes/agents.ts +704 -0
- package/src/routes/approvals.ts +130 -0
- package/src/routes/assets.test.ts +567 -0
- package/src/routes/assets.ts +592 -0
- package/src/routes/credentials.ts +435 -0
- package/src/routes/git-tokens.test.ts +709 -0
- package/src/routes/git-tokens.ts +771 -0
- package/src/routes/grants.ts +509 -0
- package/src/routes/instances.test.ts +1103 -0
- package/src/routes/instances.ts +1797 -0
- package/src/routes/me.ts +405 -0
- package/src/routes/oauth-clients.ts +349 -0
- package/src/routes/observability.ts +146 -0
- package/src/routes/offerings.ts +382 -0
- package/src/routes/principals.ts +515 -0
- package/src/routes/providers.ts +351 -0
- package/src/routes/roles.ts +452 -0
- package/src/routes/sidecars.ts +221 -0
- package/src/routes/tenant-federation.ts +225 -0
- package/src/routes/tenants.ts +369 -0
- package/src/routes/wallets.ts +370 -0
- package/src/session.ts +44 -0
- package/src/timeline-reconstruction.test.ts +786 -0
- package/src/timeline-reconstruction.ts +383 -0
- package/tsconfig.json +4 -0
- package/tsconfig.tsbuildinfo +1 -0
package/src/routes/me.ts
ADDED
|
@@ -0,0 +1,405 @@
|
|
|
1
|
+
import { type SQL, eq, and } from "drizzle-orm";
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
import { describeRoute, resolver } from "hono-openapi";
|
|
4
|
+
|
|
5
|
+
import { agent, agentInstance, principal } from "@intx/db/schema";
|
|
6
|
+
import type { DB } from "@intx/db";
|
|
7
|
+
import {
|
|
8
|
+
UserProfile,
|
|
9
|
+
PrincipalSummary,
|
|
10
|
+
AgentSummary,
|
|
11
|
+
InstanceSummary,
|
|
12
|
+
SessionSummary,
|
|
13
|
+
ApprovalSummary,
|
|
14
|
+
ErrorResponse,
|
|
15
|
+
paginatedSchema,
|
|
16
|
+
} from "@intx/types";
|
|
17
|
+
|
|
18
|
+
import type { AppEnv } from "../context";
|
|
19
|
+
import { ts } from "../format";
|
|
20
|
+
import {
|
|
21
|
+
parsePageParams,
|
|
22
|
+
cursorCondition,
|
|
23
|
+
pageOrder,
|
|
24
|
+
paginatedResponse,
|
|
25
|
+
pageParameters,
|
|
26
|
+
} from "../pagination";
|
|
27
|
+
|
|
28
|
+
export type CreateMeRoutesDeps = {
|
|
29
|
+
db: DB["db"];
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function createMeRoutes({ db }: CreateMeRoutesDeps): Hono<AppEnv> {
|
|
33
|
+
const app = new Hono<AppEnv>();
|
|
34
|
+
|
|
35
|
+
app.get(
|
|
36
|
+
"/",
|
|
37
|
+
describeRoute({
|
|
38
|
+
tags: ["User"],
|
|
39
|
+
summary: "Get current user profile",
|
|
40
|
+
responses: {
|
|
41
|
+
200: {
|
|
42
|
+
description: "User profile",
|
|
43
|
+
content: {
|
|
44
|
+
"application/json": { schema: resolver(UserProfile) },
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
401: {
|
|
48
|
+
description: "Not authenticated",
|
|
49
|
+
content: {
|
|
50
|
+
"application/json": { schema: resolver(ErrorResponse) },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
}),
|
|
55
|
+
(c) => {
|
|
56
|
+
const user = c.get("user");
|
|
57
|
+
if (!user) {
|
|
58
|
+
return c.json(
|
|
59
|
+
{
|
|
60
|
+
error: { code: "unauthorized", message: "Authentication required" },
|
|
61
|
+
},
|
|
62
|
+
401,
|
|
63
|
+
);
|
|
64
|
+
}
|
|
65
|
+
return c.json({
|
|
66
|
+
id: user.id,
|
|
67
|
+
name: user.name,
|
|
68
|
+
email: user.email,
|
|
69
|
+
emailVerified: user.emailVerified,
|
|
70
|
+
image: user.image ?? null,
|
|
71
|
+
createdAt: ts(user.createdAt),
|
|
72
|
+
updatedAt: ts(user.updatedAt),
|
|
73
|
+
});
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
app.get(
|
|
78
|
+
"/principals",
|
|
79
|
+
describeRoute({
|
|
80
|
+
tags: ["User"],
|
|
81
|
+
summary: "List principals across all tenants",
|
|
82
|
+
description:
|
|
83
|
+
"Returns all of the authenticated user's principals across tenants, with tenant name, roles, and status in each.",
|
|
84
|
+
parameters: [...pageParameters],
|
|
85
|
+
responses: {
|
|
86
|
+
200: {
|
|
87
|
+
description: "List of principals across tenants",
|
|
88
|
+
content: {
|
|
89
|
+
"application/json": {
|
|
90
|
+
schema: resolver(paginatedSchema(PrincipalSummary)),
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
401: {
|
|
95
|
+
description: "Not authenticated",
|
|
96
|
+
content: {
|
|
97
|
+
"application/json": { schema: resolver(ErrorResponse) },
|
|
98
|
+
},
|
|
99
|
+
},
|
|
100
|
+
},
|
|
101
|
+
}),
|
|
102
|
+
async (c) => {
|
|
103
|
+
const user = c.get("user");
|
|
104
|
+
if (!user) {
|
|
105
|
+
return c.json(
|
|
106
|
+
{
|
|
107
|
+
error: { code: "unauthorized", message: "Authentication required" },
|
|
108
|
+
},
|
|
109
|
+
401,
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
const { limit, cursor } = parsePageParams({
|
|
113
|
+
cursor: c.req.query("cursor"),
|
|
114
|
+
limit: c.req.query("limit"),
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const conditions = [
|
|
118
|
+
eq(principal.kind, "user"),
|
|
119
|
+
eq(principal.refId, user.id),
|
|
120
|
+
];
|
|
121
|
+
if (cursor) {
|
|
122
|
+
conditions.push(
|
|
123
|
+
cursorCondition(principal.createdAt, principal.id, cursor),
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const rows = await db.query.principal.findMany({
|
|
128
|
+
where: and(...conditions),
|
|
129
|
+
orderBy: pageOrder(principal.createdAt, principal.id),
|
|
130
|
+
limit,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const tenantIds = rows.map((p) => p.tenantId);
|
|
134
|
+
const tenants =
|
|
135
|
+
tenantIds.length > 0
|
|
136
|
+
? await db.query.tenant.findMany({
|
|
137
|
+
where: (t, { inArray }) => inArray(t.id, tenantIds),
|
|
138
|
+
})
|
|
139
|
+
: [];
|
|
140
|
+
const tenantMap = new Map(tenants.map((t) => [t.id, t]));
|
|
141
|
+
|
|
142
|
+
const principalIds = rows.map((p) => p.id);
|
|
143
|
+
const assignments =
|
|
144
|
+
principalIds.length > 0
|
|
145
|
+
? await db.query.principalRole.findMany({
|
|
146
|
+
where: (pr, { inArray }) => inArray(pr.principalId, principalIds),
|
|
147
|
+
})
|
|
148
|
+
: [];
|
|
149
|
+
|
|
150
|
+
const roleIds = [...new Set(assignments.map((a) => a.roleId))];
|
|
151
|
+
const roles =
|
|
152
|
+
roleIds.length > 0
|
|
153
|
+
? await db.query.role.findMany({
|
|
154
|
+
where: (r, { inArray }) => inArray(r.id, roleIds),
|
|
155
|
+
})
|
|
156
|
+
: [];
|
|
157
|
+
const roleMap = new Map(roles.map((r) => [r.id, r]));
|
|
158
|
+
|
|
159
|
+
const assignmentsByPrincipal = new Map<
|
|
160
|
+
string,
|
|
161
|
+
{ id: string; name: string }[]
|
|
162
|
+
>();
|
|
163
|
+
for (const a of assignments) {
|
|
164
|
+
const r = roleMap.get(a.roleId);
|
|
165
|
+
if (!r) continue;
|
|
166
|
+
const list = assignmentsByPrincipal.get(a.principalId) ?? [];
|
|
167
|
+
list.push({ id: r.id, name: r.name });
|
|
168
|
+
assignmentsByPrincipal.set(a.principalId, list);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const items = rows.map((p) => {
|
|
172
|
+
const t = tenantMap.get(p.tenantId);
|
|
173
|
+
return {
|
|
174
|
+
principalId: p.id,
|
|
175
|
+
tenantId: p.tenantId,
|
|
176
|
+
tenantName: t?.name ?? "Unknown",
|
|
177
|
+
tenantSlug: t?.slug ?? "unknown",
|
|
178
|
+
kind: p.kind as "user" | "agent",
|
|
179
|
+
status: p.status as
|
|
180
|
+
| "active"
|
|
181
|
+
| "suspended"
|
|
182
|
+
| "invited"
|
|
183
|
+
| "deactivated",
|
|
184
|
+
roles: assignmentsByPrincipal.get(p.id) ?? [],
|
|
185
|
+
};
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return c.json(paginatedResponse(items, rows, limit));
|
|
189
|
+
},
|
|
190
|
+
);
|
|
191
|
+
|
|
192
|
+
app.get(
|
|
193
|
+
"/agents",
|
|
194
|
+
describeRoute({
|
|
195
|
+
tags: ["User"],
|
|
196
|
+
summary: "List agents across all tenants",
|
|
197
|
+
description:
|
|
198
|
+
"Aggregates agents from all tenants the user belongs to. Each result is tagged with tenantId.",
|
|
199
|
+
parameters: [...pageParameters],
|
|
200
|
+
responses: {
|
|
201
|
+
200: {
|
|
202
|
+
description: "Agents across tenants",
|
|
203
|
+
content: {
|
|
204
|
+
"application/json": {
|
|
205
|
+
schema: resolver(paginatedSchema(AgentSummary)),
|
|
206
|
+
},
|
|
207
|
+
},
|
|
208
|
+
},
|
|
209
|
+
},
|
|
210
|
+
}),
|
|
211
|
+
async (c) => {
|
|
212
|
+
const user = c.get("user");
|
|
213
|
+
if (!user) {
|
|
214
|
+
return c.json(
|
|
215
|
+
{
|
|
216
|
+
error: { code: "unauthorized", message: "Authentication required" },
|
|
217
|
+
},
|
|
218
|
+
401,
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
const { limit, cursor } = parsePageParams({
|
|
222
|
+
cursor: c.req.query("cursor"),
|
|
223
|
+
limit: c.req.query("limit"),
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const principals = await db.query.principal.findMany({
|
|
227
|
+
where: and(eq(principal.kind, "user"), eq(principal.refId, user.id)),
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
const tenantIds = principals.map((p) => p.tenantId);
|
|
231
|
+
if (tenantIds.length === 0) {
|
|
232
|
+
return c.json({ data: [], nextCursor: null });
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const tenants = await db.query.tenant.findMany({
|
|
236
|
+
where: (t, { inArray }) => inArray(t.id, tenantIds),
|
|
237
|
+
});
|
|
238
|
+
const tenantMap = new Map(tenants.map((t) => [t.id, t]));
|
|
239
|
+
|
|
240
|
+
const conditions: SQL[] = [];
|
|
241
|
+
if (cursor) {
|
|
242
|
+
conditions.push(cursorCondition(agent.createdAt, agent.id, cursor));
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const rows = await db.query.agent.findMany({
|
|
246
|
+
where: (a, { inArray }) =>
|
|
247
|
+
and(inArray(a.tenantId, tenantIds), ...conditions),
|
|
248
|
+
orderBy: pageOrder(agent.createdAt, agent.id),
|
|
249
|
+
limit,
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
const items = rows.map((a) => ({
|
|
253
|
+
id: a.id,
|
|
254
|
+
tenantId: a.tenantId,
|
|
255
|
+
tenantName: tenantMap.get(a.tenantId)?.name ?? "Unknown",
|
|
256
|
+
name: a.name,
|
|
257
|
+
description: a.description ?? null,
|
|
258
|
+
status: a.status as "deployed" | "stopped" | "updating" | "error",
|
|
259
|
+
}));
|
|
260
|
+
|
|
261
|
+
return c.json(paginatedResponse(items, rows, limit));
|
|
262
|
+
},
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
app.get(
|
|
266
|
+
"/instances",
|
|
267
|
+
describeRoute({
|
|
268
|
+
tags: ["User"],
|
|
269
|
+
summary: "List running agent instances across all tenants",
|
|
270
|
+
description:
|
|
271
|
+
"Aggregates running agent instances from all tenants the user belongs to. Each result is tagged with tenantId.",
|
|
272
|
+
parameters: [...pageParameters],
|
|
273
|
+
responses: {
|
|
274
|
+
200: {
|
|
275
|
+
description: "Instances across tenants",
|
|
276
|
+
content: {
|
|
277
|
+
"application/json": {
|
|
278
|
+
schema: resolver(paginatedSchema(InstanceSummary)),
|
|
279
|
+
},
|
|
280
|
+
},
|
|
281
|
+
},
|
|
282
|
+
},
|
|
283
|
+
}),
|
|
284
|
+
async (c) => {
|
|
285
|
+
const user = c.get("user");
|
|
286
|
+
if (!user) {
|
|
287
|
+
return c.json(
|
|
288
|
+
{
|
|
289
|
+
error: { code: "unauthorized", message: "Authentication required" },
|
|
290
|
+
},
|
|
291
|
+
401,
|
|
292
|
+
);
|
|
293
|
+
}
|
|
294
|
+
const { limit, cursor } = parsePageParams({
|
|
295
|
+
cursor: c.req.query("cursor"),
|
|
296
|
+
limit: c.req.query("limit"),
|
|
297
|
+
});
|
|
298
|
+
|
|
299
|
+
const principals = await db.query.principal.findMany({
|
|
300
|
+
where: and(eq(principal.kind, "user"), eq(principal.refId, user.id)),
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
const tenantIds = principals.map((p) => p.tenantId);
|
|
304
|
+
if (tenantIds.length === 0) {
|
|
305
|
+
return c.json({ data: [], nextCursor: null });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const tenants = await db.query.tenant.findMany({
|
|
309
|
+
where: (t, { inArray }) => inArray(t.id, tenantIds),
|
|
310
|
+
});
|
|
311
|
+
const tenantMap = new Map(tenants.map((t) => [t.id, t]));
|
|
312
|
+
|
|
313
|
+
const conditions: SQL[] = [eq(agentInstance.status, "running")];
|
|
314
|
+
if (cursor) {
|
|
315
|
+
conditions.push(
|
|
316
|
+
cursorCondition(agentInstance.createdAt, agentInstance.id, cursor),
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const rows = await db.query.agentInstance.findMany({
|
|
321
|
+
where: (ai, { inArray }) =>
|
|
322
|
+
and(inArray(ai.tenantId, tenantIds), ...conditions),
|
|
323
|
+
orderBy: pageOrder(agentInstance.createdAt, agentInstance.id),
|
|
324
|
+
limit,
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
const agentIds = [...new Set(rows.map((r) => r.agentId))];
|
|
328
|
+
const agents =
|
|
329
|
+
agentIds.length > 0
|
|
330
|
+
? await db.query.agent.findMany({
|
|
331
|
+
where: (a, { inArray }) => inArray(a.id, agentIds),
|
|
332
|
+
})
|
|
333
|
+
: [];
|
|
334
|
+
const agentMap = new Map(agents.map((a) => [a.id, a]));
|
|
335
|
+
|
|
336
|
+
const items = rows.map((r) => ({
|
|
337
|
+
id: r.id,
|
|
338
|
+
tenantId: r.tenantId,
|
|
339
|
+
tenantName: tenantMap.get(r.tenantId)?.name ?? "Unknown",
|
|
340
|
+
agentId: r.agentId,
|
|
341
|
+
agentName: agentMap.get(r.agentId)?.name ?? "Unknown",
|
|
342
|
+
address: r.address,
|
|
343
|
+
status: r.status as
|
|
344
|
+
| "deployed"
|
|
345
|
+
| "running"
|
|
346
|
+
| "updating"
|
|
347
|
+
| "error"
|
|
348
|
+
| "stopped",
|
|
349
|
+
createdAt: ts(r.createdAt),
|
|
350
|
+
}));
|
|
351
|
+
|
|
352
|
+
return c.json(paginatedResponse(items, rows, limit));
|
|
353
|
+
},
|
|
354
|
+
);
|
|
355
|
+
|
|
356
|
+
app.get(
|
|
357
|
+
"/sessions",
|
|
358
|
+
describeRoute({
|
|
359
|
+
tags: ["User"],
|
|
360
|
+
summary: "List sessions across all tenants",
|
|
361
|
+
description:
|
|
362
|
+
"Aggregates active sessions from all tenants the user belongs to. Each result is tagged with tenantId.",
|
|
363
|
+
responses: {
|
|
364
|
+
200: {
|
|
365
|
+
description: "Sessions across tenants",
|
|
366
|
+
content: {
|
|
367
|
+
"application/json": {
|
|
368
|
+
schema: resolver(SessionSummary.array()),
|
|
369
|
+
},
|
|
370
|
+
},
|
|
371
|
+
},
|
|
372
|
+
},
|
|
373
|
+
}),
|
|
374
|
+
(_c) => {
|
|
375
|
+
// Sessions deferred to later phase
|
|
376
|
+
return _c.json([]);
|
|
377
|
+
},
|
|
378
|
+
);
|
|
379
|
+
|
|
380
|
+
app.get(
|
|
381
|
+
"/approvals",
|
|
382
|
+
describeRoute({
|
|
383
|
+
tags: ["User"],
|
|
384
|
+
summary: "List pending approvals across all tenants",
|
|
385
|
+
description:
|
|
386
|
+
"Aggregates pending approval requests from all tenants the user belongs to. Each result is tagged with tenantId.",
|
|
387
|
+
responses: {
|
|
388
|
+
200: {
|
|
389
|
+
description: "Approvals across tenants",
|
|
390
|
+
content: {
|
|
391
|
+
"application/json": {
|
|
392
|
+
schema: resolver(ApprovalSummary.array()),
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
},
|
|
396
|
+
},
|
|
397
|
+
}),
|
|
398
|
+
(_c) => {
|
|
399
|
+
// Approvals deferred to later phase
|
|
400
|
+
return _c.json([]);
|
|
401
|
+
},
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
return app;
|
|
405
|
+
}
|