reallink-cli 0.1.13 → 0.1.14
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 +5 -1
- package/bin/reallink.cjs +10 -3
- package/package.json +1 -1
- package/prebuilt/win32-x64/reallink-cli.exe +0 -0
- package/rust/Cargo.lock +1 -1
- package/rust/Cargo.toml +1 -1
- package/rust/src/main.rs +561 -0
package/README.md
CHANGED
|
@@ -75,6 +75,9 @@ reallink tool register --manifest ./spec/tools/trace-placeholder.tool.jsonc
|
|
|
75
75
|
reallink tool publish --tool-id trace.placeholder --channel ga --visibility public
|
|
76
76
|
reallink tool enable --tool-id trace.placeholder --project-id prj_xxx
|
|
77
77
|
reallink tool enable --tool-id trace.placeholder --user-id usr_xxx
|
|
78
|
+
reallink tool context put --context-id aa-main --project-id prj_xxx --text "Build notes and constraints"
|
|
79
|
+
reallink tool context get --context-id aa-main --project-id prj_xxx
|
|
80
|
+
reallink tool prompt --tool-id agent.chat --project-id prj_xxx --prompt "Summarize latest trace findings" --session-key aa-main
|
|
78
81
|
reallink tool run --tool-id trace.placeholder --project-id prj_xxx --input-file ./run-input.jsonc --idempotency-key run-001
|
|
79
82
|
reallink tool runs --project-id prj_xxx
|
|
80
83
|
reallink tool get-run --run-id trun_xxx
|
|
@@ -104,7 +107,8 @@ reallink link plugin install --project-id prj_xxx --name RealLinkUnreal --url ht
|
|
|
104
107
|
- If the server rejects `tools:*` as invalid, CLI automatically retries with compatible scopes.
|
|
105
108
|
- You can always pass explicit scopes via repeated `--scope`.
|
|
106
109
|
|
|
107
|
-
`tool register`, `tool enable/disable` metadata, and `tool
|
|
110
|
+
`tool register`, `tool enable/disable` metadata, `tool run --input-file`, and `tool prompt --input-file` accept JSONC.
|
|
111
|
+
`tool context put --text-file` accepts plain UTF-8 text files and stores scoped context via API gateway.
|
|
108
112
|
|
|
109
113
|
## Session Behavior
|
|
110
114
|
|
package/bin/reallink.cjs
CHANGED
|
@@ -34,8 +34,15 @@ function runBinary(binaryPath) {
|
|
|
34
34
|
});
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
function resolvePreferredBinary() {
|
|
38
|
+
if (fs.existsSync(prebuiltBinaryPath)) {
|
|
39
|
+
return prebuiltBinaryPath;
|
|
40
|
+
}
|
|
41
|
+
return resolveExistingReleaseBinary();
|
|
42
|
+
}
|
|
43
|
+
|
|
37
44
|
function ensureBinary() {
|
|
38
|
-
if (
|
|
45
|
+
if (resolvePreferredBinary()) {
|
|
39
46
|
return true;
|
|
40
47
|
}
|
|
41
48
|
|
|
@@ -56,14 +63,14 @@ function ensureBinary() {
|
|
|
56
63
|
return false;
|
|
57
64
|
}
|
|
58
65
|
|
|
59
|
-
return build.status === 0 && !!
|
|
66
|
+
return build.status === 0 && !!resolvePreferredBinary();
|
|
60
67
|
}
|
|
61
68
|
|
|
62
69
|
if (!ensureBinary()) {
|
|
63
70
|
process.exit(1);
|
|
64
71
|
}
|
|
65
72
|
|
|
66
|
-
const binaryPath =
|
|
73
|
+
const binaryPath = resolvePreferredBinary();
|
|
67
74
|
const result = runBinary(binaryPath);
|
|
68
75
|
if (result.error) {
|
|
69
76
|
console.error(`Failed to run reallink binary: ${result.error.message}`);
|
package/package.json
CHANGED
|
Binary file
|
package/rust/Cargo.lock
CHANGED
package/rust/Cargo.toml
CHANGED
package/rust/src/main.rs
CHANGED
|
@@ -82,6 +82,10 @@ enum Commands {
|
|
|
82
82
|
#[command(subcommand)]
|
|
83
83
|
command: FileCommands,
|
|
84
84
|
},
|
|
85
|
+
Skill {
|
|
86
|
+
#[command(subcommand)]
|
|
87
|
+
command: SkillCommands,
|
|
88
|
+
},
|
|
85
89
|
Tool {
|
|
86
90
|
#[command(subcommand)]
|
|
87
91
|
command: ToolCommands,
|
|
@@ -192,10 +196,16 @@ enum ToolCommands {
|
|
|
192
196
|
Publish(ToolPublishArgs),
|
|
193
197
|
Enable(ToolEnableArgs),
|
|
194
198
|
Disable(ToolDisableArgs),
|
|
199
|
+
Context {
|
|
200
|
+
#[command(subcommand)]
|
|
201
|
+
command: ToolContextCommands,
|
|
202
|
+
},
|
|
203
|
+
Prompt(ToolPromptArgs),
|
|
195
204
|
Run(ToolRunArgs),
|
|
196
205
|
Runs(ToolRunsArgs),
|
|
197
206
|
GetRun(ToolGetRunArgs),
|
|
198
207
|
RunEvents(ToolRunEventsArgs),
|
|
208
|
+
TraceStatus(ToolTraceStatusArgs),
|
|
199
209
|
Local {
|
|
200
210
|
#[command(subcommand)]
|
|
201
211
|
command: ToolLocalCommands,
|
|
@@ -209,6 +219,35 @@ enum ToolLocalCommands {
|
|
|
209
219
|
CompleteRun(ToolLocalCompleteRunArgs),
|
|
210
220
|
}
|
|
211
221
|
|
|
222
|
+
#[derive(Subcommand)]
|
|
223
|
+
enum ToolContextCommands {
|
|
224
|
+
Put(ToolContextPutArgs),
|
|
225
|
+
Get(ToolContextGetArgs),
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
#[derive(Subcommand)]
|
|
229
|
+
enum SkillCommands {
|
|
230
|
+
List(ToolListArgs),
|
|
231
|
+
Register(ToolRegisterArgs),
|
|
232
|
+
Publish(ToolPublishArgs),
|
|
233
|
+
Enable(ToolEnableArgs),
|
|
234
|
+
Disable(ToolDisableArgs),
|
|
235
|
+
Context {
|
|
236
|
+
#[command(subcommand)]
|
|
237
|
+
command: ToolContextCommands,
|
|
238
|
+
},
|
|
239
|
+
Prompt(ToolPromptArgs),
|
|
240
|
+
Run(ToolRunArgs),
|
|
241
|
+
Runs(ToolRunsArgs),
|
|
242
|
+
GetRun(ToolGetRunArgs),
|
|
243
|
+
RunEvents(ToolRunEventsArgs),
|
|
244
|
+
TraceStatus(ToolTraceStatusArgs),
|
|
245
|
+
Local {
|
|
246
|
+
#[command(subcommand)]
|
|
247
|
+
command: ToolLocalCommands,
|
|
248
|
+
},
|
|
249
|
+
}
|
|
250
|
+
|
|
212
251
|
#[derive(Subcommand)]
|
|
213
252
|
enum LogsCommands {
|
|
214
253
|
Status,
|
|
@@ -478,6 +517,46 @@ struct ToolRunArgs {
|
|
|
478
517
|
metadata_file: Option<PathBuf>,
|
|
479
518
|
#[arg(long, help = "Idempotency key for deduplicating retries")]
|
|
480
519
|
idempotency_key: Option<String>,
|
|
520
|
+
#[arg(long, action = ArgAction::SetTrue, help = "Wait until the run reaches a terminal state")]
|
|
521
|
+
wait: bool,
|
|
522
|
+
#[arg(long, default_value_t = 300_000, help = "Wait timeout in milliseconds")]
|
|
523
|
+
timeout_ms: u64,
|
|
524
|
+
#[arg(long, default_value_t = 1_500, help = "Polling interval in milliseconds")]
|
|
525
|
+
poll_interval_ms: u64,
|
|
526
|
+
#[arg(long)]
|
|
527
|
+
base_url: Option<String>,
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
#[derive(Args)]
|
|
531
|
+
struct ToolPromptArgs {
|
|
532
|
+
#[arg(long)]
|
|
533
|
+
tool_id: String,
|
|
534
|
+
#[arg(long)]
|
|
535
|
+
prompt: String,
|
|
536
|
+
#[arg(long)]
|
|
537
|
+
org_id: Option<String>,
|
|
538
|
+
#[arg(long)]
|
|
539
|
+
project_id: Option<String>,
|
|
540
|
+
#[arg(long, help = "Optional system prompt")]
|
|
541
|
+
system_prompt: Option<String>,
|
|
542
|
+
#[arg(long, help = "Optional model hint passed to remote runtime")]
|
|
543
|
+
model: Option<String>,
|
|
544
|
+
#[arg(long, help = "Session key used by remote runtime for context restore")]
|
|
545
|
+
session_key: Option<String>,
|
|
546
|
+
#[arg(long, help = "Inline JSON object merged into tool input")]
|
|
547
|
+
input_json: Option<String>,
|
|
548
|
+
#[arg(long, help = "Path to JSON/JSONC file merged into tool input")]
|
|
549
|
+
input_file: Option<PathBuf>,
|
|
550
|
+
#[arg(long, help = "Path to JSON/JSONC metadata file")]
|
|
551
|
+
metadata_file: Option<PathBuf>,
|
|
552
|
+
#[arg(long, help = "Idempotency key for deduplicating retries")]
|
|
553
|
+
idempotency_key: Option<String>,
|
|
554
|
+
#[arg(long, action = ArgAction::SetTrue, help = "Wait until the run reaches a terminal state")]
|
|
555
|
+
wait: bool,
|
|
556
|
+
#[arg(long, default_value_t = 300_000, help = "Wait timeout in milliseconds")]
|
|
557
|
+
timeout_ms: u64,
|
|
558
|
+
#[arg(long, default_value_t = 1_500, help = "Polling interval in milliseconds")]
|
|
559
|
+
poll_interval_ms: u64,
|
|
481
560
|
#[arg(long)]
|
|
482
561
|
base_url: Option<String>,
|
|
483
562
|
}
|
|
@@ -522,6 +601,44 @@ struct ToolRunEventsArgs {
|
|
|
522
601
|
base_url: Option<String>,
|
|
523
602
|
}
|
|
524
603
|
|
|
604
|
+
#[derive(Args)]
|
|
605
|
+
struct ToolTraceStatusArgs {
|
|
606
|
+
#[arg(long)]
|
|
607
|
+
project_id: String,
|
|
608
|
+
#[arg(long)]
|
|
609
|
+
limit: Option<u32>,
|
|
610
|
+
#[arg(long)]
|
|
611
|
+
base_url: Option<String>,
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
#[derive(Args)]
|
|
615
|
+
struct ToolContextPutArgs {
|
|
616
|
+
#[arg(long)]
|
|
617
|
+
context_id: String,
|
|
618
|
+
#[arg(long)]
|
|
619
|
+
text: Option<String>,
|
|
620
|
+
#[arg(long, help = "Path to UTF-8 text file for context payload")]
|
|
621
|
+
text_file: Option<PathBuf>,
|
|
622
|
+
#[arg(long)]
|
|
623
|
+
org_id: Option<String>,
|
|
624
|
+
#[arg(long)]
|
|
625
|
+
project_id: Option<String>,
|
|
626
|
+
#[arg(long)]
|
|
627
|
+
base_url: Option<String>,
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
#[derive(Args)]
|
|
631
|
+
struct ToolContextGetArgs {
|
|
632
|
+
#[arg(long)]
|
|
633
|
+
context_id: String,
|
|
634
|
+
#[arg(long)]
|
|
635
|
+
org_id: Option<String>,
|
|
636
|
+
#[arg(long)]
|
|
637
|
+
project_id: Option<String>,
|
|
638
|
+
#[arg(long)]
|
|
639
|
+
base_url: Option<String>,
|
|
640
|
+
}
|
|
641
|
+
|
|
525
642
|
#[derive(Args)]
|
|
526
643
|
struct ToolLocalCatalogArgs {
|
|
527
644
|
#[arg(long)]
|
|
@@ -4943,6 +5060,141 @@ async fn tool_disable_command(client: &reqwest::Client, args: ToolDisableArgs) -
|
|
|
4943
5060
|
.await
|
|
4944
5061
|
}
|
|
4945
5062
|
|
|
5063
|
+
async fn tool_prompt_command(client: &reqwest::Client, args: ToolPromptArgs) -> Result<()> {
|
|
5064
|
+
let mut session = load_session()?;
|
|
5065
|
+
apply_base_url_override(&mut session, args.base_url);
|
|
5066
|
+
|
|
5067
|
+
if args.input_json.is_some() && args.input_file.is_some() {
|
|
5068
|
+
return Err(anyhow!(
|
|
5069
|
+
"Provide either --input-json or --input-file, not both"
|
|
5070
|
+
));
|
|
5071
|
+
}
|
|
5072
|
+
|
|
5073
|
+
let prompt = args.prompt.trim();
|
|
5074
|
+
if prompt.is_empty() {
|
|
5075
|
+
return Err(anyhow!("--prompt is required"));
|
|
5076
|
+
}
|
|
5077
|
+
|
|
5078
|
+
let merged_input = if let Some(path) = args.input_file {
|
|
5079
|
+
load_jsonc_file(&path, "tool prompt input")?
|
|
5080
|
+
} else if let Some(input_json) = args.input_json {
|
|
5081
|
+
parse_jsonc_str(&input_json, "tool prompt input")?
|
|
5082
|
+
} else {
|
|
5083
|
+
serde_json::Value::Object(serde_json::Map::new())
|
|
5084
|
+
};
|
|
5085
|
+
let mut input_map = parse_object_from_value(merged_input, "tool prompt input")?;
|
|
5086
|
+
input_map.insert(
|
|
5087
|
+
"prompt".to_string(),
|
|
5088
|
+
serde_json::Value::String(prompt.to_string()),
|
|
5089
|
+
);
|
|
5090
|
+
if let Some(system_prompt) = args.system_prompt {
|
|
5091
|
+
let normalized = system_prompt.trim();
|
|
5092
|
+
if !normalized.is_empty() {
|
|
5093
|
+
input_map.insert(
|
|
5094
|
+
"systemPrompt".to_string(),
|
|
5095
|
+
serde_json::Value::String(normalized.to_string()),
|
|
5096
|
+
);
|
|
5097
|
+
}
|
|
5098
|
+
}
|
|
5099
|
+
if let Some(model) = args.model {
|
|
5100
|
+
let normalized = model.trim();
|
|
5101
|
+
if !normalized.is_empty() {
|
|
5102
|
+
input_map.insert(
|
|
5103
|
+
"model".to_string(),
|
|
5104
|
+
serde_json::Value::String(normalized.to_string()),
|
|
5105
|
+
);
|
|
5106
|
+
}
|
|
5107
|
+
}
|
|
5108
|
+
if let Some(session_key) = args.session_key {
|
|
5109
|
+
let normalized = session_key.trim();
|
|
5110
|
+
if !normalized.is_empty() {
|
|
5111
|
+
input_map.insert(
|
|
5112
|
+
"sessionKey".to_string(),
|
|
5113
|
+
serde_json::Value::String(normalized.to_string()),
|
|
5114
|
+
);
|
|
5115
|
+
}
|
|
5116
|
+
}
|
|
5117
|
+
|
|
5118
|
+
let mut body = serde_json::Map::new();
|
|
5119
|
+
body.insert(
|
|
5120
|
+
"toolId".to_string(),
|
|
5121
|
+
serde_json::Value::String(args.tool_id),
|
|
5122
|
+
);
|
|
5123
|
+
body.insert("input".to_string(), serde_json::Value::Object(input_map));
|
|
5124
|
+
if let Some(org_id) = args.org_id {
|
|
5125
|
+
body.insert("orgId".to_string(), serde_json::Value::String(org_id));
|
|
5126
|
+
}
|
|
5127
|
+
if let Some(project_id) = args.project_id {
|
|
5128
|
+
body.insert(
|
|
5129
|
+
"projectId".to_string(),
|
|
5130
|
+
serde_json::Value::String(project_id),
|
|
5131
|
+
);
|
|
5132
|
+
}
|
|
5133
|
+
|
|
5134
|
+
let mut metadata_map = if let Some(path) = args.metadata_file {
|
|
5135
|
+
let metadata = load_jsonc_file(&path, "tool prompt metadata")?;
|
|
5136
|
+
parse_object_from_value(metadata, "tool prompt metadata")?
|
|
5137
|
+
} else {
|
|
5138
|
+
serde_json::Map::new()
|
|
5139
|
+
};
|
|
5140
|
+
if let Some(idempotency_key) = args.idempotency_key {
|
|
5141
|
+
let normalized = idempotency_key.trim();
|
|
5142
|
+
if !normalized.is_empty() {
|
|
5143
|
+
metadata_map.insert(
|
|
5144
|
+
"idempotencyKey".to_string(),
|
|
5145
|
+
serde_json::Value::String(normalized.to_string()),
|
|
5146
|
+
);
|
|
5147
|
+
}
|
|
5148
|
+
}
|
|
5149
|
+
metadata_map.insert(
|
|
5150
|
+
"clientCommand".to_string(),
|
|
5151
|
+
serde_json::Value::String("tool.prompt".to_string()),
|
|
5152
|
+
);
|
|
5153
|
+
if !metadata_map.is_empty() {
|
|
5154
|
+
body.insert(
|
|
5155
|
+
"metadata".to_string(),
|
|
5156
|
+
serde_json::Value::Object(metadata_map),
|
|
5157
|
+
);
|
|
5158
|
+
}
|
|
5159
|
+
|
|
5160
|
+
let response = authed_request(
|
|
5161
|
+
client,
|
|
5162
|
+
&mut session,
|
|
5163
|
+
Method::POST,
|
|
5164
|
+
"/tools/runs",
|
|
5165
|
+
Some(serde_json::Value::Object(body)),
|
|
5166
|
+
)
|
|
5167
|
+
.await?;
|
|
5168
|
+
if !response.status().is_success() {
|
|
5169
|
+
let body = read_error_body(response).await;
|
|
5170
|
+
return Err(anyhow!("tool prompt failed: {}", body));
|
|
5171
|
+
}
|
|
5172
|
+
let payload: serde_json::Value = response.json().await?;
|
|
5173
|
+
let payload = if args.wait {
|
|
5174
|
+
let run_id = payload
|
|
5175
|
+
.get("run")
|
|
5176
|
+
.and_then(|run| run.get("id"))
|
|
5177
|
+
.and_then(serde_json::Value::as_str)
|
|
5178
|
+
.ok_or_else(|| anyhow!("tool prompt response missing run.id"))?;
|
|
5179
|
+
wait_for_tool_run_completion(
|
|
5180
|
+
client,
|
|
5181
|
+
&mut session,
|
|
5182
|
+
run_id,
|
|
5183
|
+
args.timeout_ms,
|
|
5184
|
+
args.poll_interval_ms,
|
|
5185
|
+
)
|
|
5186
|
+
.await?
|
|
5187
|
+
} else {
|
|
5188
|
+
payload
|
|
5189
|
+
};
|
|
5190
|
+
println!(
|
|
5191
|
+
"{}",
|
|
5192
|
+
serde_json::to_string_pretty(payload.get("run").unwrap_or(&payload))?
|
|
5193
|
+
);
|
|
5194
|
+
save_session(&session)?;
|
|
5195
|
+
Ok(())
|
|
5196
|
+
}
|
|
5197
|
+
|
|
4946
5198
|
async fn tool_run_command(client: &reqwest::Client, args: ToolRunArgs) -> Result<()> {
|
|
4947
5199
|
let mut session = load_session()?;
|
|
4948
5200
|
apply_base_url_override(&mut session, args.base_url);
|
|
@@ -5014,6 +5266,23 @@ async fn tool_run_command(client: &reqwest::Client, args: ToolRunArgs) -> Result
|
|
|
5014
5266
|
return Err(anyhow!("tool run failed: {}", body));
|
|
5015
5267
|
}
|
|
5016
5268
|
let payload: serde_json::Value = response.json().await?;
|
|
5269
|
+
let payload = if args.wait {
|
|
5270
|
+
let run_id = payload
|
|
5271
|
+
.get("run")
|
|
5272
|
+
.and_then(|run| run.get("id"))
|
|
5273
|
+
.and_then(serde_json::Value::as_str)
|
|
5274
|
+
.ok_or_else(|| anyhow!("tool run response missing run.id"))?;
|
|
5275
|
+
wait_for_tool_run_completion(
|
|
5276
|
+
client,
|
|
5277
|
+
&mut session,
|
|
5278
|
+
run_id,
|
|
5279
|
+
args.timeout_ms,
|
|
5280
|
+
args.poll_interval_ms,
|
|
5281
|
+
)
|
|
5282
|
+
.await?
|
|
5283
|
+
} else {
|
|
5284
|
+
payload
|
|
5285
|
+
};
|
|
5017
5286
|
println!(
|
|
5018
5287
|
"{}",
|
|
5019
5288
|
serde_json::to_string_pretty(payload.get("run").unwrap_or(&payload))?
|
|
@@ -5022,6 +5291,42 @@ async fn tool_run_command(client: &reqwest::Client, args: ToolRunArgs) -> Result
|
|
|
5022
5291
|
Ok(())
|
|
5023
5292
|
}
|
|
5024
5293
|
|
|
5294
|
+
async fn wait_for_tool_run_completion(
|
|
5295
|
+
client: &reqwest::Client,
|
|
5296
|
+
session: &mut SessionConfig,
|
|
5297
|
+
run_id: &str,
|
|
5298
|
+
timeout_ms: u64,
|
|
5299
|
+
poll_interval_ms: u64,
|
|
5300
|
+
) -> Result<serde_json::Value> {
|
|
5301
|
+
let poll_interval = Duration::from_millis(poll_interval_ms.max(250));
|
|
5302
|
+
let deadline = std::time::Instant::now() + Duration::from_millis(timeout_ms.max(1_000));
|
|
5303
|
+
|
|
5304
|
+
loop {
|
|
5305
|
+
let path = format!("/tools/runs/{}", run_id);
|
|
5306
|
+
let response = authed_request(client, session, Method::GET, &path, None).await?;
|
|
5307
|
+
if !response.status().is_success() {
|
|
5308
|
+
let body = read_error_body(response).await;
|
|
5309
|
+
return Err(anyhow!("tool wait failed: {}", body));
|
|
5310
|
+
}
|
|
5311
|
+
let payload: serde_json::Value = response.json().await?;
|
|
5312
|
+
let status = payload
|
|
5313
|
+
.get("run")
|
|
5314
|
+
.and_then(|run| run.get("status"))
|
|
5315
|
+
.and_then(serde_json::Value::as_str)
|
|
5316
|
+
.unwrap_or_default();
|
|
5317
|
+
if matches!(status, "succeeded" | "failed" | "cancelled") {
|
|
5318
|
+
return Ok(payload);
|
|
5319
|
+
}
|
|
5320
|
+
if std::time::Instant::now() >= deadline {
|
|
5321
|
+
return Err(anyhow!(
|
|
5322
|
+
"Timed out waiting for tool run {} to finish",
|
|
5323
|
+
run_id
|
|
5324
|
+
));
|
|
5325
|
+
}
|
|
5326
|
+
sleep(poll_interval).await;
|
|
5327
|
+
}
|
|
5328
|
+
}
|
|
5329
|
+
|
|
5025
5330
|
async fn tool_runs_command(client: &reqwest::Client, args: ToolRunsArgs) -> Result<()> {
|
|
5026
5331
|
let mut session = load_session()?;
|
|
5027
5332
|
apply_base_url_override(&mut session, args.base_url);
|
|
@@ -5117,6 +5422,232 @@ async fn tool_run_events_command(client: &reqwest::Client, args: ToolRunEventsAr
|
|
|
5117
5422
|
Ok(())
|
|
5118
5423
|
}
|
|
5119
5424
|
|
|
5425
|
+
fn resolve_report_url(base_url: &str, report_download_path: Option<&str>) -> Option<String> {
|
|
5426
|
+
let path = report_download_path?.trim();
|
|
5427
|
+
if path.is_empty() {
|
|
5428
|
+
return None;
|
|
5429
|
+
}
|
|
5430
|
+
if path.starts_with("http://") || path.starts_with("https://") {
|
|
5431
|
+
return Some(path.to_string());
|
|
5432
|
+
}
|
|
5433
|
+
Some(format!("{}{}", normalize_base_url(base_url), path))
|
|
5434
|
+
}
|
|
5435
|
+
|
|
5436
|
+
fn extract_tool_run_report_paths(
|
|
5437
|
+
run: &serde_json::Map<String, serde_json::Value>,
|
|
5438
|
+
base_url: &str,
|
|
5439
|
+
) -> serde_json::Value {
|
|
5440
|
+
let output = run
|
|
5441
|
+
.get("output")
|
|
5442
|
+
.and_then(|value| value.as_object())
|
|
5443
|
+
.cloned()
|
|
5444
|
+
.unwrap_or_default();
|
|
5445
|
+
|
|
5446
|
+
let report_asset_id = output
|
|
5447
|
+
.get("reportHtmlAssetId")
|
|
5448
|
+
.and_then(|value| value.as_str())
|
|
5449
|
+
.or_else(|| output.get("reportAssetId").and_then(|value| value.as_str()));
|
|
5450
|
+
let report_download_path = output
|
|
5451
|
+
.get("reportHtmlDownloadPath")
|
|
5452
|
+
.and_then(|value| value.as_str())
|
|
5453
|
+
.or_else(|| output.get("reportDownloadPath").and_then(|value| value.as_str()));
|
|
5454
|
+
let report_url = resolve_report_url(base_url, report_download_path);
|
|
5455
|
+
let runtime = output
|
|
5456
|
+
.get("runtime")
|
|
5457
|
+
.and_then(|value| value.as_str())
|
|
5458
|
+
.unwrap_or_default();
|
|
5459
|
+
let workflow_mode = output
|
|
5460
|
+
.get("workflowMode")
|
|
5461
|
+
.and_then(|value| value.as_str())
|
|
5462
|
+
.unwrap_or_default();
|
|
5463
|
+
let summary = output
|
|
5464
|
+
.get("summary")
|
|
5465
|
+
.and_then(|value| value.as_str())
|
|
5466
|
+
.unwrap_or_default();
|
|
5467
|
+
|
|
5468
|
+
serde_json::json!({
|
|
5469
|
+
"reportAssetId": report_asset_id,
|
|
5470
|
+
"reportDownloadPath": report_download_path,
|
|
5471
|
+
"reportUrl": report_url,
|
|
5472
|
+
"runtime": runtime,
|
|
5473
|
+
"workflowMode": workflow_mode,
|
|
5474
|
+
"summary": summary
|
|
5475
|
+
})
|
|
5476
|
+
}
|
|
5477
|
+
|
|
5478
|
+
async fn tool_trace_status_command(client: &reqwest::Client, args: ToolTraceStatusArgs) -> Result<()> {
|
|
5479
|
+
let mut session = load_session()?;
|
|
5480
|
+
apply_base_url_override(&mut session, args.base_url);
|
|
5481
|
+
|
|
5482
|
+
let limit = args.limit.unwrap_or(80).max(1);
|
|
5483
|
+
let path = format!("/tools/runs?projectId={}&limit={}", args.project_id, limit);
|
|
5484
|
+
let response = authed_request(client, &mut session, Method::GET, &path, None).await?;
|
|
5485
|
+
if !response.status().is_success() {
|
|
5486
|
+
let body = read_error_body(response).await;
|
|
5487
|
+
return Err(anyhow!("tool trace-status failed: {}", body));
|
|
5488
|
+
}
|
|
5489
|
+
let payload: serde_json::Value = response.json().await?;
|
|
5490
|
+
let runs = payload
|
|
5491
|
+
.get("runs")
|
|
5492
|
+
.and_then(|value| value.as_array())
|
|
5493
|
+
.cloned()
|
|
5494
|
+
.unwrap_or_default();
|
|
5495
|
+
|
|
5496
|
+
let mut items: Vec<serde_json::Value> = Vec::new();
|
|
5497
|
+
for run in &runs {
|
|
5498
|
+
let Some(run_obj) = run.as_object() else {
|
|
5499
|
+
continue;
|
|
5500
|
+
};
|
|
5501
|
+
let tool_id = run_obj
|
|
5502
|
+
.get("toolId")
|
|
5503
|
+
.and_then(|value| value.as_str())
|
|
5504
|
+
.unwrap_or_default()
|
|
5505
|
+
.to_string();
|
|
5506
|
+
let normalized_tool_id = tool_id.to_ascii_lowercase();
|
|
5507
|
+
if !normalized_tool_id.starts_with("trace") && !normalized_tool_id.starts_with("crash") {
|
|
5508
|
+
continue;
|
|
5509
|
+
}
|
|
5510
|
+
|
|
5511
|
+
let run_id = run_obj
|
|
5512
|
+
.get("id")
|
|
5513
|
+
.and_then(|value| value.as_str())
|
|
5514
|
+
.unwrap_or_default();
|
|
5515
|
+
let status = run_obj
|
|
5516
|
+
.get("status")
|
|
5517
|
+
.and_then(|value| value.as_str())
|
|
5518
|
+
.unwrap_or_default();
|
|
5519
|
+
let created_at = run_obj
|
|
5520
|
+
.get("createdAt")
|
|
5521
|
+
.and_then(|value| value.as_str())
|
|
5522
|
+
.unwrap_or_default();
|
|
5523
|
+
let completed_at = run_obj
|
|
5524
|
+
.get("completedAt")
|
|
5525
|
+
.and_then(|value| value.as_str())
|
|
5526
|
+
.unwrap_or_default();
|
|
5527
|
+
let error_message = run_obj
|
|
5528
|
+
.get("errorMessage")
|
|
5529
|
+
.and_then(|value| value.as_str())
|
|
5530
|
+
.unwrap_or_default();
|
|
5531
|
+
let report = extract_tool_run_report_paths(run_obj, &session.base_url);
|
|
5532
|
+
|
|
5533
|
+
items.push(serde_json::json!({
|
|
5534
|
+
"runId": run_id,
|
|
5535
|
+
"toolId": tool_id,
|
|
5536
|
+
"status": status,
|
|
5537
|
+
"createdAt": created_at,
|
|
5538
|
+
"completedAt": if completed_at.is_empty() { serde_json::Value::Null } else { serde_json::Value::String(completed_at.to_string()) },
|
|
5539
|
+
"errorMessage": if error_message.is_empty() { serde_json::Value::Null } else { serde_json::Value::String(error_message.to_string()) },
|
|
5540
|
+
"report": report
|
|
5541
|
+
}));
|
|
5542
|
+
}
|
|
5543
|
+
|
|
5544
|
+
println!(
|
|
5545
|
+
"{}",
|
|
5546
|
+
serde_json::to_string_pretty(&serde_json::json!({
|
|
5547
|
+
"projectId": args.project_id,
|
|
5548
|
+
"count": items.len(),
|
|
5549
|
+
"items": items
|
|
5550
|
+
}))?
|
|
5551
|
+
);
|
|
5552
|
+
save_session(&session)?;
|
|
5553
|
+
Ok(())
|
|
5554
|
+
}
|
|
5555
|
+
|
|
5556
|
+
fn build_tool_context_path(
|
|
5557
|
+
context_id: &str,
|
|
5558
|
+
org_id: Option<&str>,
|
|
5559
|
+
project_id: Option<&str>,
|
|
5560
|
+
) -> Result<String> {
|
|
5561
|
+
if org_id.is_some() && project_id.is_some() {
|
|
5562
|
+
return Err(anyhow!("Only one of --org-id or --project-id can be set"));
|
|
5563
|
+
}
|
|
5564
|
+
let normalized_context_id = context_id.trim();
|
|
5565
|
+
if normalized_context_id.is_empty() {
|
|
5566
|
+
return Err(anyhow!("--context-id is required"));
|
|
5567
|
+
}
|
|
5568
|
+
|
|
5569
|
+
let mut path = format!("/tools/runtime/contexts/{}", normalized_context_id);
|
|
5570
|
+
let mut query_parts: Vec<String> = Vec::new();
|
|
5571
|
+
if let Some(org_id) = org_id {
|
|
5572
|
+
let normalized = org_id.trim();
|
|
5573
|
+
if !normalized.is_empty() {
|
|
5574
|
+
query_parts.push(format!("orgId={}", normalized));
|
|
5575
|
+
}
|
|
5576
|
+
}
|
|
5577
|
+
if let Some(project_id) = project_id {
|
|
5578
|
+
let normalized = project_id.trim();
|
|
5579
|
+
if !normalized.is_empty() {
|
|
5580
|
+
query_parts.push(format!("projectId={}", normalized));
|
|
5581
|
+
}
|
|
5582
|
+
}
|
|
5583
|
+
if !query_parts.is_empty() {
|
|
5584
|
+
path.push_str(&format!("?{}", query_parts.join("&")));
|
|
5585
|
+
}
|
|
5586
|
+
Ok(path)
|
|
5587
|
+
}
|
|
5588
|
+
|
|
5589
|
+
async fn tool_context_put_command(client: &reqwest::Client, args: ToolContextPutArgs) -> Result<()> {
|
|
5590
|
+
let mut session = load_session()?;
|
|
5591
|
+
apply_base_url_override(&mut session, args.base_url);
|
|
5592
|
+
|
|
5593
|
+
if args.text.is_some() && args.text_file.is_some() {
|
|
5594
|
+
return Err(anyhow!("Provide either --text or --text-file, not both"));
|
|
5595
|
+
}
|
|
5596
|
+
let context_text = if let Some(text_file) = args.text_file {
|
|
5597
|
+
fs::read_to_string(&text_file)
|
|
5598
|
+
.with_context(|| format!("Failed to read context text file {}", text_file.display()))?
|
|
5599
|
+
} else {
|
|
5600
|
+
args.text.unwrap_or_default()
|
|
5601
|
+
};
|
|
5602
|
+
if context_text.trim().is_empty() {
|
|
5603
|
+
return Err(anyhow!("Context text is required (--text or --text-file)"));
|
|
5604
|
+
}
|
|
5605
|
+
|
|
5606
|
+
let path = build_tool_context_path(
|
|
5607
|
+
&args.context_id,
|
|
5608
|
+
args.org_id.as_deref(),
|
|
5609
|
+
args.project_id.as_deref(),
|
|
5610
|
+
)?;
|
|
5611
|
+
let response = authed_request(
|
|
5612
|
+
client,
|
|
5613
|
+
&mut session,
|
|
5614
|
+
Method::PUT,
|
|
5615
|
+
&path,
|
|
5616
|
+
Some(serde_json::json!({
|
|
5617
|
+
"text": context_text
|
|
5618
|
+
})),
|
|
5619
|
+
)
|
|
5620
|
+
.await?;
|
|
5621
|
+
if !response.status().is_success() {
|
|
5622
|
+
let body = read_error_body(response).await;
|
|
5623
|
+
return Err(anyhow!("tool context put failed: {}", body));
|
|
5624
|
+
}
|
|
5625
|
+
let payload: serde_json::Value = response.json().await?;
|
|
5626
|
+
println!("{}", serde_json::to_string_pretty(&payload)?);
|
|
5627
|
+
save_session(&session)?;
|
|
5628
|
+
Ok(())
|
|
5629
|
+
}
|
|
5630
|
+
|
|
5631
|
+
async fn tool_context_get_command(client: &reqwest::Client, args: ToolContextGetArgs) -> Result<()> {
|
|
5632
|
+
let mut session = load_session()?;
|
|
5633
|
+
apply_base_url_override(&mut session, args.base_url);
|
|
5634
|
+
|
|
5635
|
+
let path = build_tool_context_path(
|
|
5636
|
+
&args.context_id,
|
|
5637
|
+
args.org_id.as_deref(),
|
|
5638
|
+
args.project_id.as_deref(),
|
|
5639
|
+
)?;
|
|
5640
|
+
let response = authed_request(client, &mut session, Method::GET, &path, None).await?;
|
|
5641
|
+
if !response.status().is_success() {
|
|
5642
|
+
let body = read_error_body(response).await;
|
|
5643
|
+
return Err(anyhow!("tool context get failed: {}", body));
|
|
5644
|
+
}
|
|
5645
|
+
let payload: serde_json::Value = response.json().await?;
|
|
5646
|
+
println!("{}", serde_json::to_string_pretty(&payload)?);
|
|
5647
|
+
save_session(&session)?;
|
|
5648
|
+
Ok(())
|
|
5649
|
+
}
|
|
5650
|
+
|
|
5120
5651
|
async fn tool_local_catalog_command(client: &reqwest::Client, args: ToolLocalCatalogArgs) -> Result<()> {
|
|
5121
5652
|
let mut session = load_session()?;
|
|
5122
5653
|
apply_base_url_override(&mut session, args.base_url);
|
|
@@ -5741,16 +6272,46 @@ async fn run_cli(cli: Cli) -> Result<()> {
|
|
|
5741
6272
|
FileCommands::Rmdir(args) => file_rmdir_command(&client, args).await?,
|
|
5742
6273
|
FileCommands::Remove(args) => file_remove_command(&client, args).await?,
|
|
5743
6274
|
},
|
|
6275
|
+
Commands::Skill { command } => match command {
|
|
6276
|
+
SkillCommands::List(args) => tool_list_command(&client, args).await?,
|
|
6277
|
+
SkillCommands::Register(args) => tool_register_command(&client, args).await?,
|
|
6278
|
+
SkillCommands::Publish(args) => tool_publish_command(&client, args).await?,
|
|
6279
|
+
SkillCommands::Enable(args) => tool_enable_command(&client, args).await?,
|
|
6280
|
+
SkillCommands::Disable(args) => tool_disable_command(&client, args).await?,
|
|
6281
|
+
SkillCommands::Context { command } => match command {
|
|
6282
|
+
ToolContextCommands::Put(args) => tool_context_put_command(&client, args).await?,
|
|
6283
|
+
ToolContextCommands::Get(args) => tool_context_get_command(&client, args).await?,
|
|
6284
|
+
},
|
|
6285
|
+
SkillCommands::Prompt(args) => tool_prompt_command(&client, args).await?,
|
|
6286
|
+
SkillCommands::Run(args) => tool_run_command(&client, args).await?,
|
|
6287
|
+
SkillCommands::Runs(args) => tool_runs_command(&client, args).await?,
|
|
6288
|
+
SkillCommands::GetRun(args) => tool_get_run_command(&client, args).await?,
|
|
6289
|
+
SkillCommands::RunEvents(args) => tool_run_events_command(&client, args).await?,
|
|
6290
|
+
SkillCommands::TraceStatus(args) => tool_trace_status_command(&client, args).await?,
|
|
6291
|
+
SkillCommands::Local { command } => match command {
|
|
6292
|
+
ToolLocalCommands::Catalog(args) => tool_local_catalog_command(&client, args).await?,
|
|
6293
|
+
ToolLocalCommands::Install(args) => tool_local_install_command(&client, args).await?,
|
|
6294
|
+
ToolLocalCommands::CompleteRun(args) => {
|
|
6295
|
+
tool_local_complete_run_command(&client, args).await?
|
|
6296
|
+
}
|
|
6297
|
+
},
|
|
6298
|
+
},
|
|
5744
6299
|
Commands::Tool { command } => match command {
|
|
5745
6300
|
ToolCommands::List(args) => tool_list_command(&client, args).await?,
|
|
5746
6301
|
ToolCommands::Register(args) => tool_register_command(&client, args).await?,
|
|
5747
6302
|
ToolCommands::Publish(args) => tool_publish_command(&client, args).await?,
|
|
5748
6303
|
ToolCommands::Enable(args) => tool_enable_command(&client, args).await?,
|
|
5749
6304
|
ToolCommands::Disable(args) => tool_disable_command(&client, args).await?,
|
|
6305
|
+
ToolCommands::Context { command } => match command {
|
|
6306
|
+
ToolContextCommands::Put(args) => tool_context_put_command(&client, args).await?,
|
|
6307
|
+
ToolContextCommands::Get(args) => tool_context_get_command(&client, args).await?,
|
|
6308
|
+
},
|
|
6309
|
+
ToolCommands::Prompt(args) => tool_prompt_command(&client, args).await?,
|
|
5750
6310
|
ToolCommands::Run(args) => tool_run_command(&client, args).await?,
|
|
5751
6311
|
ToolCommands::Runs(args) => tool_runs_command(&client, args).await?,
|
|
5752
6312
|
ToolCommands::GetRun(args) => tool_get_run_command(&client, args).await?,
|
|
5753
6313
|
ToolCommands::RunEvents(args) => tool_run_events_command(&client, args).await?,
|
|
6314
|
+
ToolCommands::TraceStatus(args) => tool_trace_status_command(&client, args).await?,
|
|
5754
6315
|
ToolCommands::Local { command } => match command {
|
|
5755
6316
|
ToolLocalCommands::Catalog(args) => tool_local_catalog_command(&client, args).await?,
|
|
5756
6317
|
ToolLocalCommands::Install(args) => tool_local_install_command(&client, args).await?,
|