@lmctl-ai/lmctl 0.2.449 → 0.2.450

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/dist/cli/index.js CHANGED
@@ -2405,7 +2405,7 @@ $XDG_CONFIG_HOME/lmctl/appkey.json (same appkey as the lmctl.ai tools).`;Hn=clas
2405
2405
  When you have finished, report the outcome exactly once by running this command (the token is
2406
2406
  run-specific; do not change it):
2407
2407
 
2408
- ${r}lmbee cron report ${e} --status succeeded --summary "short summary" [--artifact PATH]
2408
+ ${r}lmctl cron report ${e} --status succeeded --summary "short summary" [--artifact PATH]
2409
2409
 
2410
2410
  Use --status failed if the work did not succeed, or --status escalated together with
2411
2411
  --requested-action "requested operator action" and optionally --owner NAME if a human decision is
@@ -2419,7 +2419,7 @@ Respond with EXACTLY one line of strict JSON and no other text, using this run t
2419
2419
  For an escalation use {"run_token":"...","status":"escalated","summary":"...","escalation":{"owner":"...","reason":"...","requestedAction":"..."}}.
2420
2420
  Escalation reason and requestedAction are required. Never claim succeeded unless the work actually
2421
2421
  succeeded.`}function JL(t,e){if(t===null)return{ok:!1,reason:"no output"};if(Buffer.byteLength(t,"utf8")>Ow)return{ok:!1,reason:`result exceeds ${Ow} bytes`};let n=t.trim();if(n.length===0)return{ok:!1,reason:"empty output"};let r;try{r=JSON.parse(n)}catch{return{ok:!1,reason:"output is not a single JSON object"}}if(typeof r!="object"||r===null||Array.isArray(r))return{ok:!1,reason:"output is not a JSON object"};let s=r;for(let l of Object.keys(s))if(!h6.has(l))return{ok:!1,reason:`unknown field "${l}"`};if(s.run_token!==e)return{ok:!1,reason:"run token missing or mismatched"};let o=s.status;if(o!=="succeeded"&&o!=="failed"&&o!=="escalated")return{ok:!1,reason:"status must be succeeded, failed, or escalated"};if(typeof s.summary!="string"||s.summary.trim().length===0)return{ok:!1,reason:"summary must be a nonblank string"};if(Buffer.byteLength(s.summary,"utf8")>xo)return{ok:!1,reason:"summary is too large"};let i=[];if(s.artifacts!==void 0){if(!Array.isArray(s.artifacts)||s.artifacts.length>Zf)return{ok:!1,reason:"artifacts must be an array of at most 100 strings"};for(let l of s.artifacts)if(typeof l!="string"||l.trim().length===0||Buffer.byteLength(l,"utf8")>Cw)return{ok:!1,reason:"each artifact must be a nonblank string of at most 4096 bytes"};i=s.artifacts}let a={status:o,summary:s.summary.trim(),artifacts:i};if(o==="escalated"){let l=s.escalation;if(typeof l!="object"||l===null||Array.isArray(l))return{ok:!1,reason:"an escalated result requires an escalation object"};let c=l;for(let d of Object.keys(c))if(!_6.has(d))return{ok:!1,reason:`unknown escalation field "${d}"`};if(typeof c.reason!="string"||c.reason.trim().length===0||Buffer.byteLength(c.reason,"utf8")>fc)return{ok:!1,reason:"escalation.reason is required and bounded"};if(typeof c.requestedAction!="string"||c.requestedAction.trim().length===0||Buffer.byteLength(c.requestedAction,"utf8")>fc)return{ok:!1,reason:"escalation.requestedAction is required and bounded"};if(a.reason=c.reason.trim(),a.action=c.requestedAction.trim(),c.owner!==void 0){if(typeof c.owner!="string"||c.owner.trim().length===0||c.owner.length>Lw)return{ok:!1,reason:"escalation.owner must be a nonblank string of at most 512 chars"};a.owner=c.owner.trim()}}return{ok:!0,result:a}}function g6(t,e,n){let r=e.session_alias??"Worker";if(e.session_teamfile!==null&&e.session_seeded_at!==null)return{teamfile:e.session_teamfile,alias:r};if(e.working_directory===null||e.provider===null)throw new U("INVALID_ARGUMENT",`session job ${e.id} requires provider and an absolute working_directory`);let s=wc(n??u6(),d6(t)),o=e.session_generation??0,i=e.session_teamfile??wc(s,`job-${e.id}-g${o}.lmctl`);if(e.session_teamfile!==null)return{teamfile:e.session_teamfile,alias:r};if(Jz(i))return t.prepare("UPDATE cron_jobs SET session_teamfile = ?, session_alias = ?, updated_at = ? WHERE id = ?").run(i,r,new Date().toISOString(),e.id),{teamfile:i,alias:r};Vz(s,{recursive:!0});let a=c6({alias:r,provider:e.provider,model:e.model,sessiondir:e.working_directory});return Hw(i,`#teamfile: ${i}
2422
- # private single-member session for lmbee cron job ${e.id} (generation ${o})
2422
+ # private single-member session for lmctl cron job ${e.id} (generation ${o})
2423
2423
 
2424
2424
  ${a}
2425
2425
  `,{mode:384}),t.prepare("UPDATE cron_jobs SET session_teamfile = ?, session_alias = ?, updated_at = ? WHERE id = ?").run(i,r,new Date().toISOString(),e.id),{teamfile:i,alias:r}}function Xw(t){try{return process.kill(-t,0),!0}catch{return!1}}function Ww(t,e){if(t.pid===void 0)return;let n=t.pid;try{process.kill(-n,"SIGTERM")}catch{return}setTimeout(()=>{try{process.kill(-n,0)}catch{return}try{process.kill(-n,"SIGKILL")}catch{}},e).unref()}function BL(t){return t.action_kind==="session"&&t.provider===null&&t.session_teamfile!==null&&t.session_seeded_at!==null}function y6(t){let e=null,n=null,r=null,s=null;for(let o of t.split(`
@@ -2489,7 +2489,7 @@ install registers the daemon as a boot-surviving systemd user service on Linux a
2489
2489
  On other operating systems, install and start open a terminal window and start the daemon in it.`});function bc(t,e=[]){let n=new Set(e),r=[],s={};for(let o=0;o<t.length;o++){let i=t[o];if(i==="-h")s.help=!0;else if(i.startsWith("--")){let a=i.slice(2),l=t[o+1];!n.has(a)&&l!==void 0&&!l.startsWith("--")?(s[a]=l,o++):s[a]=!0}else r.push(i)}return{positionals:r,flags:s}}var eE=C(()=>{"use strict"});function yC(t){return mt(t)}var wC=C(()=>{"use strict";Fs();Pt();Kn()});import{existsSync as X6,readFileSync as q6,writeFileSync as G6}from"node:fs";function z6(t){return{id:t.id,title:t.title,schedule_expr:t.schedule_expr,action_kind:t.action_kind,action_payload:t.action_payload,provider:t.provider,model:t.model,working_directory:t.working_directory,escalation_owner:t.escalation_owner,timezone:t.timezone,enabled:!!t.enabled}}function Y6(t){return t instanceof Error&&"code"in t&&typeof t.code=="string"&&t.code.startsWith("SQLITE_BUSY")}function Q6(t,e){let n;try{n=t.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'cron_jobs'").get()!==void 0?t.prepare("SELECT * FROM cron_jobs ORDER BY id").all():[]}catch(r){throw Y6(r)?new U("SOURCE_BUSY","source database is locked by another process -- export never waits on locks (busy_timeout = 0); retry shortly",3):r}for(let r of n)if(r.action_kind!=="command"&&r.action_kind!=="script")throw new U("INVALID_ARGUMENT",`cron job ${r.id} "${r.title}" has action_kind "${r.action_kind}" -- export does not carry ${r.action_kind}-kind jobs, so no file was written (import would refuse it; never produce a transferable payload containing one -- re-create the job manually on the target)`);return{schema_version:1,kind:tE,exported_at:new Date().toISOString(),exported_from:e,jobs:n.map(z6)}}function Z6(t,e,n){let r=Q6(t,n);try{G6(e,`${JSON.stringify(r,null,2)}
2490
2490
  `,{flag:"wx"})}catch(s){throw s&&typeof s=="object"&&"code"in s&&s.code==="EEXIST"?new U("INVALID_ARGUMENT",`export path already exists: ${e} (rm it first; there is no --force)`):new U("INVALID_ARGUMENT",`cannot write export to ${e}: ${s instanceof Error?s.message:String(s)}`)}return r}function e4(t){let e=J6(t);V6(e);let n=new Ln(e,{readonly:!0,fileMustExist:!0});return n.pragma("busy_timeout = 0"),{db:n,path:e,close:()=>n.close()}}function Ic(t,e,n){if(e.length>1)throw new U("INVALID_ARGUMENT",`cron ${t} takes exactly one PATH argument -- refusing unexpected extra argument ${JSON.stringify(e[1])}`);for(let r of Object.keys(n))if(r!=="json"&&r!=="help")throw new U("INVALID_ARGUMENT",`cron ${t} does not know flag "--${r}" (only --json and --help)`)}function SC(t){let{positionals:e,flags:n}=bc(t,["json","help"]),r=n.json===!0;try{if(n.help===!0)return{output:vC,exitCode:0};Ic("export",e,n);let s=e[0];if(s===void 0)throw new U("INVALID_ARGUMENT",`an export PATH is required
2491
2491
  ${vC}`);let o=e4();try{let i=Z6(o.db,s,o.path);return r?{output:JSON.stringify({schema_version:1,ok:!0,data:{path:s,exported_from:o.path,jobs:i.jobs.length}}),exitCode:0}:{output:`exported ${i.jobs.length} jobs to ${s}`,exitCode:0}}finally{o.close()}}catch(s){if(s instanceof U)return r?{output:JSON.stringify({schema_version:1,ok:!1,error:{code:s.code,message:s.message}}),exitCode:s.exitCode}:{output:`${s.code}: ${s.message}`,exitCode:s.exitCode};throw s}}function pn(t,e){throw new U("INVALID_ARGUMENT",`jobs[${t}]: ${e}`)}function t4(t,e){(typeof t!="object"||t===null||Array.isArray(t))&&pn(e,"must be a JSON object");let n=t;for(let c of Object.keys(n))TC.includes(c)||pn(e,`unknown key "${c}"`);for(let c of TC)c in n||pn(e,`missing key "${c}"`);let{id:r,title:s,schedule_expr:o,action_kind:i,action_payload:a,enabled:l}=n;(typeof r!="number"||!Number.isSafeInteger(r)||r<1)&&pn(e,"id must be a positive safe integer"),(typeof s!="string"||s.trim().length===0)&&pn(e,"title must be a nonblank string"),typeof o!="string"&&pn(e,"schedule_expr must be a string"),typeof i!="string"&&pn(e,"action_kind must be a string"),i==="enqueue"&&pn(e,"import does not carry enqueue-kind jobs (zero existed when this was designed; refusing rather than risking a payload current validation rejects -- create it manually)"),(i==="session"||i==="miniplayer")&&pn(e,`import does not carry ${i}-kind jobs (out of scope -- untested expansion; create it manually)`),i!=="command"&&i!=="script"&&pn(e,`action_kind "${i}" is not a known kind; only "command" and "script" are accepted`),typeof a!="string"&&pn(e,"action_payload must be a string"),typeof l!="boolean"&&pn(e,`enabled must be a JSON boolean (true/false), not ${JSON.stringify(l)}`);for(let c of K6){let d=n[c];d!==null&&typeof d!="string"&&pn(e,`${c} must be a string or null`)}return n}function bC(t){let e;try{e=q6(t,"utf8")}catch(i){throw new U("INVALID_ARGUMENT",`cannot read import file ${t}: ${i instanceof Error?i.message:String(i)}`)}let n;try{n=JSON.parse(e)}catch(i){throw new U("INVALID_ARGUMENT",`import file ${t} is not valid JSON: ${i instanceof Error?i.message:String(i)}`)}if(typeof n!="object"||n===null||Array.isArray(n))throw new U("INVALID_ARGUMENT","import document must be a JSON object");let r=n;for(let i of Object.keys(r))if(!EC.includes(i))throw new U("INVALID_ARGUMENT",`unknown top-level key "${i}"`);for(let i of EC)if(!(i in r))throw new U("INVALID_ARGUMENT",`missing top-level key "${i}"`);if(r.schema_version!==1)throw new U("INVALID_ARGUMENT",`schema_version must be 1, got ${JSON.stringify(r.schema_version)}`);if(r.kind!==tE)throw new U("INVALID_ARGUMENT",`kind must be "${tE}", got ${JSON.stringify(r.kind)}`);for(let i of["exported_at","exported_from"])if(typeof r[i]!="string")throw new U("INVALID_ARGUMENT",`${i} must be a string, got ${JSON.stringify(r[i])}`);if(!Array.isArray(r.jobs))throw new U("INVALID_ARGUMENT","jobs must be an array");let s=r.jobs.map((i,a)=>t4(i,a)),o=new Set;for(let i of s){if(o.has(i.id))throw new U("INVALID_ARGUMENT",`duplicate id ${i.id} within the document`);o.add(i.id)}return s}function IC(t){return{...t,title:t.title.trim(),timezone:t.timezone===null?null:t.timezone.trim()}}function n4(t,e){let n=IC(e);return t.title===n.title&&t.schedule_expr===n.schedule_expr&&t.action_kind===n.action_kind&&t.action_payload===n.action_payload&&t.provider===n.provider&&t.model===n.model&&t.working_directory===n.working_directory&&t.escalation_owner===n.escalation_owner&&t.timezone===n.timezone&&!!t.enabled===n.enabled}function r4(t){let e=IC(t);return{id:e.id,title:e.title,scheduleExpr:e.schedule_expr,actionKind:e.action_kind,actionPayload:e.action_payload,enabled:e.enabled,...e.provider!==null?{provider:e.provider}:{},...e.model!==null?{model:e.model}:{},...e.working_directory!==null?{workingDirectory:e.working_directory}:{},...e.escalation_owner!==null?{escalationOwner:e.escalation_owner}:{},...e.timezone!==null?{timezone:e.timezone}:{}}}function kC(t){bC(t)}function AC(t,e){let n=bC(e),r=[],s=[];t.transaction(()=>{for(let a of n){let l=om(t,a.id);if(l!==null){if(n4(l,a)){r.push({id:a.id,title:a.title});continue}throw new U("CONFLICT",`cron job ${a.id} already exists with a different definition: existing "${l.title}" vs import "${a.title}" -- refusing the whole file, nothing written`,4)}lm(t,r4(a)),s.push(a)}}).immediate();let i=[];for(let a of s)a.working_directory!==null&&!X6(a.working_directory)&&i.push(`job ${a.id} "${a.title}": working_directory ${a.working_directory} does not exist on this host`);return{imported:s.length,skipped:r,warnings:i}}function NC(t){let e=[`imported ${t.imported}, skipped ${t.skipped.length} already-present`];for(let n of t.skipped)e.push(`skipped: ${n.id} "${n.title}"`);for(let n of t.warnings)e.push(`warning: ${n}`);return e.join(`
2492
- `)}var J6,V6,tE,EC,TC,K6,vC,nE=C(()=>{"use strict";Fs();wC();eE();No();dm();J6=t=>t||process.env.LMBEE_DB||yC(t),V6=t=>{},tE="lmbee-cron-export",EC=["schema_version","kind","exported_at","exported_from","jobs"],TC=["id","title","schedule_expr","action_kind","action_payload","provider","model","working_directory","escalation_owner","timezone","enabled"],K6=["provider","model","working_directory","escalation_owner","timezone"];vC=`usage: lmbee cron export PATH [--json] -- write all cron job DEFINITIONS (ids preserved) as one JSON document;
2492
+ `)}var J6,V6,tE,EC,TC,K6,vC,nE=C(()=>{"use strict";Fs();wC();eE();No();dm();J6=t=>t||process.env.LMBEE_DB||yC(t),V6=t=>{},tE="lmbee-cron-export",EC=["schema_version","kind","exported_at","exported_from","jobs"],TC=["id","title","schedule_expr","action_kind","action_payload","provider","model","working_directory","escalation_owner","timezone","enabled"],K6=["provider","model","working_directory","escalation_owner","timezone"];vC=`usage: lmctl cron export PATH [--json] -- write all cron job DEFINITIONS (ids preserved) as one JSON document;
2493
2493
  refuses if PATH already exists (rm it first; there is no --force). Definitions only, never execution state.`});import{existsSync as s4,readFileSync as o4}from"node:fs";import{resolve as i4}from"node:path";function Ds(t,e,n={}){if(t===void 0)return;if(typeof t!="string"||!/^\d+$/.test(t))throw new U("INVALID_ARGUMENT",`${e} must be a nonnegative integer`);let r=Number(t),s=n.minimum??1;if(!Number.isSafeInteger(r)||r<s||n.maximum!==void 0&&r>n.maximum){let o=n.maximum!==void 0?`between ${s} and ${n.maximum}`:`>= ${s}`;throw new U("INVALID_ARGUMENT",`${e} must be an integer ${o}`)}return r}function Br(t,e){let n=Ds(t,e,{minimum:1});if(n===void 0)throw new U("INVALID_ARGUMENT",`${e} is required`);return n}function a4(t){return t==="enqueue"||t==="command"||t==="script"||t==="session"||t==="miniplayer"}function c4(t){return l4.includes(t)}function xC(t){let e=t.payload,n=t.prompt,r=t["prompt-file"];if([e,n,r].filter(o=>o!==void 0).length>1)throw new U("INVALID_ARGUMENT","provide only one of --payload, --prompt, or --prompt-file");if(r!==void 0){if(typeof r!="string")throw new U("INVALID_ARGUMENT","--prompt-file requires a path");try{return o4(r,"utf8")}catch(o){throw new U("INVALID_ARGUMENT",`cannot read --prompt-file ${r}: ${o instanceof Error?o.message:String(o)}`)}}if(typeof e=="string")return e;if(typeof n=="string")return n}function Ve(t){return typeof t=="string"?t:void 0}function Lt(t,e,n={}){return t?JSON.stringify({schema_version:1,ok:!0,data:e,meta:n}):f4(e,n)}function RC(t,e){return t?{output:JSON.stringify({schema_version:1,ok:!1,error:{code:e.code,message:e.message}}),exitCode:e.exitCode}:{output:`${e.code}: ${e.message}`,exitCode:e.exitCode}}function rE(t){return t.last_tick_at===null?"last cron evaluation: not recorded yet (the daemon predates the tick record, or has not ticked)":`last cron evaluation: ${t.last_tick_at} (${ac(t.last_tick_age_ms??0)} ago); gaps in the last 24h: ${t.gaps_24h}${t.gaps_24h>0?`, longest ${ac(t.longest_gap_24h_ms)}`:""}`}function u4(t){let e=t.last_run,n=t.recent_runs,r=t.holding_fence;if(e===void 0||n===void 0)return"";let s=[];return e===null?s.push("never run"):(s.push(e.exit_code!==null&&e.exit_code!==0&&e.status==="failed"?`${e.status} (exit ${e.exit_code})`:e.status),s.push(`${n.failed}/${n.window} failed${n.escalated>0?`, ${n.escalated} escalated`:""}`)),r!=null&&Date.now()-Date.parse(r.since)>d4&&s.push(`HOLDING FENCE run ${r.id} (${r.status}/${r.execution_state}) since ${r.since}`),s.join("; ")}function f4(t,e){let n;if(Array.isArray(t)){let s=o=>o!==null&&typeof o=="object"&&"title"in o;n=t.length===0?"(empty)":t.every(s)?(0,ea.formatTable)([["ID","ENABLED","KIND","SCHEDULE","LAST RUN","TITLE"],...t.map(o=>[...["id","enabled","action_kind","schedule_expr"].map(a=>(0,ea.escapeCell)(String(o[a]??""))),(0,ea.escapeCell)(u4(o)),(0,ea.escapeCell)(String(o.title??""))])]):t.map(o=>JSON.stringify(o)).join(`
2494
2494
  `)}else typeof t=="string"?n=t:n=JSON.stringify(t,null,2);let r=e.next_cursor;return r!=null?`${n}
2495
2495
  More records: --after ${String(r)}`:n}async function LC(t,e,n,r,s=[]){try{let o=()=>typeof t=="function"?t():t,i=()=>typeof s=="function"?s():s,[a,...l]=e,c=a==="add"?"create":a==="rm"?"remove":a;switch(c){case"tick":{let d=await cm(o(),i(),Date.now(),{});return{output:Lt(r,{fired:d}),exitCode:0}}case"create":{let d=n.title;if(typeof d!="string")throw new U("INVALID_ARGUMENT","--title is required");let u=n.schedule;if(typeof u!="string")throw new U("INVALID_ARGUMENT","--schedule is required");let f=n.kind;if(typeof f!="string"||!a4(f))throw new U("INVALID_ARGUMENT",'--kind must be "enqueue", "command", "script", "session", or "miniplayer"');let m=xC(n);if(m===void 0)throw new U("INVALID_ARGUMENT",f==="session"||f==="miniplayer"?"--prompt or --prompt-file is required":"--payload is required");vc(u);let p=null;if(n.target!==void 0){if(f!=="session")throw new U("INVALID_ARGUMENT","--target is only for --kind session");let h=String(n.target),g=h.lastIndexOf(":"),w=g>0?h.slice(g+1):"";if(!/^[A-Za-z0-9_][A-Za-z0-9_.-]{0,127}$/.test(w))throw new U("INVALID_ARGUMENT","--target must be TEAMFILE:ALIAS");let y=i4(h.slice(0,g));if(!s4(y))throw new U("INVALID_ARGUMENT",`--target teamfile not found: ${y}`);p={teamfile:y,alias:w}}im(f,m),am({targeted:p!==null,actionKind:f,provider:Ve(n.provider)??null,model:Ve(n.model)??null,workingDirectory:Ve(n.cwd)??null,escalationOwner:Ve(n.owner)??null}),n.timezone!==void 0&&n.timezone!==""&&Tc(String(n.timezone));let _=lm(o(),{...p?{sessionTarget:p}:{},title:d,scheduleExpr:u,actionKind:f,actionPayload:m,enabled:n.disabled!==!0,timezone:typeof n.timezone=="string"?n.timezone:void 0,...Ve(n.provider)!==void 0?{provider:Ve(n.provider)}:{},...Ve(n.model)!==void 0?{model:Ve(n.model)}:{},...Ve(n.cwd)!==void 0?{workingDirectory:Ve(n.cwd)}:{},...Ve(n.owner)!==void 0?{escalationOwner:Ve(n.owner)}:{}});return{output:Lt(r,Je(o(),_.id)),exitCode:0}}case"show":{let d=Br(l[0],"id"),u=Je(o(),d);return{output:Lt(r,{...u,...$w(o(),[d]).get(d)}),exitCode:0}}case"list":{let d=n.enabled,u;if(d!==void 0){if(d!=="true"&&d!=="false")throw new U("INVALID_ARGUMENT",'--enabled must be "true" or "false"');u=d==="true"}let{rows:f,meta:m}=eC(o(),{enabled:u,limit:Ds(n.limit,"--limit",{maximum:1e4})??100,after:Ds(n.after,"--after",{minimum:0})??0}),p=$w(o(),f.map(g=>g.id)),_=lc(o(),Date.now()),h=Lt(r,f.map(g=>({...g,...p.get(g.id)})),{...m,tick:_});return{output:r?h:`${h}