1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
use clap::{Args, Subcommand};
#[derive(Args, Debug)]
pub(crate) struct IpcArgs {
#[command(subcommand)]
pub(crate) action: IpcAction,
}
#[derive(Subcommand, Debug)]
pub(crate) enum IpcAction {
/// List active arf sessions as JSON
///
/// Returns a JSON object with a `sessions` array. Each entry contains
/// pid, r_version, r_home, socket_path, cwd, started_at, session_type,
/// log_file, and history_session_id.
/// Returns `{"sessions": []}` when no sessions are running (exit 0).
#[command(after_long_help = "\
Examples:
List all sessions:
$ arf ipc list
Extract PIDs with jq:
$ arf ipc list | jq '.sessions[].pid'")]
List,
/// Evaluate R code and return captured output as JSON
///
/// Returns a JSON object with stdout, stderr, value, and error fields.
/// All fields are always present (null when not applicable). In silent
/// mode (the default), the printed result appears in value rather than
/// stdout. R evaluation errors are included in the error field with
/// exit code 0 — they are a normal response, not an IPC failure.
#[command(after_long_help = "\
Examples:
Silent evaluation only runs allowlisted calls, so start the server with the
ones these examples use:
$ arf headless --ipc-eval-allow-function '+' \\
--ipc-eval-allow-function 'Sys.sleep' \\
--ipc-eval-allow-function 'getwd' &
Evaluate an expression:
$ arf ipc eval '1 + 1'
Pipe code via stdin:
$ echo '1 + 1' | arf ipc eval
Bound the wait for a reply without cancelling the R evaluation:
$ arf ipc eval --timeout 10000 'Sys.sleep(5); 42'
Run it where the session shows it, which is why it needs no allowlist entry:
$ arf ipc eval --visible 'cat(\"hello\\n\")'
Target a specific session when multiple are running:
$ arf ipc eval --pid 12345 'getwd()'
Extract the value with jq:
$ arf ipc eval '1 + 1' | jq -r '.value'")]
Eval {
/// R code to evaluate (reads from stdin if omitted)
code: Option<String>,
/// PID of the target arf session (optional if only one session is running)
#[arg(long)]
pid: Option<u32>,
/// Also show output in the session (REPL or headless stdout)
#[arg(long)]
visible: bool,
/// Timeout in milliseconds for waiting for the response (default: 300000 = 5 minutes).
/// This does NOT cancel the R evaluation — long-running code keeps R busy after timeout.
#[arg(long)]
timeout: Option<u64>,
},
/// Send code as user input to a running session
///
/// Unlike `eval`, the code is executed as if the user typed it at the
/// prompt. Output goes to the session's output streams (the REPL
/// terminal or headless stdout/log file) and is not captured in the
/// IPC response. Returns JSON `{"accepted": true}` on success.
#[command(after_long_help = "\
Examples:
Send code that appears in the session output:
$ arf ipc send 'library(dplyr)'
Pipe code via stdin:
$ echo 'library(dplyr)' | arf ipc send
Target a specific session:
$ arf ipc send --pid 12345 'print(mtcars)'")]
Send {
/// R code to send (reads from stdin if omitted)
code: Option<String>,
/// PID of the target arf session (optional if only one session is running)
#[arg(long)]
pid: Option<u32>,
},
/// Get session information as JSON (arf + R environment)
///
/// Returns structured session information including arf version, OS,
/// log file path (if any), and R environment details (loaded namespaces,
/// attached packages, library paths, locale, working directory, etc.).
///
/// Unlike `eval "sessionInfo()"`, this returns machine-readable JSON
/// that can be piped to jq or consumed by AI agents without parsing
/// human-readable text output. When R is busy, arf-side information is
/// still returned with `r` set to null and `r_unavailable_reason`
/// / `hint` fields explaining the situation. The `log_file` field is
/// null when no dedicated log file is configured (logging to stderr
/// without redirection). The JSON shape is always consistent — check
/// `r` for null to determine availability.
///
/// Output is pretty-printed when writing to a terminal, and compact
/// JSON when piped to another program.
#[command(after_long_help = "\
Examples:
Get session info (pretty-printed on terminal):
$ arf ipc session
Extract R version with jq:
$ arf ipc session | jq -r '.r.version'
Check loaded namespaces:
$ arf ipc session | jq '.r.loaded_namespaces'")]
Session {
/// PID of the target arf session (optional if only one session is running)
#[arg(long)]
pid: Option<u32>,
},
/// Shut down a running arf headless session (returns JSON `{"accepted": true}`)
#[command(after_long_help = "\
Examples:
Shut down the only running session:
$ arf ipc shutdown
Shut down a specific session:
$ arf ipc shutdown --pid 12345")]
Shutdown {
/// PID of the target arf session (optional if only one session is running)
#[arg(long)]
pid: Option<u32>,
},
/// Query command history from a running session
///
/// Returns history entries as JSON, newest first. Output is
/// pretty-printed when writing to a terminal and compact when piped.
/// Only completed commands are recorded; a currently executing
/// command will not appear until it finishes.
#[command(after_long_help = "\
Examples:
Show recent history (default 50 entries):
$ arf ipc history
Show last 10 entries:
$ arf ipc history --limit 10
Search for commands containing 'dplyr':
$ arf ipc history --grep dplyr
Filter by working directory:
$ arf ipc history --cwd /path/to/project
Show entries since a date:
$ arf ipc history --since 2026-03-29
Include history from all sessions (not just current):
$ arf ipc history --all-sessions
Combine filters:
$ arf ipc history --grep 'library' --limit 20
Extract commands with jq:
$ arf ipc history | jq -r '.entries[].command'")]
History {
/// Maximum number of entries to return (must be positive)
#[arg(long, default_value = "50", value_parser = clap::value_parser!(i64).range(1..))]
limit: i64,
/// Include entries from all sessions, not just the current one
#[arg(long)]
all_sessions: bool,
/// Filter entries by exact working directory
#[arg(long)]
cwd: Option<String>,
/// Filter entries whose command contains this substring
#[arg(long)]
grep: Option<String>,
/// Only return entries after this timestamp (RFC 3339 or YYYY-MM-DD)
#[arg(long)]
since: Option<String>,
/// PID of the target arf session (optional if only one session is running)
#[arg(long)]
pid: Option<u32>,
},
}