@rivus/agent 0.14.2 → 0.14.4

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