overarch-linux-arm64 0.8.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/LICENSE +21 -0
- package/README.md +214 -0
- package/dist/bin/overarch.d.ts +2 -0
- package/dist/bin/overarch.js +178 -0
- package/dist/src/agent/context.d.ts +14 -0
- package/dist/src/agent/context.js +200 -0
- package/dist/src/agent/context_compressor.d.ts +21 -0
- package/dist/src/agent/context_compressor.js +90 -0
- package/dist/src/agent/manifest.d.ts +22 -0
- package/dist/src/agent/manifest.js +131 -0
- package/dist/src/agent/planner.d.ts +38 -0
- package/dist/src/agent/planner.js +118 -0
- package/dist/src/agent/runtime.d.ts +59 -0
- package/dist/src/agent/runtime.js +400 -0
- package/dist/src/cli/approval.d.ts +13 -0
- package/dist/src/cli/approval.js +71 -0
- package/dist/src/cli/args.d.ts +15 -0
- package/dist/src/cli/args.js +47 -0
- package/dist/src/cli/commands/audit.d.ts +10 -0
- package/dist/src/cli/commands/audit.js +79 -0
- package/dist/src/cli/commands/companion.d.ts +1 -0
- package/dist/src/cli/commands/companion.js +143 -0
- package/dist/src/cli/commands/config.d.ts +1 -0
- package/dist/src/cli/commands/config.js +135 -0
- package/dist/src/cli/commands/doctor.d.ts +1 -0
- package/dist/src/cli/commands/doctor.js +217 -0
- package/dist/src/cli/commands/init.d.ts +1 -0
- package/dist/src/cli/commands/init.js +134 -0
- package/dist/src/cli/commands/memory.d.ts +2 -0
- package/dist/src/cli/commands/memory.js +98 -0
- package/dist/src/cli/commands/onboarding.d.ts +3 -0
- package/dist/src/cli/commands/onboarding.js +223 -0
- package/dist/src/cli/commands/sessions.d.ts +1 -0
- package/dist/src/cli/commands/sessions.js +22 -0
- package/dist/src/cli/commands/status.d.ts +1 -0
- package/dist/src/cli/commands/status.js +37 -0
- package/dist/src/cli/commands/update.d.ts +9 -0
- package/dist/src/cli/commands/update.js +105 -0
- package/dist/src/cli/repl.d.ts +3 -0
- package/dist/src/cli/repl.js +821 -0
- package/dist/src/cli/ui.d.ts +53 -0
- package/dist/src/cli/ui.js +215 -0
- package/dist/src/companion/petdex_adapter.d.ts +31 -0
- package/dist/src/companion/petdex_adapter.js +171 -0
- package/dist/src/companion/petdex_bridge.d.ts +65 -0
- package/dist/src/companion/petdex_bridge.js +356 -0
- package/dist/src/companion/types.d.ts +57 -0
- package/dist/src/companion/types.js +11 -0
- package/dist/src/config/index.d.ts +24 -0
- package/dist/src/config/index.js +237 -0
- package/dist/src/config/types.d.ts +58 -0
- package/dist/src/config/types.js +7 -0
- package/dist/src/core/diagnostics.d.ts +48 -0
- package/dist/src/core/diagnostics.js +115 -0
- package/dist/src/core/index.d.ts +23 -0
- package/dist/src/core/index.js +23 -0
- package/dist/src/core/mission.d.ts +55 -0
- package/dist/src/core/mission.js +173 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/learning/memory_manager.d.ts +26 -0
- package/dist/src/learning/memory_manager.js +211 -0
- package/dist/src/learning/types.d.ts +54 -0
- package/dist/src/learning/types.js +1 -0
- package/dist/src/mcp/registry.d.ts +17 -0
- package/dist/src/mcp/registry.js +97 -0
- package/dist/src/mcp/types.d.ts +13 -0
- package/dist/src/mcp/types.js +1 -0
- package/dist/src/providers/anthropic.d.ts +10 -0
- package/dist/src/providers/anthropic.js +95 -0
- package/dist/src/providers/base.d.ts +15 -0
- package/dist/src/providers/base.js +108 -0
- package/dist/src/providers/factory.d.ts +28 -0
- package/dist/src/providers/factory.js +142 -0
- package/dist/src/providers/gemini.d.ts +10 -0
- package/dist/src/providers/gemini.js +97 -0
- package/dist/src/providers/openai.d.ts +9 -0
- package/dist/src/providers/openai.js +85 -0
- package/dist/src/providers/openrouter.d.ts +5 -0
- package/dist/src/providers/openrouter.js +14 -0
- package/dist/src/providers/registry.d.ts +19 -0
- package/dist/src/providers/registry.js +209 -0
- package/dist/src/providers/types.d.ts +59 -0
- package/dist/src/providers/types.js +1 -0
- package/dist/src/providers/validator.d.ts +24 -0
- package/dist/src/providers/validator.js +161 -0
- package/dist/src/security/audit.d.ts +56 -0
- package/dist/src/security/audit.js +154 -0
- package/dist/src/security/guardrails.d.ts +9 -0
- package/dist/src/security/guardrails.js +32 -0
- package/dist/src/security/permissions.d.ts +12 -0
- package/dist/src/security/permissions.js +68 -0
- package/dist/src/security/policy.d.ts +220 -0
- package/dist/src/security/policy.js +720 -0
- package/dist/src/session/manager.d.ts +22 -0
- package/dist/src/session/manager.js +172 -0
- package/dist/src/session/types.d.ts +36 -0
- package/dist/src/session/types.js +1 -0
- package/dist/src/tools/checkpoint.d.ts +26 -0
- package/dist/src/tools/checkpoint.js +160 -0
- package/dist/src/tools/diagnostics.d.ts +2 -0
- package/dist/src/tools/diagnostics.js +125 -0
- package/dist/src/tools/diff.d.ts +14 -0
- package/dist/src/tools/diff.js +124 -0
- package/dist/src/tools/filesystem.d.ts +2 -0
- package/dist/src/tools/filesystem.js +224 -0
- package/dist/src/tools/git.d.ts +2 -0
- package/dist/src/tools/git.js +138 -0
- package/dist/src/tools/markdown.d.ts +33 -0
- package/dist/src/tools/markdown.js +190 -0
- package/dist/src/tools/project.d.ts +2 -0
- package/dist/src/tools/project.js +63 -0
- package/dist/src/tools/registry.d.ts +14 -0
- package/dist/src/tools/registry.js +230 -0
- package/dist/src/tools/search.d.ts +2 -0
- package/dist/src/tools/search.js +136 -0
- package/dist/src/tools/shell.d.ts +2 -0
- package/dist/src/tools/shell.js +74 -0
- package/dist/src/tools/symbols.d.ts +30 -0
- package/dist/src/tools/symbols.js +155 -0
- package/dist/src/tools/types.d.ts +28 -0
- package/dist/src/tools/types.js +1 -0
- package/dist/src/tools/web_search.d.ts +14 -0
- package/dist/src/tools/web_search.js +154 -0
- package/install.sh +29 -0
- package/overarch +15 -0
- package/package.json +28 -0
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
import { PermissionTier } from '../config/types.js';
|
|
3
|
+
import { policyEngine } from '../security/policy.js';
|
|
4
|
+
export function createShellTools() {
|
|
5
|
+
return [
|
|
6
|
+
{
|
|
7
|
+
definition: {
|
|
8
|
+
name: 'executeCommand',
|
|
9
|
+
description: 'Execute a shell command',
|
|
10
|
+
tier: PermissionTier.StateMutating,
|
|
11
|
+
parameters: [
|
|
12
|
+
{ name: 'command', type: 'string', description: 'Command to run', required: true },
|
|
13
|
+
{ name: 'cwd', type: 'string', description: 'Working directory', required: false }
|
|
14
|
+
]
|
|
15
|
+
},
|
|
16
|
+
handler: async (params) => {
|
|
17
|
+
const cmd = String(params.command);
|
|
18
|
+
const cwd = params.cwd ? String(params.cwd) : process.cwd();
|
|
19
|
+
const isWindows = process.platform === 'win32';
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
const child = spawn(cmd, [], {
|
|
22
|
+
shell: isWindows ? 'powershell.exe' : true,
|
|
23
|
+
cwd,
|
|
24
|
+
windowsVerbatimArguments: true,
|
|
25
|
+
});
|
|
26
|
+
if (child.pid) {
|
|
27
|
+
policyEngine.registerProcess(child.pid);
|
|
28
|
+
child.on('close', () => {
|
|
29
|
+
if (child.pid)
|
|
30
|
+
policyEngine.unregisterProcess(child.pid);
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
let stdout = '';
|
|
34
|
+
let stderr = '';
|
|
35
|
+
child.stdout?.on('data', (data) => {
|
|
36
|
+
if (stdout.length < 10240) {
|
|
37
|
+
stdout += data.toString();
|
|
38
|
+
}
|
|
39
|
+
});
|
|
40
|
+
child.stderr?.on('data', (data) => {
|
|
41
|
+
if (stderr.length < 5120) {
|
|
42
|
+
stderr += data.toString();
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
let timer = setTimeout(() => {
|
|
46
|
+
child.kill();
|
|
47
|
+
resolve({
|
|
48
|
+
success: false,
|
|
49
|
+
output: stdout,
|
|
50
|
+
error: 'Command timed out after 60s.\n' + stderr
|
|
51
|
+
});
|
|
52
|
+
}, 60000);
|
|
53
|
+
child.on('close', (code) => {
|
|
54
|
+
clearTimeout(timer);
|
|
55
|
+
resolve({
|
|
56
|
+
success: code === 0,
|
|
57
|
+
output: stdout.slice(0, 10240),
|
|
58
|
+
error: stderr.slice(0, 5120),
|
|
59
|
+
metadata: { code }
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
child.on('error', (err) => {
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
resolve({
|
|
65
|
+
success: false,
|
|
66
|
+
output: stdout,
|
|
67
|
+
error: err.message
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
];
|
|
74
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface CodeSymbol {
|
|
2
|
+
name: string;
|
|
3
|
+
kind: 'function' | 'class' | 'interface' | 'type' | 'variable' | 'export';
|
|
4
|
+
line: number;
|
|
5
|
+
file: string;
|
|
6
|
+
}
|
|
7
|
+
export interface SymbolReference {
|
|
8
|
+
file: string;
|
|
9
|
+
line: number;
|
|
10
|
+
context: string;
|
|
11
|
+
}
|
|
12
|
+
export interface ChangeImpact {
|
|
13
|
+
targetFile: string;
|
|
14
|
+
symbolsChanged: string[];
|
|
15
|
+
referencingFiles: string[];
|
|
16
|
+
affectedTests: string[];
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Lightweight regex-based AST extractor for common languages (TS, JS, Python, Go)
|
|
20
|
+
*/
|
|
21
|
+
export declare function extractSymbols(content: string, filePath: string): CodeSymbol[];
|
|
22
|
+
/**
|
|
23
|
+
* Finds all references to a symbol across files in a directory
|
|
24
|
+
*/
|
|
25
|
+
export declare function findSymbolReferences(cwd: string, symbolName: string, excludes?: string[]): Promise<SymbolReference[]>;
|
|
26
|
+
/**
|
|
27
|
+
* Computes change-impact analysis: given a file, discovers its symbols,
|
|
28
|
+
* referencing files, and matching tests.
|
|
29
|
+
*/
|
|
30
|
+
export declare function analyzeChangeImpact(cwd: string, targetFile: string): Promise<ChangeImpact>;
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import * as fs from 'node:fs/promises';
|
|
2
|
+
import * as path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Lightweight regex-based AST extractor for common languages (TS, JS, Python, Go)
|
|
5
|
+
*/
|
|
6
|
+
export function extractSymbols(content, filePath) {
|
|
7
|
+
const symbols = [];
|
|
8
|
+
const lines = content.split('\n');
|
|
9
|
+
for (let i = 0; i < lines.length; i++) {
|
|
10
|
+
const line = lines[i].trim();
|
|
11
|
+
const lineNum = i + 1;
|
|
12
|
+
// TypeScript / JavaScript
|
|
13
|
+
const fnMatch = line.match(/^(?:export\s+)?(?:async\s+)?function\s+([a-zA-Z0-9_$]+)/);
|
|
14
|
+
if (fnMatch) {
|
|
15
|
+
symbols.push({ name: fnMatch[1], kind: 'function', line: lineNum, file: filePath });
|
|
16
|
+
continue;
|
|
17
|
+
}
|
|
18
|
+
const classMatch = line.match(/^(?:export\s+)?(?:abstract\s+)?class\s+([a-zA-Z0-9_$]+)/);
|
|
19
|
+
if (classMatch) {
|
|
20
|
+
symbols.push({ name: classMatch[1], kind: 'class', line: lineNum, file: filePath });
|
|
21
|
+
continue;
|
|
22
|
+
}
|
|
23
|
+
const interfaceMatch = line.match(/^(?:export\s+)?interface\s+([a-zA-Z0-9_$]+)/);
|
|
24
|
+
if (interfaceMatch) {
|
|
25
|
+
symbols.push({ name: interfaceMatch[1], kind: 'interface', line: lineNum, file: filePath });
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const typeMatch = line.match(/^(?:export\s+)?type\s+([a-zA-Z0-9_$]+)\s*=/);
|
|
29
|
+
if (typeMatch) {
|
|
30
|
+
symbols.push({ name: typeMatch[1], kind: 'type', line: lineNum, file: filePath });
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const constFnMatch = line.match(/^(?:export\s+)?const\s+([a-zA-Z0-9_$]+)\s*=\s*(?:async\s*)?\(/);
|
|
34
|
+
if (constFnMatch) {
|
|
35
|
+
symbols.push({ name: constFnMatch[1], kind: 'function', line: lineNum, file: filePath });
|
|
36
|
+
continue;
|
|
37
|
+
}
|
|
38
|
+
// Python
|
|
39
|
+
const pyDef = line.match(/^def\s+([a-zA-Z0-9_]+)\s*\(/);
|
|
40
|
+
if (pyDef) {
|
|
41
|
+
symbols.push({ name: pyDef[1], kind: 'function', line: lineNum, file: filePath });
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const pyClass = line.match(/^class\s+([a-zA-Z0-9_]+)/);
|
|
45
|
+
if (pyClass) {
|
|
46
|
+
symbols.push({ name: pyClass[1], kind: 'class', line: lineNum, file: filePath });
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
// Go
|
|
50
|
+
const goFn = line.match(/^func\s+(?:\([a-zA-Z0-9_*\s]+\)\s+)?([a-zA-Z0-9_]+)\s*\(/);
|
|
51
|
+
if (goFn) {
|
|
52
|
+
symbols.push({ name: goFn[1], kind: 'function', line: lineNum, file: filePath });
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
return symbols;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Finds all references to a symbol across files in a directory
|
|
60
|
+
*/
|
|
61
|
+
export async function findSymbolReferences(cwd, symbolName, excludes = ['node_modules', '.git', 'dist', 'build']) {
|
|
62
|
+
const references = [];
|
|
63
|
+
const regex = new RegExp(`\\b${symbolName}\\b`);
|
|
64
|
+
async function scan(dir) {
|
|
65
|
+
let entries;
|
|
66
|
+
try {
|
|
67
|
+
entries = await fs.readdir(dir, { withFileTypes: true });
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
const fullPath = path.join(dir, entry.name);
|
|
74
|
+
const relPath = path.relative(cwd, fullPath);
|
|
75
|
+
if (entry.isDirectory()) {
|
|
76
|
+
if (excludes.some(ex => relPath.split(path.sep).includes(ex)))
|
|
77
|
+
continue;
|
|
78
|
+
await scan(fullPath);
|
|
79
|
+
}
|
|
80
|
+
else if (entry.isFile()) {
|
|
81
|
+
const ext = path.extname(entry.name).toLowerCase();
|
|
82
|
+
if (!['.ts', '.js', '.jsx', '.tsx', '.py', '.go', '.rs'].includes(ext))
|
|
83
|
+
continue;
|
|
84
|
+
try {
|
|
85
|
+
const content = await fs.readFile(fullPath, 'utf8');
|
|
86
|
+
const lines = content.split('\n');
|
|
87
|
+
for (let i = 0; i < lines.length; i++) {
|
|
88
|
+
if (regex.test(lines[i])) {
|
|
89
|
+
references.push({
|
|
90
|
+
file: relPath.replace(/\\/g, '/'),
|
|
91
|
+
line: i + 1,
|
|
92
|
+
context: lines[i].trim()
|
|
93
|
+
});
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch { }
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
await scan(cwd);
|
|
102
|
+
return references;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Computes change-impact analysis: given a file, discovers its symbols,
|
|
106
|
+
* referencing files, and matching tests.
|
|
107
|
+
*/
|
|
108
|
+
export async function analyzeChangeImpact(cwd, targetFile) {
|
|
109
|
+
const absPath = path.isAbsolute(targetFile) ? targetFile : path.join(cwd, targetFile);
|
|
110
|
+
const relPath = path.relative(cwd, absPath).replace(/\\/g, '/');
|
|
111
|
+
let content = '';
|
|
112
|
+
try {
|
|
113
|
+
content = await fs.readFile(absPath, 'utf8');
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
return { targetFile: relPath, symbolsChanged: [], referencingFiles: [], affectedTests: [] };
|
|
117
|
+
}
|
|
118
|
+
const symbols = extractSymbols(content, relPath);
|
|
119
|
+
const referencingFilesSet = new Set();
|
|
120
|
+
const affectedTestsSet = new Set();
|
|
121
|
+
// Check direct test mapping
|
|
122
|
+
const baseName = path.basename(relPath, path.extname(relPath));
|
|
123
|
+
const testPatterns = [
|
|
124
|
+
`tests/${baseName}.test.ts`,
|
|
125
|
+
`tests/${baseName}.test.js`,
|
|
126
|
+
`test/${baseName}.test.ts`,
|
|
127
|
+
`tests/test_${baseName}.py`,
|
|
128
|
+
`${baseName}_test.go`
|
|
129
|
+
];
|
|
130
|
+
for (const tp of testPatterns) {
|
|
131
|
+
try {
|
|
132
|
+
await fs.access(path.join(cwd, tp));
|
|
133
|
+
affectedTestsSet.add(tp);
|
|
134
|
+
}
|
|
135
|
+
catch { }
|
|
136
|
+
}
|
|
137
|
+
// Scan top 5 symbols for callers
|
|
138
|
+
for (const sym of symbols.slice(0, 5)) {
|
|
139
|
+
const refs = await findSymbolReferences(cwd, sym.name);
|
|
140
|
+
for (const ref of refs) {
|
|
141
|
+
if (ref.file !== relPath) {
|
|
142
|
+
referencingFilesSet.add(ref.file);
|
|
143
|
+
if (ref.file.toLowerCase().includes('test') || ref.file.toLowerCase().includes('spec')) {
|
|
144
|
+
affectedTestsSet.add(ref.file);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return {
|
|
150
|
+
targetFile: relPath,
|
|
151
|
+
symbolsChanged: symbols.map(s => `${s.kind} ${s.name}`),
|
|
152
|
+
referencingFiles: Array.from(referencingFilesSet),
|
|
153
|
+
affectedTests: Array.from(affectedTestsSet)
|
|
154
|
+
};
|
|
155
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { PermissionTier } from '../config/types.js';
|
|
2
|
+
export interface ToolParameter {
|
|
3
|
+
name: string;
|
|
4
|
+
type: 'string' | 'number' | 'boolean' | 'array' | 'object';
|
|
5
|
+
description: string;
|
|
6
|
+
required: boolean;
|
|
7
|
+
enum?: string[];
|
|
8
|
+
items?: {
|
|
9
|
+
type: string;
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
export interface ToolDefinition {
|
|
13
|
+
name: string;
|
|
14
|
+
description: string;
|
|
15
|
+
parameters: ToolParameter[];
|
|
16
|
+
tier: PermissionTier;
|
|
17
|
+
}
|
|
18
|
+
export interface ToolResult {
|
|
19
|
+
success: boolean;
|
|
20
|
+
output: string;
|
|
21
|
+
error?: string;
|
|
22
|
+
metadata?: Record<string, unknown>;
|
|
23
|
+
}
|
|
24
|
+
export type ToolHandler = (params: Record<string, unknown>) => Promise<ToolResult>;
|
|
25
|
+
export interface Tool {
|
|
26
|
+
definition: ToolDefinition;
|
|
27
|
+
handler: ToolHandler;
|
|
28
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export interface WebSearchResult {
|
|
2
|
+
title: string;
|
|
3
|
+
snippet: string;
|
|
4
|
+
url: string;
|
|
5
|
+
}
|
|
6
|
+
export interface WebSearchResponse {
|
|
7
|
+
query: string;
|
|
8
|
+
results: WebSearchResult[];
|
|
9
|
+
totalResults: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Executes a web search using DuckDuckGo HTML endpoint without external dependencies.
|
|
13
|
+
*/
|
|
14
|
+
export declare function searchWeb(query: string, maxResults?: number): Promise<WebSearchResponse>;
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import * as https from 'node:https';
|
|
2
|
+
import * as http from 'node:http';
|
|
3
|
+
import { URL } from 'node:url';
|
|
4
|
+
/**
|
|
5
|
+
* Clean HTML tags and decode HTML entities from text.
|
|
6
|
+
*/
|
|
7
|
+
function cleanHtml(raw) {
|
|
8
|
+
return raw
|
|
9
|
+
.replace(/<[^>]+>/g, '')
|
|
10
|
+
.replace(/&/g, '&')
|
|
11
|
+
.replace(/</g, '<')
|
|
12
|
+
.replace(/>/g, '>')
|
|
13
|
+
.replace(/"/g, '"')
|
|
14
|
+
.replace(/'/g, "'")
|
|
15
|
+
.replace(/ /g, ' ')
|
|
16
|
+
.replace(/\s+/g, ' ')
|
|
17
|
+
.trim();
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Standard-library HTTP GET helper with timeout.
|
|
21
|
+
*/
|
|
22
|
+
function fetchText(urlStr, timeoutMs = 8000) {
|
|
23
|
+
return new Promise((resolve, reject) => {
|
|
24
|
+
const parsedUrl = new URL(urlStr);
|
|
25
|
+
const client = parsedUrl.protocol === 'https:' ? https : http;
|
|
26
|
+
const req = client.get(urlStr, {
|
|
27
|
+
headers: {
|
|
28
|
+
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36',
|
|
29
|
+
Accept: 'text/html,application/xhtml+xml,application/json,*/*',
|
|
30
|
+
'Accept-Language': 'en-US,en;q=0.9',
|
|
31
|
+
},
|
|
32
|
+
}, (res) => {
|
|
33
|
+
// Follow redirect (301, 302)
|
|
34
|
+
if (res.statusCode &&
|
|
35
|
+
res.statusCode >= 300 &&
|
|
36
|
+
res.statusCode < 400 &&
|
|
37
|
+
res.headers.location) {
|
|
38
|
+
const redirectUrl = new URL(res.headers.location, urlStr).toString();
|
|
39
|
+
return fetchText(redirectUrl, timeoutMs).then(resolve).catch(reject);
|
|
40
|
+
}
|
|
41
|
+
if (res.statusCode && (res.statusCode < 200 || res.statusCode >= 300)) {
|
|
42
|
+
return reject(new Error(`HTTP request failed with status ${res.statusCode}`));
|
|
43
|
+
}
|
|
44
|
+
let data = '';
|
|
45
|
+
res.on('data', (chunk) => (data += chunk));
|
|
46
|
+
res.on('end', () => resolve(data));
|
|
47
|
+
});
|
|
48
|
+
req.on('error', reject);
|
|
49
|
+
req.setTimeout(timeoutMs, () => {
|
|
50
|
+
req.destroy();
|
|
51
|
+
reject(new Error(`Search request timed out after ${timeoutMs}ms`));
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Executes a web search using DuckDuckGo HTML endpoint without external dependencies.
|
|
57
|
+
*/
|
|
58
|
+
export async function searchWeb(query, maxResults = 5) {
|
|
59
|
+
const cleanQuery = query.trim();
|
|
60
|
+
if (!cleanQuery) {
|
|
61
|
+
return { query: '', results: [], totalResults: 0 };
|
|
62
|
+
}
|
|
63
|
+
const encoded = encodeURIComponent(cleanQuery);
|
|
64
|
+
const searchUrl = `https://html.duckduckgo.com/html/?q=${encoded}`;
|
|
65
|
+
try {
|
|
66
|
+
const html = await fetchText(searchUrl, 10000);
|
|
67
|
+
const results = [];
|
|
68
|
+
// Parse DuckDuckGo HTML results
|
|
69
|
+
// Matches: <a class="result__snippet" ...>...</a> and result links
|
|
70
|
+
const resultBlocks = html.split(/class="result\s+results_links/i).slice(1);
|
|
71
|
+
for (const block of resultBlocks) {
|
|
72
|
+
if (results.length >= maxResults)
|
|
73
|
+
break;
|
|
74
|
+
// Title & URL
|
|
75
|
+
const linkMatch = block.match(/<a[^>]+class="result__url"[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/i) ||
|
|
76
|
+
block.match(/<a[^>]+class="result__snippet"[^>]+href="([^"]+)"[^>]*>/i);
|
|
77
|
+
const titleMatch = block.match(/<a[^>]+class="result__a"[^>]+href="([^"]+)"[^>]*>([\s\S]*?)<\/a>/i);
|
|
78
|
+
const snippetMatch = block.match(/<a[^>]+class="result__snippet"[^>]*>([\s\S]*?)<\/a>/i);
|
|
79
|
+
let url = '';
|
|
80
|
+
if (titleMatch && titleMatch[1]) {
|
|
81
|
+
url = titleMatch[1];
|
|
82
|
+
}
|
|
83
|
+
else if (linkMatch && linkMatch[1]) {
|
|
84
|
+
url = linkMatch[1];
|
|
85
|
+
}
|
|
86
|
+
// Unwrap DuckDuckGo redirect uddg= if present
|
|
87
|
+
if (url.includes('uddg=')) {
|
|
88
|
+
try {
|
|
89
|
+
const match = url.match(/uddg=([^&]+)/);
|
|
90
|
+
if (match)
|
|
91
|
+
url = decodeURIComponent(match[1]);
|
|
92
|
+
}
|
|
93
|
+
catch { }
|
|
94
|
+
}
|
|
95
|
+
const title = titleMatch ? cleanHtml(titleMatch[2]) : cleanQuery;
|
|
96
|
+
const snippet = snippetMatch ? cleanHtml(snippetMatch[1]) : '';
|
|
97
|
+
if (url && (title || snippet)) {
|
|
98
|
+
results.push({
|
|
99
|
+
title: title || cleanQuery,
|
|
100
|
+
snippet: snippet || 'No snippet available',
|
|
101
|
+
url,
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
// Fallback if HTML layout changes: DuckDuckGo instant API
|
|
106
|
+
if (results.length === 0) {
|
|
107
|
+
try {
|
|
108
|
+
const apiUrl = `https://api.duckduckgo.com/?q=${encoded}&format=json&no_html=1&skip_disambig=1`;
|
|
109
|
+
const jsonText = await fetchText(apiUrl, 5000);
|
|
110
|
+
const data = JSON.parse(jsonText);
|
|
111
|
+
if (data.AbstractText) {
|
|
112
|
+
results.push({
|
|
113
|
+
title: data.Heading || cleanQuery,
|
|
114
|
+
snippet: data.AbstractText,
|
|
115
|
+
url: data.AbstractURL || 'https://duckduckgo.com',
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
if (Array.isArray(data.RelatedTopics)) {
|
|
119
|
+
for (const topic of data.RelatedTopics) {
|
|
120
|
+
if (results.length >= maxResults)
|
|
121
|
+
break;
|
|
122
|
+
if (topic.Text && topic.FirstURL) {
|
|
123
|
+
results.push({
|
|
124
|
+
title: topic.Text.split(' - ')[0] || cleanQuery,
|
|
125
|
+
snippet: topic.Text,
|
|
126
|
+
url: topic.FirstURL,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
catch { }
|
|
133
|
+
}
|
|
134
|
+
return {
|
|
135
|
+
query: cleanQuery,
|
|
136
|
+
results,
|
|
137
|
+
totalResults: results.length,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
catch (err) {
|
|
141
|
+
// Return empty results with error message rather than crashing
|
|
142
|
+
return {
|
|
143
|
+
query: cleanQuery,
|
|
144
|
+
results: [
|
|
145
|
+
{
|
|
146
|
+
title: `Search error for: ${cleanQuery}`,
|
|
147
|
+
snippet: `Unable to retrieve live search results: ${err.message}`,
|
|
148
|
+
url: `https://duckduckgo.com/?q=${encoded}`,
|
|
149
|
+
},
|
|
150
|
+
],
|
|
151
|
+
totalResults: 0,
|
|
152
|
+
};
|
|
153
|
+
}
|
|
154
|
+
}
|
package/install.sh
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
|
|
3
|
+
set -e
|
|
4
|
+
|
|
5
|
+
INSTALL_DIR="${OVERARCH_INSTALL_DIR:-$HOME/.local/bin}"
|
|
6
|
+
|
|
7
|
+
mkdir -p "$INSTALL_DIR"
|
|
8
|
+
|
|
9
|
+
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
|
10
|
+
|
|
11
|
+
rm -rf "$HOME/.overarch-dist"
|
|
12
|
+
cp -R "$SCRIPT_DIR/dist" "$HOME/.overarch-dist"
|
|
13
|
+
|
|
14
|
+
cp "$SCRIPT_DIR/overarch" "$INSTALL_DIR/overarch"
|
|
15
|
+
|
|
16
|
+
chmod +x "$INSTALL_DIR/overarch"
|
|
17
|
+
|
|
18
|
+
echo "✓ Overarch CLI v0.8.0 installed to:"
|
|
19
|
+
echo " $INSTALL_DIR/overarch"
|
|
20
|
+
|
|
21
|
+
case ":$PATH:" in
|
|
22
|
+
*":$INSTALL_DIR:"*)
|
|
23
|
+
;;
|
|
24
|
+
*)
|
|
25
|
+
echo
|
|
26
|
+
echo "Add this directory to your PATH:"
|
|
27
|
+
echo " export PATH=\"\$PATH:$INSTALL_DIR\""
|
|
28
|
+
;;
|
|
29
|
+
esac
|
package/overarch
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
#!/usr/bin/env sh
|
|
2
|
+
# Overarch CLI v0.8.0
|
|
3
|
+
# Linux (ARM64 / aarch64)
|
|
4
|
+
|
|
5
|
+
set -e
|
|
6
|
+
|
|
7
|
+
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
|
|
8
|
+
|
|
9
|
+
if ! command -v node >/dev/null 2>&1; then
|
|
10
|
+
echo "Error: Node.js >= 20 is required to run Overarch."
|
|
11
|
+
echo "Install Node.js from https://nodejs.org or your package manager."
|
|
12
|
+
exit 1
|
|
13
|
+
fi
|
|
14
|
+
|
|
15
|
+
exec node "$SCRIPT_DIR/dist/bin/overarch.js" "$@"
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "overarch-linux-arm64",
|
|
3
|
+
"version": "0.8.0",
|
|
4
|
+
"description": "Overarch CLI platform package for Linux (ARM64 / aarch64)",
|
|
5
|
+
"os": [
|
|
6
|
+
"linux"
|
|
7
|
+
],
|
|
8
|
+
"cpu": [
|
|
9
|
+
"arm64"
|
|
10
|
+
],
|
|
11
|
+
"bin": "./overarch",
|
|
12
|
+
"files": [
|
|
13
|
+
"overarch",
|
|
14
|
+
"install.sh",
|
|
15
|
+
"dist",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE"
|
|
18
|
+
],
|
|
19
|
+
"license": "MIT",
|
|
20
|
+
"author": "Yatharth Roy (The Thursday AI Company of New Delhi)",
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/yritb/overarch.git"
|
|
24
|
+
},
|
|
25
|
+
"engines": {
|
|
26
|
+
"node": ">=20.0.0"
|
|
27
|
+
}
|
|
28
|
+
}
|