@rynx-ai/cli 0.1.10
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/dist/agent-file.d.ts +13 -0
- package/dist/agent-file.js +61 -0
- package/dist/browser-cli-args.d.ts +28 -0
- package/dist/browser-cli-args.js +181 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +11 -0
- package/dist/client.d.ts +4 -0
- package/dist/client.js +2 -0
- package/dist/commands/agent.d.ts +1 -0
- package/dist/commands/agent.js +55 -0
- package/dist/commands/app-distribution.d.ts +6 -0
- package/dist/commands/app-distribution.js +98 -0
- package/dist/commands/browser.d.ts +1 -0
- package/dist/commands/browser.js +273 -0
- package/dist/commands/cleanup.d.ts +1 -0
- package/dist/commands/cleanup.js +24 -0
- package/dist/commands/emulator.d.ts +1 -0
- package/dist/commands/emulator.js +23 -0
- package/dist/commands/errors.d.ts +5 -0
- package/dist/commands/errors.js +10 -0
- package/dist/commands/index.d.ts +9 -0
- package/dist/commands/index.js +9 -0
- package/dist/commands/plugin.d.ts +6 -0
- package/dist/commands/plugin.js +289 -0
- package/dist/commands/runtime.d.ts +1 -0
- package/dist/commands/runtime.js +136 -0
- package/dist/commands/skills.d.ts +10 -0
- package/dist/commands/skills.js +80 -0
- package/dist/control-client.d.ts +163 -0
- package/dist/control-client.js +1028 -0
- package/dist/control-endpoint.d.ts +29 -0
- package/dist/control-endpoint.js +121 -0
- package/dist/desktop-browser-host-client.d.ts +44 -0
- package/dist/desktop-browser-host-client.js +430 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +2 -0
- package/dist/legacy-adapter.d.ts +7 -0
- package/dist/legacy-adapter.js +63 -0
- package/dist/run-cli.d.ts +1 -0
- package/dist/run-cli.js +62 -0
- package/dist/usage.d.ts +1 -0
- package/dist/usage.js +56 -0
- package/dist/version.d.ts +1 -0
- package/dist/version.js +8 -0
- package/package.json +52 -0
- package/skills/rynx-cli/SKILL.md +99 -0
- package/skills/rynx-cli/agents/openai.yaml +4 -0
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { type LoadedAgentSpec } from "@rynx-ai/core";
|
|
2
|
+
/** Absolute path to an agent's spec file. */
|
|
3
|
+
export declare function agentSpecPath(id: string): string;
|
|
4
|
+
/**
|
|
5
|
+
* Validate + atomically write a spec into the agent's config directory. The id
|
|
6
|
+
* is the directory name and is never persisted inside the file (any `id` in the
|
|
7
|
+
* input is dropped by the schema). Throws on a schema violation.
|
|
8
|
+
*/
|
|
9
|
+
export declare function writeAgentSpec(id: string, spec: Record<string, unknown>): LoadedAgentSpec;
|
|
10
|
+
/** Remove an agent (its whole config directory). Returns false if it didn't exist. */
|
|
11
|
+
export declare function removeAgentSpec(id: string): boolean;
|
|
12
|
+
/** Write a minimal, valid starter spec for `rynx agent add`. */
|
|
13
|
+
export declare function scaffoldAgentSpec(id: string): LoadedAgentSpec;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `~/.rynx/agents/<id>/agent.json` — CLI/web-managed declarative agents. Each
|
|
3
|
+
* agent is a directory whose **name is the agent id**; the spec lives at
|
|
4
|
+
* `agent.json` inside it (alongside any instruction markdown the spec references).
|
|
5
|
+
*
|
|
6
|
+
* Reading + validation live in `@rynx-ai/core` (`loadAgentSpec` / `listAgentSpecs`);
|
|
7
|
+
* this module owns the write/remove side (atomic tmp+rename for the spec file).
|
|
8
|
+
*/
|
|
9
|
+
import { existsSync, mkdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
10
|
+
import { agentSpecSchema, assertValidAgentId, rynxAgentDir, rynxAgentSpecFile, } from "@rynx-ai/core";
|
|
11
|
+
/** Absolute path to an agent's spec file. */
|
|
12
|
+
export function agentSpecPath(id) {
|
|
13
|
+
return rynxAgentSpecFile(id);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Validate + atomically write a spec into the agent's config directory. The id
|
|
17
|
+
* is the directory name and is never persisted inside the file (any `id` in the
|
|
18
|
+
* input is dropped by the schema). Throws on a schema violation.
|
|
19
|
+
*/
|
|
20
|
+
export function writeAgentSpec(id, spec) {
|
|
21
|
+
assertValidAgentId(id); // id is a directory name — reject path-escaping / unsafe ids
|
|
22
|
+
const parsed = agentSpecSchema.parse(spec); // unknown keys (incl. `id`) are stripped
|
|
23
|
+
mkdirSync(rynxAgentDir(id), { recursive: true });
|
|
24
|
+
const path = rynxAgentSpecFile(id);
|
|
25
|
+
const tmp = `${path}.tmp`;
|
|
26
|
+
writeFileSync(tmp, `${JSON.stringify(parsed, null, 2)}\n`, "utf8");
|
|
27
|
+
renameSync(tmp, path);
|
|
28
|
+
return { ...parsed, id };
|
|
29
|
+
}
|
|
30
|
+
/** Remove an agent (its whole config directory). Returns false if it didn't exist. */
|
|
31
|
+
export function removeAgentSpec(id) {
|
|
32
|
+
assertValidAgentId(id); // guard rmSync against `..`/path-escaping ids
|
|
33
|
+
const dir = rynxAgentDir(id);
|
|
34
|
+
if (!existsSync(dir)) {
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
rmSync(dir, { recursive: true, force: true });
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
/** Write a minimal, valid starter spec for `rynx agent add`. */
|
|
41
|
+
export function scaffoldAgentSpec(id) {
|
|
42
|
+
return writeAgentSpec(id, {
|
|
43
|
+
specVersion: 1,
|
|
44
|
+
name: id,
|
|
45
|
+
description: "A rynx agent.",
|
|
46
|
+
instructions: "You are a helpful agent.",
|
|
47
|
+
executor: {
|
|
48
|
+
runtime: "claude",
|
|
49
|
+
model: "claude-sonnet-4-6",
|
|
50
|
+
reasoningEffort: "medium",
|
|
51
|
+
// Runtime budget (edit/remove as needed): timeout is enforced on every
|
|
52
|
+
// runtime; retry maps to per-runtime env. maxIterations (claude only) and
|
|
53
|
+
// contextWindow (codex only) are also accepted.
|
|
54
|
+
timeout: 1800,
|
|
55
|
+
retry: { maxRetries: 2 },
|
|
56
|
+
},
|
|
57
|
+
osEnv: { sandbox: "danger-full-access", approvalPolicy: "never" },
|
|
58
|
+
tools: { allowed: [], disallowed: [], mcp: [] },
|
|
59
|
+
params: {},
|
|
60
|
+
});
|
|
61
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { RuntimeBrowserBootstrapCredential } from "@rynx-ai/protocol/runtime-browser-bootstrap";
|
|
2
|
+
export type BrowserCliSubcommand = "install" | "update" | "version" | "clean" | "open" | "status" | "pages" | "endpoint" | "snapshot" | "navigate" | "click" | "type" | "screenshot" | "close";
|
|
3
|
+
export interface BrowserCliArgs {
|
|
4
|
+
subcommand: BrowserCliSubcommand;
|
|
5
|
+
json: boolean;
|
|
6
|
+
ensure: boolean;
|
|
7
|
+
channel?: string;
|
|
8
|
+
version?: string;
|
|
9
|
+
runtime?: string;
|
|
10
|
+
session?: string;
|
|
11
|
+
url?: string;
|
|
12
|
+
ref?: string;
|
|
13
|
+
selector?: string;
|
|
14
|
+
text?: string;
|
|
15
|
+
output?: string;
|
|
16
|
+
format?: "png" | "jpeg" | "webp";
|
|
17
|
+
quality?: number;
|
|
18
|
+
x?: number;
|
|
19
|
+
y?: number;
|
|
20
|
+
}
|
|
21
|
+
export interface BrowserCliTarget {
|
|
22
|
+
runtimeSelector: string;
|
|
23
|
+
sessionId: string;
|
|
24
|
+
credential?: RuntimeBrowserBootstrapCredential;
|
|
25
|
+
}
|
|
26
|
+
export declare function parseBrowserCliArgs(args: readonly string[]): BrowserCliArgs;
|
|
27
|
+
/** Resolve authority without ever turning a managed Session into an operator. */
|
|
28
|
+
export declare function resolveBrowserCliTarget(args: BrowserCliArgs, managedCredential: RuntimeBrowserBootstrapCredential | undefined): BrowserCliTarget;
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
const SPECS = {
|
|
2
|
+
install: { values: ["--channel", "--version"], flags: ["--json"] },
|
|
3
|
+
update: { values: ["--channel"], flags: ["--json"] },
|
|
4
|
+
version: { values: [], flags: ["--json"] },
|
|
5
|
+
clean: { values: [], flags: ["--json"] },
|
|
6
|
+
open: {
|
|
7
|
+
values: ["--url", "--session", "--runtime"],
|
|
8
|
+
flags: ["--json"],
|
|
9
|
+
positionalUrl: true,
|
|
10
|
+
},
|
|
11
|
+
status: { values: ["--session", "--runtime"], flags: ["--json"] },
|
|
12
|
+
pages: { values: ["--session", "--runtime"], flags: ["--json"] },
|
|
13
|
+
endpoint: { values: ["--session"], flags: ["--ensure", "--json"] },
|
|
14
|
+
snapshot: { values: ["--session"], flags: ["--json"] },
|
|
15
|
+
navigate: {
|
|
16
|
+
values: ["--url", "--session"],
|
|
17
|
+
flags: ["--json"],
|
|
18
|
+
positionalUrl: true,
|
|
19
|
+
},
|
|
20
|
+
click: {
|
|
21
|
+
values: ["--ref", "--selector", "--x", "--y", "--session"],
|
|
22
|
+
flags: ["--json"],
|
|
23
|
+
},
|
|
24
|
+
type: {
|
|
25
|
+
values: ["--ref", "--selector", "--text", "--session"],
|
|
26
|
+
flags: ["--json"],
|
|
27
|
+
},
|
|
28
|
+
screenshot: {
|
|
29
|
+
values: ["--output", "--format", "--quality", "--session"],
|
|
30
|
+
flags: ["--json"],
|
|
31
|
+
},
|
|
32
|
+
close: { values: ["--session", "--runtime"], flags: ["--json"] },
|
|
33
|
+
};
|
|
34
|
+
export function parseBrowserCliArgs(args) {
|
|
35
|
+
const subcommand = args[0];
|
|
36
|
+
if (!isBrowserSubcommand(subcommand)) {
|
|
37
|
+
throw new Error("browser: expected install, update, version, clean, open, status, pages, endpoint, " +
|
|
38
|
+
"snapshot, navigate, click, type, screenshot, or close");
|
|
39
|
+
}
|
|
40
|
+
const spec = SPECS[subcommand];
|
|
41
|
+
const values = new Map();
|
|
42
|
+
const flags = new Set();
|
|
43
|
+
const positionals = [];
|
|
44
|
+
for (let index = 1; index < args.length; index += 1) {
|
|
45
|
+
const arg = args[index];
|
|
46
|
+
if (arg.startsWith("--")) {
|
|
47
|
+
if (spec.flags.includes(arg)) {
|
|
48
|
+
if (flags.has(arg))
|
|
49
|
+
throw new Error(`browser ${subcommand}: duplicate option ${arg}`);
|
|
50
|
+
flags.add(arg);
|
|
51
|
+
continue;
|
|
52
|
+
}
|
|
53
|
+
if (spec.values.includes(arg)) {
|
|
54
|
+
if (values.has(arg))
|
|
55
|
+
throw new Error(`browser ${subcommand}: duplicate option ${arg}`);
|
|
56
|
+
const value = args[index + 1];
|
|
57
|
+
if (!value || value.startsWith("--")) {
|
|
58
|
+
throw new Error(`browser ${subcommand}: ${arg} requires a value`);
|
|
59
|
+
}
|
|
60
|
+
values.set(arg, value);
|
|
61
|
+
index += 1;
|
|
62
|
+
continue;
|
|
63
|
+
}
|
|
64
|
+
throw new Error(`browser ${subcommand}: unknown option ${arg}`);
|
|
65
|
+
}
|
|
66
|
+
positionals.push(arg);
|
|
67
|
+
}
|
|
68
|
+
if (!spec.positionalUrl && positionals.length > 0) {
|
|
69
|
+
throw new Error(`browser ${subcommand}: unexpected argument ${positionals[0]}`);
|
|
70
|
+
}
|
|
71
|
+
if (positionals.length > 1) {
|
|
72
|
+
throw new Error(`browser ${subcommand}: unexpected extra argument ${positionals[1]}`);
|
|
73
|
+
}
|
|
74
|
+
if (positionals.length === 1 && values.has("--url")) {
|
|
75
|
+
throw new Error("browser open: pass the URL once, either positionally or with --url");
|
|
76
|
+
}
|
|
77
|
+
const result = {
|
|
78
|
+
subcommand,
|
|
79
|
+
json: flags.has("--json"),
|
|
80
|
+
ensure: flags.has("--ensure"),
|
|
81
|
+
...(values.has("--channel") ? { channel: values.get("--channel") } : {}),
|
|
82
|
+
...(values.has("--version") ? { version: values.get("--version") } : {}),
|
|
83
|
+
...(values.has("--runtime") ? { runtime: values.get("--runtime") } : {}),
|
|
84
|
+
...(values.has("--session") ? { session: values.get("--session") } : {}),
|
|
85
|
+
...(values.has("--url") || positionals[0]
|
|
86
|
+
? { url: values.get("--url") ?? positionals[0] }
|
|
87
|
+
: {}),
|
|
88
|
+
};
|
|
89
|
+
if (values.has("--ref"))
|
|
90
|
+
result.ref = nonEmpty(values.get("--ref"), "--ref");
|
|
91
|
+
if (values.has("--selector"))
|
|
92
|
+
result.selector = nonEmpty(values.get("--selector"), "--selector");
|
|
93
|
+
if (values.has("--text"))
|
|
94
|
+
result.text = values.get("--text");
|
|
95
|
+
if (values.has("--output"))
|
|
96
|
+
result.output = nonEmpty(values.get("--output"), "--output");
|
|
97
|
+
if (values.has("--format")) {
|
|
98
|
+
const format = values.get("--format");
|
|
99
|
+
if (format !== "png" && format !== "jpeg" && format !== "webp") {
|
|
100
|
+
throw new Error(`browser screenshot: --format must be png, jpeg, or webp`);
|
|
101
|
+
}
|
|
102
|
+
result.format = format;
|
|
103
|
+
}
|
|
104
|
+
if (values.has("--quality")) {
|
|
105
|
+
result.quality = integer(values.get("--quality"), "--quality", 0, 100);
|
|
106
|
+
}
|
|
107
|
+
if (values.has("--x"))
|
|
108
|
+
result.x = finiteNumber(values.get("--x"), "--x");
|
|
109
|
+
if (values.has("--y"))
|
|
110
|
+
result.y = finiteNumber(values.get("--y"), "--y");
|
|
111
|
+
if (subcommand === "navigate" && !result.url) {
|
|
112
|
+
throw new Error("browser navigate: URL is required");
|
|
113
|
+
}
|
|
114
|
+
if (subcommand === "click") {
|
|
115
|
+
const targets = Number(result.ref !== undefined)
|
|
116
|
+
+ Number(result.selector !== undefined)
|
|
117
|
+
+ Number(result.x !== undefined || result.y !== undefined);
|
|
118
|
+
if (targets !== 1 || (result.x === undefined) !== (result.y === undefined)) {
|
|
119
|
+
throw new Error("browser click: pass exactly one of --ref, --selector, or both --x and --y");
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (subcommand === "type") {
|
|
123
|
+
if ((result.ref === undefined) === (result.selector === undefined)) {
|
|
124
|
+
throw new Error("browser type: pass exactly one of --ref or --selector");
|
|
125
|
+
}
|
|
126
|
+
if (result.text === undefined)
|
|
127
|
+
throw new Error("browser type: --text is required");
|
|
128
|
+
}
|
|
129
|
+
if (subcommand === "screenshot" && !result.output) {
|
|
130
|
+
throw new Error("browser screenshot: --output is required");
|
|
131
|
+
}
|
|
132
|
+
return result;
|
|
133
|
+
}
|
|
134
|
+
/** Resolve authority without ever turning a managed Session into an operator. */
|
|
135
|
+
export function resolveBrowserCliTarget(args, managedCredential) {
|
|
136
|
+
const runtimeSelector = args.runtime ?? "local";
|
|
137
|
+
if (runtimeSelector !== "local") {
|
|
138
|
+
if (managedCredential) {
|
|
139
|
+
throw new Error("browser: a managed Session cannot select another Runtime");
|
|
140
|
+
}
|
|
141
|
+
if (!args.session) {
|
|
142
|
+
throw new Error("browser: --session is required when selecting a remote Runtime");
|
|
143
|
+
}
|
|
144
|
+
return { runtimeSelector, sessionId: args.session };
|
|
145
|
+
}
|
|
146
|
+
if (managedCredential) {
|
|
147
|
+
if (args.session && args.session !== managedCredential.sessionId) {
|
|
148
|
+
throw new Error("browser: a managed Session cannot control another Session with --session");
|
|
149
|
+
}
|
|
150
|
+
return {
|
|
151
|
+
runtimeSelector: "local",
|
|
152
|
+
sessionId: managedCredential.sessionId,
|
|
153
|
+
credential: managedCredential,
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
if (!args.session) {
|
|
157
|
+
throw new Error("browser: run inside a managed Rynx Session or pass --session for operator access");
|
|
158
|
+
}
|
|
159
|
+
return { runtimeSelector: "local", sessionId: args.session };
|
|
160
|
+
}
|
|
161
|
+
function isBrowserSubcommand(value) {
|
|
162
|
+
return value !== undefined && Object.hasOwn(SPECS, value);
|
|
163
|
+
}
|
|
164
|
+
function nonEmpty(value, option) {
|
|
165
|
+
if (value.length === 0)
|
|
166
|
+
throw new Error(`${option} must not be empty`);
|
|
167
|
+
return value;
|
|
168
|
+
}
|
|
169
|
+
function finiteNumber(value, option) {
|
|
170
|
+
const parsed = Number(value);
|
|
171
|
+
if (!Number.isFinite(parsed))
|
|
172
|
+
throw new Error(`${option} must be a finite number`);
|
|
173
|
+
return parsed;
|
|
174
|
+
}
|
|
175
|
+
function integer(value, option, minimum, maximum) {
|
|
176
|
+
const parsed = Number(value);
|
|
177
|
+
if (!Number.isSafeInteger(parsed) || parsed < minimum || parsed > maximum) {
|
|
178
|
+
throw new Error(`${option} must be an integer from ${minimum} to ${maximum}`);
|
|
179
|
+
}
|
|
180
|
+
return parsed;
|
|
181
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { CliUsageError } from "./commands/errors.js";
|
|
3
|
+
import { runCli } from "./run-cli.js";
|
|
4
|
+
runCli(process.argv.slice(2))
|
|
5
|
+
.then((code) => {
|
|
6
|
+
process.exitCode = code;
|
|
7
|
+
})
|
|
8
|
+
.catch((error) => {
|
|
9
|
+
console.error(error instanceof Error ? error.message : String(error));
|
|
10
|
+
process.exitCode = error instanceof CliUsageError ? error.exitCode : 1;
|
|
11
|
+
});
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { callManagedRuntimeBrowser, callResidentRuntime, cancelResidentPluginInstallation, commitResidentPluginInstallation, cleanResidentBrowserArtifacts, cleanupResidentSessions, createResidentRemoteRuntimePairingOffer, ensureResidentDaemon, forgetResidentRuntimeTarget, getResidentChromeInspectionStatus, getResidentDaemonIdentity, getResidentDaemonRuntimeStatus, getResidentBrowserArtifactVersion, getResidentRuntimeLocalBrowserEndpoint, invokeResidentPluginCommand, installResidentBrowserArtifact, listResidentPlugins, listResidentRemoteRuntimeClients, listResidentRuntimeTargets, pairResidentRuntimeTarget, prepareResidentPluginInstallation, readManagedRuntimeBrowserCredential, readOptionalManagedRuntimeBrowserCredential, reloadResidentPluginRuntime, revokeResidentRemoteRuntimeClient, setResidentPluginEnabled, shutdownResidentDaemonIfIdle, configureResidentChromeInspection, testResidentRuntimeTarget, uninstallResidentPlugin, updateResidentBrowserArtifact, connectResidentDesktopBrowserHost, ResidentRuntimeCallError, type InvokeResidentPluginCommandOptions, type ResidentDaemon, type ResidentDaemonIdentity, type ResidentDesktopBrowserHostCommandRequest, type ResidentDesktopBrowserHostConnectOptions, type ResidentDesktopBrowserHostConnection, type ResidentDesktopBrowserHostFailure, type ResidentPluginCommandResult, type ResidentPluginManagementItem, type ResidentPluginManagementState, type ResidentRemoteRuntimeClientGrant, type ResidentRuntimeCallErrorCode, type ResidentRuntimeCallOutcome, type ResidentRuntimeTargetTestResult, type ResidentRuntimeTargetView, } from "./control-client.js";
|
|
2
|
+
export type { DaemonChromeInspectionConfigureInput, DaemonChromeInspectionStatus, } from "@rynx-ai/protocol/control";
|
|
3
|
+
export type { PluginInstallCommitInput, PluginInstallCommitResult, PluginInstallPreparation, PluginInstallPrepareInput, } from "@rynx-ai/protocol/plugin-management";
|
|
4
|
+
export { ensureDaemonControlEndpoint, resolveDaemonControlEndpoint, type DaemonControlEndpoint, type ResolveDaemonControlEndpointOptions, } from "./control-endpoint.js";
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export { callManagedRuntimeBrowser, callResidentRuntime, cancelResidentPluginInstallation, commitResidentPluginInstallation, cleanResidentBrowserArtifacts, cleanupResidentSessions, createResidentRemoteRuntimePairingOffer, ensureResidentDaemon, forgetResidentRuntimeTarget, getResidentChromeInspectionStatus, getResidentDaemonIdentity, getResidentDaemonRuntimeStatus, getResidentBrowserArtifactVersion, getResidentRuntimeLocalBrowserEndpoint, invokeResidentPluginCommand, installResidentBrowserArtifact, listResidentPlugins, listResidentRemoteRuntimeClients, listResidentRuntimeTargets, pairResidentRuntimeTarget, prepareResidentPluginInstallation, readManagedRuntimeBrowserCredential, readOptionalManagedRuntimeBrowserCredential, reloadResidentPluginRuntime, revokeResidentRemoteRuntimeClient, setResidentPluginEnabled, shutdownResidentDaemonIfIdle, configureResidentChromeInspection, testResidentRuntimeTarget, uninstallResidentPlugin, updateResidentBrowserArtifact, connectResidentDesktopBrowserHost, ResidentRuntimeCallError, } from "./control-client.js";
|
|
2
|
+
export { ensureDaemonControlEndpoint, resolveDaemonControlEndpoint, } from "./control-endpoint.js";
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runAgentCommand(args: readonly string[]): Promise<number>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { listAgentSpecs, loadAgentSpec, rynxAgentsDir } from "@rynx-ai/core";
|
|
3
|
+
import { agentSpecPath, removeAgentSpec, scaffoldAgentSpec } from "../agent-file.js";
|
|
4
|
+
import { fail } from "./errors.js";
|
|
5
|
+
export async function runAgentCommand(args) {
|
|
6
|
+
const [subcommand, id] = args;
|
|
7
|
+
switch (subcommand) {
|
|
8
|
+
case "list": {
|
|
9
|
+
const specs = await listAgentSpecs();
|
|
10
|
+
console.log(`Agents (dir: ${rynxAgentsDir()})`);
|
|
11
|
+
if (specs.length === 0) {
|
|
12
|
+
console.log(" (none)");
|
|
13
|
+
return 0;
|
|
14
|
+
}
|
|
15
|
+
for (const spec of specs) {
|
|
16
|
+
const runtime = spec.executor?.runtime ?? "(default)";
|
|
17
|
+
const model = spec.executor?.model ?? "(default)";
|
|
18
|
+
const display = spec.name && spec.name !== spec.id ? ` (${spec.name})` : "";
|
|
19
|
+
console.log(` ${spec.id.padEnd(16)} ${runtime.padEnd(8)} ${model}${display}`);
|
|
20
|
+
}
|
|
21
|
+
return 0;
|
|
22
|
+
}
|
|
23
|
+
case "show": {
|
|
24
|
+
if (!id)
|
|
25
|
+
fail("agent show: missing agent id");
|
|
26
|
+
const spec = await loadAgentSpec(id);
|
|
27
|
+
if (!spec)
|
|
28
|
+
fail(`agent "${id}" not found at ${agentSpecPath(id)}`);
|
|
29
|
+
console.log(JSON.stringify(spec, null, 2));
|
|
30
|
+
return 0;
|
|
31
|
+
}
|
|
32
|
+
case "add": {
|
|
33
|
+
if (!id)
|
|
34
|
+
fail("agent add: missing agent id");
|
|
35
|
+
if (existsSync(agentSpecPath(id))) {
|
|
36
|
+
fail(`agent "${id}" already exists at ${agentSpecPath(id)}`);
|
|
37
|
+
}
|
|
38
|
+
scaffoldAgentSpec(id);
|
|
39
|
+
console.log(`Agent "${id}" created at ${agentSpecPath(id)}. ` +
|
|
40
|
+
`Edit it, then select it in a chat with \`/agent ${id}\`.`);
|
|
41
|
+
return 0;
|
|
42
|
+
}
|
|
43
|
+
case "rm":
|
|
44
|
+
case "remove": {
|
|
45
|
+
if (!id)
|
|
46
|
+
fail("agent rm: missing agent id");
|
|
47
|
+
if (!removeAgentSpec(id))
|
|
48
|
+
fail(`agent "${id}" not found`);
|
|
49
|
+
console.log(`Agent "${id}" removed.`);
|
|
50
|
+
return 0;
|
|
51
|
+
}
|
|
52
|
+
default:
|
|
53
|
+
fail("agent: expected list, show <id>, add <id>, or rm <id>");
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export declare function shouldUseAppDistributionCommand(command: string | undefined, env?: NodeJS.ProcessEnv): boolean;
|
|
2
|
+
/**
|
|
3
|
+
* Prevent the App launcher from falling through to the compatibility daemon
|
|
4
|
+
* CLI and accidentally creating or inspecting a PM2-owned second lifecycle.
|
|
5
|
+
*/
|
|
6
|
+
export declare function runAppDistributionCommand(command: string, args: readonly string[]): Promise<number>;
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { closeSync, fstatSync, openSync, readSync, } from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { rynxHome } from "@rynx-ai/core";
|
|
4
|
+
import { resolveDaemonControlEndpoint } from "../control-endpoint.js";
|
|
5
|
+
import { fail } from "./errors.js";
|
|
6
|
+
const APP_ONLY_COMMANDS = new Set([
|
|
7
|
+
"setup",
|
|
8
|
+
"update",
|
|
9
|
+
"start",
|
|
10
|
+
"restart",
|
|
11
|
+
"stop",
|
|
12
|
+
"status",
|
|
13
|
+
"logs",
|
|
14
|
+
]);
|
|
15
|
+
const LOG_TAIL_MAX_BYTES = 256 * 1024;
|
|
16
|
+
export function shouldUseAppDistributionCommand(command, env = process.env) {
|
|
17
|
+
return command !== undefined
|
|
18
|
+
&& APP_ONLY_COMMANDS.has(command)
|
|
19
|
+
&& env.RYNX_DISTRIBUTION === "app";
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Prevent the App launcher from falling through to the compatibility daemon
|
|
23
|
+
* CLI and accidentally creating or inspecting a PM2-owned second lifecycle.
|
|
24
|
+
*/
|
|
25
|
+
export async function runAppDistributionCommand(command, args) {
|
|
26
|
+
const json = args.includes("--json");
|
|
27
|
+
if (command === "status") {
|
|
28
|
+
const endpoint = await resolveDaemonControlEndpoint();
|
|
29
|
+
const status = endpoint
|
|
30
|
+
? {
|
|
31
|
+
state: "running",
|
|
32
|
+
pid: endpoint.pid,
|
|
33
|
+
distribution: endpoint.distribution ?? "unknown",
|
|
34
|
+
lifecycle: endpoint.daemonLifecycle ?? "unknown",
|
|
35
|
+
version: endpoint.productVersion,
|
|
36
|
+
buildId: endpoint.buildId,
|
|
37
|
+
origin: endpoint.origin,
|
|
38
|
+
}
|
|
39
|
+
: { state: "stopped" };
|
|
40
|
+
console.log(json ? JSON.stringify(status, null, 2) : formatStatus(status));
|
|
41
|
+
return endpoint ? 0 : 1;
|
|
42
|
+
}
|
|
43
|
+
if (command === "logs") {
|
|
44
|
+
printRecentDaemonLogs();
|
|
45
|
+
return 0;
|
|
46
|
+
}
|
|
47
|
+
if (command === "setup") {
|
|
48
|
+
fail("Rynx App setup is completed in its onboarding and Desktop Settings.");
|
|
49
|
+
}
|
|
50
|
+
if (command === "update") {
|
|
51
|
+
fail("Rynx App updates are managed from Rynx > Check for Updates.");
|
|
52
|
+
}
|
|
53
|
+
if (command === "start") {
|
|
54
|
+
const endpoint = await resolveDaemonControlEndpoint();
|
|
55
|
+
if (endpoint?.distribution === "app") {
|
|
56
|
+
console.log(`Rynx Runtime is already running (PID ${endpoint.pid}).`);
|
|
57
|
+
return 0;
|
|
58
|
+
}
|
|
59
|
+
fail("Open Rynx App to start its bundled Runtime.");
|
|
60
|
+
}
|
|
61
|
+
fail(`rynx ${command} is managed by Rynx App. Use Desktop Settings > Daemon ` +
|
|
62
|
+
"so the App remains the only lifecycle owner.");
|
|
63
|
+
}
|
|
64
|
+
function formatStatus(status) {
|
|
65
|
+
if (status.state !== "running")
|
|
66
|
+
return "Rynx Runtime is stopped.";
|
|
67
|
+
return [
|
|
68
|
+
"Rynx Runtime is running",
|
|
69
|
+
status.version ? `version ${status.version}` : null,
|
|
70
|
+
status.lifecycle ? status.lifecycle : null,
|
|
71
|
+
status.pid ? `PID ${status.pid}` : null,
|
|
72
|
+
].filter(Boolean).join(" · ");
|
|
73
|
+
}
|
|
74
|
+
function printRecentDaemonLogs() {
|
|
75
|
+
const root = path.join(rynxHome(), "logs");
|
|
76
|
+
for (const name of ["daemon.out.log", "daemon.err.log"]) {
|
|
77
|
+
const file = path.join(root, name);
|
|
78
|
+
console.log(`==> ${file} <==`);
|
|
79
|
+
let descriptor;
|
|
80
|
+
try {
|
|
81
|
+
descriptor = openSync(file, "r");
|
|
82
|
+
const size = fstatSync(descriptor).size;
|
|
83
|
+
const length = Math.min(size, LOG_TAIL_MAX_BYTES);
|
|
84
|
+
const buffer = Buffer.alloc(length);
|
|
85
|
+
readSync(descriptor, buffer, 0, length, Math.max(0, size - length));
|
|
86
|
+
const content = buffer.toString("utf8");
|
|
87
|
+
const lines = content.split(/\r?\n/);
|
|
88
|
+
console.log(lines.slice(Math.max(0, lines.length - 200)).join("\n"));
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
console.log("(no log output)");
|
|
92
|
+
}
|
|
93
|
+
finally {
|
|
94
|
+
if (descriptor !== undefined)
|
|
95
|
+
closeSync(descriptor);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runBrowserCommand(args: readonly string[]): Promise<number>;
|