@kaddo/cli 3.69.1 → 3.69.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.
@@ -0,0 +1,448 @@
1
+ // src/server.ts
2
+ import Fastify from "fastify";
3
+ import fastifyCookie from "@fastify/cookie";
4
+ import fastifyCors from "@fastify/cors";
5
+ import fastifyStatic from "@fastify/static";
6
+ import path from "path";
7
+
8
+ // src/session.ts
9
+ import { randomBytes } from "crypto";
10
+ var SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
11
+ var SessionManager = class {
12
+ constructor(storage) {
13
+ this.storage = storage;
14
+ }
15
+ storage;
16
+ activeSessionId = null;
17
+ createSession() {
18
+ this.storage.sessions.deleteExpired();
19
+ const id = randomBytes(32).toString("hex");
20
+ const now = /* @__PURE__ */ new Date();
21
+ const expiresAt = new Date(now.getTime() + SESSION_TTL_MS);
22
+ this.storage.sessions.create({
23
+ id,
24
+ createdAt: now.toISOString(),
25
+ expiresAt: expiresAt.toISOString()
26
+ });
27
+ this.activeSessionId = id;
28
+ return id;
29
+ }
30
+ validateSession(id) {
31
+ if (!id) return false;
32
+ const session = this.storage.sessions.findById(id);
33
+ if (!session) return false;
34
+ if (new Date(session.expiresAt) < /* @__PURE__ */ new Date()) {
35
+ this.storage.sessions.deleteById(id);
36
+ return false;
37
+ }
38
+ return true;
39
+ }
40
+ invalidateSession(id) {
41
+ this.storage.sessions.deleteById(id);
42
+ if (this.activeSessionId === id) this.activeSessionId = null;
43
+ }
44
+ invalidateAll() {
45
+ if (this.activeSessionId) {
46
+ this.storage.sessions.deleteById(this.activeSessionId);
47
+ this.activeSessionId = null;
48
+ }
49
+ }
50
+ };
51
+
52
+ // src/core-adapter.ts
53
+ import {
54
+ buildProjectExplanation,
55
+ buildReadinessReport,
56
+ buildProjectRoute,
57
+ loadConfig,
58
+ loadMappedModules
59
+ } from "@kaddo/cli/core";
60
+ function getProjectSummary(dir) {
61
+ const config = loadConfig(dir);
62
+ if (!config) throw new CoreError("PROJECT_NOT_FOUND", "No Kaddo project was found.");
63
+ return {
64
+ name: config.project.name ?? "unknown",
65
+ state: config.project.state ?? "unknown",
66
+ structure: config.project.structure ?? "unknown",
67
+ language: config.project.language ?? "en",
68
+ teamSize: config.team.size ?? "unknown"
69
+ };
70
+ }
71
+ function getKnowledgeSummary(dir) {
72
+ const exp = buildProjectExplanation(dir);
73
+ return {
74
+ layers: exp.layers.map((l) => ({ layer: l.layer, status: l.status })),
75
+ missing: exp.missingKnowledge
76
+ };
77
+ }
78
+ function getWorkItemSummary(dir) {
79
+ const exp = buildProjectExplanation(dir);
80
+ return {
81
+ total: exp.workItems.total,
82
+ byState: exp.workItems.byState,
83
+ byType: exp.workItems.byType,
84
+ items: exp.workItems.items.map((i) => ({
85
+ id: i.id,
86
+ title: i.title,
87
+ type: i.type,
88
+ lifecycle: i.lifecycle,
89
+ initiative: i.initiative
90
+ }))
91
+ };
92
+ }
93
+ function getModules(dir) {
94
+ const mapped = loadMappedModules(dir);
95
+ return {
96
+ modules: mapped.map((m) => ({
97
+ id: m.id,
98
+ role: m.role,
99
+ path: m.path,
100
+ available: m.available
101
+ }))
102
+ };
103
+ }
104
+ function getProjectReadiness(dir) {
105
+ const report = buildReadinessReport(dir);
106
+ return {
107
+ overall: report.overall,
108
+ recommendedNextStep: {
109
+ label: report.nextStepRecommendation.label,
110
+ command: report.nextStepRecommendation.command
111
+ }
112
+ };
113
+ }
114
+ function getProjectRoute(dir) {
115
+ const route = buildProjectRoute(dir);
116
+ return {
117
+ type: route.type,
118
+ completed: route.completed,
119
+ total: route.total,
120
+ progressPercent: route.progressPercent,
121
+ steps: route.steps.map((s) => ({
122
+ id: s.id,
123
+ label: s.label,
124
+ status: s.status,
125
+ ...s.evidence ? { evidence: s.evidence } : {},
126
+ ...s.reason ? { reason: s.reason } : {},
127
+ ...s.command ? { command: s.command } : {}
128
+ }))
129
+ };
130
+ }
131
+ function getFindings(dir) {
132
+ const exp = buildProjectExplanation(dir);
133
+ const items = [];
134
+ if (exp.missingKnowledge.length > 0) {
135
+ for (const m of exp.missingKnowledge) {
136
+ items.push({ level: "warning", message: `Missing: ${m}` });
137
+ }
138
+ }
139
+ if (exp.duplicateWorkItems.length > 0) {
140
+ for (const d of exp.duplicateWorkItems) {
141
+ items.push({ level: "warning", message: `Possible duplicate Work Items: ${d.items.map((i) => i.id).join(", ")} (${d.reason})` });
142
+ }
143
+ }
144
+ const blocking = items.filter((i) => i.level === "blocking").length;
145
+ const warning = items.filter((i) => i.level === "warning").length;
146
+ const fyi = items.filter((i) => i.level === "fyi").length;
147
+ return { blocking, warning, fyi, items };
148
+ }
149
+ function getProjectOverview(dir) {
150
+ return {
151
+ project: getProjectSummary(dir),
152
+ knowledge: getKnowledgeSummary(dir),
153
+ workItems: getWorkItemSummary(dir),
154
+ modules: getModules(dir),
155
+ readiness: getProjectReadiness(dir),
156
+ route: getProjectRoute(dir),
157
+ findings: getFindings(dir)
158
+ };
159
+ }
160
+ var CoreError = class extends Error {
161
+ constructor(code, message) {
162
+ super(message);
163
+ this.code = code;
164
+ this.name = "CoreError";
165
+ }
166
+ code;
167
+ };
168
+
169
+ // src/server.ts
170
+ async function createAdminServer(opts) {
171
+ const { projectDir, storage, staticDir, host = "127.0.0.1", port = 4173 } = opts;
172
+ const app = Fastify({ logger: false });
173
+ const sessionManager = new SessionManager(storage);
174
+ await app.register(fastifyCookie);
175
+ await app.register(fastifyCors, {
176
+ origin: `http://${host}:${port}`,
177
+ credentials: true
178
+ });
179
+ if (staticDir) {
180
+ await app.register(fastifyStatic, {
181
+ root: path.resolve(staticDir),
182
+ prefix: "/",
183
+ wildcard: false
184
+ });
185
+ }
186
+ const sessionId = sessionManager.createSession();
187
+ app.addHook("onRequest", async (request, reply) => {
188
+ if (!request.url.startsWith("/api/")) return;
189
+ const cookieSession = request.cookies["kaddo-session"];
190
+ if (!sessionManager.validateSession(cookieSession)) {
191
+ reply.code(401).send({ error: { code: "SESSION_INVALID", message: "Invalid or expired session." } });
192
+ }
193
+ });
194
+ app.get("/api/v1/admin/health", async () => ({ status: "ok" }));
195
+ app.get("/api/v1/admin/session", async (_request, reply) => {
196
+ reply.setCookie("kaddo-session", sessionId, {
197
+ path: "/",
198
+ httpOnly: true,
199
+ sameSite: "strict",
200
+ maxAge: 86400
201
+ });
202
+ return { status: "active" };
203
+ });
204
+ const coreRoute = (handler) => {
205
+ return async () => {
206
+ try {
207
+ return handler(projectDir);
208
+ } catch (err) {
209
+ if (err instanceof CoreError) {
210
+ return { error: { code: err.code, message: err.message } };
211
+ }
212
+ throw err;
213
+ }
214
+ };
215
+ };
216
+ app.get("/api/v1/admin/overview", coreRoute(getProjectOverview));
217
+ app.get("/api/v1/admin/project", coreRoute(getProjectSummary));
218
+ app.get("/api/v1/admin/knowledge", coreRoute(getKnowledgeSummary));
219
+ app.get("/api/v1/admin/work-items", coreRoute(getWorkItemSummary));
220
+ app.get("/api/v1/admin/modules", coreRoute(getModules));
221
+ app.get("/api/v1/admin/readiness", coreRoute(getProjectReadiness));
222
+ app.get("/api/v1/admin/route", coreRoute(getProjectRoute));
223
+ app.get("/api/v1/admin/findings", coreRoute(getFindings));
224
+ if (staticDir) {
225
+ app.setNotFoundHandler(async (_request, reply) => {
226
+ return reply.sendFile("index.html");
227
+ });
228
+ }
229
+ return {
230
+ app,
231
+ sessionId,
232
+ sessionManager,
233
+ start: async () => {
234
+ await app.listen({ host, port });
235
+ return `http://${host}:${port}`;
236
+ },
237
+ stop: async () => {
238
+ sessionManager.invalidateAll();
239
+ await app.close();
240
+ await storage.close();
241
+ }
242
+ };
243
+ }
244
+
245
+ // src/storage/sqlite-storage.ts
246
+ import { DatabaseSync } from "sqlite";
247
+ var SQLiteAdminStorage = class {
248
+ constructor(dbPath) {
249
+ this.dbPath = dbPath;
250
+ this.db = new DatabaseSync(dbPath);
251
+ this.db.exec("PRAGMA journal_mode = WAL");
252
+ this.sessions = {
253
+ create: (session) => {
254
+ this.db.prepare(
255
+ "INSERT OR REPLACE INTO admin_sessions (id, created_at, expires_at) VALUES (?, ?, ?)"
256
+ ).run(session.id, session.createdAt, session.expiresAt);
257
+ },
258
+ findById: (id) => {
259
+ const row = this.db.prepare(
260
+ "SELECT id, created_at, expires_at FROM admin_sessions WHERE id = ?"
261
+ ).get(id);
262
+ if (!row) return void 0;
263
+ return { id: row.id, createdAt: row.created_at, expiresAt: row.expires_at };
264
+ },
265
+ deleteById: (id) => {
266
+ this.db.prepare("DELETE FROM admin_sessions WHERE id = ?").run(id);
267
+ },
268
+ deleteExpired: () => {
269
+ const now = (/* @__PURE__ */ new Date()).toISOString();
270
+ this.db.prepare("DELETE FROM admin_sessions WHERE expires_at < ?").run(now);
271
+ }
272
+ };
273
+ this.preferences = {
274
+ get: (key) => {
275
+ const row = this.db.prepare(
276
+ "SELECT value FROM admin_preferences WHERE key = ?"
277
+ ).get(key);
278
+ return row?.value;
279
+ },
280
+ set: (key, value) => {
281
+ this.db.prepare(
282
+ "INSERT OR REPLACE INTO admin_preferences (key, value) VALUES (?, ?)"
283
+ ).run(key, value);
284
+ },
285
+ delete: (key) => {
286
+ this.db.prepare("DELETE FROM admin_preferences WHERE key = ?").run(key);
287
+ },
288
+ all: () => {
289
+ return this.db.prepare("SELECT key, value FROM admin_preferences").all();
290
+ }
291
+ };
292
+ this.cache = {
293
+ get: (key) => {
294
+ const row = this.db.prepare(
295
+ "SELECT value, expires_at FROM admin_cache WHERE key = ?"
296
+ ).get(key);
297
+ if (!row) return void 0;
298
+ if (row.expires_at && new Date(row.expires_at) < /* @__PURE__ */ new Date()) {
299
+ this.db.prepare("DELETE FROM admin_cache WHERE key = ?").run(key);
300
+ return void 0;
301
+ }
302
+ return row.value;
303
+ },
304
+ set: (key, value, ttlMs) => {
305
+ const expiresAt = ttlMs ? new Date(Date.now() + ttlMs).toISOString() : null;
306
+ this.db.prepare(
307
+ "INSERT OR REPLACE INTO admin_cache (key, value, expires_at) VALUES (?, ?, ?)"
308
+ ).run(key, value, expiresAt);
309
+ },
310
+ delete: (key) => {
311
+ this.db.prepare("DELETE FROM admin_cache WHERE key = ?").run(key);
312
+ },
313
+ clear: () => {
314
+ this.db.exec("DELETE FROM admin_cache");
315
+ }
316
+ };
317
+ }
318
+ dbPath;
319
+ db;
320
+ sessions;
321
+ preferences;
322
+ cache;
323
+ async initialize() {
324
+ this.db.exec(`
325
+ CREATE TABLE IF NOT EXISTS admin_sessions (
326
+ id TEXT PRIMARY KEY,
327
+ created_at TEXT NOT NULL,
328
+ expires_at TEXT NOT NULL
329
+ );
330
+ CREATE TABLE IF NOT EXISTS admin_preferences (
331
+ key TEXT PRIMARY KEY,
332
+ value TEXT NOT NULL
333
+ );
334
+ CREATE TABLE IF NOT EXISTS admin_cache (
335
+ key TEXT PRIMARY KEY,
336
+ value TEXT NOT NULL,
337
+ expires_at TEXT
338
+ );
339
+ CREATE TABLE IF NOT EXISTS audit_events (
340
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
341
+ timestamp TEXT NOT NULL,
342
+ action TEXT NOT NULL,
343
+ detail TEXT
344
+ );
345
+ `);
346
+ }
347
+ async close() {
348
+ this.db.close();
349
+ }
350
+ };
351
+
352
+ // src/contracts/schemas.ts
353
+ import { z } from "zod";
354
+ var ProjectSummarySchema = z.object({
355
+ name: z.string(),
356
+ state: z.string(),
357
+ structure: z.string(),
358
+ language: z.string(),
359
+ teamSize: z.string()
360
+ });
361
+ var KnowledgeSummarySchema = z.object({
362
+ layers: z.array(z.object({
363
+ layer: z.string(),
364
+ status: z.string()
365
+ })),
366
+ missing: z.array(z.string())
367
+ });
368
+ var WorkItemSummarySchema = z.object({
369
+ total: z.number(),
370
+ byState: z.record(z.string(), z.number()),
371
+ byType: z.record(z.string(), z.number()),
372
+ items: z.array(z.object({
373
+ id: z.string(),
374
+ title: z.string(),
375
+ type: z.string(),
376
+ lifecycle: z.string(),
377
+ initiative: z.string()
378
+ }))
379
+ });
380
+ var ModuleSummarySchema = z.object({
381
+ modules: z.array(z.object({
382
+ id: z.string(),
383
+ role: z.string(),
384
+ path: z.string().optional(),
385
+ available: z.boolean()
386
+ }))
387
+ });
388
+ var ProjectReadinessSchema = z.object({
389
+ overall: z.string(),
390
+ recommendedNextStep: z.object({
391
+ label: z.string(),
392
+ command: z.string().optional()
393
+ })
394
+ });
395
+ var RouteStepSchema = z.object({
396
+ id: z.string(),
397
+ label: z.string(),
398
+ status: z.string(),
399
+ evidence: z.array(z.string()).optional(),
400
+ reason: z.string().optional(),
401
+ command: z.string().optional()
402
+ });
403
+ var ProjectRouteSchema = z.object({
404
+ type: z.string(),
405
+ completed: z.number(),
406
+ total: z.number(),
407
+ progressPercent: z.number(),
408
+ steps: z.array(RouteStepSchema)
409
+ });
410
+ var FindingsSummarySchema = z.object({
411
+ blocking: z.number(),
412
+ warning: z.number(),
413
+ fyi: z.number(),
414
+ items: z.array(z.object({
415
+ level: z.enum(["blocking", "warning", "fyi"]),
416
+ message: z.string()
417
+ }))
418
+ });
419
+ var ProjectOverviewSchema = z.object({
420
+ project: ProjectSummarySchema,
421
+ knowledge: KnowledgeSummarySchema,
422
+ workItems: WorkItemSummarySchema,
423
+ modules: ModuleSummarySchema,
424
+ readiness: ProjectReadinessSchema,
425
+ route: ProjectRouteSchema,
426
+ findings: FindingsSummarySchema
427
+ });
428
+ var ErrorResponseSchema = z.object({
429
+ error: z.object({
430
+ code: z.string(),
431
+ message: z.string()
432
+ })
433
+ });
434
+ export {
435
+ ErrorResponseSchema,
436
+ FindingsSummarySchema,
437
+ KnowledgeSummarySchema,
438
+ ModuleSummarySchema,
439
+ ProjectOverviewSchema,
440
+ ProjectReadinessSchema,
441
+ ProjectRouteSchema,
442
+ ProjectSummarySchema,
443
+ RouteStepSchema,
444
+ SQLiteAdminStorage,
445
+ SessionManager,
446
+ WorkItemSummarySchema,
447
+ createAdminServer
448
+ };
package/dist/index.js CHANGED
@@ -18510,8 +18510,8 @@ function isPortAvailable(port, host) {
18510
18510
  function resolveStaticDir() {
18511
18511
  const __dirname = path8.dirname(fileURLToPath2(import.meta.url));
18512
18512
  const candidates = [
18513
- // Bundled inside CLI package (npm install)
18514
- path8.resolve(__dirname, "..", "admin-dist"),
18513
+ // Bundled inside CLI dist (npm install)
18514
+ path8.resolve(__dirname, "admin-dist"),
18515
18515
  // Monorepo development
18516
18516
  path8.resolve(__dirname, "..", "..", "admin", "dist"),
18517
18517
  path8.resolve(__dirname, "..", "node_modules", "@kaddo", "admin", "dist")
@@ -18554,7 +18554,19 @@ async function runAdmin(opts = {}) {
18554
18554
  console.error("Admin frontend not built. Run `pnpm -r build` first.");
18555
18555
  process.exit(1);
18556
18556
  }
18557
- const { createAdminServer, SQLiteAdminStorage } = await import("@kaddo/admin-server");
18557
+ let adminServer;
18558
+ try {
18559
+ const __dirname = path8.dirname(fileURLToPath2(import.meta.url));
18560
+ const bundled = path8.resolve(__dirname, "admin-server", "index.js");
18561
+ if (exists(bundled)) {
18562
+ adminServer = await import(bundled);
18563
+ } else {
18564
+ adminServer = await import("@kaddo/admin-server");
18565
+ }
18566
+ } catch {
18567
+ adminServer = await import("@kaddo/admin-server");
18568
+ }
18569
+ const { createAdminServer, SQLiteAdminStorage } = adminServer;
18558
18570
  const dbDir = join(dir, ".kaddo", "admin");
18559
18571
  ensureDir(dbDir);
18560
18572
  const dbPath = join(dbDir, "admin.db");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@kaddo/cli",
3
- "version": "3.69.1",
3
+ "version": "3.69.2",
4
4
  "description": "Knowledge Driven Development toolkit",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -29,7 +29,6 @@
29
29
  },
30
30
  "files": [
31
31
  "dist",
32
- "dist/admin-dist",
33
32
  "README.md",
34
33
  "LICENSE"
35
34
  ],
@@ -37,7 +36,7 @@
37
36
  "access": "public"
38
37
  },
39
38
  "scripts": {
40
- "build": "tsup && node -e \"const fs=require('fs');const s='../admin/dist',d='dist/admin-dist';if(fs.existsSync(s)){fs.cpSync(s,d,{recursive:true})}\"",
39
+ "build": "tsup && node -e \"const fs=require('fs');[['../admin/dist','dist/admin-dist'],['../admin-server/dist','dist/admin-server']].forEach(([s,d])=>{if(fs.existsSync(s)){fs.cpSync(s,d,{recursive:true})}})\"",
41
40
  "dev": "tsup --watch",
42
41
  "test": "vitest run",
43
42
  "prepublishOnly": "npm run build"