fss-link 1.6.1 → 1.6.12
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/LICENSE +1 -0
- package/README.md +63 -316
- package/bundle/fss-link.js +4038 -134262
- package/docs/CONFIGURATION.md +200 -0
- package/docs/README.md +192 -0
- package/docs/TOOLS.md +268 -0
- package/docs/assets/batch_0001_fss-link-terminal-interface.png +0 -0
- package/docs/assets/batch_0002_multiprovider-connection-hub-a.png +0 -0
- package/docs/assets/batch_0003_database-persistence-system-a.png +0 -0
- package/docs/assets/batch_0004_context-window-compression-a.png +0 -0
- package/docs/assets/batch_0005_file-operations-with-safety.png +0 -0
- package/docs/assets/batch_0006_semantic-search-rag-system.png +0 -0
- package/docs/assets/batch_0008_document-parsing-suite-a.png +0 -0
- package/docs/assets/batch_0009_web-research-tools-a.png +0 -0
- package/docs/assets/batch_0010_texttospeech-voice-output-a.png +0 -0
- package/docs/assets/batch_0011_shell-command-execution-a.png +0 -0
- package/docs/assets/batch_0012_mcp-extension-system-a.png +0 -0
- package/docs/assets/batch_0014_approval-mode-switches-a.png +0 -0
- package/docs/assets/batch_0015_folder-trust-system-a.png +0 -0
- package/package.json +53 -38
- package/scripts/check-publish.js +2 -1
- package/scripts/install-linux.sh +1 -2
- package/scripts/install-macos.sh +1 -2
- package/scripts/install-windows.ps1 +1 -2
- package/scripts/prebundle-sync-dist.js +51 -0
- package/README.pdf +0 -0
- package/scripts/analyze-session-logs.sh +0 -279
- package/scripts/emergency-kill-all-tests.sh +0 -95
- package/scripts/emergency-kill-vitest.sh +0 -95
- package/scripts/extract-session-logs.sh +0 -202
- package/scripts/get-previous-tag.js +0 -213
- package/scripts/get-release-version.js +0 -119
- package/scripts/index-session-logs.sh +0 -173
- package/scripts/local_telemetry.js +0 -219
- package/scripts/memory-monitor.sh +0 -165
- package/scripts/process-session-log.py +0 -302
- package/scripts/quick-install.sh +0 -195
- package/scripts/telemetry_gcp.js +0 -188
- package/scripts/telemetry_utils.js +0 -421
- package/scripts/test-windows-paths.js +0 -51
- package/scripts/tests/get-release-version.test.js +0 -110
- package/scripts/tests/test-setup.ts +0 -12
- package/scripts/tests/vitest.config.ts +0 -20
|
@@ -1,302 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env python3
|
|
2
|
-
"""
|
|
3
|
-
FSS Link Session Log Processor
|
|
4
|
-
Converts JSON session logs to markdown format optimized for RAG indexing
|
|
5
|
-
"""
|
|
6
|
-
|
|
7
|
-
import json
|
|
8
|
-
import sys
|
|
9
|
-
from pathlib import Path
|
|
10
|
-
from datetime import datetime
|
|
11
|
-
from typing import Dict, List, Any, Optional
|
|
12
|
-
import re
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
class SessionLogProcessor:
|
|
16
|
-
"""Process FSS Link session logs into RAG-optimized markdown"""
|
|
17
|
-
|
|
18
|
-
def __init__(self, input_path: str, output_path: str):
|
|
19
|
-
self.input_path = Path(input_path)
|
|
20
|
-
self.output_path = Path(output_path)
|
|
21
|
-
self.session_data: Dict[str, Any] = {}
|
|
22
|
-
self.metadata: Dict[str, Any] = {}
|
|
23
|
-
self.tool_calls: List[Dict[str, Any]] = []
|
|
24
|
-
self.errors: List[Dict[str, Any]] = []
|
|
25
|
-
|
|
26
|
-
def load_session(self) -> None:
|
|
27
|
-
"""Load session log JSON file"""
|
|
28
|
-
try:
|
|
29
|
-
with open(self.input_path, 'r', encoding='utf-8') as f:
|
|
30
|
-
data = json.load(f)
|
|
31
|
-
# Handle both array format and object format
|
|
32
|
-
if isinstance(data, list):
|
|
33
|
-
self.session_data = {'messages': data}
|
|
34
|
-
else:
|
|
35
|
-
self.session_data = data
|
|
36
|
-
except Exception as e:
|
|
37
|
-
print(f"Error loading session file: {e}", file=sys.stderr)
|
|
38
|
-
sys.exit(1)
|
|
39
|
-
|
|
40
|
-
def extract_metadata(self) -> None:
|
|
41
|
-
"""Extract session metadata from JSON structure (called after analyze_conversation)"""
|
|
42
|
-
messages = self.session_data.get('messages', [])
|
|
43
|
-
|
|
44
|
-
# Extract session ID from file path
|
|
45
|
-
session_id = self.input_path.parent.name
|
|
46
|
-
|
|
47
|
-
# Count messages by role
|
|
48
|
-
user_messages = sum(1 for m in messages if m.get('role') == 'user')
|
|
49
|
-
model_messages = sum(1 for m in messages if m.get('role') == 'model')
|
|
50
|
-
|
|
51
|
-
# Estimate session duration from file modification time
|
|
52
|
-
file_mtime = self.input_path.stat().st_mtime
|
|
53
|
-
session_date = datetime.fromtimestamp(file_mtime)
|
|
54
|
-
|
|
55
|
-
# Get unique tools from tool_calls analysis
|
|
56
|
-
tools_used = sorted(list(set(call['tool'] for call in self.tool_calls)))
|
|
57
|
-
|
|
58
|
-
self.metadata = {
|
|
59
|
-
'session_id': session_id,
|
|
60
|
-
'session_date': session_date.isoformat(),
|
|
61
|
-
'total_messages': len(messages),
|
|
62
|
-
'user_messages': user_messages,
|
|
63
|
-
'model_messages': model_messages,
|
|
64
|
-
'tools_used': tools_used,
|
|
65
|
-
'total_tool_calls': len(self.tool_calls),
|
|
66
|
-
'total_errors': len(self.errors),
|
|
67
|
-
'source_file': str(self.input_path),
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
def _get_message_text(self, message: Dict[str, Any]) -> str:
|
|
71
|
-
"""Extract text content from message structure"""
|
|
72
|
-
parts = message.get('parts', [])
|
|
73
|
-
if not parts:
|
|
74
|
-
return ""
|
|
75
|
-
|
|
76
|
-
# Handle both string and object parts
|
|
77
|
-
text_parts = []
|
|
78
|
-
for part in parts:
|
|
79
|
-
if isinstance(part, str):
|
|
80
|
-
text_parts.append(part)
|
|
81
|
-
elif isinstance(part, dict):
|
|
82
|
-
if 'text' in part:
|
|
83
|
-
# Handle both string and list formats for text
|
|
84
|
-
text = part['text']
|
|
85
|
-
if isinstance(text, list):
|
|
86
|
-
# Join list items (streaming format)
|
|
87
|
-
text_parts.append(''.join(str(t) for t in text))
|
|
88
|
-
else:
|
|
89
|
-
text_parts.append(str(text))
|
|
90
|
-
elif 'functionCall' in part:
|
|
91
|
-
# Function call representation
|
|
92
|
-
func = part['functionCall']
|
|
93
|
-
func_name = func.get('name', 'unknown')
|
|
94
|
-
text_parts.append(f"[FUNCTION CALL: {func_name}]")
|
|
95
|
-
|
|
96
|
-
return ''.join(text_parts) # Use join without separator for streaming format
|
|
97
|
-
|
|
98
|
-
def analyze_conversation(self) -> None:
|
|
99
|
-
"""Analyze conversation for tool calls, errors, and key events"""
|
|
100
|
-
messages = self.session_data.get('messages', [])
|
|
101
|
-
|
|
102
|
-
for idx, msg in enumerate(messages):
|
|
103
|
-
role = msg.get('role', 'unknown')
|
|
104
|
-
content = self._get_message_text(msg)
|
|
105
|
-
parts = msg.get('parts', [])
|
|
106
|
-
|
|
107
|
-
if not content:
|
|
108
|
-
continue
|
|
109
|
-
|
|
110
|
-
# Detect function calls from parts structure
|
|
111
|
-
for part in parts:
|
|
112
|
-
if isinstance(part, dict) and 'functionCall' in part:
|
|
113
|
-
func_name = part['functionCall'].get('name', 'unknown')
|
|
114
|
-
self.tool_calls.append({
|
|
115
|
-
'message_index': idx,
|
|
116
|
-
'tool': func_name,
|
|
117
|
-
'role': role,
|
|
118
|
-
'content_preview': f"[FUNCTION CALL: {func_name}]"
|
|
119
|
-
})
|
|
120
|
-
|
|
121
|
-
# Also detect from text content patterns
|
|
122
|
-
tool_patterns = {
|
|
123
|
-
'read_file': r'\[FUNCTION CALL: read_file\]',
|
|
124
|
-
'grep': r'\[FUNCTION CALL: search_file_content\]',
|
|
125
|
-
'glob': r'\[FUNCTION CALL: glob\]',
|
|
126
|
-
'TodoWrite': r'\[FUNCTION CALL: todo_write\]',
|
|
127
|
-
'bash': r'\[FUNCTION CALL: run_shell_command\]',
|
|
128
|
-
'write': r'\[FUNCTION CALL: write_file\]',
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
for tool_name, pattern in tool_patterns.items():
|
|
132
|
-
if re.search(pattern, content, re.IGNORECASE):
|
|
133
|
-
# Only add if not already detected from functionCall
|
|
134
|
-
already_detected = any(
|
|
135
|
-
call['message_index'] == idx and tool_name in call['tool'].lower()
|
|
136
|
-
for call in self.tool_calls
|
|
137
|
-
)
|
|
138
|
-
if not already_detected:
|
|
139
|
-
self.tool_calls.append({
|
|
140
|
-
'message_index': idx,
|
|
141
|
-
'tool': tool_name,
|
|
142
|
-
'role': role,
|
|
143
|
-
'content_preview': content[:200]
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
# Detect errors
|
|
147
|
-
error_patterns = [
|
|
148
|
-
r'error:',
|
|
149
|
-
r'failed:',
|
|
150
|
-
r'exception:',
|
|
151
|
-
r'traceback',
|
|
152
|
-
r'❌',
|
|
153
|
-
]
|
|
154
|
-
for pattern in error_patterns:
|
|
155
|
-
if re.search(pattern, content, re.IGNORECASE):
|
|
156
|
-
self.errors.append({
|
|
157
|
-
'message_index': idx,
|
|
158
|
-
'role': role,
|
|
159
|
-
'error_preview': content[:300]
|
|
160
|
-
})
|
|
161
|
-
break
|
|
162
|
-
|
|
163
|
-
def generate_markdown(self) -> str:
|
|
164
|
-
"""Generate RAG-optimized markdown output"""
|
|
165
|
-
lines = []
|
|
166
|
-
|
|
167
|
-
# Document header with metadata
|
|
168
|
-
lines.append(f"# FSS Link Session Log: {self.metadata['session_id'][:16]}...")
|
|
169
|
-
lines.append("")
|
|
170
|
-
lines.append(f"**Session Date**: {self.metadata['session_date']}")
|
|
171
|
-
lines.append(f"**Total Messages**: {self.metadata['total_messages']}")
|
|
172
|
-
lines.append(f"**User Messages**: {self.metadata['user_messages']}")
|
|
173
|
-
lines.append(f"**Model Messages**: {self.metadata['model_messages']}")
|
|
174
|
-
lines.append(f"**Tools Used**: {', '.join(self.metadata['tools_used']) if self.metadata['tools_used'] else 'None detected'}")
|
|
175
|
-
lines.append("")
|
|
176
|
-
lines.append("---")
|
|
177
|
-
lines.append("")
|
|
178
|
-
|
|
179
|
-
# Session summary
|
|
180
|
-
lines.append("## Session Summary")
|
|
181
|
-
lines.append("")
|
|
182
|
-
lines.append(f"This session contained {self.metadata['total_messages']} messages with "
|
|
183
|
-
f"{len(self.tool_calls)} tool calls and {len(self.errors)} errors detected.")
|
|
184
|
-
lines.append("")
|
|
185
|
-
|
|
186
|
-
# Tool calls section
|
|
187
|
-
if self.tool_calls:
|
|
188
|
-
lines.append("## Tool Calls Detected")
|
|
189
|
-
lines.append("")
|
|
190
|
-
|
|
191
|
-
# Group by tool
|
|
192
|
-
tools_grouped = {}
|
|
193
|
-
for call in self.tool_calls:
|
|
194
|
-
tool = call['tool']
|
|
195
|
-
if tool not in tools_grouped:
|
|
196
|
-
tools_grouped[tool] = []
|
|
197
|
-
tools_grouped[tool].append(call)
|
|
198
|
-
|
|
199
|
-
for tool, calls in sorted(tools_grouped.items()):
|
|
200
|
-
lines.append(f"### {tool} ({len(calls)} calls)")
|
|
201
|
-
lines.append("")
|
|
202
|
-
for call in calls[:5]: # Limit to first 5 per tool
|
|
203
|
-
lines.append(f"**Message {call['message_index']}** ({call['role']}):")
|
|
204
|
-
lines.append(f"```")
|
|
205
|
-
lines.append(call['content_preview'])
|
|
206
|
-
lines.append(f"```")
|
|
207
|
-
lines.append("")
|
|
208
|
-
|
|
209
|
-
if len(calls) > 5:
|
|
210
|
-
lines.append(f"*... and {len(calls) - 5} more calls*")
|
|
211
|
-
lines.append("")
|
|
212
|
-
|
|
213
|
-
# Errors section
|
|
214
|
-
if self.errors:
|
|
215
|
-
lines.append("## Errors and Issues")
|
|
216
|
-
lines.append("")
|
|
217
|
-
for idx, error in enumerate(self.errors[:10]): # Limit to first 10
|
|
218
|
-
lines.append(f"### Error {idx + 1} (Message {error['message_index']})")
|
|
219
|
-
lines.append("")
|
|
220
|
-
lines.append(f"**Role**: {error['role']}")
|
|
221
|
-
lines.append("")
|
|
222
|
-
lines.append("```")
|
|
223
|
-
lines.append(error['error_preview'])
|
|
224
|
-
lines.append("```")
|
|
225
|
-
lines.append("")
|
|
226
|
-
|
|
227
|
-
if len(self.errors) > 10:
|
|
228
|
-
lines.append(f"*... and {len(self.errors) - 10} more errors*")
|
|
229
|
-
lines.append("")
|
|
230
|
-
|
|
231
|
-
# Full conversation
|
|
232
|
-
lines.append("## Full Conversation")
|
|
233
|
-
lines.append("")
|
|
234
|
-
|
|
235
|
-
messages = self.session_data.get('messages', [])
|
|
236
|
-
for idx, msg in enumerate(messages):
|
|
237
|
-
role = msg.get('role', 'unknown')
|
|
238
|
-
content = self._get_message_text(msg)
|
|
239
|
-
|
|
240
|
-
if not content:
|
|
241
|
-
continue
|
|
242
|
-
|
|
243
|
-
# Limit very long messages
|
|
244
|
-
if len(content) > 5000:
|
|
245
|
-
content = content[:5000] + "\n\n*[Message truncated for length]*"
|
|
246
|
-
|
|
247
|
-
lines.append(f"### Message {idx}: {role.upper()}")
|
|
248
|
-
lines.append("")
|
|
249
|
-
lines.append(content)
|
|
250
|
-
lines.append("")
|
|
251
|
-
lines.append("---")
|
|
252
|
-
lines.append("")
|
|
253
|
-
|
|
254
|
-
# Metadata footer for RAG
|
|
255
|
-
lines.append("## Metadata")
|
|
256
|
-
lines.append("")
|
|
257
|
-
lines.append("```json")
|
|
258
|
-
lines.append(json.dumps(self.metadata, indent=2))
|
|
259
|
-
lines.append("```")
|
|
260
|
-
lines.append("")
|
|
261
|
-
|
|
262
|
-
return '\n'.join(lines)
|
|
263
|
-
|
|
264
|
-
def process(self) -> None:
|
|
265
|
-
"""Main processing workflow"""
|
|
266
|
-
print(f"Processing session log: {self.input_path.name}")
|
|
267
|
-
|
|
268
|
-
# Load and analyze (order matters: analyze first, then extract metadata)
|
|
269
|
-
self.load_session()
|
|
270
|
-
self.analyze_conversation()
|
|
271
|
-
self.extract_metadata()
|
|
272
|
-
|
|
273
|
-
# Generate markdown
|
|
274
|
-
markdown = self.generate_markdown()
|
|
275
|
-
|
|
276
|
-
# Write output
|
|
277
|
-
self.output_path.parent.mkdir(parents=True, exist_ok=True)
|
|
278
|
-
with open(self.output_path, 'w', encoding='utf-8') as f:
|
|
279
|
-
f.write(markdown)
|
|
280
|
-
|
|
281
|
-
print(f"✓ Processed successfully")
|
|
282
|
-
print(f" - Tools used: {', '.join(self.metadata['tools_used']) if self.metadata['tools_used'] else 'None'}")
|
|
283
|
-
print(f" - Tool calls: {len(self.tool_calls)}")
|
|
284
|
-
print(f" - Errors found: {len(self.errors)}")
|
|
285
|
-
print(f" - Output: {self.output_path}")
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
def main():
|
|
289
|
-
"""Main entry point"""
|
|
290
|
-
if len(sys.argv) != 3:
|
|
291
|
-
print("Usage: process-session-log.py <input_json> <output_md>", file=sys.stderr)
|
|
292
|
-
sys.exit(1)
|
|
293
|
-
|
|
294
|
-
input_path = sys.argv[1]
|
|
295
|
-
output_path = sys.argv[2]
|
|
296
|
-
|
|
297
|
-
processor = SessionLogProcessor(input_path, output_path)
|
|
298
|
-
processor.process()
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
if __name__ == '__main__':
|
|
302
|
-
main()
|
package/scripts/quick-install.sh
DELETED
|
@@ -1,195 +0,0 @@
|
|
|
1
|
-
#!/bin/bash
|
|
2
|
-
|
|
3
|
-
# FSS Link Quick Install Script
|
|
4
|
-
# Universal installer that detects OS and runs appropriate installation
|
|
5
|
-
|
|
6
|
-
set -e
|
|
7
|
-
|
|
8
|
-
# Colors for output
|
|
9
|
-
RED='\033[0;31m'
|
|
10
|
-
GREEN='\033[0;32m'
|
|
11
|
-
YELLOW='\033[1;33m'
|
|
12
|
-
BLUE='\033[0;34m'
|
|
13
|
-
NC='\033[0m'
|
|
14
|
-
|
|
15
|
-
log_info() {
|
|
16
|
-
echo -e "${BLUE}[INFO]${NC} $1"
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
log_success() {
|
|
20
|
-
echo -e "${GREEN}[SUCCESS]${NC} $1"
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
log_warning() {
|
|
24
|
-
echo -e "${YELLOW}[WARNING]${NC} $1"
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
log_error() {
|
|
28
|
-
echo -e "${RED}[ERROR]${NC} $1"
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
# Detect operating system
|
|
32
|
-
detect_os() {
|
|
33
|
-
case "$(uname -s)" in
|
|
34
|
-
Darwin)
|
|
35
|
-
OS="macos"
|
|
36
|
-
;;
|
|
37
|
-
Linux)
|
|
38
|
-
OS="linux"
|
|
39
|
-
;;
|
|
40
|
-
CYGWIN*|MINGW32*|MSYS*|MINGW*)
|
|
41
|
-
OS="windows"
|
|
42
|
-
;;
|
|
43
|
-
*)
|
|
44
|
-
log_error "Unsupported operating system: $(uname -s)"
|
|
45
|
-
exit 1
|
|
46
|
-
;;
|
|
47
|
-
esac
|
|
48
|
-
|
|
49
|
-
log_info "Detected OS: $OS"
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
# Download installation script
|
|
53
|
-
download_installer() {
|
|
54
|
-
local script_name="install-${OS}.sh"
|
|
55
|
-
local script_url="https://raw.githubusercontent.com/FSSCoding/fss-link/main/scripts/${script_name}"
|
|
56
|
-
|
|
57
|
-
log_info "Downloading $script_name..."
|
|
58
|
-
|
|
59
|
-
if command -v curl &> /dev/null; then
|
|
60
|
-
curl -fsSL "$script_url" -o "$script_name"
|
|
61
|
-
elif command -v wget &> /dev/null; then
|
|
62
|
-
wget -q "$script_url" -O "$script_name"
|
|
63
|
-
else
|
|
64
|
-
log_error "Neither curl nor wget found. Please install one of them."
|
|
65
|
-
exit 1
|
|
66
|
-
fi
|
|
67
|
-
|
|
68
|
-
if [ -f "$script_name" ]; then
|
|
69
|
-
chmod +x "$script_name"
|
|
70
|
-
log_success "Downloaded $script_name"
|
|
71
|
-
return 0
|
|
72
|
-
else
|
|
73
|
-
log_error "Failed to download $script_name"
|
|
74
|
-
return 1
|
|
75
|
-
fi
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
# Run the appropriate installer
|
|
79
|
-
run_installer() {
|
|
80
|
-
local script_name="install-${OS}.sh"
|
|
81
|
-
|
|
82
|
-
if [ "$OS" = "windows" ]; then
|
|
83
|
-
# For Windows, we need PowerShell
|
|
84
|
-
log_info "For Windows installation, please run:"
|
|
85
|
-
log_info "PowerShell -ExecutionPolicy Bypass -File install-windows.ps1"
|
|
86
|
-
return 0
|
|
87
|
-
fi
|
|
88
|
-
|
|
89
|
-
log_info "Running $script_name..."
|
|
90
|
-
./"$script_name" "$@"
|
|
91
|
-
|
|
92
|
-
# Clean up
|
|
93
|
-
rm -f "$script_name"
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
# NPM fallback installation
|
|
97
|
-
npm_install() {
|
|
98
|
-
log_info "Attempting direct NPM installation..."
|
|
99
|
-
|
|
100
|
-
if command -v npm &> /dev/null; then
|
|
101
|
-
npm install -g fss-link
|
|
102
|
-
|
|
103
|
-
if command -v fss-link &> /dev/null; then
|
|
104
|
-
log_success "FSS Link installed via NPM"
|
|
105
|
-
log_info "Run 'fss-link --help' to get started"
|
|
106
|
-
return 0
|
|
107
|
-
else
|
|
108
|
-
log_error "NPM installation succeeded but fss-link command not found"
|
|
109
|
-
return 1
|
|
110
|
-
fi
|
|
111
|
-
else
|
|
112
|
-
log_error "NPM not found. Please install Node.js 20+ first."
|
|
113
|
-
return 1
|
|
114
|
-
fi
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
# Main function
|
|
118
|
-
main() {
|
|
119
|
-
echo "🚀 FSS Link Universal Quick Installer"
|
|
120
|
-
echo "====================================="
|
|
121
|
-
echo ""
|
|
122
|
-
|
|
123
|
-
# Handle help flag
|
|
124
|
-
if [ "${1:-}" = "--help" ] || [ "${1:-}" = "-h" ]; then
|
|
125
|
-
cat << EOF
|
|
126
|
-
FSS Link Universal Quick Installer
|
|
127
|
-
|
|
128
|
-
Usage: $0 [options]
|
|
129
|
-
|
|
130
|
-
Options:
|
|
131
|
-
--help, -h Show this help message
|
|
132
|
-
--npm-only Skip OS-specific installer, use NPM directly
|
|
133
|
-
--version, -v Show script version
|
|
134
|
-
|
|
135
|
-
This script will:
|
|
136
|
-
1. Detect your operating system
|
|
137
|
-
2. Download the appropriate installation script
|
|
138
|
-
3. Run the OS-specific installer
|
|
139
|
-
4. Verify the installation
|
|
140
|
-
|
|
141
|
-
Supported Systems:
|
|
142
|
-
- Linux (Ubuntu, Debian, CentOS, Fedora, Arch, openSUSE)
|
|
143
|
-
- macOS (10.15+)
|
|
144
|
-
- Windows (10+)
|
|
145
|
-
|
|
146
|
-
Requirements:
|
|
147
|
-
- Node.js 20.0.0 or higher
|
|
148
|
-
- Build tools for native dependencies
|
|
149
|
-
- Internet connection for downloading
|
|
150
|
-
|
|
151
|
-
Examples:
|
|
152
|
-
curl -fsSL https://raw.githubusercontent.com/FSSCoding/fss-link/main/scripts/quick-install.sh | bash
|
|
153
|
-
bash quick-install.sh --npm-only
|
|
154
|
-
EOF
|
|
155
|
-
exit 0
|
|
156
|
-
fi
|
|
157
|
-
|
|
158
|
-
# Handle version flag
|
|
159
|
-
if [ "${1:-}" = "--version" ] || [ "${1:-}" = "-v" ]; then
|
|
160
|
-
echo "FSS Link Universal Quick Installer v1.0.0"
|
|
161
|
-
exit 0
|
|
162
|
-
fi
|
|
163
|
-
|
|
164
|
-
# Handle NPM-only installation
|
|
165
|
-
if [ "${1:-}" = "--npm-only" ]; then
|
|
166
|
-
npm_install
|
|
167
|
-
exit $?
|
|
168
|
-
fi
|
|
169
|
-
|
|
170
|
-
# Detect OS
|
|
171
|
-
detect_os
|
|
172
|
-
|
|
173
|
-
# Try downloading and running OS-specific installer
|
|
174
|
-
if download_installer; then
|
|
175
|
-
run_installer "$@"
|
|
176
|
-
else
|
|
177
|
-
log_warning "OS-specific installer download failed, trying NPM installation..."
|
|
178
|
-
npm_install
|
|
179
|
-
fi
|
|
180
|
-
|
|
181
|
-
# Final verification
|
|
182
|
-
if command -v fss-link &> /dev/null; then
|
|
183
|
-
VERSION=$(fss-link --version 2>/dev/null || echo "unknown")
|
|
184
|
-
log_success "🎉 FSS Link installation complete!"
|
|
185
|
-
log_info "Version: $VERSION"
|
|
186
|
-
log_info "Run 'fss-link --help' to get started"
|
|
187
|
-
else
|
|
188
|
-
log_error "Installation may have failed - fss-link command not found"
|
|
189
|
-
log_info "Try running: npm install -g fss-link"
|
|
190
|
-
exit 1
|
|
191
|
-
fi
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
# Run main function with all arguments
|
|
195
|
-
main "$@"
|
package/scripts/telemetry_gcp.js
DELETED
|
@@ -1,188 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* @license
|
|
5
|
-
* Copyright 2025 Google LLC
|
|
6
|
-
* SPDX-License-Identifier: Apache-2.0
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import path from 'path';
|
|
10
|
-
import fs from 'fs';
|
|
11
|
-
import { spawn, execSync } from 'child_process';
|
|
12
|
-
import {
|
|
13
|
-
OTEL_DIR,
|
|
14
|
-
BIN_DIR,
|
|
15
|
-
fileExists,
|
|
16
|
-
waitForPort,
|
|
17
|
-
ensureBinary,
|
|
18
|
-
manageTelemetrySettings,
|
|
19
|
-
registerCleanup,
|
|
20
|
-
} from './telemetry_utils.js';
|
|
21
|
-
|
|
22
|
-
const OTEL_CONFIG_FILE = path.join(OTEL_DIR, 'collector-gcp.yaml');
|
|
23
|
-
const OTEL_LOG_FILE = path.join(OTEL_DIR, 'collector-gcp.log');
|
|
24
|
-
|
|
25
|
-
const getOtelConfigContent = (projectId) => `
|
|
26
|
-
receivers:
|
|
27
|
-
otlp:
|
|
28
|
-
protocols:
|
|
29
|
-
grpc:
|
|
30
|
-
endpoint: "localhost:4317"
|
|
31
|
-
processors:
|
|
32
|
-
batch:
|
|
33
|
-
timeout: 1s
|
|
34
|
-
exporters:
|
|
35
|
-
googlecloud:
|
|
36
|
-
project: "${projectId}"
|
|
37
|
-
metric:
|
|
38
|
-
prefix: "custom.googleapis.com/gemini_cli"
|
|
39
|
-
log:
|
|
40
|
-
default_log_name: "gemini_cli"
|
|
41
|
-
debug:
|
|
42
|
-
verbosity: detailed
|
|
43
|
-
service:
|
|
44
|
-
telemetry:
|
|
45
|
-
logs:
|
|
46
|
-
level: "debug"
|
|
47
|
-
metrics:
|
|
48
|
-
level: "none"
|
|
49
|
-
pipelines:
|
|
50
|
-
traces:
|
|
51
|
-
receivers: [otlp]
|
|
52
|
-
processors: [batch]
|
|
53
|
-
exporters: [googlecloud]
|
|
54
|
-
metrics:
|
|
55
|
-
receivers: [otlp]
|
|
56
|
-
processors: [batch]
|
|
57
|
-
exporters: [googlecloud, debug]
|
|
58
|
-
logs:
|
|
59
|
-
receivers: [otlp]
|
|
60
|
-
processors: [batch]
|
|
61
|
-
exporters: [googlecloud, debug]
|
|
62
|
-
`;
|
|
63
|
-
|
|
64
|
-
async function main() {
|
|
65
|
-
console.log('✨ Starting Local Telemetry Exporter for Google Cloud ✨');
|
|
66
|
-
|
|
67
|
-
let collectorProcess;
|
|
68
|
-
let collectorLogFd;
|
|
69
|
-
|
|
70
|
-
const originalSandboxSetting = manageTelemetrySettings(
|
|
71
|
-
true,
|
|
72
|
-
'http://localhost:4317',
|
|
73
|
-
'gcp',
|
|
74
|
-
);
|
|
75
|
-
registerCleanup(
|
|
76
|
-
() => [collectorProcess].filter((p) => p), // Function to get processes
|
|
77
|
-
() => [collectorLogFd].filter((fd) => fd), // Function to get FDs
|
|
78
|
-
originalSandboxSetting,
|
|
79
|
-
);
|
|
80
|
-
|
|
81
|
-
const projectId = process.env.OTLP_GOOGLE_CLOUD_PROJECT;
|
|
82
|
-
if (!projectId) {
|
|
83
|
-
console.error(
|
|
84
|
-
'🛑 Error: OTLP_GOOGLE_CLOUD_PROJECT environment variable is not exported.',
|
|
85
|
-
);
|
|
86
|
-
console.log(
|
|
87
|
-
' Please set it to your Google Cloud Project ID and try again.',
|
|
88
|
-
);
|
|
89
|
-
console.log(' `export OTLP_GOOGLE_CLOUD_PROJECT=your-project-id`');
|
|
90
|
-
process.exit(1);
|
|
91
|
-
}
|
|
92
|
-
console.log(`✅ Using OTLP Google Cloud Project ID: ${projectId}`);
|
|
93
|
-
|
|
94
|
-
console.log('\n🔑 Please ensure you are authenticated with Google Cloud:');
|
|
95
|
-
console.log(
|
|
96
|
-
' - Run `gcloud auth application-default login` OR ensure `GOOGLE_APPLICATION_CREDENTIALS` environment variable points to a valid service account key.',
|
|
97
|
-
);
|
|
98
|
-
console.log(
|
|
99
|
-
' - The account needs "Cloud Trace Agent", "Monitoring Metric Writer", and "Logs Writer" roles.',
|
|
100
|
-
);
|
|
101
|
-
|
|
102
|
-
if (!fileExists(BIN_DIR)) fs.mkdirSync(BIN_DIR, { recursive: true });
|
|
103
|
-
|
|
104
|
-
const otelcolPath = await ensureBinary(
|
|
105
|
-
'otelcol-contrib',
|
|
106
|
-
'open-telemetry/opentelemetry-collector-releases',
|
|
107
|
-
(version, platform, arch, ext) =>
|
|
108
|
-
`otelcol-contrib_${version}_${platform}_${arch}.${ext}`,
|
|
109
|
-
'otelcol-contrib',
|
|
110
|
-
false, // isJaeger = false
|
|
111
|
-
).catch((e) => {
|
|
112
|
-
console.error(`🛑 Error getting otelcol-contrib: ${e.message}`);
|
|
113
|
-
return null;
|
|
114
|
-
});
|
|
115
|
-
if (!otelcolPath) process.exit(1);
|
|
116
|
-
|
|
117
|
-
console.log('🧹 Cleaning up old processes and logs...');
|
|
118
|
-
try {
|
|
119
|
-
execSync('pkill -f "otelcol-contrib"');
|
|
120
|
-
console.log('✅ Stopped existing otelcol-contrib process.');
|
|
121
|
-
} catch (_e) {
|
|
122
|
-
/* no-op */
|
|
123
|
-
}
|
|
124
|
-
try {
|
|
125
|
-
fs.unlinkSync(OTEL_LOG_FILE);
|
|
126
|
-
console.log('✅ Deleted old GCP collector log.');
|
|
127
|
-
} catch (e) {
|
|
128
|
-
if (e.code !== 'ENOENT') console.error(e);
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
if (!fileExists(OTEL_DIR)) fs.mkdirSync(OTEL_DIR, { recursive: true });
|
|
132
|
-
fs.writeFileSync(OTEL_CONFIG_FILE, getOtelConfigContent(projectId));
|
|
133
|
-
console.log(`📄 Wrote OTEL collector config to ${OTEL_CONFIG_FILE}`);
|
|
134
|
-
|
|
135
|
-
console.log(`🚀 Starting OTEL collector for GCP... Logs: ${OTEL_LOG_FILE}`);
|
|
136
|
-
collectorLogFd = fs.openSync(OTEL_LOG_FILE, 'a');
|
|
137
|
-
collectorProcess = spawn(otelcolPath, ['--config', OTEL_CONFIG_FILE], {
|
|
138
|
-
stdio: ['ignore', collectorLogFd, collectorLogFd],
|
|
139
|
-
env: { ...process.env },
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
console.log(
|
|
143
|
-
`⏳ Waiting for OTEL collector to start (PID: ${collectorProcess.pid})...`,
|
|
144
|
-
);
|
|
145
|
-
|
|
146
|
-
try {
|
|
147
|
-
await waitForPort(4317);
|
|
148
|
-
console.log(`✅ OTEL collector started successfully on port 4317.`);
|
|
149
|
-
} catch (err) {
|
|
150
|
-
console.error(`🛑 Error: OTEL collector failed to start on port 4317.`);
|
|
151
|
-
console.error(err.message);
|
|
152
|
-
if (collectorProcess && collectorProcess.pid) {
|
|
153
|
-
process.kill(collectorProcess.pid, 'SIGKILL');
|
|
154
|
-
}
|
|
155
|
-
if (fileExists(OTEL_LOG_FILE)) {
|
|
156
|
-
console.error('📄 OTEL Collector Log Output:');
|
|
157
|
-
console.error(fs.readFileSync(OTEL_LOG_FILE, 'utf-8'));
|
|
158
|
-
}
|
|
159
|
-
process.exit(1);
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
collectorProcess.on('error', (err) => {
|
|
163
|
-
console.error(`${collectorProcess.spawnargs[0]} process error:`, err);
|
|
164
|
-
process.exit(1);
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
console.log(`\n✨ Local OTEL collector for GCP is running.`);
|
|
168
|
-
console.log(
|
|
169
|
-
'\n🚀 To send telemetry, run the Gemini CLI in a separate terminal window.',
|
|
170
|
-
);
|
|
171
|
-
console.log(`\n📄 Collector logs are being written to: ${OTEL_LOG_FILE}`);
|
|
172
|
-
console.log(
|
|
173
|
-
`📄 Tail collector logs in another terminal: tail -f ${OTEL_LOG_FILE}`,
|
|
174
|
-
);
|
|
175
|
-
console.log(`\n📊 View your telemetry data in Google Cloud Console:`);
|
|
176
|
-
console.log(
|
|
177
|
-
` - Logs: https://console.cloud.google.com/logs/query;query=logName%3D%22projects%2F${projectId}%2Flogs%2Fgemini_cli%22?project=${projectId}`,
|
|
178
|
-
);
|
|
179
|
-
console.log(
|
|
180
|
-
` - Metrics: https://console.cloud.google.com/monitoring/metrics-explorer?project=${projectId}`,
|
|
181
|
-
);
|
|
182
|
-
console.log(
|
|
183
|
-
` - Traces: https://console.cloud.google.com/traces/list?project=${projectId}`,
|
|
184
|
-
);
|
|
185
|
-
console.log(`\nPress Ctrl+C to exit.`);
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
main();
|