@rome-os/node 0.1.0 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -5
- package/dist/entry.js +133 -48
- package/dist/help.js +47 -8
- package/dist/index.d.ts +2 -5
- package/dist/index.js +2 -5
- package/package.json +7 -6
- package/dist/actions.d.ts +0 -20
- package/dist/actions.js +0 -18
- package/dist/client.d.ts +0 -29
- package/dist/client.js +0 -143
- package/dist/cloud.d.ts +0 -7
- package/dist/cloud.js +0 -53
- package/dist/connect.d.ts +0 -2
- package/dist/connect.js +0 -114
- package/dist/connector.d.ts +0 -24
- package/dist/connector.js +0 -148
- package/dist/daemon.d.ts +0 -11
- package/dist/daemon.js +0 -208
- package/dist/executor.d.ts +0 -5
- package/dist/executor.js +0 -121
- package/dist/local.d.ts +0 -9
- package/dist/local.js +0 -33
- package/dist/login.d.ts +0 -6
- package/dist/login.js +0 -100
- package/dist/protocol.d.ts +0 -30
- package/dist/protocol.js +0 -40
- package/dist/socket.d.ts +0 -2
- package/dist/socket.js +0 -27
- package/dist/storage.d.ts +0 -2
- package/dist/storage.js +0 -37
package/README.md
CHANGED
|
@@ -8,8 +8,9 @@ Build an installation archive from this repository:
|
|
|
8
8
|
|
|
9
9
|
```sh
|
|
10
10
|
pnpm --filter @rome-os/node build
|
|
11
|
+
pnpm --filter @rome-os/node-core pack --pack-destination /tmp
|
|
11
12
|
pnpm --filter @rome-os/node pack --pack-destination /tmp
|
|
12
|
-
npm install -g /tmp/rome-os-node-0.1.0.tgz
|
|
13
|
+
npm install -g /tmp/rome-os-node-core-0.1.0.tgz /tmp/rome-os-node-0.1.0.tgz
|
|
13
14
|
```
|
|
14
15
|
|
|
15
16
|
On the target computer:
|
|
@@ -22,7 +23,27 @@ Open the printed authorization URL in a browser on that computer and approve it.
|
|
|
22
23
|
The browser callback uses a temporary loopback listener. A remote browser needs
|
|
23
24
|
a tunnel to that listener. Ctrl+C disconnects and preserves the credential.
|
|
24
25
|
|
|
25
|
-
|
|
26
|
+
Over SSH or on a headless computer, use device authorization:
|
|
27
|
+
|
|
28
|
+
```sh
|
|
29
|
+
rome-node connect --device-code --name "Remote Linux" --cloud https://romeos.cc
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Open the printed URL on another device and approve the displayed user code.
|
|
33
|
+
The CLI prints the expiry and waits for approval without a callback listener or SSH tunnel.
|
|
34
|
+
Ctrl+C cancels the authorization. Both modes reuse stored credentials when valid.
|
|
35
|
+
|
|
36
|
+
Rome instances prepare caller authorization automatically at startup and after
|
|
37
|
+
Cloud enrollment. In a server environment that supplies `ROME_INSTANCE_TOKEN`, run:
|
|
38
|
+
|
|
39
|
+
```sh
|
|
40
|
+
rome-node auth --server --cloud https://romeos.cc
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
This exchanges the Instance Token with Cloud and stores only the communication
|
|
44
|
+
token. Existing caller credentials are reused. The CLI does not read Rome's database.
|
|
45
|
+
|
|
46
|
+
For manual caller configuration, pass an issued communication token through stdin:
|
|
26
47
|
|
|
27
48
|
```sh
|
|
28
49
|
rome-node auth --cloud https://romeos.cc < /path/to/private-token-file
|
|
@@ -49,10 +70,21 @@ Gateway connection. `rome-node daemon status` reports its PID, and
|
|
|
49
70
|
daemon before changing credentials. `rome-node daemon serve` runs in the
|
|
50
71
|
foreground for diagnosis. Set `ROME_NODE_CONFIG_DIR` to isolate configurations.
|
|
51
72
|
|
|
73
|
+
`rome-node watch` subscribes to caller connection changes and prints one JSON
|
|
74
|
+
event per line. It starts the daemon if needed and begins with a snapshot.
|
|
75
|
+
After a connection loss it waits for the daemon and subscribes again; it does
|
|
76
|
+
not restart a stopped daemon. Ctrl+C closes only the watcher. These events
|
|
77
|
+
report the caller's Gateway connection, not remote device presence.
|
|
78
|
+
|
|
52
79
|
Commands print JSON to stdout and diagnostics to stderr. Errors and nonzero
|
|
53
|
-
remote exit codes produce a nonzero local exit status.
|
|
80
|
+
remote exit codes produce a nonzero local exit status. Output is collected in memory
|
|
81
|
+
and returned in full after program exit. Action message limits follow the WebSocket library and infrastructure.
|
|
82
|
+
The local daemon disconnects event observers that exceed its send-buffer limit. A lost response reports
|
|
83
|
+
`unknown_outcome`. Never automatically retry an unknown outcome.
|
|
84
|
+
An authorized device is
|
|
54
85
|
not necessarily online. Use `describe` to query its current platform and actions.
|
|
55
86
|
|
|
56
|
-
The
|
|
57
|
-
|
|
87
|
+
The CLI uses [`@rome-os/node-core`](../rome-node-core/README.md) for authorization,
|
|
88
|
+
its shared caller daemon, and the computer host. The package also re-exports
|
|
89
|
+
the Gateway client, socket adapter, and action response contract for compatibility. See [the device architecture and operating guide](../../docs/rome-node.md)
|
|
58
90
|
for credential boundaries, file operations, output limits, and disconnection behavior.
|
package/dist/entry.js
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
import { parseArgs } from "node:util";
|
|
2
|
-
import {
|
|
3
|
-
import { isRecord,
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import { cloudOrigin, gatewayConfig } from "./cloud.js";
|
|
7
|
-
import { callDaemon, daemonStatus, serveDaemon, startDaemon, stopDaemon } from "./daemon.js";
|
|
8
|
-
import { validId } from "./protocol.js";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { isRecord, cloudOrigin, validId } from "@rome-os/node-core";
|
|
4
|
+
import { authorizeServer, configureCaller } from "@rome-os/node-core/auth";
|
|
5
|
+
import { createNodeClient, CallerConfigurationError, DeviceActionError, nodeConfigFromEnvironment, readOptionalCallerCredential, daemonStatus, startDaemon, stopDaemon, } from "@rome-os/node-core/client";
|
|
9
6
|
import { commandHelp } from "./help.js";
|
|
7
|
+
async function withShutdown(run) {
|
|
8
|
+
const controller = new AbortController();
|
|
9
|
+
const stop = () => controller.abort();
|
|
10
|
+
process.once("SIGINT", stop);
|
|
11
|
+
process.once("SIGTERM", stop);
|
|
12
|
+
try {
|
|
13
|
+
await run(controller.signal);
|
|
14
|
+
}
|
|
15
|
+
catch (error) {
|
|
16
|
+
if (!controller.signal.aborted)
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
process.removeListener("SIGINT", stop);
|
|
21
|
+
process.removeListener("SIGTERM", stop);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
10
24
|
async function main() {
|
|
11
25
|
const { values, positionals } = parseArgs({
|
|
12
26
|
allowPositionals: true,
|
|
@@ -16,6 +30,8 @@ async function main() {
|
|
|
16
30
|
cloud: { type: "string" },
|
|
17
31
|
name: { type: "string" },
|
|
18
32
|
args: { type: "string" },
|
|
33
|
+
server: { type: "boolean" },
|
|
34
|
+
"device-code": { type: "boolean" },
|
|
19
35
|
},
|
|
20
36
|
});
|
|
21
37
|
if (values.help || positionals.length === 0 || positionals[0] === "help") {
|
|
@@ -23,10 +39,28 @@ async function main() {
|
|
|
23
39
|
process.stdout.write(commandHelp(explicitTopic ? positionals.slice(1) : positionals, explicitTopic));
|
|
24
40
|
return;
|
|
25
41
|
}
|
|
42
|
+
const config = nodeConfigFromEnvironment(process.env);
|
|
43
|
+
if (values.server && !(positionals[0] === "auth" && positionals.length === 1))
|
|
44
|
+
throw new Error("--server is only supported by rome-node auth.");
|
|
45
|
+
if (values["device-code"] && !(positionals[0] === "connect" && positionals.length === 1))
|
|
46
|
+
throw new Error("--device-code is only supported by rome-node connect.");
|
|
47
|
+
if (positionals[0] === "auth" && positionals[1] === "status" && positionals.length === 2) {
|
|
48
|
+
const credential = await readOptionalCallerCredential(config);
|
|
49
|
+
process.stdout.write(`${JSON.stringify(credential ? { configured: true, cloudUrl: credential.cloudUrl } : { configured: false })}\n`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
26
52
|
if (positionals[0] === "auth" && positionals.length === 1) {
|
|
53
|
+
const cloudUrl = cloudOrigin(values.cloud ?? "https://romeos.cc");
|
|
54
|
+
if (values.server) {
|
|
55
|
+
await authorizeServer(cloudUrl, process.env.ROME_INSTANCE_TOKEN, config, () => {
|
|
56
|
+
process.stderr.write("Rome Cloud is temporarily unavailable. Retrying server authorization.\n");
|
|
57
|
+
});
|
|
58
|
+
process.stdout.write(`${JSON.stringify({ configured: true })}\n`);
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
27
61
|
if (process.stdin.isTTY)
|
|
28
62
|
throw new Error("Pass the communication token through stdin, not command arguments.");
|
|
29
|
-
if (await daemonStatus())
|
|
63
|
+
if (await daemonStatus(config))
|
|
30
64
|
throw new Error("Stop the CLI daemon before changing credentials: rome-node daemon stop");
|
|
31
65
|
const chunks = [];
|
|
32
66
|
let size = 0;
|
|
@@ -38,11 +72,7 @@ async function main() {
|
|
|
38
72
|
chunks.push(buffer);
|
|
39
73
|
}
|
|
40
74
|
const token = Buffer.concat(chunks).toString("utf8").trim();
|
|
41
|
-
|
|
42
|
-
throw new Error("Invalid communication token.");
|
|
43
|
-
const cloudUrl = cloudOrigin(values.cloud ?? "https://romeos.cc");
|
|
44
|
-
await gatewayConfig(cloudUrl, token);
|
|
45
|
-
await writePrivateJson(callerCredentialPath(), { cloudUrl, token });
|
|
75
|
+
await configureCaller(cloudUrl, token, config);
|
|
46
76
|
process.stdout.write(`${JSON.stringify({ configured: true })}\n`);
|
|
47
77
|
return;
|
|
48
78
|
}
|
|
@@ -51,36 +81,75 @@ async function main() {
|
|
|
51
81
|
throw new Error("Invalid daemon command.");
|
|
52
82
|
const operation = positionals[1] ?? "start";
|
|
53
83
|
if (operation === "serve") {
|
|
54
|
-
await
|
|
84
|
+
const { serveDaemon } = await import("@rome-os/node-core/daemon");
|
|
85
|
+
await withShutdown((signal) => serveDaemon(config, signal));
|
|
55
86
|
return;
|
|
56
87
|
}
|
|
57
88
|
if (operation === "stop") {
|
|
58
|
-
await stopDaemon();
|
|
89
|
+
await stopDaemon(config);
|
|
59
90
|
process.stdout.write(`${JSON.stringify({ running: false })}\n`);
|
|
60
91
|
return;
|
|
61
92
|
}
|
|
62
93
|
if (operation !== "start" && operation !== "status")
|
|
63
94
|
throw new Error("Unknown daemon command.");
|
|
64
|
-
const state = operation === "start" ? await startDaemon() : await daemonStatus();
|
|
95
|
+
const state = operation === "start" ? await startDaemon(config) : await daemonStatus(config);
|
|
65
96
|
process.stdout.write(`${JSON.stringify(state ? { running: true, pid: state.pid } : { running: false })}\n`);
|
|
66
97
|
return;
|
|
67
98
|
}
|
|
99
|
+
if (positionals[0] === "watch" && positionals.length === 1) {
|
|
100
|
+
await withShutdown(async (signal) => {
|
|
101
|
+
const client = createNodeClient(config);
|
|
102
|
+
let finish;
|
|
103
|
+
const stopped = new Promise((resolve) => {
|
|
104
|
+
finish = resolve;
|
|
105
|
+
});
|
|
106
|
+
const stop = () => {
|
|
107
|
+
client.disconnect();
|
|
108
|
+
finish();
|
|
109
|
+
};
|
|
110
|
+
signal.addEventListener("abort", stop, { once: true });
|
|
111
|
+
try {
|
|
112
|
+
if (signal.aborted)
|
|
113
|
+
return;
|
|
114
|
+
await client.subscribe("connection", (event) => process.stdout.write(`${JSON.stringify(event)}\n`));
|
|
115
|
+
await stopped;
|
|
116
|
+
}
|
|
117
|
+
finally {
|
|
118
|
+
signal.removeEventListener("abort", stop);
|
|
119
|
+
client.disconnect();
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
68
124
|
if (positionals[0] === "connect" && positionals.length === 1) {
|
|
69
|
-
const
|
|
70
|
-
const
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
125
|
+
const { connectHost, defaultDeviceName, loginDevice, loginDeviceCode, createFileCredentialStore, } = await import("@rome-os/node-core/host");
|
|
126
|
+
const name = values.name ?? defaultDeviceName();
|
|
127
|
+
await withShutdown((signal) => connectHost({
|
|
128
|
+
cloudUrl: values.cloud ?? "https://romeos.cc",
|
|
129
|
+
name,
|
|
130
|
+
signal,
|
|
131
|
+
credentials: createFileCredentialStore(join(config.directory, "credential.json")),
|
|
132
|
+
authorize: (origin, deviceName, abort) => values["device-code"]
|
|
133
|
+
? loginDeviceCode(origin, deviceName, abort, (prompt) => {
|
|
134
|
+
process.stderr.write(`Open this URL in a browser on another device:\n${prompt.verificationUri}\n` +
|
|
135
|
+
`User code: ${prompt.userCode}\n` +
|
|
136
|
+
`Or open: ${prompt.verificationUriComplete}\n` +
|
|
137
|
+
`Expires in ${prompt.expiresIn} seconds. Waiting for approval. Ctrl+C cancels.\n`);
|
|
138
|
+
})
|
|
139
|
+
: loginDevice(origin, deviceName, abort, (url) => {
|
|
140
|
+
process.stderr.write(`Open this URL in your browser to authorize ${deviceName}:\n${url}\n`);
|
|
141
|
+
}),
|
|
142
|
+
onEvent: (event) => {
|
|
143
|
+
if (event.type === "authorization_required")
|
|
144
|
+
process.stderr.write("While connect is running, Rome instances in your account can execute programs and read or change files as your OS user.\n");
|
|
145
|
+
else if (event.type === "retrying")
|
|
146
|
+
process.stderr.write("Rome Cloud is unavailable; keeping credentials and retrying.\n");
|
|
147
|
+
else if (event.type === "device")
|
|
148
|
+
process.stderr.write(`Device: ${event.name}\nDevice ID: ${event.deviceId}\n`);
|
|
149
|
+
else
|
|
150
|
+
process.stderr.write(`Connection: ${event.status}\n`);
|
|
151
|
+
},
|
|
152
|
+
}));
|
|
84
153
|
return;
|
|
85
154
|
}
|
|
86
155
|
if (positionals[0] !== "device")
|
|
@@ -102,26 +171,42 @@ async function main() {
|
|
|
102
171
|
throw new Error("--args must contain valid JSON.");
|
|
103
172
|
}
|
|
104
173
|
}
|
|
105
|
-
const
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
174
|
+
const client = createNodeClient(config);
|
|
175
|
+
try {
|
|
176
|
+
if (listing) {
|
|
177
|
+
process.stdout.write(`${JSON.stringify(await client.listDevices())}\n`);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
const result = command === "describe"
|
|
181
|
+
? await client.describe(deviceId)
|
|
182
|
+
: await client.run(deviceId, action, args);
|
|
183
|
+
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
184
|
+
if (!result.ok ||
|
|
185
|
+
(isRecord(result.result) &&
|
|
186
|
+
Object.hasOwn(result.result, "exitCode") &&
|
|
187
|
+
result.result.exitCode !== 0))
|
|
188
|
+
process.exitCode = 1;
|
|
189
|
+
}
|
|
190
|
+
finally {
|
|
191
|
+
client.disconnect();
|
|
112
192
|
}
|
|
113
|
-
const result = parseResponse(body);
|
|
114
|
-
if (!result)
|
|
115
|
-
throw new Error("Invalid response from the CLI daemon.");
|
|
116
|
-
process.stdout.write(`${JSON.stringify(result)}\n`);
|
|
117
|
-
if (!result.ok ||
|
|
118
|
-
!response.ok ||
|
|
119
|
-
(isRecord(result.result) &&
|
|
120
|
-
Object.hasOwn(result.result, "exitCode") &&
|
|
121
|
-
result.result.exitCode !== 0))
|
|
122
|
-
process.exitCode = 1;
|
|
123
193
|
}
|
|
124
194
|
main().catch((error) => {
|
|
195
|
+
if (error instanceof DeviceActionError) {
|
|
196
|
+
process.stdout.write(`${JSON.stringify(error.response)}\n`);
|
|
197
|
+
process.exitCode = 1;
|
|
198
|
+
return;
|
|
199
|
+
}
|
|
200
|
+
if (error instanceof CallerConfigurationError) {
|
|
201
|
+
const messages = {
|
|
202
|
+
not_configured: "Configure a communication token with rome-node auth --cloud <origin> using stdin first.",
|
|
203
|
+
daemon_running: "Stop the CLI daemon before changing credentials: rome-node daemon stop",
|
|
204
|
+
instance_token_required: "Server authorization requires ROME_INSTANCE_TOKEN in the environment.",
|
|
205
|
+
};
|
|
206
|
+
process.stderr.write(`${messages[error.code]}\n`);
|
|
207
|
+
process.exitCode = 1;
|
|
208
|
+
return;
|
|
209
|
+
}
|
|
125
210
|
process.stderr.write(`${error instanceof Error ? error.message : "Rome Node failed."}\n`);
|
|
126
211
|
process.exitCode = 1;
|
|
127
212
|
});
|
package/dist/help.js
CHANGED
|
@@ -6,10 +6,12 @@ Usage:
|
|
|
6
6
|
|
|
7
7
|
Commands:
|
|
8
8
|
connect Keep this computer online as a remote executor.
|
|
9
|
-
auth Configure
|
|
9
|
+
auth Configure caller authorization through stdin or server mode.
|
|
10
|
+
auth status Report local caller configuration without revealing the token.
|
|
10
11
|
device List authorized devices (they may be offline).
|
|
11
12
|
device describe Query a device's platform and supported actions.
|
|
12
13
|
device run Execute an action on a device.
|
|
14
|
+
watch Stream caller connection snapshots and changes as JSON lines.
|
|
13
15
|
daemon Manage the caller's background connection.
|
|
14
16
|
|
|
15
17
|
Agent workflow:
|
|
@@ -23,36 +25,72 @@ Use -h or --help on any command. No arguments also prints this overview.
|
|
|
23
25
|
Set ROME_NODE_CONFIG_DIR to use a separate configuration directory.
|
|
24
26
|
`;
|
|
25
27
|
const commands = {
|
|
26
|
-
|
|
28
|
+
watch: `Usage: rome-node watch
|
|
29
|
+
|
|
30
|
+
Subscribe to caller connection state as newline-delimited JSON.
|
|
31
|
+
Starts the daemon if needed, but does not open its Gateway connection.
|
|
32
|
+
Each connected event includes {transport:"connected",daemon:{pid,protocolVersion,connection}}.
|
|
33
|
+
Local disconnection emits {transport:"disconnected",daemon:null,reason:"connection_lost"}.
|
|
34
|
+
The daemon connection and its Gateway connection are separate states.
|
|
35
|
+
|
|
36
|
+
Reconnection restores the subscription and emits a fresh snapshot, not missed history.
|
|
37
|
+
A watcher does not restart an explicitly stopped daemon. Another command can start it.
|
|
38
|
+
Ctrl+C exits the watcher without stopping the daemon or remote operations.
|
|
39
|
+
These events do not indicate whether individual remote devices are online.
|
|
40
|
+
`,
|
|
41
|
+
connect: `Usage: rome-node connect [--device-code] [--cloud <origin>] [--name <name>]
|
|
27
42
|
|
|
28
43
|
Run this on the computer that will execute programs.
|
|
29
44
|
Open the printed browser URL and approve access when authorization is needed.
|
|
45
|
+
By default, the browser returns to a loopback listener on this computer.
|
|
46
|
+
Use --device-code over SSH or on a headless computer to approve from another device.
|
|
47
|
+
This mode prints a URL and user code without opening a browser or callback listener.
|
|
48
|
+
Stored credentials are reused in either mode. SSH is not detected automatically.
|
|
30
49
|
The process stays in the foreground. Ctrl+C disconnects and preserves authorization.
|
|
31
50
|
Programs run as the OS user who starts connect.
|
|
32
51
|
|
|
33
52
|
Options:
|
|
34
53
|
--cloud <origin> Cloud origin (default: https://romeos.cc).
|
|
35
54
|
--name <name> Name for browser authorization (default: computer hostname).
|
|
55
|
+
--device-code Authorize from another device without a callback or SSH tunnel.
|
|
36
56
|
|
|
37
57
|
Example:
|
|
38
58
|
rome-node connect --name "My computer"
|
|
59
|
+
rome-node connect --device-code --name "Remote Linux" --cloud https://romeos.cc
|
|
39
60
|
`,
|
|
40
|
-
auth: `Usage:
|
|
61
|
+
auth: `Usage:
|
|
62
|
+
rome-node auth [--cloud <origin>] < token-file
|
|
63
|
+
rome-node auth --server [--cloud <origin>]
|
|
41
64
|
|
|
42
65
|
Read an issued communication token (romedev_...) from stdin, validate it with
|
|
43
|
-
Cloud, and save it privately for device commands.
|
|
44
|
-
be exchanged through Cloud. This command does not perform that exchange.
|
|
66
|
+
Cloud, and save it privately for device commands.
|
|
45
67
|
Stop a running caller daemon before changing credentials.
|
|
46
68
|
|
|
69
|
+
With --server, reuse existing caller credentials for this Cloud origin. If missing,
|
|
70
|
+
read ROME_INSTANCE_TOKEN from the environment, exchange it with Cloud, and save
|
|
71
|
+
only the communication token. This does not start a daemon or read Rome's database.
|
|
72
|
+
Transient Cloud failures get up to three attempts with delays of one and five seconds.
|
|
73
|
+
An issued token is reused across validation retries. Existing credentials for
|
|
74
|
+
another Cloud origin are preserved and require manual configuration.
|
|
75
|
+
|
|
47
76
|
Options:
|
|
48
77
|
--cloud <origin> Cloud origin (default: https://romeos.cc).
|
|
78
|
+
--server Authorize using ROME_INSTANCE_TOKEN from the environment.
|
|
49
79
|
|
|
50
80
|
Example:
|
|
51
81
|
rome-node daemon stop
|
|
52
82
|
rome-node auth --cloud https://romeos.cc < /path/to/private-token-file
|
|
83
|
+
rome-node auth --server --cloud https://romeos.cc
|
|
53
84
|
|
|
54
85
|
Output: {"configured":true}
|
|
55
|
-
Pass tokens through stdin. Never put
|
|
86
|
+
Pass communication tokens through stdin. Never put tokens in command arguments or print them.
|
|
87
|
+
`,
|
|
88
|
+
"auth status": `Usage: rome-node auth status
|
|
89
|
+
|
|
90
|
+
Report local caller configuration without contacting Cloud or starting the daemon.
|
|
91
|
+
Output: {"configured":true,"cloudUrl":"https://romeos.cc"} or {"configured":false}.
|
|
92
|
+
This checks the local credential format, not its validity at Cloud.
|
|
93
|
+
The token is never printed.
|
|
56
94
|
`,
|
|
57
95
|
device: `Usage:
|
|
58
96
|
rome-node device
|
|
@@ -112,8 +150,9 @@ Output:
|
|
|
112
150
|
Check exec's exitCode even when ok is true. The local exit status is 1 for
|
|
113
151
|
action failures, nonzero remote exit codes, or signal termination, otherwise 0.
|
|
114
152
|
Local setup and argument errors print diagnostics to stderr and exit 1.
|
|
115
|
-
|
|
116
|
-
|
|
153
|
+
Output is collected in memory and returned in full after program exit.
|
|
154
|
+
The CLI does not truncate output or impose message or send-buffer size limits.
|
|
155
|
+
Transport limits still apply. Connection loss returns unknown_outcome.
|
|
117
156
|
|
|
118
157
|
The caller waits up to 60 seconds for a response. unknown_outcome means execution
|
|
119
158
|
may have started or completed. A local timeout does not cancel the remote program.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
export {
|
|
3
|
-
export { CloudError, cloudOrigin, cloudRequest, gatewayConfig } from "./cloud.js";
|
|
4
|
-
export { actionError, isRecord, parseResponse, type ActionResponse, type ActionRequest, } from "./actions.js";
|
|
5
|
-
export { createNodeSocket } from "./socket.js";
|
|
1
|
+
export * from "@rome-os/node-core";
|
|
2
|
+
export { createNodeSocket } from "@rome-os/node-core/socket";
|
package/dist/index.js
CHANGED
|
@@ -1,5 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
export {
|
|
3
|
-
export { CloudError, cloudOrigin, cloudRequest, gatewayConfig } from "./cloud.js";
|
|
4
|
-
export { actionError, isRecord, parseResponse, } from "./actions.js";
|
|
5
|
-
export { createNodeSocket } from "./socket.js";
|
|
1
|
+
export * from "@rome-os/node-core";
|
|
2
|
+
export { createNodeSocket } from "@rome-os/node-core/socket";
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rome-os/node",
|
|
3
|
-
"version": "0.1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "0.1.2",
|
|
4
|
+
"description": "Standalone CLI for Rome remote devices",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"engines": {
|
|
@@ -22,21 +22,22 @@
|
|
|
22
22
|
}
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"
|
|
25
|
+
"@rome-os/node-core": "^0.1.1"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@rstest/core": "^0.11.11",
|
|
29
29
|
"@types/node": "25.2.3",
|
|
30
30
|
"@types/ws": "^8.18.0",
|
|
31
|
-
"typescript": "^5.9.3"
|
|
31
|
+
"typescript": "^5.9.3",
|
|
32
|
+
"ws": "^8.18.0"
|
|
32
33
|
},
|
|
33
34
|
"repository": {
|
|
34
35
|
"type": "git",
|
|
35
36
|
"url": "git+https://github.com/rome-os/rome.git",
|
|
36
|
-
"directory": "packages/rome-node"
|
|
37
|
+
"directory": "packages/rome-node-cli"
|
|
37
38
|
},
|
|
38
39
|
"scripts": {
|
|
39
|
-
"build": "tsc -p tsconfig.build.json",
|
|
40
|
+
"build": "pnpm --filter @rome-os/node-core build && node -e \"require('node:fs').rmSync('dist', {recursive:true, force:true})\" && tsc -p tsconfig.build.json",
|
|
40
41
|
"typecheck": "tsc --noEmit",
|
|
41
42
|
"test": "pnpm build && ../../scripts/test-env.sh rstest"
|
|
42
43
|
}
|
package/dist/actions.d.ts
DELETED
|
@@ -1,20 +0,0 @@
|
|
|
1
|
-
export interface ActionRequest {
|
|
2
|
-
type: "request";
|
|
3
|
-
action: string;
|
|
4
|
-
args: unknown;
|
|
5
|
-
}
|
|
6
|
-
export type ActionResponse = {
|
|
7
|
-
type: "response";
|
|
8
|
-
ok: true;
|
|
9
|
-
result: unknown;
|
|
10
|
-
} | {
|
|
11
|
-
type: "response";
|
|
12
|
-
ok: false;
|
|
13
|
-
error: {
|
|
14
|
-
code: string;
|
|
15
|
-
message: string;
|
|
16
|
-
};
|
|
17
|
-
};
|
|
18
|
-
export declare function actionError(code: string, message: string): ActionResponse;
|
|
19
|
-
export declare function isRecord(value: unknown): value is Record<string, unknown>;
|
|
20
|
-
export declare function parseResponse(value: unknown): ActionResponse | null;
|
package/dist/actions.js
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
export function actionError(code, message) {
|
|
2
|
-
return { type: "response", ok: false, error: { code, message } };
|
|
3
|
-
}
|
|
4
|
-
export function isRecord(value) {
|
|
5
|
-
return Boolean(value) && typeof value === "object" && !Array.isArray(value);
|
|
6
|
-
}
|
|
7
|
-
export function parseResponse(value) {
|
|
8
|
-
if (!isRecord(value) || value.type !== "response")
|
|
9
|
-
return null;
|
|
10
|
-
if (value.ok === true && Object.hasOwn(value, "result"))
|
|
11
|
-
return { type: "response", ok: true, result: value.result };
|
|
12
|
-
if (value.ok === false &&
|
|
13
|
-
isRecord(value.error) &&
|
|
14
|
-
typeof value.error.code === "string" &&
|
|
15
|
-
typeof value.error.message === "string")
|
|
16
|
-
return actionError(value.error.code, value.error.message);
|
|
17
|
-
return null;
|
|
18
|
-
}
|
package/dist/client.d.ts
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { type GatewayMessage, type OutboundEnvelope } from "./protocol.js";
|
|
2
|
-
export interface ClientSocket {
|
|
3
|
-
readyState: number;
|
|
4
|
-
bufferedAmount: number;
|
|
5
|
-
send(data: string): void;
|
|
6
|
-
close(code?: number, reason?: string): void;
|
|
7
|
-
addEventListener(type: "open", listener: () => void): void;
|
|
8
|
-
addEventListener(type: "message", listener: (event: {
|
|
9
|
-
data: unknown;
|
|
10
|
-
}) => void): void;
|
|
11
|
-
addEventListener(type: "close", listener: (event: {
|
|
12
|
-
code: number;
|
|
13
|
-
}) => void): void;
|
|
14
|
-
addEventListener(type: "error", listener: () => void): void;
|
|
15
|
-
}
|
|
16
|
-
export type ConnectionStatus = "connecting" | "online" | "retrying" | "revoked" | "superseded" | "stopped";
|
|
17
|
-
export interface GatewayClientOptions {
|
|
18
|
-
gatewayUrl: string;
|
|
19
|
-
deviceToken: string;
|
|
20
|
-
createSocket(url: string, authorization: string): ClientSocket;
|
|
21
|
-
onMessage(message: GatewayMessage): void;
|
|
22
|
-
onStatus?(status: ConnectionStatus): void;
|
|
23
|
-
random?(): number;
|
|
24
|
-
beforeConnect?(): Promise<string | null>;
|
|
25
|
-
}
|
|
26
|
-
export declare function connectGateway(options: GatewayClientOptions): {
|
|
27
|
-
send(envelope: OutboundEnvelope): boolean;
|
|
28
|
-
stop(): void;
|
|
29
|
-
};
|