@jamesaphoenix/tx-api-server 0.1.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.
- package/dist/__tests__/api.test.d.ts +7 -0
- package/dist/__tests__/api.test.d.ts.map +1 -0
- package/dist/__tests__/api.test.js +184 -0
- package/dist/__tests__/api.test.js.map +1 -0
- package/dist/__tests__/auth.test.d.ts +7 -0
- package/dist/__tests__/auth.test.d.ts.map +1 -0
- package/dist/__tests__/auth.test.js +174 -0
- package/dist/__tests__/auth.test.js.map +1 -0
- package/dist/__tests__/body-limit.test.d.ts +7 -0
- package/dist/__tests__/body-limit.test.d.ts.map +1 -0
- package/dist/__tests__/body-limit.test.js +60 -0
- package/dist/__tests__/body-limit.test.js.map +1 -0
- package/dist/__tests__/health.test.d.ts.map +1 -0
- package/dist/__tests__/health.test.js.map +1 -0
- package/dist/__tests__/log-reader.test.d.ts +8 -0
- package/dist/__tests__/log-reader.test.d.ts.map +1 -0
- package/dist/__tests__/log-reader.test.js +55 -0
- package/dist/__tests__/log-reader.test.js.map +1 -0
- package/dist/__tests__/rate-limit.test.d.ts.map +1 -0
- package/dist/__tests__/rate-limit.test.js.map +1 -0
- package/dist/__tests__/server-lib.test.d.ts +10 -0
- package/dist/__tests__/server-lib.test.d.ts.map +1 -0
- package/dist/__tests__/server-lib.test.js +145 -0
- package/dist/__tests__/server-lib.test.js.map +1 -0
- package/dist/api.d.ts +1616 -0
- package/dist/api.d.ts.map +1 -0
- package/dist/api.js +566 -0
- package/dist/api.js.map +1 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +19 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/auth.d.ts +31 -0
- package/dist/middleware/auth.d.ts.map +1 -0
- package/dist/middleware/auth.js +65 -0
- package/dist/middleware/auth.js.map +1 -0
- package/dist/middleware/body-limit.d.ts +33 -0
- package/dist/middleware/body-limit.d.ts.map +1 -0
- package/dist/middleware/body-limit.js +50 -0
- package/dist/middleware/body-limit.js.map +1 -0
- package/dist/middleware/cors.d.ts +15 -0
- package/dist/middleware/cors.d.ts.map +1 -0
- package/dist/middleware/cors.js +31 -0
- package/dist/middleware/cors.js.map +1 -0
- package/dist/middleware/error.d.ts +26 -0
- package/dist/middleware/error.d.ts.map +1 -0
- package/dist/middleware/error.js +84 -0
- package/dist/middleware/error.js.map +1 -0
- package/dist/middleware/rate-limit.d.ts +59 -0
- package/dist/middleware/rate-limit.d.ts.map +1 -0
- package/dist/middleware/rate-limit.js +296 -0
- package/dist/middleware/rate-limit.js.map +1 -0
- package/dist/routes/attempts.d.ts +9 -0
- package/dist/routes/attempts.d.ts.map +1 -0
- package/dist/routes/attempts.js +26 -0
- package/dist/routes/attempts.js.map +1 -0
- package/dist/routes/claims.d.ts +9 -0
- package/dist/routes/claims.d.ts.map +1 -0
- package/dist/routes/claims.js +42 -0
- package/dist/routes/claims.js.map +1 -0
- package/dist/routes/docs.d.ts +9 -0
- package/dist/routes/docs.d.ts.map +1 -0
- package/dist/routes/docs.js +80 -0
- package/dist/routes/docs.js.map +1 -0
- package/dist/routes/health.d.ts +8 -0
- package/dist/routes/health.d.ts.map +1 -0
- package/dist/routes/health.js +101 -0
- package/dist/routes/health.js.map +1 -0
- package/dist/routes/invariants.d.ts +9 -0
- package/dist/routes/invariants.d.ts.map +1 -0
- package/dist/routes/invariants.js +64 -0
- package/dist/routes/invariants.js.map +1 -0
- package/dist/routes/learnings.d.ts +8 -0
- package/dist/routes/learnings.d.ts.map +1 -0
- package/dist/routes/learnings.js +93 -0
- package/dist/routes/learnings.js.map +1 -0
- package/dist/routes/runs.d.ts +8 -0
- package/dist/routes/runs.d.ts.map +1 -0
- package/dist/routes/runs.js +195 -0
- package/dist/routes/runs.js.map +1 -0
- package/dist/routes/sync.d.ts +8 -0
- package/dist/routes/sync.d.ts.map +1 -0
- package/dist/routes/sync.js +67 -0
- package/dist/routes/sync.js.map +1 -0
- package/dist/routes/tasks.d.ts +9 -0
- package/dist/routes/tasks.d.ts.map +1 -0
- package/dist/routes/tasks.js +167 -0
- package/dist/routes/tasks.js.map +1 -0
- package/dist/runtime.d.ts +10 -0
- package/dist/runtime.d.ts.map +1 -0
- package/dist/runtime.js +10 -0
- package/dist/runtime.js.map +1 -0
- package/dist/server-lib.d.ts +26 -0
- package/dist/server-lib.d.ts.map +1 -0
- package/dist/server-lib.js +123 -0
- package/dist/server-lib.js.map +1 -0
- package/dist/server.d.ts +14 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +15 -0
- package/dist/server.js.map +1 -0
- package/dist/utils/log-reader.d.ts +26 -0
- package/dist/utils/log-reader.d.ts.map +1 -0
- package/dist/utils/log-reader.js +52 -0
- package/dist/utils/log-reader.js.map +1 -0
- package/dist/utils/transcript-parser.d.ts +40 -0
- package/dist/utils/transcript-parser.d.ts.map +1 -0
- package/dist/utils/transcript-parser.js +216 -0
- package/dist/utils/transcript-parser.js.map +1 -0
- package/package.json +75 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Task Route Handlers
|
|
3
|
+
*
|
|
4
|
+
* Implements task endpoint handlers using Effect HttpApiBuilder.
|
|
5
|
+
* All responses return TaskWithDeps per Doctrine Rule 1.
|
|
6
|
+
*/
|
|
7
|
+
import { HttpApiBuilder } from "@effect/platform";
|
|
8
|
+
import { Effect } from "effect";
|
|
9
|
+
import { isValidTaskStatus, TASK_STATUSES, serializeTask } from "@jamesaphoenix/tx-types";
|
|
10
|
+
import { TaskService, ReadyService, DependencyService, HierarchyService } from "@jamesaphoenix/tx-core";
|
|
11
|
+
import { TxApi, BadRequest, mapCoreError } from "../api.js";
|
|
12
|
+
const parseCursor = (cursor) => {
|
|
13
|
+
const colonIndex = cursor.lastIndexOf(":");
|
|
14
|
+
if (colonIndex === -1)
|
|
15
|
+
return null;
|
|
16
|
+
const score = parseInt(cursor.slice(0, colonIndex), 10);
|
|
17
|
+
const id = cursor.slice(colonIndex + 1);
|
|
18
|
+
if (isNaN(score))
|
|
19
|
+
return null;
|
|
20
|
+
return { score, id };
|
|
21
|
+
};
|
|
22
|
+
const buildCursor = (task) => {
|
|
23
|
+
return `${task.score}:${task.id}`;
|
|
24
|
+
};
|
|
25
|
+
// -----------------------------------------------------------------------------
|
|
26
|
+
// Handler Layer
|
|
27
|
+
// -----------------------------------------------------------------------------
|
|
28
|
+
export const TasksLive = HttpApiBuilder.group(TxApi, "tasks", (handlers) => handlers
|
|
29
|
+
.handle("listTasks", ({ urlParams }) => Effect.gen(function* () {
|
|
30
|
+
const taskService = yield* TaskService;
|
|
31
|
+
const limit = urlParams.limit ?? 20;
|
|
32
|
+
// Validate status filter
|
|
33
|
+
let statusFilter;
|
|
34
|
+
if (urlParams.status) {
|
|
35
|
+
const statuses = urlParams.status.split(",").filter(Boolean);
|
|
36
|
+
const invalidStatuses = statuses.filter(s => !isValidTaskStatus(s));
|
|
37
|
+
if (invalidStatuses.length > 0) {
|
|
38
|
+
return yield* Effect.fail(new BadRequest({ message: `Invalid status values: ${invalidStatuses.join(", ")}. Valid: ${TASK_STATUSES.join(", ")}` }));
|
|
39
|
+
}
|
|
40
|
+
statusFilter = statuses;
|
|
41
|
+
}
|
|
42
|
+
// Parse cursor for keyset pagination
|
|
43
|
+
let cursorObj;
|
|
44
|
+
if (urlParams.cursor) {
|
|
45
|
+
const parsed = parseCursor(urlParams.cursor);
|
|
46
|
+
if (parsed) {
|
|
47
|
+
cursorObj = { score: parsed.score, id: parsed.id };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const filter = {
|
|
51
|
+
status: statusFilter,
|
|
52
|
+
search: urlParams.search,
|
|
53
|
+
cursor: cursorObj,
|
|
54
|
+
limit: limit + 1,
|
|
55
|
+
};
|
|
56
|
+
const total = yield* taskService.count({
|
|
57
|
+
status: statusFilter,
|
|
58
|
+
search: urlParams.search,
|
|
59
|
+
});
|
|
60
|
+
const tasks = yield* taskService.listWithDeps(filter);
|
|
61
|
+
const hasMore = tasks.length > limit;
|
|
62
|
+
const resultTasks = hasMore ? tasks.slice(0, limit) : tasks;
|
|
63
|
+
return {
|
|
64
|
+
tasks: resultTasks.map(serializeTask),
|
|
65
|
+
nextCursor: hasMore && resultTasks.length > 0
|
|
66
|
+
? buildCursor(resultTasks[resultTasks.length - 1])
|
|
67
|
+
: null,
|
|
68
|
+
hasMore,
|
|
69
|
+
total,
|
|
70
|
+
};
|
|
71
|
+
}).pipe(Effect.mapError(mapCoreError)))
|
|
72
|
+
.handle("readyTasks", ({ urlParams }) => Effect.gen(function* () {
|
|
73
|
+
const readyService = yield* ReadyService;
|
|
74
|
+
const limit = urlParams.limit ?? 100;
|
|
75
|
+
const tasks = yield* readyService.getReady(limit);
|
|
76
|
+
return { tasks: tasks.map(serializeTask) };
|
|
77
|
+
}).pipe(Effect.mapError(mapCoreError)))
|
|
78
|
+
.handle("getTask", ({ path }) => Effect.gen(function* () {
|
|
79
|
+
const taskService = yield* TaskService;
|
|
80
|
+
const task = yield* taskService.getWithDeps(path.id);
|
|
81
|
+
const blockedByTasks = yield* taskService.getWithDepsBatch(task.blockedBy);
|
|
82
|
+
const blocksTasks = yield* taskService.getWithDepsBatch(task.blocks);
|
|
83
|
+
const childTasks = yield* taskService.getWithDepsBatch(task.children);
|
|
84
|
+
return {
|
|
85
|
+
task: serializeTask(task),
|
|
86
|
+
blockedByTasks: blockedByTasks.map(serializeTask),
|
|
87
|
+
blocksTasks: blocksTasks.map(serializeTask),
|
|
88
|
+
childTasks: childTasks.map(serializeTask),
|
|
89
|
+
};
|
|
90
|
+
}).pipe(Effect.mapError(mapCoreError)))
|
|
91
|
+
.handle("createTask", ({ payload }) => Effect.gen(function* () {
|
|
92
|
+
const taskService = yield* TaskService;
|
|
93
|
+
const created = yield* taskService.create({
|
|
94
|
+
title: payload.title,
|
|
95
|
+
description: payload.description,
|
|
96
|
+
parentId: payload.parentId,
|
|
97
|
+
score: payload.score,
|
|
98
|
+
metadata: payload.metadata,
|
|
99
|
+
});
|
|
100
|
+
const task = yield* taskService.getWithDeps(created.id);
|
|
101
|
+
return serializeTask(task);
|
|
102
|
+
}).pipe(Effect.mapError(mapCoreError)))
|
|
103
|
+
.handle("updateTask", ({ path, payload }) => Effect.gen(function* () {
|
|
104
|
+
const taskService = yield* TaskService;
|
|
105
|
+
yield* taskService.update(path.id, {
|
|
106
|
+
title: payload.title,
|
|
107
|
+
description: payload.description,
|
|
108
|
+
status: payload.status,
|
|
109
|
+
parentId: payload.parentId,
|
|
110
|
+
score: payload.score,
|
|
111
|
+
metadata: payload.metadata,
|
|
112
|
+
});
|
|
113
|
+
const task = yield* taskService.getWithDeps(path.id);
|
|
114
|
+
return serializeTask(task);
|
|
115
|
+
}).pipe(Effect.mapError(mapCoreError)))
|
|
116
|
+
.handle("completeTask", ({ path }) => Effect.gen(function* () {
|
|
117
|
+
const taskService = yield* TaskService;
|
|
118
|
+
const readyService = yield* ReadyService;
|
|
119
|
+
const blocking = yield* readyService.getBlocking(path.id);
|
|
120
|
+
yield* taskService.update(path.id, { status: "done" });
|
|
121
|
+
const completedTask = yield* taskService.getWithDeps(path.id);
|
|
122
|
+
const candidateIds = blocking
|
|
123
|
+
.filter(t => ["backlog", "ready", "planning"].includes(t.status))
|
|
124
|
+
.map(t => t.id);
|
|
125
|
+
const candidatesWithDeps = yield* taskService.getWithDepsBatch(candidateIds);
|
|
126
|
+
const nowReady = candidatesWithDeps.filter(t => t.isReady);
|
|
127
|
+
return {
|
|
128
|
+
task: serializeTask(completedTask),
|
|
129
|
+
nowReady: nowReady.map(serializeTask),
|
|
130
|
+
};
|
|
131
|
+
}).pipe(Effect.mapError(mapCoreError)))
|
|
132
|
+
.handle("deleteTask", ({ path, urlParams }) => Effect.gen(function* () {
|
|
133
|
+
const taskService = yield* TaskService;
|
|
134
|
+
const cascade = urlParams.cascade === "true";
|
|
135
|
+
yield* taskService.remove(path.id, { cascade });
|
|
136
|
+
return { success: true, id: path.id };
|
|
137
|
+
}).pipe(Effect.mapError(mapCoreError)))
|
|
138
|
+
.handle("blockTask", ({ path, payload }) => Effect.gen(function* () {
|
|
139
|
+
const depService = yield* DependencyService;
|
|
140
|
+
const taskService = yield* TaskService;
|
|
141
|
+
yield* depService.addBlocker(path.id, payload.blockerId);
|
|
142
|
+
const task = yield* taskService.getWithDeps(path.id);
|
|
143
|
+
return serializeTask(task);
|
|
144
|
+
}).pipe(Effect.mapError(mapCoreError)))
|
|
145
|
+
.handle("unblockTask", ({ path }) => Effect.gen(function* () {
|
|
146
|
+
const depService = yield* DependencyService;
|
|
147
|
+
const taskService = yield* TaskService;
|
|
148
|
+
yield* depService.removeBlocker(path.id, path.blockerId);
|
|
149
|
+
const task = yield* taskService.getWithDeps(path.id);
|
|
150
|
+
return serializeTask(task);
|
|
151
|
+
}).pipe(Effect.mapError(mapCoreError)))
|
|
152
|
+
.handle("getTaskTree", ({ path }) => Effect.gen(function* () {
|
|
153
|
+
const hierarchyService = yield* HierarchyService;
|
|
154
|
+
const taskService = yield* TaskService;
|
|
155
|
+
const tree = yield* hierarchyService.getTree(path.id);
|
|
156
|
+
const flattenTree = (node) => {
|
|
157
|
+
const ids = [node.task.id];
|
|
158
|
+
for (const child of node.children) {
|
|
159
|
+
ids.push(...flattenTree(child));
|
|
160
|
+
}
|
|
161
|
+
return ids;
|
|
162
|
+
};
|
|
163
|
+
const allIds = flattenTree(tree);
|
|
164
|
+
const tasks = yield* taskService.getWithDepsBatch(allIds);
|
|
165
|
+
return { tasks: tasks.map(serializeTask) };
|
|
166
|
+
}).pipe(Effect.mapError(mapCoreError))));
|
|
167
|
+
//# sourceMappingURL=tasks.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tasks.js","sourceRoot":"","sources":["../../src/routes/tasks.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Server Runtime
|
|
3
|
+
*
|
|
4
|
+
* Re-exports server layer factory for programmatic access.
|
|
5
|
+
* With the Effect HttpApi architecture, the server lifecycle is managed
|
|
6
|
+
* by Effect's runtime — no ManagedRuntime bridge needed.
|
|
7
|
+
*/
|
|
8
|
+
export { makeServerLive } from "./server-lib.js";
|
|
9
|
+
export { TxApi } from "./api.js";
|
|
10
|
+
//# sourceMappingURL=runtime.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.d.ts","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA"}
|
package/dist/runtime.js
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API Server Runtime
|
|
3
|
+
*
|
|
4
|
+
* Re-exports server layer factory for programmatic access.
|
|
5
|
+
* With the Effect HttpApi architecture, the server lifecycle is managed
|
|
6
|
+
* by Effect's runtime — no ManagedRuntime bridge needed.
|
|
7
|
+
*/
|
|
8
|
+
export { makeServerLive } from "./server-lib.js";
|
|
9
|
+
export { TxApi } from "./api.js";
|
|
10
|
+
//# sourceMappingURL=runtime.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"runtime.js","sourceRoot":"","sources":["../src/runtime.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAChD,OAAO,EAAE,KAAK,EAAE,MAAM,UAAU,CAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TX API Server Library
|
|
3
|
+
*
|
|
4
|
+
* REST/HTTP API server using Effect HttpApi.
|
|
5
|
+
* Provides HTTP interface for task management, learnings, runs, and sync.
|
|
6
|
+
*
|
|
7
|
+
* This module provides the library API. For CLI usage, see server.ts.
|
|
8
|
+
*/
|
|
9
|
+
import { Layer } from "effect";
|
|
10
|
+
/**
|
|
11
|
+
* Create the full server Layer with all dependencies resolved.
|
|
12
|
+
*
|
|
13
|
+
* The returned Layer, when launched, starts the HTTP server and keeps
|
|
14
|
+
* it alive until the process receives a termination signal.
|
|
15
|
+
*/
|
|
16
|
+
export declare const makeServerLive: (options: {
|
|
17
|
+
port?: number;
|
|
18
|
+
dbPath?: string;
|
|
19
|
+
hostname?: string;
|
|
20
|
+
}) => Layer.Layer<never, import("@jamesaphoenix/tx-core").DatabaseError | import("@jamesaphoenix/tx-core").LlmUnavailableError | import("@jamesaphoenix/tx-core").AgentError | import("@effect/platform/HttpServerError").ServeError, never>;
|
|
21
|
+
/**
|
|
22
|
+
* Parse command line arguments and launch the server.
|
|
23
|
+
* Exported for use by the CLI entry point.
|
|
24
|
+
*/
|
|
25
|
+
export declare const main: () => void;
|
|
26
|
+
//# sourceMappingURL=server-lib.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-lib.d.ts","sourceRoot":"","sources":["../src/server-lib.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,OAAO,EAAE,KAAK,EAAE,MAAM,QAAQ,CAAA;AAwC9B;;;;;GAKG;AACH,eAAO,MAAM,cAAc,GAAI,SAAS;IACtC,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,CAAC,EAAE,MAAM,CAAA;CAClB,2OAeA,CAAA;AAiCD;;;GAGG;AACH,eAAO,MAAM,IAAI,QAAO,IA0CvB,CAAA"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* TX API Server Library
|
|
3
|
+
*
|
|
4
|
+
* REST/HTTP API server using Effect HttpApi.
|
|
5
|
+
* Provides HTTP interface for task management, learnings, runs, and sync.
|
|
6
|
+
*
|
|
7
|
+
* This module provides the library API. For CLI usage, see server.ts.
|
|
8
|
+
*/
|
|
9
|
+
import { HttpApiBuilder } from "@effect/platform";
|
|
10
|
+
import { NodeHttpServer, NodeRuntime } from "@effect/platform-node";
|
|
11
|
+
import { Layer } from "effect";
|
|
12
|
+
import { createServer } from "node:http";
|
|
13
|
+
import { makeAppLayer } from "@jamesaphoenix/tx-core";
|
|
14
|
+
import { TasksLive } from "./routes/tasks.js";
|
|
15
|
+
import { HealthLive } from "./routes/health.js";
|
|
16
|
+
import { LearningsLive } from "./routes/learnings.js";
|
|
17
|
+
import { RunsLive } from "./routes/runs.js";
|
|
18
|
+
import { SyncLive } from "./routes/sync.js";
|
|
19
|
+
import { DocsLive } from "./routes/docs.js";
|
|
20
|
+
import { ClaimsLive } from "./routes/claims.js";
|
|
21
|
+
import { AttemptsLive } from "./routes/attempts.js";
|
|
22
|
+
import { InvariantsLive } from "./routes/invariants.js";
|
|
23
|
+
import { TxApi } from "./api.js";
|
|
24
|
+
import { authMiddleware, isAuthEnabled } from "./middleware/auth.js";
|
|
25
|
+
import { bodyLimitMiddleware } from "./middleware/body-limit.js";
|
|
26
|
+
import { getCorsConfig } from "./middleware/cors.js";
|
|
27
|
+
// -----------------------------------------------------------------------------
|
|
28
|
+
// API Implementation Layer
|
|
29
|
+
// -----------------------------------------------------------------------------
|
|
30
|
+
/**
|
|
31
|
+
* Combines all route handler groups into the full API implementation.
|
|
32
|
+
*/
|
|
33
|
+
const ApiLive = HttpApiBuilder.api(TxApi).pipe(Layer.provide(TasksLive), Layer.provide(HealthLive), Layer.provide(LearningsLive), Layer.provide(RunsLive), Layer.provide(SyncLive), Layer.provide(DocsLive), Layer.provide(ClaimsLive), Layer.provide(AttemptsLive), Layer.provide(InvariantsLive));
|
|
34
|
+
// -----------------------------------------------------------------------------
|
|
35
|
+
// Server Layer Factory
|
|
36
|
+
// -----------------------------------------------------------------------------
|
|
37
|
+
/**
|
|
38
|
+
* Create the full server Layer with all dependencies resolved.
|
|
39
|
+
*
|
|
40
|
+
* The returned Layer, when launched, starts the HTTP server and keeps
|
|
41
|
+
* it alive until the process receives a termination signal.
|
|
42
|
+
*/
|
|
43
|
+
export const makeServerLive = (options) => {
|
|
44
|
+
const port = options.port ?? parseInt(process.env.TX_API_PORT ?? "3001", 10);
|
|
45
|
+
const host = options.hostname ?? process.env.TX_API_HOST ?? "127.0.0.1";
|
|
46
|
+
const dbPath = options.dbPath ?? process.env.TX_DB_PATH ?? ".tx/tasks.db";
|
|
47
|
+
const appLayer = makeAppLayer(dbPath);
|
|
48
|
+
return HttpApiBuilder.serve().pipe(Layer.provide(HttpApiBuilder.middleware(bodyLimitMiddleware)), Layer.provide(HttpApiBuilder.middlewareCors(getCorsConfig())), Layer.provide(HttpApiBuilder.middleware(authMiddleware)), Layer.provide(ApiLive), Layer.provide(appLayer), Layer.provide(NodeHttpServer.layer(() => createServer(), { port, host })));
|
|
49
|
+
};
|
|
50
|
+
// -----------------------------------------------------------------------------
|
|
51
|
+
// CLI Support
|
|
52
|
+
// -----------------------------------------------------------------------------
|
|
53
|
+
const HELP_TEXT = `
|
|
54
|
+
tx-api - TX REST API Server
|
|
55
|
+
|
|
56
|
+
Usage:
|
|
57
|
+
tx-api [options]
|
|
58
|
+
|
|
59
|
+
Options:
|
|
60
|
+
--port, -p <port> Port to listen on (default: 3001, env: TX_API_PORT)
|
|
61
|
+
--host <host> Hostname to bind to (default: localhost, env: TX_API_HOST)
|
|
62
|
+
--db <path> Path to SQLite database (default: .tx/tasks.db, env: TX_DB_PATH)
|
|
63
|
+
--help, -h Show this help message
|
|
64
|
+
|
|
65
|
+
Environment Variables:
|
|
66
|
+
TX_API_PORT Server port (default: 3001)
|
|
67
|
+
TX_API_HOST Server hostname (default: localhost)
|
|
68
|
+
TX_DB_PATH Database path (default: .tx/tasks.db)
|
|
69
|
+
TX_API_KEY API key for authentication (optional)
|
|
70
|
+
TX_API_CORS_ORIGIN Allowed CORS origins (comma-separated, default: localhost only)
|
|
71
|
+
Use "*" to allow all origins (not recommended for production)
|
|
72
|
+
|
|
73
|
+
Examples:
|
|
74
|
+
tx-api # Start with defaults
|
|
75
|
+
tx-api --port 8080 # Start on port 8080
|
|
76
|
+
tx-api --db /path/to/tasks.db # Use custom database
|
|
77
|
+
TX_API_KEY=secret tx-api # Enable API key auth
|
|
78
|
+
`;
|
|
79
|
+
/**
|
|
80
|
+
* Parse command line arguments and launch the server.
|
|
81
|
+
* Exported for use by the CLI entry point.
|
|
82
|
+
*/
|
|
83
|
+
export const main = () => {
|
|
84
|
+
const args = process.argv.slice(2);
|
|
85
|
+
let port;
|
|
86
|
+
let dbPath;
|
|
87
|
+
let hostname;
|
|
88
|
+
for (let i = 0; i < args.length; i++) {
|
|
89
|
+
const arg = args[i];
|
|
90
|
+
const nextArg = args[i + 1];
|
|
91
|
+
if ((arg === "--port" || arg === "-p") && nextArg) {
|
|
92
|
+
port = parseInt(nextArg, 10);
|
|
93
|
+
i++;
|
|
94
|
+
}
|
|
95
|
+
else if (arg === "--db" && nextArg) {
|
|
96
|
+
dbPath = nextArg;
|
|
97
|
+
i++;
|
|
98
|
+
}
|
|
99
|
+
else if (arg === "--host") {
|
|
100
|
+
if (nextArg && !nextArg.startsWith("-")) {
|
|
101
|
+
hostname = nextArg;
|
|
102
|
+
i++;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
else if (arg === "--help" || arg === "-h") {
|
|
106
|
+
console.log(HELP_TEXT);
|
|
107
|
+
process.exit(0);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
const resolvedPort = port ?? parseInt(process.env.TX_API_PORT ?? "3001", 10);
|
|
111
|
+
const resolvedHost = hostname ?? process.env.TX_API_HOST ?? "127.0.0.1";
|
|
112
|
+
const resolvedDb = dbPath ?? process.env.TX_DB_PATH ?? ".tx/tasks.db";
|
|
113
|
+
const authStatus = isAuthEnabled() ? " (auth enabled)" : "";
|
|
114
|
+
console.log(`TX API Server running at http://${resolvedHost}:${resolvedPort}${authStatus}`);
|
|
115
|
+
console.log(` Database: ${resolvedDb}`);
|
|
116
|
+
const ServerLive = makeServerLive({
|
|
117
|
+
port: resolvedPort,
|
|
118
|
+
dbPath: resolvedDb,
|
|
119
|
+
hostname: resolvedHost,
|
|
120
|
+
});
|
|
121
|
+
NodeRuntime.runMain(Layer.launch(ServerLive));
|
|
122
|
+
};
|
|
123
|
+
//# sourceMappingURL=server-lib.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server-lib.js","sourceRoot":"","sources":["../src/server-lib.ts"],"names":[],"mappings":"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"}
|
package/dist/server.d.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* TX API Server CLI Entry Point
|
|
4
|
+
*
|
|
5
|
+
* This module auto-starts the server when executed directly.
|
|
6
|
+
* For library usage without auto-start, import from '@tx/api-server' instead.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* tx-api # Start with default settings
|
|
10
|
+
* tx-api --port 3000 # Start on specific port
|
|
11
|
+
* tx-api --db /path/to.db # Start with custom database path
|
|
12
|
+
*/
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=server.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG"}
|
package/dist/server.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* TX API Server CLI Entry Point
|
|
4
|
+
*
|
|
5
|
+
* This module auto-starts the server when executed directly.
|
|
6
|
+
* For library usage without auto-start, import from '@tx/api-server' instead.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* tx-api # Start with default settings
|
|
10
|
+
* tx-api --port 3000 # Start on specific port
|
|
11
|
+
* tx-api --db /path/to.db # Start with custom database path
|
|
12
|
+
*/
|
|
13
|
+
import { main } from "./server-lib.js";
|
|
14
|
+
main();
|
|
15
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","sourceRoot":"","sources":["../src/server.ts"],"names":[],"mappings":";AACA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,IAAI,EAAE,MAAM,iBAAiB,CAAA;AAEtC,IAAI,EAAE,CAAA"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log File Reader
|
|
3
|
+
*
|
|
4
|
+
* Safely reads per-run log files (stdout, stderr, context) from .tx/runs/ directories.
|
|
5
|
+
* Includes path traversal protection and tail support for large files.
|
|
6
|
+
*/
|
|
7
|
+
import { Effect } from "effect";
|
|
8
|
+
/**
|
|
9
|
+
* Check if a resolved path is under the project's .tx/runs/ directory.
|
|
10
|
+
* Uses prefix match (startsWith) not substring match (includes) to prevent
|
|
11
|
+
* bypasses where /.tx/runs/ appears elsewhere in the path.
|
|
12
|
+
*/
|
|
13
|
+
export declare const isAllowedRunPath: (filePath: string) => boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Read a log file with optional tail support.
|
|
16
|
+
* Validates the path is under .tx/runs/ to prevent path traversal.
|
|
17
|
+
*
|
|
18
|
+
* @param filePath - Absolute path to the log file
|
|
19
|
+
* @param tailLines - If > 0, return only the last N lines
|
|
20
|
+
* @returns The file content and whether it was truncated
|
|
21
|
+
*/
|
|
22
|
+
export declare const readLogFile: (filePath: string, tailLines?: number) => Effect.Effect<{
|
|
23
|
+
content: string;
|
|
24
|
+
truncated: boolean;
|
|
25
|
+
}, Error>;
|
|
26
|
+
//# sourceMappingURL=log-reader.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-reader.d.ts","sourceRoot":"","sources":["../../src/utils/log-reader.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAK/B;;;;GAIG;AACH,eAAO,MAAM,gBAAgB,GAAI,UAAU,MAAM,KAAG,OAInD,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,WAAW,GACtB,UAAU,MAAM,EAChB,YAAW,MAAU,KACpB,MAAM,CAAC,MAAM,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,EAAE,KAAK,CA6B3D,CAAA"}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log File Reader
|
|
3
|
+
*
|
|
4
|
+
* Safely reads per-run log files (stdout, stderr, context) from .tx/runs/ directories.
|
|
5
|
+
* Includes path traversal protection and tail support for large files.
|
|
6
|
+
*/
|
|
7
|
+
import { Effect } from "effect";
|
|
8
|
+
import { readFile } from "node:fs/promises";
|
|
9
|
+
import { existsSync } from "node:fs";
|
|
10
|
+
import { resolve, sep } from "node:path";
|
|
11
|
+
/**
|
|
12
|
+
* Check if a resolved path is under the project's .tx/runs/ directory.
|
|
13
|
+
* Uses prefix match (startsWith) not substring match (includes) to prevent
|
|
14
|
+
* bypasses where /.tx/runs/ appears elsewhere in the path.
|
|
15
|
+
*/
|
|
16
|
+
export const isAllowedRunPath = (filePath) => {
|
|
17
|
+
const resolved = resolve(filePath);
|
|
18
|
+
const runsDir = resolve(process.cwd(), ".tx", "runs");
|
|
19
|
+
return resolved.startsWith(runsDir + sep);
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Read a log file with optional tail support.
|
|
23
|
+
* Validates the path is under .tx/runs/ to prevent path traversal.
|
|
24
|
+
*
|
|
25
|
+
* @param filePath - Absolute path to the log file
|
|
26
|
+
* @param tailLines - If > 0, return only the last N lines
|
|
27
|
+
* @returns The file content and whether it was truncated
|
|
28
|
+
*/
|
|
29
|
+
export const readLogFile = (filePath, tailLines = 0) => Effect.gen(function* () {
|
|
30
|
+
if (!existsSync(filePath)) {
|
|
31
|
+
return { content: "", truncated: false };
|
|
32
|
+
}
|
|
33
|
+
// Security: ensure path is under .tx/runs/ (prefix match, not substring)
|
|
34
|
+
if (!isAllowedRunPath(filePath)) {
|
|
35
|
+
return yield* Effect.fail(new Error("Path traversal attempt: log path must be under .tx/runs/"));
|
|
36
|
+
}
|
|
37
|
+
const content = yield* Effect.tryPromise({
|
|
38
|
+
try: () => readFile(filePath, "utf-8"),
|
|
39
|
+
catch: (err) => new Error(`Failed to read log file: ${String(err)}`),
|
|
40
|
+
});
|
|
41
|
+
if (tailLines > 0) {
|
|
42
|
+
const lines = content.split("\n");
|
|
43
|
+
if (lines.length > tailLines) {
|
|
44
|
+
return {
|
|
45
|
+
content: lines.slice(-tailLines).join("\n"),
|
|
46
|
+
truncated: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { content, truncated: false };
|
|
51
|
+
});
|
|
52
|
+
//# sourceMappingURL=log-reader.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log-reader.js","sourceRoot":"","sources":["../../src/utils/log-reader.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAC/B,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAA;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAA;AACpC,OAAO,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAA;AAExC;;;;GAIG;AACH,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,QAAgB,EAAW,EAAE;IAC5D,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAClC,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,CAAA;IACrD,OAAO,QAAQ,CAAC,UAAU,CAAC,OAAO,GAAG,GAAG,CAAC,CAAA;AAC3C,CAAC,CAAA;AAED;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,WAAW,GAAG,CACzB,QAAgB,EAChB,YAAoB,CAAC,EAC0C,EAAE,CACjE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;IAClB,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,OAAO,EAAE,OAAO,EAAE,EAAE,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;IAC1C,CAAC;IAED,yEAAyE;IACzE,IAAI,CAAC,gBAAgB,CAAC,QAAQ,CAAC,EAAE,CAAC;QAChC,OAAO,KAAK,CAAC,CAAC,MAAM,CAAC,IAAI,CACvB,IAAI,KAAK,CAAC,0DAA0D,CAAC,CACtE,CAAA;IACH,CAAC;IAED,MAAM,OAAO,GAAG,KAAK,CAAC,CAAC,MAAM,CAAC,UAAU,CAAC;QACvC,GAAG,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;QACtC,KAAK,EAAE,CAAC,GAAG,EAAE,EAAE,CAAC,IAAI,KAAK,CAAC,4BAA4B,MAAM,CAAC,GAAG,CAAC,EAAE,CAAC;KACrE,CAAC,CAAA;IAEF,IAAI,SAAS,GAAG,CAAC,EAAE,CAAC;QAClB,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,KAAK,CAAC,MAAM,GAAG,SAAS,EAAE,CAAC;YAC7B,OAAO;gBACL,OAAO,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,SAAS,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC;gBAC3C,SAAS,EAAE,IAAI;aAChB,CAAA;QACH,CAAC;IACH,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,CAAA;AACtC,CAAC,CAAC,CAAA"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Transcript Parser for Claude JSONL files
|
|
3
|
+
*
|
|
4
|
+
* Parses Claude's conversation transcript files (~/.claude/projects/.../session.jsonl)
|
|
5
|
+
* and converts them to a format suitable for display in the dashboard.
|
|
6
|
+
*/
|
|
7
|
+
import { Effect } from "effect";
|
|
8
|
+
export interface ChatMessage {
|
|
9
|
+
role: "user" | "assistant" | "system";
|
|
10
|
+
content: string | unknown;
|
|
11
|
+
type?: "tool_use" | "tool_result" | "text" | "thinking";
|
|
12
|
+
tool_name?: string;
|
|
13
|
+
timestamp?: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Validate that a transcript path is under an allowed directory.
|
|
17
|
+
* Prevents arbitrary file reads via path traversal.
|
|
18
|
+
*
|
|
19
|
+
* Allowed directories: ~/.claude/ and any .tx/ directory.
|
|
20
|
+
*/
|
|
21
|
+
export declare const isAllowedTranscriptPath: (filePath: string) => boolean;
|
|
22
|
+
/**
|
|
23
|
+
* Parse a Claude transcript JSONL file and extract conversation messages
|
|
24
|
+
*/
|
|
25
|
+
export declare const parseTranscript: (path: string) => Effect.Effect<ChatMessage[], Error>;
|
|
26
|
+
/**
|
|
27
|
+
* Check if a transcript file exists
|
|
28
|
+
*/
|
|
29
|
+
export declare const transcriptExists: (path: string) => boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Find a transcript file that matches a run's time window.
|
|
32
|
+
* Looks in ~/.claude/projects/<escaped-cwd>/ for JSONL files modified during the run.
|
|
33
|
+
*
|
|
34
|
+
* @param cwd - The working directory (used to find Claude's project directory)
|
|
35
|
+
* @param startedAt - When the run started
|
|
36
|
+
* @param endedAt - When the run ended (optional, uses now if not set)
|
|
37
|
+
* @returns Path to the matching transcript, or null if none found
|
|
38
|
+
*/
|
|
39
|
+
export declare const findMatchingTranscript: (cwd: string, startedAt: Date, endedAt?: Date | null) => Promise<string | null>;
|
|
40
|
+
//# sourceMappingURL=transcript-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transcript-parser.d.ts","sourceRoot":"","sources":["../../src/utils/transcript-parser.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,QAAQ,CAAA;AAgD/B,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,GAAG,WAAW,GAAG,QAAQ,CAAA;IACrC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAA;IACzB,IAAI,CAAC,EAAE,UAAU,GAAG,aAAa,GAAG,MAAM,GAAG,UAAU,CAAA;IACvD,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB;AAED;;;;;GAKG;AACH,eAAO,MAAM,uBAAuB,GAAI,UAAU,MAAM,KAAG,OAgB1D,CAAA;AAED;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,MAAM,MAAM,KAAG,MAAM,CAAC,MAAM,CAAC,WAAW,EAAE,EAAE,KAAK,CAyH7E,CAAA;AAEJ;;GAEG;AACH,eAAO,MAAM,gBAAgB,GAAI,MAAM,MAAM,KAAG,OAG/C,CAAA;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,sBAAsB,GACjC,KAAK,MAAM,EACX,WAAW,IAAI,EACf,UAAU,IAAI,GAAG,IAAI,KACpB,OAAO,CAAC,MAAM,GAAG,IAAI,CAkDvB,CAAA"}
|