@rivus/agent 0.14.3 → 0.15.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 +1 -1
- package/dist/acp.js +1 -2
- package/dist/bootstrap/pi-feishu.d.ts +2 -2
- package/dist/bootstrap/pi-feishu.js +4119 -389
- package/dist/chunks/agent-loop.d.ts +55 -300
- package/dist/chunks/agent-loop.js +3 -1123
- package/dist/chunks/background-session-authority.js +230 -0
- package/dist/chunks/background-session-control-input.js +51 -0
- package/dist/chunks/background-session-service.d.ts +382 -0
- package/dist/chunks/index.d.ts +1294 -515
- package/dist/chunks/pi-tool-proxy.d.ts +22 -90
- package/dist/chunks/pi.js +60 -20
- package/dist/chunks/rivus-agent-definition-resolver.js +508 -0
- package/dist/chunks/rivus-daemon-cli.js +3004 -3164
- package/dist/chunks/rivus-model-management-wire.js +344 -0
- package/dist/chunks/rivus-plugin-testkit.d.ts +175 -2
- package/dist/chunks/rivus-plugin-testkit.js +11 -4
- package/dist/chunks/rivus-skill.d.ts +95 -0
- package/dist/chunks/rivus-tool.js +158 -0
- package/dist/chunks/sha256-digest.js +2 -7
- package/dist/chunks/src.js +13402 -8264
- package/dist/cli.js +901 -698
- package/dist/index.d.ts +7 -8
- package/dist/index.js +8 -9
- package/dist/mcp.d.ts +46 -7
- package/dist/mcp.js +145 -19
- package/dist/pi.d.ts +5 -4
- package/dist/pi.js +1 -1
- package/examples/pi-feishu-deployment.bootstrap.ts +557 -462
- package/package.json +9 -7
- package/skills/runtime-management/SKILL.md +61 -0
- package/dist/chunks/api.d.ts +0 -70
- package/dist/chunks/api.js +0 -471
- package/dist/chunks/api2.d.ts +0 -387
- package/dist/chunks/api2.js +0 -1331
- package/dist/chunks/api3.d.ts +0 -402
- package/dist/chunks/module.js +0 -267
- package/dist/chunks/pi-skill-tool.js +0 -460
- package/dist/chunks/spi.d.ts +0 -1
- package/dist/chunks/spi.js +0 -2
package/dist/cli.js
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import {
|
|
2
|
+
import { A as validateRivusDeploymentManifest, I as resolveFeishuEndpointCredentials, O as loadRivusDeploymentManifest, X as loadMergedLocalEnvFile, g as findTrustedPackageRoot, h as isPathWithin, m as validateTrustedModulePath, p as resolveNodeRivusPluginModulePath, t as runRivusDaemonCli } from "./chunks/rivus-daemon-cli.js";
|
|
3
|
+
import { c as renderRivusModelCliHelp, n as createRivusModelManagementWireRequest, o as parseRivusModelCliArguments, s as renderRivusModelCliArgumentError } from "./chunks/rivus-model-management-wire.js";
|
|
3
4
|
import { Effect, Either } from "effect";
|
|
4
5
|
import { lstat, mkdir, readFile, realpath, rmdir, stat, unlink, writeFile } from "node:fs/promises";
|
|
5
6
|
import { basename, dirname, isAbsolute, join, relative, resolve, sep } from "node:path";
|
|
6
7
|
import { homedir } from "node:os";
|
|
7
8
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
9
|
+
import { createConnection } from "node:net";
|
|
8
10
|
//#region src/adapters/deployment/inspection/rivus-home-deployment-inspector.ts
|
|
9
11
|
const DEFAULT_BOOTSTRAP_PEERS = Object.freeze(["@earendil-works/pi-coding-agent", "@larksuiteoapi/node-sdk"]);
|
|
10
12
|
function createRivusHomeDeploymentInspector(options = {}) {
|
|
@@ -33,7 +35,7 @@ function checkManifest$1(home) {
|
|
|
33
35
|
manifest
|
|
34
36
|
};
|
|
35
37
|
}), Effect.catchAll((error) => Effect.succeed({ check: {
|
|
36
|
-
message: `deployment manifest is invalid: ${formatError
|
|
38
|
+
message: `deployment manifest is invalid: ${formatError(error)}`,
|
|
37
39
|
name: "manifest",
|
|
38
40
|
status: "fail"
|
|
39
41
|
} })));
|
|
@@ -49,7 +51,7 @@ function checkModules(home, manifest, resolveModule, resolvePluginModule) {
|
|
|
49
51
|
const missing = [];
|
|
50
52
|
for (const specifier of specifiers) {
|
|
51
53
|
const result = yield* toEffect(() => resolveModule(specifier)).pipe(Effect.either);
|
|
52
|
-
if (result._tag === "Left") missing.push(`${specifier} (${formatError
|
|
54
|
+
if (result._tag === "Left") missing.push(`${specifier} (${formatError(result.left)})`);
|
|
53
55
|
}
|
|
54
56
|
for (const plugin of manifest.plugins) {
|
|
55
57
|
const result = yield* toEffect(() => resolvePluginModule({
|
|
@@ -57,7 +59,7 @@ function checkModules(home, manifest, resolveModule, resolvePluginModule) {
|
|
|
57
59
|
module: plugin.module,
|
|
58
60
|
pluginId: plugin.id
|
|
59
61
|
})).pipe(Effect.either);
|
|
60
|
-
if (result._tag === "Left") missing.push(`${plugin.module} (${formatError
|
|
62
|
+
if (result._tag === "Left") missing.push(`${plugin.module} (${formatError(result.left)})`);
|
|
61
63
|
}
|
|
62
64
|
return missing.length === 0 ? {
|
|
63
65
|
message: "Bootstrap, Plugin, Pi, and Feishu modules resolve from the global installation",
|
|
@@ -102,7 +104,7 @@ function checkCredentials$1(home, manifest, env, envFilePath) {
|
|
|
102
104
|
},
|
|
103
105
|
catch: toError$1
|
|
104
106
|
}).pipe(Effect.catchAll((error) => Effect.succeed({
|
|
105
|
-
message: `enabled Endpoint credentials are incomplete: ${formatError
|
|
107
|
+
message: `enabled Endpoint credentials are incomplete: ${formatError(error)}`,
|
|
106
108
|
name: "credentials",
|
|
107
109
|
status: "fail"
|
|
108
110
|
})));
|
|
@@ -120,410 +122,340 @@ function defaultResolveModule(specifier, packageManifestPath) {
|
|
|
120
122
|
function toError$1(error) {
|
|
121
123
|
return error instanceof Error ? error : new Error(String(error));
|
|
122
124
|
}
|
|
123
|
-
function formatError
|
|
125
|
+
function formatError(error) {
|
|
124
126
|
return error instanceof Error ? error.message : String(error);
|
|
125
127
|
}
|
|
126
128
|
//#endregion
|
|
127
|
-
//#region src/
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
"
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
129
|
+
//#region src/platform/home/config/rivus-home-config.ts
|
|
130
|
+
/** Loads and validates the operator-owned Home config file. */
|
|
131
|
+
function loadRivusHome(directoryPath) {
|
|
132
|
+
const directory = resolve(directoryPath);
|
|
133
|
+
const configPath = resolve(directory, "config.json");
|
|
134
|
+
return Effect.tryPromise({
|
|
135
|
+
try: async () => {
|
|
136
|
+
const config = parseRivusHomeConfig(JSON.parse(await readFile(configPath, "utf8")));
|
|
137
|
+
return {
|
|
138
|
+
bootstrap: resolveBootstrap(directory, config.bootstrap),
|
|
139
|
+
config,
|
|
140
|
+
configPath,
|
|
141
|
+
directory,
|
|
142
|
+
envFilePath: resolveHomePath(directory, config.envFile, "envFile"),
|
|
143
|
+
logsDirectory: resolveHomePath(directory, config.logs, "logs"),
|
|
144
|
+
manifestPath: resolveHomePath(directory, config.manifest, "manifest"),
|
|
145
|
+
stateDirectory: resolveHomePath(directory, config.state, "state"),
|
|
146
|
+
workspaceDirectory: resolveHomePath(directory, config.workspace, "workspace")
|
|
147
|
+
};
|
|
148
|
+
},
|
|
149
|
+
catch: asError$1
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
function parseRivusHomeConfig(value) {
|
|
153
|
+
const config = record(value, "Rivus Home config");
|
|
154
|
+
if (config.version !== 1) throw new Error("Rivus Home config version must be 1");
|
|
155
|
+
return {
|
|
156
|
+
bootstrap: nonEmptyString(config.bootstrap, "bootstrap"),
|
|
157
|
+
envFile: relativePath(config.envFile, "envFile"),
|
|
158
|
+
logs: relativePath(config.logs, "logs"),
|
|
159
|
+
manifest: relativePath(config.manifest, "manifest"),
|
|
160
|
+
state: relativePath(config.state, "state"),
|
|
161
|
+
version: 1,
|
|
162
|
+
workspace: relativePath(config.workspace, "workspace")
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
function resolveBootstrap(directory, value) {
|
|
166
|
+
if (value.startsWith("./") || value.startsWith("../")) return resolveHomePath(directory, value, "bootstrap");
|
|
167
|
+
if (isAbsolute(value)) throw new Error("Rivus Home bootstrap must be a package specifier or a relative path inside Rivus Home");
|
|
168
|
+
return value;
|
|
169
|
+
}
|
|
170
|
+
function resolveHomePath(directory, value, owner) {
|
|
171
|
+
const candidate = resolve(directory, value);
|
|
172
|
+
const relation = relative(directory, candidate);
|
|
173
|
+
if (relation === "" || !relation.startsWith(`..${sep}`) && relation !== ".." && !isAbsolute(relation)) return candidate;
|
|
174
|
+
throw new Error(`Rivus Home ${owner} escapes the Home directory`);
|
|
175
|
+
}
|
|
176
|
+
function relativePath(value, owner) {
|
|
177
|
+
const path = nonEmptyString(value, owner);
|
|
178
|
+
if (isAbsolute(path)) throw new Error(`Rivus Home ${owner} must be a relative path`);
|
|
179
|
+
return path;
|
|
180
|
+
}
|
|
181
|
+
function nonEmptyString(value, owner) {
|
|
182
|
+
if (typeof value !== "string" || !value.trim()) throw new Error(`Rivus Home ${owner} must be a non-empty string`);
|
|
183
|
+
return value;
|
|
184
|
+
}
|
|
185
|
+
function record(value, owner) {
|
|
186
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) throw new Error(`${owner} must be an object`);
|
|
187
|
+
return value;
|
|
188
|
+
}
|
|
189
|
+
function asError$1(error) {
|
|
190
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
191
|
+
}
|
|
192
|
+
//#endregion
|
|
193
|
+
//#region src/platform/home/doctor/rivus-home-doctor.ts
|
|
194
|
+
function diagnoseRivusHome(input, options) {
|
|
139
195
|
return Effect.gen(function* () {
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
}
|
|
150
|
-
const
|
|
151
|
-
checks.push(
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
catch: toError
|
|
156
|
-
}));
|
|
157
|
-
return Object.freeze({
|
|
158
|
-
checks: Object.freeze(checks),
|
|
159
|
-
directory,
|
|
160
|
-
ready: checks.every(({ status }) => status === "pass")
|
|
196
|
+
const checks = [checkNode$1(input.nodeVersion)];
|
|
197
|
+
const loaded = yield* options.load(input.directory).pipe(Effect.either);
|
|
198
|
+
if (Either.isLeft(loaded)) {
|
|
199
|
+
checks.push({
|
|
200
|
+
message: `Rivus Home is invalid: ${loaded.left.message}`,
|
|
201
|
+
name: "home",
|
|
202
|
+
status: "fail"
|
|
203
|
+
}, blockedCheck("workspace", "Workspace"), blockedCheck("manifest", "manifest"), blockedCheck("modules", "modules"), blockedCheck("credentials", "credentials"));
|
|
204
|
+
return report(input.directory, checks);
|
|
205
|
+
}
|
|
206
|
+
const home = loaded.right;
|
|
207
|
+
checks.push({
|
|
208
|
+
message: "config.json is valid and contained in Rivus Home",
|
|
209
|
+
name: "home",
|
|
210
|
+
status: "pass"
|
|
161
211
|
});
|
|
212
|
+
checks.push(yield* checkWorkspace(home, options.findMissingWorkspacePaths));
|
|
213
|
+
const deployment = yield* options.deploymentInspector.inspect({
|
|
214
|
+
env: input.env,
|
|
215
|
+
...input.envFilePath ? { envFilePath: input.envFilePath } : {},
|
|
216
|
+
home
|
|
217
|
+
});
|
|
218
|
+
checks.push(deployment.manifest, deployment.modules, deployment.credentials);
|
|
219
|
+
return report(input.directory, checks);
|
|
162
220
|
});
|
|
163
221
|
}
|
|
164
222
|
function checkNode$1(version) {
|
|
165
223
|
const [major, minor] = version.split(".").map(Number);
|
|
166
|
-
|
|
224
|
+
return major === 24 && Number.isInteger(minor) && minor >= 11 ? {
|
|
167
225
|
message: `Node.js ${version} satisfies the supported ^24.11 runtime`,
|
|
168
226
|
name: "node",
|
|
169
227
|
status: "pass"
|
|
170
|
-
}
|
|
171
|
-
return {
|
|
228
|
+
} : {
|
|
172
229
|
message: `Node.js 24.11 or newer on major 24 is required; current version is ${version}`,
|
|
173
230
|
name: "node",
|
|
174
231
|
status: "fail"
|
|
175
232
|
};
|
|
176
233
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
name: "files",
|
|
234
|
+
function checkWorkspace(home, findMissingWorkspacePaths) {
|
|
235
|
+
return findMissingWorkspacePaths(home).pipe(Effect.map((missing) => missing.length === 0 ? {
|
|
236
|
+
message: "Workspace instructions, Memory, Skills, and work directories are present",
|
|
237
|
+
name: "workspace",
|
|
182
238
|
status: "pass"
|
|
183
239
|
} : {
|
|
184
|
-
message: `
|
|
185
|
-
name: "
|
|
240
|
+
message: `Workspace paths are missing: ${missing.join(", ")}`,
|
|
241
|
+
name: "workspace",
|
|
186
242
|
status: "fail"
|
|
187
|
-
};
|
|
243
|
+
}));
|
|
188
244
|
}
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
return missing.length === 0 ? {
|
|
194
|
-
message: "Rivus, Pi, and Feishu dependencies are installed",
|
|
195
|
-
name: "dependencies",
|
|
196
|
-
status: "pass"
|
|
197
|
-
} : {
|
|
198
|
-
message: `run npm install; missing local dependencies: ${missing.join(", ")}`,
|
|
199
|
-
name: "dependencies",
|
|
245
|
+
function blockedCheck(name, owner) {
|
|
246
|
+
return {
|
|
247
|
+
message: `${owner} cannot be checked until config.json is valid`,
|
|
248
|
+
name,
|
|
200
249
|
status: "fail"
|
|
201
250
|
};
|
|
202
251
|
}
|
|
203
|
-
function
|
|
204
|
-
return
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
const result = yield* resolveNodeRivusPluginModulePath({
|
|
209
|
-
deploymentRoot: directory,
|
|
210
|
-
module: plugin.module,
|
|
211
|
-
pluginId: plugin.id
|
|
212
|
-
}).pipe(Effect.either);
|
|
213
|
-
if (Either.isLeft(result)) missingModules.push(plugin.module);
|
|
214
|
-
}
|
|
215
|
-
if (missingModules.length > 0) return { check: {
|
|
216
|
-
message: `manifest Plugin modules are missing: ${missingModules.join(", ")}`,
|
|
217
|
-
name: "manifest",
|
|
218
|
-
status: "fail"
|
|
219
|
-
} };
|
|
220
|
-
return {
|
|
221
|
-
check: {
|
|
222
|
-
message: "deployment manifest is valid",
|
|
223
|
-
name: "manifest",
|
|
224
|
-
status: "pass"
|
|
225
|
-
},
|
|
226
|
-
manifest
|
|
227
|
-
};
|
|
228
|
-
})), Effect.catchAll((error) => Effect.succeed({ check: {
|
|
229
|
-
message: `deployment manifest is invalid: ${error.message}`,
|
|
230
|
-
name: "manifest",
|
|
231
|
-
status: "fail"
|
|
232
|
-
} })));
|
|
233
|
-
}
|
|
234
|
-
async function checkCredentials(manifest, envFilePath, env) {
|
|
235
|
-
let mergedEnv;
|
|
236
|
-
try {
|
|
237
|
-
mergedEnv = await loadMergedLocalEnvFile(envFilePath, env);
|
|
238
|
-
} catch (error) {
|
|
239
|
-
return {
|
|
240
|
-
message: `required env file is unavailable at ${envFilePath}: ${error instanceof Error ? error.message : String(error)}`,
|
|
241
|
-
name: "credentials",
|
|
242
|
-
status: "fail"
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
if (!manifest) return {
|
|
246
|
-
message: "enabled Endpoint credentials cannot be checked until the deployment manifest is valid",
|
|
247
|
-
name: "credentials",
|
|
248
|
-
status: "fail"
|
|
252
|
+
function report(directory, checks) {
|
|
253
|
+
return {
|
|
254
|
+
checks,
|
|
255
|
+
directory,
|
|
256
|
+
ready: checks.every(({ status }) => status === "pass")
|
|
249
257
|
};
|
|
250
|
-
try {
|
|
251
|
-
for (const endpoint of manifest.endpoints.filter(({ enabled }) => enabled)) resolveFeishuEndpointCredentials(endpoint.credentialRef, mergedEnv);
|
|
252
|
-
return {
|
|
253
|
-
message: "enabled Endpoint credential references resolve",
|
|
254
|
-
name: "credentials",
|
|
255
|
-
status: "pass"
|
|
256
|
-
};
|
|
257
|
-
} catch (error) {
|
|
258
|
-
return {
|
|
259
|
-
message: `enabled Endpoint credentials are incomplete: ${error instanceof Error ? error.message : String(error)}`,
|
|
260
|
-
name: "credentials",
|
|
261
|
-
status: "fail"
|
|
262
|
-
};
|
|
263
|
-
}
|
|
264
|
-
}
|
|
265
|
-
async function missingRegularFiles(directory, paths) {
|
|
266
|
-
const missing = [];
|
|
267
|
-
for (const path of paths) try {
|
|
268
|
-
if (!(await stat(resolve(directory, path))).isFile()) missing.push(path);
|
|
269
|
-
} catch (error) {
|
|
270
|
-
if (!isMissingPath$1(error)) throw error;
|
|
271
|
-
missing.push(path);
|
|
272
|
-
}
|
|
273
|
-
return missing;
|
|
274
|
-
}
|
|
275
|
-
function isMissingPath$1(error) {
|
|
276
|
-
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
277
|
-
}
|
|
278
|
-
function toError(error) {
|
|
279
|
-
return error instanceof Error ? error : new Error(String(error));
|
|
280
258
|
}
|
|
281
259
|
//#endregion
|
|
282
|
-
//#region src/platform/
|
|
283
|
-
const
|
|
284
|
-
"
|
|
285
|
-
"
|
|
286
|
-
"
|
|
287
|
-
"
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
["deploy/launchd/com.rivus.agent.plist", launchdService(directory, options.nodeExecutable)],
|
|
296
|
-
["deploy/systemd/rivus.service", systemdService(directory, options.nodeExecutable)],
|
|
297
|
-
["package.json", projectPackageJson(directory, manifest)],
|
|
298
|
-
["rivus.config.json", deploymentManifest$1()]
|
|
299
|
-
]);
|
|
300
|
-
for (const [target, source] of Object.entries(TEMPLATE_FILES)) files.set(target, await readFile(join(options.templateDirectory, source), "utf8"));
|
|
301
|
-
const paths = [...files.keys()].sort();
|
|
302
|
-
await assertSafeProjectAncestors(directory, paths);
|
|
303
|
-
const conflicts = await findConflicts(directory, paths);
|
|
304
|
-
if (conflicts.length > 0) throw new Error(`Rivus project initialization refused; refusing to overwrite: ${conflicts.join(", ")}`);
|
|
305
|
-
const createdDirectories = [];
|
|
306
|
-
const createdFiles = [];
|
|
307
|
-
const writeProjectFile = options.writeProjectFile ?? writeExclusiveProjectFile;
|
|
308
|
-
try {
|
|
309
|
-
for (const path of paths) {
|
|
310
|
-
const destination = join(directory, path);
|
|
311
|
-
await ensureDirectory(dirname(destination), directory, createdDirectories);
|
|
312
|
-
await assertSafeProjectAncestors(directory, [path]);
|
|
313
|
-
await writeProjectFile(destination, files.get(path));
|
|
314
|
-
createdFiles.push(destination);
|
|
315
|
-
}
|
|
316
|
-
} catch (error) {
|
|
317
|
-
const rollbackErrors = await rollbackCreatedPaths(createdFiles, createdDirectories);
|
|
318
|
-
if (rollbackErrors.length > 0) throw new AggregateError([error, ...rollbackErrors], "Rivus project initialization failed and rollback was incomplete");
|
|
319
|
-
throw error;
|
|
320
|
-
}
|
|
260
|
+
//#region src/platform/home/setup/rivus-home-layout.ts
|
|
261
|
+
const HOME_DIRECTORIES = Object.freeze([
|
|
262
|
+
"logs",
|
|
263
|
+
"plugins",
|
|
264
|
+
"state",
|
|
265
|
+
"workspace/memory",
|
|
266
|
+
"workspace/skills",
|
|
267
|
+
"workspace/work/artifacts",
|
|
268
|
+
"workspace/work/drafts",
|
|
269
|
+
"workspace/work/inbox",
|
|
270
|
+
"workspace/work/tmp"
|
|
271
|
+
]);
|
|
272
|
+
function createRivusHomeLayout() {
|
|
321
273
|
return {
|
|
322
|
-
|
|
323
|
-
files:
|
|
274
|
+
directories: HOME_DIRECTORIES,
|
|
275
|
+
files: /* @__PURE__ */ new Map([
|
|
276
|
+
[".env.example", environmentTemplate$1()],
|
|
277
|
+
[".gitignore", ".DS_Store\n.env\nlogs/\nstate/\nworkspace/work/tmp/\n"],
|
|
278
|
+
["config.json", homeConfig()],
|
|
279
|
+
["rivus.config.json", deploymentManifest$1()],
|
|
280
|
+
["workspace/AGENTS.md", agentsTemplate()],
|
|
281
|
+
["workspace/IDENTITY.md", "# Identity\n\nName: Rivus\n\nRole: Personal agent\n"],
|
|
282
|
+
["workspace/MEMORY.md", "# Curated Memory\n\nStore confirmed, durable facts and decisions here.\n"],
|
|
283
|
+
["workspace/SOUL.md", "# Character\n\nBe direct, thoughtful, and evidence-led.\n"],
|
|
284
|
+
["workspace/USER.md", "# User\n\nRecord stable user preferences here.\n"],
|
|
285
|
+
["plugins/.gitkeep", ""],
|
|
286
|
+
["workspace/memory/.gitkeep", ""],
|
|
287
|
+
["workspace/skills/.gitkeep", ""],
|
|
288
|
+
["workspace/work/artifacts/.gitkeep", ""],
|
|
289
|
+
["workspace/work/drafts/.gitkeep", ""],
|
|
290
|
+
["workspace/work/inbox/.gitkeep", ""]
|
|
291
|
+
])
|
|
324
292
|
};
|
|
325
293
|
}
|
|
326
|
-
|
|
327
|
-
await writeFile(path, contents, {
|
|
328
|
-
encoding: "utf8",
|
|
329
|
-
flag: "wx"
|
|
330
|
-
});
|
|
331
|
-
}
|
|
332
|
-
async function assertSafeProjectAncestors(directory, paths) {
|
|
333
|
-
const rootState = await lstatOrUndefined(directory);
|
|
334
|
-
if (rootState?.isSymbolicLink()) throw new Error("Rivus project initialization refused; symbolic link ancestor: .");
|
|
335
|
-
if (rootState && !rootState.isDirectory()) throw new Error("Rivus project initialization refused; project path is not a directory");
|
|
336
|
-
if (!rootState) return;
|
|
337
|
-
for (const path of paths) {
|
|
338
|
-
let current = directory;
|
|
339
|
-
for (const segment of path.split("/").slice(0, -1)) {
|
|
340
|
-
current = join(current, segment);
|
|
341
|
-
const state = await lstatOrUndefined(current);
|
|
342
|
-
if (!state) break;
|
|
343
|
-
if (state.isSymbolicLink()) throw new Error(`Rivus project initialization refused; symbolic link ancestor: ${relative(directory, current)}`);
|
|
344
|
-
if (!state.isDirectory()) throw new Error(`Rivus project initialization refused; non-directory ancestor: ${relative(directory, current)}`);
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
async function ensureDirectory(path, projectRoot, createdDirectories) {
|
|
349
|
-
try {
|
|
350
|
-
await mkdir(path);
|
|
351
|
-
createdDirectories.push(path);
|
|
352
|
-
} catch (error) {
|
|
353
|
-
if (isMissingPath(error)) {
|
|
354
|
-
const parent = dirname(path);
|
|
355
|
-
if (parent === path) throw error;
|
|
356
|
-
await ensureDirectory(parent, projectRoot, createdDirectories);
|
|
357
|
-
await ensureDirectory(path, projectRoot, createdDirectories);
|
|
358
|
-
return;
|
|
359
|
-
}
|
|
360
|
-
if (!isAlreadyExists(error)) throw error;
|
|
361
|
-
const state = await lstat(path);
|
|
362
|
-
if (state.isSymbolicLink()) {
|
|
363
|
-
if (isPathWithin(projectRoot, path)) throw new Error(`Rivus project initialization refused; symbolic link ancestor: ${relative(projectRoot, path) || "."}`);
|
|
364
|
-
if ((await stat(path)).isDirectory()) return;
|
|
365
|
-
}
|
|
366
|
-
if (!state.isDirectory()) throw new Error(`Rivus project initialization refused; non-directory ancestor: ${path}`);
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
|
-
async function rollbackCreatedPaths(files, directories) {
|
|
370
|
-
const errors = [];
|
|
371
|
-
for (const path of files.reverse()) try {
|
|
372
|
-
await unlink(path);
|
|
373
|
-
} catch (error) {
|
|
374
|
-
if (!isMissingPath(error)) errors.push(asError$1(error));
|
|
375
|
-
}
|
|
376
|
-
for (const path of directories.reverse()) try {
|
|
377
|
-
await rmdir(path);
|
|
378
|
-
} catch (error) {
|
|
379
|
-
if (!isMissingPath(error) && !isDirectoryNotEmpty(error)) errors.push(asError$1(error));
|
|
380
|
-
}
|
|
381
|
-
return errors;
|
|
382
|
-
}
|
|
383
|
-
async function lstatOrUndefined(path) {
|
|
384
|
-
try {
|
|
385
|
-
return await lstat(path);
|
|
386
|
-
} catch (error) {
|
|
387
|
-
if (isMissingPath(error)) return void 0;
|
|
388
|
-
throw error;
|
|
389
|
-
}
|
|
390
|
-
}
|
|
391
|
-
function isAlreadyExists(error) {
|
|
392
|
-
return error instanceof Error && "code" in error && error.code === "EEXIST";
|
|
393
|
-
}
|
|
394
|
-
function isDirectoryNotEmpty(error) {
|
|
395
|
-
return error instanceof Error && "code" in error && error.code === "ENOTEMPTY";
|
|
396
|
-
}
|
|
397
|
-
function asError$1(error) {
|
|
398
|
-
return error instanceof Error ? error : new Error(String(error));
|
|
399
|
-
}
|
|
400
|
-
async function readPackageManifest(path) {
|
|
401
|
-
const manifest = JSON.parse(await readFile(path, "utf8"));
|
|
402
|
-
if (manifest.name !== "@rivus/agent" || typeof manifest.version !== "string") throw new Error("Rivus package manifest is missing its release identity");
|
|
403
|
-
return manifest;
|
|
404
|
-
}
|
|
405
|
-
async function findConflicts(directory, paths) {
|
|
406
|
-
const conflicts = [];
|
|
407
|
-
for (const path of paths) try {
|
|
408
|
-
await lstat(join(directory, path));
|
|
409
|
-
conflicts.push(path);
|
|
410
|
-
} catch (error) {
|
|
411
|
-
if (!isMissingPath(error)) throw error;
|
|
412
|
-
}
|
|
413
|
-
return conflicts;
|
|
414
|
-
}
|
|
415
|
-
function isMissingPath(error) {
|
|
416
|
-
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
417
|
-
}
|
|
418
|
-
function projectPackageJson(directory, manifest) {
|
|
419
|
-
const pi = manifest.dependencies?.["@earendil-works/pi-coding-agent"];
|
|
420
|
-
const lark = manifest.dependencies?.["@larksuiteoapi/node-sdk"];
|
|
421
|
-
const effect = manifest.dependencies?.effect;
|
|
422
|
-
if (!pi || !lark || !effect) throw new Error("Rivus package manifest is missing its Effect, Pi, or Feishu dependency range");
|
|
423
|
-
const projectName = sanitizePackageName(basename(directory));
|
|
294
|
+
function homeConfig() {
|
|
424
295
|
return `${JSON.stringify({
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
},
|
|
433
|
-
dependencies: {
|
|
434
|
-
"@earendil-works/pi-coding-agent": pi,
|
|
435
|
-
"@larksuiteoapi/node-sdk": lark,
|
|
436
|
-
"@rivus/agent": `^${manifest.version}`,
|
|
437
|
-
effect
|
|
438
|
-
}
|
|
296
|
+
bootstrap: "@rivus/agent/bootstrap/pi-feishu",
|
|
297
|
+
envFile: ".env",
|
|
298
|
+
logs: "logs",
|
|
299
|
+
manifest: "rivus.config.json",
|
|
300
|
+
state: "state",
|
|
301
|
+
version: 1,
|
|
302
|
+
workspace: "workspace"
|
|
439
303
|
}, null, 2)}\n`;
|
|
440
304
|
}
|
|
441
|
-
function sanitizePackageName(value) {
|
|
442
|
-
return value.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|[._-]+$/g, "") || "rivus-app";
|
|
443
|
-
}
|
|
444
305
|
function deploymentManifest$1() {
|
|
445
306
|
return `${JSON.stringify({
|
|
446
|
-
plugins: [{
|
|
447
|
-
id: "rivus-starter",
|
|
448
|
-
module: "./rivus-agents.plugin.mjs",
|
|
449
|
-
required: true
|
|
450
|
-
}],
|
|
451
307
|
agents: [{
|
|
452
|
-
agentId: "
|
|
453
|
-
endpointIds: ["feishu
|
|
308
|
+
agentId: "personal",
|
|
309
|
+
endpointIds: ["personal-feishu"],
|
|
310
|
+
memory: {
|
|
311
|
+
scopes: ["agent-private"],
|
|
312
|
+
tool: true
|
|
313
|
+
},
|
|
454
314
|
pluginId: "rivus-starter",
|
|
455
315
|
profileId: "agent-a",
|
|
316
|
+
projectSpaceId: "personal-home",
|
|
317
|
+
runtimeTools: { allow: [
|
|
318
|
+
"read",
|
|
319
|
+
"bash",
|
|
320
|
+
"edit",
|
|
321
|
+
"write",
|
|
322
|
+
"grep",
|
|
323
|
+
"find",
|
|
324
|
+
"ls"
|
|
325
|
+
] },
|
|
456
326
|
skills: { allow: [] },
|
|
457
327
|
tools: { allow: ["rivus-starter/current-weather"] }
|
|
458
328
|
}],
|
|
459
|
-
defaultAgentId: "
|
|
460
|
-
defaultEndpointId: "feishu
|
|
329
|
+
defaultAgentId: "personal",
|
|
330
|
+
defaultEndpointId: "personal-feishu",
|
|
461
331
|
endpoints: [{
|
|
462
|
-
agentId: "
|
|
332
|
+
agentId: "personal",
|
|
463
333
|
baseUrl: "https://open.feishu.cn",
|
|
464
334
|
cardStreamLeaseMs: 51e4,
|
|
465
335
|
credentialRef: "env:RIVUS_FEISHU",
|
|
466
336
|
enabled: true,
|
|
467
337
|
experimental: { cotMessages: false },
|
|
468
338
|
groupPolicy: "mention-only",
|
|
469
|
-
id: "feishu
|
|
339
|
+
id: "personal-feishu",
|
|
470
340
|
progressDisplay: "collapsed",
|
|
471
341
|
required: true,
|
|
472
|
-
sessionNamespace: "rivus-
|
|
342
|
+
sessionNamespace: "rivus-home-v1",
|
|
473
343
|
streamMinIntervalMs: 200
|
|
344
|
+
}],
|
|
345
|
+
plugins: [{
|
|
346
|
+
id: "rivus-starter",
|
|
347
|
+
module: "@rivus/agent/plugin/starter",
|
|
348
|
+
required: true
|
|
349
|
+
}],
|
|
350
|
+
projectSpaces: [{
|
|
351
|
+
id: "personal-home",
|
|
352
|
+
root: "workspace",
|
|
353
|
+
skills: { sources: ["skills"] },
|
|
354
|
+
workingDirectory: "."
|
|
474
355
|
}]
|
|
475
356
|
}, null, 2)}\n`;
|
|
476
357
|
}
|
|
477
358
|
function environmentTemplate$1() {
|
|
478
359
|
return [
|
|
479
|
-
"# Copy this file to .env
|
|
360
|
+
"# Copy this file to .env and keep the real values untracked.",
|
|
480
361
|
"RIVUS_FEISHU_APP_ID=",
|
|
481
362
|
"RIVUS_FEISHU_APP_SECRET=",
|
|
482
363
|
"PI_MODEL=",
|
|
483
364
|
"PI_API_KEY=",
|
|
484
365
|
"# PI_BASE_URL=",
|
|
485
366
|
"RIVUS_WEATHER_DEFAULT_LOCATION=上海",
|
|
486
|
-
"# LANGFUSE_BASE_URL=https://jp.cloud.langfuse.com",
|
|
487
|
-
"# LANGFUSE_PUBLIC_KEY=",
|
|
488
|
-
"# LANGFUSE_SECRET_KEY=",
|
|
489
|
-
"# RIVUS_TELEMETRY_CONTENT=redacted",
|
|
490
367
|
""
|
|
491
368
|
].join("\n");
|
|
492
369
|
}
|
|
493
|
-
function
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
".env.local",
|
|
509
|
-
"--bootstrap",
|
|
510
|
-
"./rivus.bootstrap.ts",
|
|
511
|
-
"--manifest",
|
|
512
|
-
"./rivus.config.json"
|
|
513
|
-
].map((value) => ` <string>${xmlEscape(value)}</string>`).join("\n")}\n </array>\n <key>WorkingDirectory</key>\n <string>${xmlEscape(directory)}</string>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <dict>\n <key>SuccessfulExit</key>\n <false/>\n </dict>\n <key>ThrottleInterval</key>\n <integer>5</integer>\n</dict>\n</plist>\n`;
|
|
370
|
+
function agentsTemplate() {
|
|
371
|
+
return [
|
|
372
|
+
"# Personal Workspace",
|
|
373
|
+
"",
|
|
374
|
+
"Use this directory as the default home for personal work.",
|
|
375
|
+
"",
|
|
376
|
+
"- Read `SOUL.md` and `IDENTITY.md` when identity or tone matters.",
|
|
377
|
+
"- Read `USER.md` when stable user preferences affect the task.",
|
|
378
|
+
"- Search `MEMORY.md` and dated files under `memory/` when prior facts or decisions matter.",
|
|
379
|
+
"- Read the selected `skills/<name>/SKILL.md` before following a Workspace Skill.",
|
|
380
|
+
"- Put incoming material in `work/inbox/`, drafts in `work/drafts/`, and durable general outputs in `work/artifacts/`.",
|
|
381
|
+
"- Put disposable files in `work/tmp/`.",
|
|
382
|
+
"- Store only confirmed durable facts in Memory; keep credentials and raw private transcripts out of tracked files.",
|
|
383
|
+
""
|
|
384
|
+
].join("\n");
|
|
514
385
|
}
|
|
515
|
-
|
|
516
|
-
|
|
386
|
+
//#endregion
|
|
387
|
+
//#region src/platform/home/setup/rivus-home-initializer.ts
|
|
388
|
+
function initializeRivusHome(directoryPath) {
|
|
389
|
+
const directory = resolve(directoryPath);
|
|
390
|
+
const { directories, files } = createRivusHomeLayout();
|
|
391
|
+
const paths = [...files.keys()].sort();
|
|
392
|
+
return Effect.tryPromise({
|
|
393
|
+
try: async () => {
|
|
394
|
+
for (const path of [...directories].sort()) await mkdir(join(directory, path), { recursive: true });
|
|
395
|
+
for (const path of paths) {
|
|
396
|
+
const destination = join(directory, path);
|
|
397
|
+
await mkdir(dirname(destination), { recursive: true });
|
|
398
|
+
await writeFile(destination, files.get(path), {
|
|
399
|
+
encoding: "utf8",
|
|
400
|
+
flag: "wx"
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
return {
|
|
404
|
+
directory,
|
|
405
|
+
files: paths
|
|
406
|
+
};
|
|
407
|
+
},
|
|
408
|
+
catch: (error) => error instanceof Error ? error : new Error(String(error))
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
//#endregion
|
|
412
|
+
//#region src/platform/home/workspace/rivus-home-workspace.ts
|
|
413
|
+
function findMissingRivusHomeWorkspacePaths(home) {
|
|
414
|
+
const required = [
|
|
415
|
+
home.workspaceDirectory,
|
|
416
|
+
resolve(home.workspaceDirectory, "AGENTS.md"),
|
|
417
|
+
resolve(home.workspaceDirectory, "MEMORY.md"),
|
|
418
|
+
resolve(home.workspaceDirectory, "memory"),
|
|
419
|
+
resolve(home.workspaceDirectory, "skills"),
|
|
420
|
+
resolve(home.workspaceDirectory, "work")
|
|
421
|
+
];
|
|
422
|
+
return Effect.tryPromise({
|
|
423
|
+
try: async () => {
|
|
424
|
+
const missing = [];
|
|
425
|
+
for (const path of required) try {
|
|
426
|
+
await stat(path);
|
|
427
|
+
} catch (error) {
|
|
428
|
+
if (error instanceof Error && "code" in error && error.code === "ENOENT") missing.push(path);
|
|
429
|
+
else throw error;
|
|
430
|
+
}
|
|
431
|
+
return missing;
|
|
432
|
+
},
|
|
433
|
+
catch: (error) => error instanceof Error ? error : new Error(String(error))
|
|
434
|
+
});
|
|
435
|
+
}
|
|
436
|
+
//#endregion
|
|
437
|
+
//#region src/platform/home/node/node-rivus-home.ts
|
|
438
|
+
function createNodeRivusHome(options) {
|
|
439
|
+
return {
|
|
440
|
+
diagnose: (input) => diagnoseRivusHome(input, {
|
|
441
|
+
deploymentInspector: options.deploymentInspector,
|
|
442
|
+
findMissingWorkspacePaths: findMissingRivusHomeWorkspacePaths,
|
|
443
|
+
load: loadRivusHome
|
|
444
|
+
}),
|
|
445
|
+
load: loadRivusHome,
|
|
446
|
+
setup: initializeRivusHome
|
|
447
|
+
};
|
|
517
448
|
}
|
|
518
449
|
//#endregion
|
|
519
|
-
//#region src/
|
|
520
|
-
const
|
|
450
|
+
//#region src/adapters/cli/command/rivus-cli-protocol.ts
|
|
451
|
+
const RIVUS_CLI_USAGE = `Usage:
|
|
521
452
|
rivus setup [directory]
|
|
522
453
|
rivus start
|
|
523
454
|
rivus status
|
|
524
455
|
rivus check-config
|
|
525
456
|
rivus init [directory]
|
|
526
457
|
rivus doctor [directory] [--env-file <path>]
|
|
458
|
+
rivus model <status|set|rollback> ...
|
|
527
459
|
rivus --bootstrap <module> [--manifest <rivus.config.json>] [options]
|
|
528
460
|
|
|
529
461
|
Commands:
|
|
@@ -534,126 +466,48 @@ Commands:
|
|
|
534
466
|
Validate and print the redacted Rivus Home manifest
|
|
535
467
|
init Create a standalone local Rivus project without overwriting files
|
|
536
468
|
doctor Check Rivus Home by default, or an explicit standalone project directory
|
|
469
|
+
model Query or change the managed default model through the current Home
|
|
537
470
|
|
|
538
471
|
Run rivus --help for the complete daemon option list.
|
|
539
472
|
`;
|
|
540
|
-
function
|
|
541
|
-
|
|
542
|
-
if (command === "setup") return runSetupCommand(options);
|
|
543
|
-
if (command === "start") return runHomeDaemonCommand(options, []);
|
|
544
|
-
if (command === "status") return runHomeDaemonCommand(options, ["--status"]);
|
|
545
|
-
if (command === "check-config") return runHomeDaemonCommand(options, ["--check-config"]);
|
|
546
|
-
if (command === "init") return runInitCommand(options);
|
|
547
|
-
if (command === "doctor") return runDoctorCommand(options);
|
|
548
|
-
if (command && !command.startsWith("-")) return Effect.sync(() => {
|
|
549
|
-
options.stderr.write(`Unknown command: ${command}\n\n${USAGE}`);
|
|
550
|
-
return 1;
|
|
551
|
-
});
|
|
552
|
-
return runRivusDaemonCli(options);
|
|
473
|
+
function renderRivusCliError(error) {
|
|
474
|
+
return `${error instanceof Error ? error.message : String(error)}\n`;
|
|
553
475
|
}
|
|
554
|
-
function
|
|
555
|
-
return
|
|
476
|
+
function renderRivusCliUnknownCommand(command) {
|
|
477
|
+
return `Unknown command: ${command}\n\n${RIVUS_CLI_USAGE}`;
|
|
556
478
|
}
|
|
557
|
-
function
|
|
558
|
-
|
|
559
|
-
options.stderr.write(`Usage: rivus ${options.argv[0]}\n`);
|
|
560
|
-
return 1;
|
|
561
|
-
});
|
|
562
|
-
return resolveHomeDirectoryEffect(options).pipe(Effect.flatMap((directory) => options.homeApi.load(directory)), Effect.tap((home) => Effect.sync(() => options.changeWorkingDirectory(home.workspaceDirectory))), Effect.flatMap((home) => runRivusDaemonCli({
|
|
563
|
-
...options,
|
|
564
|
-
argv: [
|
|
565
|
-
"--env-file",
|
|
566
|
-
home.envFilePath,
|
|
567
|
-
"--bootstrap",
|
|
568
|
-
home.bootstrap,
|
|
569
|
-
"--manifest",
|
|
570
|
-
home.manifestPath,
|
|
571
|
-
...daemonArgs
|
|
572
|
-
],
|
|
573
|
-
env: {
|
|
574
|
-
...options.env,
|
|
575
|
-
RIVUS_DEPLOYMENT_STATE_DIR: home.stateDirectory,
|
|
576
|
-
RIVUS_HOME: home.directory
|
|
577
|
-
},
|
|
578
|
-
pluginPackageManifestPath: options.packageManifestPath
|
|
579
|
-
})), Effect.catchAll((error) => writeError(options, error)));
|
|
479
|
+
function renderRivusDirectoryCommandUsage(command) {
|
|
480
|
+
return `Usage: rivus ${command} [directory]\n`;
|
|
580
481
|
}
|
|
581
|
-
function
|
|
582
|
-
return
|
|
583
|
-
const directory = resolve(options.cwd, argument ?? ".");
|
|
584
|
-
return Effect.tryPromise({
|
|
585
|
-
try: () => initializeRivusProject({
|
|
586
|
-
directory,
|
|
587
|
-
nodeExecutable: options.nodeExecutable,
|
|
588
|
-
packageManifestPath: options.packageManifestPath,
|
|
589
|
-
templateDirectory: options.templateDirectory
|
|
590
|
-
}),
|
|
591
|
-
catch: (error) => error
|
|
592
|
-
}).pipe(Effect.tap((result) => Effect.sync(() => options.stdout.write(`Initialized Rivus project in ${result.directory}\n\nNext:\n cd ${shellQuote(result.directory)}\n npm install\n cp .env.example .env.local\n npm run doctor\n npm start\n`))), Effect.as(0), Effect.catchAll((error) => writeError(options, error)));
|
|
593
|
-
});
|
|
482
|
+
function renderRivusHomeCommandUsage(command) {
|
|
483
|
+
return `Usage: rivus ${command}\n`;
|
|
594
484
|
}
|
|
595
|
-
function
|
|
596
|
-
|
|
597
|
-
const usage = `Usage: rivus ${command} [directory]\n`;
|
|
598
|
-
if (args.includes("--help") || args.includes("-h")) return Effect.sync(() => {
|
|
599
|
-
options.stdout.write(usage);
|
|
600
|
-
return 0;
|
|
601
|
-
});
|
|
602
|
-
if (args.length > 1 || args[0]?.startsWith("-")) return Effect.sync(() => {
|
|
603
|
-
options.stderr.write(usage);
|
|
604
|
-
return 1;
|
|
605
|
-
});
|
|
606
|
-
return run(args[0]);
|
|
485
|
+
function renderRivusDoctorUsage() {
|
|
486
|
+
return "Usage: rivus doctor [directory] [--env-file <path>]\n";
|
|
607
487
|
}
|
|
608
|
-
function
|
|
609
|
-
|
|
610
|
-
if (parsed.help) return Effect.sync(() => {
|
|
611
|
-
options.stdout.write("Usage: rivus doctor [directory] [--env-file <path>]\n");
|
|
612
|
-
return 0;
|
|
613
|
-
});
|
|
614
|
-
if (parsed.error) return Effect.sync(() => {
|
|
615
|
-
options.stderr.write(`${parsed.error}\nUsage: rivus doctor [directory] [--env-file <path>]\n`);
|
|
616
|
-
return 1;
|
|
617
|
-
});
|
|
618
|
-
if (parsed.directory === void 0) return resolveHomeDirectoryEffect(options).pipe(Effect.flatMap((directory) => options.homeApi.diagnose({
|
|
619
|
-
directory,
|
|
620
|
-
env: options.env,
|
|
621
|
-
...parsed.envFilePath ? { envFilePath: parsed.envFilePath } : {},
|
|
622
|
-
nodeVersion: options.nodeVersion
|
|
623
|
-
})), Effect.map((report) => writeDoctorReport(options.stdout, report, "Home")), Effect.catchAll((error) => writeError(options, error)));
|
|
624
|
-
const projectDirectory = parsed.directory;
|
|
625
|
-
return diagnoseRivusProject({
|
|
626
|
-
directory: resolve(options.cwd, projectDirectory),
|
|
627
|
-
env: options.env,
|
|
628
|
-
...parsed.envFilePath ? { envFilePath: parsed.envFilePath } : {},
|
|
629
|
-
nodeVersion: options.nodeVersion
|
|
630
|
-
}).pipe(Effect.map((report) => writeDoctorReport(options.stdout, report, "project")), Effect.catchAll((error) => writeError(options, error)));
|
|
488
|
+
function renderRivusDoctorArgumentError(error) {
|
|
489
|
+
return `${error}\n${renderRivusDoctorUsage()}`;
|
|
631
490
|
}
|
|
632
|
-
function
|
|
633
|
-
return
|
|
634
|
-
try: () => {
|
|
635
|
-
if (argument) return resolve(options.cwd, argument);
|
|
636
|
-
const configured = options.env.RIVUS_HOME?.trim();
|
|
637
|
-
if (!configured) return resolve(options.homeDirectory, ".rivus-agent");
|
|
638
|
-
if (!isAbsolute(configured)) throw new Error("RIVUS_HOME must be an absolute path");
|
|
639
|
-
return resolve(configured);
|
|
640
|
-
},
|
|
641
|
-
catch: (error) => error instanceof Error ? error : new Error(String(error))
|
|
642
|
-
});
|
|
491
|
+
function renderRivusSetupSuccess(directory) {
|
|
492
|
+
return `Initialized Rivus Home in ${directory}\n\nNext:\n cp ${shellQuote(`${directory}/.env.example`)} ${shellQuote(`${directory}/.env`)}\n rivus doctor\n rivus start\n`;
|
|
643
493
|
}
|
|
644
|
-
function
|
|
645
|
-
return
|
|
646
|
-
options.stderr.write(`${formatError(error)}\n`);
|
|
647
|
-
return 1;
|
|
648
|
-
});
|
|
494
|
+
function renderRivusProjectInitializationSuccess(directory) {
|
|
495
|
+
return `Initialized Rivus project in ${directory}\n\nNext:\n cd ${shellQuote(directory)}\n npm install\n cp .env.example .env.local\n npm run doctor\n npm start\n`;
|
|
649
496
|
}
|
|
650
|
-
function
|
|
651
|
-
|
|
652
|
-
for (const check of report.checks)
|
|
653
|
-
|
|
654
|
-
|
|
497
|
+
function* renderRivusDoctorReport(report, owner) {
|
|
498
|
+
yield `Rivus doctor: ${report.directory}\n`;
|
|
499
|
+
for (const check of report.checks) yield `${check.status === "pass" ? "PASS" : "FAIL"} ${check.name}: ${check.message}\n`;
|
|
500
|
+
yield report.ready ? `Rivus ${owner} is ready\n` : `Rivus ${owner} is not ready\n`;
|
|
501
|
+
}
|
|
502
|
+
function parseRivusDirectoryArguments(argv) {
|
|
503
|
+
if (argv.includes("--help") || argv.includes("-h")) return { help: true };
|
|
504
|
+
if (argv.length > 1 || argv[0]?.startsWith("-")) return { error: "invalid directory arguments" };
|
|
505
|
+
return argv[0] === void 0 ? {} : { directory: argv[0] };
|
|
655
506
|
}
|
|
656
|
-
function
|
|
507
|
+
function hasRivusHomeCommandArguments(argv) {
|
|
508
|
+
return argv.length > 0;
|
|
509
|
+
}
|
|
510
|
+
function parseRivusDoctorArguments(argv) {
|
|
657
511
|
let directory;
|
|
658
512
|
let envFilePath;
|
|
659
513
|
for (let index = 0; index < argv.length; index += 1) {
|
|
@@ -680,357 +534,706 @@ function parseDoctorArguments(argv) {
|
|
|
680
534
|
...envFilePath !== void 0 ? { envFilePath } : {}
|
|
681
535
|
};
|
|
682
536
|
}
|
|
683
|
-
function formatError(error) {
|
|
684
|
-
return error instanceof Error ? error.message : String(error);
|
|
685
|
-
}
|
|
686
537
|
function shellQuote(value) {
|
|
687
538
|
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
688
539
|
}
|
|
689
540
|
//#endregion
|
|
690
|
-
//#region src/
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
541
|
+
//#region src/adapters/cli/model/rivus-model-management-socket-client.ts
|
|
542
|
+
const MAX_FRAME_BYTES = 64 * 1024;
|
|
543
|
+
const DEFAULT_TIMEOUT_MS = 1e4;
|
|
544
|
+
var RivusModelManagementTransportError = class extends Error {
|
|
545
|
+
code;
|
|
546
|
+
constructor(code, message, options) {
|
|
547
|
+
super(message, options);
|
|
548
|
+
this.name = "RivusModelManagementTransportError";
|
|
549
|
+
this.code = code;
|
|
550
|
+
}
|
|
551
|
+
};
|
|
552
|
+
function createRivusModelManagementSocketClient(options) {
|
|
553
|
+
if (!isAbsolute(options.socketPath)) throw new Error("model socket path must be absolute");
|
|
554
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_TIMEOUT_MS;
|
|
555
|
+
return { execute: (command) => request(options.socketPath, options.env, command, timeoutMs) };
|
|
556
|
+
}
|
|
557
|
+
async function request(socketPath, env, command, timeoutMs) {
|
|
558
|
+
const payload = createRivusModelManagementWireRequest(command, env);
|
|
559
|
+
return new Promise((resolve, reject) => {
|
|
560
|
+
const socket = createConnection(socketPath);
|
|
561
|
+
let response = "";
|
|
562
|
+
let settled = false;
|
|
563
|
+
const finish = (callback) => {
|
|
564
|
+
if (settled) return;
|
|
565
|
+
settled = true;
|
|
566
|
+
callback();
|
|
567
|
+
};
|
|
568
|
+
socket.setEncoding("utf8");
|
|
569
|
+
socket.setTimeout(timeoutMs, () => {
|
|
570
|
+
finish(() => reject(new RivusModelManagementTransportError("timeout", "model management socket timed out")));
|
|
571
|
+
socket.destroy();
|
|
572
|
+
});
|
|
573
|
+
socket.on("connect", () => socket.write(`${JSON.stringify(payload)}\n`));
|
|
574
|
+
socket.on("data", (chunk) => {
|
|
575
|
+
response += chunk;
|
|
576
|
+
if (Buffer.byteLength(response, "utf8") > MAX_FRAME_BYTES) {
|
|
577
|
+
finish(() => reject(new RivusModelManagementTransportError("invalid_response", "model response is too large")));
|
|
578
|
+
socket.destroy();
|
|
579
|
+
}
|
|
580
|
+
});
|
|
581
|
+
socket.on("error", (error) => {
|
|
582
|
+
finish(() => reject(new RivusModelManagementTransportError("socket_error", "model management socket is unavailable", { cause: error })));
|
|
583
|
+
});
|
|
584
|
+
socket.on("end", () => {
|
|
585
|
+
const line = response.split("\n", 1)[0]?.trim();
|
|
586
|
+
if (!line) {
|
|
587
|
+
finish(() => reject(new RivusModelManagementTransportError("invalid_response", "model response was empty")));
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
try {
|
|
591
|
+
const parsed = JSON.parse(line);
|
|
592
|
+
if (!isResponse(parsed)) throw new Error("model response must be a JSON object");
|
|
593
|
+
finish(() => resolve(parsed));
|
|
594
|
+
} catch (error) {
|
|
595
|
+
finish(() => reject(new RivusModelManagementTransportError("invalid_response", "model response was not valid JSON", { cause: error })));
|
|
596
|
+
}
|
|
597
|
+
});
|
|
711
598
|
});
|
|
712
599
|
}
|
|
713
|
-
function
|
|
714
|
-
|
|
715
|
-
if (config.version !== 1) throw new Error("Rivus Home config version must be 1");
|
|
716
|
-
return {
|
|
717
|
-
bootstrap: nonEmptyString(config.bootstrap, "bootstrap"),
|
|
718
|
-
envFile: relativePath(config.envFile, "envFile"),
|
|
719
|
-
logs: relativePath(config.logs, "logs"),
|
|
720
|
-
manifest: relativePath(config.manifest, "manifest"),
|
|
721
|
-
state: relativePath(config.state, "state"),
|
|
722
|
-
version: 1,
|
|
723
|
-
workspace: relativePath(config.workspace, "workspace")
|
|
724
|
-
};
|
|
725
|
-
}
|
|
726
|
-
function resolveBootstrap(directory, value) {
|
|
727
|
-
if (value.startsWith("./") || value.startsWith("../")) return resolveHomePath(directory, value, "bootstrap");
|
|
728
|
-
if (isAbsolute(value)) throw new Error("Rivus Home bootstrap must be a package specifier or a relative path inside Rivus Home");
|
|
729
|
-
return value;
|
|
730
|
-
}
|
|
731
|
-
function resolveHomePath(directory, value, owner) {
|
|
732
|
-
const candidate = resolve(directory, value);
|
|
733
|
-
const relation = relative(directory, candidate);
|
|
734
|
-
if (relation === "" || !relation.startsWith(`..${sep}`) && relation !== ".." && !isAbsolute(relation)) return candidate;
|
|
735
|
-
throw new Error(`Rivus Home ${owner} escapes the Home directory`);
|
|
600
|
+
function isResponse(value) {
|
|
601
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
736
602
|
}
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
603
|
+
//#endregion
|
|
604
|
+
//#region src/adapters/cli/model/rivus-model-socket.ts
|
|
605
|
+
const RIVUS_MODEL_SOCKET_ENV = "RIVUS_MODEL_SOCKET";
|
|
606
|
+
const RIVUS_MODEL_MANAGEMENT_ENABLED_ENV = "RIVUS_MODEL_MANAGEMENT_ENABLED";
|
|
607
|
+
const RIVUS_MODEL_SOCKET_RELATIVE_PATH = "state/model-management/control.sock";
|
|
608
|
+
function resolveRivusModelSocketPath(options) {
|
|
609
|
+
const configured = optional(options.env[RIVUS_MODEL_SOCKET_ENV]);
|
|
610
|
+
if (configured) {
|
|
611
|
+
if (!isAbsolute(configured)) throw new Error(`${RIVUS_MODEL_SOCKET_ENV} must be an absolute path`);
|
|
612
|
+
return resolve(configured);
|
|
613
|
+
}
|
|
614
|
+
const configuredHome = optional(options.env.RIVUS_HOME);
|
|
615
|
+
return join(configuredHome ? resolveAbsoluteHome(configuredHome) : resolve(options.homeDirectory, ".rivus-agent"), RIVUS_MODEL_SOCKET_RELATIVE_PATH);
|
|
741
616
|
}
|
|
742
|
-
function
|
|
743
|
-
|
|
744
|
-
return value;
|
|
617
|
+
function isRivusModelManagementEnabled(env) {
|
|
618
|
+
const value = optional(env[RIVUS_MODEL_MANAGEMENT_ENABLED_ENV]);
|
|
619
|
+
return value === "1" || value === "true";
|
|
745
620
|
}
|
|
746
|
-
function
|
|
747
|
-
if (
|
|
748
|
-
return value;
|
|
621
|
+
function resolveAbsoluteHome(value) {
|
|
622
|
+
if (!isAbsolute(value)) throw new Error("RIVUS_HOME must be an absolute path");
|
|
623
|
+
return resolve(value);
|
|
749
624
|
}
|
|
750
|
-
function
|
|
751
|
-
return
|
|
625
|
+
function optional(value) {
|
|
626
|
+
return value?.trim() || void 0;
|
|
752
627
|
}
|
|
753
628
|
//#endregion
|
|
754
|
-
//#region src/
|
|
755
|
-
|
|
629
|
+
//#region src/adapters/deployment/inspection/rivus-project-doctor.ts
|
|
630
|
+
const REQUIRED_FILES = Object.freeze([
|
|
631
|
+
"package.json",
|
|
632
|
+
"rivus.bootstrap.ts",
|
|
633
|
+
"rivus.config.json"
|
|
634
|
+
]);
|
|
635
|
+
const REQUIRED_DEPENDENCIES = Object.freeze([
|
|
636
|
+
"@rivus/agent",
|
|
637
|
+
"@earendil-works/pi-coding-agent",
|
|
638
|
+
"@larksuiteoapi/node-sdk"
|
|
639
|
+
]);
|
|
640
|
+
function diagnoseRivusProject(options) {
|
|
756
641
|
return Effect.gen(function* () {
|
|
757
|
-
const
|
|
758
|
-
const
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
}
|
|
767
|
-
const
|
|
768
|
-
checks.push(
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
});
|
|
779
|
-
checks.push(deployment.manifest, deployment.modules, deployment.credentials);
|
|
780
|
-
return report(input.directory, checks);
|
|
642
|
+
const directory = resolve(options.directory);
|
|
643
|
+
const checks = [checkNode(options.nodeVersion)];
|
|
644
|
+
checks.push(yield* Effect.tryPromise({
|
|
645
|
+
try: () => checkFiles(directory),
|
|
646
|
+
catch: toError
|
|
647
|
+
}));
|
|
648
|
+
checks.push(yield* Effect.tryPromise({
|
|
649
|
+
try: () => checkDependencies(directory),
|
|
650
|
+
catch: toError
|
|
651
|
+
}));
|
|
652
|
+
const manifestResult = yield* checkManifest(directory);
|
|
653
|
+
checks.push(manifestResult.check);
|
|
654
|
+
const envFilePath = resolve(directory, options.envFilePath ?? ".env.local");
|
|
655
|
+
checks.push(yield* Effect.tryPromise({
|
|
656
|
+
try: () => checkCredentials(manifestResult.manifest, envFilePath, options.env),
|
|
657
|
+
catch: toError
|
|
658
|
+
}));
|
|
659
|
+
return Object.freeze({
|
|
660
|
+
checks: Object.freeze(checks),
|
|
661
|
+
directory,
|
|
662
|
+
ready: checks.every(({ status }) => status === "pass")
|
|
663
|
+
});
|
|
781
664
|
});
|
|
782
665
|
}
|
|
783
666
|
function checkNode(version) {
|
|
784
667
|
const [major, minor] = version.split(".").map(Number);
|
|
785
|
-
|
|
668
|
+
if (major === 24 && Number.isInteger(minor) && minor >= 11) return {
|
|
786
669
|
message: `Node.js ${version} satisfies the supported ^24.11 runtime`,
|
|
787
670
|
name: "node",
|
|
788
671
|
status: "pass"
|
|
789
|
-
}
|
|
672
|
+
};
|
|
673
|
+
return {
|
|
790
674
|
message: `Node.js 24.11 or newer on major 24 is required; current version is ${version}`,
|
|
791
675
|
name: "node",
|
|
792
676
|
status: "fail"
|
|
793
677
|
};
|
|
794
678
|
}
|
|
795
|
-
function
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
679
|
+
async function checkFiles(directory) {
|
|
680
|
+
const missing = await missingRegularFiles(directory, REQUIRED_FILES);
|
|
681
|
+
return missing.length === 0 ? {
|
|
682
|
+
message: "required project files are present",
|
|
683
|
+
name: "files",
|
|
799
684
|
status: "pass"
|
|
800
685
|
} : {
|
|
801
|
-
message: `
|
|
802
|
-
name: "
|
|
686
|
+
message: `required project files are missing: ${missing.join(", ")}`,
|
|
687
|
+
name: "files",
|
|
803
688
|
status: "fail"
|
|
804
|
-
}
|
|
689
|
+
};
|
|
805
690
|
}
|
|
806
|
-
function
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
691
|
+
async function checkDependencies(directory) {
|
|
692
|
+
const packageFiles = REQUIRED_DEPENDENCIES.map((name) => join("node_modules", ...name.split("/"), "package.json"));
|
|
693
|
+
const missingIndexes = new Set((await missingRegularFiles(directory, packageFiles)).map((path) => packageFiles.indexOf(path)));
|
|
694
|
+
const missing = REQUIRED_DEPENDENCIES.filter((_, index) => missingIndexes.has(index));
|
|
695
|
+
return missing.length === 0 ? {
|
|
696
|
+
message: "Rivus, Pi, and Feishu dependencies are installed",
|
|
697
|
+
name: "dependencies",
|
|
698
|
+
status: "pass"
|
|
699
|
+
} : {
|
|
700
|
+
message: `run npm install; missing local dependencies: ${missing.join(", ")}`,
|
|
701
|
+
name: "dependencies",
|
|
810
702
|
status: "fail"
|
|
811
703
|
};
|
|
812
704
|
}
|
|
813
|
-
function
|
|
814
|
-
return {
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
705
|
+
function checkManifest(directory) {
|
|
706
|
+
return loadRivusDeploymentManifest(join(directory, "rivus.config.json")).pipe(Effect.flatMap((manifest) => Effect.gen(function* () {
|
|
707
|
+
validateRivusDeploymentManifest(manifest);
|
|
708
|
+
const missingModules = [];
|
|
709
|
+
for (const plugin of manifest.plugins) {
|
|
710
|
+
const result = yield* resolveNodeRivusPluginModulePath({
|
|
711
|
+
deploymentRoot: directory,
|
|
712
|
+
module: plugin.module,
|
|
713
|
+
pluginId: plugin.id
|
|
714
|
+
}).pipe(Effect.either);
|
|
715
|
+
if (Either.isLeft(result)) missingModules.push(plugin.module);
|
|
716
|
+
}
|
|
717
|
+
if (missingModules.length > 0) return { check: {
|
|
718
|
+
message: `manifest Plugin modules are missing: ${missingModules.join(", ")}`,
|
|
719
|
+
name: "manifest",
|
|
720
|
+
status: "fail"
|
|
721
|
+
} };
|
|
722
|
+
return {
|
|
723
|
+
check: {
|
|
724
|
+
message: "deployment manifest is valid",
|
|
725
|
+
name: "manifest",
|
|
726
|
+
status: "pass"
|
|
727
|
+
},
|
|
728
|
+
manifest
|
|
729
|
+
};
|
|
730
|
+
})), Effect.catchAll((error) => Effect.succeed({ check: {
|
|
731
|
+
message: `deployment manifest is invalid: ${error.message}`,
|
|
732
|
+
name: "manifest",
|
|
733
|
+
status: "fail"
|
|
734
|
+
} })));
|
|
735
|
+
}
|
|
736
|
+
async function checkCredentials(manifest, envFilePath, env) {
|
|
737
|
+
let mergedEnv;
|
|
738
|
+
try {
|
|
739
|
+
mergedEnv = await loadMergedLocalEnvFile(envFilePath, env);
|
|
740
|
+
} catch (error) {
|
|
741
|
+
return {
|
|
742
|
+
message: `required env file is unavailable at ${envFilePath}: ${error instanceof Error ? error.message : String(error)}`,
|
|
743
|
+
name: "credentials",
|
|
744
|
+
status: "fail"
|
|
745
|
+
};
|
|
746
|
+
}
|
|
747
|
+
if (!manifest) return {
|
|
748
|
+
message: "enabled Endpoint credentials cannot be checked until the deployment manifest is valid",
|
|
749
|
+
name: "credentials",
|
|
750
|
+
status: "fail"
|
|
818
751
|
};
|
|
752
|
+
try {
|
|
753
|
+
for (const endpoint of manifest.endpoints.filter(({ enabled }) => enabled)) resolveFeishuEndpointCredentials(endpoint.credentialRef, mergedEnv);
|
|
754
|
+
return {
|
|
755
|
+
message: "enabled Endpoint credential references resolve",
|
|
756
|
+
name: "credentials",
|
|
757
|
+
status: "pass"
|
|
758
|
+
};
|
|
759
|
+
} catch (error) {
|
|
760
|
+
return {
|
|
761
|
+
message: `enabled Endpoint credentials are incomplete: ${error instanceof Error ? error.message : String(error)}`,
|
|
762
|
+
name: "credentials",
|
|
763
|
+
status: "fail"
|
|
764
|
+
};
|
|
765
|
+
}
|
|
766
|
+
}
|
|
767
|
+
async function missingRegularFiles(directory, paths) {
|
|
768
|
+
const missing = [];
|
|
769
|
+
for (const path of paths) try {
|
|
770
|
+
if (!(await stat(resolve(directory, path))).isFile()) missing.push(path);
|
|
771
|
+
} catch (error) {
|
|
772
|
+
if (!isMissingPath$1(error)) throw error;
|
|
773
|
+
missing.push(path);
|
|
774
|
+
}
|
|
775
|
+
return missing;
|
|
776
|
+
}
|
|
777
|
+
function isMissingPath$1(error) {
|
|
778
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
779
|
+
}
|
|
780
|
+
function toError(error) {
|
|
781
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
819
782
|
}
|
|
820
783
|
//#endregion
|
|
821
|
-
//#region src/platform/
|
|
822
|
-
const
|
|
823
|
-
"
|
|
824
|
-
"
|
|
825
|
-
"
|
|
826
|
-
"
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
]
|
|
833
|
-
|
|
784
|
+
//#region src/platform/project/setup/rivus-project-initializer.ts
|
|
785
|
+
const TEMPLATE_FILES = Object.freeze({
|
|
786
|
+
"current-weather.mjs": "current-weather.mjs",
|
|
787
|
+
"https-response-reader.mjs": "https-response-reader.mjs",
|
|
788
|
+
"rivus-agents.plugin.mjs": "rivus-starter.plugin.mjs",
|
|
789
|
+
"rivus.bootstrap.ts": "pi-feishu-deployment.bootstrap.ts"
|
|
790
|
+
});
|
|
791
|
+
async function initializeRivusProject(options) {
|
|
792
|
+
const directory = resolve(options.directory);
|
|
793
|
+
const manifest = await readPackageManifest(options.packageManifestPath);
|
|
794
|
+
const files = /* @__PURE__ */ new Map([
|
|
795
|
+
[".env.example", environmentTemplate()],
|
|
796
|
+
[".gitignore", ".env.local\n.rivus/\nnode_modules/\n"],
|
|
797
|
+
["deploy/launchd/com.rivus.agent.plist", launchdService(directory, options.nodeExecutable)],
|
|
798
|
+
["deploy/systemd/rivus.service", systemdService(directory, options.nodeExecutable)],
|
|
799
|
+
["package.json", projectPackageJson(directory, manifest)],
|
|
800
|
+
["rivus.config.json", deploymentManifest()]
|
|
801
|
+
]);
|
|
802
|
+
for (const [target, source] of Object.entries(TEMPLATE_FILES)) files.set(target, await readFile(join(options.templateDirectory, source), "utf8"));
|
|
803
|
+
const paths = [...files.keys()].sort();
|
|
804
|
+
await assertSafeProjectAncestors(directory, paths);
|
|
805
|
+
const conflicts = await findConflicts(directory, paths);
|
|
806
|
+
if (conflicts.length > 0) throw new Error(`Rivus project initialization refused; refusing to overwrite: ${conflicts.join(", ")}`);
|
|
807
|
+
const createdDirectories = [];
|
|
808
|
+
const createdFiles = [];
|
|
809
|
+
const writeProjectFile = options.writeProjectFile ?? writeExclusiveProjectFile;
|
|
810
|
+
try {
|
|
811
|
+
for (const path of paths) {
|
|
812
|
+
const destination = join(directory, path);
|
|
813
|
+
await ensureDirectory(dirname(destination), directory, createdDirectories);
|
|
814
|
+
await assertSafeProjectAncestors(directory, [path]);
|
|
815
|
+
await writeProjectFile(destination, files.get(path));
|
|
816
|
+
createdFiles.push(destination);
|
|
817
|
+
}
|
|
818
|
+
} catch (error) {
|
|
819
|
+
const rollbackErrors = await rollbackCreatedPaths(createdFiles, createdDirectories);
|
|
820
|
+
if (rollbackErrors.length > 0) throw new AggregateError([error, ...rollbackErrors], "Rivus project initialization failed and rollback was incomplete");
|
|
821
|
+
throw error;
|
|
822
|
+
}
|
|
834
823
|
return {
|
|
835
|
-
|
|
836
|
-
files:
|
|
837
|
-
[".env.example", environmentTemplate()],
|
|
838
|
-
[".gitignore", ".DS_Store\n.env\nlogs/\nstate/\nworkspace/work/tmp/\n"],
|
|
839
|
-
["config.json", homeConfig()],
|
|
840
|
-
["rivus.config.json", deploymentManifest()],
|
|
841
|
-
["workspace/AGENTS.md", agentsTemplate()],
|
|
842
|
-
["workspace/IDENTITY.md", "# Identity\n\nName: Rivus\n\nRole: Personal agent\n"],
|
|
843
|
-
["workspace/MEMORY.md", "# Curated Memory\n\nStore confirmed, durable facts and decisions here.\n"],
|
|
844
|
-
["workspace/SOUL.md", "# Character\n\nBe direct, thoughtful, and evidence-led.\n"],
|
|
845
|
-
["workspace/USER.md", "# User\n\nRecord stable user preferences here.\n"],
|
|
846
|
-
["plugins/.gitkeep", ""],
|
|
847
|
-
["workspace/memory/.gitkeep", ""],
|
|
848
|
-
["workspace/skills/.gitkeep", ""],
|
|
849
|
-
["workspace/work/artifacts/.gitkeep", ""],
|
|
850
|
-
["workspace/work/drafts/.gitkeep", ""],
|
|
851
|
-
["workspace/work/inbox/.gitkeep", ""]
|
|
852
|
-
])
|
|
824
|
+
directory,
|
|
825
|
+
files: Object.freeze(paths)
|
|
853
826
|
};
|
|
854
827
|
}
|
|
855
|
-
function
|
|
828
|
+
async function writeExclusiveProjectFile(path, contents) {
|
|
829
|
+
await writeFile(path, contents, {
|
|
830
|
+
encoding: "utf8",
|
|
831
|
+
flag: "wx"
|
|
832
|
+
});
|
|
833
|
+
}
|
|
834
|
+
async function assertSafeProjectAncestors(directory, paths) {
|
|
835
|
+
const rootState = await lstatOrUndefined(directory);
|
|
836
|
+
if (rootState?.isSymbolicLink()) throw new Error("Rivus project initialization refused; symbolic link ancestor: .");
|
|
837
|
+
if (rootState && !rootState.isDirectory()) throw new Error("Rivus project initialization refused; project path is not a directory");
|
|
838
|
+
if (!rootState) return;
|
|
839
|
+
for (const path of paths) {
|
|
840
|
+
let current = directory;
|
|
841
|
+
for (const segment of path.split("/").slice(0, -1)) {
|
|
842
|
+
current = join(current, segment);
|
|
843
|
+
const state = await lstatOrUndefined(current);
|
|
844
|
+
if (!state) break;
|
|
845
|
+
if (state.isSymbolicLink()) throw new Error(`Rivus project initialization refused; symbolic link ancestor: ${relative(directory, current)}`);
|
|
846
|
+
if (!state.isDirectory()) throw new Error(`Rivus project initialization refused; non-directory ancestor: ${relative(directory, current)}`);
|
|
847
|
+
}
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
async function ensureDirectory(path, projectRoot, createdDirectories) {
|
|
851
|
+
try {
|
|
852
|
+
await mkdir(path);
|
|
853
|
+
createdDirectories.push(path);
|
|
854
|
+
} catch (error) {
|
|
855
|
+
if (isMissingPath(error)) {
|
|
856
|
+
const parent = dirname(path);
|
|
857
|
+
if (parent === path) throw error;
|
|
858
|
+
await ensureDirectory(parent, projectRoot, createdDirectories);
|
|
859
|
+
await ensureDirectory(path, projectRoot, createdDirectories);
|
|
860
|
+
return;
|
|
861
|
+
}
|
|
862
|
+
if (!isAlreadyExists(error)) throw error;
|
|
863
|
+
const state = await lstat(path);
|
|
864
|
+
if (state.isSymbolicLink()) {
|
|
865
|
+
if (isPathWithin(projectRoot, path)) throw new Error(`Rivus project initialization refused; symbolic link ancestor: ${relative(projectRoot, path) || "."}`);
|
|
866
|
+
if ((await stat(path)).isDirectory()) return;
|
|
867
|
+
}
|
|
868
|
+
if (!state.isDirectory()) throw new Error(`Rivus project initialization refused; non-directory ancestor: ${path}`);
|
|
869
|
+
}
|
|
870
|
+
}
|
|
871
|
+
async function rollbackCreatedPaths(files, directories) {
|
|
872
|
+
const errors = [];
|
|
873
|
+
for (const path of files.reverse()) try {
|
|
874
|
+
await unlink(path);
|
|
875
|
+
} catch (error) {
|
|
876
|
+
if (!isMissingPath(error)) errors.push(asError(error));
|
|
877
|
+
}
|
|
878
|
+
for (const path of directories.reverse()) try {
|
|
879
|
+
await rmdir(path);
|
|
880
|
+
} catch (error) {
|
|
881
|
+
if (!isMissingPath(error) && !isDirectoryNotEmpty(error)) errors.push(asError(error));
|
|
882
|
+
}
|
|
883
|
+
return errors;
|
|
884
|
+
}
|
|
885
|
+
async function lstatOrUndefined(path) {
|
|
886
|
+
try {
|
|
887
|
+
return await lstat(path);
|
|
888
|
+
} catch (error) {
|
|
889
|
+
if (isMissingPath(error)) return void 0;
|
|
890
|
+
throw error;
|
|
891
|
+
}
|
|
892
|
+
}
|
|
893
|
+
function isAlreadyExists(error) {
|
|
894
|
+
return error instanceof Error && "code" in error && error.code === "EEXIST";
|
|
895
|
+
}
|
|
896
|
+
function isDirectoryNotEmpty(error) {
|
|
897
|
+
return error instanceof Error && "code" in error && error.code === "ENOTEMPTY";
|
|
898
|
+
}
|
|
899
|
+
function asError(error) {
|
|
900
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
901
|
+
}
|
|
902
|
+
async function readPackageManifest(path) {
|
|
903
|
+
const manifest = JSON.parse(await readFile(path, "utf8"));
|
|
904
|
+
if (manifest.name !== "@rivus/agent" || typeof manifest.version !== "string") throw new Error("Rivus package manifest is missing its release identity");
|
|
905
|
+
return manifest;
|
|
906
|
+
}
|
|
907
|
+
async function findConflicts(directory, paths) {
|
|
908
|
+
const conflicts = [];
|
|
909
|
+
for (const path of paths) try {
|
|
910
|
+
await lstat(join(directory, path));
|
|
911
|
+
conflicts.push(path);
|
|
912
|
+
} catch (error) {
|
|
913
|
+
if (!isMissingPath(error)) throw error;
|
|
914
|
+
}
|
|
915
|
+
return conflicts;
|
|
916
|
+
}
|
|
917
|
+
function isMissingPath(error) {
|
|
918
|
+
return error instanceof Error && "code" in error && error.code === "ENOENT";
|
|
919
|
+
}
|
|
920
|
+
function projectPackageJson(directory, manifest) {
|
|
921
|
+
const pi = manifest.dependencies?.["@earendil-works/pi-coding-agent"];
|
|
922
|
+
const lark = manifest.dependencies?.["@larksuiteoapi/node-sdk"];
|
|
923
|
+
const effect = manifest.dependencies?.effect;
|
|
924
|
+
if (!pi || !lark || !effect) throw new Error("Rivus package manifest is missing its Effect, Pi, or Feishu dependency range");
|
|
925
|
+
const projectName = sanitizePackageName(basename(directory));
|
|
856
926
|
return `${JSON.stringify({
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
927
|
+
name: projectName,
|
|
928
|
+
private: true,
|
|
929
|
+
type: "module",
|
|
930
|
+
scripts: {
|
|
931
|
+
"check-config": "rivus --manifest ./rivus.config.json --check-config",
|
|
932
|
+
doctor: "rivus doctor .",
|
|
933
|
+
start: "rivus --env-file .env.local --bootstrap ./rivus.bootstrap.ts --manifest ./rivus.config.json"
|
|
934
|
+
},
|
|
935
|
+
dependencies: {
|
|
936
|
+
"@earendil-works/pi-coding-agent": pi,
|
|
937
|
+
"@larksuiteoapi/node-sdk": lark,
|
|
938
|
+
"@rivus/agent": `^${manifest.version}`,
|
|
939
|
+
effect
|
|
940
|
+
}
|
|
864
941
|
}, null, 2)}\n`;
|
|
865
942
|
}
|
|
943
|
+
function sanitizePackageName(value) {
|
|
944
|
+
return value.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^[._-]+|[._-]+$/g, "") || "rivus-app";
|
|
945
|
+
}
|
|
866
946
|
function deploymentManifest() {
|
|
867
947
|
return `${JSON.stringify({
|
|
948
|
+
plugins: [{
|
|
949
|
+
id: "rivus-starter",
|
|
950
|
+
module: "./rivus-agents.plugin.mjs",
|
|
951
|
+
required: true
|
|
952
|
+
}],
|
|
868
953
|
agents: [{
|
|
869
|
-
agentId: "
|
|
870
|
-
endpointIds: ["
|
|
871
|
-
memory: {
|
|
872
|
-
scopes: ["agent-private"],
|
|
873
|
-
tool: true
|
|
874
|
-
},
|
|
954
|
+
agentId: "agent-a",
|
|
955
|
+
endpointIds: ["feishu-agent-a"],
|
|
875
956
|
pluginId: "rivus-starter",
|
|
876
957
|
profileId: "agent-a",
|
|
877
|
-
projectSpaceId: "personal-home",
|
|
878
|
-
runtimeTools: { allow: [
|
|
879
|
-
"read",
|
|
880
|
-
"bash",
|
|
881
|
-
"edit",
|
|
882
|
-
"write",
|
|
883
|
-
"grep",
|
|
884
|
-
"find",
|
|
885
|
-
"ls"
|
|
886
|
-
] },
|
|
887
958
|
skills: { allow: [] },
|
|
888
959
|
tools: { allow: ["rivus-starter/current-weather"] }
|
|
889
960
|
}],
|
|
890
|
-
defaultAgentId: "
|
|
891
|
-
defaultEndpointId: "
|
|
961
|
+
defaultAgentId: "agent-a",
|
|
962
|
+
defaultEndpointId: "feishu-agent-a",
|
|
892
963
|
endpoints: [{
|
|
893
|
-
agentId: "
|
|
964
|
+
agentId: "agent-a",
|
|
894
965
|
baseUrl: "https://open.feishu.cn",
|
|
895
966
|
cardStreamLeaseMs: 51e4,
|
|
896
967
|
credentialRef: "env:RIVUS_FEISHU",
|
|
897
968
|
enabled: true,
|
|
898
969
|
experimental: { cotMessages: false },
|
|
899
970
|
groupPolicy: "mention-only",
|
|
900
|
-
id: "
|
|
971
|
+
id: "feishu-agent-a",
|
|
901
972
|
progressDisplay: "collapsed",
|
|
902
973
|
required: true,
|
|
903
|
-
sessionNamespace: "rivus-
|
|
974
|
+
sessionNamespace: "rivus-starter",
|
|
904
975
|
streamMinIntervalMs: 200
|
|
905
|
-
}],
|
|
906
|
-
plugins: [{
|
|
907
|
-
id: "rivus-starter",
|
|
908
|
-
module: "@rivus/agent/plugin/starter",
|
|
909
|
-
required: true
|
|
910
|
-
}],
|
|
911
|
-
projectSpaces: [{
|
|
912
|
-
id: "personal-home",
|
|
913
|
-
root: "workspace",
|
|
914
|
-
skills: { sources: ["skills"] },
|
|
915
|
-
workingDirectory: "."
|
|
916
976
|
}]
|
|
917
977
|
}, null, 2)}\n`;
|
|
918
978
|
}
|
|
919
979
|
function environmentTemplate() {
|
|
920
980
|
return [
|
|
921
|
-
"# Copy this file to .env and keep the real values untracked.",
|
|
981
|
+
"# Copy this file to .env.local and keep the real values untracked.",
|
|
922
982
|
"RIVUS_FEISHU_APP_ID=",
|
|
923
983
|
"RIVUS_FEISHU_APP_SECRET=",
|
|
924
984
|
"PI_MODEL=",
|
|
925
985
|
"PI_API_KEY=",
|
|
926
986
|
"# PI_BASE_URL=",
|
|
927
987
|
"RIVUS_WEATHER_DEFAULT_LOCATION=上海",
|
|
988
|
+
"# LANGFUSE_BASE_URL=https://jp.cloud.langfuse.com",
|
|
989
|
+
"# LANGFUSE_PUBLIC_KEY=",
|
|
990
|
+
"# LANGFUSE_SECRET_KEY=",
|
|
991
|
+
"# RIVUS_TELEMETRY_CONTENT=redacted",
|
|
928
992
|
""
|
|
929
993
|
].join("\n");
|
|
930
994
|
}
|
|
931
|
-
function
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
""
|
|
945
|
-
|
|
995
|
+
function systemdService(directory, nodeExecutable) {
|
|
996
|
+
const cli = join(directory, "node_modules", "@rivus", "agent", "dist", "cli.js");
|
|
997
|
+
return `[Unit]\nDescription=Rivus Agent\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nWorkingDirectory=${systemdPath(directory)}\nExecStart=${systemdQuote(nodeExecutable)} ${systemdQuote(cli)} --env-file .env.local --bootstrap ./rivus.bootstrap.ts --manifest ./rivus.config.json\nRestart=on-failure\nRestartSec=5\nEnvironment=NODE_ENV=production\n\n[Install]\nWantedBy=default.target\n`;
|
|
998
|
+
}
|
|
999
|
+
function systemdPath(value) {
|
|
1000
|
+
return value.replaceAll("%", "%%");
|
|
1001
|
+
}
|
|
1002
|
+
function systemdQuote(value) {
|
|
1003
|
+
return `"${value.replace(/%/g, "%%").replace(/\$/g, () => "$$").replace(/\\/g, "\\\\").replace(/"/g, "\\\"")}"`;
|
|
1004
|
+
}
|
|
1005
|
+
function launchdService(directory, nodeExecutable) {
|
|
1006
|
+
return `<?xml version="1.0" encoding="UTF-8"?>\n<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">\n<plist version="1.0">\n<dict>\n <key>Label</key>\n <string>com.rivus.agent</string>\n <key>ProgramArguments</key>\n <array>\n${[
|
|
1007
|
+
nodeExecutable,
|
|
1008
|
+
join(directory, "node_modules", "@rivus", "agent", "dist", "cli.js"),
|
|
1009
|
+
"--env-file",
|
|
1010
|
+
".env.local",
|
|
1011
|
+
"--bootstrap",
|
|
1012
|
+
"./rivus.bootstrap.ts",
|
|
1013
|
+
"--manifest",
|
|
1014
|
+
"./rivus.config.json"
|
|
1015
|
+
].map((value) => ` <string>${xmlEscape(value)}</string>`).join("\n")}\n </array>\n <key>WorkingDirectory</key>\n <string>${xmlEscape(directory)}</string>\n <key>RunAtLoad</key>\n <true/>\n <key>KeepAlive</key>\n <dict>\n <key>SuccessfulExit</key>\n <false/>\n </dict>\n <key>ThrottleInterval</key>\n <integer>5</integer>\n</dict>\n</plist>\n`;
|
|
1016
|
+
}
|
|
1017
|
+
function xmlEscape(value) {
|
|
1018
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """);
|
|
946
1019
|
}
|
|
947
1020
|
//#endregion
|
|
948
|
-
//#region src/
|
|
949
|
-
function
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
1021
|
+
//#region src/bootstrap/cli/rivus-cli.ts
|
|
1022
|
+
function runRivusCli(options) {
|
|
1023
|
+
const command = options.argv[0];
|
|
1024
|
+
if (command === "setup") return runSetupCommand(options);
|
|
1025
|
+
if (command === "start") return runHomeDaemonCommand(options, "start", []);
|
|
1026
|
+
if (command === "status") return runHomeDaemonCommand(options, "status", ["--status"]);
|
|
1027
|
+
if (command === "check-config") return runHomeDaemonCommand(options, "check-config", ["--check-config"]);
|
|
1028
|
+
if (command === "init") return runInitCommand(options);
|
|
1029
|
+
if (command === "doctor") return runDoctorCommand(options);
|
|
1030
|
+
if (command === "model") return runModelCommand(options);
|
|
1031
|
+
if (command && !command.startsWith("-")) return Effect.sync(() => {
|
|
1032
|
+
options.stderr.write(renderRivusCliUnknownCommand(command));
|
|
1033
|
+
return 1;
|
|
1034
|
+
});
|
|
1035
|
+
return runRivusDaemonCli(options);
|
|
1036
|
+
}
|
|
1037
|
+
function runModelCommand(options) {
|
|
1038
|
+
const parsed = parseRivusModelCliArguments(options.argv.slice(1));
|
|
1039
|
+
if ("help" in parsed) return Effect.sync(() => {
|
|
1040
|
+
options.stdout.write(renderRivusModelCliHelp());
|
|
1041
|
+
return 0;
|
|
1042
|
+
});
|
|
1043
|
+
if ("error" in parsed) return Effect.sync(() => {
|
|
1044
|
+
options.stderr.write(renderRivusModelCliArgumentError(parsed.error));
|
|
1045
|
+
return 1;
|
|
1046
|
+
});
|
|
1047
|
+
if (parsed.operation !== "status" && !isRivusModelManagementEnabled(options.env)) return Effect.sync(() => {
|
|
1048
|
+
writeModelResponse(options.stdout, {
|
|
1049
|
+
error: {
|
|
1050
|
+
code: "management_disabled",
|
|
1051
|
+
message: "model management is not enabled for this Home"
|
|
1052
|
+
},
|
|
1053
|
+
schemaVersion: 1,
|
|
1054
|
+
status: "failed"
|
|
1055
|
+
});
|
|
1056
|
+
return 1;
|
|
1057
|
+
});
|
|
1058
|
+
const client = options.modelManagementClient ?? createRivusModelManagementSocketClient({
|
|
1059
|
+
env: options.env,
|
|
1060
|
+
socketPath: resolveRivusModelSocketPath({
|
|
1061
|
+
env: options.env,
|
|
1062
|
+
homeDirectory: options.homeDirectory
|
|
1063
|
+
})
|
|
1064
|
+
});
|
|
953
1065
|
return Effect.tryPromise({
|
|
954
|
-
try:
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
1066
|
+
try: () => client.execute(parsed),
|
|
1067
|
+
catch: (error) => error
|
|
1068
|
+
}).pipe(Effect.tap((response) => Effect.sync(() => writeModelResponse(options.stdout, response))), Effect.map((response) => modelManagementExitCode(response)), Effect.catchAll((error) => Effect.sync(() => {
|
|
1069
|
+
writeModelResponse(options.stdout, modelTransportFailure(error));
|
|
1070
|
+
return 1;
|
|
1071
|
+
})));
|
|
1072
|
+
}
|
|
1073
|
+
function modelManagementExitCode(response) {
|
|
1074
|
+
if (response.status !== "failed") return 0;
|
|
1075
|
+
if (typeof response.requestId === "string" && response.requestId.trim()) return 0;
|
|
1076
|
+
const request = response.request;
|
|
1077
|
+
if (typeof request !== "object" || request === null || Array.isArray(request)) return 1;
|
|
1078
|
+
const requestId = request.requestId;
|
|
1079
|
+
return typeof requestId === "string" && requestId.trim() ? 0 : 1;
|
|
1080
|
+
}
|
|
1081
|
+
function writeModelResponse(stdout, response) {
|
|
1082
|
+
stdout.write(`${JSON.stringify(response)}\n`);
|
|
1083
|
+
}
|
|
1084
|
+
function modelTransportFailure(error) {
|
|
1085
|
+
const code = error instanceof RivusModelManagementTransportError ? error.code : "socket_error";
|
|
1086
|
+
return {
|
|
1087
|
+
error: {
|
|
1088
|
+
code,
|
|
1089
|
+
message: code === "timeout" ? "model management service timed out" : code === "invalid_response" ? "model management returned an invalid response" : "model management service is unavailable"
|
|
968
1090
|
},
|
|
969
|
-
|
|
1091
|
+
schemaVersion: 1,
|
|
1092
|
+
status: "failed"
|
|
1093
|
+
};
|
|
1094
|
+
}
|
|
1095
|
+
function runSetupCommand(options) {
|
|
1096
|
+
return withOptionalDirectoryArgument(options, "setup", (argument) => resolveHomeDirectoryEffect(options, argument).pipe(Effect.flatMap((directory) => options.homeApi.setup(directory).pipe(Effect.tap(() => Effect.sync(() => options.stdout.write(renderRivusSetupSuccess(directory)))), Effect.as(0))), Effect.catchAll((error) => writeError(options, error))));
|
|
1097
|
+
}
|
|
1098
|
+
function runHomeDaemonCommand(options, command, daemonArgs) {
|
|
1099
|
+
if (hasRivusHomeCommandArguments(options.argv.slice(1))) return Effect.sync(() => {
|
|
1100
|
+
options.stderr.write(renderRivusHomeCommandUsage(command));
|
|
1101
|
+
return 1;
|
|
970
1102
|
});
|
|
1103
|
+
return resolveHomeDirectoryEffect(options).pipe(Effect.flatMap((directory) => options.homeApi.load(directory)), Effect.tap((home) => Effect.sync(() => options.changeWorkingDirectory(home.workspaceDirectory))), Effect.flatMap((home) => runRivusDaemonCli({
|
|
1104
|
+
...options,
|
|
1105
|
+
argv: [
|
|
1106
|
+
"--env-file",
|
|
1107
|
+
home.envFilePath,
|
|
1108
|
+
"--bootstrap",
|
|
1109
|
+
home.bootstrap,
|
|
1110
|
+
"--manifest",
|
|
1111
|
+
home.manifestPath,
|
|
1112
|
+
...daemonArgs
|
|
1113
|
+
],
|
|
1114
|
+
env: {
|
|
1115
|
+
...options.env,
|
|
1116
|
+
RIVUS_DEPLOYMENT_STATE_DIR: home.stateDirectory,
|
|
1117
|
+
RIVUS_HOME: home.directory
|
|
1118
|
+
},
|
|
1119
|
+
pluginPackageManifestPath: options.packageManifestPath
|
|
1120
|
+
})), Effect.catchAll((error) => writeError(options, error)));
|
|
971
1121
|
}
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
1122
|
+
function runInitCommand(options) {
|
|
1123
|
+
return withOptionalDirectoryArgument(options, "init", (argument) => {
|
|
1124
|
+
const directory = resolve(options.cwd, argument ?? ".");
|
|
1125
|
+
return Effect.tryPromise({
|
|
1126
|
+
try: () => initializeRivusProject({
|
|
1127
|
+
directory,
|
|
1128
|
+
nodeExecutable: options.nodeExecutable,
|
|
1129
|
+
packageManifestPath: options.packageManifestPath,
|
|
1130
|
+
templateDirectory: options.templateDirectory
|
|
1131
|
+
}),
|
|
1132
|
+
catch: (error) => error
|
|
1133
|
+
}).pipe(Effect.tap((result) => Effect.sync(() => options.stdout.write(renderRivusProjectInitializationSuccess(result.directory)))), Effect.as(0), Effect.catchAll((error) => writeError(options, error)));
|
|
1134
|
+
});
|
|
1135
|
+
}
|
|
1136
|
+
function withOptionalDirectoryArgument(options, command, run) {
|
|
1137
|
+
const parsed = parseRivusDirectoryArguments(options.argv.slice(1));
|
|
1138
|
+
if (parsed.help) return Effect.sync(() => {
|
|
1139
|
+
options.stdout.write(renderRivusDirectoryCommandUsage(command));
|
|
1140
|
+
return 0;
|
|
1141
|
+
});
|
|
1142
|
+
if (parsed.error) return Effect.sync(() => {
|
|
1143
|
+
options.stderr.write(renderRivusDirectoryCommandUsage(command));
|
|
1144
|
+
return 1;
|
|
1145
|
+
});
|
|
1146
|
+
return run(parsed.directory);
|
|
1147
|
+
}
|
|
1148
|
+
function runDoctorCommand(options) {
|
|
1149
|
+
const parsed = parseRivusDoctorArguments(options.argv.slice(1));
|
|
1150
|
+
if (parsed.help) return Effect.sync(() => {
|
|
1151
|
+
options.stdout.write(renderRivusDoctorUsage());
|
|
1152
|
+
return 0;
|
|
1153
|
+
});
|
|
1154
|
+
if (parsed.error) {
|
|
1155
|
+
const error = parsed.error;
|
|
1156
|
+
return Effect.sync(() => {
|
|
1157
|
+
options.stderr.write(renderRivusDoctorArgumentError(error));
|
|
1158
|
+
return 1;
|
|
1159
|
+
});
|
|
1160
|
+
}
|
|
1161
|
+
if (parsed.directory === void 0) return resolveHomeDirectoryEffect(options).pipe(Effect.flatMap((directory) => options.homeApi.diagnose({
|
|
1162
|
+
directory,
|
|
1163
|
+
env: options.env,
|
|
1164
|
+
...parsed.envFilePath ? { envFilePath: parsed.envFilePath } : {},
|
|
1165
|
+
nodeVersion: options.nodeVersion
|
|
1166
|
+
})), Effect.map((report) => writeDoctorReport(options.stdout, report, "Home")), Effect.catchAll((error) => writeError(options, error)));
|
|
1167
|
+
const projectDirectory = parsed.directory;
|
|
1168
|
+
return diagnoseRivusProject({
|
|
1169
|
+
directory: resolve(options.cwd, projectDirectory),
|
|
1170
|
+
env: options.env,
|
|
1171
|
+
...parsed.envFilePath ? { envFilePath: parsed.envFilePath } : {},
|
|
1172
|
+
nodeVersion: options.nodeVersion
|
|
1173
|
+
}).pipe(Effect.map((report) => writeDoctorReport(options.stdout, report, "project")), Effect.catchAll((error) => writeError(options, error)));
|
|
1174
|
+
}
|
|
1175
|
+
function resolveHomeDirectoryEffect(options, argument) {
|
|
1176
|
+
return Effect.try({
|
|
1177
|
+
try: () => {
|
|
1178
|
+
if (argument) return resolve(options.cwd, argument);
|
|
1179
|
+
const configured = options.env.RIVUS_HOME?.trim();
|
|
1180
|
+
if (!configured) return resolve(options.homeDirectory, ".rivus-agent");
|
|
1181
|
+
if (!isAbsolute(configured)) throw new Error("RIVUS_HOME must be an absolute path");
|
|
1182
|
+
return resolve(configured);
|
|
993
1183
|
},
|
|
994
1184
|
catch: (error) => error instanceof Error ? error : new Error(String(error))
|
|
995
1185
|
});
|
|
996
1186
|
}
|
|
1187
|
+
function writeDoctorReport(stdout, report, owner) {
|
|
1188
|
+
for (const chunk of renderRivusDoctorReport(report, owner)) stdout.write(chunk);
|
|
1189
|
+
return report.ready ? 0 : 1;
|
|
1190
|
+
}
|
|
1191
|
+
function writeError(options, error) {
|
|
1192
|
+
return Effect.sync(() => {
|
|
1193
|
+
options.stderr.write(renderRivusCliError(error));
|
|
1194
|
+
return 1;
|
|
1195
|
+
});
|
|
1196
|
+
}
|
|
997
1197
|
//#endregion
|
|
998
|
-
//#region src/
|
|
999
|
-
function
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1198
|
+
//#region src/bootstrap/cli/rivus-node-entrypoint.ts
|
|
1199
|
+
function runRivusNodeEntrypoint(options) {
|
|
1200
|
+
const packageManifestPath = join(options.packageDirectory, "package.json");
|
|
1201
|
+
return runRivusCli({
|
|
1202
|
+
argv: options.argv,
|
|
1203
|
+
changeWorkingDirectory: options.changeWorkingDirectory,
|
|
1204
|
+
cwd: options.cwd,
|
|
1205
|
+
env: options.env,
|
|
1206
|
+
...options.exitAfterSignal !== void 0 ? { exitAfterSignal: options.exitAfterSignal } : {},
|
|
1207
|
+
homeApi: createNodeRivusHome({ deploymentInspector: createRivusHomeDeploymentInspector({ packageManifestPath }) }),
|
|
1208
|
+
homeDirectory: homedir(),
|
|
1209
|
+
loadBootstrap: (specifier) => import(toImportSpecifier(specifier)),
|
|
1210
|
+
nodeExecutable: process.execPath,
|
|
1211
|
+
nodeVersion: process.versions.node,
|
|
1212
|
+
packageManifestPath,
|
|
1213
|
+
signalSource: options.signalSource,
|
|
1214
|
+
stderr: options.stderr,
|
|
1215
|
+
stdout: options.stdout,
|
|
1216
|
+
templateDirectory: join(options.packageDirectory, "examples")
|
|
1217
|
+
});
|
|
1218
|
+
}
|
|
1219
|
+
function toImportSpecifier(specifier) {
|
|
1220
|
+
if (specifier.startsWith(".") || specifier.startsWith("/")) return pathToFileURL(resolve(specifier)).href;
|
|
1221
|
+
return specifier;
|
|
1009
1222
|
}
|
|
1010
1223
|
//#endregion
|
|
1011
1224
|
//#region src/cli.ts
|
|
1012
1225
|
const packageDirectory = fileURLToPath(new URL("..", import.meta.url));
|
|
1013
|
-
const exitCode = await Effect.runPromise(
|
|
1226
|
+
const exitCode = await Effect.runPromise(runRivusNodeEntrypoint({
|
|
1014
1227
|
argv: process.argv.slice(2),
|
|
1015
1228
|
changeWorkingDirectory: (directory) => process.chdir(directory),
|
|
1016
1229
|
cwd: process.cwd(),
|
|
1017
1230
|
env: process.env,
|
|
1018
1231
|
exitAfterSignal: (code) => process.exit(code),
|
|
1019
|
-
|
|
1020
|
-
loadBootstrap: (specifier) => import(toImportSpecifier(specifier)),
|
|
1021
|
-
homeDirectory: homedir(),
|
|
1022
|
-
nodeExecutable: process.execPath,
|
|
1023
|
-
nodeVersion: process.versions.node,
|
|
1024
|
-
packageManifestPath: join(packageDirectory, "package.json"),
|
|
1232
|
+
packageDirectory,
|
|
1025
1233
|
signalSource: process,
|
|
1026
1234
|
stderr: process.stderr,
|
|
1027
|
-
stdout: process.stdout
|
|
1028
|
-
templateDirectory: join(packageDirectory, "examples")
|
|
1235
|
+
stdout: process.stdout
|
|
1029
1236
|
}));
|
|
1030
1237
|
process.exitCode = exitCode;
|
|
1031
|
-
function toImportSpecifier(specifier) {
|
|
1032
|
-
if (specifier.startsWith(".") || specifier.startsWith("/")) return pathToFileURL(resolve(specifier)).href;
|
|
1033
|
-
return specifier;
|
|
1034
|
-
}
|
|
1035
1238
|
//#endregion
|
|
1036
1239
|
export {};
|