@lmctl-ai/lmctl 0.2.7 → 0.2.8
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 +1 -1
- package/dist/cli/hello.d.ts +9 -0
- package/dist/cli/index.js +420 -340
- package/dist/cli/init.d.ts +21 -0
- package/dist/cli/plan.d.ts +9 -0
- package/dist/cli/quickrun.d.ts +11 -0
- package/dist/cli/script.js +1 -1
- package/dist/cli/teamfile-template.d.ts +12 -5
- package/dist/providers/config/init-config.d.ts +43 -0
- package/dist/providers/seed-instruction.d.ts +1 -1
- package/examples/book-team.lmctl +1 -1
- package/examples/research-team.lmctl +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `lmctl init` — the first command in the onboarding sequence (operator,
|
|
3
|
+
* 2026-09-29): writes ~/.lmctl/init.toml, a starting-point config of three
|
|
4
|
+
* provider SLOTS (Provider1/2/3), each with three model tiers, used by
|
|
5
|
+
* `lmctl plan` and `lmctl quickrun` to fill ${ProviderN}/${ModelN}
|
|
6
|
+
* placeholders without asking the user provider/model questions directly.
|
|
7
|
+
*
|
|
8
|
+
* Presence detection only -- never a usage ranking (see init-config.ts's
|
|
9
|
+
* own header comment for why session-count ranking was confirmed wrong on
|
|
10
|
+
* a real machine, not just assumed). The bar for the generated file is
|
|
11
|
+
* "useful starting point", not "correct": it is shown to the user and
|
|
12
|
+
* clearly marked as theirs to edit.
|
|
13
|
+
*/
|
|
14
|
+
import type { CliIo } from './common.js';
|
|
15
|
+
export interface InitDeps {
|
|
16
|
+
/** Test seam: defaults to ~/.lmctl/init.toml (getDefaultInitConfigPath()). */
|
|
17
|
+
configPath?: string;
|
|
18
|
+
/** Test seam: defaults to real filesystem presence detection. */
|
|
19
|
+
detectProviders?: () => string[];
|
|
20
|
+
}
|
|
21
|
+
export declare function runInit(args: string[], io?: CliIo, deps?: InitDeps): Promise<number>;
|
package/dist/cli/plan.d.ts
CHANGED
|
@@ -16,5 +16,14 @@ export interface PlanDeps {
|
|
|
16
16
|
editFile?: (path: string) => void;
|
|
17
17
|
/** Overrides stdin TTY detection (wizard auto-trigger). */
|
|
18
18
|
stdinIsTTY?: boolean;
|
|
19
|
+
/** Test seam: defaults to ~/.lmctl/init.toml (getDefaultInitConfigPath()). */
|
|
20
|
+
initConfigPath?: string;
|
|
19
21
|
}
|
|
20
22
|
export declare function runPlan(args: string[], io?: CliIo, deps?: PlanDeps): Promise<number>;
|
|
23
|
+
/** Reads a `--from`-style source: a local file, a directory holding exactly
|
|
24
|
+
* one .lmctl file, or an https:// URL (https-only, redirect-pinned).
|
|
25
|
+
* Exported for reuse by quickrun/hello, which fetch the same kinds of
|
|
26
|
+
* sources (a teamfile template, or a per-provider example) via the same
|
|
27
|
+
* rules -- no local http://, https-only redirects, one candidate file per
|
|
28
|
+
* directory. */
|
|
29
|
+
export declare function readPlanSource(source: string, fetchFn: typeof fetch, cwd: string): Promise<string>;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { CliIo } from './common.js';
|
|
2
|
+
import { runTerminal } from './terminal.js';
|
|
3
|
+
export interface QuickrunDeps {
|
|
4
|
+
fetchFn?: typeof fetch;
|
|
5
|
+
cwd?: string;
|
|
6
|
+
/** Test seam: defaults to ~/.lmctl/init.toml (getDefaultInitConfigPath()). */
|
|
7
|
+
initConfigPath?: string;
|
|
8
|
+
/** Test seam: defaults to the real runTerminal (a live provider TUI). */
|
|
9
|
+
openTerminal?: typeof runTerminal;
|
|
10
|
+
}
|
|
11
|
+
export declare function runQuickrun(args: string[], io?: CliIo, deps?: QuickrunDeps): Promise<number>;
|
package/dist/cli/script.js
CHANGED
|
@@ -1910,7 +1910,7 @@ fn clamp(val, low, high) {
|
|
|
1910
1910
|
AND status = 'running'`,e,t),s=0;for(let i of n)ep(r,i)!==null&&(s+=1);return s}import{platform as Ya}from"node:os";import{realpathSync as tp}from"node:fs";import{resolve as rp}from"node:path";function Rs(r){let e=rp(r);try{return tp(e)}catch{return e}}var rn=class{providers=[];adapters=new Map;register(e){this.providers.push(e)}async listAll(e){let t=[],n=e?.provider?this.providers.filter(o=>o.name===e.provider):this.providers,s=await Promise.all(n.map(o=>o.discoverSessions()));for(let o of s)t.push(...o);let i=t;if(e?.workspace){let o=Rs(e.workspace);i=i.filter(a=>Rs(a.cwd)===o)}if(e?.from){let o=e.from.getTime();i=i.filter(a=>a.modifiedAt.getTime()>=o)}if(e?.to){let o=e.to.getTime();i=i.filter(a=>a.modifiedAt.getTime()<=o)}return i.sort((o,a)=>a.modifiedAt.getTime()-o.modifiedAt.getTime()),e?.limit&&(i=i.slice(0,e.limit)),i}async getSession(e,t){let n=t?this.providers.filter(s=>s.name===t):this.providers;for(let s of n){let i=await s.loadSession(e);if(i)return i}return null}async resolveSession(e,t){let n=await this.resolveSessions(e,t);return n.length===1?n[0]:null}async resolveSessions(e,t){let n=t?this.providers.filter(i=>i.name===t):this.providers,s=[];for(let i of n){let o=await i.resolveSessionPath(e);o&&s.push({provider:i,filePath:o})}return s}getWatchTargets(){let e=Ya()==="darwin"||Ya()==="win32",t=[];for(let n of this.providers)if(typeof n.getWatchPaths=="function")for(let s of n.getWatchPaths())t.push({provider:n.name,directory:s.directory,recursive:e,filePattern:s.filePattern});return t}async deleteSession(e,t){let n=await this.resolveSession(e,t);return n?n.provider.deleteSession(e):!1}registerAdapter(e){this.adapters.set(e.name,e),this.providers.push({name:e.name,discoverSessions:()=>e.discover(),loadSession:t=>e.load(t),resolveSessionPath:t=>e.resolvePath(t),deleteSession:t=>e.delete(t),getWatchPaths:()=>e.getWatchPaths?.()??[]})}getAdapter(e){return this.adapters.get(e)}getAllAdapters(){return[...this.adapters.values()]}};function dt(r){let e=new rn;for(let t of r)e.registerAdapter(t);return e}import{join as lc}from"node:path";import{readdir as nn,stat as el,unlink as ip}from"node:fs/promises";import{join as Nt,basename as tl}from"node:path";import{createReadStream as za}from"node:fs";import{createInterface as Qa}from"node:readline";import{stat as np}from"node:fs/promises";import{homedir as sp}from"node:os";var U=sp();function Za(r){try{let e=JSON.parse(r);return e===null||typeof e!="object"||Array.isArray(e)?void 0:e}catch{return}}async function*Q(r){let e=za(r,{encoding:"utf-8"}),t=Qa({input:e,crlfDelay:1/0});try{for await(let n of t){let s=n.trim();if(!s)continue;let i=Za(s);i!==void 0&&(yield i)}}finally{e.destroy()}}async function Ee(r,e){let t=[],n=za(r,{encoding:"utf-8"}),s=Qa({input:n,crlfDelay:1/0});try{for await(let i of s){let o=i.trim();if(!o)continue;let a=Za(o);if(a!==void 0&&t.push(a),t.length>=e)break}}finally{s.close(),n.destroy()}return t}async function Z(r){let e=await np(r);return{createdAt:e.birthtime,modifiedAt:e.mtime}}function q(r){return r.replace(/\\n/g,`
|
|
1911
1911
|
`).replace(/\\t/g," ").replace(/\\r/g,"\r")}function M(r,e){return r.length<=e?r:r.slice(0,e-3)+"..."}function ve(r){return r!==null&&typeof r=="object"&&!Array.isArray(r)?r:void 0}function op(r){return r==="file-history-snapshot"||r==="progress"||r==="system"||r==="queue-operation"}function ap(r){if(r.type==="system"){let e=r.message?.content;if(typeof e=="string"&&e.trim())return q(e.trim());if(e!==void 0)return JSON.stringify(e,null,2)}return r.type==="progress"&&r.data!==void 0?JSON.stringify(r.data,null,2):JSON.stringify(r,null,2)}async function rl(r){let e=0,t=0,n=0,s=0,i=0,o=0,a,l;for await(let c of Q(r))if(!c.isSidechain){if(c.type==="user"&&!c.isMeta&&c.message){let u=c.message.content;if(typeof u=="string"){if(u.includes("<command-name>")||u.includes("<local-command-"))continue;e++,l=M(u.trim(),80)}}else if(c.type==="assistant"&&c.message){let u=c.message.content;if(Array.isArray(u))for(let p of u)p.type==="text"&&e++;let d=c.message.usage;if(d){let p=d.input_tokens||0,m=d.cache_creation_input_tokens||0,h=d.cache_read_input_tokens||0,g=d.output_tokens||0;t+=p+m+h,n+=h,s+=m,i+=g;let T=p+m+h;T&&(o=T),a={inputTokens:p,cacheWriteInputTokens:m,cachedInputTokens:h,outputTokens:g}}}}return{messageCount:e,totalInputTokens:t,totalCacheReadInputTokens:n,totalCacheWriteInputTokens:s,totalOutputTokens:i,lastInputTokens:o,lastTokenUsage:a,lastUserMessage:l}}var sn=class{name="claude";claudeDir;projectsDir;constructor(e={}){this.claudeDir=e.claudeDir||Nt(U,".claude"),this.projectsDir=Nt(this.claudeDir,"projects")}getWatchPaths(){return[{directory:this.projectsDir,filePattern:/\.jsonl$/}]}async discoverSessions(){let e=[],t;try{t=await nn(this.projectsDir)}catch{return e}for(let n of t){let s=Nt(this.projectsDir,n);if(!(await el(s).catch(()=>null))?.isDirectory())continue;let o;try{o=await nn(s)}catch{continue}for(let a of o){if(!a.endsWith(".jsonl"))continue;let l=tl(a,".jsonl");if(!/^[0-9a-f]{8}-/.test(l))continue;let c=Nt(s,a);try{let u=await this.extractSummary(c,l);u&&e.push(u)}catch{}}}return e}async extractSummary(e,t){let n=await Ee(e,50),s=await Z(e),i="",o,a,l,c,u,d;for(let m of n)if(!op(m.type)&&(!i&&m.cwd&&(i=m.cwd),!o&&m.slug&&(o=m.slug),!a&&m.version&&(a=m.version),!d&&m.timestamp&&(d=new Date(m.timestamp)),m.type==="assistant"&&m.message?.model&&(l=m.message.model),m.type==="user"&&!m.isMeta&&m.message)){let h=m.message.content;if(typeof h=="string"){if(h.includes("<command-name>")||h.includes("<local-command-"))continue;c||(c=M(h.trim(),80)),u=M(h.trim(),80)}}if(!i)return null;let p=await rl(e);return{provider:"claude",sessionId:t,slug:o,cwd:i,createdAt:d||s.createdAt,modifiedAt:s.modifiedAt,model:l,cliVersion:a,startCommand:"claude",firstUserMessage:c,lastUserMessage:p.lastUserMessage??u,messageCount:p.messageCount,filePath:e,totalInputTokens:p.totalInputTokens||void 0,totalCachedInputTokens:p.totalCacheReadInputTokens||void 0,totalCacheWriteInputTokens:p.totalCacheWriteInputTokens||void 0,totalOutputTokens:p.totalOutputTokens||void 0,lastInputTokens:p.lastInputTokens||void 0,lastTokenUsage:p.lastTokenUsage}}async loadSession(e){let t=await this.resolveSessionFile(e);if(!t)return null;let n=await Z(t),s=[],i="",o,a,l,c,u,d,p,m=e,h,g=[];for await(let f of Q(t))if(f.type!=="file-history-snapshot"&&!f.isSidechain){if(!i&&f.cwd&&(i=f.cwd),!o&&f.slug&&(o=f.slug),!a&&f.version&&(a=f.version),!c&&f.gitBranch&&(c=f.gitBranch),!p&&f.timestamp&&(p=new Date(f.timestamp)),f.sessionId&&(m=f.sessionId),f.type==="system"||f.type==="progress"||f.type==="queue-operation"){s.push({role:"system",timestamp:new Date(f.timestamp||0),content:ap(f)});continue}if(f.type==="user"&&f.message){if(f.isMeta)continue;let E=new Date(f.timestamp||0),y=f.message.content;if(f.isCompactSummary!==!0&&f.isVisibleInTranscriptOnly!==!0&&f.promptSource!=="system"&&f.toolUseResult===void 0&&f.origin?.kind!=="task-notification"&&f.interruptedMessageId===void 0&&f.interruptedByShutdown!==!0&&f.scheduledTaskId===void 0&&f.scheduledFireId===void 0&&!(Array.isArray(y)&&y.some(I=>I.type==="tool_result"))){let I=typeof y=="string"?y:Array.isArray(y)?y.filter(v=>v.type==="text"&&typeof v.text=="string").map(v=>v.text).join(`
|
|
1912
1912
|
`):"";I&&!/^<(?:command-name>|local-command-)/u.test(I.trimStart())&&g.push(I)}if(typeof y=="string"){if(y.includes("<command-name>")||y.includes("<local-command-"))continue;u||(u=M(y.trim(),80)),d=M(y.trim(),80),h=void 0,s.push({role:"user",timestamp:E,content:q(y.trim())})}else if(Array.isArray(y)){for(let I of y)if(I.type==="tool_result"){let v=typeof I.content=="string"?q(I.content):JSON.stringify(I.content);s.push({role:"tool_result",timestamp:E,content:v,toolCallId:I.tool_use_id})}}}if(f.type==="assistant"&&f.message){f.message.stop_reason===null||f.message.stop_reason==="tool_use"?h="in_progress":typeof f.message.stop_reason=="string"&&(h="complete"),f.message.model&&(l=f.message.model);let E=new Date(f.timestamp||0),y=f.message.content;if(Array.isArray(y))for(let I of y)I.type==="text"&&I.text?s.push({role:"assistant",timestamp:E,content:q(I.text),model:f.message.model,...typeof f.message.stop_reason=="string"?{stopReason:f.message.stop_reason}:{}}):I.type==="tool_use"?s.push({role:"tool_use",timestamp:E,content:I.name||"unknown tool",toolName:I.name,toolInput:JSON.stringify(I.input,null,2),toolInputData:I.input,toolCallId:I.id,model:f.message.model,...typeof f.message.stop_reason=="string"?{stopReason:f.message.stop_reason}:{}}):I.type==="thinking"&&I.thinking&&s.push({role:"thinking",timestamp:E,content:I.thinking})}}if(!i)return null;lp(s);let T=await rl(t);return{provider:"claude",sessionId:m,slug:o,cwd:i,createdAt:p||n.createdAt,modifiedAt:n.modifiedAt,model:l,cliVersion:a,startCommand:"claude",gitBranch:c,turnStatus:h,firstUserMessage:u,lastUserMessage:d,messageCount:T.messageCount,filePath:t,totalInputTokens:T.totalInputTokens||void 0,totalCachedInputTokens:T.totalCacheReadInputTokens||void 0,totalCacheWriteInputTokens:T.totalCacheWriteInputTokens||void 0,totalOutputTokens:T.totalOutputTokens||void 0,lastInputTokens:T.lastInputTokens||void 0,lastTokenUsage:T.lastTokenUsage,messages:s,userPrompts:g}}async resolveSessionPath(e){return this.resolveSessionFile(e)}async deleteSession(e){let t=await this.resolveSessionFile(e);return t?(await ip(t),!0):!1}async resolveSessionFile(e){let t;try{t=await nn(this.projectsDir)}catch{return null}let n=[];for(let o of t){let a=Nt(this.projectsDir,o);if(!(await el(a).catch(()=>null))?.isDirectory())continue;let c;try{c=await nn(a)}catch{continue}for(let u of c){if(!u.endsWith(".jsonl"))continue;let d=tl(u,".jsonl");/^[0-9a-f]{8}-/.test(d)&&n.push({filePath:Nt(a,u),fileSessionId:d})}}let s=n.find(o=>o.fileSessionId===e);if(s)return s.filePath;let i=n.filter(o=>o.fileSessionId.startsWith(e));if(i.length===1)return i[0].filePath;for(let o of n){let a=await Ee(o.filePath,20);for(let l of a)if(l.slug&&l.slug===e)return o.filePath}return null}};function lp(r){let e=cp(r),t=new Set;for(let n of r){if(n.role!=="tool_use"||n.toolName!=="Bash"||n.toolInputData?.run_in_background!==!0||n.toolCallId===void 0)continue;let s=e.get(n.toolCallId);if(s===void 0)continue;let i=s.content.match(/Command running in background with ID:\s*([A-Za-z0-9_-]+)/)?.[1];i!==void 0&&(t.add(i),n.backgroundTaskId=i,s.backgroundTaskId=i)}for(let n of r){if(n.role!=="tool_use"||n.toolName!=="TaskOutput"&&n.toolName!=="TaskStop"||n.toolCallId===void 0)continue;let s=n.toolInputData?.task_id;if(typeof s!="string"||!t.has(s))continue;n.backgroundTaskId=s;let i=e.get(n.toolCallId);i!==void 0&&(i.backgroundTaskId=s)}}function cp(r){let e=new Map;for(let t of r)t.role==="tool_result"&&t.toolCallId!==void 0&&e.set(t.toolCallId,t);return e}import{spawn as cl}from"node:child_process";import{mkdirSync as Ip}from"node:fs";import{readFileSync as dp,writeFileSync as dS,mkdirSync as uS}from"node:fs";import{join as up,dirname as fS}from"node:path";import{homedir as mp}from"node:os";var Ns={claude:{start:"claude",resume:"claude --resume {{session_id}} --dangerously-skip-permissions",profiles:{default:{description:"Default profile",flags:""}}},codex:{start:"codex",resume:"codex resume {{session_id}} --dangerously-bypass-approvals-and-sandbox",profiles:{default:{description:"Default profile",flags:""}}},opencode:{start:"opencode",resume:"opencode --session {{session_id}}",profiles:{default:{description:"Default profile",flags:""}}},gemini:{start:"gemini",resume:"gemini --resume {{session_id}} -y",profiles:{default:{description:"Default profile",flags:""}}},copilot:{start:"copilot",resume:"copilot --resume={{session_id}} --yolo",profiles:{default:{description:"Default profile",flags:""}}},qwen:{start:"qwen",resume:"qwen --resume {{session_id}} --approval-mode yolo",profiles:{default:{description:"Default profile",flags:""}}},agy:{start:"agy",resume:"agy --conversation {{session_id}} --dangerously-skip-permissions",profiles:{default:{description:"Default profile",flags:""}}},kimi:{start:"kimi --auto",resume:"kimi --session {{session_id}} --auto",profiles:{default:{description:"Default profile",flags:""}}},hermes:{start:"hermes",resume:"hermes --yolo --resume {{session_id}}",profiles:{default:{description:"Default profile",flags:""}}}},fp={claude:{mode:"argv",argv:["claude","-p","{{prompt}}","--resume","{{session_id}}"]},codex:{mode:"argv",argv:["codex","exec","--dangerously-bypass-approvals-and-sandbox","--skip-git-repo-check","resume","{{session_id}}","{{prompt}}"]},opencode:{mode:"argv",argv:["opencode","run","--session","{{session_id}}","{{prompt}}"]},gemini:{mode:"argv",argv:["gemini","-p","{{prompt}}","--resume","{{session_id}}"]},copilot:{mode:"argv",argv:["gh","copilot","--","--resume={{session_id}}","-p","{{prompt}}","-s","--allow-all-tools"]},qwen:{mode:"argv",argv:["qwen","-p","{{prompt}}","--resume","{{session_id}}","--approval-mode","yolo"]},agy:{mode:"argv",argv:["agy","--conversation","{{session_id}}","-p","{{prompt}}","--dangerously-skip-permissions","--print-timeout","8h"]},kimi:{mode:"argv",argv:["kimi","-p","{{prompt}}","--session","{{session_id}}","--output-format","stream-json"]},hermes:{mode:"argv",argv:["hermes","--yolo","-z","{{prompt}}","--resume","{{session_id}}"]}};function pp(r){return r.startsWith('"')&&r.endsWith('"')||r.startsWith("'")&&r.endsWith("'")?r.slice(1,-1):r}function hp(r){let e=r.indexOf("=");if(e===-1)return null;let t=r.slice(0,e).trim(),n=pp(r.slice(e+1).trim());return t?{key:t,value:n}:null}function gp(r){let e=r.trim();return e.startsWith("[")&&e.endsWith("]")?e.slice(1,-1).trim():null}function on(r){return"'"+r.replace(/'/g,"'\\''")+"'"}function _p(){return up(mp(),".lmctl","config.toml")}function sl(r){let e=r??_p(),t;try{t=dp(e,"utf-8")}catch{return{tools:{}}}return Ep(t)}function nl(r){if(typeof r=="number")return Number.isFinite(r)?r:null;let e=String(r).trim();if(!e)return null;let t=e.match(/^(\d+(?:\.\d+)?)\s*([KkMm]?)$/);if(!t)return null;let n=parseFloat(t[1]);if(!Number.isFinite(n))return null;switch(t[2]){case"K":case"k":return Math.round(n*1e3);case"M":case"m":return Math.round(n*1e6);default:return Math.round(n)}}function Ep(r){let e={},t={},n=r.split(`
|
|
1913
|
-
`),s={kind:"none"},i={},o=!1,a={};function l(){if(s.kind==="tool"){let d=e[s.tool]??{};if(s.profile){let p=d.profiles?.[s.profile]??{};i.description&&(p.description=i.description),i.flags!==void 0&&(p.flags=i.flags),d.profiles||(d.profiles={}),d.profiles[s.profile]=p}else i.start&&(d.start=i.start),i.resume&&(d.resume=i.resume),i.agent&&(d.agent=i.agent),i.profile_flag!==void 0&&(d.profile_flag=i.profile_flag);e[s.tool]=d}else if(s.kind==="compact"){let d=s.provider?t.providers?.[s.provider]??{}:t.global??{};if(i.min!==void 0){let p=nl(i.min);p!==null&&(d.min=p)}if(i.max!==void 0){let p=nl(i.max);p!==null&&(d.max=p)}s.provider?(t.providers||(t.providers={}),t.providers[s.provider]=d):t.global=d}i={}}for(let d of n){let p=d.trim();if(!p||p.startsWith("#"))continue;let m=gp(p);if(m!==null){if(o=!0,l(),m.startsWith("tool.")){let g=".profile.",T=m.indexOf(g);T!==-1?s={kind:"tool",tool:m.slice(5,T),profile:m.slice(T+g.length)}:s={kind:"tool",tool:m.slice(5),profile:null}}else m==="compact"?s={kind:"compact",provider:null}:m.startsWith("compact.")?s={kind:"compact",provider:m.slice(8)}:s={kind:"none"};continue}let h=hp(p);h&&s.kind!=="none"?i[h.key]=h.value:h&&!o&&(a[h.key]=h.value)}l();let c={tools:e};(t.global||t.providers)&&(c.compact=t);let u=a.site_name;return u!==void 0&&u.length>0&&(c.site_name=u),c}function yp(r,e,t){if(!t)return"";let n=r.tools[e]??Ns[e],s=n?.profiles?.[t]?.flags??Ns[e]?.profiles?.[t]?.flags;if(s!==void 0)return s.trim();if(n?.profile_flag)return n.profile_flag.replace(/\{\{profile\}\}/g,t).trim();throw new Error(`Profile "${t}" is not configured for tool "${e}"`)}function il(r,e){return!!r.tools[e]}function ol(r,e,t,n,s){let i=r.tools[e]??Ns[e],o;if(i?.agent){let l=on(n);o=i.agent.replace(/\{\{session_id\}\}/g,t).replace(/\{\{prompt\}\}/g,l)}else{let l=fp[e];if(!l)throw new Error(`No agent command configured for tool "${e}"`);let c=l.argv.map(u=>u.replace(/\{\{session_id\}\}/g,t).replace(/\{\{prompt\}\}/g,n));if(l.mode==="stdin"){let u=c.map(on).join(" ");o=`printf '%s\\n' ${on(n)} | ${u}`}else o=c.map(on).join(" ")}let a=yp(r,e,s);return a&&(o+=" "+a),o}function al(r=process.env){let e=r.LMCTL_ONE_SHOT_TIMEOUT_MS;if(!e)return 6e5;let t=Number(e);return!Number.isFinite(t)||t<=0?6e5:t}var Tp="ACK: waiting for explicit lmctl task.",B=`lmctl seed initial connection only. Create or pin this provider session, then stop. Do not inspect files, run commands, infer a task, delegate, or edit.
|
|
1913
|
+
`),s={kind:"none"},i={},o=!1,a={};function l(){if(s.kind==="tool"){let d=e[s.tool]??{};if(s.profile){let p=d.profiles?.[s.profile]??{};i.description&&(p.description=i.description),i.flags!==void 0&&(p.flags=i.flags),d.profiles||(d.profiles={}),d.profiles[s.profile]=p}else i.start&&(d.start=i.start),i.resume&&(d.resume=i.resume),i.agent&&(d.agent=i.agent),i.profile_flag!==void 0&&(d.profile_flag=i.profile_flag);e[s.tool]=d}else if(s.kind==="compact"){let d=s.provider?t.providers?.[s.provider]??{}:t.global??{};if(i.min!==void 0){let p=nl(i.min);p!==null&&(d.min=p)}if(i.max!==void 0){let p=nl(i.max);p!==null&&(d.max=p)}s.provider?(t.providers||(t.providers={}),t.providers[s.provider]=d):t.global=d}i={}}for(let d of n){let p=d.trim();if(!p||p.startsWith("#"))continue;let m=gp(p);if(m!==null){if(o=!0,l(),m.startsWith("tool.")){let g=".profile.",T=m.indexOf(g);T!==-1?s={kind:"tool",tool:m.slice(5,T),profile:m.slice(T+g.length)}:s={kind:"tool",tool:m.slice(5),profile:null}}else m==="compact"?s={kind:"compact",provider:null}:m.startsWith("compact.")?s={kind:"compact",provider:m.slice(8)}:s={kind:"none"};continue}let h=hp(p);h&&s.kind!=="none"?i[h.key]=h.value:h&&!o&&(a[h.key]=h.value)}l();let c={tools:e};(t.global||t.providers)&&(c.compact=t);let u=a.site_name;return u!==void 0&&u.length>0&&(c.site_name=u),c}function yp(r,e,t){if(!t)return"";let n=r.tools[e]??Ns[e],s=n?.profiles?.[t]?.flags??Ns[e]?.profiles?.[t]?.flags;if(s!==void 0)return s.trim();if(n?.profile_flag)return n.profile_flag.replace(/\{\{profile\}\}/g,t).trim();throw new Error(`Profile "${t}" is not configured for tool "${e}"`)}function il(r,e){return!!r.tools[e]}function ol(r,e,t,n,s){let i=r.tools[e]??Ns[e],o;if(i?.agent){let l=on(n);o=i.agent.replace(/\{\{session_id\}\}/g,t).replace(/\{\{prompt\}\}/g,l)}else{let l=fp[e];if(!l)throw new Error(`No agent command configured for tool "${e}"`);let c=l.argv.map(u=>u.replace(/\{\{session_id\}\}/g,t).replace(/\{\{prompt\}\}/g,n));if(l.mode==="stdin"){let u=c.map(on).join(" ");o=`printf '%s\\n' ${on(n)} | ${u}`}else o=c.map(on).join(" ")}let a=yp(r,e,s);return a&&(o+=" "+a),o}function al(r=process.env){let e=r.LMCTL_ONE_SHOT_TIMEOUT_MS;if(!e)return 6e5;let t=Number(e);return!Number.isFinite(t)||t<=0?6e5:t}var Tp="ACK: waiting for explicit lmctl task.",B=`lmctl seed initial connection only. Create or pin this provider session, then stop. Do not inspect files, run commands, infer a task, delegate, or edit. Reply exactly: ${Tp}`,an=`send this exact lmctl seed instruction: "${B}"`;import{existsSync as Sp}from"node:fs";var ks="LMCTL_SELF_SESSIONID",wp="LMCTL_LAUNCH_SOURCE";function ll(r){let e=r.CLAUDE_CODE_EXECPATH;if(e===void 0||e.length===0)return r;let t=process.platform!=="win32"&&e.toLowerCase().endsWith(".exe"),n=!t&&!Sp(e);if(!t&&!n)return r;b({event:"claude_execpath_sanitized",reason:t?"wrong_platform_exe":"missing_file",value:e,platform:process.platform});let s={...r};return delete s.CLAUDE_CODE_EXECPATH,s}function kt(r,e){let t={...r??process.env,PWD:e};return delete t.OLDPWD,t}function vp(r,e){return e===void 0||e.length===0?r:{...r??process.env,[ks]:e}}function ae(r,e,t,n="chat"){return{...vp(kt(r,e),t),[wp]:n}}function er(r){return r.replace(/\x1b\[[0-9;]*[a-zA-Z]/g,"")}var Ls=class r extends Error{cancelReason;constructor(e,t){super(e),this.name="TurnAbortError",this.cancelReason=t,Object.setPrototypeOf(this,r.prototype)}},G=class r{name;capabilities;injection;provider;binary;seedArgs;seedStdin;resumeTemplate;startTemplate;transportFn;constructor(e){this.name=e.provider.name,this.provider=e.provider,this.capabilities=e.capabilities,this.injection=e.injection,this.binary=e.binary,this.seedArgs=e.seedArgs,this.seedStdin=e.seedStdin,this.resumeTemplate=e.resumeTemplate,this.startTemplate=e.startTemplate,this.transportFn=e.transport}discover(){return this.provider.discoverSessions()}load(e){return this.provider.loadSession(e)}resolvePath(e){return this.provider.resolveSessionPath(e)}delete(e){return this.provider.deleteSession(e)}getWatchPaths(){return this.provider.getWatchPaths?.()??[]}seedArgsFor(e){let t=e?.prompt;return t===void 0?this.seedArgs:this.seedArgs.map(n=>n===B?t:n)}turnOptsFor(e){return e}seed(e,t){return Ip(e,{recursive:!0}),new Promise(n=>{let s;try{s=cl(this.binary,this.seedArgsFor(t),{cwd:e,env:kt(void 0,e),stdio:["pipe","pipe","pipe"],timeout:6e4})}catch(l){return n({success:!1,error:l instanceof Error?l.message:String(l)})}let i="",o="";s.stdout?.on("data",l=>{i+=l.toString()}),s.stderr?.on("data",l=>{o+=l.toString()});let a=t?.prompt!==void 0&&this.seedStdin!==void 0?`${t.prompt}
|
|
1914
1914
|
`:this.seedStdin;a&&s.stdin?.write(a),s.stdin?.end(),s.on("exit",l=>{if(l===0||l===null)n({success:!0});else{let c=er(o).trim(),u=er(i).trim();n({success:!1,error:(c||u).slice(0,500)||`exit code ${l}`})}}),s.on("error",l=>{n({success:!1,error:l.message})})})}resumeCommand(e,t){return this.resumeTemplate.replace(/\{\{session_id\}\}/g,e)}startCommand(e){return this.startTemplate}manualSeedHint(e){return{launchCommand:`cd ${e} && ${this.binary}`,postLaunchNote:an,lsCommand:`lmctl ls --provider ${this.name} --sessiondir ${e}`}}static DEFAULT_IDLE_TIMEOUT_MS=8*60*60*1e3;sendTurn(e,t){let n=this.turnOptsFor(t);if(n.userConfig&&il(n.userConfig,this.name)){let i=n.userConfig.tools[this.name];if(i?.agent)return this.runOneShotAgent(e,n,i.agent)}let s=n.timeoutMs??r.DEFAULT_IDLE_TIMEOUT_MS;return s<=0&&!n.signal?this.transportFn(e,{...n,binary:this.binary}):this.withTimeouts(s,e,n)}withTimeouts(e,t,n){let s=new AbortController,i,o,a="",l,c=()=>{e<=0||(clearTimeout(i),i=setTimeout(()=>{n.onIdleWarning?.(e),u(),s.abort(),l&&l({text:a,sessionId:n.sessionId,durationMs:e,status:"stalled",stalledAfterMs:e})},e))},u=()=>{clearTimeout(i)};n.signal&&(n.signal.aborted?(o="external",s.abort()):n.signal.addEventListener("abort",()=>{o="external",s.abort()},{once:!0})),c();let d={...n,binary:this.binary,signal:s.signal,onDelta:h=>{a+=h,c(),n.onDelta?.(h)},onProgress:h=>{h.kind!=="api_retry"&&c(),n.onProgress?.(h)}},p=this.transportFn(t,d).then(h=>(u(),{...h,status:"ok"})).catch(h=>{throw u(),o==="external"?new Ls("Turn cancelled","external"):h}),m=new Promise(h=>{l=h});return Promise.race([p,m])}runOneShotAgent(e,t,n){let s=ol(t.userConfig,this.name,t.sessionId,e,t.profile),i=Date.now();return new Promise((o,a)=>{let l=al(t.env),c=cl(s,{shell:!0,cwd:t.cwd,env:ae(t.env,t.cwd,t.sessionId),stdio:["pipe","pipe","pipe"]}),u=!1,d=!1,p=setTimeout(()=>{d=!0,c.kill()},l),m="",h="";if(c.stdout?.on("data",g=>{let T=g.toString();m+=T,t.onDelta?.(T)}),c.stderr?.on("data",g=>{h+=g.toString()}),t.signal){if(t.signal.aborted)return clearTimeout(p),c.kill(),a(new Error("Aborted"));t.signal.addEventListener("abort",()=>c.kill(),{once:!0})}c.on("exit",g=>{if(u)return;u=!0,clearTimeout(p);let T=Date.now()-i;d?a(new Error(`Agent command timed out after ${l}ms`)):g===0||g===null?o({text:m.trim(),sessionId:t.sessionId,durationMs:T}):a(new Error(h.trim().slice(0,200)||`Agent command exited with code ${g}`))}),c.on("error",g=>{u||(u=!0,clearTimeout(p),a(g))})})}};import{spawn as hl}from"node:child_process";import{existsSync as gl}from"node:fs";var tr=class extends Error{constructor(e,t){super(e,t),this.name=this.constructor.name}},J=class extends tr{};var ln=class extends tr{rpcCode;rpcData;constructor(e,t,n){super(e),t!==void 0&&(this.rpcCode=t),n!==void 0&&(this.rpcData=n)}};function rr(r,e){let t;if(e&&typeof e=="object"){let n=e.code;typeof n=="number"&&(t=n)}return new ln(r,t,e)}import*as Os from"node:child_process";var xp=1e3;function Ap(r){if(typeof Os.spawnSync=="function")try{Os.spawnSync("taskkill",["/T","/F","/PID",String(r)],{windowsHide:!0})}catch{}}function dl(r){return r.exitCode!==null||r.signalCode!==null}function Cs(r,e){return dl(r)?Promise.resolve(!0):new Promise(t=>{let n=!1,s=()=>{n||(n=!0,clearTimeout(i),t(!0))},i=setTimeout(()=>{n||(n=!0,r.removeListener("exit",s),t(!1))},e);r.once("exit",s)})}async function V(r,e={}){let t=Date.now(),n=e.stdinEnd!==!1,s=e.softDelayMs??5e3,i=e.killDelayMs??5e3,o=e.platform??process.platform,a=e.runTaskkill??Ap;if(dl(r))return{signal:"natural",elapsedMs:Date.now()-t};if(n&&r.stdin&&!r.stdin.destroyed)try{r.stdin.end()}catch{}if(await Cs(r,s))return{signal:"stdin",elapsedMs:Date.now()-t};if(o==="win32"&&typeof r.pid=="number")a(r.pid);else try{r.kill("SIGTERM")}catch{}if(await Cs(r,i))return{signal:"SIGTERM",elapsedMs:Date.now()-t};if(o!=="win32")try{r.kill("SIGKILL")}catch{}return await Cs(r,xp)?{signal:"SIGKILL",elapsedMs:Date.now()-t}:{signal:"abandoned",elapsedMs:Date.now()-t}}function ul(r){K()&&he({event:"legacy",msg:`[debug] ${r}`},{always:!0})}var bp={claude:["--dangerously-skip-permissions"],qwen:["--approval-mode","yolo"],gemini:["-y"],copilot:["--yolo"],agy:["--dangerously-skip-permissions"],lmplayer:["--dangerously-skip-permissions"]},Rp={claude:{yolo:["--dangerously-skip-permissions"],plan:["--permission-mode","plan"]},qwen:{yolo:["--approval-mode","yolo"],plan:["--approval-mode","plan"]},gemini:{yolo:["-y"],plan:["--approval-mode","plan"]},copilot:{yolo:["--yolo"],plan:["--yolo"]},agy:{yolo:["--dangerously-skip-permissions"],plan:["--dangerously-skip-permissions"]},lmplayer:{yolo:["--dangerously-skip-permissions"],plan:["--dangerously-skip-permissions"]}};function Ve(r,e){let t=Rp[r];if(t)return t[e]??t.yolo}var Np=new Set(["codex","opencode","kimi","dsh","hermes","pi"]);function kp(r,e){if(e.length===0)return!0;for(let t=0;t+e.length<=r.length;t++){let n=!0;for(let s=0;s<e.length;s++)if(r[t+s]!==e[s]){n=!1;break}if(n)return!0}return!1}function ie(r,e,t="yolo"){if(Np.has(r)){ul(`YOLO ${r} [mode=${t}]: ${r==="codex"?`JSON-RPC params (approvalPolicy=never, sandbox=${t==="plan"?"read-only":"danger-full-access"})`:r==="opencode"?".opencode/opencode.json (permission=allow)":r==="kimi"?`ACP session/set_config_option (configId=mode, value=${t==="plan"?"plan":"auto"})`:r==="dsh"?"DSH_PERMISSION_MODE=danger-full-access env var on the sidecar spawn \u2014 no argv flag":r==="hermes"?"HERMES_YOLO_MODE=1 shell gate + ACP session/set_mode (modeId=dont_ask) edit gate":r==="pi"?"no tool-permission prompts exist in pi by design \u2014 nothing to bypass":"protocol/config (out-of-band)"} \u2014 no argv flag`);return}let n=t==="plan"?Ve(r,"plan"):bp[r];if(!n){let s=`unknown provider "${r}" \u2014 YOLO contract not configured. Add an entry to YOLO_FLAGS in src/providers/yolo-contract.ts before spawning this provider.`;throw b({event:"yolo_assert_failed",provider:r,reason:"unconfigured_provider",args:e.join(" "),message:s}),new J(s)}if(!kp(e,n)){let s=n.join(" "),i=`${r} spawn missing YOLO flag '${s}' \u2014 YOLO is mandatory for every provider; see src/providers/yolo-contract.ts. Got argv: [${e.map(o=>JSON.stringify(o)).join(", ")}]`;throw b({event:"yolo_assert_failed",provider:r,reason:"flag_missing",required_flag:s,mode:t,args:e.join(" "),message:i}),new J(i)}ul(`YOLO ${r} [mode=${t}]: argv flag '${n.join(" ")}'`)}var Lp=2e3,cn=8*60*60*1e3,Cp=5e3,Op=1e3,Dp=50,Ds=Symbol("check-timed-out");async function dn(r,e,t){if(typeof r=="boolean")return r;let n,s;try{return await Promise.race([r,new Promise(i=>{n=setTimeout(()=>i(Ds),e),n.unref?.(),t!==void 0&&(s=()=>i(Ds),t.addEventListener("abort",s,{once:!0}))})])}finally{n!==void 0&&clearTimeout(n),s!==void 0&&t?.removeEventListener("abort",s)}}function Ke(r,e){if(r===void 0){e.closeStdin();return}let t=!1,n=i=>{if(!t){t=!0;try{r.onRelease?.({elapsedMs:Date.now()-i.startedMs,held:i.held,reason:i.reason})}catch{}}e.closeStdin()},s=Date.now();Mp(r,e,s,n).catch(()=>{n({startedMs:s,held:!1,reason:"check-error"})})}async function Mp(r,e,t,n){let s=r.pollMs??Lp,i=r.maxHoldMs??cn,o=r.settleMs??Cp,a=r.backgroundGraceMs??Op,l=!1,c=null,u=d=>n({startedMs:t,held:l,reason:d});for(;;){if(e.isAborted())return u("aborted");if(e.hasChildExited())return u("child-exited");let d=i-(Date.now()-t);if(d<=0)return u("max-hold");let p;try{p=await dn(r.hasOutstandingWork(),d,e.signal)}catch{return u("check-error")}if(p===Ds)return u(e.isAborted()||e.signal?.aborted===!0?"aborted":"max-hold");if(p)c=null;else{if(!l&&e.wasBackgroundJobLaunched?.()===!0&&Date.now()-t<a){let g=a-(Date.now()-t);await new Promise(T=>setTimeout(T,Math.max(0,Math.min(s,Dp,g))).unref?.());continue}if(!l||o<=0)return u("cleared");c===null&&(c=Date.now(),se(`${e.label}: dispatched work cleared \u2014 settling ${o}ms before closing stdin`));let h=Math.max(c,e.lastEventAtMs());if(!e.isTurnInFlight()&&Date.now()-h>=o)return u("cleared")}if(l||(l=!0,r.onHold?.(),se(`${e.label}: deferring stdin close \u2014 outstanding dispatched work (poll ${s}ms)`)),Date.now()-t>=i)return b({event:"transport_hold_open_max_wait",provider:e.label,elapsed_ms:Date.now()-t}),u("max-hold");let m=Math.max(0,Math.min(s,i-(Date.now()-t)));await new Promise(h=>setTimeout(h,m).unref?.())}}function ml(r){if(r.length===0)return"";if(r.length===1)return r[0];let e=[];for(let t=0;t<r.length;t++)t===0?e.push(r[t]):e.push(`
|
|
1915
1915
|
|
|
1916
1916
|
--- additional response (turn ${t+1} of ${r.length}; likely an injected mid-call notification) ---
|
|
@@ -24,11 +24,18 @@ export declare function parseMemberSpec(raw: string): {
|
|
|
24
24
|
};
|
|
25
25
|
export declare function renderMemberLine(member: MemberSpec): string;
|
|
26
26
|
/**
|
|
27
|
-
* Render the canonical starter teamfile.
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
27
|
+
* Render the canonical starter teamfile. Session ids are never generated
|
|
28
|
+
* here — that is `lmctl seed`'s job.
|
|
29
|
+
*
|
|
30
|
+
* Operator, 2026-09-29 (onboarding revamp, queued#21811): the header
|
|
31
|
+
* teaches the loop -- generate, edit, lint, seed -- and points at ONE page
|
|
32
|
+
* covering the teamfile format plus what lint and seed each do, rather
|
|
33
|
+
* than explaining any of that inline. Deliberately ONE link, not two:
|
|
34
|
+
* lmctl-lead.md is dropped (the seed prompt is cut to bare minimum for the
|
|
35
|
+
* same reason -- an arriving Lead isn't front-loaded with an operating
|
|
36
|
+
* guide) and the previously-inline no-`timeout` paragraph is cut entirely
|
|
37
|
+
* (queued#21813/#21814/#21820, operator: a warning that never stopped
|
|
38
|
+
* anyone who hit it is noise for everyone who has not).
|
|
32
39
|
*/
|
|
33
40
|
export declare function renderTeamfile(opts: {
|
|
34
41
|
teamName: string;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
export interface InitProviderSlot {
|
|
2
|
+
provider: string;
|
|
3
|
+
model1: string;
|
|
4
|
+
model2: string;
|
|
5
|
+
model3: string;
|
|
6
|
+
}
|
|
7
|
+
export interface InitConfig {
|
|
8
|
+
provider1: InitProviderSlot;
|
|
9
|
+
provider2: InitProviderSlot;
|
|
10
|
+
provider3: InitProviderSlot;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* The four providers lmctl actively ships defaults for, each with three
|
|
14
|
+
* model tiers (top / mid / lighter). These are real, currently-declared
|
|
15
|
+
* models observed on a live fleet (2026-09-29) -- a useful starting point,
|
|
16
|
+
* not a claim of permanent correctness; lint warns (never errors) if one
|
|
17
|
+
* goes stale, and the whole point of `lmctl init`'s output is that it is
|
|
18
|
+
* meant to be edited.
|
|
19
|
+
*/
|
|
20
|
+
export declare const PROVIDER_MODEL_DEFAULTS: Record<string, readonly [string, string, string]>;
|
|
21
|
+
/** Providers lmctl init knows how to fill in, in fixed preference order
|
|
22
|
+
* (used only to pick 3 of 4 when every provider is present; presence
|
|
23
|
+
* detection itself never ranks). */
|
|
24
|
+
export declare const SUPPORTED_INIT_PROVIDERS: readonly ["claude", "codex", "agy", "kimi"];
|
|
25
|
+
export declare function getDefaultInitConfigPath(): string;
|
|
26
|
+
/** Fill rule (operator, 2026-09-29): 3 detected -> use all 3; 2 -> Provider3 = Provider1;
|
|
27
|
+
* 1 (or 0, using a placeholder) -> Provider2 and Provider3 = Provider1. */
|
|
28
|
+
export declare function buildInitConfig(detectedProviders: string[]): InitConfig;
|
|
29
|
+
/** Returns the subset of SUPPORTED_INIT_PROVIDERS present on this machine,
|
|
30
|
+
* in SUPPORTED_INIT_PROVIDERS order (not a usage ranking -- presence is a
|
|
31
|
+
* yes/no question, order here is just deterministic output, not a claim
|
|
32
|
+
* about which is "best"). */
|
|
33
|
+
export declare function detectPresentProviders(): string[];
|
|
34
|
+
export declare function renderInitConfig(config: InitConfig): string;
|
|
35
|
+
export declare function parseInitConfigText(raw: string): InitConfig | null;
|
|
36
|
+
/** Reads the init config, or null if it doesn't exist / doesn't parse into
|
|
37
|
+
* all three complete slots. Never throws. */
|
|
38
|
+
export declare function readInitConfig(filePath?: string): InitConfig | null;
|
|
39
|
+
export declare function writeInitConfig(config: InitConfig, filePath?: string): void;
|
|
40
|
+
/** Substitutes ${Provider1..3} and ${Model1..3} in template text from a
|
|
41
|
+
* resolved InitConfig. Unknown placeholders are left untouched (quickrun
|
|
42
|
+
* surfaces them separately so a lint failure names the real cause). */
|
|
43
|
+
export declare function substituteInitPlaceholders(text: string, config: InitConfig): string;
|
|
@@ -1,3 +1,3 @@
|
|
|
1
1
|
export declare const SEED_ACK = "ACK: waiting for explicit lmctl task.";
|
|
2
2
|
export declare const SEED_INITIAL_PROMPT: string;
|
|
3
|
-
export declare const MANUAL_SEED_POST_LAUNCH_NOTE = "send this exact lmctl seed instruction: \"lmctl seed initial connection only. Create or pin this provider session, then stop. Do not inspect files, run commands, infer a task, delegate, or edit.
|
|
3
|
+
export declare const MANUAL_SEED_POST_LAUNCH_NOTE = "send this exact lmctl seed instruction: \"lmctl seed initial connection only. Create or pin this provider session, then stop. Do not inspect files, run commands, infer a task, delegate, or edit. Reply exactly: ACK: waiting for explicit lmctl task.\"";
|
package/examples/book-team.lmctl
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lmctl-ai/lmctl",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.8",
|
|
4
4
|
"description": "A provider-agnostic control plane for teams of AI coding agents — across providers, with independent review and durable memory.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"homepage": "https://lmctl.com",
|