Native detz entry + libcurl HTTP/SSE client; drop python execvp shim
This commit is contained in:
+3
-3
@@ -27,7 +27,7 @@ $(BUILD)/main.o: main.cpp $(VERSION_HPP)
|
||||
$(CXX) $(CXXFLAGS) -I$(GEN_INC) $(foreach d,$(shell find src -type d),-I$d) -c main.cpp -o $@
|
||||
|
||||
$(BIN): $(MAIN_OBJ) $(ALL_OBJS)
|
||||
$(CXX) $(CXXFLAGS) $(MAIN_OBJ) $(ALL_OBJS) -lsqlite3 -o $@
|
||||
$(CXX) $(CXXFLAGS) $(MAIN_OBJ) $(ALL_OBJS) -lsqlite3 -lcurl -o $@
|
||||
|
||||
test: $(BIN) $(BUILD)/detz_test $(BUILD)/store_test
|
||||
./$(BUILD)/detz_test
|
||||
@@ -36,11 +36,11 @@ test: $(BIN) $(BUILD)/detz_test $(BUILD)/store_test
|
||||
|
||||
$(BUILD)/store_test: tests/store_test.cpp src/store/store.cpp
|
||||
@mkdir -p $(BUILD)
|
||||
$(CXX) $(CXXFLAGS) -Isrc/store tests/store_test.cpp src/store/store.cpp -lsqlite3 -o $@
|
||||
$(CXX) $(CXXFLAGS) -Isrc/store tests/store_test.cpp src/store/store.cpp -lsqlite3 -lcurl -o $@
|
||||
|
||||
$(BUILD)/detz_test: tests/test_main.cpp $(ALL_SRCS) $(VERSION_HPP)
|
||||
@mkdir -p $(BUILD)
|
||||
$(CXX) $(CXXFLAGS) -I$(GEN_INC) $(foreach d,$(shell find src -type d),-I$d) tests/test_main.cpp $(ALL_SRCS) -lsqlite3 -o $@
|
||||
$(CXX) $(CXXFLAGS) -I$(GEN_INC) $(foreach d,$(shell find src -type d),-I$d) tests/test_main.cpp $(ALL_SRCS) -lsqlite3 -lcurl -o $@
|
||||
|
||||
lint:
|
||||
$(CXX) $(CXXSTD) $(WARN) -Werror -fsyntax-only main.cpp -I$(GEN_INC) $(foreach d,$(shell find src -type d),-I$d) || true
|
||||
|
||||
+10
-2
@@ -1,2 +1,10 @@
|
||||
#include "src/cli/cli.h"
|
||||
int main(int argc, char** argv) { return detz::cli::DetzCli{}.run(argc, argv); }
|
||||
#include "cli/cli.h"
|
||||
#include "version.hpp"
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
int main(int argc, char** argv){
|
||||
detz::cli::Parser p; auto cmd = p.parse(argc, argv);
|
||||
if(cmd.opts.version){ std::puts(std::string(detz::kVersion).c_str()); return 0; }
|
||||
if(cmd.opts.cap_manifest){ std::fputs("# tai CAP agent manifest\n", stdout); return 0; }
|
||||
return cmd.run();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
#include "util/http.h"
|
||||
#include <curl/curl.h>
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
namespace detz {
|
||||
namespace {
|
||||
struct Buf { std::string data; std::map<std::string,std::string>* hdrs{nullptr}; std::string* ctype{nullptr}; std::string sse; std::function<void(const SseEvent&)>* cb{nullptr}; std::string ev; std::string dt; };
|
||||
size_t wbody(char* p, size_t n, size_t m, void* u){ ((Buf*)u)->data.append(p,n*m); return n*m; }
|
||||
size_t whdr(char* p, size_t n, size_t m, void* u){ Buf* b=(Buf*)u; std::string l(p,n*m); auto c=l.find(':'); if(c!=std::string::npos){ std::string k=l.substr(0,c),v=l.substr(c+1); while(!v.empty()&&(v.back()=='\r'||v.back()=='\n'))v.pop_back(); while(!v.empty()&&v[0]==' ')v.erase(v.begin()); if(b->hdrs)(*b->hdrs)[k]=v; if(k=="Content-Type"&&b->ctype)*b->ctype=v; } return n*m; }
|
||||
size_t wsse(char* p, size_t n, size_t m, void* u){ Buf* b=(Buf*)u; b->sse.append(p,n*m); size_t pos; while((pos=b->sse.find("\n\n"))!=std::string::npos){ std::string blk=b->sse.substr(0,pos); b->sse.erase(0,pos+2); SseEvent e; std::istringstream s(blk); std::string ln; while(std::getline(s,ln)){ if(!ln.empty()&&ln.back()=='\r')ln.pop_back(); if(ln.rfind("event:",0)==0)e.event=ln.substr(6); else if(ln.rfind("data:",0)==0){ if(!e.data.empty())e.data+="\n"; e.data+=ln.substr(5);} } if(e.event.empty())e.event="message"; if((*b->cb)&&!e.data.empty())(*b->cb)(e); } b->data+=std::string(p,n*m); return n*m; }
|
||||
}
|
||||
HttpResponse HttpRequest(const std::string& method, const std::string& url, const std::string& body, const std::map<std::string,std::string>& headers, long timeout_s){
|
||||
HttpResponse r; CURL* c=curl_easy_init(); if(!c) return r; Buf b; b.data=""; b.hdrs=&r.headers; b.ctype=&r.content_type;
|
||||
curl_easy_setopt(c,CURLOPT_URL,url.c_str()); curl_easy_setopt(c,CURLOPT_WRITEFUNCTION,wbody); curl_easy_setopt(c,CURLOPT_WRITEDATA,&b);
|
||||
curl_easy_setopt(c,CURLOPT_HEADERFUNCTION,whdr); curl_easy_setopt(c,CURLOPT_HEADERDATA,&b);
|
||||
curl_easy_setopt(c,CURLOPT_TIMEOUT,timeout_s); curl_easy_setopt(c,CURLOPT_FOLLOWLOCATION,1L);
|
||||
struct curl_slist* hs=nullptr; for(auto&[k,v]:headers) hs=curl_slist_append(hs,(k+": "+v).c_str());
|
||||
if(hs) curl_easy_setopt(c,CURLOPT_HTTPHEADER,hs);
|
||||
if(method=="POST"){ curl_easy_setopt(c,CURLOPT_POST,1L); curl_easy_setopt(c,CURLOPT_POSTFIELDS,body.c_str()); curl_easy_setopt(c,CURLOPT_POSTFIELDSIZE,(long)body.size()); }
|
||||
else if(method!="GET"){ curl_easy_setopt(c,CURLOPT_CUSTOMREQUEST,method.c_str()); if(!body.empty()){curl_easy_setopt(c,CURLOPT_POSTFIELDS,body.c_str());curl_easy_setopt(c,CURLOPT_POSTFIELDSIZE,(long)body.size());} }
|
||||
CURLcode rc=curl_easy_perform(c); if(rc==CURLE_OK){ curl_easy_getinfo(c,CURLINFO_RESPONSE_CODE,&r.status); r.body=b.data; }
|
||||
curl_slist_free_all(hs); curl_easy_cleanup(c); return r;
|
||||
}
|
||||
HttpResponse HttpGet(const std::string& u,const std::map<std::string,std::string>& h,long t){ return HttpRequest("GET",u,"",h,t); }
|
||||
HttpResponse HttpPost(const std::string& u,const std::string& b,const std::map<std::string,std::string>& h,long t){ return HttpRequest("POST",u,b,h,t); }
|
||||
long HttpPostSse(const std::string& url,const std::string& body,const std::map<std::string,std::string>& headers,long timeout_s,const std::function<void(const SseEvent&)>& cb){
|
||||
CURL* c=curl_easy_init(); if(!c) return 0; Buf b; b.cb=(std::function<void(const SseEvent&)>*)&cb;
|
||||
curl_easy_setopt(c,CURLOPT_URL,url.c_str()); curl_easy_setopt(c,CURLOPT_POST,1L);
|
||||
curl_easy_setopt(c,CURLOPT_POSTFIELDS,body.c_str()); curl_easy_setopt(c,CURLOPT_POSTFIELDSIZE,(long)body.size());
|
||||
curl_easy_setopt(c,CURLOPT_WRITEFUNCTION,wsse); curl_easy_setopt(c,CURLOPT_WRITEDATA,&b);
|
||||
curl_easy_setopt(c,CURLOPT_TIMEOUT,timeout_s);
|
||||
struct curl_slist* hs=nullptr; for(auto&[k,v]:headers) hs=curl_slist_append(hs,(k+": "+v).c_str());
|
||||
if(hs) curl_easy_setopt(c,CURLOPT_HTTPHEADER,hs);
|
||||
long st=0; if(curl_easy_perform(c)==CURLE_OK) curl_easy_getinfo(c,CURLINFO_RESPONSE_CODE,&st);
|
||||
curl_slist_free_all(hs); curl_easy_cleanup(c); return st;
|
||||
}
|
||||
std::string JsonEscape(const std::string& s){ std::string o; for(char c:s){ switch(c){case '"':o+="\\\"";break;case '\\':o+="\\\\";break;case '\n':o+="\\n";break;case '\r':o+="\\r";break;case '\t':o+="\\t";break;default: if((unsigned char)c<0x20){char b[8];snprintf(b,8,"\\u%04x",c);o+=b;} else o+=c;} } return o; }
|
||||
std::string JsonGetStr(const std::string& o,const std::string& k){ std::string p="\""+k+"\""; auto i=o.find(p); if(i==std::string::npos)return ""; i=o.find(':',i); if(i==std::string::npos)return ""; ++i; while(i<o.size()&&(o[i]==' '||o[i]=='"')){if(o[i]=='"'){++i;std::string v;while(i<o.size()&&o[i]!='"'){if(o[i]=='\\'&&i+1<o.size()){v+=o[i+1];i+=2;}else v+=o[i++];}return v;}++i;} std::string v; while(i<o.size()&&o[i]!=','&&o[i]!='}'&&o[i]!='"')v+=o[i++]; return v; }
|
||||
long JsonGetInt(const std::string& o,const std::string& k,long d){ std::string v=JsonGetStr(o,k); if(v.empty())return d; try{return std::stol(v);}catch(...){return d;} }
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
#include <functional>
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
namespace detz {
|
||||
// Minimal HTTP client over libcurl (mirrors tai.py urllib flows: GET/POST,
|
||||
// headers, timeouts, SSE streaming). No dependency beyond libcurl.
|
||||
struct HttpResponse { long status{0}; std::string body; std::map<std::string,std::string> headers; std::string content_type; };
|
||||
struct SseEvent { std::string event; std::string data; };
|
||||
HttpResponse HttpGet(const std::string& url, const std::map<std::string,std::string>& headers = {}, long timeout_s = 60);
|
||||
HttpResponse HttpPost(const std::string& url, const std::string& body, const std::map<std::string,std::string>& headers = {}, long timeout_s = 60);
|
||||
HttpResponse HttpRequest(const std::string& method, const std::string& url, const std::string& body, const std::map<std::string,std::string>& headers, long timeout_s);
|
||||
// Stream SSE: invokes cb per event, returns final HTTP status.
|
||||
long HttpPostSse(const std::string& url, const std::string& body, const std::map<std::string,std::string>& headers, long timeout_s, const std::function<void(const SseEvent&)>& cb);
|
||||
// Tiny JSON helpers (escape/extract) mirroring tai.py json usage.
|
||||
std::string JsonEscape(const std::string& s);
|
||||
std::string JsonGetStr(const std::string& obj, const std::string& key);
|
||||
long JsonGetInt(const std::string& obj, const std::string& key, long dflt = 0);
|
||||
}
|
||||
Reference in New Issue
Block a user