noema-cli 0.1.0 → 0.2.0
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/index.d.ts +46 -3
- package/dist/index.js +14 -10
- package/package.json +6 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,8 +1,15 @@
|
|
|
1
|
-
import { PromptRunner as PromptRunner$1, ProjectFileAccess, WorkflowClassifier,
|
|
1
|
+
import { WorkflowCatalog, WritingAgent, PromptRunner as PromptRunner$1, ProjectFileAccess, WorkflowClassifier, WritingAgentSessionSnapshot, WriteRequest } from '@noema/core';
|
|
2
2
|
|
|
3
3
|
type PromptRunner = PromptRunner$1["runPrompt"];
|
|
4
4
|
type AdapterRunner = (input: NodeJS.ReadableStream, output: NodeJS.WritableStream, runner: PromptRunner) => void;
|
|
5
|
-
|
|
5
|
+
type WritingAgentFactory = (params: {
|
|
6
|
+
apiKey: string;
|
|
7
|
+
model: string;
|
|
8
|
+
root: string;
|
|
9
|
+
workflowCatalog: WorkflowCatalog;
|
|
10
|
+
workflowCatalogPath: string;
|
|
11
|
+
}) => Promise<WritingAgent>;
|
|
12
|
+
declare const runMain: (runner: AdapterRunner, args?: string[], output?: NodeJS.WritableStream, agentFactory?: WritingAgentFactory) => void;
|
|
6
13
|
|
|
7
14
|
declare class NodeProjectFileAccess implements ProjectFileAccess {
|
|
8
15
|
statPath(filePath: string): Promise<"dir" | "file" | null>;
|
|
@@ -29,11 +36,47 @@ declare class SimpleWorkflowClassifier implements WorkflowClassifier {
|
|
|
29
36
|
classify(prompt: string, catalog: WorkflowCatalog): Promise<string>;
|
|
30
37
|
}
|
|
31
38
|
|
|
39
|
+
type SessionPersistenceConfig = {
|
|
40
|
+
enabled?: boolean;
|
|
41
|
+
path?: string;
|
|
42
|
+
};
|
|
43
|
+
type NoemaConfig = WorkflowCatalog & {
|
|
44
|
+
model?: string;
|
|
45
|
+
provider?: string;
|
|
46
|
+
sessionPersistence?: SessionPersistenceConfig;
|
|
47
|
+
};
|
|
32
48
|
declare const DEFAULT_CONFIG_PATH: string;
|
|
33
49
|
declare const DEFAULT_CONFIG_DISPLAY_PATH = "~/.noema/config.json";
|
|
34
50
|
declare const loadWorkflowCatalog: (configPath?: string) => Promise<WorkflowCatalog>;
|
|
51
|
+
declare const loadNoemaConfig: (configPath?: string) => Promise<NoemaConfig | null>;
|
|
35
52
|
declare const createWorkflowCatalogLoader: (configPath?: string) => (() => Promise<WorkflowCatalog>);
|
|
36
53
|
|
|
54
|
+
type SessionListEntry = {
|
|
55
|
+
id: string;
|
|
56
|
+
lastUpdated: string;
|
|
57
|
+
};
|
|
58
|
+
type SessionStore = {
|
|
59
|
+
listByRoot: (root: string, limit: number) => SessionListEntry[];
|
|
60
|
+
load: (sessionId: string) => WritingAgentSessionSnapshot | null;
|
|
61
|
+
loadForRoot: (sessionId: string, root: string) => WritingAgentSessionSnapshot | null;
|
|
62
|
+
save: (sessionId: string, snapshot: WritingAgentSessionSnapshot) => Promise<void>;
|
|
63
|
+
};
|
|
64
|
+
declare const createSessionStore: (config: NoemaConfig | null, root: string) => Promise<SessionStore | null>;
|
|
65
|
+
|
|
66
|
+
type SlashCommandContext = {
|
|
67
|
+
root: string;
|
|
68
|
+
sessionStore: SessionStore | null;
|
|
69
|
+
};
|
|
70
|
+
type SlashCommandResult = {
|
|
71
|
+
responseText: string;
|
|
72
|
+
resume?: {
|
|
73
|
+
sessionId: string;
|
|
74
|
+
snapshot: WritingAgentSessionSnapshot;
|
|
75
|
+
};
|
|
76
|
+
};
|
|
77
|
+
declare const handleSlashCommand: (prompt: string, context: SlashCommandContext) => SlashCommandResult | null;
|
|
78
|
+
declare const formatSessionList: (entries: SessionListEntry[], now: Date) => string;
|
|
79
|
+
|
|
37
80
|
declare const writeFiles: (root: string, writes: WriteRequest[]) => Promise<void>;
|
|
38
81
|
declare const resolveWritePath: (root: string, target: string) => string;
|
|
39
82
|
|
|
@@ -41,4 +84,4 @@ declare const cliVersion = "0.0.1";
|
|
|
41
84
|
|
|
42
85
|
declare const runCli: (args?: string[], output?: NodeJS.WritableStream) => void;
|
|
43
86
|
|
|
44
|
-
export { DEFAULT_CONFIG_DISPLAY_PATH, DEFAULT_CONFIG_PATH, NodeProjectFileAccess, PiAdapter, SimpleWorkflowClassifier, cliVersion, createWorkflowCatalogLoader, loadWorkflowCatalog, resolveWritePath, runCli, runMain, writeFiles };
|
|
87
|
+
export { DEFAULT_CONFIG_DISPLAY_PATH, DEFAULT_CONFIG_PATH, NodeProjectFileAccess, PiAdapter, type SessionListEntry, type SessionStore, SimpleWorkflowClassifier, type SlashCommandContext, type SlashCommandResult, type WritingAgentFactory, cliVersion, createSessionStore, createWorkflowCatalogLoader, formatSessionList, handleSlashCommand, loadNoemaConfig, loadWorkflowCatalog, resolveWritePath, runCli, runMain, writeFiles };
|
package/dist/index.js
CHANGED
|
@@ -1,22 +1,26 @@
|
|
|
1
|
-
var
|
|
2
|
-
`)},
|
|
1
|
+
import j from"path";var $=()=>({nextSessionId:1,sessions:new Map}),st=t=>{let e=`session-${t.nextSessionId}`;return t.nextSessionId+=1,e},U=(t,e)=>{t.sessions.has(e)||t.sessions.set(e,{id:e})};var D=async(t,e,s,n)=>{let r=[],o=n??(a=>r.push(a));switch(t.method){case"initialize":{let a=nt(t.params),l=Bt(a);return{responses:[{jsonrpc:"2.0",id:t.id,result:{protocolVersion:l,agentCapabilities:{loadSession:Kt(),mcpCapabilities:{http:!1,sse:!1},promptCapabilities:{audio:!1,embeddedContext:!0,image:!1},sessionCapabilities:{}},agentInfo:{name:"noema-cli",title:"Noema CLI",version:"0.0.1"},authMethods:[]}}],notifications:r}}case"session/new":{let a=st(e);return U(e,a),{responses:[{jsonrpc:"2.0",id:t.id,result:{sessionId:a}}],notifications:r}}case"session/prompt":{let a=nt(t.params),l=typeof a?.sessionId=="string"?a.sessionId:null;if(l===null)return Jt(t.id);let i=_t(s);if(i===null)return o(it(l,"Missing prompt runner")),Yt(t.id,r);U(e,l);let c=Nt(a);try{await i(l,c,u=>{o(at(l,u))})}catch(u){let f=Vt(u);return o(it(l,f)),Gt(t.id,f,r)}return{responses:[{jsonrpc:"2.0",id:t.id,result:{stopReason:"end_turn"}}],notifications:r}}default:return{responses:[{jsonrpc:"2.0",id:t.id,error:{code:-32601,message:"Method not found"}}],notifications:r}}},nt=t=>P(t)?t:null,P=t=>typeof t=="object"&&t!==null,_t=t=>typeof t=="function"?t:P(t)&&typeof t.runPrompt=="function"?t.runPrompt:null,Nt=t=>t===null?"":(Array.isArray(t.prompt)?t.prompt:[]).map(Lt).join(""),at=(t,e)=>({jsonrpc:"2.0",method:"session/update",params:{sessionId:t,update:{sessionUpdate:"agent_message_chunk",content:{type:"text",text:e}}}}),Lt=t=>{let e=It(t);return e?e.type==="text"&&typeof e.text=="string"?e.text:e.type==="resource_link"?rt($t(e)):e.type==="resource"&&P(e.resource)?rt(Ut(e.resource)):"":""},It=t=>P(t)?typeof t.type=="string"?t:P(t.content)&&typeof t.content.type=="string"?t.content:null:null,rt=t=>{let e=typeof t=="string"&&t.length>0?t:null;return e===null||!Dt(e)?"":` ${`@${zt(e)}`}`},$t=t=>{let e=lt(t.uri);if(e!==null)return e;let s=ot(t.name);return s!==null?s:ot(t.title)},Ut=t=>lt(t.uri),lt=t=>{if(typeof t!="string"||t.length===0)return null;if(!t.includes("://"))return t;try{let e=new URL(t);if(e.protocol!=="file:")return null;let s=decodeURIComponent(e.pathname),n=/^\/[A-Za-z]:\//.test(s)?s.slice(1):s;return jt(n)}catch{return null}},ot=t=>typeof t!="string"||t.length===0?null:t,jt=t=>{let e=process.cwd(),s=j.relative(e,t);return!s.startsWith("..")&&!j.isAbsolute(s)?j.normalize(s):t},Dt=t=>t.startsWith("./")||t.startsWith("../")||t.startsWith("/")||t.includes("/")?!0:/^[A-Za-z0-9][A-Za-z0-9._-]*\.[A-Za-z0-9._-]+$/.test(t),zt=t=>/\s/.test(t)?`"${t}"`:t,Bt=t=>t===null?1:typeof t.protocolVersion=="number"?t.protocolVersion:1,Kt=()=>{let t=process.env.NOEMA_SESSION_PERSISTENCE;if(t===void 0||t.length===0)return!0;let e=t.toLowerCase();return!(e==="0"||e==="false"||e==="disabled"||e==="off")},Jt=t=>({responses:[{jsonrpc:"2.0",id:t,error:{code:-32602,message:"Invalid params"}}],notifications:[]}),Yt=(t,e)=>({responses:[{jsonrpc:"2.0",id:t,error:{code:-32e3,message:"Missing prompt runner"}}],notifications:e}),Gt=(t,e,s)=>({responses:[{jsonrpc:"2.0",id:t,error:{code:-32e3,message:e}}],notifications:s}),Vt=t=>t instanceof Error?t.message:typeof t=="string"&&t.length>0?t:"Provider error",it=(t,e)=>at(t,`Error: ${e}`);import{createInterface as Ht}from"readline";var z=(t=process.stdin,e=process.stdout,s=$(),n)=>{Ht({input:t}).on("line",o=>{Zt(o,e,s,n)})},Zt=async(t,e,s,n)=>{let r=t.trim();if(r.length===0)return;let o;try{o=JSON.parse(r)}catch{R(e,{jsonrpc:"2.0",id:null,error:{code:-32700,message:"Parse error"}});return}if(!Xt(o)){ct(o)&&R(e,{jsonrpc:"2.0",id:o.id??null,error:{code:-32600,message:"Invalid Request"}});return}let{responses:a,notifications:l}=await D(o,s,n,i=>{R(e,i)});for(let i of l)R(e,i);for(let i of a)R(e,i)},R=(t,e)=>{t.write(`${JSON.stringify(e)}
|
|
2
|
+
`)},Xt=t=>!ut(t)||t.jsonrpc!=="2.0"||typeof t.method!="string"?!1:ct(t),ct=t=>ut(t)?typeof t.id=="string"||typeof t.id=="number"||t.id===null:!1,ut=t=>typeof t=="object"&&t!==null;import m from"path";var qt=`{
|
|
3
3
|
"defaultWorkflow": "draft",
|
|
4
4
|
"workflows": [
|
|
5
5
|
{ "id": "draft", "description": "Draft new content" }
|
|
6
6
|
]
|
|
7
|
-
}`,
|
|
7
|
+
}`,Qt=t=>t?`Workflow catalog is missing or invalid. Configure workflows in ${t}.
|
|
8
8
|
Example:
|
|
9
|
-
${
|
|
10
|
-
${
|
|
11
|
-
${
|
|
9
|
+
${qt}`:"Workflow catalog is missing or invalid.",F=class{deps;sessions=new Map;constructor(e){this.deps=e}setWorkflowCatalog(e){this.deps.workflowCatalog=e}async runPrompt(e,s,n){let r=this.ensureSession(e);if(!oe(this.deps.workflowCatalog))return this.respondWithText(r,s,n,Qt(this.deps.workflowCatalogPath));if(r.pending)return this.handlePending(e,r.pending,s,n);let o=await this.deps.workflowClassifier.classify(s,this.deps.workflowCatalog);return this.handlePrompt({onDelta:n,pathSelectionPrompt:s,prompt:s,sessionId:e,workflowId:o})}getLastInteraction(e){return this.sessions.get(e)??null}getSessionSnapshot(e){let s=this.sessions.get(e);if(!s)return null;let{lastPrompt:n,lastResponse:r,pending:o}=s;return o?{lastPrompt:n,lastResponse:r,pending:o}:{lastPrompt:n,lastResponse:r}}loadSessionSnapshot(e,s){this.sessions.set(e,{lastPrompt:s.lastPrompt,lastResponse:s.lastResponse,pending:s.pending})}async handlePending(e,s,n,r){switch(s.type){case"paths":return this.handlePrompt({onDelta:r,pathSelectionPrompt:n,prompt:s.prompt,sessionId:e,workflowId:s.workflowId});case"outsideRoot":return await this.isOutsideRootAllowed(n,s.outsideRootPaths,s.writeIntent.allowMissingTargets)?(this.clearPending(e),this.processParsedPaths({onDelta:r,pathReferences:s.pathReferences,prompt:s.prompt,sessionId:e,workflowId:s.workflowId,writeIntent:s.writeIntent,writePathReferences:s.writePathReferences})):this.respondWithText(this.ensureSession(e),n,r,dt(s.outsideRootPaths));case"writePath":return this.handlePrompt({onDelta:r,pathSelectionPrompt:n,prompt:s.prompt,pathReferencesOverride:s.pathReferences,sessionId:e,workflowId:s.workflowId,writeIntentOverride:s.writeIntent});case"overwrite":{if(!await this.isOverwriteAllowed(n,s.writeTargets))return this.respondWithText(this.ensureSession(e),n,r,ht(s.writeTargets));this.clearPending(e);let a={...s.writeIntent,overwrite:!0};return this.processParsedPaths({onDelta:r,pathReferences:s.pathReferences,prompt:s.prompt,sessionId:e,workflowId:s.workflowId,writeIntent:a,writePathReferences:s.pathReferences,writeTargetsOverride:s.writeTargets})}}}async handlePrompt({onDelta:e,pathSelectionPrompt:s,prompt:n,pathReferencesOverride:r,sessionId:o,workflowId:a,writeIntentOverride:l,writePathReferenceOverrides:i}){let c=l??ie(n,a),u=r===void 0?await this.parseExistingPaths(s):{outsideRootPaths:[],pathReferences:r},f=i??(c.allowMissingTargets?await this.parseAllPathCandidates(s):u.pathReferences),p=ae([u.outsideRootPaths,f]);if(p.length>0)return this.setPending(o,{outsideRootPaths:p,pathReferences:u.pathReferences,prompt:n,type:"outsideRoot",workflowId:a,writeIntent:c,writePathReferences:f}),this.respondWithText(this.ensureSession(o),n,e,dt(p));if(c.enabled){let d=await this.resolveWriteTargets(f,c.allowMissingTargets);if(d.length===0)return this.setPending(o,{pathReferences:u.pathReferences,prompt:n,type:"writePath",workflowId:a,writeIntent:c}),this.respondWithText(this.ensureSession(o),n,e,"Which file should I write to?");if(d.length>1)return this.respondWithText(this.ensureSession(o),n,e,"Please provide a single target file path.");let h=d[0];return!c.overwrite&&await this.deps.fileAccess.statPath(h)==="file"?(this.setPending(o,{pathReferences:u.pathReferences,prompt:n,type:"overwrite",workflowId:a,writeIntent:c,writeTargets:d}),this.respondWithText(this.ensureSession(o),n,e,ht(d))):this.processParsedPaths({onDelta:e,pathReferences:u.pathReferences,prompt:n,sessionId:o,workflowId:a,writeIntent:c,writePathReferences:f,writeTargetsOverride:d})}return u.pathReferences.length===0&&f.length===0?(this.setPending(o,{prompt:n,type:"paths",workflowId:a}),this.respondWithText(this.ensureSession(o),n,e,"Which files/folders should I use?")):this.processParsedPaths({onDelta:e,pathReferences:u.pathReferences,prompt:n,sessionId:o,workflowId:a,writeIntent:c,writePathReferences:f})}async processParsedPaths({onDelta:e,pathReferences:s,prompt:n,sessionId:r,workflowId:o,writeIntent:a,writePathReferences:l,writeTargetsOverride:i}){this.clearPending(r);let c=await this.expandContextPaths(s),u=await re({fileAccess:this.deps.fileAccess,options:this.deps.contextOptions,paths:c}),f=le({context:u,prompt:n,workflowId:o}),p=await this.deps.runner.runPrompt(r,f,e),d=i??(a.enabled?await this.resolveWriteTargets(l,a.allowMissingTargets):[]),h=a.enabled&&d.length===1?[{path:d[0],content:p.responseText}]:void 0;return this.recordResponse(r,n,p.responseText,h)}async parseExistingPaths(e){return M({pathExists:this.createPathExists(!1),prompt:e,root:this.deps.root})}async parseAllPathCandidates(e){let s=await M({pathExists:this.createPathExists(!0),prompt:e,root:this.deps.root});return s.pathReferences.concat(s.outsideRootPaths)}createPathExists(e){return async s=>{if(e)return!0;let n=fe(s,this.deps.root);return await this.deps.fileAccess.statPath(n)!==null}}async expandContextPaths(e){let s=[],n=new Set;for(let r of e){let o=await this.deps.fileAccess.statPath(r.path);if(o==="file"){n.has(r.path)||(n.add(r.path),s.push(r.path));continue}if(o!=="dir")continue;let a=await this.deps.fileAccess.listFiles(r.path);for(let l of a){let i=m.normalize(m.join(r.path,l));n.has(i)||(n.add(i),s.push(i))}}return s}async resolveWriteTargets(e,s){let n=[];for(let r of e){let o=await this.deps.fileAccess.statPath(r.path);if(o==="file"){n.push(r.path);continue}o===null&&s&&n.push(r.path)}return n}respondWithText(e,s,n,r){return n(r),e.lastPrompt=s,e.lastResponse=r,{responseText:r}}recordResponse(e,s,n,r){let o=this.ensureSession(e);return o.lastPrompt=s,o.lastResponse=n,r?{responseText:n,writes:r}:{responseText:n}}ensureSession(e){let s=this.sessions.get(e);if(s)return s;let n={lastPrompt:"",lastResponse:""};return this.sessions.set(e,n),n}setPending(e,s){let n=this.ensureSession(e);n.pending=s}clearPending(e){let s=this.ensureSession(e);delete s.pending}async isOutsideRootAllowed(e,s,n){if(!ce(e))return!1;let o=(await M({pathExists:this.createPathExists(n),prompt:e,root:this.deps.root})).outsideRootPaths.map(a=>a.absolutePath.toLowerCase());return s.every(a=>o.includes(a.absolutePath.toLowerCase()))}async isOverwriteAllowed(e,s){if(!ue(e))return!1;let n=await M({pathExists:this.createPathExists(!0),prompt:e,root:this.deps.root}),r=n.pathReferences.concat(n.outsideRootPaths).map(o=>o.path.toLowerCase());return s.every(o=>r.includes(o.toLowerCase()))}},te=1e6,ee=2*1024*1024,ft="[[SKIPPED: unreadable]]",se="[[SKIPPED: file exceeds size limit]]",pt="[[SKIPPED: context size limit reached]]",ne="[[TRUNCATED: context size limit reached]]",M=async({pathExists:t,prompt:e,root:s})=>{let n=pe(e),r=new Map,o=new Map;for(let a of n){let l=de(a);if(l===null||!me(l.value))continue;let i=m.resolve(s,l.value);if(!await t(i))continue;let c=m.relative(s,i),u=c.startsWith("..")||m.isAbsolute(c),f=u?i:m.normalize(c),p={absolutePath:i,isOutsideRoot:u,path:f,source:l.source};if(u){o.has(i)||o.set(i,p);continue}r.has(i)||r.set(i,p)}return{outsideRootPaths:Array.from(o.values()),pathReferences:Array.from(r.values())}},re=async({fileAccess:t,paths:e,options:s})=>{let n=s?.maxTotalBytes??te,r=s?.maxFileBytes??ee,o=[],a=[],l=0,i=!1;for(let c of e){let u=await t.fileSize(c);if(u===null){o.push({path:c,content:ft}),a.push(c);continue}if(u>r){o.push({path:c,content:se}),a.push(c);continue}if(l>=n){o.push({path:c,content:pt}),a.push(c),i=!0;continue}let f=await t.readTextFile(c);if(f===null){o.push({path:c,content:ft}),a.push(c);continue}let p=Buffer.byteLength(f);if(l+p<=n){o.push({path:c,content:f}),l+=p;continue}let d=n-l;if(d<=0){o.push({path:c,content:pt}),a.push(c),i=!0;continue}let h=ge(f,d);o.push({path:c,content:`${h}
|
|
10
|
+
${ne}`}),l=n,i=!0}return{entries:o,skippedPaths:a,totalBytes:l,truncated:i}},oe=t=>!t||typeof t.defaultWorkflow!="string"||t.defaultWorkflow.length===0||!Array.isArray(t.workflows)||t.workflows.length===0?!1:t.workflows.some(e=>e.id===t.defaultWorkflow),ie=(t,e)=>{let s=/\b(write|save|overwrite|replace|update|create)\b/i.test(t),n=/\b(overwrite|replace)\b/i.test(t);return{allowMissingTargets:s,enabled:s||e==="rewrite",overwrite:n}},ae=t=>{let e=new Map;for(let s of t)for(let n of s)n.isOutsideRoot&&(e.has(n.absolutePath)||e.set(n.absolutePath,n));return Array.from(e.values())},le=({context:t,prompt:e,workflowId:s})=>{let n=t.entries.length===0?"(none)":t.entries.map(r=>`[[FILE: ${r.path}]]
|
|
11
|
+
${r.content}`.trimEnd()).join(`
|
|
12
12
|
|
|
13
13
|
`);return`Workflow: ${s}
|
|
14
14
|
|
|
15
15
|
Context:
|
|
16
|
-
${
|
|
16
|
+
${n}
|
|
17
17
|
|
|
18
18
|
User:
|
|
19
|
-
${
|
|
20
|
-
`);c
|
|
21
|
-
|
|
19
|
+
${e}`},dt=t=>{let e=gt(t.map(s=>s.path));return`You requested paths outside the project root: ${e}. Reply with "allow ${e}" to proceed.`},ht=t=>{let e=gt(t);return`The following files already exist: ${e}. Reply with "overwrite ${e}" to proceed.`},ce=t=>/\b(allow|yes)\b/i.test(t),ue=t=>/\b(overwrite|yes)\b/i.test(t),gt=t=>t.map(e=>`"${e}"`).join(", "),fe=(t,e)=>{let s=m.relative(e,t);return!s.startsWith("..")&&!m.isAbsolute(s)?m.normalize(s):t},pe=t=>t.match(/@"[^"]*"|@'[^']*'|@`[^`]*`|"[^"]*"|'[^']*'|`[^`]*`|\S+/g)??[],de=t=>{if(t.length===0||t.includes("://"))return null;let e=he(t);if(e!==null)return e;let s=mt(t);if(s.length===0||s.includes("://"))return null;if(s.startsWith("@")){let n=mt(s.slice(1));return n.length===0?null:{source:"@",value:n}}return{source:"plain",value:s}},he=t=>{let e="plain",s=t;if(s.startsWith("@")&&(e="@",s=s.slice(1)),s.length<2)return null;let n=s[0];if((n==='"'||n==="'"||n==="`")&&s.endsWith(n)){let r=s.slice(1,-1);return r.length===0||r.includes("://")?null:{source:e,value:r}}return null},mt=t=>t.replace(/^[\s"'([{<]+/,"").replace(/[\s"')\]}>.,;:!?]+$/,""),me=t=>t.startsWith("./")||t.startsWith("../")||t.startsWith("/")||t.includes("/")?!0:/^[A-Za-z0-9][A-Za-z0-9._-]*\.[A-Za-z0-9._-]+$/.test(t),ge=(t,e)=>{let s=Buffer.from(t,"utf8");return s.length<=e?t:s.subarray(0,e).toString("utf8")};import{readFileSync as ds,promises as q}from"fs";import hs from"os";import Ft from"path";import{promises as v}from"fs";import B from"path";import{TextDecoder as we}from"util";var yt=2*1024*1024,wt=6,K=8*1024,ye=new Set([".git","node_modules"]),x=class{async statPath(e){let s=await O(e);return s===null||s.isSymbolicLink()?null:s.isFile()?"file":s.isDirectory()?"dir":null}async listFiles(e){let s=[];return await this.walk(e,e,0,s),s}async readTextFile(e){let s=await O(e);if(s===null||!s.isFile()||s.isSymbolicLink()||s.size>yt)return null;let n=await Re(e);return n===null||St(n.subarray(0,K))?null:Pt(n)}async fileSize(e){let s=await O(e);return s===null||!s.isFile()||s.isSymbolicLink()?null:s.size}async walk(e,s,n,r){if(n>wt)return;let o=await xe(s);if(o!==null){o.sort((a,l)=>a.name.localeCompare(l.name));for(let a of o){if(ye.has(a.name))continue;let l=B.join(s,a.name);if(a.isSymbolicLink())continue;if(a.isDirectory()){if(n>=wt)continue;await this.walk(e,l,n+1,r);continue}if(!a.isFile()||!await Se(l))continue;let i=B.relative(e,l);i.length!==0&&r.push(B.normalize(i))}}}},Se=async t=>{let e=await O(t);if(e===null||!e.isFile()||e.isSymbolicLink()||e.size>yt)return!1;let s=await Pe(t);return s===null?!1:!St(s)},St=t=>t.includes(0)?!0:Pt(t)===null,Pt=t=>{try{return new we("utf-8",{fatal:!0}).decode(t)}catch{return null}},Pe=async t=>{let e=await Ce(t);if(e===null)return null;try{let s=Buffer.alloc(K),{bytesRead:n}=await e.read(s,0,K,0);return s.subarray(0,n)}catch{return null}finally{await e.close()}},Re=async t=>{try{return await v.readFile(t)}catch{return null}},xe=async t=>{try{return await v.readdir(t,{withFileTypes:!0})}catch{return null}},O=async t=>{try{return await v.lstat(t)}catch{return null}},Ce=async t=>{try{return await v.open(t,"r")}catch{return null}};import{Agent as We}from"@mariozechner/pi-agent-core";import{getModel as be}from"@mariozechner/pi-ai";import{AssistantMessageEventStream as Te}from"@mariozechner/pi-ai/dist/utils/event-stream.js";var N=class{config;baseUrl;constructor(e){this.config=e,this.baseUrl=e.baseUrl??"https://openrouter.ai/api/v1"}async*streamPrompt(e){let s=[{role:"user",content:e}];yield*this.streamChat({messages:s})}async*streamChat({messages:e,model:s,signal:n}){let r=await fetch(`${this.baseUrl}/chat/completions`,{method:"POST",headers:{Authorization:`Bearer ${this.config.apiKey}`,"Content-Type":"application/json"},body:JSON.stringify({model:s??this.config.model,messages:e,stream:!0}),signal:n});if(!r.ok)throw new Error(`OpenRouter error: ${r.status} ${r.statusText}`);if(r.body===null){let i=await r.json(),c=ke(i);c.length>0&&(yield c);return}let o=r.body.getReader(),a=new TextDecoder,l="";for(;;){let i=await o.read();if(i.done)break;l+=a.decode(i.value,{stream:!0});let c=l.split(`
|
|
20
|
+
`);l=c.pop()??"";for(let u of c){let f=Ae(u);f!==null&&(yield f)}}}},Ae=t=>{let e=t.trim();if(e.length===0||!e.startsWith("data:"))return null;let s=e.slice(5).trim();if(s==="[DONE]")return null;try{let n=JSON.parse(s),r=n?.choices?.[0]?.delta?.content??n?.choices?.[0]?.message?.content;return typeof r=="string"&&r.length>0?r:null}catch{return null}},ke=t=>{let e=Ee(t),n=(Array.isArray(e?.choices)?e.choices:[])[0];if(!_(n))return"";let r=_(n.message)?n.message:null,o=_(n.delta)?n.delta:null,a=r?.content??o?.content;return typeof a=="string"?a:""},Ee=t=>_(t)?t:null,_=t=>typeof t=="object"&&t!==null;var C=class{constructor(e,s){this.apiKey=e;this.model=s;this.streamFn=Fe(this.apiKey),this.modelDefinition=Me(this.model)}agents=new Map;streamFn;modelDefinition;async runPrompt(e,s,n){let r=this.getAgent(e),o="",a="",l=r.subscribe(i=>{i.type==="message_update"&&i.assistantMessageEvent.type==="text_delta"&&(o+=i.assistantMessageEvent.delta,n(i.assistantMessageEvent.delta)),i.type==="message_end"&&i.message.role==="assistant"&&(a=xt(i.message.content))});return await r.prompt(s),l(),o.length===0&&a.length>0&&(o=a,n(o)),{responseText:o}}getAgent(e){let s=this.agents.get(e);return s||(s=new We({initialState:{model:this.modelDefinition},sessionId:e,streamFn:this.streamFn}),this.agents.set(e,s)),s.sessionId=e,s.setModel(this.modelDefinition),s}},Me=t=>{let e=be("openrouter",t);if(!e)throw new Error(`Unknown OpenRouter model: ${t}`);return e},Fe=t=>(s,n,r)=>{let o=new Te;return Oe({apiKey:r?.apiKey??t,context:n,model:s,signal:r?.signal,stream:o}),o},Oe=async({apiKey:t,context:e,model:s,signal:n,stream:r})=>{let o=ve(e),a=new N({apiKey:t,baseUrl:s.baseUrl,model:s.id}),l=Rt(s),i="",c=!1;try{r.push({type:"start",partial:l});for await(let u of a.streamChat({messages:o,model:s.id,signal:n}))c||(l.content.push({type:"text",text:""}),r.push({type:"text_start",contentIndex:0,partial:l}),c=!0),i+=u,l.content[0].text=i,r.push({type:"text_delta",contentIndex:0,delta:u,partial:l});c&&r.push({type:"text_end",contentIndex:0,content:i,partial:l}),r.push({type:"done",reason:"stop",message:l})}catch(u){let f=u instanceof Error?u.message:String(u),p=Rt(s,{content:[{type:"text",text:f}],errorMessage:f,stopReason:"error"});r.push({type:"error",reason:"error",error:p})}},ve=t=>{let e=[];t.systemPrompt&&t.systemPrompt.trim().length>0&&e.push({role:"system",content:t.systemPrompt});for(let s of t.messages){let n=xt(s.content);if(s.role==="toolResult"){if(n.length===0)continue;e.push({role:"tool",content:n,tool_call_id:s.toolCallId});continue}n.length!==0&&e.push({role:s.role,content:n})}return e},xt=t=>typeof t=="string"?t:Array.isArray(t)?t.map(e=>e.type==="text"?e.text:"").join(""):"",Rt=(t,e)=>({role:"assistant",content:[],api:t.api,provider:t.provider,model:t.id,usage:{input:0,output:0,cacheRead:0,cacheWrite:0,totalTokens:0,cost:{input:0,output:0,cacheRead:0,cacheWrite:0,total:0}},stopReason:"stop",timestamp:Date.now(),...e});var A=class{async classify(e,s){let n=e.toLowerCase();for(let r of s.workflows)if(_e(n,r))return r.id;return s.defaultWorkflow}},_e=(t,e)=>{let s=e.id.trim().toLowerCase();if(s.length>0&&Ne(t,s))return!0;if(e.description){let n=e.description.toLowerCase().trim();if(n.length>0&&t.includes(n))return!0}return!1},Ne=(t,e)=>{let s=Le(e);return new RegExp(`\\b${s}\\b`,"i").test(t)},Le=t=>t.replace(/[.*+?^${}()|[\\]\\]/g,"\\$&");import{promises as k}from"fs";import Ie from"os";import Ct from"path";var At={defaultWorkflow:"draft",workflows:[{id:"draft",description:"Draft new content"}]},$e={defaultWorkflow:"",workflows:[]},E=()=>Ct.join(Ie.homedir(),".noema","config.json"),Ue=E(),J="~/.noema/config.json",kt=async(t=E())=>{let e=await Y(t);return e===null?$e:{defaultWorkflow:e.defaultWorkflow,workflows:e.workflows}},Y=async(t=E())=>{try{let e=await k.readFile(t,"utf8"),s=JSON.parse(e);return Je(s)}catch(e){return De(e)?(await je(t),At):null}},je=async t=>{let e=Ct.dirname(t);await k.mkdir(e,{recursive:!0});let s=`${JSON.stringify(At,null,2)}
|
|
21
|
+
`;await k.writeFile(t,s,"utf8")},De=t=>typeof t=="object"&&t!==null&&"code"in t&&t.code==="ENOENT",ze=(t=E())=>{let e=null,s=null;return async()=>{let n=await k.stat(t).catch(()=>null),r=n?n.mtimeMs:null;return(e===null||s!==r)&&(e=await kt(t),s=r),e}},Et=(t=E())=>{let e=null,s=null;return async()=>{let n=await k.stat(t).catch(()=>null),r=n?n.mtimeMs:null;return(e===null||s!==r)&&(e=await Y(t),s=r),e}},Be=t=>{if(!t||typeof t!="object")return!1;let e=t;return typeof e.defaultWorkflow!="string"||!Array.isArray(e.workflows)?!1:e.workflows.every(s=>Ke(s))},Ke=t=>{if(!t||typeof t!="object")return!1;let e=t;return!(typeof e.id!="string"||e.id.length===0||e.description!==void 0&&typeof e.description!="string")},Je=t=>{if(!Be(t))return null;let e=t,s={defaultWorkflow:e.defaultWorkflow,workflows:e.workflows},n=typeof e.provider=="string"?e.provider.trim():"";n.length>0&&(s.provider=n);let r=typeof e.model=="string"?e.model.trim():"";r.length>0&&(s.model=r);let o=Ye(e.sessionPersistence);return o&&(s.sessionPersistence=o),s},Ye=t=>{if(!Ge(t))return null;let e=t,s=typeof e.enabled=="boolean"?e.enabled:void 0,n=typeof e.path=="string"&&e.path.trim().length>0?e.path.trim():void 0;if(s===void 0&&n===void 0)return null;let r={};return s!==void 0&&(r.enabled=s),n!==void 0&&(r.path=n),r},Ge=t=>typeof t=="object"&&t!==null;import{promises as L}from"fs";import Ve from"os";import y from"path";var He="sessions.json",V=async(t,e)=>{if(!(ls()??t?.sessionPersistence?.enabled??!0))return null;let r=Xe(t?.sessionPersistence?.path),o=await Ze(e),l=(await qe(r)).sessions;return{listByRoot:(i,c)=>Object.entries(l).filter(([u,f])=>f.root===i&&f.lastUpdated.length>0).sort((u,f)=>f[1].lastUpdated.localeCompare(u[1].lastUpdated)).slice(0,c).map(([u,f])=>({id:u,lastUpdated:f.lastUpdated})),load:i=>l[i]?.snapshot??null,loadForRoot:(i,c)=>{let u=l[i];return!u||u.root!==c||u.lastUpdated.length===0?null:u.snapshot},save:async(i,c)=>{l[i]={lastUpdated:new Date().toISOString(),root:o,snapshot:c},await Qe(r,{version:1,sessions:l})}}},Ze=async t=>{try{return await L.realpath(t)}catch{return t}},Xe=t=>{let e=y.join(Ve.homedir(),".noema"),s=y.join(e,He);if(!t||t.length===0)return s;let n=y.isAbsolute(t)?y.resolve(t):y.resolve(e,t);return n.startsWith(`${e}${y.sep}`)?n:s},qe=async t=>{try{let e=await L.readFile(t,"utf8"),s=JSON.parse(e);return ts(s)?ns(s):{version:1,sessions:{}}}catch{return{version:1,sessions:{}}}},Qe=async(t,e)=>{await L.mkdir(y.dirname(t),{recursive:!0});let s=`${JSON.stringify(e,null,2)}
|
|
22
|
+
`;await L.writeFile(t,s,"utf8")},ts=t=>!(!g(t)||t.version!==1||!g(t.sessions)),g=t=>typeof t=="object"&&t!==null,es=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/,ss=t=>{if(!es.test(t))return!1;try{return new Date(t).toISOString()===t}catch{return!1}},ns=t=>{let e={};for(let[s,n]of Object.entries(t.sessions)){let r=rs(n);r&&(e[s]=r)}return{version:1,sessions:e}},rs=t=>{if(!g(t))return null;if(typeof t.root=="string"&&typeof t.lastUpdated=="string"&&g(t.snapshot)){if(!ss(t.lastUpdated))return null;let s=Wt(t.snapshot);return s?{lastUpdated:t.lastUpdated,root:t.root,snapshot:s}:null}let e=Wt(t);return e?{lastUpdated:"",root:"",snapshot:e}:null},Wt=t=>{if(!g(t)||typeof t.lastPrompt!="string"||typeof t.lastResponse!="string")return null;let e={lastPrompt:t.lastPrompt,lastResponse:t.lastResponse};return t.pending===void 0||!os(t.pending)?e:{...e,pending:t.pending}},os=t=>{if(!g(t)||typeof t.type!="string"||typeof t.prompt!="string"||typeof t.workflowId!="string")return!1;switch(t.type){case"paths":return!0;case"outsideRoot":return W(t.outsideRootPaths)&&W(t.pathReferences)&&W(t.writePathReferences)&&G(t.writeIntent);case"writePath":return W(t.pathReferences)&&G(t.writeIntent);case"overwrite":return W(t.pathReferences)&&G(t.writeIntent)&&as(t.writeTargets);default:return!1}},W=t=>Array.isArray(t)?t.every(is):!1,is=t=>!(!g(t)||typeof t.absolutePath!="string"||typeof t.path!="string"||typeof t.isOutsideRoot!="boolean"||t.source!=="plain"&&t.source!=="@"),G=t=>g(t)?typeof t.allowMissingTargets=="boolean"&&typeof t.enabled=="boolean"&&typeof t.overwrite=="boolean":!1,as=t=>Array.isArray(t)?t.every(e=>typeof e=="string"):!1,ls=()=>{let t=process.env.NOEMA_SESSION_PERSISTENCE;if(t===void 0||t.length===0)return null;let e=t.toLowerCase();return!(e==="0"||e==="false"||e==="disabled"||e==="off")};var H=(t,e)=>{let s=t.trim();if(s==="/sessions")return cs(e);if(s==="/resume"||s.startsWith("/resume ")){let n=s.slice(7).trim();return us(e,n)}return null},cs=t=>{if(t.sessionStore===null)return{responseText:"Session persistence is disabled."};let e=t.sessionStore.listByRoot(t.root,20);return e.length===0?{responseText:"No sessions found for current worktree."}:{responseText:bt(e,new Date)}},us=(t,e)=>{if(t.sessionStore===null)return{responseText:"Session persistence is disabled."};if(e.length===0)return{responseText:"Usage: /resume <id>"};let s=t.sessionStore.loadForRoot(e,t.root);return s===null?{responseText:`Session "${e}" not found. Run /sessions to see available sessions.`}:{responseText:`Session "${e}" restored.`,resume:{sessionId:e,snapshot:s}}},bt=(t,e)=>t.filter(s=>!Number.isNaN(new Date(s.lastUpdated).getTime())).map(s=>{let n=new Date(s.lastUpdated),r=fs(n),o=ps(n,e);return`${s.id} \u2014 ${r} (${o})`}).join(`
|
|
23
|
+
`),fs=t=>{let e=t.getFullYear(),s=String(t.getMonth()+1).padStart(2,"0"),n=String(t.getDate()).padStart(2,"0"),r=String(t.getHours()).padStart(2,"0"),o=String(t.getMinutes()).padStart(2,"0"),a=String(t.getSeconds()).padStart(2,"0");return`${e}-${s}-${n} ${r}:${o}:${a}`},ps=(t,e)=>{let s=e.getTime()-t.getTime();if(s<0)return"just now";let n=Math.floor(s/1e3);if(n<60)return"just now";let r=Math.floor(n/60);if(r<60)return r===1?"1 minute ago":`${r} minutes ago`;let o=Math.floor(r/60);if(o<24)return o===1?"1 hour ago":`${o} hours ago`;let a=Math.floor(o/24);if(a<30)return a===1?"1 day ago":`${a} days ago`;let l=Math.floor(a/30);if(l<12)return l===1?"1 month ago":`${l} months ago`;let i=Math.floor(a/365);return i===1?"1 year ago":`${i} years ago`};import{promises as Tt}from"fs";import Z from"path";var X=async(t,e)=>{for(let s of e){let n=Mt(t,s.path);await Tt.mkdir(Z.dirname(n),{recursive:!0}),await Tt.writeFile(n,s.content,"utf8")}},Mt=(t,e)=>Z.isAbsolute(e)?e:Z.join(t,e);var Ot=(t,e=process.argv.slice(2),s=process.stdout,n)=>{if(e.includes("--version")||e.includes("-v")){s.write(`${ws()}
|
|
24
|
+
`);return}t(process.stdin,s,ms(n))},ms=t=>{let e=null,s=null,n=new Set,r=new Map,o=process.cwd(),a=Et(),l=q.realpath(o).catch(()=>o);return async(i,c,u)=>{try{let f=await a();s===null&&(s=V(f,o));let p=await s,d=await l,h=H(c,{sessionStore:p,root:d});if(h)return h.resume&&p&&(r.set(i,h.resume.snapshot),n.delete(i),await p.save(h.resume.sessionId,h.resume.snapshot)),u(h.responseText),{responseText:h.responseText};let b=process.env.OPENROUTER_API_KEY;if(b===void 0||b.length===0)throw new Error("OPENROUTER_API_KEY is required");let vt=f?.model,I=process.env.OPENROUTER_MODEL??vt;if(I===void 0||I.length===0)throw new Error("OPENROUTER_MODEL is required");let Q=f?{defaultWorkflow:f.defaultWorkflow,workflows:f.workflows}:{defaultWorkflow:"",workflows:[]};e===null&&(e=(t??gs)({apiKey:b,model:I,root:o,workflowCatalog:Q,workflowCatalogPath:J}));let S=await e,tt=r.get(i);if(tt)S.loadSessionSnapshot(i,tt),n.add(i),r.delete(i);else if(p&&!n.has(i)){let w=p.load(i);w&&S.loadSessionSnapshot(i,w),n.add(i)}S.setWorkflowCatalog(Q);let et=async()=>{if(!p)return;let w=S.getSessionSnapshot(i);w&&await p.save(i,w)},T;try{T=await S.runPrompt(i,c,u),T.writes&&await X(o,T.writes)}catch(w){try{await et()}catch{}throw w}return await et(),T}catch(f){throw await ys(f),f}}},gs=async({apiKey:t,model:e,root:s,workflowCatalog:n,workflowCatalogPath:r})=>{let o=new x,a=new A,l=new C(t,e);return new F({fileAccess:o,root:s,runner:l,workflowCatalog:n,workflowCatalogPath:r,workflowClassifier:a})},ws=()=>{try{let t=new URL("../package.json",import.meta.url),e=ds(t,"utf8"),s=JSON.parse(e);return typeof s?.version=="string"?s.version:"unknown"}catch{return"unknown"}},ys=async t=>{let e=t instanceof Error?t.stack??t.message:String(t),s=`[${new Date().toISOString()}] ${e}
|
|
25
|
+
`,n=Ft.join(hs.homedir(),".noema","error.log");try{await q.mkdir(Ft.dirname(n),{recursive:!0}),await q.appendFile(n,s)}catch{}};var Sn="0.0.1";var Ss=(t=process.argv.slice(2),e=process.stdout)=>{Ot((s,n,r)=>{z(s,n,void 0,r)},t,e)};import.meta.url===`file://${process.argv[1]}`&&Ss();export{J as DEFAULT_CONFIG_DISPLAY_PATH,Ue as DEFAULT_CONFIG_PATH,x as NodeProjectFileAccess,C as PiAdapter,A as SimpleWorkflowClassifier,Sn as cliVersion,V as createSessionStore,ze as createWorkflowCatalogLoader,bt as formatSessionList,H as handleSlashCommand,Y as loadNoemaConfig,kt as loadWorkflowCatalog,Mt as resolveWritePath,Ss as runCli,Ot as runMain,X as writeFiles};
|
|
22
26
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "noema-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"license": "UNLICENSED",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -11,5 +11,9 @@
|
|
|
11
11
|
"files": [
|
|
12
12
|
"bin",
|
|
13
13
|
"dist"
|
|
14
|
-
]
|
|
14
|
+
],
|
|
15
|
+
"dependencies": {
|
|
16
|
+
"@mariozechner/pi-agent-core": "^0.51.2",
|
|
17
|
+
"@mariozechner/pi-ai": "^0.51.2"
|
|
18
|
+
}
|
|
15
19
|
}
|