@bike4mind/cli 0.1.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/README.md +246 -0
- package/bin/bike4mind-cli.mjs +66 -0
- package/dist/artifactExtractor-QN34RFDM.js +181 -0
- package/dist/chunk-BDQBOLYG.js +120 -0
- package/dist/chunk-BPFEGDC7.js +192 -0
- package/dist/chunk-CC67R4RB.js +345 -0
- package/dist/chunk-CPNUKQQ3.js +110 -0
- package/dist/chunk-ETEFNJEP.js +235 -0
- package/dist/chunk-JVPB6BB5.js +10399 -0
- package/dist/chunk-LM6ZFZT6.js +92 -0
- package/dist/chunk-MKO2KCCS.js +6119 -0
- package/dist/chunk-PDX44BCA.js +11 -0
- package/dist/create-AYVZNCEH.js +13 -0
- package/dist/formatConverter-I7EIUVDY.js +8 -0
- package/dist/index.js +7614 -0
- package/dist/llmMarkdownGenerator-JBDLN44A.js +372 -0
- package/dist/markdownGenerator-MM5N3H5I.js +270 -0
- package/dist/mementoService-N5HYIH4Y.js +13 -0
- package/dist/notificationDeduplicator-LQAMED4L.js +10 -0
- package/dist/src-GTQ5UBCP.js +255 -0
- package/dist/src-OVEHYUVN.js +588 -0
- package/dist/subtractCredits-22TZUVZX.js +13 -0
- package/dist/utils-JPMDGUBL.js +32 -0
- package/package.json +111 -0
package/README.md
ADDED
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
# Bike4Mind CLI
|
|
2
|
+
|
|
3
|
+
Interactive command-line interface for Bike4Mind with ReAct agents.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- 🤖 ReAct agent with reasoning and tool use
|
|
8
|
+
- 💬 Interactive chat interface
|
|
9
|
+
- 💾 Session persistence
|
|
10
|
+
- 🛠️ B4M tools + MCP integration
|
|
11
|
+
- 🎨 Rich terminal UI with Ink
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
### From NPM (Recommended)
|
|
16
|
+
|
|
17
|
+
Install globally:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
npm install -g @bike4mind/cli
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or run without installing:
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
npx @bike4mind/cli
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
### From Source (Development)
|
|
30
|
+
|
|
31
|
+
From the project root:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pnpm install
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Development
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Run in development mode
|
|
41
|
+
cd apps/cli
|
|
42
|
+
pnpm dev
|
|
43
|
+
|
|
44
|
+
# Build for production
|
|
45
|
+
pnpm build
|
|
46
|
+
|
|
47
|
+
# Type check
|
|
48
|
+
pnpm typecheck
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Usage
|
|
52
|
+
|
|
53
|
+
### Start Interactive Session
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
b4m
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Quick Query
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
b4m "What's the weather in San Francisco?"
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### With Options
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
# Use specific model
|
|
69
|
+
b4m --model claude-3-5-haiku-20241022
|
|
70
|
+
|
|
71
|
+
# Load saved session
|
|
72
|
+
b4m --session my-project
|
|
73
|
+
|
|
74
|
+
# Debug mode
|
|
75
|
+
b4m --debug
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Commands
|
|
79
|
+
|
|
80
|
+
While in interactive mode:
|
|
81
|
+
|
|
82
|
+
- `/help` - Show help
|
|
83
|
+
- `/exit` - Exit CLI
|
|
84
|
+
- `/save <name>` - Save current session
|
|
85
|
+
- `/sessions` - List saved sessions
|
|
86
|
+
- `/config` - Show configuration
|
|
87
|
+
- `/models` - List available models
|
|
88
|
+
- `/tools` - List available tools
|
|
89
|
+
|
|
90
|
+
## Configuration
|
|
91
|
+
|
|
92
|
+
Configuration is stored in `~/.bike4mind/config.json`
|
|
93
|
+
|
|
94
|
+
### Required: LLM API Keys
|
|
95
|
+
|
|
96
|
+
Set at least one LLM API key in `~/.bike4mind/config.json`:
|
|
97
|
+
|
|
98
|
+
```json
|
|
99
|
+
{
|
|
100
|
+
"apiKeys": {
|
|
101
|
+
"anthropic": "sk-ant-...",
|
|
102
|
+
"openai": "sk-..."
|
|
103
|
+
},
|
|
104
|
+
"defaultModel": "claude-3-5-sonnet-20241022",
|
|
105
|
+
"preferences": {
|
|
106
|
+
"maxTokens": 4096,
|
|
107
|
+
"temperature": 0.7,
|
|
108
|
+
"autoSave": true
|
|
109
|
+
},
|
|
110
|
+
"mcpServers": []
|
|
111
|
+
}
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Optional: Tool API Keys
|
|
115
|
+
|
|
116
|
+
Some tools require additional API keys. Add them to your config file:
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
{
|
|
120
|
+
"apiKeys": {
|
|
121
|
+
"anthropic": "sk-ant-..."
|
|
122
|
+
},
|
|
123
|
+
"toolApiKeys": {
|
|
124
|
+
"openweather": "your-openweather-key",
|
|
125
|
+
"serper": "your-serper-key"
|
|
126
|
+
},
|
|
127
|
+
"defaultModel": "claude-3-5-sonnet-20241022",
|
|
128
|
+
"preferences": {
|
|
129
|
+
"maxTokens": 4096,
|
|
130
|
+
"temperature": 0.7,
|
|
131
|
+
"autoSave": true
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Or use environment variables (config takes precedence):
|
|
137
|
+
|
|
138
|
+
```bash
|
|
139
|
+
export OPENWEATHER_API_KEY="your-key-here"
|
|
140
|
+
export SERPER_API_KEY="your-key-here"
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
**Available tools**:
|
|
144
|
+
- ✅ `dice_roll` - No API key needed
|
|
145
|
+
- ✅ `math_evaluate` - No API key needed
|
|
146
|
+
- ✅ `current_datetime` - No API key needed
|
|
147
|
+
- ✅ `prompt_enhancement` - No API key needed
|
|
148
|
+
- 🔑 `weather_info` - Requires `toolApiKeys.openweather`
|
|
149
|
+
- 🔑 `web_search` - Requires `toolApiKeys.serper`
|
|
150
|
+
- 🔑 `deep_research` - Requires `toolApiKeys.serper`
|
|
151
|
+
|
|
152
|
+
**Get API keys**:
|
|
153
|
+
- OpenWeather API: https://openweathermap.org/api
|
|
154
|
+
- Serper API: https://serper.dev/
|
|
155
|
+
|
|
156
|
+
### Optional: MCP Servers
|
|
157
|
+
|
|
158
|
+
MCP (Model Context Protocol) servers provide additional tools and capabilities. Configure them in your config file:
|
|
159
|
+
|
|
160
|
+
```json
|
|
161
|
+
{
|
|
162
|
+
"apiKeys": {
|
|
163
|
+
"anthropic": "sk-ant-..."
|
|
164
|
+
},
|
|
165
|
+
"mcpServers": [
|
|
166
|
+
{
|
|
167
|
+
"name": "github",
|
|
168
|
+
"command": "node",
|
|
169
|
+
"args": ["path/to/mcp/server.js"],
|
|
170
|
+
"env": {
|
|
171
|
+
"GITHUB_ACCESS_TOKEN": "ghp_..."
|
|
172
|
+
},
|
|
173
|
+
"enabled": true
|
|
174
|
+
}
|
|
175
|
+
]
|
|
176
|
+
}
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
**Available MCP Servers:**
|
|
180
|
+
- 🔧 **GitHub** - Repository management, issues, PRs (requires `GITHUB_ACCESS_TOKEN`)
|
|
181
|
+
- 🔧 **LinkedIn** - Post management, analytics (requires `LINKEDIN_ACCESS_TOKEN`, `COMPANY_NAME`)
|
|
182
|
+
- 🔧 **Atlassian** - Jira/Confluence integration (requires `ATLASSIAN_ACCESS_TOKEN`, `ATLASSIAN_CLOUD_ID`, `ATLASSIAN_SITE_URL`)
|
|
183
|
+
|
|
184
|
+
**Note:** MCP servers must be built and available in the `b4m-core/packages/mcp/dist/src/` directory. The CLI will automatically find them if you're running from the monorepo.
|
|
185
|
+
|
|
186
|
+
## Session Storage
|
|
187
|
+
|
|
188
|
+
Sessions are saved to `~/.bike4mind/sessions/`
|
|
189
|
+
|
|
190
|
+
Each session includes:
|
|
191
|
+
- Full conversation history
|
|
192
|
+
- Token usage tracking
|
|
193
|
+
- Agent reasoning steps
|
|
194
|
+
- Metadata
|
|
195
|
+
|
|
196
|
+
## Architecture
|
|
197
|
+
|
|
198
|
+
```
|
|
199
|
+
apps/cli/
|
|
200
|
+
├── src/
|
|
201
|
+
│ ├── components/ # Ink React components
|
|
202
|
+
│ │ ├── App.tsx
|
|
203
|
+
│ │ ├── StatusBar.tsx
|
|
204
|
+
│ │ ├── MessageList.tsx
|
|
205
|
+
│ │ ├── InputPrompt.tsx
|
|
206
|
+
│ │ └── ThoughtStream.tsx
|
|
207
|
+
│ ├── storage/ # Local persistence
|
|
208
|
+
│ │ ├── SessionStore.ts
|
|
209
|
+
│ │ └── ConfigStore.ts
|
|
210
|
+
│ └── index.tsx # Main entry point
|
|
211
|
+
└── bin/
|
|
212
|
+
└── bike4mind-cli.js # Executable
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Dependencies
|
|
216
|
+
|
|
217
|
+
- `@bike4mind/agents` - ReAct agent implementation
|
|
218
|
+
- `@bike4mind/services` - B4M tools
|
|
219
|
+
- `@bike4mind/mcp` - MCP integration
|
|
220
|
+
- `@bike4mind/utils` - LLM backends
|
|
221
|
+
- `ink` - React for CLIs
|
|
222
|
+
- `commander` - CLI argument parsing
|
|
223
|
+
- `lowdb` - Local storage
|
|
224
|
+
|
|
225
|
+
## Development Status
|
|
226
|
+
|
|
227
|
+
Current implementation includes:
|
|
228
|
+
|
|
229
|
+
✅ Basic CLI structure
|
|
230
|
+
✅ ReAct agent integration
|
|
231
|
+
✅ Session persistence
|
|
232
|
+
✅ Configuration management
|
|
233
|
+
✅ Interactive UI with Ink
|
|
234
|
+
✅ B4M tools integration
|
|
235
|
+
✅ MCP tools support
|
|
236
|
+
|
|
237
|
+
Coming soon:
|
|
238
|
+
|
|
239
|
+
⏳ Advanced UI features (streaming visualization, syntax highlighting)
|
|
240
|
+
⏳ Export functionality (sessions to markdown/JSON)
|
|
241
|
+
⏳ Session search and management
|
|
242
|
+
⏳ Tool execution monitoring
|
|
243
|
+
|
|
244
|
+
## License
|
|
245
|
+
|
|
246
|
+
Private - Bike4Mind
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/* eslint-disable */
|
|
3
|
+
|
|
4
|
+
// This is the executable entry point for the CLI
|
|
5
|
+
// In development, it will use tsx to run the TypeScript directly
|
|
6
|
+
// In production, it will run the compiled JavaScript
|
|
7
|
+
|
|
8
|
+
// Suppress punycode deprecation warning from dependencies
|
|
9
|
+
// This is a known issue with older npm packages using Node's built-in punycode
|
|
10
|
+
process.removeAllListeners('warning');
|
|
11
|
+
process.on('warning', (warning) => {
|
|
12
|
+
// Only suppress punycode deprecation warnings
|
|
13
|
+
if (warning.name === 'DeprecationWarning' && warning.message.includes('punycode')) {
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
// Show all other warnings
|
|
17
|
+
console.warn(warning);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
import { createRequire } from 'module';
|
|
21
|
+
import { fileURLToPath } from 'url';
|
|
22
|
+
import { dirname, join } from 'path';
|
|
23
|
+
import { existsSync } from 'fs';
|
|
24
|
+
|
|
25
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
26
|
+
const __dirname = dirname(__filename);
|
|
27
|
+
const require = createRequire(import.meta.url);
|
|
28
|
+
|
|
29
|
+
// Auto-detect environment: prefer production mode when dist exists
|
|
30
|
+
const distPath = join(__dirname, '../dist/index.js');
|
|
31
|
+
const srcPath = join(__dirname, '../src/index.tsx');
|
|
32
|
+
const hasSource = existsSync(srcPath);
|
|
33
|
+
const hasDist = existsSync(distPath);
|
|
34
|
+
|
|
35
|
+
// Development mode ONLY when:
|
|
36
|
+
// 1. NODE_ENV is explicitly 'development', OR
|
|
37
|
+
// 2. dist doesn't exist but source does (fallback)
|
|
38
|
+
// Otherwise: use production mode (this is what npm users will run)
|
|
39
|
+
const isDev = process.env.NODE_ENV === 'development' ||
|
|
40
|
+
(!hasDist && hasSource);
|
|
41
|
+
|
|
42
|
+
if (isDev) {
|
|
43
|
+
// Show dev mode indicator for developers
|
|
44
|
+
console.log('🔧 Running in development mode (using TypeScript source)\n');
|
|
45
|
+
// Development: use tsx to run TypeScript
|
|
46
|
+
try {
|
|
47
|
+
const { register } = require('tsx/esm/api');
|
|
48
|
+
register();
|
|
49
|
+
|
|
50
|
+
// Import and run the main app
|
|
51
|
+
await import(join(__dirname, '../src/index.tsx'));
|
|
52
|
+
} catch (error) {
|
|
53
|
+
console.error('Failed to start CLI in development mode:', error);
|
|
54
|
+
console.error('\nTry running: pnpm install');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
// Production: run compiled JavaScript
|
|
59
|
+
try {
|
|
60
|
+
await import(join(__dirname, '../dist/index.js'));
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('Failed to start CLI:', error);
|
|
63
|
+
console.error('\nTry running: pnpm build');
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
CurationArtifactType
|
|
4
|
+
} from "./chunk-MKO2KCCS.js";
|
|
5
|
+
import "./chunk-PDX44BCA.js";
|
|
6
|
+
|
|
7
|
+
// ../../b4m-core/packages/services/dist/src/notebookCurationService/artifactExtractor.js
|
|
8
|
+
var ARTIFACT_TAG_REGEX = /<artifact\s+(.*?)>([\s\S]*?)<\/artifact>/gi;
|
|
9
|
+
var ATTRIBUTE_REGEX = /(\w+)=["']([^"']*?)["']/g;
|
|
10
|
+
var CODE_BLOCK_REGEX = /```(\w+)?\s*([\s\S]*?)```/g;
|
|
11
|
+
function extractArtifactsFromMessage(message, options) {
|
|
12
|
+
const artifacts = [];
|
|
13
|
+
const messageId = message.id || message._id?.toString() || "unknown";
|
|
14
|
+
const timestamp = message.timestamp ? new Date(message.timestamp) : /* @__PURE__ */ new Date();
|
|
15
|
+
const textContent = [
|
|
16
|
+
message.prompt || "",
|
|
17
|
+
message.reply || "",
|
|
18
|
+
...message.replies || [],
|
|
19
|
+
message.questMasterReply || ""
|
|
20
|
+
].filter(Boolean).join("\n\n");
|
|
21
|
+
if (!textContent) {
|
|
22
|
+
return artifacts;
|
|
23
|
+
}
|
|
24
|
+
if (options.includeCode || options.includeDiagrams || options.includeDataViz) {
|
|
25
|
+
const tagArtifacts = extractArtifactTags(textContent, messageId, timestamp);
|
|
26
|
+
artifacts.push(...tagArtifacts);
|
|
27
|
+
}
|
|
28
|
+
if (options.includeCode) {
|
|
29
|
+
const codeArtifacts = extractCodeBlocks(textContent, messageId, timestamp);
|
|
30
|
+
artifacts.push(...codeArtifacts);
|
|
31
|
+
}
|
|
32
|
+
if (options.includeQuestMaster && message.questMasterPlanId) {
|
|
33
|
+
artifacts.push({
|
|
34
|
+
type: CurationArtifactType.QUESTMASTER_PLAN,
|
|
35
|
+
content: message.questMasterPlanId,
|
|
36
|
+
messageId,
|
|
37
|
+
timestamp,
|
|
38
|
+
metadata: {
|
|
39
|
+
planId: message.questMasterPlanId
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
if (options.includeResearch && message.deepResearchState) {
|
|
44
|
+
const research = message.deepResearchState;
|
|
45
|
+
artifacts.push({
|
|
46
|
+
type: CurationArtifactType.DEEP_RESEARCH,
|
|
47
|
+
content: JSON.stringify(research, null, 2),
|
|
48
|
+
messageId,
|
|
49
|
+
timestamp,
|
|
50
|
+
metadata: {
|
|
51
|
+
findingsCount: research.findings?.length || 0,
|
|
52
|
+
sourcesCount: research.sources?.length || 0,
|
|
53
|
+
depth: research.depth,
|
|
54
|
+
completed: research.completed,
|
|
55
|
+
topic: research.topic
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
if (options.includeImages && message.images && message.images.length > 0) {
|
|
60
|
+
message.images.forEach((imagePath, index) => {
|
|
61
|
+
artifacts.push({
|
|
62
|
+
type: CurationArtifactType.IMAGE,
|
|
63
|
+
content: imagePath,
|
|
64
|
+
messageId,
|
|
65
|
+
timestamp,
|
|
66
|
+
metadata: {
|
|
67
|
+
index,
|
|
68
|
+
path: imagePath
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
return artifacts;
|
|
74
|
+
}
|
|
75
|
+
function extractArtifactTags(content, messageId, timestamp) {
|
|
76
|
+
const artifacts = [];
|
|
77
|
+
let match;
|
|
78
|
+
ARTIFACT_TAG_REGEX.lastIndex = 0;
|
|
79
|
+
while ((match = ARTIFACT_TAG_REGEX.exec(content)) !== null) {
|
|
80
|
+
const [, attributesString, artifactContent] = match;
|
|
81
|
+
const attributes = {};
|
|
82
|
+
let attrMatch;
|
|
83
|
+
ATTRIBUTE_REGEX.lastIndex = 0;
|
|
84
|
+
while ((attrMatch = ATTRIBUTE_REGEX.exec(attributesString)) !== null) {
|
|
85
|
+
const [, key, value] = attrMatch;
|
|
86
|
+
attributes[key] = value;
|
|
87
|
+
}
|
|
88
|
+
const mimeType = attributes.type || "";
|
|
89
|
+
const title = attributes.title || "Untitled";
|
|
90
|
+
const language = attributes.language;
|
|
91
|
+
const identifier = attributes.identifier;
|
|
92
|
+
const artifactType = mapMimeTypeToArtifactType(mimeType);
|
|
93
|
+
if (artifactType) {
|
|
94
|
+
artifacts.push({
|
|
95
|
+
type: artifactType,
|
|
96
|
+
content: artifactContent.trim(),
|
|
97
|
+
language: language || inferLanguageFromType(artifactType),
|
|
98
|
+
messageId,
|
|
99
|
+
timestamp,
|
|
100
|
+
metadata: {
|
|
101
|
+
title,
|
|
102
|
+
identifier,
|
|
103
|
+
mimeType
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
return artifacts;
|
|
109
|
+
}
|
|
110
|
+
function extractCodeBlocks(content, messageId, timestamp) {
|
|
111
|
+
const artifacts = [];
|
|
112
|
+
let match;
|
|
113
|
+
CODE_BLOCK_REGEX.lastIndex = 0;
|
|
114
|
+
while ((match = CODE_BLOCK_REGEX.exec(content)) !== null) {
|
|
115
|
+
const [, language, codeContent] = match;
|
|
116
|
+
if (!codeContent || !codeContent.trim()) {
|
|
117
|
+
continue;
|
|
118
|
+
}
|
|
119
|
+
const lineCount = codeContent.trim().split("\n").length;
|
|
120
|
+
if (lineCount < 3) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
const lang = (language || "text").toLowerCase();
|
|
124
|
+
let artifactType;
|
|
125
|
+
if (["mermaid"].includes(lang)) {
|
|
126
|
+
artifactType = CurationArtifactType.MERMAID;
|
|
127
|
+
} else if (["recharts"].includes(lang)) {
|
|
128
|
+
artifactType = CurationArtifactType.RECHARTS;
|
|
129
|
+
} else if (["svg", "xml"].includes(lang)) {
|
|
130
|
+
artifactType = CurationArtifactType.SVG;
|
|
131
|
+
} else if (["html", "htm"].includes(lang)) {
|
|
132
|
+
artifactType = CurationArtifactType.HTML;
|
|
133
|
+
} else if (["tsx", "jsx", "javascript", "typescript", "react"].includes(lang) && (codeContent.includes("useState") || codeContent.includes("useEffect") || codeContent.includes("export default"))) {
|
|
134
|
+
artifactType = CurationArtifactType.REACT;
|
|
135
|
+
} else {
|
|
136
|
+
artifactType = CurationArtifactType.CODE;
|
|
137
|
+
}
|
|
138
|
+
artifacts.push({
|
|
139
|
+
type: artifactType,
|
|
140
|
+
content: codeContent.trim(),
|
|
141
|
+
language: lang || "text",
|
|
142
|
+
messageId,
|
|
143
|
+
timestamp,
|
|
144
|
+
metadata: {
|
|
145
|
+
lineCount
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
}
|
|
149
|
+
return artifacts;
|
|
150
|
+
}
|
|
151
|
+
function mapMimeTypeToArtifactType(mimeType) {
|
|
152
|
+
const mimeMap = {
|
|
153
|
+
"application/vnd.ant.react": CurationArtifactType.REACT,
|
|
154
|
+
"text/html": CurationArtifactType.HTML,
|
|
155
|
+
"image/svg+xml": CurationArtifactType.SVG,
|
|
156
|
+
"application/vnd.ant.mermaid": CurationArtifactType.MERMAID,
|
|
157
|
+
"application/vnd.ant.recharts": CurationArtifactType.RECHARTS,
|
|
158
|
+
"application/vnd.ant.code": CurationArtifactType.CODE,
|
|
159
|
+
"text/x.python": CurationArtifactType.CODE,
|
|
160
|
+
"text/x.typescript": CurationArtifactType.CODE,
|
|
161
|
+
"text/x.javascript": CurationArtifactType.CODE
|
|
162
|
+
};
|
|
163
|
+
return mimeMap[mimeType] || null;
|
|
164
|
+
}
|
|
165
|
+
function inferLanguageFromType(type) {
|
|
166
|
+
const languageMap = {
|
|
167
|
+
[CurationArtifactType.CODE]: "typescript",
|
|
168
|
+
[CurationArtifactType.REACT]: "tsx",
|
|
169
|
+
[CurationArtifactType.MERMAID]: "mermaid",
|
|
170
|
+
[CurationArtifactType.RECHARTS]: "json",
|
|
171
|
+
[CurationArtifactType.SVG]: "xml",
|
|
172
|
+
[CurationArtifactType.HTML]: "html",
|
|
173
|
+
[CurationArtifactType.QUESTMASTER_PLAN]: void 0,
|
|
174
|
+
[CurationArtifactType.DEEP_RESEARCH]: "json",
|
|
175
|
+
[CurationArtifactType.IMAGE]: void 0
|
|
176
|
+
};
|
|
177
|
+
return languageMap[type];
|
|
178
|
+
}
|
|
179
|
+
export {
|
|
180
|
+
extractArtifactsFromMessage
|
|
181
|
+
};
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// ../../b4m-core/packages/services/dist/src/mfaService/utils.js
|
|
4
|
+
import speakeasy from "speakeasy";
|
|
5
|
+
import QRCode from "qrcode";
|
|
6
|
+
var originalEmitWarning = process.emitWarning;
|
|
7
|
+
process.emitWarning = (warning, name) => {
|
|
8
|
+
if (name === "DeprecationWarning" && warning?.toString().includes("Buffer")) {
|
|
9
|
+
return;
|
|
10
|
+
}
|
|
11
|
+
return originalEmitWarning.call(process, warning, name);
|
|
12
|
+
};
|
|
13
|
+
process.emitWarning = originalEmitWarning;
|
|
14
|
+
async function generateTOTPSetup(userEmail, appName = "Bike4Mind") {
|
|
15
|
+
const secret = speakeasy.generateSecret({ name: `${appName} (${userEmail})` });
|
|
16
|
+
const qrCodeUrl = await QRCode.toDataURL(secret.otpauth_url);
|
|
17
|
+
return {
|
|
18
|
+
secret: secret.base32,
|
|
19
|
+
qrCodeUrl,
|
|
20
|
+
manualEntryKey: secret.base32
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function verifyTOTPToken(secret, token, window = 1) {
|
|
24
|
+
return speakeasy.totp.verify({
|
|
25
|
+
secret,
|
|
26
|
+
encoding: "base32",
|
|
27
|
+
token,
|
|
28
|
+
window
|
|
29
|
+
// Allow ±30 seconds for clock drift (industry standard)
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
function generateBackupCodes(count = 10) {
|
|
33
|
+
return Array.from({ length: count }, () => {
|
|
34
|
+
const secret = speakeasy.generateSecret({ length: 20 });
|
|
35
|
+
return secret.base32.substring(0, 10).toUpperCase();
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function verifyBackupCode(userBackupCodes, providedCode) {
|
|
39
|
+
if (!userBackupCodes || !providedCode) {
|
|
40
|
+
return { isValid: false };
|
|
41
|
+
}
|
|
42
|
+
const cleanProvidedCode = providedCode.trim().toUpperCase();
|
|
43
|
+
const idx = userBackupCodes.findIndex((code) => {
|
|
44
|
+
const cleanStoredCode = code.trim().toUpperCase();
|
|
45
|
+
return cleanStoredCode === cleanProvidedCode;
|
|
46
|
+
});
|
|
47
|
+
if (idx !== -1) {
|
|
48
|
+
return { isValid: true, usedBackupCode: userBackupCodes[idx] };
|
|
49
|
+
}
|
|
50
|
+
return { isValid: false };
|
|
51
|
+
}
|
|
52
|
+
function userRequiresMFA(user, enforceMFASetting) {
|
|
53
|
+
return enforceMFASetting;
|
|
54
|
+
}
|
|
55
|
+
function userHasMFAConfigured(user) {
|
|
56
|
+
return !!(user.mfa && user.mfa.totpEnabled && user.mfa.totpSecret);
|
|
57
|
+
}
|
|
58
|
+
var MAX_FAILED_ATTEMPTS = 3;
|
|
59
|
+
var LOCKOUT_DURATION_MS = 15 * 60 * 1e3;
|
|
60
|
+
var ATTEMPT_RESET_WINDOW_MS = 60 * 60 * 1e3;
|
|
61
|
+
function isUserLockedOut(user) {
|
|
62
|
+
if (!user.mfa?.lockedUntil)
|
|
63
|
+
return false;
|
|
64
|
+
return /* @__PURE__ */ new Date() < new Date(user.mfa.lockedUntil);
|
|
65
|
+
}
|
|
66
|
+
function getLockoutTimeRemaining(user) {
|
|
67
|
+
if (!user.mfa?.lockedUntil)
|
|
68
|
+
return 0;
|
|
69
|
+
const remaining = new Date(user.mfa.lockedUntil).getTime() - Date.now();
|
|
70
|
+
return Math.max(0, Math.ceil(remaining / (60 * 1e3)));
|
|
71
|
+
}
|
|
72
|
+
function shouldResetFailedAttempts(user) {
|
|
73
|
+
if (!user.mfa?.lastFailedAttempt)
|
|
74
|
+
return false;
|
|
75
|
+
const timeSinceLastFailure = Date.now() - new Date(user.mfa.lastFailedAttempt).getTime();
|
|
76
|
+
return timeSinceLastFailure > ATTEMPT_RESET_WINDOW_MS;
|
|
77
|
+
}
|
|
78
|
+
function recordFailedAttempt(user) {
|
|
79
|
+
const currentAttempts = shouldResetFailedAttempts(user) ? 0 : user.mfa?.failedAttempts || 0;
|
|
80
|
+
const newAttempts = currentAttempts + 1;
|
|
81
|
+
const now = /* @__PURE__ */ new Date();
|
|
82
|
+
const updatedMFA = { ...user.mfa };
|
|
83
|
+
updatedMFA.failedAttempts = newAttempts;
|
|
84
|
+
updatedMFA.lastFailedAttempt = now;
|
|
85
|
+
if (newAttempts >= MAX_FAILED_ATTEMPTS) {
|
|
86
|
+
updatedMFA.lockedUntil = new Date(Date.now() + LOCKOUT_DURATION_MS);
|
|
87
|
+
}
|
|
88
|
+
return updatedMFA;
|
|
89
|
+
}
|
|
90
|
+
function clearFailedAttempts(user) {
|
|
91
|
+
if (!user.mfa)
|
|
92
|
+
return null;
|
|
93
|
+
const updatedMFA = { ...user.mfa };
|
|
94
|
+
updatedMFA.failedAttempts = 0;
|
|
95
|
+
updatedMFA.lastFailedAttempt = void 0;
|
|
96
|
+
updatedMFA.lockedUntil = void 0;
|
|
97
|
+
return updatedMFA;
|
|
98
|
+
}
|
|
99
|
+
function userEligibleForMFA(user) {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
function userCanDisableMFA(user, enforceMFASetting) {
|
|
103
|
+
return !enforceMFASetting;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export {
|
|
107
|
+
generateTOTPSetup,
|
|
108
|
+
verifyTOTPToken,
|
|
109
|
+
generateBackupCodes,
|
|
110
|
+
verifyBackupCode,
|
|
111
|
+
userRequiresMFA,
|
|
112
|
+
userHasMFAConfigured,
|
|
113
|
+
isUserLockedOut,
|
|
114
|
+
getLockoutTimeRemaining,
|
|
115
|
+
shouldResetFailedAttempts,
|
|
116
|
+
recordFailedAttempt,
|
|
117
|
+
clearFailedAttempts,
|
|
118
|
+
userEligibleForMFA,
|
|
119
|
+
userCanDisableMFA
|
|
120
|
+
};
|