@blackbelt-technology/pi-agent-dashboard 0.2.0
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/AGENTS.md +342 -0
- package/README.md +619 -0
- package/docs/architecture.md +646 -0
- package/package.json +92 -0
- package/packages/extension/package.json +33 -0
- package/packages/extension/src/__tests__/ask-user-tool.test.ts +85 -0
- package/packages/extension/src/__tests__/command-handler.test.ts +712 -0
- package/packages/extension/src/__tests__/connection.test.ts +344 -0
- package/packages/extension/src/__tests__/credentials-updated.test.ts +26 -0
- package/packages/extension/src/__tests__/dev-build.test.ts +79 -0
- package/packages/extension/src/__tests__/event-forwarder.test.ts +89 -0
- package/packages/extension/src/__tests__/git-info.test.ts +112 -0
- package/packages/extension/src/__tests__/git-link-builder.test.ts +102 -0
- package/packages/extension/src/__tests__/openspec-activity-detector.test.ts +232 -0
- package/packages/extension/src/__tests__/openspec-poller.test.ts +119 -0
- package/packages/extension/src/__tests__/process-metrics.test.ts +47 -0
- package/packages/extension/src/__tests__/process-scanner.test.ts +202 -0
- package/packages/extension/src/__tests__/prompt-expander.test.ts +54 -0
- package/packages/extension/src/__tests__/server-auto-start.test.ts +167 -0
- package/packages/extension/src/__tests__/server-launcher.test.ts +44 -0
- package/packages/extension/src/__tests__/server-probe.test.ts +25 -0
- package/packages/extension/src/__tests__/session-switch.test.ts +139 -0
- package/packages/extension/src/__tests__/session-sync.test.ts +55 -0
- package/packages/extension/src/__tests__/source-detector.test.ts +73 -0
- package/packages/extension/src/__tests__/stats-extractor.test.ts +92 -0
- package/packages/extension/src/__tests__/ui-proxy.test.ts +583 -0
- package/packages/extension/src/__tests__/watchdog.test.ts +161 -0
- package/packages/extension/src/ask-user-tool.ts +63 -0
- package/packages/extension/src/bridge-context.ts +64 -0
- package/packages/extension/src/bridge.ts +926 -0
- package/packages/extension/src/command-handler.ts +538 -0
- package/packages/extension/src/connection.ts +204 -0
- package/packages/extension/src/dev-build.ts +39 -0
- package/packages/extension/src/event-forwarder.ts +40 -0
- package/packages/extension/src/flow-event-wiring.ts +102 -0
- package/packages/extension/src/git-info.ts +65 -0
- package/packages/extension/src/git-link-builder.ts +112 -0
- package/packages/extension/src/model-tracker.ts +56 -0
- package/packages/extension/src/pi-env.d.ts +23 -0
- package/packages/extension/src/process-metrics.ts +70 -0
- package/packages/extension/src/process-scanner.ts +396 -0
- package/packages/extension/src/prompt-expander.ts +87 -0
- package/packages/extension/src/provider-register.ts +276 -0
- package/packages/extension/src/server-auto-start.ts +87 -0
- package/packages/extension/src/server-launcher.ts +82 -0
- package/packages/extension/src/server-probe.ts +33 -0
- package/packages/extension/src/session-sync.ts +154 -0
- package/packages/extension/src/source-detector.ts +26 -0
- package/packages/extension/src/ui-proxy.ts +269 -0
- package/packages/extension/tsconfig.json +11 -0
- package/packages/server/package.json +37 -0
- package/packages/server/src/__tests__/auth-plugin.test.ts +117 -0
- package/packages/server/src/__tests__/auth.test.ts +224 -0
- package/packages/server/src/__tests__/auto-attach.test.ts +246 -0
- package/packages/server/src/__tests__/auto-resume.test.ts +135 -0
- package/packages/server/src/__tests__/auto-shutdown.test.ts +136 -0
- package/packages/server/src/__tests__/browse-endpoint.test.ts +104 -0
- package/packages/server/src/__tests__/bulk-archive-handler.test.ts +15 -0
- package/packages/server/src/__tests__/cli-parse.test.ts +73 -0
- package/packages/server/src/__tests__/client-discovery.test.ts +39 -0
- package/packages/server/src/__tests__/config-api.test.ts +104 -0
- package/packages/server/src/__tests__/cors.test.ts +48 -0
- package/packages/server/src/__tests__/directory-service.test.ts +240 -0
- package/packages/server/src/__tests__/editor-detection.test.ts +60 -0
- package/packages/server/src/__tests__/editor-endpoints.test.ts +26 -0
- package/packages/server/src/__tests__/editor-manager.test.ts +73 -0
- package/packages/server/src/__tests__/editor-registry.test.ts +151 -0
- package/packages/server/src/__tests__/event-status-extraction-flow.test.ts +55 -0
- package/packages/server/src/__tests__/event-status-extraction.test.ts +58 -0
- package/packages/server/src/__tests__/extension-register.test.ts +61 -0
- package/packages/server/src/__tests__/file-endpoint.test.ts +49 -0
- package/packages/server/src/__tests__/force-kill-handler.test.ts +109 -0
- package/packages/server/src/__tests__/git-operations.test.ts +251 -0
- package/packages/server/src/__tests__/headless-pid-registry.test.ts +233 -0
- package/packages/server/src/__tests__/headless-shutdown-fallback.test.ts +109 -0
- package/packages/server/src/__tests__/health-endpoint.test.ts +35 -0
- package/packages/server/src/__tests__/heartbeat-ack.test.ts +63 -0
- package/packages/server/src/__tests__/json-store.test.ts +70 -0
- package/packages/server/src/__tests__/localhost-guard.test.ts +149 -0
- package/packages/server/src/__tests__/memory-event-store.test.ts +260 -0
- package/packages/server/src/__tests__/memory-session-manager.test.ts +80 -0
- package/packages/server/src/__tests__/meta-persistence.test.ts +107 -0
- package/packages/server/src/__tests__/migrate-persistence.test.ts +180 -0
- package/packages/server/src/__tests__/npm-search-proxy.test.ts +153 -0
- package/packages/server/src/__tests__/oauth-callback-server.test.ts +165 -0
- package/packages/server/src/__tests__/openspec-archive.test.ts +87 -0
- package/packages/server/src/__tests__/package-manager-wrapper.test.ts +163 -0
- package/packages/server/src/__tests__/package-routes.test.ts +172 -0
- package/packages/server/src/__tests__/pending-fork-registry.test.ts +69 -0
- package/packages/server/src/__tests__/pending-load-manager.test.ts +144 -0
- package/packages/server/src/__tests__/pending-resume-registry.test.ts +130 -0
- package/packages/server/src/__tests__/pi-resource-scanner.test.ts +235 -0
- package/packages/server/src/__tests__/preferences-store.test.ts +108 -0
- package/packages/server/src/__tests__/process-manager.test.ts +184 -0
- package/packages/server/src/__tests__/provider-auth-handlers.test.ts +93 -0
- package/packages/server/src/__tests__/provider-auth-routes.test.ts +143 -0
- package/packages/server/src/__tests__/provider-auth-storage.test.ts +114 -0
- package/packages/server/src/__tests__/resolve-path.test.ts +38 -0
- package/packages/server/src/__tests__/ring-buffer.test.ts +45 -0
- package/packages/server/src/__tests__/server-pid.test.ts +89 -0
- package/packages/server/src/__tests__/session-api.test.ts +244 -0
- package/packages/server/src/__tests__/session-diff.test.ts +138 -0
- package/packages/server/src/__tests__/session-file-dedup.test.ts +102 -0
- package/packages/server/src/__tests__/session-file-reader.test.ts +85 -0
- package/packages/server/src/__tests__/session-lifecycle-logging.test.ts +138 -0
- package/packages/server/src/__tests__/session-order-manager.test.ts +135 -0
- package/packages/server/src/__tests__/session-ordering-integration.test.ts +102 -0
- package/packages/server/src/__tests__/session-scanner.test.ts +199 -0
- package/packages/server/src/__tests__/shutdown-endpoint.test.ts +42 -0
- package/packages/server/src/__tests__/skip-wipe.test.ts +123 -0
- package/packages/server/src/__tests__/sleep-aware-heartbeat.test.ts +126 -0
- package/packages/server/src/__tests__/smoke-integration.test.ts +175 -0
- package/packages/server/src/__tests__/spa-fallback.test.ts +68 -0
- package/packages/server/src/__tests__/subscription-handler.test.ts +155 -0
- package/packages/server/src/__tests__/terminal-gateway.test.ts +61 -0
- package/packages/server/src/__tests__/terminal-manager.test.ts +257 -0
- package/packages/server/src/__tests__/trusted-networks-config.test.ts +84 -0
- package/packages/server/src/__tests__/tunnel.test.ts +206 -0
- package/packages/server/src/__tests__/ws-ping-pong.test.ts +112 -0
- package/packages/server/src/auth-plugin.ts +302 -0
- package/packages/server/src/auth.ts +323 -0
- package/packages/server/src/browse.ts +55 -0
- package/packages/server/src/browser-gateway.ts +495 -0
- package/packages/server/src/browser-handlers/directory-handler.ts +137 -0
- package/packages/server/src/browser-handlers/handler-context.ts +45 -0
- package/packages/server/src/browser-handlers/session-action-handler.ts +271 -0
- package/packages/server/src/browser-handlers/session-meta-handler.ts +95 -0
- package/packages/server/src/browser-handlers/subscription-handler.ts +154 -0
- package/packages/server/src/browser-handlers/terminal-handler.ts +37 -0
- package/packages/server/src/cli.ts +347 -0
- package/packages/server/src/config-api.ts +130 -0
- package/packages/server/src/directory-service.ts +162 -0
- package/packages/server/src/editor-detection.ts +60 -0
- package/packages/server/src/editor-manager.ts +352 -0
- package/packages/server/src/editor-proxy.ts +134 -0
- package/packages/server/src/editor-registry.ts +108 -0
- package/packages/server/src/event-status-extraction.ts +131 -0
- package/packages/server/src/event-wiring.ts +589 -0
- package/packages/server/src/extension-register.ts +92 -0
- package/packages/server/src/git-operations.ts +200 -0
- package/packages/server/src/headless-pid-registry.ts +207 -0
- package/packages/server/src/idle-timer.ts +61 -0
- package/packages/server/src/json-store.ts +32 -0
- package/packages/server/src/localhost-guard.ts +117 -0
- package/packages/server/src/memory-event-store.ts +193 -0
- package/packages/server/src/memory-session-manager.ts +123 -0
- package/packages/server/src/meta-persistence.ts +64 -0
- package/packages/server/src/migrate-persistence.ts +195 -0
- package/packages/server/src/npm-search-proxy.ts +143 -0
- package/packages/server/src/oauth-callback-server.ts +177 -0
- package/packages/server/src/openspec-archive.ts +60 -0
- package/packages/server/src/package-manager-wrapper.ts +200 -0
- package/packages/server/src/pending-fork-registry.ts +53 -0
- package/packages/server/src/pending-load-manager.ts +110 -0
- package/packages/server/src/pending-resume-registry.ts +69 -0
- package/packages/server/src/pi-gateway.ts +419 -0
- package/packages/server/src/pi-resource-scanner.ts +369 -0
- package/packages/server/src/preferences-store.ts +116 -0
- package/packages/server/src/process-manager.ts +311 -0
- package/packages/server/src/provider-auth-handlers.ts +438 -0
- package/packages/server/src/provider-auth-storage.ts +200 -0
- package/packages/server/src/resolve-path.ts +12 -0
- package/packages/server/src/routes/editor-routes.ts +86 -0
- package/packages/server/src/routes/file-routes.ts +116 -0
- package/packages/server/src/routes/git-routes.ts +89 -0
- package/packages/server/src/routes/openspec-routes.ts +99 -0
- package/packages/server/src/routes/package-routes.ts +172 -0
- package/packages/server/src/routes/provider-auth-routes.ts +244 -0
- package/packages/server/src/routes/provider-routes.ts +101 -0
- package/packages/server/src/routes/route-deps.ts +23 -0
- package/packages/server/src/routes/session-routes.ts +91 -0
- package/packages/server/src/routes/system-routes.ts +271 -0
- package/packages/server/src/server-pid.ts +84 -0
- package/packages/server/src/server.ts +554 -0
- package/packages/server/src/session-api.ts +330 -0
- package/packages/server/src/session-bootstrap.ts +80 -0
- package/packages/server/src/session-diff.ts +178 -0
- package/packages/server/src/session-discovery.ts +134 -0
- package/packages/server/src/session-file-reader.ts +135 -0
- package/packages/server/src/session-order-manager.ts +73 -0
- package/packages/server/src/session-scanner.ts +233 -0
- package/packages/server/src/session-stats-reader.ts +99 -0
- package/packages/server/src/terminal-gateway.ts +51 -0
- package/packages/server/src/terminal-manager.ts +241 -0
- package/packages/server/src/tunnel.ts +329 -0
- package/packages/server/tsconfig.json +11 -0
- package/packages/shared/package.json +15 -0
- package/packages/shared/src/__tests__/config.test.ts +358 -0
- package/packages/shared/src/__tests__/deriveChangeState.test.ts +95 -0
- package/packages/shared/src/__tests__/mdns-discovery.test.ts +80 -0
- package/packages/shared/src/__tests__/protocol.test.ts +243 -0
- package/packages/shared/src/__tests__/resolve-jiti.test.ts +17 -0
- package/packages/shared/src/__tests__/server-identity.test.ts +73 -0
- package/packages/shared/src/__tests__/session-meta.test.ts +125 -0
- package/packages/shared/src/archive-types.ts +11 -0
- package/packages/shared/src/browser-protocol.ts +534 -0
- package/packages/shared/src/config.ts +245 -0
- package/packages/shared/src/diff-types.ts +41 -0
- package/packages/shared/src/editor-types.ts +18 -0
- package/packages/shared/src/mdns-discovery.ts +248 -0
- package/packages/shared/src/openspec-activity-detector.ts +109 -0
- package/packages/shared/src/openspec-poller.ts +96 -0
- package/packages/shared/src/protocol.ts +369 -0
- package/packages/shared/src/resolve-jiti.ts +43 -0
- package/packages/shared/src/rest-api.ts +255 -0
- package/packages/shared/src/server-identity.ts +51 -0
- package/packages/shared/src/session-meta.ts +86 -0
- package/packages/shared/src/state-replay.ts +174 -0
- package/packages/shared/src/stats-extractor.ts +54 -0
- package/packages/shared/src/terminal-types.ts +18 -0
- package/packages/shared/src/types.ts +351 -0
- package/packages/shared/tsconfig.json +8 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REST routes for editor (code-server) lifecycle management.
|
|
3
|
+
*/
|
|
4
|
+
import type { FastifyInstance } from "fastify";
|
|
5
|
+
import type { ApiResponse } from "@blackbelt-technology/pi-dashboard-shared/types.js";
|
|
6
|
+
import type { EditorManager } from "../editor-manager.js";
|
|
7
|
+
import type { NetworkGuard } from "./route-deps.js";
|
|
8
|
+
|
|
9
|
+
export function registerEditorRoutes(
|
|
10
|
+
fastify: FastifyInstance,
|
|
11
|
+
editorManager: EditorManager,
|
|
12
|
+
deps: { networkGuard: NetworkGuard },
|
|
13
|
+
) {
|
|
14
|
+
const { networkGuard } = deps;
|
|
15
|
+
// Start or return existing editor instance
|
|
16
|
+
fastify.post<{ Body: { cwd?: string; theme?: "dark" | "light" } }>(
|
|
17
|
+
"/api/editor/start",
|
|
18
|
+
{ preHandler: networkGuard },
|
|
19
|
+
async (request) => {
|
|
20
|
+
const { cwd, theme } = request.body ?? {};
|
|
21
|
+
if (!cwd) {
|
|
22
|
+
return { success: false, error: "cwd required" } satisfies ApiResponse;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
const info = await editorManager.start(cwd, theme);
|
|
27
|
+
return { success: true, data: info } satisfies ApiResponse;
|
|
28
|
+
} catch (err: any) {
|
|
29
|
+
return { success: false, error: err.message } satisfies ApiResponse;
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Heartbeat to keep instance alive
|
|
35
|
+
fastify.post<{ Params: { id: string } }>(
|
|
36
|
+
"/api/editor/:id/heartbeat",
|
|
37
|
+
{ preHandler: networkGuard },
|
|
38
|
+
async (request) => {
|
|
39
|
+
const { id } = request.params;
|
|
40
|
+
const inst = editorManager.get(id);
|
|
41
|
+
if (!inst) {
|
|
42
|
+
return { success: false, error: "instance not found" } satisfies ApiResponse;
|
|
43
|
+
}
|
|
44
|
+
editorManager.heartbeat(id);
|
|
45
|
+
return { success: true } satisfies ApiResponse;
|
|
46
|
+
},
|
|
47
|
+
);
|
|
48
|
+
|
|
49
|
+
// Stop an editor instance
|
|
50
|
+
fastify.post<{ Params: { id: string } }>(
|
|
51
|
+
"/api/editor/:id/stop",
|
|
52
|
+
{ preHandler: networkGuard },
|
|
53
|
+
async (request) => {
|
|
54
|
+
const { id } = request.params;
|
|
55
|
+
editorManager.stop(id);
|
|
56
|
+
return { success: true } satisfies ApiResponse;
|
|
57
|
+
},
|
|
58
|
+
);
|
|
59
|
+
|
|
60
|
+
// Update theme for a running editor instance
|
|
61
|
+
fastify.post<{ Params: { id: string }; Body: { theme?: "dark" | "light" } }>(
|
|
62
|
+
"/api/editor/:id/theme",
|
|
63
|
+
{ preHandler: networkGuard },
|
|
64
|
+
async (request) => {
|
|
65
|
+
const { id } = request.params;
|
|
66
|
+
const { theme } = request.body ?? {};
|
|
67
|
+
const inst = editorManager.get(id);
|
|
68
|
+
if (!inst) {
|
|
69
|
+
return { success: false, error: "instance not found" } satisfies ApiResponse;
|
|
70
|
+
}
|
|
71
|
+
if (theme) {
|
|
72
|
+
editorManager.setTheme(inst.cwd, theme);
|
|
73
|
+
}
|
|
74
|
+
return { success: true } satisfies ApiResponse;
|
|
75
|
+
},
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
// List all editor instances
|
|
79
|
+
fastify.get(
|
|
80
|
+
"/api/editor/status",
|
|
81
|
+
{ preHandler: networkGuard },
|
|
82
|
+
async () => {
|
|
83
|
+
return { success: true, data: editorManager.list() } satisfies ApiResponse;
|
|
84
|
+
},
|
|
85
|
+
);
|
|
86
|
+
}
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* File, directory browse, and README REST API routes (localhost-only).
|
|
3
|
+
*/
|
|
4
|
+
import type { FastifyInstance } from "fastify";
|
|
5
|
+
import type { SessionManager } from "../memory-session-manager.js";
|
|
6
|
+
import type { PreferencesStore } from "../preferences-store.js";
|
|
7
|
+
import type { ApiResponse } from "@blackbelt-technology/pi-dashboard-shared/types.js";
|
|
8
|
+
import type { NetworkGuard } from "./route-deps.js";
|
|
9
|
+
import { listDirectories } from "../browse.js";
|
|
10
|
+
import path from "node:path";
|
|
11
|
+
import fs from "node:fs/promises";
|
|
12
|
+
|
|
13
|
+
export function registerFileRoutes(
|
|
14
|
+
fastify: FastifyInstance,
|
|
15
|
+
deps: {
|
|
16
|
+
sessionManager: SessionManager;
|
|
17
|
+
preferencesStore: PreferencesStore;
|
|
18
|
+
networkGuard: NetworkGuard;
|
|
19
|
+
},
|
|
20
|
+
) {
|
|
21
|
+
const { sessionManager, preferencesStore, networkGuard } = deps;
|
|
22
|
+
|
|
23
|
+
// Directory browse endpoint
|
|
24
|
+
fastify.get<{ Querystring: { path?: string } }>(
|
|
25
|
+
"/api/browse",
|
|
26
|
+
{ preHandler: networkGuard },
|
|
27
|
+
async (request) => {
|
|
28
|
+
try {
|
|
29
|
+
const result = await listDirectories(request.query.path || undefined);
|
|
30
|
+
return { success: true, data: result } satisfies ApiResponse;
|
|
31
|
+
} catch {
|
|
32
|
+
return { success: false, error: "directory not found" } satisfies ApiResponse;
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// File read endpoint — read file content or list directory
|
|
38
|
+
fastify.get<{ Querystring: { cwd?: string; path?: string } }>(
|
|
39
|
+
"/api/file",
|
|
40
|
+
{ preHandler: networkGuard },
|
|
41
|
+
async (request, reply) => {
|
|
42
|
+
const cwd = request.query.cwd;
|
|
43
|
+
const relPath = request.query.path;
|
|
44
|
+
if (!cwd || !relPath) {
|
|
45
|
+
reply.code(400);
|
|
46
|
+
return { success: false, error: "cwd and path parameters required" } satisfies ApiResponse;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const allSessions = sessionManager.listAll();
|
|
50
|
+
if (!allSessions.some((s) => s.cwd === cwd)) {
|
|
51
|
+
reply.code(403);
|
|
52
|
+
return { success: false, error: "unknown session path" } satisfies ApiResponse;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const resolved = path.resolve(cwd, relPath);
|
|
56
|
+
if (!resolved.startsWith(cwd + path.sep) && resolved !== cwd) {
|
|
57
|
+
reply.code(403);
|
|
58
|
+
return { success: false, error: "path outside working directory" } satisfies ApiResponse;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
const stat = await fs.stat(resolved);
|
|
63
|
+
if (stat.isDirectory()) {
|
|
64
|
+
const entries = await fs.readdir(resolved);
|
|
65
|
+
entries.sort();
|
|
66
|
+
return { success: true, data: { type: "directory", entries } } satisfies ApiResponse;
|
|
67
|
+
}
|
|
68
|
+
const content = await fs.readFile(resolved, "utf-8");
|
|
69
|
+
return { success: true, data: { type: "file", content } } satisfies ApiResponse;
|
|
70
|
+
} catch {
|
|
71
|
+
reply.code(404);
|
|
72
|
+
return { success: false, error: "not found" } satisfies ApiResponse;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
);
|
|
76
|
+
|
|
77
|
+
// README endpoint — read README.md from a directory
|
|
78
|
+
fastify.get<{ Querystring: { cwd?: string; check?: string } }>(
|
|
79
|
+
"/api/readme",
|
|
80
|
+
{ preHandler: networkGuard },
|
|
81
|
+
async (request, reply) => {
|
|
82
|
+
const cwd = request.query.cwd;
|
|
83
|
+
if (!cwd) {
|
|
84
|
+
reply.code(400);
|
|
85
|
+
return { success: false, error: "cwd parameter required" } satisfies ApiResponse;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const allSessions = sessionManager.listAll();
|
|
89
|
+
const knownCwds = new Set(allSessions.map((s) => s.cwd));
|
|
90
|
+
for (const dir of preferencesStore.getPinnedDirectories()) knownCwds.add(dir);
|
|
91
|
+
|
|
92
|
+
if (!knownCwds.has(cwd)) {
|
|
93
|
+
reply.code(403);
|
|
94
|
+
return { success: false, error: "unknown directory" } satisfies ApiResponse;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const readmePath = path.join(cwd, "README.md");
|
|
98
|
+
try {
|
|
99
|
+
if (request.query.check) {
|
|
100
|
+
await fs.access(readmePath);
|
|
101
|
+
return { success: true, data: { exists: true } } satisfies ApiResponse;
|
|
102
|
+
}
|
|
103
|
+
const content = await fs.readFile(readmePath, "utf-8");
|
|
104
|
+
return { success: true, data: { content } } satisfies ApiResponse;
|
|
105
|
+
} catch {
|
|
106
|
+
reply.code(404);
|
|
107
|
+
return { success: false, error: "README.md not found" } satisfies ApiResponse;
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
// Pinned directories endpoint
|
|
113
|
+
fastify.get("/api/pinned-dirs", async () => {
|
|
114
|
+
return { success: true, data: preferencesStore.getPinnedDirectories() } satisfies ApiResponse;
|
|
115
|
+
});
|
|
116
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Git operation REST API routes (localhost-only).
|
|
3
|
+
*/
|
|
4
|
+
import type { FastifyInstance } from "fastify";
|
|
5
|
+
import type { ApiResponse } from "@blackbelt-technology/pi-dashboard-shared/types.js";
|
|
6
|
+
import type { NetworkGuard } from "./route-deps.js";
|
|
7
|
+
import { isGitRepo, listBranches, checkoutBranch, gitInit, stashPop } from "../git-operations.js";
|
|
8
|
+
|
|
9
|
+
export function registerGitRoutes(fastify: FastifyInstance, deps: { networkGuard: NetworkGuard }) {
|
|
10
|
+
const { networkGuard } = deps;
|
|
11
|
+
fastify.get<{ Querystring: { cwd?: string } }>(
|
|
12
|
+
"/api/git/branches",
|
|
13
|
+
{ preHandler: networkGuard },
|
|
14
|
+
async (request, reply) => {
|
|
15
|
+
const cwd = request.query.cwd;
|
|
16
|
+
if (!cwd) {
|
|
17
|
+
reply.code(400);
|
|
18
|
+
return { success: false, error: "cwd parameter required" } satisfies ApiResponse;
|
|
19
|
+
}
|
|
20
|
+
if (!isGitRepo(cwd)) {
|
|
21
|
+
return { success: false, error: "not a git repository" } satisfies ApiResponse;
|
|
22
|
+
}
|
|
23
|
+
try {
|
|
24
|
+
const data = listBranches(cwd);
|
|
25
|
+
return { success: true, data } satisfies ApiResponse;
|
|
26
|
+
} catch (err: any) {
|
|
27
|
+
return { success: false, error: err.message ?? "failed to list branches" } satisfies ApiResponse;
|
|
28
|
+
}
|
|
29
|
+
},
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
fastify.post<{ Body: { cwd?: string; branch?: string; stash?: boolean } }>(
|
|
33
|
+
"/api/git/checkout",
|
|
34
|
+
{ preHandler: networkGuard },
|
|
35
|
+
async (request, reply) => {
|
|
36
|
+
const { cwd, branch, stash } = request.body ?? {};
|
|
37
|
+
if (!cwd || !branch) {
|
|
38
|
+
reply.code(400);
|
|
39
|
+
return { success: false, error: "cwd and branch required" } satisfies ApiResponse;
|
|
40
|
+
}
|
|
41
|
+
try {
|
|
42
|
+
const result = checkoutBranch(cwd, branch, stash ?? false);
|
|
43
|
+
if (!result.success) {
|
|
44
|
+
reply.code(409);
|
|
45
|
+
return result;
|
|
46
|
+
}
|
|
47
|
+
return { success: true, data: { stashed: result.stashed } } satisfies ApiResponse;
|
|
48
|
+
} catch (err: any) {
|
|
49
|
+
return { success: false, error: err.message ?? "checkout failed" } satisfies ApiResponse;
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
fastify.post<{ Body: { cwd?: string } }>(
|
|
55
|
+
"/api/git/init",
|
|
56
|
+
{ preHandler: networkGuard },
|
|
57
|
+
async (request, reply) => {
|
|
58
|
+
const { cwd } = request.body ?? {};
|
|
59
|
+
if (!cwd) {
|
|
60
|
+
reply.code(400);
|
|
61
|
+
return { success: false, error: "cwd required" } satisfies ApiResponse;
|
|
62
|
+
}
|
|
63
|
+
try {
|
|
64
|
+
gitInit(cwd);
|
|
65
|
+
return { success: true } satisfies ApiResponse;
|
|
66
|
+
} catch (err: any) {
|
|
67
|
+
return { success: false, error: err.message ?? "init failed" } satisfies ApiResponse;
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
fastify.post<{ Body: { cwd?: string } }>(
|
|
73
|
+
"/api/git/stash-pop",
|
|
74
|
+
{ preHandler: networkGuard },
|
|
75
|
+
async (request, reply) => {
|
|
76
|
+
const { cwd } = request.body ?? {};
|
|
77
|
+
if (!cwd) {
|
|
78
|
+
reply.code(400);
|
|
79
|
+
return { success: false, error: "cwd required" } satisfies ApiResponse;
|
|
80
|
+
}
|
|
81
|
+
try {
|
|
82
|
+
const result = stashPop(cwd);
|
|
83
|
+
return { success: true, data: result } satisfies ApiResponse;
|
|
84
|
+
} catch (err: any) {
|
|
85
|
+
return { success: false, error: err.message ?? "stash pop failed" } satisfies ApiResponse;
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* OpenSpec and Pi Resources REST API routes (localhost-only).
|
|
3
|
+
*/
|
|
4
|
+
import type { FastifyInstance } from "fastify";
|
|
5
|
+
import type { SessionManager } from "../memory-session-manager.js";
|
|
6
|
+
import type { PreferencesStore } from "../preferences-store.js";
|
|
7
|
+
import type { DirectoryService } from "../directory-service.js";
|
|
8
|
+
import type { ApiResponse } from "@blackbelt-technology/pi-dashboard-shared/types.js";
|
|
9
|
+
import type { NetworkGuard } from "./route-deps.js";
|
|
10
|
+
import { scanOpenSpecArchive } from "../openspec-archive.js";
|
|
11
|
+
import path from "node:path";
|
|
12
|
+
import fs from "node:fs/promises";
|
|
13
|
+
|
|
14
|
+
export function registerOpenSpecRoutes(
|
|
15
|
+
fastify: FastifyInstance,
|
|
16
|
+
deps: {
|
|
17
|
+
sessionManager: SessionManager;
|
|
18
|
+
preferencesStore: PreferencesStore;
|
|
19
|
+
directoryService: DirectoryService;
|
|
20
|
+
networkGuard: NetworkGuard;
|
|
21
|
+
},
|
|
22
|
+
) {
|
|
23
|
+
const { sessionManager, preferencesStore, directoryService, networkGuard } = deps;
|
|
24
|
+
|
|
25
|
+
// OpenSpec archive listing endpoint
|
|
26
|
+
fastify.get<{ Querystring: { cwd?: string } }>(
|
|
27
|
+
"/api/openspec-archive",
|
|
28
|
+
{ preHandler: networkGuard },
|
|
29
|
+
async (request, reply) => {
|
|
30
|
+
const cwd = request.query.cwd;
|
|
31
|
+
if (!cwd) {
|
|
32
|
+
reply.code(400);
|
|
33
|
+
return { success: false, error: "Missing cwd" } satisfies ApiResponse;
|
|
34
|
+
}
|
|
35
|
+
const data = await scanOpenSpecArchive(cwd);
|
|
36
|
+
return { success: true, data } satisfies ApiResponse;
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
// Pi Resources endpoint — returns discovered extensions, skills, prompts
|
|
41
|
+
fastify.get<{ Querystring: { cwd?: string; refresh?: string } }>(
|
|
42
|
+
"/api/pi-resources",
|
|
43
|
+
{ preHandler: networkGuard },
|
|
44
|
+
async (request, reply) => {
|
|
45
|
+
const cwd = request.query.cwd;
|
|
46
|
+
if (!cwd) {
|
|
47
|
+
reply.code(400);
|
|
48
|
+
return { success: false, error: "cwd parameter required" } satisfies ApiResponse;
|
|
49
|
+
}
|
|
50
|
+
const forceRefresh = request.query.refresh === "true" || request.query.refresh === "1";
|
|
51
|
+
let data = forceRefresh ? undefined : directoryService.getPiResources(cwd);
|
|
52
|
+
if (!data) {
|
|
53
|
+
data = await directoryService.refreshPiResources(cwd);
|
|
54
|
+
}
|
|
55
|
+
return { success: true, data } satisfies ApiResponse;
|
|
56
|
+
},
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
// Pi Resource file endpoint — reads files from allowed pi resource locations
|
|
60
|
+
fastify.get<{ Querystring: { path?: string } }>(
|
|
61
|
+
"/api/pi-resource-file",
|
|
62
|
+
{ preHandler: networkGuard },
|
|
63
|
+
async (request, reply) => {
|
|
64
|
+
const filePath = request.query.path;
|
|
65
|
+
if (!filePath) {
|
|
66
|
+
reply.code(400);
|
|
67
|
+
return { success: false, error: "path parameter required" } satisfies ApiResponse;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const homeDir = process.env.HOME || process.env.USERPROFILE || "";
|
|
71
|
+
const globalPiDir = path.join(homeDir, ".pi", "agent");
|
|
72
|
+
const allSessions = sessionManager.listAll();
|
|
73
|
+
const knownCwds = new Set(allSessions.map((s) => s.cwd));
|
|
74
|
+
for (const dir of preferencesStore.getPinnedDirectories()) knownCwds.add(dir);
|
|
75
|
+
|
|
76
|
+
const normalizedPath = path.resolve(filePath);
|
|
77
|
+
const isAllowed =
|
|
78
|
+
normalizedPath.startsWith(globalPiDir + path.sep) ||
|
|
79
|
+
[...knownCwds].some(
|
|
80
|
+
(cwd) => normalizedPath.startsWith(path.join(cwd, ".pi") + path.sep),
|
|
81
|
+
) ||
|
|
82
|
+
normalizedPath.includes(path.join(".pi", "git") + path.sep) ||
|
|
83
|
+
normalizedPath.includes("node_modules" + path.sep);
|
|
84
|
+
|
|
85
|
+
if (!isAllowed) {
|
|
86
|
+
reply.code(403);
|
|
87
|
+
return { success: false, error: "path not in allowed resource location" } satisfies ApiResponse;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
try {
|
|
91
|
+
const content = await fs.readFile(normalizedPath, "utf-8");
|
|
92
|
+
return { success: true, data: { type: "file", content } } satisfies ApiResponse;
|
|
93
|
+
} catch {
|
|
94
|
+
reply.code(404);
|
|
95
|
+
return { success: false, error: "not found" } satisfies ApiResponse;
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
);
|
|
99
|
+
}
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* REST routes for pi package management: search, readme, installed, install, remove, update, check-updates.
|
|
3
|
+
*/
|
|
4
|
+
import type { FastifyInstance } from "fastify";
|
|
5
|
+
import type { ApiResponse } from "@blackbelt-technology/pi-dashboard-shared/types.js";
|
|
6
|
+
import type { PackageManagerWrapper } from "../package-manager-wrapper.js";
|
|
7
|
+
import { PackageOperationBusyError } from "../package-manager-wrapper.js";
|
|
8
|
+
import { searchPackages, fetchReadme, PackageNotFoundError } from "../npm-search-proxy.js";
|
|
9
|
+
|
|
10
|
+
export function registerPackageRoutes(
|
|
11
|
+
fastify: FastifyInstance,
|
|
12
|
+
deps: {
|
|
13
|
+
packageManagerWrapper: PackageManagerWrapper;
|
|
14
|
+
},
|
|
15
|
+
) {
|
|
16
|
+
const { packageManagerWrapper } = deps;
|
|
17
|
+
|
|
18
|
+
// ── Search npm packages ─────────────────────────────────────────
|
|
19
|
+
|
|
20
|
+
fastify.get<{ Querystring: { q?: string; type?: string } }>(
|
|
21
|
+
"/api/packages/search",
|
|
22
|
+
async (request) => {
|
|
23
|
+
const { q, type } = request.query;
|
|
24
|
+
try {
|
|
25
|
+
const result = await searchPackages({ query: q, type });
|
|
26
|
+
return { success: true, data: result } satisfies ApiResponse;
|
|
27
|
+
} catch (err: any) {
|
|
28
|
+
return { success: false, error: err.message } satisfies ApiResponse;
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
// ── Fetch package README ────────────────────────────────────────
|
|
34
|
+
|
|
35
|
+
fastify.get<{ Querystring: { pkg: string } }>(
|
|
36
|
+
"/api/packages/readme",
|
|
37
|
+
async (request, reply) => {
|
|
38
|
+
const { pkg } = request.query;
|
|
39
|
+
if (!pkg) {
|
|
40
|
+
reply.code(400);
|
|
41
|
+
return { success: false, error: "pkg parameter required" } satisfies ApiResponse;
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
const result = await fetchReadme(pkg);
|
|
45
|
+
return { success: true, data: result } satisfies ApiResponse;
|
|
46
|
+
} catch (err: any) {
|
|
47
|
+
if (err instanceof PackageNotFoundError) {
|
|
48
|
+
reply.code(404);
|
|
49
|
+
return { success: false, error: err.message } satisfies ApiResponse;
|
|
50
|
+
}
|
|
51
|
+
return { success: false, error: err.message } satisfies ApiResponse;
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
// ── List installed packages ─────────────────────────────────────
|
|
57
|
+
|
|
58
|
+
fastify.get<{ Querystring: { scope?: string; cwd?: string } }>(
|
|
59
|
+
"/api/packages/installed",
|
|
60
|
+
async (request) => {
|
|
61
|
+
const scope = request.query.scope === "local" ? "local" : "global";
|
|
62
|
+
const cwd = request.query.cwd;
|
|
63
|
+
try {
|
|
64
|
+
const packages = await packageManagerWrapper.listInstalled(scope, cwd);
|
|
65
|
+
return { success: true, data: packages } satisfies ApiResponse;
|
|
66
|
+
} catch (err: any) {
|
|
67
|
+
return { success: false, error: err.message } satisfies ApiResponse;
|
|
68
|
+
}
|
|
69
|
+
},
|
|
70
|
+
);
|
|
71
|
+
|
|
72
|
+
// ── Install package ─────────────────────────────────────────────
|
|
73
|
+
|
|
74
|
+
fastify.post<{ Body: { source: string; scope: string; cwd?: string } }>(
|
|
75
|
+
"/api/packages/install",
|
|
76
|
+
async (request, reply) => {
|
|
77
|
+
const { source, scope, cwd } = request.body ?? {};
|
|
78
|
+
if (!source) {
|
|
79
|
+
reply.code(400);
|
|
80
|
+
return { success: false, error: "source is required" } satisfies ApiResponse;
|
|
81
|
+
}
|
|
82
|
+
const effectiveScope = scope === "local" ? "local" as const : "global" as const;
|
|
83
|
+
try {
|
|
84
|
+
const operationId = await packageManagerWrapper.run({
|
|
85
|
+
action: "install",
|
|
86
|
+
source,
|
|
87
|
+
scope: effectiveScope,
|
|
88
|
+
cwd,
|
|
89
|
+
});
|
|
90
|
+
reply.code(202);
|
|
91
|
+
return { success: true, data: { operationId } } satisfies ApiResponse;
|
|
92
|
+
} catch (err: any) {
|
|
93
|
+
if (err instanceof PackageOperationBusyError) {
|
|
94
|
+
reply.code(409);
|
|
95
|
+
return { success: false, error: err.message } satisfies ApiResponse;
|
|
96
|
+
}
|
|
97
|
+
return { success: false, error: err.message } satisfies ApiResponse;
|
|
98
|
+
}
|
|
99
|
+
},
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// ── Remove package ──────────────────────────────────────────────
|
|
103
|
+
|
|
104
|
+
fastify.post<{ Body: { source: string; scope: string; cwd?: string } }>(
|
|
105
|
+
"/api/packages/remove",
|
|
106
|
+
async (request, reply) => {
|
|
107
|
+
const { source, scope, cwd } = request.body ?? {};
|
|
108
|
+
if (!source) {
|
|
109
|
+
reply.code(400);
|
|
110
|
+
return { success: false, error: "source is required" } satisfies ApiResponse;
|
|
111
|
+
}
|
|
112
|
+
const effectiveScope = scope === "local" ? "local" as const : "global" as const;
|
|
113
|
+
try {
|
|
114
|
+
const operationId = await packageManagerWrapper.run({
|
|
115
|
+
action: "remove",
|
|
116
|
+
source,
|
|
117
|
+
scope: effectiveScope,
|
|
118
|
+
cwd,
|
|
119
|
+
});
|
|
120
|
+
reply.code(202);
|
|
121
|
+
return { success: true, data: { operationId } } satisfies ApiResponse;
|
|
122
|
+
} catch (err: any) {
|
|
123
|
+
if (err instanceof PackageOperationBusyError) {
|
|
124
|
+
reply.code(409);
|
|
125
|
+
return { success: false, error: err.message } satisfies ApiResponse;
|
|
126
|
+
}
|
|
127
|
+
return { success: false, error: err.message } satisfies ApiResponse;
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
);
|
|
131
|
+
|
|
132
|
+
// ── Update packages ─────────────────────────────────────────────
|
|
133
|
+
|
|
134
|
+
fastify.post<{ Body: { source?: string; scope: string; cwd?: string } }>(
|
|
135
|
+
"/api/packages/update",
|
|
136
|
+
async (request, reply) => {
|
|
137
|
+
const { source, scope, cwd } = request.body ?? {};
|
|
138
|
+
const effectiveScope = scope === "local" ? "local" as const : "global" as const;
|
|
139
|
+
try {
|
|
140
|
+
const operationId = await packageManagerWrapper.run({
|
|
141
|
+
action: "update",
|
|
142
|
+
source: source ?? "",
|
|
143
|
+
scope: effectiveScope,
|
|
144
|
+
cwd,
|
|
145
|
+
});
|
|
146
|
+
reply.code(202);
|
|
147
|
+
return { success: true, data: { operationId } } satisfies ApiResponse;
|
|
148
|
+
} catch (err: any) {
|
|
149
|
+
if (err instanceof PackageOperationBusyError) {
|
|
150
|
+
reply.code(409);
|
|
151
|
+
return { success: false, error: err.message } satisfies ApiResponse;
|
|
152
|
+
}
|
|
153
|
+
return { success: false, error: err.message } satisfies ApiResponse;
|
|
154
|
+
}
|
|
155
|
+
},
|
|
156
|
+
);
|
|
157
|
+
|
|
158
|
+
// ── Check for updates ───────────────────────────────────────────
|
|
159
|
+
|
|
160
|
+
fastify.post<{ Body: { cwd?: string } }>(
|
|
161
|
+
"/api/packages/check-updates",
|
|
162
|
+
async (request) => {
|
|
163
|
+
const { cwd } = request.body ?? {};
|
|
164
|
+
try {
|
|
165
|
+
const updates = await packageManagerWrapper.checkUpdates(cwd);
|
|
166
|
+
return { success: true, data: updates } satisfies ApiResponse;
|
|
167
|
+
} catch (err: any) {
|
|
168
|
+
return { success: false, error: err.message } satisfies ApiResponse;
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
);
|
|
172
|
+
}
|