@assistkick/create 1.9.0 → 1.11.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/dist/src/scaffolder.d.ts +12 -1
- package/dist/src/scaffolder.js +40 -3
- package/dist/src/scaffolder.js.map +1 -1
- package/package.json +1 -1
- package/templates/assistkick-product-system/package.json +1 -1
- package/templates/assistkick-product-system/packages/backend/package.json +1 -0
- package/templates/assistkick-product-system/packages/backend/src/mcp/permission_mcp_server.ts +196 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/agents.ts +31 -7
- package/templates/assistkick-product-system/packages/backend/src/routes/auth.ts +15 -12
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_files.test.ts +95 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_files.ts +97 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_permission.ts +94 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_sessions.ts +189 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_upload.test.ts +131 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/chat_upload.ts +94 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/files.test.ts +12 -3
- package/templates/assistkick-product-system/packages/backend/src/routes/files.ts +2 -2
- package/templates/assistkick-product-system/packages/backend/src/routes/git.ts +391 -23
- package/templates/assistkick-product-system/packages/backend/src/routes/git_branches.test.ts +306 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/git_connect.test.ts +133 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/pipeline.ts +66 -9
- package/templates/assistkick-product-system/packages/backend/src/routes/preview.ts +204 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/projects.test.ts +205 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/projects.ts +37 -9
- package/templates/assistkick-product-system/packages/backend/src/routes/skills.test.ts +139 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/skills.ts +95 -0
- package/templates/assistkick-product-system/packages/backend/src/routes/terminal.ts +5 -4
- package/templates/assistkick-product-system/packages/backend/src/routes/users.ts +4 -4
- package/templates/assistkick-product-system/packages/backend/src/routes/video.ts +8 -8
- package/templates/assistkick-product-system/packages/backend/src/routes/workflow_groups.ts +5 -5
- package/templates/assistkick-product-system/packages/backend/src/routes/workflows.ts +6 -6
- package/templates/assistkick-product-system/packages/backend/src/server.ts +107 -27
- package/templates/assistkick-product-system/packages/backend/src/services/agent_service.test.ts +105 -203
- package/templates/assistkick-product-system/packages/backend/src/services/agent_service.ts +76 -266
- package/templates/assistkick-product-system/packages/backend/src/services/chat_cli_bridge.test.ts +427 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_cli_bridge.ts +345 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_message_repository.test.ts +170 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_message_repository.ts +106 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_session_service.test.ts +217 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_session_service.ts +188 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_ws_handler.test.ts +1243 -0
- package/templates/assistkick-product-system/packages/backend/src/services/chat_ws_handler.ts +894 -0
- package/templates/assistkick-product-system/packages/backend/src/services/coherence-review.ts +3 -3
- package/templates/assistkick-product-system/packages/backend/src/services/dev_command_detector.test.ts +85 -0
- package/templates/assistkick-product-system/packages/backend/src/services/dev_command_detector.ts +54 -0
- package/templates/assistkick-product-system/packages/backend/src/services/email_service.ts +13 -10
- package/templates/assistkick-product-system/packages/backend/src/services/init.ts +11 -3
- package/templates/assistkick-product-system/packages/backend/src/services/invitation_service.ts +1 -1
- package/templates/assistkick-product-system/packages/backend/src/services/password_reset_service.ts +1 -1
- package/templates/assistkick-product-system/packages/backend/src/services/permission_service.test.ts +243 -0
- package/templates/assistkick-product-system/packages/backend/src/services/permission_service.ts +259 -0
- package/templates/assistkick-product-system/packages/backend/src/services/preview_server_manager.test.ts +172 -0
- package/templates/assistkick-product-system/packages/backend/src/services/preview_server_manager.ts +225 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_service.test.ts +29 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_service.ts +17 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.test.ts +255 -0
- package/templates/assistkick-product-system/packages/backend/src/services/project_workspace_service.ts +300 -25
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.test.ts +44 -0
- package/templates/assistkick-product-system/packages/backend/src/services/pty_session_manager.ts +62 -7
- package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.test.ts +77 -6
- package/templates/assistkick-product-system/packages/backend/src/services/ssh_key_service.ts +149 -14
- package/templates/assistkick-product-system/packages/backend/src/services/terminal_ws_handler.ts +2 -1
- package/templates/assistkick-product-system/packages/backend/src/services/title_generator_service.test.ts +45 -0
- package/templates/assistkick-product-system/packages/backend/src/services/title_generator_service.ts +157 -0
- package/templates/assistkick-product-system/packages/backend/src/services/tts_service.ts +4 -3
- package/templates/assistkick-product-system/packages/backend/src/services/video_render_service.ts +3 -3
- package/templates/assistkick-product-system/packages/frontend/package.json +5 -0
- package/templates/assistkick-product-system/packages/frontend/src/App.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/api/client.ts +336 -5
- package/templates/assistkick-product-system/packages/frontend/src/components/AgentsView.tsx +192 -12
- package/templates/assistkick-product-system/packages/frontend/src/components/AttachmentPreviewList.tsx +98 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/AutocompleteDropdown.tsx +65 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatAttachButton.tsx +56 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatDropZone.tsx +80 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageBubble.tsx +155 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageContent.tsx +182 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatMessageInput.tsx +233 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatSessionSidebar.tsx +218 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatStopButton.tsx +32 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatTodoSidebar.tsx +113 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ChatView.tsx +842 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/CommitMessageModal.tsx +82 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/DiagramOverlay.tsx +160 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/EditorTabBar.tsx +5 -5
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTree.tsx +9 -10
- package/templates/assistkick-product-system/packages/frontend/src/components/FileTreeInlineInput.tsx +5 -5
- package/templates/assistkick-product-system/packages/frontend/src/components/FilesView.tsx +112 -41
- package/templates/assistkick-product-system/packages/frontend/src/components/GraphLegend.tsx +2 -2
- package/templates/assistkick-product-system/packages/frontend/src/components/HighlightedText.tsx +87 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ImageLightbox.tsx +192 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/KanbanView.tsx +2 -2
- package/templates/assistkick-product-system/packages/frontend/src/components/MentionPill.tsx +33 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/MermaidBlock.tsx +148 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/PermissionDialog.tsx +91 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/PermissionModeSelector.tsx +229 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ProjectSelector.tsx +249 -83
- package/templates/assistkick-product-system/packages/frontend/src/components/QueuedMessageBubble.tsx +38 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/SidePanel.tsx +212 -117
- package/templates/assistkick-product-system/packages/frontend/src/components/SystemPromptAccordion.tsx +48 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/TaskIcon.tsx +11 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/TerminalView.tsx +25 -9
- package/templates/assistkick-product-system/packages/frontend/src/components/ToolDiffView.tsx +114 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ToolResultCard.tsx +87 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/ToolUseCard.tsx +149 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/Toolbar.tsx +25 -8
- package/templates/assistkick-product-system/packages/frontend/src/components/UnifiedGitWidget.tsx +722 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/GroupNode.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/NodePalette.tsx +2 -1
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/ProgrammableNode.tsx +178 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowCanvas.tsx +3 -0
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/WorkflowMonitorModal.tsx +103 -9
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/monitor_nodes.tsx +26 -2
- package/templates/assistkick-product-system/packages/frontend/src/components/workflow/workflow_types.ts +42 -1
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useDocumentTitle.ts +11 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/useProjects.ts +1 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/use_chat_stream.ts +826 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/use_file_tree_cache.ts +69 -0
- package/templates/assistkick-product-system/packages/frontend/src/hooks/use_mention_autocomplete.ts +284 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/attachment_manager.test.ts +183 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/attachment_manager.ts +150 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/chat_message_helpers.test.ts +305 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/chat_message_helpers.ts +113 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/context_usage_helpers.test.ts +157 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/context_usage_helpers.ts +95 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/mermaid_helpers.test.ts +65 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/mermaid_helpers.ts +110 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/message_queue.ts +66 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/tool_use_summary.test.ts +124 -0
- package/templates/assistkick-product-system/packages/frontend/src/lib/tool_use_summary.ts +112 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/AgentsRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/ChatRoute.tsx +8 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/CoherenceRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/DashboardLayout.tsx +0 -4
- package/templates/assistkick-product-system/packages/frontend/src/routes/DesignSystemRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/FilesRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/GraphRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/KanbanRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/TerminalRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/UsersRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/VideographyRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/WorkflowsRoute.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/accept_invitation.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/forgot_password.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/login.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/register.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/routes/reset_password.tsx +2 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useAttachmentStore.ts +66 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useChatSessionStore.ts +107 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useMessageQueueStore.ts +110 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/usePreviewStore.ts +78 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useProjectStore.ts +7 -0
- package/templates/assistkick-product-system/packages/frontend/src/stores/useSidePanelStore.ts +6 -1
- package/templates/assistkick-product-system/packages/frontend/src/styles/index.css +30 -357
- package/templates/assistkick-product-system/packages/frontend/src/utils/parse_node_markdown.test.ts +115 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/parse_node_markdown.ts +91 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/preview_utils.test.ts +30 -0
- package/templates/assistkick-product-system/packages/frontend/src/utils/preview_utils.ts +3 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0015_magenta_jazinda.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0016_giant_xorn.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0017_sloppy_mentor.sql +6 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0018_vengeful_kabuki.sql +9 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0019_careful_sentinels.sql +8 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0020_clever_spot.sql +27 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0021_graceful_hex.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0022_short_kingpin.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0023_ambiguous_sharon_carter.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/0024_fat_unus.sql +1 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0015_snapshot.json +1552 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0016_snapshot.json +1560 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0017_snapshot.json +1598 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0018_snapshot.json +1657 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0019_snapshot.json +1709 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0020_snapshot.json +1733 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0021_snapshot.json +1740 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0022_snapshot.json +1755 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0023_snapshot.json +1762 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/0024_snapshot.json +1769 -0
- package/templates/assistkick-product-system/packages/shared/db/migrations/meta/_journal.json +70 -0
- package/templates/assistkick-product-system/packages/shared/db/schema.ts +40 -1
- package/templates/assistkick-product-system/packages/shared/lib/claude-service.test.ts +236 -0
- package/templates/assistkick-product-system/packages/shared/lib/claude-service.ts +46 -5
- package/templates/assistkick-product-system/packages/shared/lib/git_workflow.ts +65 -39
- package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.test.ts +173 -0
- package/templates/assistkick-product-system/packages/shared/lib/programmable_node_executor.ts +213 -0
- package/templates/assistkick-product-system/packages/shared/lib/validator.test.ts +70 -0
- package/templates/assistkick-product-system/packages/shared/lib/validator.ts +17 -1
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.test.ts +803 -27
- package/templates/assistkick-product-system/packages/shared/lib/workflow_engine.ts +502 -68
- package/templates/assistkick-product-system/packages/shared/lib/workflow_orchestrator.ts +4 -4
- package/templates/assistkick-product-system/packages/shared/package.json +2 -1
- package/templates/assistkick-product-system/packages/shared/test_fixtures/hanging_stream.mjs +46 -0
- package/templates/assistkick-product-system/packages/shared/tools/add_node.test.ts +44 -0
- package/templates/assistkick-product-system/packages/shared/tools/add_node.ts +7 -0
- package/templates/assistkick-product-system/packages/shared/tools/remove_node.ts +2 -1
- package/templates/assistkick-product-system/packages/shared/tools/resolve_question.ts +2 -1
- package/templates/assistkick-product-system/packages/shared/tools/update_node.ts +2 -1
- package/templates/assistkick-product-system/tests/message_queue.test.ts +178 -0
- package/templates/assistkick-product-system/tests/message_queue_per_session.test.ts +143 -0
- package/templates/skills/assistkick-bootstrap/SKILL.md +26 -26
- package/templates/skills/assistkick-code-reviewer/SKILL.md +45 -46
- package/templates/skills/assistkick-db-explorer/SKILL.md +13 -13
- package/templates/skills/assistkick-debugger/SKILL.md +23 -23
- package/templates/skills/assistkick-developer/SKILL.md +59 -63
- package/templates/skills/assistkick-interview/SKILL.md +26 -26
- package/templates/skills/assistkick-video-composition-agent/SKILL.md +231 -0
- package/templates/skills/assistkick-video-script-writer/SKILL.md +136 -0
package/dist/src/scaffolder.d.ts
CHANGED
|
@@ -9,6 +9,15 @@ type PortConfig = {
|
|
|
9
9
|
backendPort: number;
|
|
10
10
|
frontendPort: number;
|
|
11
11
|
};
|
|
12
|
+
type SecretConfig = {
|
|
13
|
+
jwtSecret: string;
|
|
14
|
+
encryptionKey: string;
|
|
15
|
+
};
|
|
16
|
+
type AppConfig = {
|
|
17
|
+
resendApiKey: string;
|
|
18
|
+
appBaseUrl: string;
|
|
19
|
+
emailFrom: string;
|
|
20
|
+
};
|
|
12
21
|
export type PromptFn = (question: string) => Promise<string>;
|
|
13
22
|
export declare class Scaffolder {
|
|
14
23
|
private readonly targetDir;
|
|
@@ -19,9 +28,11 @@ export declare class Scaffolder {
|
|
|
19
28
|
private checkPnpm;
|
|
20
29
|
private promptDbConfig;
|
|
21
30
|
private promptPortConfig;
|
|
31
|
+
private promptSecrets;
|
|
32
|
+
private promptAppConfig;
|
|
22
33
|
private scaffoldProductSystem;
|
|
23
34
|
private scaffoldSkills;
|
|
24
35
|
skillHasModifications: (sourceDir: string, targetDir: string) => boolean;
|
|
25
|
-
generateEnv: (productSystemDir: string, dbConfig: DbConfig, portConfig: PortConfig) => void;
|
|
36
|
+
generateEnv: (productSystemDir: string, dbConfig: DbConfig, portConfig: PortConfig, secretConfig: SecretConfig, appConfig: AppConfig) => void;
|
|
26
37
|
}
|
|
27
38
|
export {};
|
package/dist/src/scaffolder.js
CHANGED
|
@@ -2,6 +2,7 @@ import { cpSync, existsSync, mkdirSync, readdirSync, readFileSync, statSync, wri
|
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
import { createInterface } from 'node:readline';
|
|
4
4
|
import { execSync } from 'node:child_process';
|
|
5
|
+
import { randomBytes } from 'node:crypto';
|
|
5
6
|
const createReadlinePrompt = () => {
|
|
6
7
|
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
7
8
|
return (question) => new Promise(resolve => rl.question(question, answer => resolve(answer)));
|
|
@@ -22,17 +23,21 @@ export class Scaffolder {
|
|
|
22
23
|
const isRerun = existsSync(productSystemTarget);
|
|
23
24
|
let dbConfig = null;
|
|
24
25
|
let portConfig = null;
|
|
26
|
+
let secretConfig = null;
|
|
27
|
+
let appConfig = null;
|
|
25
28
|
if (isRerun) {
|
|
26
29
|
console.log('assistkick-product-system/ already exists. Updating code, preserving data/ and .env...');
|
|
27
30
|
}
|
|
28
31
|
else {
|
|
29
32
|
dbConfig = await this.promptDbConfig();
|
|
30
33
|
portConfig = await this.promptPortConfig();
|
|
34
|
+
secretConfig = await this.promptSecrets();
|
|
35
|
+
appConfig = await this.promptAppConfig();
|
|
31
36
|
}
|
|
32
37
|
this.scaffoldProductSystem(productSystemTarget);
|
|
33
38
|
await this.scaffoldSkills(skillsTarget, isRerun);
|
|
34
|
-
if (!isRerun && dbConfig && portConfig) {
|
|
35
|
-
this.generateEnv(productSystemTarget, dbConfig, portConfig);
|
|
39
|
+
if (!isRerun && dbConfig && portConfig && secretConfig && appConfig) {
|
|
40
|
+
this.generateEnv(productSystemTarget, dbConfig, portConfig, secretConfig, appConfig);
|
|
36
41
|
}
|
|
37
42
|
console.log('\nRunning pnpm install...');
|
|
38
43
|
execSync('pnpm install', { cwd: productSystemTarget, stdio: 'inherit' });
|
|
@@ -71,6 +76,35 @@ export class Scaffolder {
|
|
|
71
76
|
const frontendPort = frontendInput.trim() ? parseInt(frontendInput.trim(), 10) : 5173;
|
|
72
77
|
return { backendPort, frontendPort };
|
|
73
78
|
};
|
|
79
|
+
promptSecrets = async () => {
|
|
80
|
+
console.log('\nSecret configuration:');
|
|
81
|
+
const jwtChoice = await this.prompt('JWT_SECRET — auto-generate (recommended)? [Y/n]: ');
|
|
82
|
+
let jwtSecret;
|
|
83
|
+
if (jwtChoice.trim().toLowerCase() === 'n') {
|
|
84
|
+
jwtSecret = (await this.prompt('JWT_SECRET: ')).trim();
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
jwtSecret = randomBytes(32).toString('hex');
|
|
88
|
+
console.log(' Auto-generated JWT_SECRET');
|
|
89
|
+
}
|
|
90
|
+
const encChoice = await this.prompt('ENCRYPTION_KEY — auto-generate (recommended)? [Y/n]: ');
|
|
91
|
+
let encryptionKey;
|
|
92
|
+
if (encChoice.trim().toLowerCase() === 'n') {
|
|
93
|
+
encryptionKey = (await this.prompt('ENCRYPTION_KEY: ')).trim();
|
|
94
|
+
}
|
|
95
|
+
else {
|
|
96
|
+
encryptionKey = randomBytes(32).toString('hex');
|
|
97
|
+
console.log(' Auto-generated ENCRYPTION_KEY');
|
|
98
|
+
}
|
|
99
|
+
return { jwtSecret, encryptionKey };
|
|
100
|
+
};
|
|
101
|
+
promptAppConfig = async () => {
|
|
102
|
+
console.log('\nApplication configuration:');
|
|
103
|
+
const resendApiKey = (await this.prompt('RESEND_API_KEY (optional, press Enter to skip): ')).trim();
|
|
104
|
+
const appBaseUrl = (await this.prompt('APP_BASE_URL [default=http://localhost:3000]: ')).trim() || 'http://localhost:3000';
|
|
105
|
+
const emailFrom = (await this.prompt('EMAIL_FROM [default=noreply@example.com]: ')).trim() || 'noreply@example.com';
|
|
106
|
+
return { resendApiKey, appBaseUrl, emailFrom };
|
|
107
|
+
};
|
|
74
108
|
scaffoldProductSystem = (targetDir) => {
|
|
75
109
|
const sourceDir = join(this.templatesDir, 'assistkick-product-system');
|
|
76
110
|
mkdirSync(targetDir, { recursive: true });
|
|
@@ -115,7 +149,7 @@ export class Scaffolder {
|
|
|
115
149
|
return false;
|
|
116
150
|
return readFileSync(sourceFile, 'utf-8') !== readFileSync(targetFile, 'utf-8');
|
|
117
151
|
};
|
|
118
|
-
generateEnv = (productSystemDir, dbConfig, portConfig) => {
|
|
152
|
+
generateEnv = (productSystemDir, dbConfig, portConfig, secretConfig, appConfig) => {
|
|
119
153
|
const lines = [];
|
|
120
154
|
if (dbConfig.type === 'local') {
|
|
121
155
|
lines.push('# Database — local SQLite', '# The database file is created automatically at data/local.db', '# To upgrade to Turso, uncomment and fill in the variables below:', '#TURSO_DATABASE_URL=libsql://your-db.turso.io', '#TURSO_AUTH_TOKEN=your-token', '');
|
|
@@ -124,6 +158,9 @@ export class Scaffolder {
|
|
|
124
158
|
lines.push('# Database — Turso cloud', `TURSO_DATABASE_URL=${dbConfig.url}`, `TURSO_AUTH_TOKEN=${dbConfig.authToken}`, '');
|
|
125
159
|
}
|
|
126
160
|
lines.push('# Ports', `PORT=${portConfig.backendPort}`, `FRONTEND_PORT=${portConfig.frontendPort}`, '');
|
|
161
|
+
lines.push('# Secrets', `JWT_SECRET=${secretConfig.jwtSecret}`, `ENCRYPTION_KEY=${secretConfig.encryptionKey}`, '');
|
|
162
|
+
lines.push('# Application', `APP_BASE_URL=${appConfig.appBaseUrl}`, `EMAIL_FROM=${appConfig.emailFrom}`, `RESEND_API_KEY=${appConfig.resendApiKey}`, '');
|
|
163
|
+
lines.push('# Workspaces', 'WORKSPACES_DIR=./workspaces', '');
|
|
127
164
|
writeFileSync(join(productSystemDir, '.env'), lines.join('\n'));
|
|
128
165
|
console.log('Generated .env');
|
|
129
166
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scaffolder.js","sourceRoot":"","sources":["../../src/scaffolder.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,MAAM,EACN,UAAU,EACV,SAAS,EACT,WAAW,EACX,YAAY,EACZ,QAAQ,EACR,aAAa,GACd,MAAM,SAAS,CAAC;AACjB,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,eAAe,EAAE,MAAM,eAAe,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,oBAAoB,CAAC;
|
|
1
|
+
{"version":3,"file":"scaffolder.js","sourceRoot":"","sources":["../../src/scaffolder.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"remotion:render": "pnpm --filter @assistkick/video render"
|
|
18
18
|
},
|
|
19
19
|
"pnpm": {
|
|
20
|
-
"onlyBuiltDependencies": ["esbuild", "node-pty"]
|
|
20
|
+
"onlyBuiltDependencies": ["esbuild", "node-pty", "isolated-vm"]
|
|
21
21
|
},
|
|
22
22
|
"devDependencies": {
|
|
23
23
|
"@libsql/client": "^0.17.0",
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Permission MCP Server — bridges Claude CLI permission prompts to the web UI.
|
|
4
|
+
*
|
|
5
|
+
* Launched as a stdio MCP server by Claude CLI via --mcp-config.
|
|
6
|
+
* When Claude needs permission to use a tool, it calls the `ask` tool on this server.
|
|
7
|
+
* The server makes an HTTP POST to the backend, which forwards the request
|
|
8
|
+
* to the frontend via WebSocket and waits for the user's decision.
|
|
9
|
+
*
|
|
10
|
+
* Environment variables (set via MCP config):
|
|
11
|
+
* BACKEND_URL — e.g. "http://localhost:3000"
|
|
12
|
+
* CLAUDE_SESSION_ID — the Claude session ID for routing
|
|
13
|
+
* PROJECT_ID — the project ID for permission rule lookup
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { createInterface } from 'node:readline';
|
|
17
|
+
|
|
18
|
+
const BACKEND_URL = process.env.BACKEND_URL || 'http://localhost:3000';
|
|
19
|
+
const CLAUDE_SESSION_ID = process.env.CLAUDE_SESSION_ID || '';
|
|
20
|
+
const PROJECT_ID = process.env.PROJECT_ID || '';
|
|
21
|
+
|
|
22
|
+
interface JsonRpcRequest {
|
|
23
|
+
jsonrpc: '2.0';
|
|
24
|
+
id: number | string;
|
|
25
|
+
method: string;
|
|
26
|
+
params?: Record<string, unknown>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface JsonRpcResponse {
|
|
30
|
+
jsonrpc: '2.0';
|
|
31
|
+
id: number | string | null;
|
|
32
|
+
result?: unknown;
|
|
33
|
+
error?: { code: number; message: string; data?: unknown };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const sendResponse = (response: JsonRpcResponse): void => {
|
|
37
|
+
const json = JSON.stringify(response);
|
|
38
|
+
process.stdout.write(`${json}\n`);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
const handleInitialize = (id: number | string): void => {
|
|
42
|
+
sendResponse({
|
|
43
|
+
jsonrpc: '2.0',
|
|
44
|
+
id,
|
|
45
|
+
result: {
|
|
46
|
+
protocolVersion: '2024-11-05',
|
|
47
|
+
capabilities: { tools: {} },
|
|
48
|
+
serverInfo: { name: 'permission', version: '1.0.0' },
|
|
49
|
+
},
|
|
50
|
+
});
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const handleToolsList = (id: number | string): void => {
|
|
54
|
+
sendResponse({
|
|
55
|
+
jsonrpc: '2.0',
|
|
56
|
+
id,
|
|
57
|
+
result: {
|
|
58
|
+
tools: [
|
|
59
|
+
{
|
|
60
|
+
name: 'ask',
|
|
61
|
+
description: 'Request permission from the user to use a tool',
|
|
62
|
+
inputSchema: {
|
|
63
|
+
type: 'object',
|
|
64
|
+
properties: {
|
|
65
|
+
tool_name: { type: 'string', description: 'Name of the tool requesting permission' },
|
|
66
|
+
input: {
|
|
67
|
+
type: 'object',
|
|
68
|
+
description: 'The input that will be passed to the tool',
|
|
69
|
+
additionalProperties: true,
|
|
70
|
+
},
|
|
71
|
+
},
|
|
72
|
+
required: ['tool_name'],
|
|
73
|
+
},
|
|
74
|
+
},
|
|
75
|
+
],
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const handleToolCall = async (id: number | string, params: Record<string, unknown>): Promise<void> => {
|
|
81
|
+
const toolName = params.name as string;
|
|
82
|
+
|
|
83
|
+
if (toolName !== 'ask') {
|
|
84
|
+
sendResponse({
|
|
85
|
+
jsonrpc: '2.0',
|
|
86
|
+
id,
|
|
87
|
+
error: { code: -32601, message: `Unknown tool: ${toolName}` },
|
|
88
|
+
});
|
|
89
|
+
return;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const args = (params.arguments || {}) as Record<string, unknown>;
|
|
93
|
+
const requestedToolName = (args.tool_name || 'unknown') as string;
|
|
94
|
+
const toolInput = (args.input || {}) as Record<string, unknown>;
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
const response = await fetch(`${BACKEND_URL}/api/chat/permission/request`, {
|
|
98
|
+
method: 'POST',
|
|
99
|
+
headers: { 'Content-Type': 'application/json' },
|
|
100
|
+
body: JSON.stringify({
|
|
101
|
+
claudeSessionId: CLAUDE_SESSION_ID,
|
|
102
|
+
projectId: PROJECT_ID,
|
|
103
|
+
toolName: requestedToolName,
|
|
104
|
+
input: toolInput,
|
|
105
|
+
}),
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
const errorText = await response.text();
|
|
110
|
+
sendResponse({
|
|
111
|
+
jsonrpc: '2.0',
|
|
112
|
+
id,
|
|
113
|
+
result: {
|
|
114
|
+
content: [{ type: 'text', text: JSON.stringify({ behavior: 'deny', message: `Permission service error: ${errorText}` }) }],
|
|
115
|
+
},
|
|
116
|
+
});
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const result = await response.json() as { decision: string };
|
|
121
|
+
const allowed = result.decision !== 'deny';
|
|
122
|
+
|
|
123
|
+
sendResponse({
|
|
124
|
+
jsonrpc: '2.0',
|
|
125
|
+
id,
|
|
126
|
+
result: {
|
|
127
|
+
content: [
|
|
128
|
+
{
|
|
129
|
+
type: 'text',
|
|
130
|
+
text: JSON.stringify({
|
|
131
|
+
behavior: allowed ? 'allow' : 'deny',
|
|
132
|
+
message: allowed ? `User allowed: ${result.decision}` : 'User denied permission',
|
|
133
|
+
}),
|
|
134
|
+
},
|
|
135
|
+
],
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
} catch (err: unknown) {
|
|
139
|
+
const errorMsg = err instanceof Error ? err.message : 'Unknown error';
|
|
140
|
+
sendResponse({
|
|
141
|
+
jsonrpc: '2.0',
|
|
142
|
+
id,
|
|
143
|
+
result: {
|
|
144
|
+
content: [{ type: 'text', text: JSON.stringify({ behavior: 'deny', message: `Failed to reach permission service: ${errorMsg}` }) }],
|
|
145
|
+
},
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
};
|
|
149
|
+
|
|
150
|
+
const handleMessage = async (line: string): Promise<void> => {
|
|
151
|
+
let msg: JsonRpcRequest;
|
|
152
|
+
try {
|
|
153
|
+
msg = JSON.parse(line);
|
|
154
|
+
} catch {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (msg.jsonrpc !== '2.0') return;
|
|
159
|
+
|
|
160
|
+
// Notifications (no id) — acknowledge silently
|
|
161
|
+
if (msg.id === undefined || msg.id === null) {
|
|
162
|
+
// Handle notifications/initialized — no response needed
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
switch (msg.method) {
|
|
167
|
+
case 'initialize':
|
|
168
|
+
handleInitialize(msg.id);
|
|
169
|
+
break;
|
|
170
|
+
case 'tools/list':
|
|
171
|
+
handleToolsList(msg.id);
|
|
172
|
+
break;
|
|
173
|
+
case 'tools/call':
|
|
174
|
+
await handleToolCall(msg.id, msg.params || {});
|
|
175
|
+
break;
|
|
176
|
+
case 'notifications/initialized':
|
|
177
|
+
// Client notification — no response needed, but has id sometimes
|
|
178
|
+
break;
|
|
179
|
+
default:
|
|
180
|
+
sendResponse({
|
|
181
|
+
jsonrpc: '2.0',
|
|
182
|
+
id: msg.id,
|
|
183
|
+
error: { code: -32601, message: `Method not found: ${msg.method}` },
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
|
|
188
|
+
// Read JSON-RPC messages from stdin, one per line
|
|
189
|
+
const rl = createInterface({ input: process.stdin, terminal: false });
|
|
190
|
+
rl.on('line', (line: string) => {
|
|
191
|
+
handleMessage(line).catch((err) => {
|
|
192
|
+
process.stderr.write(`MCP permission server error: ${err}\n`);
|
|
193
|
+
});
|
|
194
|
+
});
|
|
195
|
+
|
|
196
|
+
process.stderr.write(`[permission-mcp] Started (session=${CLAUDE_SESSION_ID}, project=${PROJECT_ID})\n`);
|
|
@@ -5,7 +5,8 @@
|
|
|
5
5
|
* POST /api/agents — create agent
|
|
6
6
|
* PUT /api/agents/:id — update agent
|
|
7
7
|
* DELETE /api/agents/:id — delete agent (409 if assigned or default)
|
|
8
|
-
* POST /api/agents/:id/reset — reset default agent to original
|
|
8
|
+
* POST /api/agents/:id/reset — reset default agent to original grounding
|
|
9
|
+
* GET /api/skills — list available skills from disk
|
|
9
10
|
*/
|
|
10
11
|
|
|
11
12
|
import { Router } from 'express';
|
|
@@ -64,7 +65,7 @@ export const createAgentRoutes = ({ agentService, log }: AgentRoutesDeps): Route
|
|
|
64
65
|
|
|
65
66
|
// POST /api/agents
|
|
66
67
|
router.post('/', async (req, res) => {
|
|
67
|
-
const { name,
|
|
68
|
+
const { name, grounding, model, skills, projectId } = req.body;
|
|
68
69
|
log('AGENTS', `POST /api/agents name="${name}"`);
|
|
69
70
|
|
|
70
71
|
if (!name || typeof name !== 'string' || !name.trim()) {
|
|
@@ -72,15 +73,17 @@ export const createAgentRoutes = ({ agentService, log }: AgentRoutesDeps): Route
|
|
|
72
73
|
return;
|
|
73
74
|
}
|
|
74
75
|
|
|
75
|
-
if (!
|
|
76
|
-
res.status(400).json({ error: '
|
|
76
|
+
if (!grounding || typeof grounding !== 'string') {
|
|
77
|
+
res.status(400).json({ error: 'grounding is required' });
|
|
77
78
|
return;
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
try {
|
|
81
82
|
const agent = await agentService.create({
|
|
82
83
|
name: name.trim(),
|
|
83
|
-
|
|
84
|
+
grounding,
|
|
85
|
+
model,
|
|
86
|
+
skills,
|
|
84
87
|
projectId: projectId || null,
|
|
85
88
|
});
|
|
86
89
|
res.status(201).json({ agent });
|
|
@@ -93,7 +96,7 @@ export const createAgentRoutes = ({ agentService, log }: AgentRoutesDeps): Route
|
|
|
93
96
|
// PUT /api/agents/:id
|
|
94
97
|
router.put('/:id', async (req, res) => {
|
|
95
98
|
const { id } = req.params;
|
|
96
|
-
const { name,
|
|
99
|
+
const { name, grounding, model, skills } = req.body;
|
|
97
100
|
log('AGENTS', `PUT /api/agents/${id}`);
|
|
98
101
|
|
|
99
102
|
if (name !== undefined && (typeof name !== 'string' || !name.trim())) {
|
|
@@ -104,7 +107,9 @@ export const createAgentRoutes = ({ agentService, log }: AgentRoutesDeps): Route
|
|
|
104
107
|
try {
|
|
105
108
|
const agent = await agentService.update(id, {
|
|
106
109
|
name: name?.trim(),
|
|
107
|
-
|
|
110
|
+
grounding,
|
|
111
|
+
model,
|
|
112
|
+
skills,
|
|
108
113
|
});
|
|
109
114
|
res.json({ agent });
|
|
110
115
|
} catch (err: any) {
|
|
@@ -163,3 +168,22 @@ export const createAgentRoutes = ({ agentService, log }: AgentRoutesDeps): Route
|
|
|
163
168
|
|
|
164
169
|
return router;
|
|
165
170
|
};
|
|
171
|
+
|
|
172
|
+
export const createSkillListRoutes = ({ agentService, log }: AgentRoutesDeps): Router => {
|
|
173
|
+
const router: Router = Router();
|
|
174
|
+
|
|
175
|
+
// GET /api/skills
|
|
176
|
+
router.get('/', async (_req, res) => {
|
|
177
|
+
log('SKILLS', 'GET /api/skills');
|
|
178
|
+
|
|
179
|
+
try {
|
|
180
|
+
const skills = await agentService.listSkills();
|
|
181
|
+
res.json({ skills });
|
|
182
|
+
} catch (err: any) {
|
|
183
|
+
log('SKILLS', `List skills failed: ${err.message}`);
|
|
184
|
+
res.status(500).json({ error: 'Failed to list skills' });
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return router;
|
|
189
|
+
};
|
|
@@ -101,7 +101,7 @@ export const createAuthRoutes = ({ getDb, authService, passwordResetService, inv
|
|
|
101
101
|
user: { id: userId, email: email.toLowerCase(), role: 'admin' },
|
|
102
102
|
});
|
|
103
103
|
} catch (err: any) {
|
|
104
|
-
log('AUTH', `Registration failed
|
|
104
|
+
log('AUTH', `Registration failed:`, err.stack || err.message);
|
|
105
105
|
res.status(500).json({ error: 'Registration failed' });
|
|
106
106
|
}
|
|
107
107
|
});
|
|
@@ -167,7 +167,7 @@ export const createAuthRoutes = ({ getDb, authService, passwordResetService, inv
|
|
|
167
167
|
user: { id: user.id, email: user.email, role: user.role },
|
|
168
168
|
});
|
|
169
169
|
} catch (err: any) {
|
|
170
|
-
log('AUTH', `Login failed
|
|
170
|
+
log('AUTH', `Login failed:`, err.stack || err.message);
|
|
171
171
|
res.status(500).json({ error: 'Login failed' });
|
|
172
172
|
}
|
|
173
173
|
});
|
|
@@ -182,7 +182,8 @@ export const createAuthRoutes = ({ getDb, authService, passwordResetService, inv
|
|
|
182
182
|
try {
|
|
183
183
|
const payload = await authService.verifyToken(token);
|
|
184
184
|
return res.json({ user: { id: payload.sub, email: payload.email, role: payload.role } });
|
|
185
|
-
} catch {
|
|
185
|
+
} catch (err: any) {
|
|
186
|
+
log('AUTH', 'Token verification failed (GET /me):', err.message);
|
|
186
187
|
return res.status(401).json({ error: 'Invalid or expired token' });
|
|
187
188
|
}
|
|
188
189
|
});
|
|
@@ -251,7 +252,8 @@ export const createAuthRoutes = ({ getDb, authService, passwordResetService, inv
|
|
|
251
252
|
|
|
252
253
|
log('AUTH', `Token refreshed for user: ${user.email}`);
|
|
253
254
|
res.json({ message: 'Token refreshed' });
|
|
254
|
-
} catch {
|
|
255
|
+
} catch (err: any) {
|
|
256
|
+
log('AUTH', 'Refresh token invalid:', err.message);
|
|
255
257
|
return res.status(401).json({ error: 'Invalid refresh token' });
|
|
256
258
|
}
|
|
257
259
|
});
|
|
@@ -280,8 +282,8 @@ export const createAuthRoutes = ({ getDb, authService, passwordResetService, inv
|
|
|
280
282
|
break;
|
|
281
283
|
}
|
|
282
284
|
}
|
|
283
|
-
} catch {
|
|
284
|
-
|
|
285
|
+
} catch (err: any) {
|
|
286
|
+
log('AUTH', 'Logout: failed to revoke refresh token (proceeding):', err.message);
|
|
285
287
|
}
|
|
286
288
|
}
|
|
287
289
|
|
|
@@ -308,7 +310,7 @@ export const createAuthRoutes = ({ getDb, authService, passwordResetService, inv
|
|
|
308
310
|
// Always return success to prevent user enumeration
|
|
309
311
|
res.json({ message: 'If an account with that email exists, a reset link has been sent.' });
|
|
310
312
|
} catch (err: any) {
|
|
311
|
-
log('AUTH', `Forgot password failed
|
|
313
|
+
log('AUTH', `Forgot password failed:`, err.stack || err.message);
|
|
312
314
|
res.status(500).json({ error: 'Failed to process reset request' });
|
|
313
315
|
}
|
|
314
316
|
});
|
|
@@ -334,7 +336,7 @@ export const createAuthRoutes = ({ getDb, authService, passwordResetService, inv
|
|
|
334
336
|
await passwordResetService.confirmReset(token, password);
|
|
335
337
|
res.json({ message: 'Password has been reset successfully.' });
|
|
336
338
|
} catch (err: any) {
|
|
337
|
-
log('AUTH', `Reset password failed
|
|
339
|
+
log('AUTH', `Reset password failed:`, err.stack || err.message);
|
|
338
340
|
if (err.message === 'Invalid or expired reset token') {
|
|
339
341
|
return res.status(400).json({ error: err.message });
|
|
340
342
|
}
|
|
@@ -356,7 +358,8 @@ export const createAuthRoutes = ({ getDb, authService, passwordResetService, inv
|
|
|
356
358
|
let payload: { sub: string; email?: string; role?: string };
|
|
357
359
|
try {
|
|
358
360
|
payload = await authService.verifyToken(token);
|
|
359
|
-
} catch {
|
|
361
|
+
} catch (err: any) {
|
|
362
|
+
log('AUTH', 'Invite: token verification failed:', err.message);
|
|
360
363
|
return res.status(401).json({ error: 'Invalid or expired token' });
|
|
361
364
|
}
|
|
362
365
|
|
|
@@ -378,7 +381,7 @@ export const createAuthRoutes = ({ getDb, authService, passwordResetService, inv
|
|
|
378
381
|
await invitationService.sendInvitation(email, payload.sub);
|
|
379
382
|
res.json({ message: `Invitation sent to ${email.toLowerCase()}` });
|
|
380
383
|
} catch (err: any) {
|
|
381
|
-
log('AUTH', `Invite failed
|
|
384
|
+
log('AUTH', `Invite failed:`, err.stack || err.message);
|
|
382
385
|
if (err.message.includes('already exists') || err.message.includes('active invitation')) {
|
|
383
386
|
return res.status(409).json({ error: err.message });
|
|
384
387
|
}
|
|
@@ -399,7 +402,7 @@ export const createAuthRoutes = ({ getDb, authService, passwordResetService, inv
|
|
|
399
402
|
const result = await invitationService.validateToken(token, email);
|
|
400
403
|
res.json(result);
|
|
401
404
|
} catch (err: any) {
|
|
402
|
-
log('AUTH', `Invitation validation failed
|
|
405
|
+
log('AUTH', `Invitation validation failed:`, err.stack || err.message);
|
|
403
406
|
res.status(500).json({ error: 'Failed to validate invitation' });
|
|
404
407
|
}
|
|
405
408
|
});
|
|
@@ -451,7 +454,7 @@ export const createAuthRoutes = ({ getDb, authService, passwordResetService, inv
|
|
|
451
454
|
user: { id: userId, email: userEmail, role: 'user' },
|
|
452
455
|
});
|
|
453
456
|
} catch (err: any) {
|
|
454
|
-
log('AUTH', `Accept invitation failed
|
|
457
|
+
log('AUTH', `Accept invitation failed:`, err.stack || err.message);
|
|
455
458
|
if (err.message === 'Invalid or expired invitation token' || err.message.includes('already exists')) {
|
|
456
459
|
return res.status(400).json({ error: err.message });
|
|
457
460
|
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { describe, it, beforeEach, afterEach, mock } from 'node:test';
|
|
2
|
+
import assert from 'node:assert/strict';
|
|
3
|
+
import { mkdtemp, rm, mkdir, writeFile } from 'node:fs/promises';
|
|
4
|
+
import { join } from 'node:path';
|
|
5
|
+
import { tmpdir } from 'node:os';
|
|
6
|
+
import { createChatFilesRoutes } from './chat_files.ts';
|
|
7
|
+
import express from 'express';
|
|
8
|
+
import type { Server } from 'node:http';
|
|
9
|
+
|
|
10
|
+
const createTestApp = (workspacePath: string) => {
|
|
11
|
+
const workspaceService = {
|
|
12
|
+
getWorkspacePath: (_projectId: string) => workspacePath,
|
|
13
|
+
} as any;
|
|
14
|
+
const log = mock.fn();
|
|
15
|
+
const app = express();
|
|
16
|
+
const routes = createChatFilesRoutes({ workspaceService, log });
|
|
17
|
+
app.use('/api/chat-files', routes);
|
|
18
|
+
return { app, log };
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const request = (server: Server) => {
|
|
22
|
+
const addr = server.address() as { port: number };
|
|
23
|
+
const base = `http://127.0.0.1:${addr.port}`;
|
|
24
|
+
return {
|
|
25
|
+
get: async (path: string) => {
|
|
26
|
+
const res = await fetch(`${base}${path}`);
|
|
27
|
+
return {
|
|
28
|
+
status: res.status,
|
|
29
|
+
headers: Object.fromEntries(res.headers.entries()),
|
|
30
|
+
text: await res.text(),
|
|
31
|
+
};
|
|
32
|
+
},
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
describe('chat_files route', () => {
|
|
37
|
+
let tmpDir: string;
|
|
38
|
+
let server: Server;
|
|
39
|
+
let req: ReturnType<typeof request>;
|
|
40
|
+
|
|
41
|
+
beforeEach(async () => {
|
|
42
|
+
tmpDir = await mkdtemp(join(tmpdir(), 'chat-files-test-'));
|
|
43
|
+
// Create .chat-uploads directory with test files
|
|
44
|
+
const uploadsDir = join(tmpDir, '.chat-uploads');
|
|
45
|
+
await mkdir(uploadsDir, { recursive: true });
|
|
46
|
+
await writeFile(join(uploadsDir, 'test_image.png'), Buffer.from('fake-png-data'));
|
|
47
|
+
await writeFile(join(uploadsDir, 'doc.txt'), 'hello world');
|
|
48
|
+
|
|
49
|
+
const { app } = createTestApp(tmpDir);
|
|
50
|
+
server = await new Promise<Server>((resolve) => {
|
|
51
|
+
const s = app.listen(0, '127.0.0.1', () => resolve(s));
|
|
52
|
+
});
|
|
53
|
+
req = request(server);
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
afterEach(async () => {
|
|
57
|
+
server.close();
|
|
58
|
+
await rm(tmpDir, { recursive: true, force: true });
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
it('serves an uploaded image file with correct content-type and caching headers', async () => {
|
|
62
|
+
const res = await req.get('/api/chat-files/proj_test/test_image.png');
|
|
63
|
+
|
|
64
|
+
assert.equal(res.status, 200);
|
|
65
|
+
assert.equal(res.headers['content-type'], 'image/png');
|
|
66
|
+
assert.ok(res.headers['cache-control']?.includes('immutable'));
|
|
67
|
+
assert.ok(res.headers['cache-control']?.includes('max-age=31536000'));
|
|
68
|
+
assert.ok(res.headers['etag']);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it('serves a text file with correct content-type', async () => {
|
|
72
|
+
const res = await req.get('/api/chat-files/proj_test/doc.txt');
|
|
73
|
+
|
|
74
|
+
assert.equal(res.status, 200);
|
|
75
|
+
assert.equal(res.headers['content-type'], 'text/plain');
|
|
76
|
+
assert.equal(res.text, 'hello world');
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('returns 404 for nonexistent file', async () => {
|
|
80
|
+
const res = await req.get('/api/chat-files/proj_test/nonexistent.png');
|
|
81
|
+
assert.equal(res.status, 404);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it('returns 404 for nonexistent workspace', async () => {
|
|
85
|
+
const { app: badApp } = createTestApp('/nonexistent/path');
|
|
86
|
+
const badServer = await new Promise<Server>((resolve) => {
|
|
87
|
+
const s = badApp.listen(0, '127.0.0.1', () => resolve(s));
|
|
88
|
+
});
|
|
89
|
+
const badReq = request(badServer);
|
|
90
|
+
|
|
91
|
+
const res = await badReq.get('/api/chat-files/proj_test/test.png');
|
|
92
|
+
assert.equal(res.status, 404);
|
|
93
|
+
badServer.close();
|
|
94
|
+
});
|
|
95
|
+
});
|