openkrew 0.0.1 → 0.1.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/LICENSE +75 -0
- package/README.md +148 -0
- package/core-bootstrap.mjs +169 -0
- package/openkrew.mjs +204 -0
- package/package.json +34 -6
- package/scripts/check-global.mjs +7 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
License text copyright (c) 2020 MariaDB Corporation Ab, All Rights Reserved.
|
|
2
|
+
"Business Source License" is a trademark of MariaDB Corporation Ab.
|
|
3
|
+
|
|
4
|
+
Parameters
|
|
5
|
+
|
|
6
|
+
Licensor: OpenKrew (Timothy Carter)
|
|
7
|
+
Licensed Work: OpenKrew. The Licensed Work is (c) 2026 OpenKrew.
|
|
8
|
+
Additional Use Grant: You may make use of the Licensed Work for any
|
|
9
|
+
non-commercial purpose. You may also make production
|
|
10
|
+
use of the Licensed Work for internal purposes within
|
|
11
|
+
your organization. Your organization includes all of
|
|
12
|
+
your affiliates under common control.
|
|
13
|
+
|
|
14
|
+
You may NOT offer the Licensed Work to third parties
|
|
15
|
+
on a hosted or embedded basis as a commercial product
|
|
16
|
+
or service that competes with the Licensed Work.
|
|
17
|
+
|
|
18
|
+
For purposes of this license:
|
|
19
|
+
|
|
20
|
+
"Non-commercial" means personal, educational,
|
|
21
|
+
research, evaluation, or non-profit use.
|
|
22
|
+
|
|
23
|
+
"Internal purposes" means use by your employees and
|
|
24
|
+
contractors for the benefit of your organization,
|
|
25
|
+
and not for the benefit of third parties.
|
|
26
|
+
Change Date: March 27, 2029
|
|
27
|
+
Change License: Apache License, Version 2.0
|
|
28
|
+
|
|
29
|
+
For information about alternative licensing arrangements for the Licensed Work,
|
|
30
|
+
please contact licensing@openkrew.com.
|
|
31
|
+
|
|
32
|
+
Notice
|
|
33
|
+
|
|
34
|
+
Business Source License 1.1
|
|
35
|
+
|
|
36
|
+
Terms
|
|
37
|
+
|
|
38
|
+
The Licensor hereby grants you the right to copy, modify, create derivative
|
|
39
|
+
works, redistribute, and make non-production use of the Licensed Work. The
|
|
40
|
+
Licensor may make an Additional Use Grant, above, permitting limited production use.
|
|
41
|
+
|
|
42
|
+
Effective on the Change Date, or the third anniversary of the first publicly
|
|
43
|
+
available distribution of a specific version of the Licensed Work under this
|
|
44
|
+
License, whichever comes first, the Licensor hereby grants you rights under
|
|
45
|
+
the terms of the Change License, and the rights granted in the paragraph
|
|
46
|
+
above terminate.
|
|
47
|
+
|
|
48
|
+
If your use of the Licensed Work does not comply with the requirements
|
|
49
|
+
currently in effect as described in this License, you must purchase a
|
|
50
|
+
commercial license from the Licensor, its affiliated entities, or authorized
|
|
51
|
+
resellers, or you must refrain from using the Licensed Work.
|
|
52
|
+
|
|
53
|
+
All copies of the original and modified Licensed Work, and derivative works
|
|
54
|
+
of the Licensed Work, are subject to this License. This License applies
|
|
55
|
+
separately for each version of the Licensed Work and the Change Date may vary
|
|
56
|
+
for each version of the Licensed Work released by Licensor.
|
|
57
|
+
|
|
58
|
+
You must conspicuously display this License on each original or modified copy
|
|
59
|
+
of the Licensed Work. If you receive the Licensed Work in original or
|
|
60
|
+
modified form from a third party, the terms and conditions set forth in this
|
|
61
|
+
License apply to your use of that work.
|
|
62
|
+
|
|
63
|
+
Any use of the Licensed Work in violation of this License will automatically
|
|
64
|
+
terminate your rights under this License for the current and all other
|
|
65
|
+
versions of the Licensed Work.
|
|
66
|
+
|
|
67
|
+
This License does not grant you any right in any trademark or logo of
|
|
68
|
+
Licensor or its affiliates (provided that you may use a trademark or logo of
|
|
69
|
+
Licensor as expressly required by this License).
|
|
70
|
+
|
|
71
|
+
TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE LICENSED WORK IS PROVIDED ON
|
|
72
|
+
AN "AS IS" BASIS. LICENSOR HEREBY DISCLAIMS ALL WARRANTIES AND CONDITIONS,
|
|
73
|
+
EXPRESS OR IMPLIED, INCLUDING (WITHOUT LIMITATION) WARRANTIES OF
|
|
74
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT, AND
|
|
75
|
+
TITLE.
|
package/README.md
ADDED
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
# OpenKrew
|
|
2
|
+
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="docs/assets/openkrew-logo.png" alt="OpenKrew" width="500">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<strong>AI agents on separate machines, working as a team.</strong>
|
|
9
|
+
</p>
|
|
10
|
+
|
|
11
|
+
<p align="center">
|
|
12
|
+
<a href="https://github.com/OpenKrew/openkrew/actions"><img src="https://img.shields.io/github/actions/workflow/status/OpenKrew/openkrew/ci.yml?branch=main&style=for-the-badge" alt="CI status"></a>
|
|
13
|
+
<a href="https://github.com/OpenKrew/openkrew/releases"><img src="https://img.shields.io/github/v/release/OpenKrew/openkrew?include_prereleases&style=for-the-badge" alt="GitHub release"></a>
|
|
14
|
+
<a href="LICENSE"><img src="https://img.shields.io/badge/License-BSL_1.1-blue.svg?style=for-the-badge" alt="BSL 1.1 License"></a>
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
OpenKrew runs named AI agents on different physical machines and lets them work together on projects. Each agent has its own identity, role, and permissions. They coordinate through Slack and a central Hub API.
|
|
20
|
+
|
|
21
|
+
Most multi-agent frameworks run everything in one process. OpenKrew doesn't. Your agents are actually distributed, each on its own box, talking to each other over real channels. Think less "threads sharing memory" and more "remote coworkers in a Slack workspace."
|
|
22
|
+
|
|
23
|
+
[Project Vision](PROJECT_VISION.md)
|
|
24
|
+
|
|
25
|
+
## How it works
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
Primary Machine (Hub) -- Always On
|
|
29
|
+
├── Agent Registry API (:4000)
|
|
30
|
+
├── Shared Memory DB (PostgreSQL + pgvector)
|
|
31
|
+
├── Task Queue (Redis / BullMQ)
|
|
32
|
+
├── Provisioning Service (SSH)
|
|
33
|
+
└── Agent: "Quinn" -- Dev Lead
|
|
34
|
+
|
|
35
|
+
Spoke 1 (e.g., MacBook Mini)
|
|
36
|
+
└── Agent: "Morgan" -- Frontend Developer
|
|
37
|
+
|
|
38
|
+
Spoke 2 (e.g., Linux Server)
|
|
39
|
+
└── Agent: "Reese" -- Infrastructure / DevOps
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
The Hub runs the registry, shared memory, and task queue. Spokes are remote machines, each running one agent. Agents talk through Slack for conversation and the Hub API for structured handoffs.
|
|
43
|
+
|
|
44
|
+
## What's in the box
|
|
45
|
+
|
|
46
|
+
Agents run on separate machines, not threads. Each one has a name, role, and personality defined in YAML. They share a three tier memory system: personal (local files), team (PostgreSQL + pgvector), and project scoped. You set per-agent permissions for shell access, filesystem, git, packages, and secrets.
|
|
47
|
+
|
|
48
|
+
Slack is the communication layer. Works with Claude, GPT, DeepSeek, Ollama, LM Studio, and others. SSH provisioning handles remote setup. A privilege broker lets agents request elevated access without holding root credentials directly.
|
|
49
|
+
|
|
50
|
+
## Agent config
|
|
51
|
+
|
|
52
|
+
Each agent gets a YAML file:
|
|
53
|
+
|
|
54
|
+
```yaml
|
|
55
|
+
agent:
|
|
56
|
+
name: Quinn
|
|
57
|
+
role: dev-lead
|
|
58
|
+
machine: mac-mini-lan
|
|
59
|
+
model: claude-opus-4-6
|
|
60
|
+
teammates:
|
|
61
|
+
- name: Morgan
|
|
62
|
+
role: frontend
|
|
63
|
+
channel: "#frontend"
|
|
64
|
+
- name: Reese
|
|
65
|
+
role: infra
|
|
66
|
+
channel: "#infrastructure"
|
|
67
|
+
tools: [git, gh, dotnet-cli, docker]
|
|
68
|
+
mcps: [context7, sequential-thinking]
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Permissions
|
|
72
|
+
|
|
73
|
+
You control what each agent can do. Morgan here can push code and open PRs but can't merge, can't sudo, and can't touch anything outside the UI and docs folders:
|
|
74
|
+
|
|
75
|
+
```yaml
|
|
76
|
+
agent: Morgan
|
|
77
|
+
role: frontend
|
|
78
|
+
|
|
79
|
+
shell:
|
|
80
|
+
enabled: true
|
|
81
|
+
sudo: false
|
|
82
|
+
blocked_commands: [rm -rf /, shutdown, reboot]
|
|
83
|
+
|
|
84
|
+
filesystem:
|
|
85
|
+
mode: scoped
|
|
86
|
+
allowed_paths:
|
|
87
|
+
- ~/projects/my-app/UI/**
|
|
88
|
+
- ~/projects/my-app/docs/**
|
|
89
|
+
|
|
90
|
+
git:
|
|
91
|
+
push: true
|
|
92
|
+
create_pr: true
|
|
93
|
+
merge_pr: false # Only lead can merge
|
|
94
|
+
force_push: false
|
|
95
|
+
|
|
96
|
+
packages:
|
|
97
|
+
install: true
|
|
98
|
+
managers: [npm, npx]
|
|
99
|
+
|
|
100
|
+
secrets:
|
|
101
|
+
access: false
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Memory
|
|
105
|
+
|
|
106
|
+
| Tier | Scope | Storage | Example |
|
|
107
|
+
| -------- | --------------- | --------------------- | --------------------------------------------- |
|
|
108
|
+
| Personal | One agent | Local filesystem | "I prefer TDD" |
|
|
109
|
+
| Team | All agents | PostgreSQL + pgvector | "API uses .NET 10, frontend is Next.js 14" |
|
|
110
|
+
| Project | Current project | Shared DB, scoped | "Stage 2 of auth migration, blocked on OAuth" |
|
|
111
|
+
|
|
112
|
+
## Tech stack
|
|
113
|
+
|
|
114
|
+
| Component | Technology |
|
|
115
|
+
| ---------------- | ------------------------------------------- |
|
|
116
|
+
| Hub API | Node.js (Fastify) or .NET, REST + WebSocket |
|
|
117
|
+
| Shared memory | PostgreSQL + pgvector |
|
|
118
|
+
| Task queue | Redis / BullMQ |
|
|
119
|
+
| Agent runtime | Node.js (Pi agent framework) |
|
|
120
|
+
| Messaging | Slack (via Slack Bolt) |
|
|
121
|
+
| SSH provisioning | node-ssh |
|
|
122
|
+
| LLM support | Model agnostic, 12+ providers |
|
|
123
|
+
|
|
124
|
+
## Building from source
|
|
125
|
+
|
|
126
|
+
Needs Node 22.16+ (24 recommended).
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
git clone https://github.com/OpenKrew/openkrew.git
|
|
130
|
+
cd openkrew
|
|
131
|
+
|
|
132
|
+
pnpm install
|
|
133
|
+
pnpm build
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
## Roadmap
|
|
137
|
+
|
|
138
|
+
We're starting with the basics: get two agents talking on one machine, nail the communication and memory protocols. After that, SSH provisioning for actual multi machine setups, then the interactive setup wizard, then the permission system. Agent to agent coordination (handoffs, conflict resolution) comes last. Then we open source it properly.
|
|
139
|
+
|
|
140
|
+
## Origin
|
|
141
|
+
|
|
142
|
+
OpenKrew started as a fork of [OpenClaw](https://github.com/openclaw/openclaw) (MIT). We kept the Gateway, Pi agent framework, LLM adapters, and Slack integration. Everything else -- multi machine distribution, shared memory, role based permissions, team coordination -- is new.
|
|
143
|
+
|
|
144
|
+
See [THIRD_PARTY_LICENSES.md](THIRD_PARTY_LICENSES.md) for attribution.
|
|
145
|
+
|
|
146
|
+
## License
|
|
147
|
+
|
|
148
|
+
[Business Source License 1.1](LICENSE) -- free for non-commercial and internal use. Converts to Apache 2.0 after 3 years per version. Commercial licensing available at licensing@openkrew.com.
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* OpenKrew .NET Core Bootstrapper
|
|
5
|
+
*
|
|
6
|
+
* Downloads the platform-specific self-contained binary from S3.
|
|
7
|
+
* Subsequent runs use the cached binary directly.
|
|
8
|
+
*
|
|
9
|
+
* This file is invoked by openkrew.mjs when the user runs `openkrew`.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { spawn } from "node:child_process";
|
|
13
|
+
import {
|
|
14
|
+
createWriteStream,
|
|
15
|
+
existsSync,
|
|
16
|
+
mkdirSync,
|
|
17
|
+
chmodSync,
|
|
18
|
+
readFileSync,
|
|
19
|
+
writeFileSync,
|
|
20
|
+
rmSync,
|
|
21
|
+
} from "node:fs";
|
|
22
|
+
import { homedir, platform, arch } from "node:os";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
import { pipeline } from "node:stream/promises";
|
|
25
|
+
|
|
26
|
+
const OPENKREW_DIR = join(homedir(), ".openkrew");
|
|
27
|
+
const BIN_DIR = join(OPENKREW_DIR, "bin");
|
|
28
|
+
const VERSION_FILE = join(BIN_DIR, ".version");
|
|
29
|
+
const S3_BASE = "https://openkrew-releases.s3.amazonaws.com";
|
|
30
|
+
const PACKAGE_VERSION = (() => {
|
|
31
|
+
try {
|
|
32
|
+
const pkg = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8"));
|
|
33
|
+
return pkg.version;
|
|
34
|
+
} catch {
|
|
35
|
+
return "0.1.0";
|
|
36
|
+
}
|
|
37
|
+
})();
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Resolves the platform-specific runtime identifier.
|
|
41
|
+
*/
|
|
42
|
+
function getRuntimeId() {
|
|
43
|
+
const p = platform();
|
|
44
|
+
const a = arch();
|
|
45
|
+
|
|
46
|
+
const map = {
|
|
47
|
+
"darwin-arm64": "osx-arm64",
|
|
48
|
+
"darwin-x64": "osx-x64",
|
|
49
|
+
"linux-x64": "linux-x64",
|
|
50
|
+
"linux-arm64": "linux-arm64",
|
|
51
|
+
"win32-x64": "win-x64",
|
|
52
|
+
"win32-arm64": "win-arm64",
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
const rid = map[`${p}-${a}`];
|
|
56
|
+
if (!rid) {
|
|
57
|
+
throw new Error(
|
|
58
|
+
`Unsupported platform: ${p}-${a}. OpenKrew supports: macOS (arm64/x64), Linux (x64/arm64), Windows (x64/arm64).`,
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
return rid;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Returns the expected binary name for the current platform.
|
|
66
|
+
*/
|
|
67
|
+
function getBinaryName() {
|
|
68
|
+
return platform() === "win32" ? "OpenKrew.Hub.exe" : "OpenKrew.Hub";
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Checks if the binary is already downloaded and matches the expected version.
|
|
73
|
+
*/
|
|
74
|
+
function isInstalled() {
|
|
75
|
+
const binaryPath = join(BIN_DIR, getBinaryName());
|
|
76
|
+
if (!existsSync(binaryPath)) return false;
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const installedVersion = readFileSync(VERSION_FILE, "utf8").trim();
|
|
80
|
+
return installedVersion === PACKAGE_VERSION;
|
|
81
|
+
} catch {
|
|
82
|
+
return false;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Downloads the self-contained binary from S3.
|
|
88
|
+
*/
|
|
89
|
+
async function downloadBinary() {
|
|
90
|
+
const rid = getRuntimeId();
|
|
91
|
+
const url = `${S3_BASE}/stable/${rid}/openkrew.tar.gz`;
|
|
92
|
+
|
|
93
|
+
process.stderr.write(`\x1b[36mOpenKrew\x1b[0m: downloading ${rid} binary (v${PACKAGE_VERSION})...\n`);
|
|
94
|
+
|
|
95
|
+
mkdirSync(BIN_DIR, { recursive: true });
|
|
96
|
+
|
|
97
|
+
const response = await fetch(url);
|
|
98
|
+
|
|
99
|
+
if (!response.ok) {
|
|
100
|
+
process.stderr.write(`\x1b[31mError: Failed to download binary (HTTP ${response.status})\x1b[0m\n`);
|
|
101
|
+
process.stderr.write(`URL: ${url}\n`);
|
|
102
|
+
process.stderr.write("Try again later or check https://github.com/OpenKrew/openkrew/releases\n");
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const tmpFile = join(OPENKREW_DIR, `download-${Date.now()}.tar.gz`);
|
|
107
|
+
|
|
108
|
+
try {
|
|
109
|
+
const fileStream = createWriteStream(tmpFile);
|
|
110
|
+
await pipeline(response.body, fileStream);
|
|
111
|
+
|
|
112
|
+
// Extract
|
|
113
|
+
const tarProcess = spawn("tar", ["xzf", tmpFile, "-C", BIN_DIR], { stdio: "inherit" });
|
|
114
|
+
await new Promise((resolve, reject) => {
|
|
115
|
+
tarProcess.on("close", (code) =>
|
|
116
|
+
code === 0 ? resolve() : reject(new Error(`tar exited with code ${code}`)),
|
|
117
|
+
);
|
|
118
|
+
tarProcess.on("error", reject);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
// Make binary executable on Unix
|
|
122
|
+
if (platform() !== "win32") {
|
|
123
|
+
const binaryPath = join(BIN_DIR, getBinaryName());
|
|
124
|
+
if (existsSync(binaryPath)) {
|
|
125
|
+
chmodSync(binaryPath, 0o755);
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
writeFileSync(VERSION_FILE, PACKAGE_VERSION);
|
|
130
|
+
process.stderr.write(`\x1b[32mInstalled OpenKrew v${PACKAGE_VERSION}\x1b[0m\n`);
|
|
131
|
+
} finally {
|
|
132
|
+
try { rmSync(tmpFile); } catch {}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Runs the .NET Hub binary, passing through all CLI arguments.
|
|
138
|
+
*/
|
|
139
|
+
function runBinary() {
|
|
140
|
+
const binaryPath = join(BIN_DIR, getBinaryName());
|
|
141
|
+
|
|
142
|
+
if (!existsSync(binaryPath)) {
|
|
143
|
+
process.stderr.write(`\x1b[31mError: OpenKrew binary not found at ${binaryPath}\x1b[0m\n`);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
148
|
+
stdio: "inherit",
|
|
149
|
+
env: {
|
|
150
|
+
...process.env,
|
|
151
|
+
OPENKREW_BOOTSTRAPPED: "1",
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
child.on("close", (code) => process.exit(code ?? 0));
|
|
156
|
+
child.on("error", (err) => {
|
|
157
|
+
process.stderr.write(`\x1b[31mFailed to start OpenKrew: ${err.message}\x1b[0m\n`);
|
|
158
|
+
process.exit(1);
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ── Main ──────────────────────────────────────────────────────────────
|
|
163
|
+
|
|
164
|
+
export async function bootstrap() {
|
|
165
|
+
if (!isInstalled()) {
|
|
166
|
+
await downloadBinary();
|
|
167
|
+
}
|
|
168
|
+
runBinary();
|
|
169
|
+
}
|
package/openkrew.mjs
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { access } from "node:fs/promises";
|
|
5
|
+
import module from "node:module";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
|
|
8
|
+
const MIN_NODE_MAJOR = 22;
|
|
9
|
+
const MIN_NODE_MINOR = 12;
|
|
10
|
+
const MIN_NODE_VERSION = `${MIN_NODE_MAJOR}.${MIN_NODE_MINOR}`;
|
|
11
|
+
|
|
12
|
+
const parseNodeVersion = (rawVersion) => {
|
|
13
|
+
const [majorRaw = "0", minorRaw = "0"] = rawVersion.split(".");
|
|
14
|
+
return {
|
|
15
|
+
major: Number(majorRaw),
|
|
16
|
+
minor: Number(minorRaw),
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const isSupportedNodeVersion = (version) =>
|
|
21
|
+
version.major > MIN_NODE_MAJOR ||
|
|
22
|
+
(version.major === MIN_NODE_MAJOR && version.minor >= MIN_NODE_MINOR);
|
|
23
|
+
|
|
24
|
+
const ensureSupportedNodeVersion = () => {
|
|
25
|
+
if (isSupportedNodeVersion(parseNodeVersion(process.versions.node))) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
process.stderr.write(
|
|
30
|
+
`openkrew: Node.js v${MIN_NODE_VERSION}+ is required (current: v${process.versions.node}).\n` +
|
|
31
|
+
"If you use nvm, run:\n" +
|
|
32
|
+
` nvm install ${MIN_NODE_MAJOR}\n` +
|
|
33
|
+
` nvm use ${MIN_NODE_MAJOR}\n` +
|
|
34
|
+
` nvm alias default ${MIN_NODE_MAJOR}\n`,
|
|
35
|
+
);
|
|
36
|
+
process.exit(1);
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
ensureSupportedNodeVersion();
|
|
40
|
+
|
|
41
|
+
// https://nodejs.org/api/module.html#module-compile-cache
|
|
42
|
+
if (module.enableCompileCache && !process.env.NODE_DISABLE_COMPILE_CACHE) {
|
|
43
|
+
try {
|
|
44
|
+
module.enableCompileCache();
|
|
45
|
+
} catch {
|
|
46
|
+
// Ignore errors
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// ── OpenKrew .NET Core Bootstrap ──────────────────────────────────────
|
|
51
|
+
// The primary runtime is the .NET core platform. The bootstrapper handles
|
|
52
|
+
// downloading, caching, and running the self-contained binary.
|
|
53
|
+
// Falls back to the legacy TypeScript gateway if the bootstrap fails.
|
|
54
|
+
|
|
55
|
+
const isLegacyMode = process.argv.includes("--legacy") || process.env.OPENKREW_LEGACY === "1";
|
|
56
|
+
|
|
57
|
+
if (!isLegacyMode) {
|
|
58
|
+
try {
|
|
59
|
+
const { bootstrap } = await import("./core-bootstrap.mjs");
|
|
60
|
+
await bootstrap();
|
|
61
|
+
// bootstrap() calls process.exit() via the spawned child, so we
|
|
62
|
+
// only reach here if something went wrong.
|
|
63
|
+
} catch (err) {
|
|
64
|
+
// If the .NET bootstrapper fails (no release, no SDK, etc.),
|
|
65
|
+
// fall through to the legacy TypeScript gateway with a warning.
|
|
66
|
+
process.stderr.write(
|
|
67
|
+
`\x1b[33mOpenKrew .NET core unavailable: ${err.message}\x1b[0m\n` +
|
|
68
|
+
"Falling back to legacy TypeScript gateway.\n" +
|
|
69
|
+
"Run with OPENKREW_LEGACY=1 to skip this check.\n\n",
|
|
70
|
+
);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// ── Legacy TypeScript Gateway (OpenClaw fork) ─────────────────────────
|
|
75
|
+
|
|
76
|
+
const isModuleNotFoundError = (err) =>
|
|
77
|
+
err && typeof err === "object" && "code" in err && err.code === "ERR_MODULE_NOT_FOUND";
|
|
78
|
+
|
|
79
|
+
const isDirectModuleNotFoundError = (err, specifier) => {
|
|
80
|
+
if (!isModuleNotFoundError(err)) {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const expectedUrl = new URL(specifier, import.meta.url);
|
|
85
|
+
if ("url" in err && err.url === expectedUrl.href) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const message = "message" in err && typeof err.message === "string" ? err.message : "";
|
|
90
|
+
const expectedPath = fileURLToPath(expectedUrl);
|
|
91
|
+
return (
|
|
92
|
+
message.includes(`Cannot find module '${expectedPath}'`) ||
|
|
93
|
+
message.includes(`Cannot find module "${expectedPath}"`)
|
|
94
|
+
);
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
const installProcessWarningFilter = async () => {
|
|
98
|
+
for (const specifier of ["./dist/warning-filter.js", "./dist/warning-filter.mjs"]) {
|
|
99
|
+
try {
|
|
100
|
+
const mod = await import(specifier);
|
|
101
|
+
if (typeof mod.installProcessWarningFilter === "function") {
|
|
102
|
+
mod.installProcessWarningFilter();
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
} catch (err) {
|
|
106
|
+
if (isDirectModuleNotFoundError(err, specifier)) {
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
throw err;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
const tryImport = async (specifier) => {
|
|
115
|
+
try {
|
|
116
|
+
await import(specifier);
|
|
117
|
+
return true;
|
|
118
|
+
} catch (err) {
|
|
119
|
+
if (isDirectModuleNotFoundError(err, specifier)) {
|
|
120
|
+
return false;
|
|
121
|
+
}
|
|
122
|
+
throw err;
|
|
123
|
+
}
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
const exists = async (specifier) => {
|
|
127
|
+
try {
|
|
128
|
+
await access(new URL(specifier, import.meta.url));
|
|
129
|
+
return true;
|
|
130
|
+
} catch {
|
|
131
|
+
return false;
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
|
|
135
|
+
const buildMissingEntryErrorMessage = async () => {
|
|
136
|
+
const lines = ["openkrew: missing dist/entry.(m)js (build output)."];
|
|
137
|
+
if (!(await exists("./src/entry.ts"))) {
|
|
138
|
+
return lines.join("\n");
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
lines.push("This install looks like an unbuilt source tree or GitHub source archive.");
|
|
142
|
+
lines.push(
|
|
143
|
+
"Build locally with `pnpm install && pnpm build`, or install a built package instead.",
|
|
144
|
+
);
|
|
145
|
+
lines.push(
|
|
146
|
+
"For pinned GitHub installs, use `npm install -g github:openkrew/openkrew#<ref>` instead of a raw `/archive/<ref>.tar.gz` URL.",
|
|
147
|
+
);
|
|
148
|
+
lines.push("For releases, use `npm install -g openkrew@latest`.");
|
|
149
|
+
return lines.join("\n");
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const isBareRootHelpInvocation = (argv) =>
|
|
153
|
+
argv.length === 3 && (argv[2] === "--help" || argv[2] === "-h");
|
|
154
|
+
|
|
155
|
+
const loadPrecomputedRootHelpText = () => {
|
|
156
|
+
try {
|
|
157
|
+
const raw = readFileSync(new URL("./dist/cli-startup-metadata.json", import.meta.url), "utf8");
|
|
158
|
+
const parsed = JSON.parse(raw);
|
|
159
|
+
return typeof parsed?.rootHelpText === "string" && parsed.rootHelpText.length > 0
|
|
160
|
+
? parsed.rootHelpText
|
|
161
|
+
: null;
|
|
162
|
+
} catch {
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
const tryOutputBareRootHelp = async () => {
|
|
168
|
+
if (!isBareRootHelpInvocation(process.argv)) {
|
|
169
|
+
return false;
|
|
170
|
+
}
|
|
171
|
+
const precomputed = loadPrecomputedRootHelpText();
|
|
172
|
+
if (precomputed) {
|
|
173
|
+
process.stdout.write(precomputed);
|
|
174
|
+
return true;
|
|
175
|
+
}
|
|
176
|
+
for (const specifier of ["./dist/cli/program/root-help.js", "./dist/cli/program/root-help.mjs"]) {
|
|
177
|
+
try {
|
|
178
|
+
const mod = await import(specifier);
|
|
179
|
+
if (typeof mod.outputRootHelp === "function") {
|
|
180
|
+
mod.outputRootHelp();
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
} catch (err) {
|
|
184
|
+
if (isDirectModuleNotFoundError(err, specifier)) {
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
throw err;
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
return false;
|
|
191
|
+
};
|
|
192
|
+
|
|
193
|
+
if (await tryOutputBareRootHelp()) {
|
|
194
|
+
// OK
|
|
195
|
+
} else {
|
|
196
|
+
await installProcessWarningFilter();
|
|
197
|
+
if (await tryImport("./dist/entry.js")) {
|
|
198
|
+
// OK
|
|
199
|
+
} else if (await tryImport("./dist/entry.mjs")) {
|
|
200
|
+
// OK
|
|
201
|
+
} else {
|
|
202
|
+
throw new Error(await buildMissingEntryErrorMessage());
|
|
203
|
+
}
|
|
204
|
+
}
|
package/package.json
CHANGED
|
@@ -1,12 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "openkrew",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "
|
|
5
|
-
"
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Distributed multi-machine AI agent team platform",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"license": "BUSL-1.1",
|
|
6
7
|
"repository": {
|
|
7
8
|
"type": "git",
|
|
8
|
-
"url": "
|
|
9
|
+
"url": "https://github.com/OpenKrew/openkrew"
|
|
9
10
|
},
|
|
10
|
-
"homepage": "https://
|
|
11
|
-
"
|
|
11
|
+
"homepage": "https://openkrew.com",
|
|
12
|
+
"bin": {
|
|
13
|
+
"openkrew": "./openkrew.mjs"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"openkrew.mjs",
|
|
17
|
+
"core-bootstrap.mjs",
|
|
18
|
+
"scripts/check-global.mjs",
|
|
19
|
+
"LICENSE"
|
|
20
|
+
],
|
|
21
|
+
"scripts": {
|
|
22
|
+
"preinstall": "node scripts/check-global.mjs"
|
|
23
|
+
},
|
|
24
|
+
"engines": {
|
|
25
|
+
"node": ">=18"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"ai",
|
|
29
|
+
"agents",
|
|
30
|
+
"multi-agent",
|
|
31
|
+
"distributed",
|
|
32
|
+
"slack",
|
|
33
|
+
"discord",
|
|
34
|
+
"teams",
|
|
35
|
+
"llm",
|
|
36
|
+
"claude",
|
|
37
|
+
"openai",
|
|
38
|
+
"developer-tools"
|
|
39
|
+
]
|
|
12
40
|
}
|