@solar_orb/agent_orb 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 +59 -0
- package/bin/agent_orb.js +27 -0
- package/dist/adapter.js +25 -0
- package/dist/checksum.js +27 -0
- package/dist/config.js +20 -0
- package/dist/download.js +201 -0
- package/dist/index.js +82 -0
- package/dist/platform.js +62 -0
- package/dist/setup.js +298 -0
- package/dist/shell.js +80 -0
- package/package.json +32 -0
package/README.md
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# agent_orb npx bootstrapper
|
|
2
|
+
|
|
3
|
+
Local development bootstrapper for Agent Orb. It is intended to become:
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
npx @solar_orb/agent_orb
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
after the package is published to npm.
|
|
10
|
+
|
|
11
|
+
## Local development
|
|
12
|
+
|
|
13
|
+
From the repo root:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
./scripts/release/smoke-npx-local.sh
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
For manual testing:
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
npm --prefix packages/agent_orb run package-runtime
|
|
23
|
+
npx --yes ./packages/agent_orb setup --yes
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
If `packages/agent_orb/releases` contains a matching native bundle, setup installs that bundle directly with SHA256 verification. Otherwise it falls back to source build.
|
|
27
|
+
|
|
28
|
+
## Windows local path
|
|
29
|
+
|
|
30
|
+
```powershell
|
|
31
|
+
cd C:\path\to\AgentOrb
|
|
32
|
+
npx --yes .\packages\agent_orb
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
If no Windows bundle is present and Rust is not installed on Windows yet, setup will print:
|
|
36
|
+
|
|
37
|
+
```powershell
|
|
38
|
+
winget install --id Rustlang.Rustup -e
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
For the intended no-Rust Windows path, publish or place `agent-orb-windows-x64.zip` plus `checksums.txt` in the release endpoint/package releases directory.
|
|
42
|
+
|
|
43
|
+
## Windows + WSL repo caveat
|
|
44
|
+
|
|
45
|
+
Windows npm may fail with `ERR_INVALID_URL` when installing a local package directly from a UNC path like:
|
|
46
|
+
|
|
47
|
+
```powershell
|
|
48
|
+
\\wsl.localhost\Ubuntu\home\...\AgentOrb
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
For Windows-host testing, prefer either:
|
|
52
|
+
|
|
53
|
+
1. run from a Windows-local clone, or
|
|
54
|
+
2. use a packed tarball from a Windows-local directory:
|
|
55
|
+
|
|
56
|
+
```powershell
|
|
57
|
+
cd $env:TEMP\agent-orb-npx
|
|
58
|
+
npx --yes .\agent_orb-0.1.0.tgz --help
|
|
59
|
+
```
|
package/bin/agent_orb.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { spawnSync } from 'node:child_process';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const packageDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
8
|
+
const entry = path.join(packageDir, 'dist', 'index.js');
|
|
9
|
+
|
|
10
|
+
if (!existsSync(entry)) {
|
|
11
|
+
console.error('[agent_orb] bootstrapper build output is missing; building it now...');
|
|
12
|
+
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
13
|
+
|
|
14
|
+
const install = spawnSync(npm, ['install'], {
|
|
15
|
+
cwd: packageDir,
|
|
16
|
+
stdio: 'inherit',
|
|
17
|
+
});
|
|
18
|
+
if (install.status !== 0) process.exit(install.status ?? 1);
|
|
19
|
+
|
|
20
|
+
const build = spawnSync(npm, ['run', 'build'], {
|
|
21
|
+
cwd: packageDir,
|
|
22
|
+
stdio: 'inherit',
|
|
23
|
+
});
|
|
24
|
+
if (build.status !== 0) process.exit(build.status ?? 1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
await import(pathToFileURL(entry).href);
|
package/dist/adapter.js
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { findCommand } from './shell.js';
|
|
2
|
+
export const adapters = [
|
|
3
|
+
{
|
|
4
|
+
name: 'codex',
|
|
5
|
+
displayName: 'Codex CLI',
|
|
6
|
+
binaryCandidates: process.platform === 'win32' ? ['codex.exe', 'codex'] : ['codex'],
|
|
7
|
+
wrapperCommand: process.platform === 'win32' ? 'codex-orb.cmd' : 'codex-orb',
|
|
8
|
+
promptPatterns: ['approve', 'permission', 'continue?', 'yes/no'],
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
name: 'claude',
|
|
12
|
+
displayName: 'Claude Code CLI',
|
|
13
|
+
binaryCandidates: process.platform === 'win32' ? ['claude.exe', 'claude'] : ['claude'],
|
|
14
|
+
wrapperCommand: process.platform === 'win32' ? 'claude-orb.cmd' : 'claude-orb',
|
|
15
|
+
promptPatterns: ['continue?', 'permission', 'press enter', 'approve'],
|
|
16
|
+
},
|
|
17
|
+
];
|
|
18
|
+
export function detectAdapters() {
|
|
19
|
+
return adapters.map((adapter) => {
|
|
20
|
+
const found = adapter.binaryCandidates
|
|
21
|
+
.map((candidate) => findCommand(candidate))
|
|
22
|
+
.find((candidate) => candidate !== undefined);
|
|
23
|
+
return { ...adapter, foundBinary: found };
|
|
24
|
+
});
|
|
25
|
+
}
|
package/dist/checksum.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import crypto from 'node:crypto';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
export function sha256File(filePath) {
|
|
4
|
+
const hash = crypto.createHash('sha256');
|
|
5
|
+
hash.update(fs.readFileSync(filePath));
|
|
6
|
+
return hash.digest('hex');
|
|
7
|
+
}
|
|
8
|
+
export function parseChecksums(text) {
|
|
9
|
+
const checksums = new Map();
|
|
10
|
+
for (const rawLine of text.split(/\r?\n/)) {
|
|
11
|
+
const line = rawLine.trim();
|
|
12
|
+
if (!line || line.startsWith('#'))
|
|
13
|
+
continue;
|
|
14
|
+
const match = line.match(/^([a-fA-F0-9]{64})\s+\*?(.+)$/);
|
|
15
|
+
if (!match)
|
|
16
|
+
continue;
|
|
17
|
+
const [, checksum, filename] = match;
|
|
18
|
+
checksums.set(filename.trim(), checksum.toLowerCase());
|
|
19
|
+
}
|
|
20
|
+
return checksums;
|
|
21
|
+
}
|
|
22
|
+
export function verifyChecksum(filePath, expectedSha256) {
|
|
23
|
+
const actual = sha256File(filePath);
|
|
24
|
+
if (actual !== expectedSha256.toLowerCase()) {
|
|
25
|
+
throw new Error(`Checksum mismatch for ${filePath}\nexpected: ${expectedSha256}\nactual: ${actual}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
export function runtimeConfigFromEnv(env = process.env) {
|
|
4
|
+
const daemonPort = Number.parseInt(env.AGENT_ORB_DAEMON_PORT ?? '17321', 10);
|
|
5
|
+
if (!Number.isInteger(daemonPort) || daemonPort <= 0 || daemonPort > 65535) {
|
|
6
|
+
throw new Error(`Invalid AGENT_ORB_DAEMON_PORT: ${env.AGENT_ORB_DAEMON_PORT}`);
|
|
7
|
+
}
|
|
8
|
+
return {
|
|
9
|
+
daemonHost: env.AGENT_ORB_DAEMON_HOST ?? '127.0.0.1',
|
|
10
|
+
daemonPort,
|
|
11
|
+
};
|
|
12
|
+
}
|
|
13
|
+
export function writeConfig(configDir, selectedAdapters, runtime = runtimeConfigFromEnv()) {
|
|
14
|
+
fs.mkdirSync(configDir, { recursive: true });
|
|
15
|
+
const configPath = path.join(configDir, 'config.toml');
|
|
16
|
+
const enabled = new Set(selectedAdapters.map((adapter) => adapter.name));
|
|
17
|
+
const content = `# Generated by npx agent_orb\n\n[install]\nmethod = "npx"\nversion = "0.1.0"\n\n[adapters.codex]\nenabled = ${enabled.has('codex')}\nbinary = "codex"\nwrapper = "codex-orb"\n\n[adapters.claude]\nenabled = ${enabled.has('claude')}\nbinary = "claude"\nwrapper = "claude-orb"\n\n[daemon]\nhost = "${runtime.daemonHost}"\nport = ${runtime.daemonPort}\nauto_start = true\n\n[orb]\nposition = "top-right"\nsize = 36\nopacity = 0.88\nalways_on_top = true\nclick_through = false\n\n[colors]\ndisconnected = "#6B7280"\nidle = "#9CA3AF"\nstarting = "#60A5FA"\nactive = "#3B82F6"\nthinking_like = "#8B5CF6"\nwaiting_input = "#FBBF24"\ncompleted = "#22C55E"\nerror = "#EF4444"\nwarning = "#F97316"\n\n[behavior]\nsilent_threshold_seconds = 20\nstuck_threshold_seconds = 180\ncompleted_hold_seconds = 10\nerror_requires_click_to_clear = true\n\n[privacy]\ninclude_output_sample = false\nmax_sample_chars = 512\n`;
|
|
18
|
+
fs.writeFileSync(configPath, content, 'utf8');
|
|
19
|
+
return configPath;
|
|
20
|
+
}
|
package/dist/download.js
ADDED
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
5
|
+
import { parseChecksums, verifyChecksum } from './checksum.js';
|
|
6
|
+
import { run } from './shell.js';
|
|
7
|
+
export async function installRuntimeBundle(platform, options = {}) {
|
|
8
|
+
if (!options.force && runtimeLooksInstalled(platform)) {
|
|
9
|
+
console.log('\n==> Runtime bundle');
|
|
10
|
+
console.log(`✓ existing runtime found at ${platform.runtimeDir}`);
|
|
11
|
+
return true;
|
|
12
|
+
}
|
|
13
|
+
const baseUrl = releaseBaseUrl(platform, options);
|
|
14
|
+
if (!baseUrl)
|
|
15
|
+
return false;
|
|
16
|
+
console.log('\n==> Downloading runtime bundle');
|
|
17
|
+
console.log(`Release base: ${baseUrl}`);
|
|
18
|
+
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'agent-orb-runtime-'));
|
|
19
|
+
try {
|
|
20
|
+
const bundlePath = path.join(tempDir, platform.bundleName);
|
|
21
|
+
const checksumsPath = path.join(tempDir, 'checksums.txt');
|
|
22
|
+
await downloadFile(joinUrl(baseUrl, platform.bundleName), bundlePath);
|
|
23
|
+
await downloadFile(joinUrl(baseUrl, 'checksums.txt'), checksumsPath);
|
|
24
|
+
const checksums = parseChecksums(fs.readFileSync(checksumsPath, 'utf8'));
|
|
25
|
+
const expected = checksums.get(platform.bundleName);
|
|
26
|
+
if (!expected) {
|
|
27
|
+
throw new Error(`checksums.txt does not contain an entry for ${platform.bundleName}`);
|
|
28
|
+
}
|
|
29
|
+
verifyChecksum(bundlePath, expected);
|
|
30
|
+
console.log(`✓ checksum verified: ${platform.bundleName}`);
|
|
31
|
+
extractBundle(bundlePath, tempDir, platform);
|
|
32
|
+
writeInstallManifest(platform, {
|
|
33
|
+
bundle: platform.bundleName,
|
|
34
|
+
sha256: expected,
|
|
35
|
+
source: baseUrl,
|
|
36
|
+
});
|
|
37
|
+
console.log(`✓ installed runtime bundle into ${platform.runtimeDir}`);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
finally {
|
|
41
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
export function runtimeLooksInstalled(platform) {
|
|
45
|
+
const required = ['agent_orb', 'agent_orbd'].map((name) => path.join(platform.runtimeDir, `${name}${platform.exeSuffix}`));
|
|
46
|
+
return required.every((file) => fs.existsSync(file));
|
|
47
|
+
}
|
|
48
|
+
function releaseBaseUrl(platform, options) {
|
|
49
|
+
if (options.releaseDir) {
|
|
50
|
+
return pathToFileURL(path.resolve(options.releaseDir)).href;
|
|
51
|
+
}
|
|
52
|
+
if (options.releaseBaseUrl) {
|
|
53
|
+
return normalizeBase(options.releaseBaseUrl);
|
|
54
|
+
}
|
|
55
|
+
if (process.env.AGENT_ORB_RELEASE_DIR) {
|
|
56
|
+
return pathToFileURL(path.resolve(process.env.AGENT_ORB_RELEASE_DIR)).href;
|
|
57
|
+
}
|
|
58
|
+
const configured = process.env.AGENT_ORB_RELEASE_BASE_URL ?? process.env.AGENT_ORB_RELEASE_URL;
|
|
59
|
+
if (configured?.trim())
|
|
60
|
+
return configured.trim().replace(/\/+$/, '');
|
|
61
|
+
const bundled = bundledReleaseBaseUrl(platform);
|
|
62
|
+
if (bundled)
|
|
63
|
+
return bundled;
|
|
64
|
+
const version = process.env.AGENT_ORB_VERSION ?? 'v0.1.0';
|
|
65
|
+
const repo = githubRepository();
|
|
66
|
+
if (repo)
|
|
67
|
+
return `https://github.com/${repo}/releases/download/${version}`;
|
|
68
|
+
return undefined;
|
|
69
|
+
}
|
|
70
|
+
function githubRepository() {
|
|
71
|
+
const configured = process.env.AGENT_ORB_GITHUB_REPOSITORY;
|
|
72
|
+
if (configured?.trim())
|
|
73
|
+
return configured.trim();
|
|
74
|
+
const packageRepo = process.env.npm_package_config_github_repository;
|
|
75
|
+
if (packageRepo?.trim())
|
|
76
|
+
return packageRepo.trim();
|
|
77
|
+
const ownPackageRepo = readPackageGithubRepository();
|
|
78
|
+
if (ownPackageRepo)
|
|
79
|
+
return ownPackageRepo;
|
|
80
|
+
return undefined;
|
|
81
|
+
}
|
|
82
|
+
function readPackageGithubRepository() {
|
|
83
|
+
try {
|
|
84
|
+
const packageJsonPath = fileURLToPath(new URL('../package.json', import.meta.url));
|
|
85
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
|
86
|
+
const repo = packageJson.config?.github_repository;
|
|
87
|
+
return typeof repo === 'string' && repo.trim() ? repo.trim() : undefined;
|
|
88
|
+
}
|
|
89
|
+
catch {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
function bundledReleaseBaseUrl(platform) {
|
|
94
|
+
const releaseDir = fileURLToPath(new URL('../releases/', import.meta.url));
|
|
95
|
+
if (fs.existsSync(path.join(releaseDir, platform.bundleName)) &&
|
|
96
|
+
fs.existsSync(path.join(releaseDir, 'checksums.txt'))) {
|
|
97
|
+
return pathToFileURL(releaseDir).href;
|
|
98
|
+
}
|
|
99
|
+
return undefined;
|
|
100
|
+
}
|
|
101
|
+
async function downloadFile(url, dest) {
|
|
102
|
+
console.log(`↓ ${url}`);
|
|
103
|
+
if (url.startsWith('file:')) {
|
|
104
|
+
fs.copyFileSync(fileURLToPath(url), dest);
|
|
105
|
+
return;
|
|
106
|
+
}
|
|
107
|
+
const response = await fetch(url);
|
|
108
|
+
if (!response.ok) {
|
|
109
|
+
throw new Error(`Download failed (${response.status} ${response.statusText}): ${url}`);
|
|
110
|
+
}
|
|
111
|
+
const bytes = Buffer.from(await response.arrayBuffer());
|
|
112
|
+
fs.writeFileSync(dest, bytes);
|
|
113
|
+
}
|
|
114
|
+
function writeInstallManifest(platform, manifest) {
|
|
115
|
+
fs.mkdirSync(platform.runtimeDir, { recursive: true });
|
|
116
|
+
const manifestPath = path.join(platform.runtimeDir, 'agent-orb-runtime.json');
|
|
117
|
+
fs.writeFileSync(manifestPath, `${JSON.stringify({
|
|
118
|
+
install_method: 'npx',
|
|
119
|
+
installed_at: new Date().toISOString(),
|
|
120
|
+
...manifest,
|
|
121
|
+
}, null, 2)}\n`, 'utf8');
|
|
122
|
+
}
|
|
123
|
+
function extractBundle(bundlePath, tempDir, platform) {
|
|
124
|
+
const extractDir = path.join(tempDir, 'extract');
|
|
125
|
+
fs.mkdirSync(extractDir, { recursive: true });
|
|
126
|
+
if (platform.platform === 'windows') {
|
|
127
|
+
run('tar', ['-xf', bundlePath, '-C', extractDir]);
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
run('tar', ['-xzf', bundlePath, '-C', extractDir]);
|
|
131
|
+
}
|
|
132
|
+
fs.mkdirSync(platform.runtimeDir, { recursive: true });
|
|
133
|
+
const binDir = findExtractedBinDir(extractDir, platform);
|
|
134
|
+
copyRuntimeFile(binDir, platform.runtimeDir, `agent_orb${platform.exeSuffix}`, platform);
|
|
135
|
+
copyRuntimeFile(binDir, platform.runtimeDir, `agent_orbd${platform.exeSuffix}`, platform);
|
|
136
|
+
copyOptionalRuntimeFile(binDir, platform.runtimeDir, `agent-orb-ui${platform.exeSuffix}`, platform);
|
|
137
|
+
}
|
|
138
|
+
function findExtractedBinDir(extractDir, platform) {
|
|
139
|
+
const candidates = [
|
|
140
|
+
extractDir,
|
|
141
|
+
path.join(extractDir, 'bin'),
|
|
142
|
+
path.join(extractDir, 'agent-orb'),
|
|
143
|
+
path.join(extractDir, 'agent-orb', 'bin'),
|
|
144
|
+
];
|
|
145
|
+
for (const candidate of candidates) {
|
|
146
|
+
if (fs.existsSync(path.join(candidate, `agent_orb${platform.exeSuffix}`))) {
|
|
147
|
+
return candidate;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
throw new Error(`Bundle does not contain agent_orb${platform.exeSuffix}`);
|
|
151
|
+
}
|
|
152
|
+
function copyRuntimeFile(sourceDir, destDir, filename, platform) {
|
|
153
|
+
const source = path.join(sourceDir, filename);
|
|
154
|
+
if (!fs.existsSync(source))
|
|
155
|
+
throw new Error(`Bundle is missing required file: ${filename}`);
|
|
156
|
+
copyFile(source, path.join(destDir, filename), platform);
|
|
157
|
+
}
|
|
158
|
+
function copyOptionalRuntimeFile(sourceDir, destDir, filename, platform) {
|
|
159
|
+
const source = path.join(sourceDir, filename);
|
|
160
|
+
if (fs.existsSync(source))
|
|
161
|
+
copyFile(source, path.join(destDir, filename), platform);
|
|
162
|
+
}
|
|
163
|
+
function copyFile(source, dest, platform) {
|
|
164
|
+
const temp = path.join(path.dirname(dest), `.${path.basename(dest)}.${process.pid}.tmp`);
|
|
165
|
+
fs.copyFileSync(source, temp);
|
|
166
|
+
if (platform.platform !== 'windows')
|
|
167
|
+
fs.chmodSync(temp, 0o755);
|
|
168
|
+
try {
|
|
169
|
+
fs.renameSync(temp, dest);
|
|
170
|
+
}
|
|
171
|
+
catch (error) {
|
|
172
|
+
fs.rmSync(temp, { force: true });
|
|
173
|
+
if (platform.platform === 'windows') {
|
|
174
|
+
throw new Error(`Could not replace ${dest}. Stop agent_orbd.exe / agent-orb-ui.exe and rerun npx agent_orb. Original error: ${formatError(error)}`);
|
|
175
|
+
}
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
function joinUrl(base, name) {
|
|
180
|
+
const normalized = normalizeBase(base);
|
|
181
|
+
if (normalized.startsWith('file:')) {
|
|
182
|
+
return new URL(encodeURIComponent(name), ensureTrailingSlash(normalized)).href;
|
|
183
|
+
}
|
|
184
|
+
return `${normalized.replace(/\/+$/, '')}/${encodeURIComponent(name)}`;
|
|
185
|
+
}
|
|
186
|
+
function formatError(error) {
|
|
187
|
+
return error instanceof Error ? error.message : String(error);
|
|
188
|
+
}
|
|
189
|
+
function normalizeBase(value) {
|
|
190
|
+
const trimmed = value.trim();
|
|
191
|
+
if (/^https?:\/\//i.test(trimmed) || /^file:\/\//i.test(trimmed)) {
|
|
192
|
+
return trimmed.replace(/\/+$/, '');
|
|
193
|
+
}
|
|
194
|
+
if (fs.existsSync(trimmed)) {
|
|
195
|
+
return pathToFileURL(path.resolve(trimmed)).href;
|
|
196
|
+
}
|
|
197
|
+
return trimmed.replace(/\/+$/, '');
|
|
198
|
+
}
|
|
199
|
+
function ensureTrailingSlash(value) {
|
|
200
|
+
return value.endsWith('/') ? value : `${value}/`;
|
|
201
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { doctor, setup } from './setup.js';
|
|
2
|
+
async function main() {
|
|
3
|
+
const args = process.argv.slice(2);
|
|
4
|
+
const command = parseCommand(args);
|
|
5
|
+
const flags = new Set(args.filter((arg) => arg.startsWith('-')));
|
|
6
|
+
switch (command) {
|
|
7
|
+
case 'setup':
|
|
8
|
+
if (flags.has('--help') || flags.has('-h')) {
|
|
9
|
+
printHelp();
|
|
10
|
+
break;
|
|
11
|
+
}
|
|
12
|
+
await setup({
|
|
13
|
+
yes: flags.has('--yes') || flags.has('-y'),
|
|
14
|
+
smoke: !flags.has('--no-smoke'),
|
|
15
|
+
force: flags.has('--force'),
|
|
16
|
+
buildFromSource: flags.has('--build-from-source'),
|
|
17
|
+
releaseBaseUrl: flagValue(args, '--release-base-url'),
|
|
18
|
+
releaseDir: flagValue(args, '--release-dir'),
|
|
19
|
+
});
|
|
20
|
+
break;
|
|
21
|
+
case 'doctor':
|
|
22
|
+
await doctor();
|
|
23
|
+
break;
|
|
24
|
+
case 'version':
|
|
25
|
+
case '--version':
|
|
26
|
+
case '-v':
|
|
27
|
+
console.log('agent_orb bootstrapper 0.1.0');
|
|
28
|
+
break;
|
|
29
|
+
case 'help':
|
|
30
|
+
case '--help':
|
|
31
|
+
case '-h':
|
|
32
|
+
printHelp();
|
|
33
|
+
break;
|
|
34
|
+
default:
|
|
35
|
+
throw new Error(`Unknown command: ${command}`);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
function parseCommand(args) {
|
|
39
|
+
const first = args[0];
|
|
40
|
+
if (!first)
|
|
41
|
+
return 'setup';
|
|
42
|
+
if (first === '--help' || first === '-h')
|
|
43
|
+
return 'help';
|
|
44
|
+
if (first === '--version' || first === '-v')
|
|
45
|
+
return 'version';
|
|
46
|
+
if (first.startsWith('-'))
|
|
47
|
+
return 'setup';
|
|
48
|
+
return first;
|
|
49
|
+
}
|
|
50
|
+
function flagValue(args, name) {
|
|
51
|
+
const equalsPrefix = `${name}=`;
|
|
52
|
+
const inline = args.find((arg) => arg.startsWith(equalsPrefix));
|
|
53
|
+
if (inline)
|
|
54
|
+
return inline.slice(equalsPrefix.length);
|
|
55
|
+
const index = args.indexOf(name);
|
|
56
|
+
if (index >= 0)
|
|
57
|
+
return args[index + 1];
|
|
58
|
+
return undefined;
|
|
59
|
+
}
|
|
60
|
+
function printHelp() {
|
|
61
|
+
console.log(`Agent Orb bootstrapper
|
|
62
|
+
|
|
63
|
+
Usage:
|
|
64
|
+
agent_orb [setup] [--yes] [--no-smoke] [--force]
|
|
65
|
+
[--release-dir <dir> | --release-base-url <url>]
|
|
66
|
+
[--build-from-source]
|
|
67
|
+
agent_orb doctor
|
|
68
|
+
agent_orb version
|
|
69
|
+
|
|
70
|
+
Local development:
|
|
71
|
+
./scripts/release/smoke-npx-local.sh
|
|
72
|
+
npx --yes ./packages/agent_orb setup --yes
|
|
73
|
+
|
|
74
|
+
After npm publish:
|
|
75
|
+
npx @solar_orb/agent_orb
|
|
76
|
+
`);
|
|
77
|
+
}
|
|
78
|
+
main().catch((error) => {
|
|
79
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
80
|
+
console.error(`agent_orb setup failed: ${message}`);
|
|
81
|
+
process.exit(1);
|
|
82
|
+
});
|
package/dist/platform.js
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
export function detectPlatform(env = process.env) {
|
|
3
|
+
const platform = detectPlatformName();
|
|
4
|
+
const arch = detectArchName();
|
|
5
|
+
const exeSuffix = platform === 'windows' ? '.exe' : '';
|
|
6
|
+
const pathDelimiter = platform === 'windows' ? ';' : ':';
|
|
7
|
+
const runtimeDir = defaultRuntimeDir(platform, env);
|
|
8
|
+
const configDir = defaultConfigDir(platform, env);
|
|
9
|
+
const bundleName = `agent-orb-${platform}-${arch}${platform === 'windows' ? '.zip' : '.tar.gz'}`;
|
|
10
|
+
return { platform, arch, exeSuffix, pathDelimiter, runtimeDir, configDir, bundleName };
|
|
11
|
+
}
|
|
12
|
+
function detectPlatformName() {
|
|
13
|
+
switch (process.platform) {
|
|
14
|
+
case 'win32':
|
|
15
|
+
return 'windows';
|
|
16
|
+
case 'darwin':
|
|
17
|
+
return 'macos';
|
|
18
|
+
case 'linux':
|
|
19
|
+
return 'linux';
|
|
20
|
+
default:
|
|
21
|
+
return 'unsupported';
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
function detectArchName() {
|
|
25
|
+
switch (os.arch()) {
|
|
26
|
+
case 'x64':
|
|
27
|
+
return 'x64';
|
|
28
|
+
case 'arm64':
|
|
29
|
+
return 'arm64';
|
|
30
|
+
default:
|
|
31
|
+
return 'unsupported';
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function defaultRuntimeDir(platform, env) {
|
|
35
|
+
if (env.AGENT_ORB_BIN_DIR)
|
|
36
|
+
return env.AGENT_ORB_BIN_DIR;
|
|
37
|
+
if (platform === 'windows') {
|
|
38
|
+
const localAppData = env.LOCALAPPDATA ?? env.USERPROFILE ?? process.cwd();
|
|
39
|
+
return `${localAppData}\\agent-orb\\bin`;
|
|
40
|
+
}
|
|
41
|
+
if (platform === 'macos') {
|
|
42
|
+
const home = env.HOME ?? process.cwd();
|
|
43
|
+
return `${home}/Library/Application Support/agent-orb/bin`;
|
|
44
|
+
}
|
|
45
|
+
const home = env.HOME ?? process.cwd();
|
|
46
|
+
return `${home}/.local/share/agent-orb/bin`;
|
|
47
|
+
}
|
|
48
|
+
function defaultConfigDir(platform, env) {
|
|
49
|
+
if (env.AGENT_ORB_CONFIG_DIR)
|
|
50
|
+
return env.AGENT_ORB_CONFIG_DIR;
|
|
51
|
+
if (platform === 'windows') {
|
|
52
|
+
const appData = env.APPDATA ?? env.USERPROFILE ?? process.cwd();
|
|
53
|
+
return `${appData}\\agent-orb`;
|
|
54
|
+
}
|
|
55
|
+
if (platform === 'macos') {
|
|
56
|
+
const home = env.HOME ?? process.cwd();
|
|
57
|
+
return `${home}/Library/Application Support/agent-orb`;
|
|
58
|
+
}
|
|
59
|
+
const xdg = env.XDG_CONFIG_HOME;
|
|
60
|
+
const home = env.HOME ?? process.cwd();
|
|
61
|
+
return `${xdg ?? `${home}/.config`}/agent-orb`;
|
|
62
|
+
}
|
package/dist/setup.js
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import readline from 'node:readline/promises';
|
|
4
|
+
import { stdin as input, stdout as output } from 'node:process';
|
|
5
|
+
import { fileURLToPath } from 'node:url';
|
|
6
|
+
import { detectAdapters } from './adapter.js';
|
|
7
|
+
import { runtimeConfigFromEnv, writeConfig } from './config.js';
|
|
8
|
+
import { installRuntimeBundle } from './download.js';
|
|
9
|
+
import { detectPlatform } from './platform.js';
|
|
10
|
+
import { commandExists, run, spawnDetached } from './shell.js';
|
|
11
|
+
export async function setup(options = {}) {
|
|
12
|
+
const platform = detectPlatform();
|
|
13
|
+
const runtime = runtimeConfigFromEnv();
|
|
14
|
+
printHeader(platform);
|
|
15
|
+
assertSupported(platform);
|
|
16
|
+
if (options.doctorOnly) {
|
|
17
|
+
await doctor(platform, runtime);
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
console.log(`Runtime dir: ${platform.runtimeDir}`);
|
|
21
|
+
console.log(`Config dir: ${platform.configDir}`);
|
|
22
|
+
const detectedAdapters = detectAdapters();
|
|
23
|
+
printDetectedAdapters(detectedAdapters);
|
|
24
|
+
const selectedAdapters = await selectAdapters(detectedAdapters, options.yes);
|
|
25
|
+
if (options.buildFromSource) {
|
|
26
|
+
installRuntimeFromSource(platform);
|
|
27
|
+
}
|
|
28
|
+
else {
|
|
29
|
+
const installedFromBundle = await installRuntimeBundle(platform, {
|
|
30
|
+
force: options.force,
|
|
31
|
+
releaseBaseUrl: options.releaseBaseUrl,
|
|
32
|
+
releaseDir: options.releaseDir,
|
|
33
|
+
});
|
|
34
|
+
if (!installedFromBundle) {
|
|
35
|
+
console.log('\n· No matching native runtime bundle was found for this platform.');
|
|
36
|
+
console.log('· Falling back to local source build.');
|
|
37
|
+
installRuntimeFromSource(platform);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
const configPath = writeConfig(platform.configDir, selectedAdapters, runtime);
|
|
41
|
+
createAdapterShims(platform, selectedAdapters);
|
|
42
|
+
ensurePathHint(platform);
|
|
43
|
+
await ensureDaemon(platform, runtime);
|
|
44
|
+
if (options.smoke ?? true) {
|
|
45
|
+
smokeTest(platform);
|
|
46
|
+
}
|
|
47
|
+
console.log('\n✓ Agent Orb setup complete');
|
|
48
|
+
console.log(`Config: ${configPath}`);
|
|
49
|
+
console.log(`Try: ${runtimeExe(platform, 'agent_orb')} run -- ${platform.platform === 'windows' ? 'cmd /C echo hello' : 'echo hello'}`);
|
|
50
|
+
const orb = runtimeExe(platform, 'agent-orb-ui');
|
|
51
|
+
if (fs.existsSync(orb)) {
|
|
52
|
+
console.log(`Orb: ${orb}`);
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
function installRuntimeFromSource(platform) {
|
|
56
|
+
console.log('\n==> Building runtime from source');
|
|
57
|
+
ensureBuildTools();
|
|
58
|
+
const repoRoot = findRepoRoot();
|
|
59
|
+
console.log(`Repository: ${repoRoot}`);
|
|
60
|
+
buildRuntime(repoRoot);
|
|
61
|
+
installRuntime(repoRoot, platform);
|
|
62
|
+
}
|
|
63
|
+
export async function doctor(platform = detectPlatform(), runtime = runtimeConfigFromEnv()) {
|
|
64
|
+
printHeader(platform);
|
|
65
|
+
assertSupported(platform);
|
|
66
|
+
const binaries = ['agent_orb', 'agent_orbd'].map((name) => runtimeExe(platform, name));
|
|
67
|
+
for (const binary of binaries) {
|
|
68
|
+
console.log(`${fs.existsSync(binary) ? '✓' : '✗'} ${binary}`);
|
|
69
|
+
}
|
|
70
|
+
console.log(`${fs.existsSync(path.join(platform.configDir, 'token')) ? '✓' : '·'} token: ${path.join(platform.configDir, 'token')}`);
|
|
71
|
+
console.log(`${await health(runtime) ? '✓' : '·'} daemon: http://${runtime.daemonHost}:${runtime.daemonPort}/health`);
|
|
72
|
+
printDetectedAdapters(detectAdapters());
|
|
73
|
+
}
|
|
74
|
+
function printHeader(platform) {
|
|
75
|
+
console.log('Agent Orb Setup');
|
|
76
|
+
console.log(`Platform: ${platform.platform}/${platform.arch}`);
|
|
77
|
+
console.log(`Bundle: ${platform.bundleName}`);
|
|
78
|
+
}
|
|
79
|
+
function assertSupported(platform) {
|
|
80
|
+
if (platform.platform === 'unsupported' || platform.arch === 'unsupported') {
|
|
81
|
+
throw new Error(`Unsupported platform: ${process.platform}/${process.arch}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
function ensureBuildTools() {
|
|
85
|
+
if (!commandExists('cargo') || !commandExists('rustc')) {
|
|
86
|
+
const install = process.platform === 'win32'
|
|
87
|
+
? 'winget install --id Rustlang.Rustup -e'
|
|
88
|
+
: 'curl https://sh.rustup.rs -sSf | sh';
|
|
89
|
+
throw new Error(`Rust toolchain is required for local npx setup. Install it first:\n ${install}`);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
function buildRuntime(repoRoot) {
|
|
93
|
+
console.log('\n==> Building Agent Orb runtime');
|
|
94
|
+
run('cargo', ['build', '--release', '-p', 'agent-orb-cli', '-p', 'agent-orb-daemon'], {
|
|
95
|
+
cwd: repoRoot,
|
|
96
|
+
stdio: 'inherit',
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
function installRuntime(repoRoot, platform) {
|
|
100
|
+
console.log('\n==> Installing runtime');
|
|
101
|
+
fs.mkdirSync(platform.runtimeDir, { recursive: true });
|
|
102
|
+
copyRequired(repoRoot, platform, 'agent_orb');
|
|
103
|
+
copyRequired(repoRoot, platform, 'agent_orbd');
|
|
104
|
+
const uiBinary = path.join(repoRoot, 'apps', 'agent-orb-ui', 'src-tauri', 'target', 'release', `agent-orb-ui${platform.exeSuffix}`);
|
|
105
|
+
if (fs.existsSync(uiBinary)) {
|
|
106
|
+
fs.copyFileSync(uiBinary, path.join(platform.runtimeDir, `agent-orb-ui${platform.exeSuffix}`));
|
|
107
|
+
console.log(`✓ installed agent-orb-ui${platform.exeSuffix}`);
|
|
108
|
+
}
|
|
109
|
+
else {
|
|
110
|
+
console.log('· UI binary not found, skipping UI install for now');
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
function copyRequired(repoRoot, platform, name) {
|
|
114
|
+
const source = path.join(repoRoot, 'target', 'release', `${name}${platform.exeSuffix}`);
|
|
115
|
+
if (!fs.existsSync(source)) {
|
|
116
|
+
throw new Error(`Build output not found: ${source}`);
|
|
117
|
+
}
|
|
118
|
+
const dest = path.join(platform.runtimeDir, `${name}${platform.exeSuffix}`);
|
|
119
|
+
if (sameFileContent(source, dest)) {
|
|
120
|
+
console.log(`✓ ${dest} already up to date`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
copyBinaryReplacingExisting(source, dest, platform);
|
|
124
|
+
console.log(`✓ installed ${dest}`);
|
|
125
|
+
}
|
|
126
|
+
function sameFileContent(left, right) {
|
|
127
|
+
if (!fs.existsSync(left) || !fs.existsSync(right))
|
|
128
|
+
return false;
|
|
129
|
+
const leftStat = fs.statSync(left);
|
|
130
|
+
const rightStat = fs.statSync(right);
|
|
131
|
+
if (leftStat.size !== rightStat.size)
|
|
132
|
+
return false;
|
|
133
|
+
return fs.readFileSync(left).equals(fs.readFileSync(right));
|
|
134
|
+
}
|
|
135
|
+
function copyBinaryReplacingExisting(source, dest, platform) {
|
|
136
|
+
const temp = path.join(path.dirname(dest), `.${path.basename(dest)}.${process.pid}.tmp`);
|
|
137
|
+
fs.copyFileSync(source, temp);
|
|
138
|
+
if (platform.platform !== 'windows')
|
|
139
|
+
fs.chmodSync(temp, 0o755);
|
|
140
|
+
try {
|
|
141
|
+
fs.renameSync(temp, dest);
|
|
142
|
+
}
|
|
143
|
+
catch (error) {
|
|
144
|
+
fs.rmSync(temp, { force: true });
|
|
145
|
+
if (platform.platform === 'windows') {
|
|
146
|
+
throw new Error(`Could not replace ${dest}. If Agent Orb is already running, stop agent_orbd.exe / agent-orb-ui.exe and rerun npx agent_orb. Original error: ${formatError(error)}`);
|
|
147
|
+
}
|
|
148
|
+
throw error;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
function formatError(error) {
|
|
152
|
+
return error instanceof Error ? error.message : String(error);
|
|
153
|
+
}
|
|
154
|
+
async function selectAdapters(adapters, yes = false) {
|
|
155
|
+
if (yes) {
|
|
156
|
+
return adapters.filter((adapter) => adapter.foundBinary);
|
|
157
|
+
}
|
|
158
|
+
const found = adapters.filter((adapter) => adapter.foundBinary);
|
|
159
|
+
if (found.length === 0) {
|
|
160
|
+
console.log('No Codex/Claude CLI detected. Manual agent_orb run remains available.');
|
|
161
|
+
return [];
|
|
162
|
+
}
|
|
163
|
+
if (!process.stdin.isTTY) {
|
|
164
|
+
return found;
|
|
165
|
+
}
|
|
166
|
+
const rl = readline.createInterface({ input, output });
|
|
167
|
+
const choices = found.map((adapter, index) => `${index + 1}) ${adapter.displayName}`).join('\n');
|
|
168
|
+
const answer = await rl.question(`\nChoose adapters to configure (comma-separated, Enter = all):\n${choices}\n> `);
|
|
169
|
+
rl.close();
|
|
170
|
+
if (!answer.trim())
|
|
171
|
+
return found;
|
|
172
|
+
const selectedIndexes = new Set(answer.split(',').map((part) => Number.parseInt(part.trim(), 10) - 1));
|
|
173
|
+
return found.filter((_, index) => selectedIndexes.has(index));
|
|
174
|
+
}
|
|
175
|
+
function printDetectedAdapters(adapters) {
|
|
176
|
+
console.log('\nDetected adapters:');
|
|
177
|
+
for (const adapter of adapters) {
|
|
178
|
+
if (adapter.foundBinary) {
|
|
179
|
+
console.log(` ✓ ${adapter.displayName}: ${adapter.foundBinary}`);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
console.log(` · ${adapter.displayName}: not found`);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
function createAdapterShims(platform, adapters) {
|
|
187
|
+
if (adapters.length === 0)
|
|
188
|
+
return;
|
|
189
|
+
console.log('\n==> Creating adapter shims');
|
|
190
|
+
for (const adapter of adapters) {
|
|
191
|
+
const shimPath = path.join(platform.runtimeDir, adapter.wrapperCommand);
|
|
192
|
+
if (platform.platform === 'windows') {
|
|
193
|
+
const target = adapter.name;
|
|
194
|
+
fs.writeFileSync(shimPath, `@echo off\r\n"%~dp0agent_orb.exe" run -- ${target} %*\r\n`, 'ascii');
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
fs.writeFileSync(shimPath, `#!/usr/bin/env sh\n"$(dirname "$0")/agent_orb" run -- ${adapter.name} "$@"\n`, 'utf8');
|
|
198
|
+
fs.chmodSync(shimPath, 0o755);
|
|
199
|
+
}
|
|
200
|
+
console.log(`✓ ${shimPath}`);
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
function ensurePathHint(platform) {
|
|
204
|
+
const currentPath = process.env.PATH ?? '';
|
|
205
|
+
const parts = currentPath.split(platform.pathDelimiter).filter(Boolean);
|
|
206
|
+
if (parts.includes(platform.runtimeDir))
|
|
207
|
+
return;
|
|
208
|
+
console.log('\nPATH note:');
|
|
209
|
+
if (platform.platform === 'windows') {
|
|
210
|
+
console.log(` Add to user PATH if needed: ${platform.runtimeDir}`);
|
|
211
|
+
console.log(' Or open a new terminal if installer/script already added it.');
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
console.log(` export PATH="${platform.runtimeDir}:$PATH"`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
async function ensureDaemon(platform, runtime) {
|
|
218
|
+
console.log('\n==> Starting daemon');
|
|
219
|
+
const tokenPath = path.join(platform.configDir, 'token');
|
|
220
|
+
if (fs.existsSync(tokenPath) && await authenticatedStatus(tokenPath, runtime)) {
|
|
221
|
+
console.log(`✓ daemon already healthy at http://${runtime.daemonHost}:${runtime.daemonPort}/health`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
const daemon = runtimeExe(platform, 'agent_orbd');
|
|
225
|
+
const daemonAlreadyHealthy = await health(runtime);
|
|
226
|
+
if (daemonAlreadyHealthy) {
|
|
227
|
+
throw new Error(`agent_orbd is already running on ${runtime.daemonHost}:${runtime.daemonPort}, but it does not accept the token at ${tokenPath}. Stop the existing agent_orbd process and rerun setup.`);
|
|
228
|
+
}
|
|
229
|
+
const pid = spawnDetached(daemon, []);
|
|
230
|
+
if (pid) {
|
|
231
|
+
fs.mkdirSync(platform.configDir, { recursive: true });
|
|
232
|
+
fs.writeFileSync(path.join(platform.configDir, 'daemon.pid'), `${pid}\n`, 'utf8');
|
|
233
|
+
}
|
|
234
|
+
for (let i = 0; i < 40; i++) {
|
|
235
|
+
if (fs.existsSync(tokenPath) && await authenticatedStatus(tokenPath, runtime)) {
|
|
236
|
+
console.log(`✓ daemon healthy at http://${runtime.daemonHost}:${runtime.daemonPort}/health`);
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
await new Promise((resolve) => setTimeout(resolve, 250));
|
|
240
|
+
}
|
|
241
|
+
throw new Error(`daemon did not become healthy on ${runtime.daemonHost}:${runtime.daemonPort}`);
|
|
242
|
+
}
|
|
243
|
+
async function health(runtime) {
|
|
244
|
+
try {
|
|
245
|
+
const response = await fetch(`http://${runtime.daemonHost}:${runtime.daemonPort}/health`);
|
|
246
|
+
return response.ok;
|
|
247
|
+
}
|
|
248
|
+
catch {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
async function authenticatedStatus(tokenPath, runtime) {
|
|
253
|
+
try {
|
|
254
|
+
const token = fs.readFileSync(tokenPath, 'utf8').trim();
|
|
255
|
+
if (!token)
|
|
256
|
+
return false;
|
|
257
|
+
const response = await fetch(`http://${runtime.daemonHost}:${runtime.daemonPort}/v1/status`, {
|
|
258
|
+
headers: {
|
|
259
|
+
Authorization: `Bearer ${token}`,
|
|
260
|
+
},
|
|
261
|
+
});
|
|
262
|
+
return response.ok;
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function smokeTest(platform) {
|
|
269
|
+
console.log('\n==> Smoke test');
|
|
270
|
+
const agentOrb = runtimeExe(platform, 'agent_orb');
|
|
271
|
+
if (platform.platform === 'windows') {
|
|
272
|
+
run(agentOrb, ['run', '--', 'cmd', '/C', 'echo', 'hello'], { stdio: 'inherit' });
|
|
273
|
+
}
|
|
274
|
+
else {
|
|
275
|
+
run(agentOrb, ['run', '--', 'echo', 'hello'], { stdio: 'inherit' });
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
function runtimeExe(platform, name) {
|
|
279
|
+
return path.join(platform.runtimeDir, `${name}${platform.exeSuffix}`);
|
|
280
|
+
}
|
|
281
|
+
function findRepoRoot() {
|
|
282
|
+
if (process.env.AGENT_ORB_REPO)
|
|
283
|
+
return process.env.AGENT_ORB_REPO;
|
|
284
|
+
let current = process.cwd();
|
|
285
|
+
for (;;) {
|
|
286
|
+
if (fs.existsSync(path.join(current, 'Cargo.toml')) && fs.existsSync(path.join(current, 'crates'))) {
|
|
287
|
+
return current;
|
|
288
|
+
}
|
|
289
|
+
const parent = path.dirname(current);
|
|
290
|
+
if (parent === current)
|
|
291
|
+
break;
|
|
292
|
+
current = parent;
|
|
293
|
+
}
|
|
294
|
+
const fromPackage = path.resolve(fileURLToPath(new URL('../../..', import.meta.url)));
|
|
295
|
+
if (fs.existsSync(path.join(fromPackage, 'Cargo.toml')))
|
|
296
|
+
return fromPackage;
|
|
297
|
+
throw new Error('Could not find AgentOrb repo root. Set AGENT_ORB_REPO to the repo path.');
|
|
298
|
+
}
|
package/dist/shell.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
export function commandExists(command) {
|
|
5
|
+
return findCommand(command) !== undefined;
|
|
6
|
+
}
|
|
7
|
+
export function findCommand(command) {
|
|
8
|
+
const pathEnv = process.env.PATH ?? '';
|
|
9
|
+
const searchDirs = pathEnv.split(process.platform === 'win32' ? ';' : ':').filter(Boolean);
|
|
10
|
+
const candidates = process.platform === 'win32'
|
|
11
|
+
? commandCandidates(command)
|
|
12
|
+
: [command];
|
|
13
|
+
for (const dir of searchDirs) {
|
|
14
|
+
for (const candidate of candidates) {
|
|
15
|
+
const fullPath = path.join(dir, candidate);
|
|
16
|
+
if (isExecutableFile(fullPath))
|
|
17
|
+
return fullPath;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
return undefined;
|
|
21
|
+
}
|
|
22
|
+
export function run(command, args, options = {}) {
|
|
23
|
+
const result = spawnSync(command, args, {
|
|
24
|
+
cwd: options.cwd,
|
|
25
|
+
env: options.env,
|
|
26
|
+
stdio: options.stdio ?? 'pipe',
|
|
27
|
+
encoding: 'utf8',
|
|
28
|
+
});
|
|
29
|
+
if (result.error)
|
|
30
|
+
throw result.error;
|
|
31
|
+
const status = result.status ?? 1;
|
|
32
|
+
if (status !== 0 && !options.allowFailure) {
|
|
33
|
+
throw new Error(`Command failed (${status}): ${command} ${args.join(' ')}\n${result.stderr ?? ''}`);
|
|
34
|
+
}
|
|
35
|
+
return {
|
|
36
|
+
status,
|
|
37
|
+
stdout: result.stdout ?? '',
|
|
38
|
+
stderr: result.stderr ?? '',
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
export function spawnDetached(command, args, cwd) {
|
|
42
|
+
const child = spawn(command, args, {
|
|
43
|
+
cwd,
|
|
44
|
+
detached: true,
|
|
45
|
+
stdio: 'ignore',
|
|
46
|
+
windowsHide: true,
|
|
47
|
+
});
|
|
48
|
+
child.unref();
|
|
49
|
+
return child.pid;
|
|
50
|
+
}
|
|
51
|
+
function commandCandidates(command) {
|
|
52
|
+
if (path.extname(command))
|
|
53
|
+
return [command];
|
|
54
|
+
const pathExt = process.env.PATHEXT ?? '.COM;.EXE;.BAT;.CMD';
|
|
55
|
+
return [
|
|
56
|
+
command,
|
|
57
|
+
...pathExt
|
|
58
|
+
.split(';')
|
|
59
|
+
.filter(Boolean)
|
|
60
|
+
.map((extension) => `${command}${extension.toLowerCase()}`),
|
|
61
|
+
...pathExt
|
|
62
|
+
.split(';')
|
|
63
|
+
.filter(Boolean)
|
|
64
|
+
.map((extension) => `${command}${extension.toUpperCase()}`),
|
|
65
|
+
];
|
|
66
|
+
}
|
|
67
|
+
function isExecutableFile(filePath) {
|
|
68
|
+
try {
|
|
69
|
+
const stat = fs.statSync(filePath);
|
|
70
|
+
if (!stat.isFile())
|
|
71
|
+
return false;
|
|
72
|
+
if (process.platform === 'win32')
|
|
73
|
+
return true;
|
|
74
|
+
fs.accessSync(filePath, fs.constants.X_OK);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@solar_orb/agent_orb",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Agent Orb local bootstrapper",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"config": {
|
|
7
|
+
"github_repository": "SolarPetal/AgentOrb"
|
|
8
|
+
},
|
|
9
|
+
"engines": {
|
|
10
|
+
"node": ">=18"
|
|
11
|
+
},
|
|
12
|
+
"bin": {
|
|
13
|
+
"agent_orb": "bin/agent_orb.js"
|
|
14
|
+
},
|
|
15
|
+
"scripts": {
|
|
16
|
+
"build": "tsc",
|
|
17
|
+
"check": "tsc --noEmit",
|
|
18
|
+
"package-runtime": "../../scripts/release/package-runtime.sh ../../dist/release-local",
|
|
19
|
+
"prepare": "npm run build",
|
|
20
|
+
"prepack": "npm run build",
|
|
21
|
+
"start": "node bin/agent_orb.js"
|
|
22
|
+
},
|
|
23
|
+
"files": [
|
|
24
|
+
"bin",
|
|
25
|
+
"dist",
|
|
26
|
+
"README.md"
|
|
27
|
+
],
|
|
28
|
+
"devDependencies": {
|
|
29
|
+
"@types/node": "^24.0.0",
|
|
30
|
+
"typescript": "^5.0.0"
|
|
31
|
+
}
|
|
32
|
+
}
|