claude-ws 0.4.9-beta.5 → 0.4.9-beta.6
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/package.json +1 -1
- package/src/app/api/sync/minio/pull/route.ts +1 -1
- package/src/app/api/sync/minio/push/route.ts +1 -1
- package/src/hooks/template/hooks/.env.example +8 -7
- package/src/hooks/template/hooks/minio-pull-sync.ts +7 -8
- package/src/lib/api-hook-url.ts +28 -18
- package/src/lib/minio-pull-queue.ts +11 -13
- package/src/lib/minio-push-queue.ts +18 -10
- package/src/lib/project-utils.ts +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-ws",
|
|
3
|
-
"version": "0.4.9-beta.
|
|
3
|
+
"version": "0.4.9-beta.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "AI-powered workspace for solo CEOs and indie builders — manage your entire business with AI agents, not just code. Kanban board, code editor, Git integration, claw agent hub, local-first SQLite.",
|
|
6
6
|
"keywords": [
|
|
@@ -98,7 +98,7 @@ export async function POST(request: NextRequest) {
|
|
|
98
98
|
return NextResponse.json({ error: 'projectId is required' }, { status: 400 });
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
const apiHookUrl = resolveApiHookUrl(undefined, request.nextUrl.hostname);
|
|
101
|
+
const apiHookUrl = resolveApiHookUrl(undefined, request.nextUrl.hostname, projectId);
|
|
102
102
|
if (!apiHookUrl) {
|
|
103
103
|
return NextResponse.json({ error: 'API_HOOK_URL is not configured on server' }, { status: 500 });
|
|
104
104
|
}
|
|
@@ -98,7 +98,7 @@ export async function POST(request: NextRequest) {
|
|
|
98
98
|
return NextResponse.json({ error: 'projectId is required' }, { status: 400 });
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
-
const apiHookUrl = resolveApiHookUrl(undefined, request.nextUrl.hostname);
|
|
101
|
+
const apiHookUrl = resolveApiHookUrl(undefined, request.nextUrl.hostname, projectId);
|
|
102
102
|
if (!apiHookUrl) {
|
|
103
103
|
return NextResponse.json({ error: 'API_HOOK_URL is not configured on server' }, { status: 500 });
|
|
104
104
|
}
|
|
@@ -1,11 +1,12 @@
|
|
|
1
|
-
#
|
|
2
|
-
#
|
|
1
|
+
# Domain template (required)
|
|
2
|
+
# {room_id} (or room_id) will be replaced by PROJECT_ID at runtime.
|
|
3
|
+
API_HOOK_URL_DOMAIN="https://privos-chat-dev.roxane.one/api/v1/internal/rooms/{room_id}/files/"
|
|
3
4
|
|
|
4
|
-
#
|
|
5
|
-
# -
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
# Optional explicit override. If this contains room_id/{room_id}, it is also replaced by PROJECT_ID.
|
|
6
|
+
# API_HOOK_URL="https://privos-chat-dev.roxane.one/api/v1/internal/rooms/{room_id}/files/"
|
|
7
|
+
|
|
8
|
+
# Optional local fallback for non-domain environments.
|
|
9
|
+
# API_HOOK_URL_LOCAL="http://localhost:5005/api/sync/"
|
|
9
10
|
API_HOOK_API_KEY=
|
|
10
11
|
API_QUEUE_URL="http://localhost:8052"
|
|
11
12
|
# Optional comma-separated extra queue endpoints/base URLs
|
|
@@ -113,10 +113,12 @@ async function ensurePullDb(): Promise<Database.Database> {
|
|
|
113
113
|
return sqlite;
|
|
114
114
|
}
|
|
115
115
|
|
|
116
|
-
async function fetchManifest(
|
|
117
|
-
|
|
116
|
+
async function fetchManifest(label: string, options?: { root?: "markdown"; allowNotFound?: boolean }): Promise<ManifestEntry[]> {
|
|
117
|
+
const root = options?.root;
|
|
118
|
+
const allowNotFound = options?.allowNotFound || false;
|
|
119
|
+
console.error(`🔍 Calling API to get manifest for '${label}'${root ? ` (root=${root})` : ""}...`);
|
|
118
120
|
|
|
119
|
-
const url = buildApiUrl(`manifest?
|
|
121
|
+
const url = root ? buildApiUrl(`manifest?root=${encodeURIComponent(root)}`) : buildApiUrl("manifest");
|
|
120
122
|
const response = await fetch(url, { headers: buildApiHeaders() });
|
|
121
123
|
|
|
122
124
|
if (response.status === 404 && allowNotFound) {
|
|
@@ -140,12 +142,9 @@ async function fetchManifest(folder: string, label: string, allowNotFound = fals
|
|
|
140
142
|
}
|
|
141
143
|
|
|
142
144
|
async function getQueueCandidates(): Promise<{ candidates: QueueFileCandidate[]; manifestData: ManifestEntry[] }> {
|
|
143
|
-
const mainPrefix = config.projectId;
|
|
144
|
-
const markdownPrefix = `markdown/${config.projectId}`;
|
|
145
|
-
|
|
146
145
|
const [mainManifest, markdownManifest] = await Promise.all([
|
|
147
|
-
fetchManifest(
|
|
148
|
-
fetchManifest(
|
|
146
|
+
fetchManifest("main folder"),
|
|
147
|
+
fetchManifest("markdown folder", { root: "markdown", allowNotFound: true }),
|
|
149
148
|
]);
|
|
150
149
|
|
|
151
150
|
const normalize = (entries: ManifestEntry[], folder: FolderType): QueueFileCandidate[] => (
|
package/src/lib/api-hook-url.ts
CHANGED
|
@@ -24,34 +24,44 @@ function readVar(key: string, hookEnvValues?: HookEnvMapLike): string {
|
|
|
24
24
|
return '';
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
function
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const
|
|
33
|
-
if (
|
|
34
|
-
return
|
|
27
|
+
function hasRoomPlaceholder(value: string): boolean {
|
|
28
|
+
return /\{room_id\}|room_id/i.test(value);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function resolveRoomTemplate(value: string, roomId?: string): string {
|
|
32
|
+
const trimmed = trimQuotes(value);
|
|
33
|
+
if (!hasRoomPlaceholder(trimmed)) {
|
|
34
|
+
return trimmed;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
-
const
|
|
38
|
-
if (
|
|
37
|
+
const normalizedRoomId = (roomId || '').trim();
|
|
38
|
+
if (!normalizedRoomId) {
|
|
39
|
+
return trimmed;
|
|
40
|
+
}
|
|
39
41
|
|
|
40
|
-
return
|
|
42
|
+
return trimmed
|
|
43
|
+
.replace(/\{room_id\}/gi, normalizedRoomId)
|
|
44
|
+
.replace(/room_id/gi, normalizedRoomId);
|
|
41
45
|
}
|
|
42
46
|
|
|
43
|
-
export function resolveApiHookUrl(hookEnvValues?: HookEnvMapLike,
|
|
47
|
+
export function resolveApiHookUrl(hookEnvValues?: HookEnvMapLike, _hostname?: string, roomId?: string): string {
|
|
48
|
+
const resolvedRoomId = (roomId || readVar('PROJECT_ID', hookEnvValues)).trim();
|
|
49
|
+
const domainTemplate = readVar('API_HOOK_URL_DOMAIN', hookEnvValues);
|
|
50
|
+
if (domainTemplate) {
|
|
51
|
+
return resolveRoomTemplate(domainTemplate, resolvedRoomId);
|
|
52
|
+
}
|
|
53
|
+
|
|
44
54
|
const explicit = readVar('API_HOOK_URL', hookEnvValues);
|
|
45
|
-
if (explicit)
|
|
55
|
+
if (explicit) {
|
|
56
|
+
return resolveRoomTemplate(explicit, resolvedRoomId);
|
|
57
|
+
}
|
|
46
58
|
|
|
47
59
|
const local = readVar('API_HOOK_URL_LOCAL', hookEnvValues);
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
if (shouldUseLocalByHost(hostname)) {
|
|
51
|
-
return local || domain;
|
|
60
|
+
if (local) {
|
|
61
|
+
return resolveRoomTemplate(local, resolvedRoomId);
|
|
52
62
|
}
|
|
53
63
|
|
|
54
|
-
return
|
|
64
|
+
return '';
|
|
55
65
|
}
|
|
56
66
|
|
|
57
67
|
/**
|
|
@@ -257,15 +257,15 @@ async function readHookEnv(projectPath: string, fallbackProjectId: string): Prom
|
|
|
257
257
|
map.set(key, value);
|
|
258
258
|
}
|
|
259
259
|
|
|
260
|
-
const
|
|
260
|
+
const projectId = map.get('PROJECT_ID')?.trim() || fallbackProjectId;
|
|
261
|
+
const apiHookUrl = resolveApiHookUrl(map, undefined, projectId);
|
|
261
262
|
if (!apiHookUrl) {
|
|
262
|
-
throw new Error('Missing API_HOOK_URL in project hook .env and process env');
|
|
263
|
+
throw new Error('Missing API_HOOK_URL/API_HOOK_URL_DOMAIN in project hook .env and process env');
|
|
263
264
|
}
|
|
264
265
|
const apiHookApiKey = map.get('API_HOOK_API_KEY')?.trim()
|
|
265
266
|
|| process.env.API_HOOK_API_KEY?.trim()
|
|
266
267
|
|| '';
|
|
267
268
|
|
|
268
|
-
const projectId = map.get('PROJECT_ID')?.trim() || fallbackProjectId;
|
|
269
269
|
return { apiHookUrl, apiHookApiKey, projectId };
|
|
270
270
|
}
|
|
271
271
|
|
|
@@ -277,10 +277,11 @@ function buildApiHeaders(apiHookApiKey: string): Record<string, string> {
|
|
|
277
277
|
async function fetchManifest(
|
|
278
278
|
apiHookUrl: string,
|
|
279
279
|
apiHookApiKey: string,
|
|
280
|
-
|
|
281
|
-
|
|
280
|
+
label: string,
|
|
281
|
+
options?: { root?: 'markdown' }
|
|
282
282
|
): Promise<ManifestEntry[]> {
|
|
283
|
-
const
|
|
283
|
+
const endpoint = options?.root ? `manifest?root=${options.root}` : 'manifest';
|
|
284
|
+
const url = buildApiHookEndpoint(apiHookUrl, endpoint);
|
|
284
285
|
const response = await fetch(url, { headers: buildApiHeaders(apiHookApiKey) });
|
|
285
286
|
if (!response.ok) {
|
|
286
287
|
throw new Error(`Manifest API failed for ${label}: HTTP ${response.status} ${response.statusText}`);
|
|
@@ -362,12 +363,9 @@ async function fetchQueueCandidates(
|
|
|
362
363
|
projectId: string,
|
|
363
364
|
sqlite: Database.Database
|
|
364
365
|
): Promise<QueueFileCandidate[]> {
|
|
365
|
-
const targetPrefix = projectId;
|
|
366
|
-
const markdownPrefix = `markdown/${projectId}`;
|
|
367
|
-
|
|
368
366
|
const [mainManifest, markdownManifest] = await Promise.all([
|
|
369
|
-
fetchManifest(apiHookUrl, apiHookApiKey,
|
|
370
|
-
fetchManifest(apiHookUrl, apiHookApiKey,
|
|
367
|
+
fetchManifest(apiHookUrl, apiHookApiKey, 'main folder'),
|
|
368
|
+
fetchManifest(apiHookUrl, apiHookApiKey, 'markdown folder', { root: 'markdown' }),
|
|
371
369
|
]);
|
|
372
370
|
|
|
373
371
|
const normalize = (entries: ManifestEntry[], folder: 'main' | 'markdown'): QueueFileCandidate[] => (
|
|
@@ -908,8 +906,8 @@ async function processJob(projectPath: string, projectId: string, job: PendingJo
|
|
|
908
906
|
const manifestEntry = await fetchManifest(
|
|
909
907
|
hookEnv.apiHookUrl,
|
|
910
908
|
hookEnv.apiHookApiKey,
|
|
911
|
-
row.folder
|
|
912
|
-
row.folder
|
|
909
|
+
row.folder,
|
|
910
|
+
row.folder === 'markdown' ? { root: 'markdown' } : undefined
|
|
913
911
|
);
|
|
914
912
|
const latest = manifestEntry.find((entry) => entry.key === row.fileKey);
|
|
915
913
|
if (!latest) {
|
|
@@ -226,15 +226,15 @@ async function readHookEnv(projectPath: string, fallbackProjectId: string): Prom
|
|
|
226
226
|
values.set(key, value);
|
|
227
227
|
}
|
|
228
228
|
|
|
229
|
-
const
|
|
229
|
+
const projectId = values.get('PROJECT_ID')?.trim() || fallbackProjectId;
|
|
230
|
+
const apiHookUrl = resolveApiHookUrl(values, undefined, projectId);
|
|
230
231
|
if (!apiHookUrl) {
|
|
231
|
-
throw new Error('Missing API_HOOK_URL in project hook .env and process env');
|
|
232
|
+
throw new Error('Missing API_HOOK_URL/API_HOOK_URL_DOMAIN in project hook .env and process env');
|
|
232
233
|
}
|
|
233
234
|
const apiHookApiKey = values.get('API_HOOK_API_KEY')?.trim()
|
|
234
235
|
|| process.env.API_HOOK_API_KEY?.trim()
|
|
235
236
|
|| '';
|
|
236
237
|
|
|
237
|
-
const projectId = values.get('PROJECT_ID')?.trim() || fallbackProjectId;
|
|
238
238
|
return { apiHookUrl, apiHookApiKey, projectId };
|
|
239
239
|
}
|
|
240
240
|
|
|
@@ -339,12 +339,18 @@ async function requestWithoutJsonWithRetry(
|
|
|
339
339
|
throw new Error(`${label} failed after ${REQUEST_MAX_RETRIES} attempts: ${lastError?.message || 'Unknown error'}`);
|
|
340
340
|
}
|
|
341
341
|
|
|
342
|
-
async function fetchManifest(
|
|
343
|
-
|
|
342
|
+
async function fetchManifest(
|
|
343
|
+
apiHookUrl: string,
|
|
344
|
+
apiHookApiKey: string,
|
|
345
|
+
label: string,
|
|
346
|
+
options?: { root?: 'markdown' },
|
|
347
|
+
): Promise<ManifestEntry[]> {
|
|
348
|
+
const endpoint = options?.root ? `manifest?root=${options.root}` : 'manifest';
|
|
349
|
+
const url = buildApiHookEndpoint(apiHookUrl, endpoint);
|
|
344
350
|
const payload = await requestJsonWithRetry(
|
|
345
351
|
url,
|
|
346
352
|
{ method: 'GET', headers: buildApiHeaders(apiHookApiKey) },
|
|
347
|
-
`Manifest fetch (${
|
|
353
|
+
`Manifest fetch (${label})`
|
|
348
354
|
) as {
|
|
349
355
|
status?: string;
|
|
350
356
|
data?: unknown;
|
|
@@ -352,7 +358,7 @@ async function fetchManifest(apiHookUrl: string, apiHookApiKey: string, folder:
|
|
|
352
358
|
};
|
|
353
359
|
|
|
354
360
|
if (payload.status !== 'success' || !Array.isArray(payload.data)) {
|
|
355
|
-
throw new Error(`Invalid manifest response for ${
|
|
361
|
+
throw new Error(`Invalid manifest response for ${label}: ${payload.message || 'Invalid payload'}`);
|
|
356
362
|
}
|
|
357
363
|
|
|
358
364
|
return payload.data as ManifestEntry[];
|
|
@@ -581,10 +587,12 @@ function enqueueInDb(
|
|
|
581
587
|
|
|
582
588
|
export async function enqueueProjectPushSync(projectPath: string, fallbackProjectId: string) {
|
|
583
589
|
const hookEnv = await readHookEnv(projectPath, fallbackProjectId);
|
|
584
|
-
const [
|
|
585
|
-
fetchManifest(hookEnv.apiHookUrl, hookEnv.apiHookApiKey,
|
|
590
|
+
const [mainManifest, markdownManifest, localFiles] = await Promise.all([
|
|
591
|
+
fetchManifest(hookEnv.apiHookUrl, hookEnv.apiHookApiKey, 'main'),
|
|
592
|
+
fetchManifest(hookEnv.apiHookUrl, hookEnv.apiHookApiKey, 'markdown', { root: 'markdown' }),
|
|
586
593
|
scanLocalFiles(projectPath, hookEnv.projectId),
|
|
587
594
|
]);
|
|
595
|
+
const remoteManifest = [...mainManifest, ...markdownManifest];
|
|
588
596
|
|
|
589
597
|
const candidates = buildQueueCandidates(hookEnv.projectId, localFiles, remoteManifest);
|
|
590
598
|
const sqlite = await ensurePushQueueDb(projectPath);
|
|
@@ -705,7 +713,7 @@ async function uploadByPresignedUrl(url: string, localPath: string): Promise<voi
|
|
|
705
713
|
|
|
706
714
|
async function deleteByApiEndpoint(apiHookUrl: string, apiHookApiKey: string, key: string): Promise<void> {
|
|
707
715
|
const encodedKey = encodeURIComponent(key);
|
|
708
|
-
const deleteUrl = buildApiHookEndpoint(apiHookUrl, `
|
|
716
|
+
const deleteUrl = buildApiHookEndpoint(apiHookUrl, `by-path?key=${encodedKey}`);
|
|
709
717
|
await requestWithoutJsonWithRetry(
|
|
710
718
|
deleteUrl,
|
|
711
719
|
{ method: 'DELETE', headers: buildApiHeaders(apiHookApiKey) },
|
package/src/lib/project-utils.ts
CHANGED
|
@@ -106,7 +106,7 @@ export async function setupProjectDefaults(projectPath: string, projectId?: stri
|
|
|
106
106
|
try {
|
|
107
107
|
await access(envPath);
|
|
108
108
|
} catch {
|
|
109
|
-
const apiBase = resolveApiHookUrl();
|
|
109
|
+
const apiBase = resolveApiHookUrl(undefined, undefined, projectId);
|
|
110
110
|
const apiHookApiKey = process.env.API_HOOK_API_KEY?.trim() || '';
|
|
111
111
|
const projectIdEnvLine = projectId ? `PROJECT_ID="${projectId}"\n` : '';
|
|
112
112
|
const apiKeyEnvLine = apiHookApiKey ? `API_HOOK_API_KEY="${apiHookApiKey}"\n` : '';
|