@lijuneleven/iris 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 +24 -0
- package/bin/iris.js +34 -0
- package/package.json +29 -0
- package/scripts/install.js +187 -0
package/README.md
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Iris
|
|
2
|
+
|
|
3
|
+
Install:
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm install -g @lijuneleven/iris
|
|
7
|
+
iris
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
The CLI starts the local Iris service. Pass server flags directly:
|
|
11
|
+
|
|
12
|
+
```sh
|
|
13
|
+
iris --port 9090
|
|
14
|
+
iris --config-dir /data/iris
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`--config-dir` controls the local config and runtime data directory.
|
|
18
|
+
|
|
19
|
+
The installer downloads the platform binary from GitHub Release first, then falls back to Gitee Release.
|
|
20
|
+
It also installs the Codex `notify` and Claude Code `Stop` completion hooks together with the Iris Feishu-context Skill for both Agents. The CLI retries Agent integration setup on every service start, and `iris --install-agent-hooks` can run it manually.
|
|
21
|
+
|
|
22
|
+
The settings page includes an optional one-time environment check for Node.js, the headless browser, writable data storage, Feishu connectivity, and the configured Agent. The result is not saved and does not block setup.
|
|
23
|
+
|
|
24
|
+
On first launch Iris automatically chooses an installed Agent in this order: Codex, then Claude Code. Developer-mode Feishu cards can switch between installed built-in Agents and the configured custom Agent; built-in Agents always use unattended permission mode.
|
package/bin/iris.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { spawn } = require("child_process");
|
|
6
|
+
|
|
7
|
+
const exeName = process.platform === "win32" ? "iris.exe" : "iris";
|
|
8
|
+
const configuredBinary = process.env.IRIS_BINARY || process.env.EASY_TERMINAL_BINARY;
|
|
9
|
+
const binaryPath = configuredBinary
|
|
10
|
+
? path.resolve(configuredBinary)
|
|
11
|
+
: path.resolve(__dirname, "..", "vendor", exeName);
|
|
12
|
+
|
|
13
|
+
if (!fs.existsSync(binaryPath)) {
|
|
14
|
+
console.error("Iris binary is missing. Reinstall the package or set IRIS_BINARY to a local binary.");
|
|
15
|
+
process.exit(1);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const child = spawn(binaryPath, process.argv.slice(2), {
|
|
19
|
+
stdio: "inherit",
|
|
20
|
+
env: process.env
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
child.on("exit", (code, signal) => {
|
|
24
|
+
if (signal) {
|
|
25
|
+
process.kill(process.pid, signal);
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
process.exit(code ?? 0);
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
child.on("error", (err) => {
|
|
32
|
+
console.error(err.message);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@lijuneleven/iris",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Feishu-first personal AI assistant powered by local Agents.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"bin": {
|
|
7
|
+
"iris": "bin/iris.js"
|
|
8
|
+
},
|
|
9
|
+
"files": [
|
|
10
|
+
"bin/",
|
|
11
|
+
"scripts/",
|
|
12
|
+
"vendor/",
|
|
13
|
+
"README.md"
|
|
14
|
+
],
|
|
15
|
+
"scripts": {
|
|
16
|
+
"postinstall": "node scripts/install.js"
|
|
17
|
+
},
|
|
18
|
+
"engines": {
|
|
19
|
+
"node": ">=18"
|
|
20
|
+
},
|
|
21
|
+
"repository": {
|
|
22
|
+
"type": "git",
|
|
23
|
+
"url": "git+https://github.com/elevenlj/iris.git"
|
|
24
|
+
},
|
|
25
|
+
"bugs": {
|
|
26
|
+
"url": "https://github.com/elevenlj/iris/issues"
|
|
27
|
+
},
|
|
28
|
+
"homepage": "https://github.com/elevenlj/iris#readme"
|
|
29
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const http = require("http");
|
|
6
|
+
const https = require("https");
|
|
7
|
+
const { spawn } = require("child_process");
|
|
8
|
+
const { pipeline } = require("stream/promises");
|
|
9
|
+
const { createWriteStream } = require("fs");
|
|
10
|
+
|
|
11
|
+
const packageJson = require("../package.json");
|
|
12
|
+
|
|
13
|
+
const owner = process.env.IRIS_GITHUB_OWNER || process.env.EASY_TERMINAL_GITHUB_OWNER || "elevenlj";
|
|
14
|
+
const repo = process.env.IRIS_GITHUB_REPO || process.env.EASY_TERMINAL_GITHUB_REPO || "iris";
|
|
15
|
+
const giteeRepo = process.env.IRIS_GITEE_REPO || process.env.EASY_TERMINAL_GITEE_REPO || "eleven_lj/iris";
|
|
16
|
+
const version = packageJson.version;
|
|
17
|
+
const platform = process.platform;
|
|
18
|
+
const arch = process.arch;
|
|
19
|
+
const requestTimeoutMs = Number(process.env.IRIS_DOWNLOAD_TIMEOUT_MS || process.env.EASY_TERMINAL_DOWNLOAD_TIMEOUT_MS || 120000);
|
|
20
|
+
|
|
21
|
+
const platformMap = {
|
|
22
|
+
darwin: "darwin",
|
|
23
|
+
linux: "linux",
|
|
24
|
+
win32: "windows"
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const archMap = {
|
|
28
|
+
x64: "amd64",
|
|
29
|
+
arm64: "arm64"
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
function fail(message) {
|
|
33
|
+
console.error(`Iris install failed: ${message}`);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
if (process.env.IRIS_SKIP_DOWNLOAD === "1" || process.env.EASY_TERMINAL_SKIP_DOWNLOAD === "1") {
|
|
38
|
+
process.exit(0);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const targetPlatform = platformMap[platform];
|
|
42
|
+
const targetArch = archMap[arch];
|
|
43
|
+
|
|
44
|
+
if (!targetPlatform || !targetArch) {
|
|
45
|
+
fail(`unsupported platform: ${platform}/${arch}`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const ext = targetPlatform === "windows" ? ".exe" : "";
|
|
49
|
+
const assetName = `iris-${targetPlatform}-${targetArch}${ext}`;
|
|
50
|
+
const urls = [
|
|
51
|
+
`https://github.com/${owner}/${repo}/releases/download/v${version}/${assetName}`,
|
|
52
|
+
`https://gitee.com/${giteeRepo}/releases/download/v${version}/${assetName}`
|
|
53
|
+
];
|
|
54
|
+
const vendorDir = path.resolve(__dirname, "..", "vendor");
|
|
55
|
+
const outPath = path.join(vendorDir, targetPlatform === "windows" ? "iris.exe" : "iris");
|
|
56
|
+
|
|
57
|
+
async function download(downloadUrl, redirects = 0) {
|
|
58
|
+
if (redirects > 5) {
|
|
59
|
+
throw new Error("too many redirects while downloading binary");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
await fs.promises.mkdir(vendorDir, { recursive: true });
|
|
63
|
+
|
|
64
|
+
await new Promise((resolve, reject) => {
|
|
65
|
+
const mod = downloadUrl.startsWith("https:") ? https : http;
|
|
66
|
+
const request = mod
|
|
67
|
+
.get(downloadUrl, { headers: { "User-Agent": "iris-npm" } }, (res) => {
|
|
68
|
+
if ([301, 302, 303, 307, 308].includes(res.statusCode || 0)) {
|
|
69
|
+
res.resume();
|
|
70
|
+
download(res.headers.location, redirects + 1).then(resolve, reject);
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (res.statusCode !== 200) {
|
|
75
|
+
res.resume();
|
|
76
|
+
reject(new Error(`download returned HTTP ${res.statusCode}`));
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
pipeline(res, createWriteStream(outPath)).then(resolve, reject);
|
|
81
|
+
})
|
|
82
|
+
.on("error", reject);
|
|
83
|
+
request.setTimeout(requestTimeoutMs, () => {
|
|
84
|
+
request.destroy(new Error(`download timed out after ${requestTimeoutMs}ms`));
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
if (targetPlatform !== "windows") {
|
|
89
|
+
await fs.promises.chmod(outPath, 0o755);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
async function downloadWithCurl(downloadUrl) {
|
|
94
|
+
await fs.promises.mkdir(vendorDir, { recursive: true });
|
|
95
|
+
|
|
96
|
+
await new Promise((resolve, reject) => {
|
|
97
|
+
const timeoutSeconds = Math.max(1, Math.ceil(requestTimeoutMs / 1000));
|
|
98
|
+
const child = spawn("curl", [
|
|
99
|
+
"-fL",
|
|
100
|
+
"--connect-timeout",
|
|
101
|
+
"20",
|
|
102
|
+
"--max-time",
|
|
103
|
+
String(timeoutSeconds),
|
|
104
|
+
"-H",
|
|
105
|
+
"User-Agent: iris-npm",
|
|
106
|
+
"-o",
|
|
107
|
+
outPath,
|
|
108
|
+
downloadUrl
|
|
109
|
+
], {
|
|
110
|
+
stdio: ["ignore", "ignore", "pipe"]
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
let stderr = "";
|
|
114
|
+
child.stderr.on("data", (chunk) => {
|
|
115
|
+
stderr += chunk.toString();
|
|
116
|
+
});
|
|
117
|
+
child.on("error", reject);
|
|
118
|
+
child.on("exit", (code) => {
|
|
119
|
+
if (code === 0) {
|
|
120
|
+
resolve();
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
reject(new Error((stderr.trim() || `curl exited with code ${code}`).split("\n").slice(-1)[0]));
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
if (targetPlatform !== "windows") {
|
|
128
|
+
await fs.promises.chmod(outPath, 0o755);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function installAgentHooks() {
|
|
133
|
+
await new Promise((resolve, reject) => {
|
|
134
|
+
const child = spawn(outPath, ["--install-agent-hooks"], {
|
|
135
|
+
stdio: ["ignore", "ignore", "pipe"],
|
|
136
|
+
env: process.env
|
|
137
|
+
});
|
|
138
|
+
let stderr = "";
|
|
139
|
+
child.stderr.on("data", (chunk) => {
|
|
140
|
+
stderr += chunk.toString();
|
|
141
|
+
});
|
|
142
|
+
child.on("error", reject);
|
|
143
|
+
child.on("exit", (code) => {
|
|
144
|
+
if (code === 0) {
|
|
145
|
+
resolve();
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
reject(new Error((stderr.trim() || `hook installer exited with code ${code}`).split("\n").slice(-1)[0]));
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
async function main() {
|
|
154
|
+
const failures = [];
|
|
155
|
+
|
|
156
|
+
for (const url of urls) {
|
|
157
|
+
try {
|
|
158
|
+
console.log(`[iris] downloading ${assetName}`);
|
|
159
|
+
console.log(`[iris] source: ${url}`);
|
|
160
|
+
try {
|
|
161
|
+
await downloadWithCurl(url);
|
|
162
|
+
} catch (curlErr) {
|
|
163
|
+
console.warn(`[iris] curl download failed, trying node downloader`);
|
|
164
|
+
await download(url);
|
|
165
|
+
}
|
|
166
|
+
console.log(`[iris] installed binary to ${outPath}`);
|
|
167
|
+
try {
|
|
168
|
+
await installAgentHooks();
|
|
169
|
+
console.log("[iris] installed Agent hooks and Feishu context skills");
|
|
170
|
+
} catch (hookErr) {
|
|
171
|
+
console.warn(`[iris] Agent integration setup deferred until first launch: ${hookErr.message}`);
|
|
172
|
+
}
|
|
173
|
+
return;
|
|
174
|
+
} catch (err) {
|
|
175
|
+
try {
|
|
176
|
+
fs.rmSync(outPath, { force: true });
|
|
177
|
+
} catch (_) {
|
|
178
|
+
}
|
|
179
|
+
failures.push(`${url}: ${err.message}`);
|
|
180
|
+
console.warn(`[iris] download failed, trying next source`);
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
fail(`could not download binary from any source.\n${failures.map((item) => `- ${item}`).join("\n")}`);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
main();
|