studiograph 1.0.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.
Files changed (101) hide show
  1. package/README.md +18 -0
  2. package/dist/agent/orchestrator.d.ts +69 -0
  3. package/dist/agent/orchestrator.js +211 -0
  4. package/dist/agent/orchestrator.js.map +1 -0
  5. package/dist/agent/tools/graph-tools.d.ts +30 -0
  6. package/dist/agent/tools/graph-tools.js +536 -0
  7. package/dist/agent/tools/graph-tools.js.map +1 -0
  8. package/dist/auth/github.d.ts +53 -0
  9. package/dist/auth/github.js +180 -0
  10. package/dist/auth/github.js.map +1 -0
  11. package/dist/cli/commands/auth.d.ts +10 -0
  12. package/dist/cli/commands/auth.js +63 -0
  13. package/dist/cli/commands/auth.js.map +1 -0
  14. package/dist/cli/commands/init.d.ts +7 -0
  15. package/dist/cli/commands/init.js +299 -0
  16. package/dist/cli/commands/init.js.map +1 -0
  17. package/dist/cli/commands/join.d.ts +14 -0
  18. package/dist/cli/commands/join.js +230 -0
  19. package/dist/cli/commands/join.js.map +1 -0
  20. package/dist/cli/commands/members.d.ts +11 -0
  21. package/dist/cli/commands/members.js +230 -0
  22. package/dist/cli/commands/members.js.map +1 -0
  23. package/dist/cli/commands/serve.d.ts +17 -0
  24. package/dist/cli/commands/serve.js +90 -0
  25. package/dist/cli/commands/serve.js.map +1 -0
  26. package/dist/cli/commands/start.d.ts +7 -0
  27. package/dist/cli/commands/start.js +381 -0
  28. package/dist/cli/commands/start.js.map +1 -0
  29. package/dist/cli/commands/sync.d.ts +10 -0
  30. package/dist/cli/commands/sync.js +121 -0
  31. package/dist/cli/commands/sync.js.map +1 -0
  32. package/dist/cli/index.d.ts +7 -0
  33. package/dist/cli/index.js +31 -0
  34. package/dist/cli/index.js.map +1 -0
  35. package/dist/core/graph.d.ts +169 -0
  36. package/dist/core/graph.js +558 -0
  37. package/dist/core/graph.js.map +1 -0
  38. package/dist/core/types.d.ts +216 -0
  39. package/dist/core/types.js +71 -0
  40. package/dist/core/types.js.map +1 -0
  41. package/dist/core/user-config.d.ts +31 -0
  42. package/dist/core/user-config.js +50 -0
  43. package/dist/core/user-config.js.map +1 -0
  44. package/dist/core/validation.d.ts +2371 -0
  45. package/dist/core/validation.js +432 -0
  46. package/dist/core/validation.js.map +1 -0
  47. package/dist/core/workspace-manager.d.ts +104 -0
  48. package/dist/core/workspace-manager.js +432 -0
  49. package/dist/core/workspace-manager.js.map +1 -0
  50. package/dist/core/workspace.d.ts +103 -0
  51. package/dist/core/workspace.js +306 -0
  52. package/dist/core/workspace.js.map +1 -0
  53. package/dist/server/index.d.ts +25 -0
  54. package/dist/server/index.js +84 -0
  55. package/dist/server/index.js.map +1 -0
  56. package/dist/server/plugin-loader.d.ts +31 -0
  57. package/dist/server/plugin-loader.js +81 -0
  58. package/dist/server/plugin-loader.js.map +1 -0
  59. package/dist/server/routes/chat.d.ts +11 -0
  60. package/dist/server/routes/chat.js +66 -0
  61. package/dist/server/routes/chat.js.map +1 -0
  62. package/dist/server/routes/graph-api.d.ts +9 -0
  63. package/dist/server/routes/graph-api.js +72 -0
  64. package/dist/server/routes/graph-api.js.map +1 -0
  65. package/dist/server/routes/webhook.d.ts +14 -0
  66. package/dist/server/routes/webhook.js +69 -0
  67. package/dist/server/routes/webhook.js.map +1 -0
  68. package/dist/services/assets/base.d.ts +69 -0
  69. package/dist/services/assets/base.js +113 -0
  70. package/dist/services/assets/base.js.map +1 -0
  71. package/dist/services/assets/index.d.ts +36 -0
  72. package/dist/services/assets/index.js +89 -0
  73. package/dist/services/assets/index.js.map +1 -0
  74. package/dist/services/assets/local.d.ts +42 -0
  75. package/dist/services/assets/local.js +161 -0
  76. package/dist/services/assets/local.js.map +1 -0
  77. package/dist/services/assets/r2.d.ts +36 -0
  78. package/dist/services/assets/r2.js +182 -0
  79. package/dist/services/assets/r2.js.map +1 -0
  80. package/dist/services/csv-service.d.ts +36 -0
  81. package/dist/services/csv-service.js +143 -0
  82. package/dist/services/csv-service.js.map +1 -0
  83. package/dist/services/git.d.ts +99 -0
  84. package/dist/services/git.js +306 -0
  85. package/dist/services/git.js.map +1 -0
  86. package/dist/services/github-provisioner.d.ts +30 -0
  87. package/dist/services/github-provisioner.js +89 -0
  88. package/dist/services/github-provisioner.js.map +1 -0
  89. package/dist/services/markdown.d.ts +82 -0
  90. package/dist/services/markdown.js +338 -0
  91. package/dist/services/markdown.js.map +1 -0
  92. package/dist/services/memory-service.d.ts +74 -0
  93. package/dist/services/memory-service.js +183 -0
  94. package/dist/services/memory-service.js.map +1 -0
  95. package/dist/utils/git.d.ts +28 -0
  96. package/dist/utils/git.js +55 -0
  97. package/dist/utils/git.js.map +1 -0
  98. package/dist/utils/preflight.d.ts +44 -0
  99. package/dist/utils/preflight.js +95 -0
  100. package/dist/utils/preflight.js.map +1 -0
  101. package/package.json +55 -0
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Graph API routes
3
+ *
4
+ * Read-only REST façade over WorkspaceManager.
5
+ * All writes go through the agent (/api/chat).
6
+ */
7
+ export async function registerGraphApiRoutes(fastify, workspaceManager) {
8
+ // GET /api/repos — list all accessible repos
9
+ fastify.get('/api/repos', async (_req, reply) => {
10
+ const repos = workspaceManager.getAllRepoConfigs();
11
+ return reply.send(repos);
12
+ });
13
+ // GET /api/repos/:repo/entities/:type — list entities of a type
14
+ fastify.get('/api/repos/:repo/entities/:type', async (req, reply) => {
15
+ const { repo, type } = req.params;
16
+ let graph;
17
+ try {
18
+ graph = workspaceManager.getGraph(repo);
19
+ }
20
+ catch (err) {
21
+ return reply.status(404).send({ error: err.message });
22
+ }
23
+ const entities = graph.search({ entityType: type });
24
+ return reply.send(entities);
25
+ });
26
+ // GET /api/repos/:repo/entities/:type/:id — get single entity
27
+ fastify.get('/api/repos/:repo/entities/:type/:id', async (req, reply) => {
28
+ const { repo, type, id } = req.params;
29
+ let graph;
30
+ try {
31
+ graph = workspaceManager.getGraph(repo);
32
+ }
33
+ catch (err) {
34
+ return reply.status(404).send({ error: err.message });
35
+ }
36
+ const entity = graph.get(type, id);
37
+ if (!entity) {
38
+ return reply.status(404).send({ error: `Entity not found: ${type}/${id}` });
39
+ }
40
+ return reply.send(entity);
41
+ });
42
+ // GET /api/repos/:repo/search?q=&type=&tags= — search within a repo
43
+ fastify.get('/api/repos/:repo/search', async (req, reply) => {
44
+ const { repo } = req.params;
45
+ const { q, type, tags } = req.query;
46
+ let graph;
47
+ try {
48
+ graph = workspaceManager.getGraph(repo);
49
+ }
50
+ catch (err) {
51
+ return reply.status(404).send({ error: err.message });
52
+ }
53
+ const results = graph.search({
54
+ query: q,
55
+ entityType: type,
56
+ tags: tags ? tags.split(',').map(t => t.trim()) : undefined,
57
+ });
58
+ return reply.send(results);
59
+ });
60
+ // GET /api/search?q=&type=&tags=&repos= — cross-repo search
61
+ fastify.get('/api/search', async (req, reply) => {
62
+ const { q, type, tags, repos } = req.query;
63
+ const results = workspaceManager.search({
64
+ query: q,
65
+ entityType: type,
66
+ tags: tags ? tags.split(',').map(t => t.trim()) : undefined,
67
+ repoNames: repos ? repos.split(',').map(r => r.trim()) : undefined,
68
+ });
69
+ return reply.send(results);
70
+ });
71
+ }
72
+ //# sourceMappingURL=graph-api.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"graph-api.js","sourceRoot":"","sources":["../../../src/server/routes/graph-api.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ /**
2
+ * GitHub webhook route
3
+ *
4
+ * For Config 2 deployments (Railway + GitHub).
5
+ * Validates HMAC-SHA256 signature, identifies the pushed repo,
6
+ * runs `git pull` on the matching local clone.
7
+ *
8
+ * Setup: add a webhook in GitHub repo settings pointing to
9
+ * POST https://<your-host>/webhooks/github
10
+ * with secret matching WEBHOOK_SECRET env var.
11
+ */
12
+ import type { FastifyInstance } from 'fastify';
13
+ import { WorkspaceManager } from '../../core/workspace-manager.js';
14
+ export declare function registerWebhookRoutes(fastify: FastifyInstance, workspaceManager: WorkspaceManager, webhookSecret?: string): Promise<void>;
@@ -0,0 +1,69 @@
1
+ /**
2
+ * GitHub webhook route
3
+ *
4
+ * For Config 2 deployments (Railway + GitHub).
5
+ * Validates HMAC-SHA256 signature, identifies the pushed repo,
6
+ * runs `git pull` on the matching local clone.
7
+ *
8
+ * Setup: add a webhook in GitHub repo settings pointing to
9
+ * POST https://<your-host>/webhooks/github
10
+ * with secret matching WEBHOOK_SECRET env var.
11
+ */
12
+ import { createHmac, timingSafeEqual } from 'crypto';
13
+ import { execSync } from 'child_process';
14
+ export async function registerWebhookRoutes(fastify, workspaceManager, webhookSecret) {
15
+ fastify.post('/webhooks/github', { config: { rawBody: true } }, async (req, reply) => {
16
+ // Validate signature if secret is configured
17
+ if (webhookSecret) {
18
+ const signature = req.headers['x-hub-signature-256'];
19
+ if (!signature) {
20
+ return reply.status(401).send({ error: 'Missing X-Hub-Signature-256 header' });
21
+ }
22
+ const rawBody = req.rawBody ?? Buffer.from(JSON.stringify(req.body));
23
+ const expected = `sha256=${createHmac('sha256', webhookSecret).update(rawBody).digest('hex')}`;
24
+ let valid = false;
25
+ try {
26
+ valid = timingSafeEqual(Buffer.from(signature), Buffer.from(expected));
27
+ }
28
+ catch {
29
+ valid = false;
30
+ }
31
+ if (!valid) {
32
+ return reply.status(401).send({ error: 'Invalid webhook signature' });
33
+ }
34
+ }
35
+ const event = req.headers['x-github-event'];
36
+ // Only process push events
37
+ if (event !== 'push') {
38
+ return reply.send({ ok: true, skipped: true, reason: `event ${event} ignored` });
39
+ }
40
+ const payload = req.body;
41
+ const repoName = payload?.repository?.name;
42
+ if (!repoName) {
43
+ return reply.status(400).send({ error: 'Cannot determine repository name from payload' });
44
+ }
45
+ // Find matching repo config
46
+ const repoConfig = workspaceManager.getRepoConfig(repoName);
47
+ if (!repoConfig) {
48
+ fastify.log.warn(`Webhook: no repo config found for "${repoName}"`);
49
+ return reply.send({ ok: true, skipped: true, reason: `repo ${repoName} not in workspace` });
50
+ }
51
+ try {
52
+ // Get the graph's underlying repo path
53
+ const graph = workspaceManager.getGraph(repoName);
54
+ const repoPath = graph.repoPath;
55
+ execSync('git pull --ff-only', {
56
+ cwd: repoPath,
57
+ stdio: 'pipe',
58
+ timeout: 30_000,
59
+ });
60
+ fastify.log.info(`Webhook: git pull completed for repo "${repoName}"`);
61
+ return reply.send({ ok: true, repo: repoName });
62
+ }
63
+ catch (err) {
64
+ fastify.log.error(err, `Webhook: git pull failed for repo "${repoName}"`);
65
+ return reply.status(500).send({ error: `git pull failed: ${err.message}` });
66
+ }
67
+ });
68
+ }
69
+ //# sourceMappingURL=webhook.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"webhook.js","sourceRoot":"","sources":["../../../src/server/routes/webhook.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,69 @@
1
+ /**
2
+ * Base types and interfaces for asset management
3
+ */
4
+ export interface AssetInfo {
5
+ filename: string;
6
+ size: number;
7
+ contentType: string;
8
+ url: string;
9
+ path: string;
10
+ mediaType: 'image' | 'video' | 'audio' | 'document' | 'other';
11
+ }
12
+ export interface AssetBackend {
13
+ /**
14
+ * Upload asset to storage
15
+ */
16
+ upload(key: string, buffer: Buffer, contentType: string): Promise<AssetInfo>;
17
+ /**
18
+ * List assets with a given prefix
19
+ */
20
+ list(prefix: string): Promise<AssetInfo[]>;
21
+ /**
22
+ * Delete a single asset
23
+ */
24
+ delete(key: string): Promise<void>;
25
+ /**
26
+ * Delete all assets with a given prefix
27
+ */
28
+ deleteAll(prefix: string): Promise<number>;
29
+ /**
30
+ * Check if an asset exists
31
+ */
32
+ exists(key: string): Promise<boolean>;
33
+ }
34
+ export interface AssetConfig {
35
+ storage: 'local' | 'r2';
36
+ localPath?: string;
37
+ r2Config?: R2Config;
38
+ }
39
+ export interface R2Config {
40
+ accountId: string;
41
+ bucket: string;
42
+ publicUrl: string;
43
+ accessKeyId: string;
44
+ secretAccessKey: string;
45
+ }
46
+ /**
47
+ * Content type mappings
48
+ */
49
+ export declare const CONTENT_TYPES: Record<string, string>;
50
+ /**
51
+ * Detect content type from filename
52
+ */
53
+ export declare function detectContentType(filename: string): string;
54
+ /**
55
+ * Determine media type from content type
56
+ */
57
+ export declare function getMediaType(contentType: string): AssetInfo['mediaType'];
58
+ /**
59
+ * Validate filename for security
60
+ */
61
+ export declare function validateFilename(filename: string): void;
62
+ /**
63
+ * Build asset key from components
64
+ */
65
+ export declare function buildAssetKey(repo: string, entityType: string, entityId: string, filename: string): string;
66
+ /**
67
+ * Build asset prefix for listing
68
+ */
69
+ export declare function buildAssetPrefix(repo: string, entityType: string, entityId: string): string;
@@ -0,0 +1,113 @@
1
+ /**
2
+ * Base types and interfaces for asset management
3
+ */
4
+ /**
5
+ * Content type mappings
6
+ */
7
+ export const CONTENT_TYPES = {
8
+ // Images
9
+ png: 'image/png',
10
+ jpg: 'image/jpeg',
11
+ jpeg: 'image/jpeg',
12
+ gif: 'image/gif',
13
+ webp: 'image/webp',
14
+ svg: 'image/svg+xml',
15
+ ico: 'image/x-icon',
16
+ // Videos
17
+ mp4: 'video/mp4',
18
+ mov: 'video/quicktime',
19
+ webm: 'video/webm',
20
+ avi: 'video/x-msvideo',
21
+ // Audio
22
+ mp3: 'audio/mpeg',
23
+ wav: 'audio/wav',
24
+ ogg: 'audio/ogg',
25
+ m4a: 'audio/mp4',
26
+ // Documents
27
+ pdf: 'application/pdf',
28
+ doc: 'application/msword',
29
+ docx: 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
30
+ xls: 'application/vnd.ms-excel',
31
+ xlsx: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
32
+ ppt: 'application/vnd.ms-powerpoint',
33
+ pptx: 'application/vnd.openxmlformats-officedocument.presentationml.presentation',
34
+ // Archives
35
+ zip: 'application/zip',
36
+ tar: 'application/x-tar',
37
+ gz: 'application/gzip',
38
+ // Design files
39
+ fig: 'application/octet-stream',
40
+ sketch: 'application/octet-stream',
41
+ // Text
42
+ txt: 'text/plain',
43
+ json: 'application/json',
44
+ xml: 'application/xml',
45
+ csv: 'text/csv',
46
+ };
47
+ /**
48
+ * Detect content type from filename
49
+ */
50
+ export function detectContentType(filename) {
51
+ const ext = filename.split('.').pop()?.toLowerCase() || '';
52
+ return CONTENT_TYPES[ext] || 'application/octet-stream';
53
+ }
54
+ /**
55
+ * Determine media type from content type
56
+ */
57
+ export function getMediaType(contentType) {
58
+ if (contentType.startsWith('image/'))
59
+ return 'image';
60
+ if (contentType.startsWith('video/'))
61
+ return 'video';
62
+ if (contentType.startsWith('audio/'))
63
+ return 'audio';
64
+ if (contentType.includes('pdf') ||
65
+ contentType.includes('document') ||
66
+ contentType.includes('word') ||
67
+ contentType.includes('excel') ||
68
+ contentType.includes('powerpoint') ||
69
+ contentType.includes('presentation')) {
70
+ return 'document';
71
+ }
72
+ return 'other';
73
+ }
74
+ /**
75
+ * Validate filename for security
76
+ */
77
+ export function validateFilename(filename) {
78
+ if (!filename || filename.trim() === '') {
79
+ throw new Error('Filename cannot be empty');
80
+ }
81
+ // Check for path traversal
82
+ if (filename.includes('..') ||
83
+ filename.includes('/') ||
84
+ filename.includes('\\')) {
85
+ throw new Error('Filename cannot contain path separators or traversal sequences');
86
+ }
87
+ // Check for hidden files
88
+ if (filename.startsWith('.')) {
89
+ throw new Error('Filename cannot start with a dot (hidden files not allowed)');
90
+ }
91
+ // Check for forbidden characters
92
+ const forbiddenChars = /[<>:"|?*\x00-\x1f]/;
93
+ if (forbiddenChars.test(filename)) {
94
+ throw new Error('Filename contains forbidden characters');
95
+ }
96
+ // Check length
97
+ if (filename.length > 255) {
98
+ throw new Error('Filename too long (max 255 characters)');
99
+ }
100
+ }
101
+ /**
102
+ * Build asset key from components
103
+ */
104
+ export function buildAssetKey(repo, entityType, entityId, filename) {
105
+ return `${repo}/${entityType}/${entityId}/${filename}`;
106
+ }
107
+ /**
108
+ * Build asset prefix for listing
109
+ */
110
+ export function buildAssetPrefix(repo, entityType, entityId) {
111
+ return `${repo}/${entityType}/${entityId}/`;
112
+ }
113
+ //# sourceMappingURL=base.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"base.js","sourceRoot":"","sources":["../../../src/services/assets/base.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Asset service for Studiograph
3
+ *
4
+ * Manages all media assets (images, videos, audio, documents)
5
+ * with pluggable storage backends (local filesystem or R2)
6
+ */
7
+ import type { AssetInfo, AssetConfig } from './base.js';
8
+ export * from './base.js';
9
+ export declare class AssetService {
10
+ private backend;
11
+ constructor(config: AssetConfig);
12
+ /**
13
+ * Upload asset for an entity
14
+ */
15
+ upload(repo: string, entityType: string, entityId: string, filename: string, buffer: Buffer): Promise<AssetInfo>;
16
+ /**
17
+ * List all assets for an entity
18
+ */
19
+ list(repo: string, entityType: string, entityId: string): Promise<AssetInfo[]>;
20
+ /**
21
+ * Delete a single asset
22
+ */
23
+ delete(repo: string, entityType: string, entityId: string, filename: string): Promise<void>;
24
+ /**
25
+ * Delete all assets for an entity (when entity is deleted)
26
+ */
27
+ deleteAll(repo: string, entityType: string, entityId: string): Promise<number>;
28
+ /**
29
+ * Check if an asset exists
30
+ */
31
+ exists(repo: string, entityType: string, entityId: string, filename: string): Promise<boolean>;
32
+ /**
33
+ * Get asset info without downloading
34
+ */
35
+ getInfo(repo: string, entityType: string, entityId: string, filename: string): Promise<AssetInfo | null>;
36
+ }
@@ -0,0 +1,89 @@
1
+ /**
2
+ * Asset service for Studiograph
3
+ *
4
+ * Manages all media assets (images, videos, audio, documents)
5
+ * with pluggable storage backends (local filesystem or R2)
6
+ */
7
+ import { validateFilename, detectContentType, buildAssetKey, buildAssetPrefix, } from './base.js';
8
+ import { LocalAssetBackend } from './local.js';
9
+ import { R2AssetBackend } from './r2.js';
10
+ export * from './base.js';
11
+ export class AssetService {
12
+ backend;
13
+ constructor(config) {
14
+ // Initialize appropriate backend
15
+ if (config.storage === 'local') {
16
+ const localPath = config.localPath || '.studiograph/assets';
17
+ this.backend = new LocalAssetBackend(localPath);
18
+ }
19
+ else if (config.storage === 'r2') {
20
+ if (!config.r2Config) {
21
+ throw new Error('R2 config is required when storage is set to r2');
22
+ }
23
+ this.backend = new R2AssetBackend(config.r2Config);
24
+ }
25
+ else {
26
+ throw new Error(`Unknown storage backend: ${config.storage}`);
27
+ }
28
+ }
29
+ /**
30
+ * Upload asset for an entity
31
+ */
32
+ async upload(repo, entityType, entityId, filename, buffer) {
33
+ // Validate filename
34
+ validateFilename(filename);
35
+ // Validate buffer
36
+ if (!buffer || buffer.length === 0) {
37
+ throw new Error('Buffer cannot be empty');
38
+ }
39
+ // Check file size (max 100MB)
40
+ const maxSize = 100 * 1024 * 1024; // 100MB
41
+ if (buffer.length > maxSize) {
42
+ throw new Error(`File too large (max ${maxSize / 1024 / 1024}MB)`);
43
+ }
44
+ // Detect content type
45
+ const contentType = detectContentType(filename);
46
+ // Build storage key
47
+ const key = buildAssetKey(repo, entityType, entityId, filename);
48
+ // Upload via backend
49
+ return await this.backend.upload(key, buffer, contentType);
50
+ }
51
+ /**
52
+ * List all assets for an entity
53
+ */
54
+ async list(repo, entityType, entityId) {
55
+ const prefix = buildAssetPrefix(repo, entityType, entityId);
56
+ return await this.backend.list(prefix);
57
+ }
58
+ /**
59
+ * Delete a single asset
60
+ */
61
+ async delete(repo, entityType, entityId, filename) {
62
+ validateFilename(filename);
63
+ const key = buildAssetKey(repo, entityType, entityId, filename);
64
+ return await this.backend.delete(key);
65
+ }
66
+ /**
67
+ * Delete all assets for an entity (when entity is deleted)
68
+ */
69
+ async deleteAll(repo, entityType, entityId) {
70
+ const prefix = buildAssetPrefix(repo, entityType, entityId);
71
+ return await this.backend.deleteAll(prefix);
72
+ }
73
+ /**
74
+ * Check if an asset exists
75
+ */
76
+ async exists(repo, entityType, entityId, filename) {
77
+ validateFilename(filename);
78
+ const key = buildAssetKey(repo, entityType, entityId, filename);
79
+ return await this.backend.exists(key);
80
+ }
81
+ /**
82
+ * Get asset info without downloading
83
+ */
84
+ async getInfo(repo, entityType, entityId, filename) {
85
+ const assets = await this.list(repo, entityType, entityId);
86
+ return assets.find(a => a.filename === filename) || null;
87
+ }
88
+ }
89
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/services/assets/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Local filesystem asset backend
3
+ *
4
+ * Stores assets in .studiograph/assets/ directory
5
+ */
6
+ import type { AssetBackend, AssetInfo } from './base.js';
7
+ export declare class LocalAssetBackend implements AssetBackend {
8
+ private basePath;
9
+ constructor(basePath: string);
10
+ /**
11
+ * Ensure base directory exists
12
+ */
13
+ private ensureBaseDirectory;
14
+ /**
15
+ * Get full file path for a key
16
+ */
17
+ private getFilePath;
18
+ /**
19
+ * Upload asset to local filesystem
20
+ */
21
+ upload(key: string, buffer: Buffer, contentType: string): Promise<AssetInfo>;
22
+ /**
23
+ * List assets with a given prefix
24
+ */
25
+ list(prefix: string): Promise<AssetInfo[]>;
26
+ /**
27
+ * Delete a single asset
28
+ */
29
+ delete(key: string): Promise<void>;
30
+ /**
31
+ * Delete all assets with a given prefix
32
+ */
33
+ deleteAll(prefix: string): Promise<number>;
34
+ /**
35
+ * Check if an asset exists
36
+ */
37
+ exists(key: string): Promise<boolean>;
38
+ /**
39
+ * Guess content type from file extension
40
+ */
41
+ private guessContentType;
42
+ }