Skip to content

Commit c163788

Browse files
MarkShawn2020claude
andcommitted
feat: add headless index CLI and project-scoped search
`ataru index status` / `ataru index build [--force]` let a scriptable caller complete ensure_index without opening the desktop window, and `ataru search` gains `--project-id` to narrow recall to one project. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 89a19b1 commit c163788

7 files changed

Lines changed: 101 additions & 6 deletions

File tree

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
# Changelog
22

3+
## 0.41.3
4+
5+
### Patch Changes
6+
7+
- JSON CLI 新增 `ataru index status``ataru index build [--force]`,无界面调用方终于可以自己完成 `ensure_index`,不必先打开桌面端;`ataru search` 支持 `--project-id` 按项目收敛检索范围。
8+
39
## 0.41.2
410

511
### Patch Changes

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "ataru",
33
"private": true,
4-
"version": "0.41.2",
4+
"version": "0.41.3",
55
"type": "module",
66
"packageManager": "pnpm@10.18.1",
77
"scripts": {

src-tauri/Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "ataru"
3-
version = "0.41.2"
3+
version = "0.41.3"
44
description = "A Tauri App"
55
authors = ["you"]
66
edition = "2021"

src-tauri/src/app/cli.rs

Lines changed: 76 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ enum CliRequest {
77
query: String,
88
limit: usize,
99
level: Option<ataru::sdk::SearchLevel>,
10+
project_id: Option<String>,
11+
},
12+
IndexStatus,
13+
IndexBuild {
14+
force: bool,
1015
},
1116
ReadSession {
1217
project_id: String,
@@ -28,20 +33,21 @@ pub(crate) fn run_cli_if_requested() -> Option<i32> {
2833
query,
2934
limit,
3035
level,
36+
project_id,
3137
}) => {
3238
let result = if let Some(level) = level {
3339
ataru::api::ataru_keyword_search(ataru::sdk::SearchRequest {
3440
query,
3541
level,
3642
mode: ataru::sdk::SearchMode::Keyword,
3743
limit,
38-
project_id: None,
44+
project_id,
3945
})
4046
.and_then(|response| {
4147
serde_json::to_string(&response).map_err(|error| error.to_string())
4248
})
4349
} else {
44-
search_chats(query, Some(limit), None).and_then(|results| {
50+
search_chats(query, Some(limit), project_id).and_then(|results| {
4551
serde_json::to_string(&results).map_err(|error| error.to_string())
4652
})
4753
};
@@ -81,6 +87,28 @@ pub(crate) fn run_cli_if_requested() -> Option<i32> {
8187
}
8288
}
8389
}
90+
Ok(CliRequest::IndexStatus) => match serde_json::to_string(&cli_search_index_status()) {
91+
Ok(json) => {
92+
println!("{json}");
93+
0
94+
}
95+
Err(error) => {
96+
eprintln!("Ataru index status failed: {error}");
97+
1
98+
}
99+
},
100+
Ok(CliRequest::IndexBuild { force }) => match cli_build_search_index(force)
101+
.and_then(|status| serde_json::to_string(&status).map_err(|error| error.to_string()))
102+
{
103+
Ok(json) => {
104+
println!("{json}");
105+
0
106+
}
107+
Err(error) => {
108+
eprintln!("Ataru index build failed: {error}");
109+
1
110+
}
111+
},
84112
Ok(CliRequest::SemanticPreflight) => match semantic_search_initialization_preview()
85113
.and_then(|preview| serde_json::to_string(&preview).map_err(|error| error.to_string()))
86114
{
@@ -105,6 +133,13 @@ fn parse_cli_request(args: &[String]) -> Option<Result<CliRequest, String>> {
105133
if matches!(command, "--version" | "-V") {
106134
return Some(Ok(CliRequest::Version));
107135
}
136+
if command == "index" {
137+
return Some(match args.get(1).map(String::as_str) {
138+
Some("status") => parse_index_status_request(args),
139+
Some("build") => parse_index_build_request(args),
140+
_ => Err("Usage: ataru index status --json | ataru index build [--force] --json".to_string()),
141+
});
142+
}
108143
if command == "semantic" {
109144
return Some(match args.get(1).map(String::as_str) {
110145
Some("preview") => Ok(CliRequest::SemanticPreflight),
@@ -121,6 +156,7 @@ fn parse_cli_request(args: &[String]) -> Option<Result<CliRequest, String>> {
121156
let mut query_parts = Vec::new();
122157
let mut limit = 50usize;
123158
let mut level = None;
159+
let mut project_id = None;
124160
let mut index = 1usize;
125161
while index < args.len() {
126162
match args[index].as_str() {
@@ -164,6 +200,20 @@ fn parse_cli_request(args: &[String]) -> Option<Result<CliRequest, String>> {
164200
};
165201
index += 2;
166202
}
203+
"--project-id" => {
204+
let Some(value) = args.get(index + 1) else {
205+
return Some(Err(
206+
"Usage: ataru search <query> --json [--project-id PROJECT_ID]".to_string(),
207+
));
208+
};
209+
if value.is_empty() {
210+
return Some(Err(
211+
"Ataru search project id must not be empty.".to_string()
212+
));
213+
}
214+
project_id = Some(value.clone());
215+
index += 2;
216+
}
167217
value if value.starts_with('-') => {
168218
return Some(Err(format!("Unknown Ataru search option: {value}")));
169219
}
@@ -189,9 +239,31 @@ fn parse_cli_request(args: &[String]) -> Option<Result<CliRequest, String>> {
189239
query,
190240
limit,
191241
level,
242+
project_id,
192243
}))
193244
}
194245

246+
fn parse_index_status_request(args: &[String]) -> Result<CliRequest, String> {
247+
for value in args.iter().skip(2) {
248+
if value != "--json" {
249+
return Err(format!("Unknown Ataru index status option: {value}"));
250+
}
251+
}
252+
Ok(CliRequest::IndexStatus)
253+
}
254+
255+
fn parse_index_build_request(args: &[String]) -> Result<CliRequest, String> {
256+
let mut force = false;
257+
for value in args.iter().skip(2) {
258+
match value.as_str() {
259+
"--json" => {}
260+
"--force" => force = true,
261+
other => return Err(format!("Unknown Ataru index build option: {other}")),
262+
}
263+
}
264+
Ok(CliRequest::IndexBuild { force })
265+
}
266+
195267
fn parse_session_request(args: &[String]) -> Result<CliRequest, String> {
196268
if args.get(1).map(String::as_str) != Some("read") {
197269
return Err(
@@ -271,6 +343,7 @@ mod tests {
271343
query: "global session".to_string(),
272344
limit: 80,
273345
level: None,
346+
project_id: None,
274347
}))
275348
);
276349
assert_eq!(
@@ -281,6 +354,7 @@ mod tests {
281354
query: "ranking".to_string(),
282355
limit: 50,
283356
level: Some(ataru::sdk::SearchLevel::Project),
357+
project_id: None,
284358
}))
285359
);
286360
assert_eq!(

src-tauri/src/app/search.rs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,21 @@ pub(crate) fn set_incremental_search_index_sync_enabled(
948948
current_incremental_search_index_sync_status()
949949
}
950950

951+
/// Headless entry points for the JSON CLI.
952+
///
953+
/// The desktop build drives indexing through Tauri commands that need an
954+
/// `AppHandle` to emit progress. Agent Skills and scripts have no window, so
955+
/// they get the same single-writer pipeline without event emission: status is
956+
/// read from the manifest on disk, and a build runs to completion in-process.
957+
pub(crate) fn cli_search_index_status() -> SearchIndexBuildStatus {
958+
current_search_index_status()
959+
}
960+
961+
pub(crate) fn cli_build_search_index(force: bool) -> Result<SearchIndexBuildStatus, String> {
962+
run_search_index_build(None, force)?;
963+
Ok(current_search_index_status())
964+
}
965+
951966
#[tauri::command]
952967
pub(crate) fn start_search_index_build(
953968
app_handle: tauri::AppHandle,

src-tauri/tauri.conf.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"$schema": "https://schema.tauri.app/config/2",
33
"productName": "Ataru",
4-
"version": "0.41.2",
4+
"version": "0.41.3",
55
"identifier": "app.lovpen.code",
66
"build": {
77
"beforeDevCommand": "pnpm dev",

0 commit comments

Comments
 (0)