@fyow/copilot-everything 1.0.0 → 1.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -1
- package/scripts/hooks/evaluate-session.js +78 -0
- package/scripts/hooks/post-tool-use.ps1 +55 -0
- package/scripts/hooks/post-tool-use.sh +68 -0
- package/scripts/hooks/pre-compact.js +48 -0
- package/scripts/hooks/session-end.js +82 -0
- package/scripts/hooks/session-start.js +61 -0
- package/scripts/hooks/suggest-compact.js +60 -0
- package/scripts/lib/package-manager.js +390 -0
- package/scripts/lib/utils.js +368 -0
- package/scripts/migrate-to-copilot.js +247 -0
- package/scripts/setup-package-manager.js +206 -0
- package/src/commands/init.js +11 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fyow/copilot-everything",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Everything you need for GitHub Copilot CLI - agents, skills, instructions, and hooks configurations",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"src/",
|
|
14
14
|
".github/",
|
|
15
|
+
"scripts/",
|
|
15
16
|
"AGENTS.md",
|
|
16
17
|
"copilot/"
|
|
17
18
|
],
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Continuous Learning - Session Evaluator
|
|
4
|
+
*
|
|
5
|
+
* Cross-platform (Windows, macOS, Linux)
|
|
6
|
+
*
|
|
7
|
+
* Runs on Stop hook to extract reusable patterns from Claude Code sessions
|
|
8
|
+
*
|
|
9
|
+
* Why Stop hook instead of UserPromptSubmit:
|
|
10
|
+
* - Stop runs once at session end (lightweight)
|
|
11
|
+
* - UserPromptSubmit runs every message (heavy, adds latency)
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
const path = require('path');
|
|
15
|
+
const fs = require('fs');
|
|
16
|
+
const {
|
|
17
|
+
getLearnedSkillsDir,
|
|
18
|
+
ensureDir,
|
|
19
|
+
readFile,
|
|
20
|
+
countInFile,
|
|
21
|
+
log
|
|
22
|
+
} = require('../lib/utils');
|
|
23
|
+
|
|
24
|
+
async function main() {
|
|
25
|
+
// Get script directory to find config
|
|
26
|
+
const scriptDir = __dirname;
|
|
27
|
+
const configFile = path.join(scriptDir, '..', '..', 'skills', 'continuous-learning', 'config.json');
|
|
28
|
+
|
|
29
|
+
// Default configuration
|
|
30
|
+
let minSessionLength = 10;
|
|
31
|
+
let learnedSkillsPath = getLearnedSkillsDir();
|
|
32
|
+
|
|
33
|
+
// Load config if exists
|
|
34
|
+
const configContent = readFile(configFile);
|
|
35
|
+
if (configContent) {
|
|
36
|
+
try {
|
|
37
|
+
const config = JSON.parse(configContent);
|
|
38
|
+
minSessionLength = config.min_session_length || 10;
|
|
39
|
+
|
|
40
|
+
if (config.learned_skills_path) {
|
|
41
|
+
// Handle ~ in path
|
|
42
|
+
learnedSkillsPath = config.learned_skills_path.replace(/^~/, require('os').homedir());
|
|
43
|
+
}
|
|
44
|
+
} catch {
|
|
45
|
+
// Invalid config, use defaults
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Ensure learned skills directory exists
|
|
50
|
+
ensureDir(learnedSkillsPath);
|
|
51
|
+
|
|
52
|
+
// Get transcript path from environment (set by Claude Code)
|
|
53
|
+
const transcriptPath = process.env.CLAUDE_TRANSCRIPT_PATH;
|
|
54
|
+
|
|
55
|
+
if (!transcriptPath || !fs.existsSync(transcriptPath)) {
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
// Count user messages in session
|
|
60
|
+
const messageCount = countInFile(transcriptPath, /"type":"user"/g);
|
|
61
|
+
|
|
62
|
+
// Skip short sessions
|
|
63
|
+
if (messageCount < minSessionLength) {
|
|
64
|
+
log(`[ContinuousLearning] Session too short (${messageCount} messages), skipping`);
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Signal to Claude that session should be evaluated for extractable patterns
|
|
69
|
+
log(`[ContinuousLearning] Session has ${messageCount} messages - evaluate for extractable patterns`);
|
|
70
|
+
log(`[ContinuousLearning] Save learned skills to: ${learnedSkillsPath}`);
|
|
71
|
+
|
|
72
|
+
process.exit(0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
main().catch(err => {
|
|
76
|
+
console.error('[ContinuousLearning] Error:', err.message);
|
|
77
|
+
process.exit(0);
|
|
78
|
+
});
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# Post tool use hook for Copilot CLI (PowerShell version)
|
|
2
|
+
# Handles formatting, type checking, and warnings after tool execution
|
|
3
|
+
|
|
4
|
+
param()
|
|
5
|
+
|
|
6
|
+
$input = $input | Out-String
|
|
7
|
+
|
|
8
|
+
try {
|
|
9
|
+
$data = $input | ConvertFrom-Json
|
|
10
|
+
$toolName = $data.tool_name
|
|
11
|
+
$filePath = $data.tool_input.file_path
|
|
12
|
+
} catch {
|
|
13
|
+
$toolName = ""
|
|
14
|
+
$filePath = ""
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
# If editing JS/TS file, run prettier
|
|
18
|
+
if ($filePath -match '\.(ts|tsx|js|jsx)$' -and (Test-Path $filePath)) {
|
|
19
|
+
try {
|
|
20
|
+
npx prettier --write $filePath 2>$null
|
|
21
|
+
} catch {
|
|
22
|
+
# Ignore prettier errors
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
# If editing TS file, run type check
|
|
27
|
+
if ($filePath -match '\.(ts|tsx)$' -and (Test-Path $filePath)) {
|
|
28
|
+
$dir = Split-Path $filePath -Parent
|
|
29
|
+
while ($dir -ne "" -and -not (Test-Path "$dir\tsconfig.json")) {
|
|
30
|
+
$dir = Split-Path $dir -Parent
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (Test-Path "$dir\tsconfig.json") {
|
|
34
|
+
try {
|
|
35
|
+
$errors = npx tsc --noEmit --pretty false 2>&1 | Select-String $filePath | Select-Object -First 5
|
|
36
|
+
if ($errors) {
|
|
37
|
+
Write-Host "[Hook] TypeScript errors in $filePath`:" -ForegroundColor Yellow
|
|
38
|
+
$errors | ForEach-Object { Write-Host $_.Line -ForegroundColor Yellow }
|
|
39
|
+
}
|
|
40
|
+
} catch {
|
|
41
|
+
# Ignore tsc errors
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
# Check for console.log in modified files
|
|
47
|
+
if ($filePath -match '\.(ts|tsx|js|jsx)$' -and (Test-Path $filePath)) {
|
|
48
|
+
$consoleLogLines = Select-String -Path $filePath -Pattern "console\.log" | Select-Object -First 3
|
|
49
|
+
if ($consoleLogLines) {
|
|
50
|
+
Write-Host "[Hook] WARNING: console.log found in $filePath - remove before commit" -ForegroundColor Yellow
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
# Output original input unchanged
|
|
55
|
+
Write-Output $input
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# Post tool use hook for Copilot CLI
|
|
3
|
+
# Handles formatting, type checking, and warnings after tool execution
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
# Read JSON input from stdin
|
|
8
|
+
input=$(cat)
|
|
9
|
+
|
|
10
|
+
# Extract tool information
|
|
11
|
+
tool_name=$(echo "$input" | node -e "
|
|
12
|
+
let d='';
|
|
13
|
+
process.stdin.on('data',c=>d+=c);
|
|
14
|
+
process.stdin.on('end',()=>{
|
|
15
|
+
try {
|
|
16
|
+
const data = JSON.parse(d);
|
|
17
|
+
console.log(data.tool_name || data.tool || '');
|
|
18
|
+
} catch(e) {
|
|
19
|
+
console.log('');
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
" <<< "$input" 2>/dev/null || echo "")
|
|
23
|
+
|
|
24
|
+
file_path=$(echo "$input" | node -e "
|
|
25
|
+
let d='';
|
|
26
|
+
process.stdin.on('data',c=>d+=c);
|
|
27
|
+
process.stdin.on('end',()=>{
|
|
28
|
+
try {
|
|
29
|
+
const data = JSON.parse(d);
|
|
30
|
+
console.log(data.tool_input?.file_path || data.file_path || '');
|
|
31
|
+
} catch(e) {
|
|
32
|
+
console.log('');
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
" <<< "$input" 2>/dev/null || echo "")
|
|
36
|
+
|
|
37
|
+
# If editing JS/TS file, run prettier
|
|
38
|
+
if [[ "$file_path" =~ \.(ts|tsx|js|jsx)$ ]] && [ -f "$file_path" ]; then
|
|
39
|
+
if command -v npx &> /dev/null; then
|
|
40
|
+
npx prettier --write "$file_path" 2>/dev/null || true
|
|
41
|
+
fi
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# If editing TS file, run type check
|
|
45
|
+
if [[ "$file_path" =~ \.(ts|tsx)$ ]] && [ -f "$file_path" ]; then
|
|
46
|
+
dir=$(dirname "$file_path")
|
|
47
|
+
while [ "$dir" != "/" ] && [ ! -f "$dir/tsconfig.json" ]; do
|
|
48
|
+
dir=$(dirname "$dir")
|
|
49
|
+
done
|
|
50
|
+
|
|
51
|
+
if [ -f "$dir/tsconfig.json" ]; then
|
|
52
|
+
errors=$(npx tsc --noEmit --pretty false 2>&1 | grep "$file_path" | head -5 || true)
|
|
53
|
+
if [ -n "$errors" ]; then
|
|
54
|
+
echo "[Hook] TypeScript errors in $file_path:" >&2
|
|
55
|
+
echo "$errors" >&2
|
|
56
|
+
fi
|
|
57
|
+
fi
|
|
58
|
+
fi
|
|
59
|
+
|
|
60
|
+
# Check for console.log in modified files
|
|
61
|
+
if [[ "$file_path" =~ \.(ts|tsx|js|jsx)$ ]] && [ -f "$file_path" ]; then
|
|
62
|
+
if grep -n "console.log" "$file_path" 2>/dev/null | head -3; then
|
|
63
|
+
echo "[Hook] WARNING: console.log found in $file_path - remove before commit" >&2
|
|
64
|
+
fi
|
|
65
|
+
fi
|
|
66
|
+
|
|
67
|
+
# Output original input unchanged
|
|
68
|
+
echo "$input"
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* PreCompact Hook - Save state before context compaction
|
|
4
|
+
*
|
|
5
|
+
* Cross-platform (Windows, macOS, Linux)
|
|
6
|
+
*
|
|
7
|
+
* Runs before Claude compacts context, giving you a chance to
|
|
8
|
+
* preserve important state that might get lost in summarization.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const {
|
|
13
|
+
getSessionsDir,
|
|
14
|
+
getDateTimeString,
|
|
15
|
+
getTimeString,
|
|
16
|
+
findFiles,
|
|
17
|
+
ensureDir,
|
|
18
|
+
appendFile,
|
|
19
|
+
log
|
|
20
|
+
} = require('../lib/utils');
|
|
21
|
+
|
|
22
|
+
async function main() {
|
|
23
|
+
const sessionsDir = getSessionsDir();
|
|
24
|
+
const compactionLog = path.join(sessionsDir, 'compaction-log.txt');
|
|
25
|
+
|
|
26
|
+
ensureDir(sessionsDir);
|
|
27
|
+
|
|
28
|
+
// Log compaction event with timestamp
|
|
29
|
+
const timestamp = getDateTimeString();
|
|
30
|
+
appendFile(compactionLog, `[${timestamp}] Context compaction triggered\n`);
|
|
31
|
+
|
|
32
|
+
// If there's an active session file, note the compaction
|
|
33
|
+
const sessions = findFiles(sessionsDir, '*.tmp');
|
|
34
|
+
|
|
35
|
+
if (sessions.length > 0) {
|
|
36
|
+
const activeSession = sessions[0].path;
|
|
37
|
+
const timeStr = getTimeString();
|
|
38
|
+
appendFile(activeSession, `\n---\n**[Compaction occurred at ${timeStr}]** - Context was summarized\n`);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
log('[PreCompact] State saved before compaction');
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
main().catch(err => {
|
|
46
|
+
console.error('[PreCompact] Error:', err.message);
|
|
47
|
+
process.exit(0);
|
|
48
|
+
});
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Stop Hook (Session End) - Persist learnings when session ends
|
|
4
|
+
*
|
|
5
|
+
* Cross-platform (Windows, macOS, Linux)
|
|
6
|
+
*
|
|
7
|
+
* Runs when Claude session ends. Creates/updates session log file
|
|
8
|
+
* with timestamp for continuity tracking.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const fs = require('fs');
|
|
13
|
+
const {
|
|
14
|
+
getSessionsDir,
|
|
15
|
+
getDateString,
|
|
16
|
+
getTimeString,
|
|
17
|
+
ensureDir,
|
|
18
|
+
readFile,
|
|
19
|
+
writeFile,
|
|
20
|
+
replaceInFile,
|
|
21
|
+
log
|
|
22
|
+
} = require('../lib/utils');
|
|
23
|
+
|
|
24
|
+
async function main() {
|
|
25
|
+
const sessionsDir = getSessionsDir();
|
|
26
|
+
const today = getDateString();
|
|
27
|
+
const sessionFile = path.join(sessionsDir, `${today}-session.tmp`);
|
|
28
|
+
|
|
29
|
+
ensureDir(sessionsDir);
|
|
30
|
+
|
|
31
|
+
const currentTime = getTimeString();
|
|
32
|
+
|
|
33
|
+
// If session file exists for today, update the end time
|
|
34
|
+
if (fs.existsSync(sessionFile)) {
|
|
35
|
+
const success = replaceInFile(
|
|
36
|
+
sessionFile,
|
|
37
|
+
/\*\*Last Updated:\*\*.*/,
|
|
38
|
+
`**Last Updated:** ${currentTime}`
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
if (success) {
|
|
42
|
+
log(`[SessionEnd] Updated session file: ${sessionFile}`);
|
|
43
|
+
}
|
|
44
|
+
} else {
|
|
45
|
+
// Create new session file with template
|
|
46
|
+
const template = `# Session: ${today}
|
|
47
|
+
**Date:** ${today}
|
|
48
|
+
**Started:** ${currentTime}
|
|
49
|
+
**Last Updated:** ${currentTime}
|
|
50
|
+
|
|
51
|
+
---
|
|
52
|
+
|
|
53
|
+
## Current State
|
|
54
|
+
|
|
55
|
+
[Session context goes here]
|
|
56
|
+
|
|
57
|
+
### Completed
|
|
58
|
+
- [ ]
|
|
59
|
+
|
|
60
|
+
### In Progress
|
|
61
|
+
- [ ]
|
|
62
|
+
|
|
63
|
+
### Notes for Next Session
|
|
64
|
+
-
|
|
65
|
+
|
|
66
|
+
### Context to Load
|
|
67
|
+
\`\`\`
|
|
68
|
+
[relevant files]
|
|
69
|
+
\`\`\`
|
|
70
|
+
`;
|
|
71
|
+
|
|
72
|
+
writeFile(sessionFile, template);
|
|
73
|
+
log(`[SessionEnd] Created session file: ${sessionFile}`);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
process.exit(0);
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
main().catch(err => {
|
|
80
|
+
console.error('[SessionEnd] Error:', err.message);
|
|
81
|
+
process.exit(0);
|
|
82
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* SessionStart Hook - Load previous context on new session
|
|
4
|
+
*
|
|
5
|
+
* Cross-platform (Windows, macOS, Linux)
|
|
6
|
+
*
|
|
7
|
+
* Runs when a new Claude session starts. Checks for recent session
|
|
8
|
+
* files and notifies Claude of available context to load.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
const path = require('path');
|
|
12
|
+
const {
|
|
13
|
+
getSessionsDir,
|
|
14
|
+
getLearnedSkillsDir,
|
|
15
|
+
findFiles,
|
|
16
|
+
ensureDir,
|
|
17
|
+
log
|
|
18
|
+
} = require('../lib/utils');
|
|
19
|
+
const { getPackageManager, getSelectionPrompt } = require('../lib/package-manager');
|
|
20
|
+
|
|
21
|
+
async function main() {
|
|
22
|
+
const sessionsDir = getSessionsDir();
|
|
23
|
+
const learnedDir = getLearnedSkillsDir();
|
|
24
|
+
|
|
25
|
+
// Ensure directories exist
|
|
26
|
+
ensureDir(sessionsDir);
|
|
27
|
+
ensureDir(learnedDir);
|
|
28
|
+
|
|
29
|
+
// Check for recent session files (last 7 days)
|
|
30
|
+
const recentSessions = findFiles(sessionsDir, '*.tmp', { maxAge: 7 });
|
|
31
|
+
|
|
32
|
+
if (recentSessions.length > 0) {
|
|
33
|
+
const latest = recentSessions[0];
|
|
34
|
+
log(`[SessionStart] Found ${recentSessions.length} recent session(s)`);
|
|
35
|
+
log(`[SessionStart] Latest: ${latest.path}`);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check for learned skills
|
|
39
|
+
const learnedSkills = findFiles(learnedDir, '*.md');
|
|
40
|
+
|
|
41
|
+
if (learnedSkills.length > 0) {
|
|
42
|
+
log(`[SessionStart] ${learnedSkills.length} learned skill(s) available in ${learnedDir}`);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Detect and report package manager
|
|
46
|
+
const pm = getPackageManager();
|
|
47
|
+
log(`[SessionStart] Package manager: ${pm.name} (${pm.source})`);
|
|
48
|
+
|
|
49
|
+
// If package manager was detected via fallback, show selection prompt
|
|
50
|
+
if (pm.source === 'fallback' || pm.source === 'default') {
|
|
51
|
+
log('[SessionStart] No package manager preference found.');
|
|
52
|
+
log(getSelectionPrompt());
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
process.exit(0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
main().catch(err => {
|
|
59
|
+
console.error('[SessionStart] Error:', err.message);
|
|
60
|
+
process.exit(0); // Don't block on errors
|
|
61
|
+
});
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Strategic Compact Suggester
|
|
4
|
+
*
|
|
5
|
+
* Cross-platform (Windows, macOS, Linux)
|
|
6
|
+
*
|
|
7
|
+
* Runs on PreToolUse or periodically to suggest manual compaction at logical intervals
|
|
8
|
+
*
|
|
9
|
+
* Why manual over auto-compact:
|
|
10
|
+
* - Auto-compact happens at arbitrary points, often mid-task
|
|
11
|
+
* - Strategic compacting preserves context through logical phases
|
|
12
|
+
* - Compact after exploration, before execution
|
|
13
|
+
* - Compact after completing a milestone, before starting next
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
const path = require('path');
|
|
17
|
+
const fs = require('fs');
|
|
18
|
+
const {
|
|
19
|
+
getTempDir,
|
|
20
|
+
readFile,
|
|
21
|
+
writeFile,
|
|
22
|
+
log
|
|
23
|
+
} = require('../lib/utils');
|
|
24
|
+
|
|
25
|
+
async function main() {
|
|
26
|
+
// Track tool call count (increment in a temp file)
|
|
27
|
+
// Use a session-specific counter file based on PID from parent process
|
|
28
|
+
// or session ID from environment
|
|
29
|
+
const sessionId = process.env.CLAUDE_SESSION_ID || process.ppid || 'default';
|
|
30
|
+
const counterFile = path.join(getTempDir(), `claude-tool-count-${sessionId}`);
|
|
31
|
+
const threshold = parseInt(process.env.COMPACT_THRESHOLD || '50', 10);
|
|
32
|
+
|
|
33
|
+
let count = 1;
|
|
34
|
+
|
|
35
|
+
// Read existing count or start at 1
|
|
36
|
+
const existing = readFile(counterFile);
|
|
37
|
+
if (existing) {
|
|
38
|
+
count = parseInt(existing.trim(), 10) + 1;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Save updated count
|
|
42
|
+
writeFile(counterFile, String(count));
|
|
43
|
+
|
|
44
|
+
// Suggest compact after threshold tool calls
|
|
45
|
+
if (count === threshold) {
|
|
46
|
+
log(`[StrategicCompact] ${threshold} tool calls reached - consider /compact if transitioning phases`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Suggest at regular intervals after threshold
|
|
50
|
+
if (count > threshold && count % 25 === 0) {
|
|
51
|
+
log(`[StrategicCompact] ${count} tool calls - good checkpoint for /compact if context is stale`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
main().catch(err => {
|
|
58
|
+
console.error('[StrategicCompact] Error:', err.message);
|
|
59
|
+
process.exit(0);
|
|
60
|
+
});
|