@north-light/cli 0.3.584 → 0.3.586

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.
Files changed (49) hide show
  1. package/dist/api/client.d.ts +3 -1
  2. package/dist/api/client.js +3 -3
  3. package/dist/api/contract.d.ts +1 -1
  4. package/dist/api/contract.js +1 -1
  5. package/dist/api/dto/bash-jobs.d.ts +17 -0
  6. package/dist/api/dto/bash-jobs.js +1 -1
  7. package/dist/api/routes.d.ts +1 -0
  8. package/dist/api/routes.js +1 -1
  9. package/dist/builtin-memory/crouter-sdk/release-notes.md +4 -0
  10. package/dist/clients/attach/command.js +1 -1
  11. package/dist/clients/attach/input/capabilities.d.ts +4 -4
  12. package/dist/clients/attach/input/capabilities.js +1 -1
  13. package/dist/clients/attach/session/connection.d.ts +7 -8
  14. package/dist/clients/attach/session/connection.js +1 -1
  15. package/dist/clients/attach/session/context.d.ts +2 -2
  16. package/dist/clients/attach/session/reconnect.d.ts +2 -0
  17. package/dist/clients/attach/session/reconnect.js +1 -1
  18. package/dist/clients/attach/viewer.js +500 -500
  19. package/dist/commands/node/bash.js +4 -3
  20. package/dist/commands/sys/desktop.js +1 -1
  21. package/dist/core/broker-client/index.d.ts +1 -0
  22. package/dist/core/broker-client/index.js +1 -1
  23. package/dist/core/broker-client/transport-relay.js +1 -1
  24. package/dist/core/broker-client/transport.d.ts +11 -1
  25. package/dist/core/broker-client/transport.js +1 -1
  26. package/dist/core/canvas/export-sqlite.js +2 -2
  27. package/dist/core/canvas/schema-pg/010-package-publications.sql +16 -0
  28. package/dist/core/canvas/schema-version.d.ts +1 -1
  29. package/dist/core/canvas/schema-version.js +1 -1
  30. package/dist/core/events/read.js +1 -1
  31. package/dist/core/graph/kinds/job.js +2 -2
  32. package/dist/core/graph/package-docs.d.ts +11 -0
  33. package/dist/core/graph/package-docs.js +6 -6
  34. package/dist/core/graph/package-publish.d.ts +30 -0
  35. package/dist/core/graph/package-publish.js +3 -0
  36. package/dist/core/log.d.ts +2 -0
  37. package/dist/core/log.js +5 -5
  38. package/dist/core/providers/bundles.js +2 -2
  39. package/dist/core/providers/call.js +1 -1
  40. package/dist/core/providers/device.js +1 -1
  41. package/dist/daemon/api/handlers/bash-jobs.js +3 -1
  42. package/dist/daemon/api/handlers/package-docs.js +1 -1
  43. package/dist/daemon/api/operations.js +1 -1
  44. package/dist/daemon/crtrd.js +18 -17
  45. package/dist/shared/inbox-entry-body.js +7 -7
  46. package/docs/sdk/release-notes.md +4 -0
  47. package/package.json +2 -2
  48. package/packages/crouter-identity/package.json +1 -1
  49. package/runtime.lock.json +13 -13
@@ -1,5 +1,6 @@
1
- var $=Object.defineProperty;var s=(e,t)=>$(e,"name",{value:t,configurable:!0});import{envNodeId as _}from"../../shared/env.js";import{defineBranch as T,defineLeaf as c}from"../../core/command.js";import{InputError as i}from"../../core/io.js";import{ExitCode as q}from"../../types.js";import{existsSync as R,mkdirSync as E,rmSync as I,writeFileSync as f}from"node:fs";import{backgroundRunningBashJobs as D,bashJobCommandLine as N,bashJobPaths as y,formatBashElapsed as k,isSafeBashJobId as w,jobStartedAtMs as B,newBashJobId as L,tailJobLog as S}from"../../core/bash-jobs.js";import{parseDurationMs as M}from"../../core/wake.js";import{cliClient as g,getNodeOrNull as h}from"../api-client.js";import{ApiError as U}from"../../api/index.js";import{PREVIEW_RESULT_PATH_ENV as J}from"../../core/preview-result-path.js";import{nodeInPane as C}from"../surface/node/placement.js";function u(e,t){const n=e.pane??process.env.TMUX_PANE;let o=e.node;if((o===void 0||o==="")&&(o=C(n)),(o===void 0||o==="")&&(o=_()),o===void 0||o==="")throw new i({error:"no_node",message:t,next:"Run from inside a node, pass --node <id>, or --pane <pane>."});return{id:o,pane:n}}s(u,"resolveBashTarget");async function j(e,t,n){try{await g().patchBashJob(e,t,n)}catch(o){throw o instanceof U&&(o.status===404||o.status===409)?new i({error:"not_running",message:`background job ${t} for ${e} is not running`,next:"List live jobs with `crtr node bash list`."}):o}}s(j,"patchRunningJob");const A=c({name:"run",description:"start a shell command as a background job and return at once",whenToUse:"a command needs longer than one tool call \u2014 a test lane, a build, a soak \u2014 and you should be woken with its exit code instead of waiting, backgrounding with `&`, or polling a log",tier:"normal",help:{name:"node bash run",summary:"start one shell command as a background job owned by the calling node and return before it finishes",params:[{kind:"positional",name:"command",required:!0,constraint:"The shell command, run by bash from the current directory with this process\u2019s environment. The same refusals apply as for a bash tool call, judged on this command."},{kind:"flag",name:"quiet-ok",type:"bool",required:!1,constraint:"Pass for a job that legitimately sits idle \u2014 `gh run watch`, `sleep`, waiting on a lock, a `docker build` whose work runs outside the process tree \u2014 so it gets no silence messages. Omit it for anything that should be making progress."}],output:[{name:"job_id",type:"string",required:!0,constraint:"The job object\u2019s id; read or watch it with `crtr canvas`, stop it with `node bash kill`."},{name:"log_path",type:"string",required:!0,constraint:"File receiving the command\u2019s combined stdout and stderr, capped at 10 MiB."}],outputKind:"object",effects:["Works only inside a node\u2019s bash tool call; the job belongs to the calling node.","Starts the command detached from this call, so it keeps running after the call and the turn end. A `timeout` around this leaf bounds only the launch, not the job.","Records the job as a canvas object; when the command exits, its watchers (the calling node by default) get an urgent `exited` push with the exit code and log path.","Sets no cancel deadline: the job runs to completion unless stopped with `node bash kill`.","A job whose processes use almost no CPU and write to no open file for 15 minutes messages the calling node about it, again at 30 and 60 minutes, then hourly; activity restarts the schedule and nothing is stopped. `--quiet-ok` turns these messages off.","Fails, with the job id and log path, if the job could not be recorded; the command is then running but will not message the node when it exits."]},run:s(async e=>{const t=e.command,n=_(),o=process.env.CRTR_CONTEXT_DIR;if(n===void 0||n===""||o===void 0||o==="")throw new i({error:"no_node",message:"`node bash run` starts a job for the calling node and works only inside a node\u2019s bash tool",next:"Run it from a node\u2019s bash tool call."});const r=process.cwd(),{bashCommandRefusal:l}=await import("../../core/bash-guard.js"),d=l(t,r);if(d!==null)throw new i({error:"refused",message:d,next:"Run a different command."});const a=y(o,L());E(a.dir,{recursive:!0}),f(a.cmdSh,t),f(a.jobLog,""),f(a.jobBg,"");const{launchBashJobSupervisor:m}=await import("../../core/runtime/broker/bash-job-launch.js"),{[J]:b,...v}=process.env;try{await m(n,a,o,r,v)}catch(p){throw I(a.dir,{recursive:!0,force:!0}),p}const{recordBackgroundedJob:x}=await import("../../core/runtime/broker/daemon-ops.js");try{await x(n,a.jobId,{command:t}),e.quietOk===!0&&await g().patchBashJob(n,a.jobId,{quiet_ok:!0})}catch(p){throw new i({error:"job_not_recorded",message:`job ${a.jobId} is running but could not be recorded (${p.message}); it will NOT message you when it exits. Log: ${a.jobLog}`,next:`Watch it with \`tail -f ${a.jobLog}\`, or stop it with \`crtr node bash kill ${a.jobId}\`.`},q.GENERAL)}return{job_id:a.jobId,log_path:a.jobLog}},"run"),render:s(e=>`[backgrounded as job ${e.job_id}. It will interrupt you with a message when it exits. Watch: tail -f ${e.log_path} ; stop: crtr node bash kill ${e.job_id}]`,"render")}),P=c({name:"background",description:"hand the focused node\u2019s running bash command to its background job system",whenToUse:"the tmux prefix menu invokes this when a foreground bash command should stop blocking the agent immediately but keep running as a file-backed background job",tier:"hidden",help:{name:"node bash background",summary:"hand every running foreground bash command for one node to its file-backed background job system",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node whose running bash command to background. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane showing the target node, when --node is omitted. Defaults to $TMUX_PANE. The prefix menu passes this for you."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The target node."},{name:"jobs",type:"object[]",required:!0,constraint:"Per handed-off job {job_id, elapsed_ms} \u2014 the foreground elapsed time in milliseconds at the moment of handoff."}],outputKind:"object",effects:["Signals every foreground bash command currently running for the node to return from its tool call immediately while its detached supervisor keeps the command running.","Each handed-off command retains its log under the node context jobs directory and becomes a job object; when it exits, the job\u2019s watchers (its node by default) get an urgent `exited` push carrying the exit code and log path."]},run:s(async e=>{const{id:t}=u(e,"no node found for the bash handoff"),n=await h(t);if(n===null)throw new i({error:"not_found",message:`no node: ${t}`,next:"List nodes with `crtr node inspect list`."});const o=D(n.paths.context_dir),r=Date.now(),l=o.map(d=>({job_id:d.jobId,elapsed_ms:r-(B(d.jobId)??r)}));return{node_id:t,jobs:l}},"run"),render:s(e=>{const t=e.jobs;if(t.length===0)return`No foreground bash command is running for ${e.node_id}.`;const n=Math.max(...t.map(o=>o.elapsed_ms));return`Handed ${t.length} bash command(s) to the background job system for ${e.node_id} \xB7 ran ${k(n)} in the foreground.`},"render")}),O=c({name:"list",description:"list a node\u2019s live background bash jobs",whenToUse:"you need the ids, commands, and ages of your jobs that are still running",help:{name:"node bash list",summary:"read every still-running background bash job owned by one node",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node whose background jobs to list. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane showing the target node, when --node is omitted. Defaults to $TMUX_PANE."},{kind:"flag",name:"tail",type:"int",required:!1,default:0,constraint:"Include this many trailing log lines per job (0 = none). Read from the local log file, so it is available only on the machine that runs the job; elsewhere log_tail is null."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The target node."},{name:"jobs",type:"object[]",required:!0,constraint:"Oldest-first {job_id, command, purpose, started_at, elapsed_ms, deadline_at, output_key, log_path, pgid, log_tail} for every live background job; log_path and log_tail are null when the job\u2019s log is not on this machine (a job running in a sandbox, listed from outside it), and no tail is available from here (its output is stored under output_key); purpose is a human-readable label or null; deadline_at is the auto-cancel time or null for a job that runs to completion."}],outputKind:"object",effects:["Reads the node\u2019s running job rows and, for log_tail, the local log files. Changes nothing."]},run:s(async e=>{const{id:t}=u(e,"no node found for the bash job list"),n=await h(t);if(n===null)throw new i({error:"not_found",message:`no node: ${t}`,next:"List nodes with `crtr node inspect list`."});const o=Date.now(),r=Math.max(0,Math.trunc(Number(e.tail??0))),l=n.paths.context_dir,d=(await g().listBashJobs(n.node_id)).map(a=>{const m=y(l,a.job_id).jobLog,b=R(m)?m:null;return{job_id:a.job_id,command:a.command,purpose:a.purpose,started_at:a.started_at,elapsed_ms:a.elapsed_ms,deadline_at:a.deadline_at,output_key:a.output_key,log_path:b,pgid:a.pgid,log_tail:b===null?null:S(b,r)}});return{node_id:n.node_id,jobs:d}},"run"),render:s(e=>{const t=e.jobs;return t.length===0?`No background bash jobs for ${e.node_id}.`:t.map(n=>`${n.job_id} \xB7 ${k(n.elapsed_ms)} \xB7 ${N(n.command)}${n.purpose===null?"":`
1
+ var T=Object.defineProperty;var r=(e,t)=>T(e,"name",{value:t,configurable:!0});import{envNodeId as _}from"../../shared/env.js";import{defineBranch as q,defineLeaf as h}from"../../core/command.js";import{InputError as i}from"../../core/io.js";import{ExitCode as R}from"../../types.js";import{existsSync as D,mkdirSync as N,rmSync as E,writeFileSync as w}from"node:fs";import{backgroundRunningBashJobs as I,bashJobCommandLine as B,bashJobPaths as y,formatBashElapsed as k,isSafeBashJobId as p,jobStartedAtMs as L,newBashJobId as M,tailJobLog as S}from"../../core/bash-jobs.js";import{parseDurationMs as U}from"../../core/wake.js";import{cliClient as b,getNodeOrNull as u}from"../api-client.js";import{ApiError as x}from"../../api/index.js";import{PREVIEW_RESULT_PATH_ENV as J}from"../../core/preview-result-path.js";import{nodeInPane as A}from"../surface/node/placement.js";function c(e,t){const n=e.pane??process.env.TMUX_PANE;let o=e.node;if((o===void 0||o==="")&&(o=A(n)),(o===void 0||o==="")&&(o=_()),o===void 0||o==="")throw new i({error:"no_node",message:t,next:"Run from inside a node, pass --node <id>, or --pane <pane>."});return{id:o,pane:n}}r(c,"resolveBashTarget");async function j(e,t,n){try{await b().patchBashJob(e,t,n)}catch(o){throw o instanceof x&&(o.status===404||o.status===409)?new i({error:"not_running",message:`background job ${t} for ${e} is not running`,next:"List live jobs with `crtr node bash list`."}):o}}r(j,"patchRunningJob");const C=h({name:"run",description:"start a shell command as a background job and return at once",whenToUse:"a command needs longer than one tool call \u2014 a test lane, a build, a soak \u2014 and you should be woken with its exit code instead of waiting, backgrounding with `&`, or polling a log",tier:"normal",help:{name:"node bash run",summary:"start one shell command as a background job owned by the calling node and return before it finishes",params:[{kind:"positional",name:"command",required:!0,constraint:"The shell command, run by bash from the current directory with this process\u2019s environment. The same refusals apply as for a bash tool call, judged on this command."},{kind:"flag",name:"quiet-ok",type:"bool",required:!1,constraint:"Pass for a job that legitimately sits idle \u2014 `gh run watch`, `sleep`, waiting on a lock, a `docker build` whose work runs outside the process tree \u2014 so it gets no silence messages. Omit it for anything that should be making progress."}],output:[{name:"job_id",type:"string",required:!0,constraint:"The job object\u2019s id; read or watch it with `crtr canvas`, stop it with `node bash kill`."},{name:"log_path",type:"string",required:!0,constraint:"File receiving the command\u2019s combined stdout and stderr, capped at 10 MiB."}],outputKind:"object",effects:["Works only inside a node\u2019s bash tool call; the job belongs to the calling node.","Starts the command detached from this call, so it keeps running after the call and the turn end. A `timeout` around this leaf bounds only the launch, not the job.","Records the job as a canvas object; when the command exits, its watchers (the calling node by default) get an urgent `exited` push with the exit code and log path.","Sets no cancel deadline: the job runs to completion unless stopped with `node bash kill`.","A job whose processes use almost no CPU and write to no open file for 15 minutes messages the calling node about it, again at 30 and 60 minutes, then hourly; activity restarts the schedule and nothing is stopped. `--quiet-ok` turns these messages off.","Fails, with the job id and log path, if the job could not be recorded; the command is then running but will not message the node when it exits."]},run:r(async e=>{const t=e.command,n=_(),o=process.env.CRTR_CONTEXT_DIR;if(n===void 0||n===""||o===void 0||o==="")throw new i({error:"no_node",message:"`node bash run` starts a job for the calling node and works only inside a node\u2019s bash tool",next:"Run it from a node\u2019s bash tool call."});const s=process.cwd(),{bashCommandRefusal:d}=await import("../../core/bash-guard.js"),l=d(t,s);if(l!==null)throw new i({error:"refused",message:l,next:"Run a different command."});const a=y(o,M());N(a.dir,{recursive:!0}),w(a.cmdSh,t),w(a.jobLog,""),w(a.jobBg,"");const{launchBashJobSupervisor:m}=await import("../../core/runtime/broker/bash-job-launch.js"),{[J]:g,...v}=process.env;try{await m(n,a,o,s,v)}catch(f){throw E(a.dir,{recursive:!0,force:!0}),f}const{recordBackgroundedJob:$}=await import("../../core/runtime/broker/daemon-ops.js");try{await $(n,a.jobId,{command:t}),e.quietOk===!0&&await b().patchBashJob(n,a.jobId,{quiet_ok:!0})}catch(f){throw new i({error:"job_not_recorded",message:`job ${a.jobId} is running but could not be recorded (${f.message}); it will NOT message you when it exits. Log: ${a.jobLog}`,next:`Watch it with \`tail -f ${a.jobLog}\`, or stop it with \`crtr node bash kill ${a.jobId}\`.`},R.GENERAL)}return{job_id:a.jobId,log_path:a.jobLog}},"run"),render:r(e=>`[backgrounded as job ${e.job_id}. It will interrupt you with a message when it exits. Watch: tail -f ${e.log_path} ; stop: crtr node bash kill ${e.job_id}]`,"render")}),P=h({name:"background",description:"hand the focused node\u2019s running bash command to its background job system",whenToUse:"the tmux prefix menu invokes this when a foreground bash command should stop blocking the agent immediately but keep running as a file-backed background job",tier:"hidden",help:{name:"node bash background",summary:"hand every running foreground bash command for one node to its file-backed background job system",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node whose running bash command to background. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane showing the target node, when --node is omitted. Defaults to $TMUX_PANE. The prefix menu passes this for you."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The target node."},{name:"jobs",type:"object[]",required:!0,constraint:"Per handed-off job {job_id, elapsed_ms} \u2014 the foreground elapsed time in milliseconds at the moment of handoff."}],outputKind:"object",effects:["Signals every foreground bash command currently running for the node to return from its tool call immediately while its detached supervisor keeps the command running.","Each handed-off command retains its log under the node context jobs directory and becomes a job object; when it exits, the job\u2019s watchers (its node by default) get an urgent `exited` push carrying the exit code and log path."]},run:r(async e=>{const{id:t}=c(e,"no node found for the bash handoff"),n=await u(t);if(n===null)throw new i({error:"not_found",message:`no node: ${t}`,next:"List nodes with `crtr node inspect list`."});const o=I(n.paths.context_dir),s=Date.now(),d=o.map(l=>({job_id:l.jobId,elapsed_ms:s-(L(l.jobId)??s)}));return{node_id:t,jobs:d}},"run"),render:r(e=>{const t=e.jobs;if(t.length===0)return`No foreground bash command is running for ${e.node_id}.`;const n=Math.max(...t.map(o=>o.elapsed_ms));return`Handed ${t.length} bash command(s) to the background job system for ${e.node_id} \xB7 ran ${k(n)} in the foreground.`},"render")}),O=h({name:"list",description:"list a node\u2019s live background bash jobs",whenToUse:"you need the ids, commands, and ages of your jobs that are still running",help:{name:"node bash list",summary:"read every still-running background bash job owned by one node",params:[{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node whose background jobs to list. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane showing the target node, when --node is omitted. Defaults to $TMUX_PANE."},{kind:"flag",name:"tail",type:"int",required:!1,default:0,constraint:"Include this many trailing log lines per job (0 = none). Read from the local log file, so it is available only on the machine that runs the job; elsewhere log_tail is null \u2014 use `node bash log` there."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The target node."},{name:"jobs",type:"object[]",required:!0,constraint:"Oldest-first {job_id, command, purpose, started_at, elapsed_ms, deadline_at, output_key, log_path, pgid, log_tail} for every live background job; log_path and log_tail are null when the job\u2019s log is not on this machine (a job running in a sandbox, listed from outside it), and no tail is available from here (`node bash log` reads its stored output); purpose is a human-readable label or null; deadline_at is the auto-cancel time or null for a job that runs to completion."}],outputKind:"object",effects:["Reads the node\u2019s running job rows and, for log_tail, the local log files. Changes nothing."]},run:r(async e=>{const{id:t}=c(e,"no node found for the bash job list"),n=await u(t);if(n===null)throw new i({error:"not_found",message:`no node: ${t}`,next:"List nodes with `crtr node inspect list`."});const o=Date.now(),s=Math.max(0,Math.trunc(Number(e.tail??0))),d=n.paths.context_dir,l=(await b().listBashJobs(n.node_id)).map(a=>{const m=y(d,a.job_id).jobLog,g=D(m)?m:null;return{job_id:a.job_id,command:a.command,purpose:a.purpose,started_at:a.started_at,elapsed_ms:a.elapsed_ms,deadline_at:a.deadline_at,output_key:a.output_key,log_path:g,pgid:a.pgid,log_tail:g===null?null:S(g,s)}});return{node_id:n.node_id,jobs:l}},"run"),render:r(e=>{const t=e.jobs;return t.length===0?`No background bash jobs for ${e.node_id}.`:t.map(n=>`${n.job_id} \xB7 ${k(n.elapsed_ms)} \xB7 ${B(n.command)}${n.purpose===null?"":`
2
2
  purpose: ${n.purpose}`}${n.deadline_at===null?"":`
3
3
  cancels at: ${n.deadline_at}`}
4
- ${n.log_path===null?`log: not on this machine (the job runs in a sandbox); no tail is available here, output is stored under ${n.output_key}`:`log: ${n.log_path}`}`).join(`
5
- `)},"render")}),K=c({name:"kill",description:"stop one of a node\u2019s background bash jobs",whenToUse:"a job you started is no longer wanted or is stuck, and it should stop now",help:{name:"node bash kill",summary:"best-effort stop a background bash job and tell the owning node it was canceled",params:[{kind:"positional",name:"job",required:!0,constraint:"Job id, as reported by node bash list."},{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node owning the job. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane showing the target node, when --node is omitted. Defaults to $TMUX_PANE."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The owning node."},{name:"signaled",type:"boolean",required:!0,constraint:"True when at least one observed supervisor-tree process received SIGTERM."}],outputKind:"object",effects:["Snapshots the supervisor\u2019s observable process tree, sends SIGTERM then SIGKILL to those processes, and cannot stop a descendant that detached and reparented before the snapshot.","Records the job\u2019s exit so it stops showing as live, and ends its job object as killed; the job\u2019s watchers (its node by default) get an urgent `killed` push."]},run:s(async e=>{const{id:t}=u(e,"no node found for the bash job"),n=await h(t);if(n===null)throw new i({error:"not_found",message:`no node: ${t}`,next:"List nodes with `crtr node inspect list`."});const o=e.job;if(!w(o))throw new i({error:"invalid_job_id",message:`invalid background job id: ${o}`,next:"Use a job id reported by `crtr node bash list`."});const r=await g().stopBashJob(n.node_id,o);return{node_id:n.node_id,signaled:r.signaled}},"run"),render:s(e=>`Canceled a background bash job for ${e.node_id}${e.signaled===!0?"":" (no observed job process was still running)"}.`,"render")}),X=c({name:"quiet",description:"mark a running background bash job as an expected wait so it sends no silence messages",whenToUse:"a silence message arrived for a job that is meant to sit idle",help:{name:"node bash quiet",summary:"stop silence messages for one of a node\u2019s live background bash jobs",params:[{kind:"positional",name:"job",required:!0,constraint:"Job id, as reported by node bash list or the silence message."},{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node owning the job. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane showing the target node, when --node is omitted. Defaults to $TMUX_PANE."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The owning node."}],outputKind:"object",effects:["Marks the job\u2019s row; the daemon sends no further silence messages for it. Signals nothing."]},run:s(async e=>{const{id:t}=u(e,"no node found for the bash job"),n=await h(t);if(n===null)throw new i({error:"not_found",message:`no node: ${t}`,next:"List nodes with `crtr node inspect list`."});const o=e.job;if(!w(o))throw new i({error:"invalid_job_id",message:`invalid background job id: ${o}`,next:"Use a job id reported by `crtr node bash list`."});return await j(n.node_id,o,{quiet_ok:!0}),{node_id:n.node_id}},"run"),render:s(e=>`Background job for ${e.node_id} will send no more silence messages.`,"render")}),G=c({name:"extend",description:"claim an auto-backgrounded bash job so the daemon does not cancel it at its deadline",whenToUse:"a command that was moved to a job automatically is worth keeping past its cancel deadline \u2014 give it more time with --for, or let it run to completion with --keep",help:{name:"node bash extend",summary:"move or clear the auto-cancel deadline on one of a node\u2019s live background bash jobs",params:[{kind:"positional",name:"job",required:!0,constraint:"Job id, as reported by node bash list or the auto-background notice."},{kind:"flag",name:"for",type:"string",required:!1,constraint:"New window from now before the job is canceled, as a duration (e.g. 90s, 10m, 1h30m). Mutually exclusive with --keep; give exactly one."},{kind:"flag",name:"keep",type:"bool",required:!1,constraint:"Remove the deadline entirely so the job runs to completion. Mutually exclusive with --for; give exactly one."},{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node owning the job. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane showing the target node, when --node is omitted. Defaults to $TMUX_PANE."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The owning node."},{name:"deadline_at",type:"string",required:!1,constraint:"The job\u2019s new auto-cancel time (ISO), or absent when --keep cleared the deadline so the job runs to completion."}],outputKind:"object",effects:["Rewrites or clears the deadline on the job\u2019s row. The daemon\u2019s deadline lane reads it on its next scan. Signals nothing."]},run:s(async e=>{const{id:t}=u(e,"no node found for the bash job"),n=await h(t);if(n===null)throw new i({error:"not_found",message:`no node: ${t}`,next:"List nodes with `crtr node inspect list`."});const o=e.job;if(!w(o))throw new i({error:"invalid_job_id",message:`invalid background job id: ${o}`,next:"Use a job id reported by `crtr node bash list`."});const r=e.for,l=e.keep===!0;if(l===(r!==void 0&&r!==""))throw new i({error:"invalid_args",message:"give exactly one of --for <duration> or --keep",next:"Extend by a window with --for 10m, or drop the deadline with --keep."});if(l)return await j(n.node_id,o,{keep:!0}),{node_id:n.node_id};const d=M(r);if(d===null||d<=0)throw new i({error:"invalid_duration",message:`not a duration: ${r}`,next:"Use a value like 90s, 10m, or 1h30m."});const a=Date.now()+d;return await j(n.node_id,o,{deadline_at_ms:a}),{node_id:n.node_id,deadline_at:new Date(a).toISOString()}},"run"),render:s(e=>e.deadline_at===void 0?`Cleared the deadline \u2014 the background job will run to completion for ${e.node_id}.`:`Background job for ${e.node_id} will be canceled at ${e.deadline_at} unless extended again.`,"render")}),ae=T({name:"bash",description:"start, list, stop, and extend your background bash jobs",whenToUse:"a command needs longer than one tool call \u2014 start it as a job and be woken with its exit code instead of using `&`, `nohup`, or a cron that polls a log \u2014 or you need to list, stop, or extend the deadline of a job you already have",help:{name:"node bash",summary:"start, list, stop, and extend background bash jobs owned by a node",model:"Every bash call runs under a detached supervisor with its output in a log file, so a command that outlives its call keeps running as a job: a canvas object its node owns, readable and watchable with `crtr canvas`. `run` starts a job on purpose and it is never auto-canceled. A job that shows no activity for 15 minutes messages its node, and is never stopped for it. A foreground command still running after 5 minutes is moved to a job automatically, with a cancel deadline unless `extend` claims it."},children:[A,P,O,K,X,G]});export{ae as nodeBash};
4
+ ${n.log_path===null?`log: not on this machine (the job runs in a sandbox); read its output with \`crtr node bash log ${n.job_id} --node ${e.node_id}\``:`log: ${n.log_path}`}`).join(`
5
+ `)},"render")}),K=h({name:"log",description:"read a background bash job\u2019s output from the blob store, from any machine",whenToUse:"a job has exited or been killed, or its sandbox is gone, and you need what it printed \u2014 the wake and the job object name this command; for a job still running on this machine, `tail -f` on its log file shows more recent output",help:{name:"node bash log",summary:"read the stored output of one background bash job, last lines first bounded",params:[{kind:"positional",name:"job",required:!0,constraint:"Job id, as named by the exit wake, `node bash list` or `node bash run`. Running and finished jobs both work."},{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node owning the job. Defaults to the node in --pane, else the caller (CRTR_NODE_ID). A job another node started needs its owner here."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane showing the target node, when --node is omitted. Defaults to $TMUX_PANE."},{kind:"flag",name:"tail",type:"int",required:!1,constraint:"Return only this many trailing lines. Without it you get the last 256 KiB of output; either way at most 4 MiB."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The owning node."},{name:"job_id",type:"string",required:!0,constraint:"The job."},{name:"output",type:"string",required:!0,constraint:"The stored output, empty when the job has written nothing yet. The broker uploads a running job\u2019s output every couple of seconds, so it can trail the live log slightly."},{name:"truncated",type:"boolean",required:!0,constraint:"True when earlier output exists that the bound left out."}],outputKind:"object",effects:["Reads the output chunks the job\u2019s broker uploaded to the blob store through the daemon; never the local log file, so it works from any machine and after the job\u2019s sandbox is deleted. Needs read access to the owning node. Changes nothing."]},run:r(async e=>{const{id:t}=c(e,"no node found for the bash job"),n=await u(t);if(n===null)throw new i({error:"not_found",message:`no node: ${t}`,next:"List nodes with `crtr node inspect list`."});const o=e.job;if(!p(o))throw new i({error:"invalid_job_id",message:`invalid background job id: ${o}`,next:"Use a job id named by the exit wake or `crtr node bash list`."});const s=e.tail===void 0?void 0:Math.trunc(Number(e.tail));if(s!==void 0&&!(s>=1))throw new i({error:"invalid_args",message:"--tail must be a positive number of lines",next:"Pass --tail 50, or omit it for the last 256 KiB."});try{const d=await b().readBashJobOutput(n.node_id,o,s===void 0?{}:{tail_lines:s});return{node_id:n.node_id,job_id:d.job_id,output:d.output,truncated:d.truncated}}catch(d){throw d instanceof x&&d.code==="job_not_found"?new i({error:"job_not_found",message:`no job ${o} owned by ${n.node_id}`,next:"A job another node started needs its owner: pass --node <owner>."}):d}},"run"),render:r(e=>`${e.truncated===!0?`[earlier output omitted]
6
+ `:""}${String(e.output)}`,"render")}),X=h({name:"kill",description:"stop one of a node\u2019s background bash jobs",whenToUse:"a job you started is no longer wanted or is stuck, and it should stop now",help:{name:"node bash kill",summary:"best-effort stop a background bash job and tell the owning node it was canceled",params:[{kind:"positional",name:"job",required:!0,constraint:"Job id, as reported by node bash list."},{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node owning the job. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane showing the target node, when --node is omitted. Defaults to $TMUX_PANE."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The owning node."},{name:"signaled",type:"boolean",required:!0,constraint:"True when at least one observed supervisor-tree process received SIGTERM."}],outputKind:"object",effects:["Snapshots the supervisor\u2019s observable process tree, sends SIGTERM then SIGKILL to those processes, and cannot stop a descendant that detached and reparented before the snapshot.","Records the job\u2019s exit so it stops showing as live, and ends its job object as killed; the job\u2019s watchers (its node by default) get an urgent `killed` push."]},run:r(async e=>{const{id:t}=c(e,"no node found for the bash job"),n=await u(t);if(n===null)throw new i({error:"not_found",message:`no node: ${t}`,next:"List nodes with `crtr node inspect list`."});const o=e.job;if(!p(o))throw new i({error:"invalid_job_id",message:`invalid background job id: ${o}`,next:"Use a job id reported by `crtr node bash list`."});const s=await b().stopBashJob(n.node_id,o);return{node_id:n.node_id,signaled:s.signaled}},"run"),render:r(e=>`Canceled a background bash job for ${e.node_id}${e.signaled===!0?"":" (no observed job process was still running)"}.`,"render")}),W=h({name:"quiet",description:"mark a running background bash job as an expected wait so it sends no silence messages",whenToUse:"a silence message arrived for a job that is meant to sit idle",help:{name:"node bash quiet",summary:"stop silence messages for one of a node\u2019s live background bash jobs",params:[{kind:"positional",name:"job",required:!0,constraint:"Job id, as reported by node bash list or the silence message."},{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node owning the job. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane showing the target node, when --node is omitted. Defaults to $TMUX_PANE."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The owning node."}],outputKind:"object",effects:["Marks the job\u2019s row; the daemon sends no further silence messages for it. Signals nothing."]},run:r(async e=>{const{id:t}=c(e,"no node found for the bash job"),n=await u(t);if(n===null)throw new i({error:"not_found",message:`no node: ${t}`,next:"List nodes with `crtr node inspect list`."});const o=e.job;if(!p(o))throw new i({error:"invalid_job_id",message:`invalid background job id: ${o}`,next:"Use a job id reported by `crtr node bash list`."});return await j(n.node_id,o,{quiet_ok:!0}),{node_id:n.node_id}},"run"),render:r(e=>`Background job for ${e.node_id} will send no more silence messages.`,"render")}),G=h({name:"extend",description:"claim an auto-backgrounded bash job so the daemon does not cancel it at its deadline",whenToUse:"a command that was moved to a job automatically is worth keeping past its cancel deadline \u2014 give it more time with --for, or let it run to completion with --keep",help:{name:"node bash extend",summary:"move or clear the auto-cancel deadline on one of a node\u2019s live background bash jobs",params:[{kind:"positional",name:"job",required:!0,constraint:"Job id, as reported by node bash list or the auto-background notice."},{kind:"flag",name:"for",type:"string",required:!1,constraint:"New window from now before the job is canceled, as a duration (e.g. 90s, 10m, 1h30m). Mutually exclusive with --keep; give exactly one."},{kind:"flag",name:"keep",type:"bool",required:!1,constraint:"Remove the deadline entirely so the job runs to completion. Mutually exclusive with --for; give exactly one."},{kind:"flag",name:"node",type:"string",required:!1,constraint:"Node owning the job. Defaults to the node in --pane, else the caller (CRTR_NODE_ID)."},{kind:"flag",name:"pane",type:"string",required:!1,constraint:"tmux pane showing the target node, when --node is omitted. Defaults to $TMUX_PANE."}],output:[{name:"node_id",type:"string",required:!0,constraint:"The owning node."},{name:"deadline_at",type:"string",required:!1,constraint:"The job\u2019s new auto-cancel time (ISO), or absent when --keep cleared the deadline so the job runs to completion."}],outputKind:"object",effects:["Rewrites or clears the deadline on the job\u2019s row. The daemon\u2019s deadline lane reads it on its next scan. Signals nothing."]},run:r(async e=>{const{id:t}=c(e,"no node found for the bash job"),n=await u(t);if(n===null)throw new i({error:"not_found",message:`no node: ${t}`,next:"List nodes with `crtr node inspect list`."});const o=e.job;if(!p(o))throw new i({error:"invalid_job_id",message:`invalid background job id: ${o}`,next:"Use a job id reported by `crtr node bash list`."});const s=e.for,d=e.keep===!0;if(d===(s!==void 0&&s!==""))throw new i({error:"invalid_args",message:"give exactly one of --for <duration> or --keep",next:"Extend by a window with --for 10m, or drop the deadline with --keep."});if(d)return await j(n.node_id,o,{keep:!0}),{node_id:n.node_id};const l=U(s);if(l===null||l<=0)throw new i({error:"invalid_duration",message:`not a duration: ${s}`,next:"Use a value like 90s, 10m, or 1h30m."});const a=Date.now()+l;return await j(n.node_id,o,{deadline_at_ms:a}),{node_id:n.node_id,deadline_at:new Date(a).toISOString()}},"run"),render:r(e=>e.deadline_at===void 0?`Cleared the deadline \u2014 the background job will run to completion for ${e.node_id}.`:`Background job for ${e.node_id} will be canceled at ${e.deadline_at} unless extended again.`,"render")}),se=q({name:"bash",description:"start, list, read, stop, and extend your background bash jobs",whenToUse:"a command needs longer than one tool call \u2014 start it as a job and be woken with its exit code instead of using `&`, `nohup`, or a cron that polls a log \u2014 or you need to list, read the output of, stop, or extend the deadline of a job you already have",help:{name:"node bash",summary:"start, list, read, stop, and extend background bash jobs owned by a node",model:"Every bash call runs under a detached supervisor with its output in a log file, so a command that outlives its call keeps running as a job: a canvas object its node owns, readable and watchable with `crtr canvas`. `run` starts a job on purpose and it is never auto-canceled. A job that shows no activity for 15 minutes messages its node, and is never stopped for it. A foreground command still running after 5 minutes is moved to a job automatically, with a cancel deadline unless `extend` claims it."},children:[C,P,O,K,X,W,G]});export{se as nodeBash};
@@ -1 +1 @@
1
- var x=Object.defineProperty;var n=(t,e)=>x(t,"name",{value:e,configurable:!0});import{execFile as T}from"node:child_process";import{existsSync as f,mkdtempSync as $,readFileSync as g,rmSync as I}from"node:fs";import{homedir as O,tmpdir as P}from"node:os";import{join as d}from"node:path";import{promisify as L}from"node:util";import{defineBranch as M,defineLeaf as U}from"../../core/command.js";import{CrtrError as j,general as p,usage as C}from"../../core/errors.js";import{atomicWriteJson as E}from"../../core/fs-utils.js";import{northlightUnreachable as w}from"../../core/directory/reach.js";import{notSignedIn as y,readPersonCredential as k,personAccount as F}from"../../core/directory/person-credential.js";import{readMachineConfig as R}from"../../daemon/machine-link/config.js";import{ExitCode as B}from"../../types.js";const D=L(T),S="https://downloads.northlight.ai/desktop/mac/Northlight-Desktop.dmg",_="BRL7SHLNLU",N="Northlight Desktop.app",J="/Applications",m=d(O(),"Library","Application Support","Northlight Desktop"),A=3e4,K=12e4,W=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,b=n(t=>new Promise(e=>setTimeout(e,t)),"sleep"),h=n(()=>d(J,N),"appPath"),l=n(async(t,e)=>(await D(t,e,{maxBuffer:16*1024*1024})).stdout,"sh");async function q(){try{return await l("pgrep",["-f",`${h()}/Contents/MacOS/`]),!0}catch{return!1}}n(q,"desktopRunning");function v(t){try{return JSON.parse(g(d(m,"machine.json"),"utf8"))?.owner===t}catch{return!1}}n(v,"desktopPaired");async function G(t){const e=d(t,"Northlight-Desktop.dmg");try{await l("curl",["-fsSL","--max-time","600","-o",e,S])}catch(o){const a=String(o.stderr??"");throw/Could not resolve|Failed to connect|timed out|SSL|Connection|resolve host/i.test(a)?w(o,S):p(`the Desktop download failed: ${a.trim()||o.message}`)}return e}n(G,"download");async function H(t,e){await l("hdiutil",["attach","-nobrowse","-readonly","-mountpoint",e,t]);try{const o=d(e,N);try{await l("codesign",["--verify","--deep","--strict",o])}catch(i){throw p(`the downloaded Desktop failed its signature check, so it was not installed: ${String(i.stderr??i.message).trim()}`)}const a=(await D("codesign",["-dv","--verbose=4",o])).stderr,s=/^TeamIdentifier=(\S+)$/m.exec(a)?.[1];if(s!==_)throw p(`the downloaded Desktop is signed by team ${s??"none"}, not Northlight's ${_}, so it was not installed`);await l("ditto",[o,h()])}finally{await l("hdiutil",["detach",e,"-quiet"]).catch(()=>{})}}n(H,"installFrom");async function z(){const t=d(m,"install.json");for(const e=Date.now()+A;Date.now()<e;await b(500))try{const o=JSON.parse(g(t,"utf8")).install_id;if(typeof o=="string"&&W.test(o))return o}catch{}throw p(`Desktop opened but did not write ${t} within ${A/1e3} seconds`,{next:"Open Northlight Desktop yourself, then run `crtr sys desktop install` again."})}n(z,"waitForInstallId");async function V(t,e){const o=k(),a=`${t.replace(/\/+$/,"")}/v1/developer/desktop-enrollment`;let s;try{s=await fetch(a,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${o.secret}`},body:JSON.stringify({install_id:e}),signal:AbortSignal.timeout(3e4)})}catch(c){throw w(c,a)}const i=await s.text();let r={};try{r=i===""?{}:JSON.parse(i)}catch{}if(s.status===401){const c=typeof r.error=="object"?r.error?.code:r.error;if(c==="credential_revoked"||c==="credential_expired"||c==="credential_unknown")throw y(`the sign-in on this computer is no longer valid (${String(c)})`)}if(!s.ok||typeof r.device_code!="string"||typeof r.expires_in!="number"){const c=typeof r.error=="object"&&typeof r.error?.message=="string"?r.error.message:`HTTP ${s.status}`;throw p(`Northlight refused to pair Desktop: ${c}`)}return{device_code:r.device_code,expires_in:r.expires_in}}n(V,"mintPairing");const u=n(()=>l("open",["-a",h()]),"openApp"),Q=U({name:"install",description:"download, install, open and pair Northlight Desktop to this account",whenToUse:"a `device` command answered that no Northlight app is paired to this account, and the person said yes to installing it",help:{name:"sys desktop install",summary:"install Northlight Desktop in Applications and pair it to the account crtr is signed in to, with no code and no Allow click",guide:"macOS only, and only on a computer signed in with `crtr sys signin` and moved to Northlight. Downloads Desktop from downloads.northlight.ai, checks it is signed by Northlight, installs it, opens it, pairs it, then waits up to two minutes until Desktop on this Mac has registered to the account. Safe to run again: a Desktop on this Mac that is already registered to the account is only opened. macOS permission prompts (Full Disk Access, Messages) still appear when a feature needs them.",params:[],output:[{name:"installed",type:"boolean",required:!0,constraint:"True when this run installed Desktop; false when it was already installed and paired."}],outputKind:"object",effects:["Downloads and mounts the Desktop image, then copies the app to `/Applications/Northlight Desktop.app` (a copy that is running is left as it is and only paired). Opens Desktop. Calls `POST /v1/developer/desktop-enrollment` with the stored sign-in and writes `~/Library/Application Support/Northlight Desktop/pairing.json` (mode 0600), which Desktop deletes when it reads it."]},run:n(async()=>{if(process.platform!=="darwin")throw C("Northlight Desktop is a Mac app: this command only runs on macOS");const t=k();if(t===void 0||R()===void 0)throw y("this computer is not signed in to Northlight");const{id:e}=await F(t.directoryUrl);if(f(h())&&v(e))return await u(),{installed:!1};if(!(f(h())&&await q())){const i=$(d(P(),"crtr-desktop-"));try{await H(await G(i),d(i,"mnt"))}finally{I(i,{recursive:!0,force:!0})}}await u();const a=await z(),s=await V(t.directoryUrl,a);E(d(m,"pairing.json"),{issuer:t.directoryUrl.replace(/\/+$/,""),device_code:s.device_code},{mode:384}),await u();for(const i=Date.now()+K;Date.now()<i;await b(3e3))if(v(e))return{installed:!0};throw new j("desktop_not_paired","Desktop opened but did not pair.",B.GENERAL,{next:"If Desktop is signed in to another account, sign out there and run `crtr sys desktop install` again."})},"run"),render:n(t=>`<desktop installed="${t.installed}"/>`,"render")}),lt=M({name:"desktop",description:"Northlight Desktop on this Mac",whenToUse:"Northlight Desktop must be installed and paired to this account",help:{name:"sys desktop",summary:"install and pair Northlight Desktop"},children:[Q]});export{S as DESKTOP_DMG_URL,_ as DESKTOP_TEAM_ID,lt as sysDesktopBranch,Q as sysDesktopInstallLeaf};
1
+ var I=Object.defineProperty;var i=(t,e)=>I(t,"name",{value:e,configurable:!0});import{execFile as x}from"node:child_process";import{existsSync as f,mkdtempSync as O,readFileSync as w,rmSync as $}from"node:fs";import{homedir as M,tmpdir as P}from"node:os";import{join as a}from"node:path";import{promisify as L}from"node:util";import{defineBranch as U,defineLeaf as C}from"../../core/command.js";import{CrtrError as g,general as h,usage as E}from"../../core/errors.js";import{atomicWriteJson as j}from"../../core/fs-utils.js";import{northlightUnreachable as y}from"../../core/directory/reach.js";import{readPersonCredential as k,personAccount as F}from"../../core/directory/person-credential.js";import{readMachineConfig as R}from"../../daemon/machine-link/config.js";import{ExitCode as B}from"../../types.js";const D=L(x),_="https://downloads.northlight.ai/desktop/mac/Northlight-Desktop.dmg",S="BRL7SHLNLU",N="Northlight Desktop.app",J="/Applications",m=a(M(),"Library","Application Support","Northlight Desktop"),A=3e4,q=12e4,K=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,b=i(t=>new Promise(e=>setTimeout(e,t)),"sleep"),p=i(()=>a(J,N),"appPath"),l=i(async(t,e)=>(await D(t,e,{maxBuffer:16*1024*1024})).stdout,"sh");async function W(){try{return await l("pgrep",["-f",`${p()}/Contents/MacOS/`]),!0}catch{return!1}}i(W,"desktopRunning");function T(t){try{return JSON.parse(w(a(m,"machine.json"),"utf8"))?.owner===t}catch{return!1}}i(T,"desktopPaired");async function G(t){const e=a(t,"Northlight-Desktop.dmg");try{await l("curl",["-fsSL","--max-time","600","-o",e,_])}catch(o){const r=String(o.stderr??"");throw/Could not resolve|Failed to connect|timed out|SSL|Connection|resolve host/i.test(r)?y(o,_):h(`the Desktop download failed: ${r.trim()||o.message}`)}return e}i(G,"download");async function H(t,e){await l("hdiutil",["attach","-nobrowse","-readonly","-mountpoint",e,t]);try{const o=a(e,N);try{await l("codesign",["--verify","--deep","--strict",o])}catch(d){throw h(`the downloaded Desktop failed its signature check, so it was not installed: ${String(d.stderr??d.message).trim()}`)}const r=(await D("codesign",["-dv","--verbose=4",o])).stderr,s=/^TeamIdentifier=(\S+)$/m.exec(r)?.[1];if(s!==S)throw h(`the downloaded Desktop is signed by team ${s??"none"}, not Northlight's ${S}, so it was not installed`);await l("ditto",[o,p()])}finally{await l("hdiutil",["detach",e,"-quiet"]).catch(()=>{})}}i(H,"installFrom");async function z(){const t=a(m,"install.json");for(const e=Date.now()+A;Date.now()<e;await b(500))try{const o=JSON.parse(w(t,"utf8")).install_id;if(typeof o=="string"&&K.test(o))return o}catch{}throw h(`Desktop opened but did not write ${t} within ${A/1e3} seconds`,{next:"Open Northlight Desktop yourself, then run `crtr sys desktop install` again."})}i(z,"waitForInstallId");async function V(t,e){const o=k(),r=`${t.replace(/\/+$/,"")}/v1/developer/desktop-enrollment`;let s;try{s=await fetch(r,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${o.secret}`},body:JSON.stringify({install_id:e}),signal:AbortSignal.timeout(3e4)})}catch(c){throw y(c,r)}const d=await s.text();let n={};try{n=d===""?{}:JSON.parse(d)}catch{}if(s.status===401){const c=typeof n.error=="object"?n.error?.code:n.error;if(c==="credential_revoked"||c==="credential_expired"||c==="credential_unknown")return}if(!s.ok||typeof n.device_code!="string"||typeof n.expires_in!="number"){const c=typeof n.error=="object"&&typeof n.error?.message=="string"?n.error.message:`HTTP ${s.status}`;throw h(`Northlight refused to pair Desktop: ${c}`)}return{device_code:n.device_code,expires_in:n.expires_in}}i(V,"mintPairing");const u=i(()=>l("open",["-a",p()]),"openApp"),v='Northlight Desktop is open. Tell the person to sign in inside it with their Northlight account: its window shows a code and one Allow click, or use "Connect this Mac\u2026" in its menu-bar icon. Then retry the command that needed it.';async function Q(){const t=k();if(!(t===void 0||R()===void 0))try{return{owner:(await F(t.directoryUrl)).id,directoryUrl:t.directoryUrl}}catch(e){if(e instanceof g&&e.code==="not_signed_in")return;throw e}}i(Q,"pairingAccount");const X=C({name:"install",description:"download, install and open Northlight Desktop on this Mac",whenToUse:"a `device` command answered that no Northlight app is paired to this account, you are on the person's Mac, and the person said yes to installing it",help:{name:"sys desktop install",summary:"install Northlight Desktop in Applications and open it; the person then signs in inside Desktop",guide:'macOS only; needs no crtr sign-in. Downloads Desktop from downloads.northlight.ai, checks it is signed by Northlight, installs it and opens it. A Desktop already in Applications is opened, not replaced. When crtr on this Mac is also signed in and moved to Northlight, Desktop is paired to that account with no code and no Allow click, and the command waits up to two minutes for it to register. Otherwise `follow_up` says the person signs in inside Desktop with their Northlight account (its window, or "Connect this Mac\u2026" in the menu-bar icon). Safe to run again. macOS permission prompts (Full Disk Access, Messages) still appear when a feature needs them.',params:[],output:[{name:"installed",type:"boolean",required:!0,constraint:"True when this run installed Desktop; false when it was already installed."},{name:"paired",type:"boolean",required:!0,constraint:"True when Desktop on this Mac is registered to the account crtr is signed in to; false when the person still has to sign in inside Desktop."},{name:"follow_up",type:"string",required:!1,constraint:"Present when `paired` is false: what to tell the person."}],outputKind:"object",effects:["Downloads and mounts the Desktop image, then copies the app to `/Applications/Northlight Desktop.app` (a copy that is running is left as it is and only paired). Opens Desktop. Only when crtr on this Mac is signed in and moved: calls `POST /v1/developer/desktop-enrollment` with the stored sign-in and writes `~/Library/Application Support/Northlight Desktop/pairing.json` (mode 0600), which Desktop deletes when it reads it."]},run:i(async()=>{if(process.platform!=="darwin")throw E("Northlight Desktop is a Mac app: this command only runs on macOS");const t=await Q();if(t!==void 0&&f(p())&&T(t.owner))return await u(),{installed:!1,paired:!0};const e=t===void 0?!f(p()):!(f(p())&&await W());if(e){const n=O(a(P(),"crtr-desktop-"));try{await H(await G(n),a(n,"mnt"))}finally{$(n,{recursive:!0,force:!0})}}if(t===void 0)return await u(),{installed:e,paired:!1,follow_up:v};const{owner:o,directoryUrl:r}=t;await u();const s=await z(),d=await V(r,s);if(d===void 0)return{installed:e,paired:!1,follow_up:v};j(a(m,"pairing.json"),{issuer:r.replace(/\/+$/,""),device_code:d.device_code},{mode:384}),await u();for(const n=Date.now()+q;Date.now()<n;await b(3e3))if(T(o))return{installed:e,paired:!0};throw new g("desktop_not_paired","Desktop opened but did not pair.",B.GENERAL,{next:"If Desktop is signed in to another account, sign out there and run `crtr sys desktop install` again."})},"run"),render:i(t=>`<desktop installed="${t.installed}" paired="${t.paired}">${t.follow_up??""}</desktop>`,"render")}),pt=U({name:"desktop",description:"Northlight Desktop on this Mac",whenToUse:"Northlight Desktop must be installed and paired to this account",help:{name:"sys desktop",summary:"install and pair Northlight Desktop"},children:[X]});export{_ as DESKTOP_DMG_URL,S as DESKTOP_TEAM_ID,pt as sysDesktopBranch,X as sysDesktopInstallLeaf};
@@ -1 +1,2 @@
1
+ export { AttachRefusedError } from './transport.js';
1
2
  export { BrokerClient, BrokerUnavailableError, reconnectShouldGiveUp, RelayTransport, SocketTransport, type BrokerClientOptions, type ReadOpRequest, type RelayTarget, type Transport, } from './client.js';
@@ -1 +1 @@
1
- import{BrokerClient as o,BrokerUnavailableError as a,reconnectShouldGiveUp as n,RelayTransport as t,SocketTransport as l}from"./client.js";export{o as BrokerClient,a as BrokerUnavailableError,t as RelayTransport,l as SocketTransport,n as reconnectShouldGiveUp};
1
+ import{AttachRefusedError as o}from"./transport.js";import{BrokerClient as a,BrokerUnavailableError as n,reconnectShouldGiveUp as l,RelayTransport as p,SocketTransport as c}from"./client.js";export{o as AttachRefusedError,a as BrokerClient,n as BrokerUnavailableError,p as RelayTransport,c as SocketTransport,l as reconnectShouldGiveUp};
@@ -1 +1 @@
1
- var l=Object.defineProperty;var a=(r,t)=>l(r,"name",{value:t,configurable:!0});import{EventEmitter as d}from"node:events";import{WebSocket as h}from"ws";import{classify as m}from"../fault-classifier.js";import{BrokerUnavailableError as f}from"./transport.js";function u(r,t){if("socketPath"in r)return`ws+unix://${r.socketPath}:/v1/nodes/${encodeURIComponent(t)}/attach?revive=0`;const e=new URL(r.previewEndpoint);return e.protocol=e.protocol==="http:"?"ws:":"wss:",e.pathname=`/v1/nodes/${encodeURIComponent(t)}/attach`,e.search="?wakeParked=1",e.hash="",e.toString()}a(u,"relayUrl");class k extends d{static{a(this,"RelayTransport")}nodeId;target;ws;closeEmitted=!1;constructor(t,e){super(),this.nodeId=t,this.target=e}get url(){return u(this.target,this.nodeId)}dial(){const t=new h(this.url,{..."relayToken"in this.target?{headers:{authorization:`Bearer ${this.target.relayToken}`}}:{},perMessageDeflate:!1});return this.ws=t,t}connect(){const t=this.dial();t.on("open",()=>{this.emit("connect")}),t.on("message",e=>this.onMessage(e)),t.on("error",e=>this.onError(e)),t.on("close",(e,n)=>this.onClose(e,n.toString()))}redial(t){return this.destroy(),this.closeEmitted=!1,new Promise((e,n)=>{const o=this.dial();let i=!1;o.on("open",()=>{i||(i=!0,this.emit("connect"),e())}),o.on("message",s=>this.onMessage(s)),o.on("error",s=>{if(i){this.onError(s);return}i=!0;try{o.terminate()}catch{}n(s)}),o.on("close",(s,c)=>{i&&this.onClose(s,c.toString())})})}send(t){const e=this.ws;if(!(e===void 0||e.readyState!==h.OPEN))try{e.send(JSON.stringify(t))}catch{}}destroy(){const t=this.ws;if(t!==void 0&&t.readyState<=h.OPEN)try{t.close(1e3)}catch{}}onMessage(t){let e;try{e=JSON.parse(t.toString())}catch{return}this.emit("frame",e)}onError(t){this.emitError(new Error(`relay transport error: ${t.message}`))}onClose(t,e){if(this.closeEmitted)return;this.closeEmitted=!0;const n=m("viewer\u2194broker",{code:t,reason:e});(t===1008||n.kind==="protocol"||t===1011&&(/^no running broker/.test(e)||/^no node /.test(e)))&&this.emitError(new f(this.nodeId)),this.emit("close")}emitError(t){this.listenerCount("error")>0&&this.emit("error",t)}}export{k as RelayTransport};
1
+ var l=Object.defineProperty;var a=(s,t)=>l(s,"name",{value:t,configurable:!0});import{EventEmitter as d}from"node:events";import{WebSocket as h}from"ws";import{classify as m}from"../fault-classifier.js";import{AttachRefusedError as f,BrokerUnavailableError as u}from"./transport.js";function p(s,t){if("socketPath"in s)return`ws+unix://${s.socketPath}:/v1/nodes/${encodeURIComponent(t)}/attach?revive=0`;const e=new URL(s.previewEndpoint);return e.protocol=e.protocol==="http:"?"ws:":"wss:",e.pathname=`/v1/nodes/${encodeURIComponent(t)}/attach`,e.search="?wakeParked=1",e.hash="",e.toString()}a(p,"relayUrl");class S extends d{static{a(this,"RelayTransport")}nodeId;target;ws;closeEmitted=!1;constructor(t,e){super(),this.nodeId=t,this.target=e}get url(){return p(this.target,this.nodeId)}dial(){const t=new h(this.url,{..."relayToken"in this.target?{headers:{authorization:`Bearer ${this.target.relayToken}`}}:{},perMessageDeflate:!1});return this.ws=t,t}connect(){const t=this.dial();t.on("open",()=>{this.emit("connect")}),t.on("message",e=>this.onMessage(e)),t.on("error",e=>this.onError(e)),t.on("close",(e,n)=>this.onClose(e,n.toString()))}redial(t){return this.destroy(),this.closeEmitted=!1,new Promise((e,n)=>{const r=this.dial();let i=!1;r.on("open",()=>{i||(i=!0,this.emit("connect"),e())}),r.on("message",o=>this.onMessage(o)),r.on("error",o=>{if(i){this.onError(o);return}i=!0;try{r.terminate()}catch{}n(o)}),r.on("close",(o,c)=>{i&&this.onClose(o,c.toString())})})}send(t){const e=this.ws;if(!(e===void 0||e.readyState!==h.OPEN))try{e.send(JSON.stringify(t))}catch{}}destroy(){const t=this.ws;if(t!==void 0&&t.readyState<=h.OPEN)try{t.close(1e3)}catch{}}onMessage(t){let e;try{e=JSON.parse(t.toString())}catch{return}this.emit("frame",e)}onError(t){this.emitError(new Error(`relay transport error: ${t.message}`))}onClose(t,e){if(this.closeEmitted)return;this.closeEmitted=!0;const n=m("viewer\u2194broker",{code:t,reason:e}),r=t===1008||n.kind==="protocol"||t===1011&&(/^no running broker/.test(e)||/^no node /.test(e));t===1008?this.emitError(new f(this.nodeId,e)):r&&this.emitError(new u(this.nodeId)),this.emit("close")}emitError(t){this.listenerCount("error")>0&&this.emit("error",t)}}export{S as RelayTransport};
@@ -32,5 +32,15 @@ export interface Transport extends EventEmitter {
32
32
  * focus/revive hint — same meaning regardless of which Transport raised it. */
33
33
  export declare class BrokerUnavailableError extends Error {
34
34
  readonly nodeId: string;
35
- constructor(nodeId: string);
35
+ constructor(nodeId: string, message?: string);
36
+ }
37
+ /** A crtrd attach that closed 1008: the daemon refused this client (revoked or
38
+ * missing permission for the hello's role, or an invalid node id) before the
39
+ * hello reached any broker. A BrokerUnavailableError too, so existing callers
40
+ * keep treating it as a terminal connect failure; a viewer that must tell a
41
+ * refusal from a broker that is merely restarting (a 1011 "no running broker")
42
+ * checks for this class. */
43
+ export declare class AttachRefusedError extends BrokerUnavailableError {
44
+ readonly reason: string;
45
+ constructor(nodeId: string, reason: string);
36
46
  }
@@ -1 +1 @@
1
- var n=Object.defineProperty;var o=(e,r)=>n(e,"name",{value:r,configurable:!0});class a extends Error{static{o(this,"BrokerUnavailableError")}nodeId;constructor(r){super(`node ${r} has no running broker \u2014 focus or revive it first`),this.nodeId=r,this.name="BrokerUnavailableError"}}export{a as BrokerUnavailableError};
1
+ var t=Object.defineProperty;var o=(s,r)=>t(s,"name",{value:r,configurable:!0});class a extends Error{static{o(this,"BrokerUnavailableError")}nodeId;constructor(r,e){super(e??`node ${r} has no running broker \u2014 focus or revive it first`),this.nodeId=r,this.name="BrokerUnavailableError"}}class c extends a{static{o(this,"AttachRefusedError")}reason;constructor(r,e){super(r,`the attach to node ${r} was refused by the canvas (${e||"no reason given"})`),this.reason=e,this.name="AttachRefusedError"}}export{c as AttachRefusedError,a as BrokerUnavailableError};
@@ -1,3 +1,3 @@
1
- var V=Object.defineProperty;var d=(i,a)=>V(i,"name",{value:a,configurable:!0});import{DatabaseSync as W}from"node:sqlite";import{existsSync as Y,mkdirSync as D,mkdtempSync as J,renameSync as X,rmSync as P}from"node:fs";import{tmpdir as Q}from"node:os";import{join as A}from"node:path";import j from"pg";import{migrate as z,MIGRATIONS as I}from"./migrations.js";import{canvasDbPath as Z}from"./paths.js";import{canvasSearchPathOptions as ee}from"./schema-version.js";import{EXPORT_MARK_KEY as F,IMPORT_MARK_KEY as te,OBJECT_COLUMNS as ne,pgColumns as se,quote as p,TABLE_ORDER as C}from"./import-sqlite.js";const oe=5e3,re=new Set(["environments","relations","schemas","charges","charge_totals","limits","viewer_attachments","reconciler_leases","environment_repos","conversation_entries","conversation_parts","charge_chunks","profiles","profile_projects","profile_defaults","scope_settings","scope_bookkeeping","scope_marketplaces","scope_plugins","person_settings","model_accounts","model_account_cooldowns","model_api_keys","model_settings","model_sign_in_flows","env_vars","import_runs","import_key_map","import_manifest","import_id_map","machine_links","machine_calls","machine_credentials","import_node_files","bodies","usage","grantee_usage","provider_receipts"]),ae={exposures:"node, object, channel",rules:"object, position",edges:"type, src, dst",unresolved_links:"src, name",revision_parents:"child, parent",canvas_meta:"key",runtime_state:"key",message_causes:"message_id",peer_ingested:"key",peer_ingested_destinations:"key",message_sequence:"id",messages:"seq",review_comment_events:"seq",audit_rows:"id",peer_outbox:"id",app_users:"uid"},ie={objects:"id",revisions:"rev_id",watches:"watch_id"},H=new Set(["daemon_owner","schema_version","schema_min_reader","ledger_migrated",te,F]);function ce(i){const a=J(A(Q(),"crtr-export-")),s=process.env.CRTR_HOME;process.env.CRTR_HOME=a,D(A(a,"runtime","nodes"),{recursive:!0});try{const o=new W(i);return o.exec("PRAGMA journal_mode = DELETE;"),o.exec("PRAGMA foreign_keys = ON;"),z(o),o.exec("DELETE FROM canvas_meta; DELETE FROM message_sequence;"),o}finally{s===void 0?delete process.env.CRTR_HOME:process.env.CRTR_HOME=s,P(a,{recursive:!0,force:!0})}}d(ce,"createEmptyDatabase");async function Re(i,a=()=>{}){const s=Z();if(Y(s))throw new Error(`${s} already exists; refusing to overwrite it`);D(A(s,".."),{recursive:!0,mode:448});const o=`${s}.exporting-${process.pid}`,u=Date.now(),t=new j.Client({connectionString:i,application_name:`crtr-export-${process.pid}`,options:ee(),types:{getTypeParser:d(((f,r)=>f===20?w=>Number(w):j.types.getTypeParser(f,r)),"getTypeParser")}});await t.connect();let _;try{if((await t.query("SELECT value FROM canvas_meta WHERE key = 'schema_version'").catch(()=>({rows:[]}))).rows.length===0)throw new Error("the Postgres database has no canvas schema; nothing to export");_=ce(o);const r=_;r.exec("PRAGMA foreign_keys = OFF;");const w=await se(t),L=[...w.keys()].filter(e=>!re.has(e)&&!C.includes(e));if(L.length>0)throw new Error(`export: Postgres tables with no export rule: ${L.join(", ")}`);await t.query("BEGIN READ ONLY ISOLATION LEVEL REPEATABLE READ");const E={};for(const[e,S]of Object.entries(ie)){const h=new Map;for(const l of(await t.query(`SELECT ${S} AS id, seq FROM ${e}`)).rows)h.set(l.id,Number(l.seq));E[e]=h}const B=E.objects;let M=0;r.exec("BEGIN");for(const e of C){const S=Date.now(),h=new Set(r.prepare(`PRAGMA table_info(${p(e)})`).all().map(n=>n.name)),l=w.get(e);if(l===void 0)throw new Error(`export: Postgres has no table ${e}`);const G=l.some(n=>n.name==="seq"),O=l.filter(n=>h.has(n.name)),K=new Set(ne[e]??[]),g=e==="objects"||e==="revisions"||e==="watches"||e==="watch_subscriptions",$=O.map(n=>n.type==="jsonb"?`${p(n.name)}::text AS ${p(n.name)}`:p(n.name));e==="watches"&&$.push("watch_id AS _watch_id"),g&&$.push("seq AS _seq");const R=O.map(n=>p(n.name)),U=r.prepare(`INSERT INTO ${p(e)} (${g?["rowid",...R]:R}) VALUES (${(g?["?",...R]:R).map(()=>"?").join(", ")})`),k=G?"seq":ae[e],b=`export_${e}`;await t.query(`DECLARE ${b} NO SCROLL CURSOR FOR SELECT ${$.join(", ")} FROM ${p(e)}${e==="canvas_meta"?` WHERE key NOT IN (${[...H].map(n=>`'${n}'`).join(", ")})`:""}${k===void 0?"":` ORDER BY ${k}`}`);let T=0;for(;;){const n=await t.query(`FETCH ${oe} FROM ${b}`);if(n.rows.length===0)break;for(const q of n.rows){const N=g?[Number(q._seq)]:[];for(const c of O){let m=q[c.name];e==="revision_parents"&&(c.name==="child"||c.name==="parent")?m=x(E.revisions,"revision",e,c.name,m):e==="watch_subscriptions"&&c.name==="watch"?m=x(E.watches,"watch",e,c.name,m):K.has(c.name)?m=x(B,"object",e,c.name,m):e==="jobs"&&(c.name==="job_dir"||c.name==="log_path")&&(m=m??""),N.push(m??null)}U.run(...N)}T+=n.rows.length}await t.query(`CLOSE ${b}`),M+=T,a(`export ${e}: ${T} rows in ${Date.now()-S} ms`)}r.exec("COMMIT"),await t.query("COMMIT");const y=r.prepare("PRAGMA foreign_key_check").all();if(y.length>0)throw new Error(`export: ${y.length} foreign key violation(s) in the exported canvas.db, first: ${JSON.stringify(y[0])}`);await me(t,r);const v=r.prepare("PRAGMA user_version").get().user_version;if(v!==I.length)throw new Error(`export: exported canvas.db is at user_version ${v}, expected ${I.length}`);return r.close(),_=void 0,await t.query("INSERT INTO canvas_meta (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value",[F,JSON.stringify({at:new Date().toISOString(),path:s})]),X(o,s),{path:s,rows:M,ms:Date.now()-u,userVersion:v}}catch(f){await t.query("ROLLBACK").catch(()=>{});try{_?.close()}catch{}for(const r of["","-wal","-shm","-journal"])P(`${o}${r}`,{force:!0});throw f}finally{await t.end().catch(()=>{})}}d(Re,"exportPostgresToSqlite");function x(i,a,s,o,u){if(u==null)return null;const t=i.get(u);if(t===void 0)throw new Error(`export: ${s}.${o} references ${a} ${String(u)}, which was not exported (`);return t}d(x,"mapId");async function me(i,a){const s=[];for(const o of C){const u=Number((await i.query(`SELECT count(*) AS n FROM ${p(o)}`)).rows[0].n);let t=Number(a.prepare(`SELECT count(*) AS n FROM ${p(o)}`).get().n),_=u;o==="canvas_meta"&&(_=Number((await i.query("SELECT count(*) AS n FROM canvas_meta WHERE key <> ALL($1)",[[...H]])).rows[0].n),t=Number(a.prepare("SELECT count(*) AS n FROM canvas_meta").get().n)),_!==t&&s.push(`${o}: Postgres ${_} rows, SQLite ${t}`)}if(s.length>0)throw new Error(`Postgres \u2192 SQLite export verification failed:
1
+ var V=Object.defineProperty;var d=(i,a)=>V(i,"name",{value:a,configurable:!0});import{DatabaseSync as W}from"node:sqlite";import{existsSync as Y,mkdirSync as D,mkdtempSync as J,renameSync as X,rmSync as P}from"node:fs";import{tmpdir as Q}from"node:os";import{join as A}from"node:path";import j from"pg";import{migrate as z,MIGRATIONS as I}from"./migrations.js";import{canvasDbPath as Z}from"./paths.js";import{canvasSearchPathOptions as ee}from"./schema-version.js";import{EXPORT_MARK_KEY as F,IMPORT_MARK_KEY as te,OBJECT_COLUMNS as ne,pgColumns as se,quote as m,TABLE_ORDER as C}from"./import-sqlite.js";const oe=5e3,re=new Set(["environments","relations","schemas","charges","charge_totals","limits","viewer_attachments","reconciler_leases","environment_repos","conversation_entries","conversation_parts","charge_chunks","profiles","profile_projects","profile_defaults","scope_settings","scope_bookkeeping","scope_marketplaces","scope_plugins","person_settings","model_accounts","model_account_cooldowns","model_api_keys","model_settings","model_sign_in_flows","env_vars","import_runs","import_key_map","import_manifest","import_id_map","machine_links","machine_calls","machine_credentials","import_node_files","bodies","package_publications","usage","grantee_usage","provider_receipts"]),ae={exposures:"node, object, channel",rules:"object, position",edges:"type, src, dst",unresolved_links:"src, name",revision_parents:"child, parent",canvas_meta:"key",runtime_state:"key",message_causes:"message_id",peer_ingested:"key",peer_ingested_destinations:"key",message_sequence:"id",messages:"seq",review_comment_events:"seq",audit_rows:"id",peer_outbox:"id",app_users:"uid"},ie={objects:"id",revisions:"rev_id",watches:"watch_id"},H=new Set(["daemon_owner","schema_version","schema_min_reader","ledger_migrated",te,F]);function ce(i){const a=J(A(Q(),"crtr-export-")),s=process.env.CRTR_HOME;process.env.CRTR_HOME=a,D(A(a,"runtime","nodes"),{recursive:!0});try{const o=new W(i);return o.exec("PRAGMA journal_mode = DELETE;"),o.exec("PRAGMA foreign_keys = ON;"),z(o),o.exec("DELETE FROM canvas_meta; DELETE FROM message_sequence;"),o}finally{s===void 0?delete process.env.CRTR_HOME:process.env.CRTR_HOME=s,P(a,{recursive:!0,force:!0})}}d(ce,"createEmptyDatabase");async function Re(i,a=()=>{}){const s=Z();if(Y(s))throw new Error(`${s} already exists; refusing to overwrite it`);D(A(s,".."),{recursive:!0,mode:448});const o=`${s}.exporting-${process.pid}`,u=Date.now(),t=new j.Client({connectionString:i,application_name:`crtr-export-${process.pid}`,options:ee(),types:{getTypeParser:d(((f,r)=>f===20?w=>Number(w):j.types.getTypeParser(f,r)),"getTypeParser")}});await t.connect();let _;try{if((await t.query("SELECT value FROM canvas_meta WHERE key = 'schema_version'").catch(()=>({rows:[]}))).rows.length===0)throw new Error("the Postgres database has no canvas schema; nothing to export");_=ce(o);const r=_;r.exec("PRAGMA foreign_keys = OFF;");const w=await se(t),L=[...w.keys()].filter(e=>!re.has(e)&&!C.includes(e));if(L.length>0)throw new Error(`export: Postgres tables with no export rule: ${L.join(", ")}`);await t.query("BEGIN READ ONLY ISOLATION LEVEL REPEATABLE READ");const E={};for(const[e,S]of Object.entries(ie)){const h=new Map;for(const l of(await t.query(`SELECT ${S} AS id, seq FROM ${e}`)).rows)h.set(l.id,Number(l.seq));E[e]=h}const B=E.objects;let M=0;r.exec("BEGIN");for(const e of C){const S=Date.now(),h=new Set(r.prepare(`PRAGMA table_info(${m(e)})`).all().map(n=>n.name)),l=w.get(e);if(l===void 0)throw new Error(`export: Postgres has no table ${e}`);const G=l.some(n=>n.name==="seq"),O=l.filter(n=>h.has(n.name)),K=new Set(ne[e]??[]),g=e==="objects"||e==="revisions"||e==="watches"||e==="watch_subscriptions",$=O.map(n=>n.type==="jsonb"?`${m(n.name)}::text AS ${m(n.name)}`:m(n.name));e==="watches"&&$.push("watch_id AS _watch_id"),g&&$.push("seq AS _seq");const R=O.map(n=>m(n.name)),U=r.prepare(`INSERT INTO ${m(e)} (${g?["rowid",...R]:R}) VALUES (${(g?["?",...R]:R).map(()=>"?").join(", ")})`),k=G?"seq":ae[e],b=`export_${e}`;await t.query(`DECLARE ${b} NO SCROLL CURSOR FOR SELECT ${$.join(", ")} FROM ${m(e)}${e==="canvas_meta"?` WHERE key NOT IN (${[...H].map(n=>`'${n}'`).join(", ")})`:""}${k===void 0?"":` ORDER BY ${k}`}`);let T=0;for(;;){const n=await t.query(`FETCH ${oe} FROM ${b}`);if(n.rows.length===0)break;for(const q of n.rows){const N=g?[Number(q._seq)]:[];for(const c of O){let p=q[c.name];e==="revision_parents"&&(c.name==="child"||c.name==="parent")?p=x(E.revisions,"revision",e,c.name,p):e==="watch_subscriptions"&&c.name==="watch"?p=x(E.watches,"watch",e,c.name,p):K.has(c.name)?p=x(B,"object",e,c.name,p):e==="jobs"&&(c.name==="job_dir"||c.name==="log_path")&&(p=p??""),N.push(p??null)}U.run(...N)}T+=n.rows.length}await t.query(`CLOSE ${b}`),M+=T,a(`export ${e}: ${T} rows in ${Date.now()-S} ms`)}r.exec("COMMIT"),await t.query("COMMIT");const y=r.prepare("PRAGMA foreign_key_check").all();if(y.length>0)throw new Error(`export: ${y.length} foreign key violation(s) in the exported canvas.db, first: ${JSON.stringify(y[0])}`);await pe(t,r);const v=r.prepare("PRAGMA user_version").get().user_version;if(v!==I.length)throw new Error(`export: exported canvas.db is at user_version ${v}, expected ${I.length}`);return r.close(),_=void 0,await t.query("INSERT INTO canvas_meta (key, value) VALUES ($1, $2) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value",[F,JSON.stringify({at:new Date().toISOString(),path:s})]),X(o,s),{path:s,rows:M,ms:Date.now()-u,userVersion:v}}catch(f){await t.query("ROLLBACK").catch(()=>{});try{_?.close()}catch{}for(const r of["","-wal","-shm","-journal"])P(`${o}${r}`,{force:!0});throw f}finally{await t.end().catch(()=>{})}}d(Re,"exportPostgresToSqlite");function x(i,a,s,o,u){if(u==null)return null;const t=i.get(u);if(t===void 0)throw new Error(`export: ${s}.${o} references ${a} ${String(u)}, which was not exported (`);return t}d(x,"mapId");async function pe(i,a){const s=[];for(const o of C){const u=Number((await i.query(`SELECT count(*) AS n FROM ${m(o)}`)).rows[0].n);let t=Number(a.prepare(`SELECT count(*) AS n FROM ${m(o)}`).get().n),_=u;o==="canvas_meta"&&(_=Number((await i.query("SELECT count(*) AS n FROM canvas_meta WHERE key <> ALL($1)",[[...H]])).rows[0].n),t=Number(a.prepare("SELECT count(*) AS n FROM canvas_meta").get().n)),_!==t&&s.push(`${o}: Postgres ${_} rows, SQLite ${t}`)}if(s.length>0)throw new Error(`Postgres \u2192 SQLite export verification failed:
2
2
  ${s.join(`
3
- `)}`)}d(me,"verify");export{Re as exportPostgresToSqlite};
3
+ `)}`)}d(pe,"verify");export{Re as exportPostgresToSqlite};
@@ -0,0 +1,16 @@
1
+ -- 010-package-publications.sql — cycle 5 package publisher (design-package-publisher "Data model"). One transaction, additions only:
2
+ -- `schema_min_reader` is not touched and stays 2. The previous build (version 9) opens the result and never names the table.
3
+ --
4
+ -- One row per published app: the lock a publisher takes (`SELECT … FOR UPDATE`), the version a rolling deploy compares against,
5
+ -- and the digest that lets an unchanged start write nothing. `origin` already allows the three kinds of package the publisher
6
+ -- will carry; this release writes only `builtin` (app `crtr`). Timestamps are text, as in 002.
7
+ CREATE TABLE package_publications (
8
+ app text PRIMARY KEY REFERENCES objects(id) ON DELETE CASCADE,
9
+ origin text NOT NULL CHECK (origin IN ('builtin', 'provider', 'plugin')),
10
+ version text NOT NULL,
11
+ fetched_at text,
12
+ digest text NOT NULL,
13
+ published_at text NOT NULL,
14
+ published_by text NOT NULL,
15
+ withdrawn_at text
16
+ );
@@ -1,7 +1,7 @@
1
1
  /** The canvas schema version this build supports: the number of the highest delta in `schema-pg/` (the baseline
2
2
  * `schema-pg.sql` is version 1). `scripts/install-runtime.mjs` reads this module from a candidate generation to refuse
3
3
  * one below the live database's `schema_min_reader`. */
4
- export declare const CANVAS_SCHEMA_VERSION = 9;
4
+ export declare const CANVAS_SCHEMA_VERSION = 10;
5
5
  /** The Postgres schema holding the canvas tables: `CRTR_DATABASE_SCHEMA` (`crtr` in the cloud deploy). Unset or empty
6
6
  * means the role's own search_path (`public` on a Mac and on a VM). A name that is not a plain identifier is an error:
7
7
  * it is put in a `search_path` connection option. */
@@ -1 +1 @@
1
- var m=Object.defineProperty;var r=(n,e)=>m(n,"name",{value:e,configurable:!0});const A=9;function _(n=process.env){const e=n.CRTR_DATABASE_SCHEMA?.trim();if(e){if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(e))throw new Error(`CRTR_DATABASE_SCHEMA must be a plain identifier (letters, digits, underscore); got ${JSON.stringify(e)}`);return e}}r(_,"canvasDatabaseSchema");function h(n=process.env){return n.CRTR_SCHEMA_DATABASE_URL?.trim()||void 0}r(h,"canvasSchemaDatabaseUrl");function E(n=process.env){const e=_(n);return e===void 0?void 0:`-c search_path=${e}`}r(E,"canvasSearchPathOptions");async function v(n,e){const t=e===void 0?"canvas_meta":`"${e.replace(/"/g,'""')}".canvas_meta`;if((await n.query(`SELECT to_regclass('${t.replace(/'/g,"''")}') IS NOT NULL AS found`)).rows[0].found!==!0)return;const u=(await n.query(`SELECT key, value FROM ${t} WHERE key IN ('schema_version', 'schema_min_reader')`)).rows,o=r(c=>{const i=u.find(f=>f.key===c);if(i===void 0)return;const d=Number(i.value);if(!Number.isSafeInteger(d))throw new Error(`canvas_meta.${c} is not an integer: ${String(i.value)}`);return d},"value"),s=o("schema_version");return s===void 0?void 0:{version:s,minReader:o("schema_min_reader")??s}}r(v,"readStoredVersions");async function l(n,e,t){const a=await v(n,t);if(a!==void 0&&a.minReader>e)throw new Error(`refusing to select runtime with canvas schema ${e}: the live database requires readers at version ${a.minReader} or above (it is at ${a.version}); install a descendant build instead`)}r(l,"assertCandidateMayOpen");export{A as CANVAS_SCHEMA_VERSION,l as assertCandidateMayOpen,_ as canvasDatabaseSchema,h as canvasSchemaDatabaseUrl,E as canvasSearchPathOptions,v as readStoredVersions};
1
+ var m=Object.defineProperty;var r=(n,e)=>m(n,"name",{value:e,configurable:!0});const A=10;function _(n=process.env){const e=n.CRTR_DATABASE_SCHEMA?.trim();if(e){if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(e))throw new Error(`CRTR_DATABASE_SCHEMA must be a plain identifier (letters, digits, underscore); got ${JSON.stringify(e)}`);return e}}r(_,"canvasDatabaseSchema");function h(n=process.env){return n.CRTR_SCHEMA_DATABASE_URL?.trim()||void 0}r(h,"canvasSchemaDatabaseUrl");function E(n=process.env){const e=_(n);return e===void 0?void 0:`-c search_path=${e}`}r(E,"canvasSearchPathOptions");async function v(n,e){const t=e===void 0?"canvas_meta":`"${e.replace(/"/g,'""')}".canvas_meta`;if((await n.query(`SELECT to_regclass('${t.replace(/'/g,"''")}') IS NOT NULL AS found`)).rows[0].found!==!0)return;const u=(await n.query(`SELECT key, value FROM ${t} WHERE key IN ('schema_version', 'schema_min_reader')`)).rows,o=r(c=>{const i=u.find(f=>f.key===c);if(i===void 0)return;const d=Number(i.value);if(!Number.isSafeInteger(d))throw new Error(`canvas_meta.${c} is not an integer: ${String(i.value)}`);return d},"value"),s=o("schema_version");return s===void 0?void 0:{version:s,minReader:o("schema_min_reader")??s}}r(v,"readStoredVersions");async function l(n,e,t){const a=await v(n,t);if(a!==void 0&&a.minReader>e)throw new Error(`refusing to select runtime with canvas schema ${e}: the live database requires readers at version ${a.minReader} or above (it is at ${a.version}); install a descendant build instead`)}r(l,"assertCandidateMayOpen");export{A as CANVAS_SCHEMA_VERSION,l as assertCandidateMayOpen,_ as canvasDatabaseSchema,h as canvasSchemaDatabaseUrl,E as canvasSearchPathOptions,v as readStoredVersions};
@@ -1 +1 @@
1
- var W=Object.defineProperty;var s=(n,e)=>W(n,"name",{value:e,configurable:!0});import{closeSync as X,constants as O,fstatSync as G,lstatSync as L,openSync as H,opendirSync as J,readSync as Q}from"node:fs";import{isAbsolute as Z,join as S,relative as N,resolve as I,sep as q}from"node:path";import{crtrHome as ee,isSafeNodeId as C,nodesRoot as ne}from"../canvas/paths.js";import{validateOperationId as A}from"./operation-id.js";import{MAX_EVENT_RECORD_BYTES as te}from"./serialize.js";import{errorClassCodes as re}from"./types.js";import{isRecord as E}from"../../shared/predicates.js";const k=200,ie=6*1024*1024,oe=256*1024,se=te-1,de=typeof O.O_NOFOLLOW=="number"?O.O_NOFOLLOW:0,fe=/^[a-z][a-z0-9]*(?:[._][a-z0-9]+)*$/,ae=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/,ce=/^\d{4}-\d{2}-\d{2}T/,ue=new Set(["nodeId","component","operationId","sinceMs","untilMs","minimumLevel","pattern"]),me=new Set(["class","status","provider","retry_disposition"]),le=new Set(["type","message","stack","cause"]),pe=new Set(["v","ts","level","event","component","operation_id","node_id","stream_id","duration_ms","outcome","attempt","error_class","error","fields","record_truncated","original_bytes","dropped_fields_count"]),R=new Set(["debug","info","warn","error"]),F=new Set(["daemon","broker"]),ye=new Set(["succeeded","failed","skipped"]),_e=new Set(["auto","manual","fatal"]),be=new Set(re),$={debug:0,info:1,warn:2,error:3},Se={2:0,1:1,0:2};function m(n,e){return Object.prototype.hasOwnProperty.call(n,e)}s(m,"hasOwn");function h(n,e){return Object.keys(n).every(t=>e.has(t))}s(h,"hasOnlyKeys");function y(n,e,t=!1){return typeof n=="string"&&(!t||n!=="")&&Buffer.byteLength(n,"utf8")<=e}s(y,"boundedString");function we(n){if(typeof n!="object"||n===null||Array.isArray(n))throw new TypeError("event read query is invalid");const e=n;if(!h(e,ue))throw new TypeError("event read query is invalid");const t={};if(e.nodeId!==void 0){if(typeof e.nodeId!="string"||!C(e.nodeId))throw new TypeError("nodeId must be a safe non-empty path segment");t.nodeId=e.nodeId}if(e.component!==void 0){if(typeof e.component!="string"||!F.has(e.component))throw new TypeError("component is invalid");t.component=e.component}if(e.operationId!==void 0&&(t.operationId=A(e.operationId)),e.sinceMs!==void 0){if(typeof e.sinceMs!="number"||!Number.isFinite(e.sinceMs))throw new TypeError("sinceMs must be finite");t.sinceMs=e.sinceMs}if(e.untilMs!==void 0){if(typeof e.untilMs!="number"||!Number.isFinite(e.untilMs))throw new TypeError("untilMs must be finite");t.untilMs=e.untilMs}if(t.sinceMs!==void 0&&t.untilMs!==void 0&&t.sinceMs>t.untilMs)throw new TypeError("sinceMs cannot exceed untilMs");if(e.minimumLevel!==void 0){if(typeof e.minimumLevel!="string"||!R.has(e.minimumLevel))throw new TypeError("minimumLevel is invalid");t.minimumLevel=e.minimumLevel}if(e.pattern!==void 0){if(!y(e.pattern,4096,!0))throw new TypeError("pattern must be a non-empty string of at most 4,096 UTF-8 bytes");t.pattern=e.pattern}return t}s(we,"normalizeQuery");function v(n,e){return n.component===void 0||n.component===e}s(v,"selected");function g(n){return{dev:n.dev,ino:n.ino}}s(g,"identityOf");function x(n,e){return n.dev===e.dev&&n.ino===e.ino}s(x,"sameIdentity");function Ee(n,e){if(n.size<0n||n.size>BigInt(Number.MAX_SAFE_INTEGER))throw new Error(`event source has an unsafe size: ${e}`);return Number(n.size)}s(Ee,"safeSize");function B(n){return n?.code==="ENOENT"}s(B,"isMissing");function j(n,e){const t=N(n,e);return t!==""&&!Z(t)&&t!==".."&&!t.startsWith(`..${q}`)}s(j,"isContained");function b(n){for(const e of n){let t;try{t=L(e.absolute,{bigint:!0})}catch(r){throw new Error(`event source directory changed: ${e.absolute}`,{cause:r})}if(t.isSymbolicLink()||!t.isDirectory()||!x(e,g(t)))throw new Error(`event source directory is unsafe: ${e.absolute}`)}}s(b,"validateDirectories");function M(n,e,t,r){if(t.length>0&&!j(n,e))throw new Error(`event source directory escapes its home: ${e}`);b(t);let i;try{i=L(e,{bigint:!0})}catch(o){if(r&&B(o))return;throw new Error(`event source directory is unavailable: ${e}`,{cause:o})}if(i.isSymbolicLink()||!i.isDirectory())throw new Error(`event source directory is unsafe: ${e}`);return b(t),{absolute:e,...g(i)}}s(M,"proveDirectory");function he(n,e){b([n]);let t;try{t=J(n.absolute)}catch(r){throw new Error(`event source directory cannot be opened: ${n.absolute}`,{cause:r})}try{for(;;){const r=t.readSync();if(r===null)break;e(r.name,r.isDirectory())}}finally{t.closeSync()}b([n])}s(he,"forEachDirectoryEntry");function ge(n,e,t){let r=0;for(;r<e.length;){const i=Q(n,e,r,e.length-r,t+r);if(i===0)throw new Error("event source changed during read");r+=i}}s(ge,"readExact");function z(n,e,t,r){if(!j(n,t))throw new Error(`event source escapes its home: ${t}`);b(e);let i;try{i=L(t,{bigint:!0})}catch(d){if(B(d))return;throw new Error(`event source is unavailable: ${t}`,{cause:d})}if(i.isSymbolicLink()||!i.isFile())throw new Error(`event source is unsafe: ${t}`);b(e);let o;try{o=H(t,O.O_RDONLY|de)}catch(d){throw new Error(`event source cannot be opened safely: ${t}`,{cause:d})}try{const d=G(o,{bigint:!0});if(!d.isFile()||!x(g(i),g(d)))throw new Error(`event source changed before read: ${t}`);b(e);const f=Ee(d,t),c=Math.min(f,r),a=f-c,l=Buffer.allocUnsafe(c);ge(o,l,a);const u=Number(d.mtimeMs);return{bytes:l,start:a,mtimeMs:Number.isFinite(u)?u:0}}finally{X(o)}}s(z,"readOwnedFile");function Me(n){return!E(n)||!h(n,me)||typeof n.class!="string"||!be.has(n.class)||m(n,"status")&&(typeof n.status!="number"||!Number.isSafeInteger(n.status))||m(n,"provider")&&!y(n.provider,128,!0)?!1:!m(n,"retry_disposition")||typeof n.retry_disposition=="string"&&_e.has(n.retry_disposition)}s(Me,"validErrorClass");function Oe(n){return!E(n)||!h(n,le)||!y(n.type,128,!0)||!y(n.message,4096)||m(n,"stack")&&!y(n.stack,16384)?!1:!m(n,"cause")||y(n.cause,4096)}s(Oe,"validError");function T(n,e,t){if(t.remaining<=0)return!1;if(t.remaining-=1,n===null||typeof n=="boolean")return!0;if(typeof n=="number")return Number.isFinite(n);if(typeof n=="string")return Buffer.byteLength(n,"utf8")<=4096;if(e>=4||typeof n!="object"||n===null)return!1;if(Array.isArray(n))return n.length<=64&&n.every(i=>T(i,e+1,t));const r=Object.entries(n);return r.length<=64&&r.every(([i,o])=>Buffer.byteLength(i,"utf8")<=128&&T(o,e+1,t))}s(T,"validEventField");function D(n){return typeof n=="number"&&Number.isSafeInteger(n)&&n>=0}s(D,"nonnegativeInteger");function Le(n){if(n.length===0||n.length>se||n.includes(13))return;let e;try{e=JSON.parse(n.toString("utf8"))}catch{return}if(!E(e)||!h(e,pe)||e.v!==1||!y(e.ts,128,!0)||!ae.test(e.ts)||!y(e.event,128,!0)||!fe.test(e.event)||typeof e.level!="string"||!R.has(e.level)||typeof e.component!="string"||!F.has(e.component))return;const t=Date.parse(e.ts);if(!Number.isFinite(t)||new Date(t).toISOString()!==e.ts)return;let r;try{r=A(e.operation_id)}catch{return}const i={v:1,ts:e.ts,level:e.level,event:e.event,component:e.component,operation_id:r};if(m(e,"node_id")){if(!y(e.node_id,128,!0))return;i.node_id=e.node_id}if(m(e,"stream_id")){if(!y(e.stream_id,128,!0))return;i.stream_id=e.stream_id}if(m(e,"duration_ms")){if(typeof e.duration_ms!="number"||!Number.isFinite(e.duration_ms)||e.duration_ms<0)return;i.duration_ms=e.duration_ms}if(m(e,"outcome")){if(typeof e.outcome!="string"||!ye.has(e.outcome))return;i.outcome=e.outcome}if(m(e,"attempt")){if(typeof e.attempt!="number"||!Number.isSafeInteger(e.attempt)||e.attempt<=0)return;i.attempt=e.attempt}if(m(e,"error_class")){if(!Me(e.error_class))return;i.error_class=e.error_class}if(m(e,"error")){if(!Oe(e.error))return;i.error=e.error}if(m(e,"fields")){if(!E(e.fields)||!T(e.fields,0,{remaining:5e3}))return;i.fields=e.fields}const o=m(e,"record_truncated"),d=m(e,"original_bytes"),f=m(e,"dropped_fields_count");if(o){if(e.record_truncated!==!0||!d||!f||!D(e.original_bytes)||!D(e.dropped_fields_count))return;i.record_truncated=!0,i.original_bytes=e.original_bytes,i.dropped_fields_count=e.dropped_fields_count}else if(d||f)return;return{envelope:i,effectiveTsMs:t}}s(Le,"parseCanonicalLine");function Ne(n,e){return e.component!==n.component?!1:n.component==="daemon"?e.stream_id===void 0:n.component==="broker"?e.node_id===n.boundNodeId&&e.stream_id===void 0:e.stream_id===n.boundStreamId}s(Ne,"descriptorMatches");function ve(n,e,t){const{envelope:r,effectiveTsMs:i}=t;return!(n.nodeId!==void 0&&e.component!=="broker"&&r.node_id!==n.nodeId||n.operationId!==void 0&&r.operation_id!==n.operationId||n.sinceMs!==void 0&&i<n.sinceMs||n.untilMs!==void 0&&i>n.untilMs||n.minimumLevel!==void 0&&$[r.level]<$[n.minimumLevel]||n.pattern!==void 0&&!JSON.stringify(r).toLocaleLowerCase("en-US").includes(n.pattern.toLocaleLowerCase("en-US")))}s(ve,"matchesCanonical");function K(n,e){return n.item.effectiveTsMs!==e.item.effectiveTsMs?n.item.effectiveTsMs-e.item.effectiveTsMs:n.orderSlot!==e.orderSlot?n.orderSlot<e.orderSlot?-1:1:n.generationRank!==e.generationRank?n.generationRank-e.generationRank:n.byteOffset-e.byteOffset}s(K,"compareCandidates");function Y(n,e){const t=n.candidates;if(t.length===k&&K(e,t[0])<=0)return;let r=0,i=t.length;for(;r<i;){const o=Math.floor((r+i)/2);K(t[o],e)<=0?r=o+1:i=o}t.splice(r,0,e),t.length>k&&t.shift()}s(Y,"retainCandidate");function Te(n){for(const e of n)if(e!==32&&e!==9&&e!==13)return!0;return!1}s(Te,"hasNonblank");function Ie(n){return n.generation===0?n.activeAbsolute:`${n.activeAbsolute}.${n.generation}`}s(Ie,"sourcePath");function Ce(n,e,t,r){const i=Ie(r),o=z(e,t,i,ie);if(o===void 0)return;const d=N(e,i);let f=0;const c=o.start>0;let a=!1;const l=[];if(c){const u=o.bytes.indexOf(10);if(u<0)return;f=u+1}for(;;){const u=o.bytes.indexOf(10,f);if(u<0)break;const p=Le(o.bytes.subarray(f,u));if(p===void 0||!Ne(r,p.envelope))n.invalidCanonicalLines+=1,a=!0;else if(ve(n.query,r,p)){const _=o.start+f,w={kind:"event",envelope:p.envelope,source:d,streamKey:r.streamKey,generation:r.generation,byteOffset:_,effectiveTsMs:p.effectiveTsMs,truncated:!1};l.push(w),Y(n,{item:w,orderSlot:r.streamKey,generationRank:Se[r.generation],byteOffset:_})}f=u+1}if(f<o.bytes.length&&Te(o.bytes.subarray(f))&&(n.invalidCanonicalLines+=1,a=!0),c||a)for(const u of l)u.truncated=!0}s(Ce,"scanCanonicalSource");function Ae(n,e){const t=/^(\S+)/u.exec(n)?.[1];if(t!==void 0&&ce.test(t)){const r=Date.parse(t);if(Number.isFinite(r))return r}return e}s(Ae,"rawTimestamp");function ke(n,e,t,r){return n.nodeId!==void 0&&e==="daemon"&&!t.includes(n.nodeId)||n.sinceMs!==void 0&&r<n.sinceMs||n.untilMs!==void 0&&r>n.untilMs?!1:n.pattern===void 0||t.toLocaleLowerCase("en-US").includes(n.pattern.toLocaleLowerCase("en-US"))}s(ke,"matchesRaw");function U(n,e,t,r,i){const o=z(e,t,r,oe);if(o===void 0)return;const d=N(e,r),f=o.start>0;let c=0;if(f){const a=o.bytes.indexOf(10);if(a<0)return;c=a+1}for(;;){const a=o.bytes.indexOf(10,c);if(a<0&&c===o.bytes.length)return;const l=a<0?o.bytes.length:a,u=o.bytes.subarray(c,l).toString("utf8"),p=Ae(u,o.mtimeMs);if(ke(n.query,i,u,p)){const _=o.start+c;Y(n,{item:{kind:"raw",component:i,source:d,content:u,effectiveTsMs:p,truncated:f},orderSlot:d,generationRank:2,byteOffset:_})}if(a<0)return;c=a+1}}s(U,"scanRawSource");function P(n,e,t,r){for(const i of[2,1,0])Ce(n,e,t,{...r,generation:i})}s(P,"scanCanonicalGenerations");function V(n,e,t,r,i,o){const d=S(t[t.length-1].absolute,r),f=M(e,d,t,!0);if(f===void 0)return;const c=S(d,"job"),a=M(e,c,[...t,f],!0);if(a===void 0)return;const l=[...t,f,a];i&&P(n,e,l,{component:"broker",streamKey:`broker:${r}`,activeAbsolute:S(c,"log.jsonl"),boundNodeId:r}),o&&U(n,e,l,S(c,"broker.log"),"broker")}s(V,"scanBrokerNode");function Ke(n,e){const t=we(n);if(typeof e!="boolean")throw new TypeError("includeRaw must be boolean");const r={query:t,candidates:[],invalidCanonicalLines:0},i=I(ee()),o=M(i,i,[],!0);if(o===void 0)return{items:[],invalidCanonicalLines:0};const d=[o],f=e&&t.operationId===void 0&&t.minimumLevel===void 0;v(t,"daemon")&&(P(r,i,d,{component:"daemon",streamKey:"daemon",activeAbsolute:S(i,"crtrd.log")}),f&&U(r,i,d,S(i,"crtrd.err"),"daemon"));const c=v(t,"broker"),a=f&&v(t,"broker");if(c||a){const l=I(ne()),u=M(i,l,d,!0);if(u!==void 0){const p=[o,u];t.nodeId!==void 0?V(r,i,p,t.nodeId,c,a):he(u,(_,w)=>{w&&C(_)&&V(r,i,p,_,c,a)})}}return{items:r.candidates.map(l=>l.item),invalidCanonicalLines:r.invalidCanonicalLines}}s(Ke,"readLogSnapshot");function Ye(n){const e={v:n.v,ts:n.ts,level:n.level,event:n.event,component:n.component,operation_id:n.operation_id};return n.node_id!==void 0&&(e.node_id=n.node_id),n.stream_id!==void 0&&(e.stream_id=n.stream_id),n.duration_ms!==void 0&&(e.duration_ms=n.duration_ms),n.outcome!==void 0&&(e.outcome=n.outcome),n.attempt!==void 0&&(e.attempt=n.attempt),n.error_class!==void 0&&(e.error_class=n.error_class),e}s(Ye,"projectTier1Event");export{Ye as projectTier1Event,Ke as readLogSnapshot};
1
+ var X=Object.defineProperty;var s=(n,e)=>X(n,"name",{value:e,configurable:!0});import{closeSync as W,constants as O,fstatSync as G,lstatSync as L,openSync as H,opendirSync as J,readSync as Q}from"node:fs";import{isAbsolute as Z,join as S,relative as N,resolve as I,sep as q}from"node:path";import{crtrHome as ee,isSafeNodeId as A,nodesRoot as ne}from"../canvas/paths.js";import{validateOperationId as C}from"./operation-id.js";import{MAX_EVENT_RECORD_BYTES as te}from"./serialize.js";import{errorClassCodes as re}from"./types.js";import{isRecord as w}from"../../shared/predicates.js";import{MAX_DAEMON_ACTIVE_BYTES as ie}from"../log.js";const k=200,oe=ie+1024*1024,se=256*1024,de=te-1,fe=typeof O.O_NOFOLLOW=="number"?O.O_NOFOLLOW:0,ae=/^[a-z][a-z0-9]*(?:[._][a-z0-9]+)*$/,ce=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/,ue=/^\d{4}-\d{2}-\d{2}T/,me=new Set(["nodeId","component","operationId","sinceMs","untilMs","minimumLevel","pattern"]),le=new Set(["class","status","provider","retry_disposition"]),pe=new Set(["type","message","stack","cause"]),_e=new Set(["v","ts","level","event","component","operation_id","node_id","stream_id","duration_ms","outcome","attempt","error_class","error","fields","record_truncated","original_bytes","dropped_fields_count"]),R=new Set(["debug","info","warn","error"]),F=new Set(["daemon","broker"]),ye=new Set(["succeeded","failed","skipped"]),be=new Set(["auto","manual","fatal"]),Se=new Set(re),$={debug:0,info:1,warn:2,error:3},Ee={2:0,1:1,0:2};function m(n,e){return Object.prototype.hasOwnProperty.call(n,e)}s(m,"hasOwn");function h(n,e){return Object.keys(n).every(t=>e.has(t))}s(h,"hasOnlyKeys");function _(n,e,t=!1){return typeof n=="string"&&(!t||n!=="")&&Buffer.byteLength(n,"utf8")<=e}s(_,"boundedString");function we(n){if(typeof n!="object"||n===null||Array.isArray(n))throw new TypeError("event read query is invalid");const e=n;if(!h(e,me))throw new TypeError("event read query is invalid");const t={};if(e.nodeId!==void 0){if(typeof e.nodeId!="string"||!A(e.nodeId))throw new TypeError("nodeId must be a safe non-empty path segment");t.nodeId=e.nodeId}if(e.component!==void 0){if(typeof e.component!="string"||!F.has(e.component))throw new TypeError("component is invalid");t.component=e.component}if(e.operationId!==void 0&&(t.operationId=C(e.operationId)),e.sinceMs!==void 0){if(typeof e.sinceMs!="number"||!Number.isFinite(e.sinceMs))throw new TypeError("sinceMs must be finite");t.sinceMs=e.sinceMs}if(e.untilMs!==void 0){if(typeof e.untilMs!="number"||!Number.isFinite(e.untilMs))throw new TypeError("untilMs must be finite");t.untilMs=e.untilMs}if(t.sinceMs!==void 0&&t.untilMs!==void 0&&t.sinceMs>t.untilMs)throw new TypeError("sinceMs cannot exceed untilMs");if(e.minimumLevel!==void 0){if(typeof e.minimumLevel!="string"||!R.has(e.minimumLevel))throw new TypeError("minimumLevel is invalid");t.minimumLevel=e.minimumLevel}if(e.pattern!==void 0){if(!_(e.pattern,4096,!0))throw new TypeError("pattern must be a non-empty string of at most 4,096 UTF-8 bytes");t.pattern=e.pattern}return t}s(we,"normalizeQuery");function T(n,e){return n.component===void 0||n.component===e}s(T,"selected");function g(n){return{dev:n.dev,ino:n.ino}}s(g,"identityOf");function x(n,e){return n.dev===e.dev&&n.ino===e.ino}s(x,"sameIdentity");function he(n,e){if(n.size<0n||n.size>BigInt(Number.MAX_SAFE_INTEGER))throw new Error(`event source has an unsafe size: ${e}`);return Number(n.size)}s(he,"safeSize");function B(n){return n?.code==="ENOENT"}s(B,"isMissing");function D(n,e){const t=N(n,e);return t!==""&&!Z(t)&&t!==".."&&!t.startsWith(`..${q}`)}s(D,"isContained");function b(n){for(const e of n){let t;try{t=L(e.absolute,{bigint:!0})}catch(r){throw new Error(`event source directory changed: ${e.absolute}`,{cause:r})}if(t.isSymbolicLink()||!t.isDirectory()||!x(e,g(t)))throw new Error(`event source directory is unsafe: ${e.absolute}`)}}s(b,"validateDirectories");function M(n,e,t,r){if(t.length>0&&!D(n,e))throw new Error(`event source directory escapes its home: ${e}`);b(t);let i;try{i=L(e,{bigint:!0})}catch(o){if(r&&B(o))return;throw new Error(`event source directory is unavailable: ${e}`,{cause:o})}if(i.isSymbolicLink()||!i.isDirectory())throw new Error(`event source directory is unsafe: ${e}`);return b(t),{absolute:e,...g(i)}}s(M,"proveDirectory");function ge(n,e){b([n]);let t;try{t=J(n.absolute)}catch(r){throw new Error(`event source directory cannot be opened: ${n.absolute}`,{cause:r})}try{for(;;){const r=t.readSync();if(r===null)break;e(r.name,r.isDirectory())}}finally{t.closeSync()}b([n])}s(ge,"forEachDirectoryEntry");function Me(n,e,t){let r=0;for(;r<e.length;){const i=Q(n,e,r,e.length-r,t+r);if(i===0)throw new Error("event source changed during read");r+=i}}s(Me,"readExact");function j(n,e,t,r){if(!D(n,t))throw new Error(`event source escapes its home: ${t}`);b(e);let i;try{i=L(t,{bigint:!0})}catch(d){if(B(d))return;throw new Error(`event source is unavailable: ${t}`,{cause:d})}if(i.isSymbolicLink()||!i.isFile())throw new Error(`event source is unsafe: ${t}`);b(e);let o;try{o=H(t,O.O_RDONLY|fe)}catch(d){throw new Error(`event source cannot be opened safely: ${t}`,{cause:d})}try{const d=G(o,{bigint:!0});if(!d.isFile()||!x(g(i),g(d)))throw new Error(`event source changed before read: ${t}`);b(e);const f=he(d,t),c=Math.min(f,r),a=f-c,l=Buffer.allocUnsafe(c);Me(o,l,a);const u=Number(d.mtimeMs);return{bytes:l,start:a,mtimeMs:Number.isFinite(u)?u:0}}finally{W(o)}}s(j,"readOwnedFile");function Oe(n){return!w(n)||!h(n,le)||typeof n.class!="string"||!Se.has(n.class)||m(n,"status")&&(typeof n.status!="number"||!Number.isSafeInteger(n.status))||m(n,"provider")&&!_(n.provider,128,!0)?!1:!m(n,"retry_disposition")||typeof n.retry_disposition=="string"&&be.has(n.retry_disposition)}s(Oe,"validErrorClass");function Le(n){return!w(n)||!h(n,pe)||!_(n.type,128,!0)||!_(n.message,4096)||m(n,"stack")&&!_(n.stack,16384)?!1:!m(n,"cause")||_(n.cause,4096)}s(Le,"validError");function v(n,e,t){if(t.remaining<=0)return!1;if(t.remaining-=1,n===null||typeof n=="boolean")return!0;if(typeof n=="number")return Number.isFinite(n);if(typeof n=="string")return Buffer.byteLength(n,"utf8")<=4096;if(e>=4||typeof n!="object"||n===null)return!1;if(Array.isArray(n))return n.length<=64&&n.every(i=>v(i,e+1,t));const r=Object.entries(n);return r.length<=64&&r.every(([i,o])=>Buffer.byteLength(i,"utf8")<=128&&v(o,e+1,t))}s(v,"validEventField");function z(n){return typeof n=="number"&&Number.isSafeInteger(n)&&n>=0}s(z,"nonnegativeInteger");function Ne(n){if(n.length===0||n.length>de||n.includes(13))return;let e;try{e=JSON.parse(n.toString("utf8"))}catch{return}if(!w(e)||!h(e,_e)||e.v!==1||!_(e.ts,128,!0)||!ce.test(e.ts)||!_(e.event,128,!0)||!ae.test(e.event)||typeof e.level!="string"||!R.has(e.level)||typeof e.component!="string"||!F.has(e.component))return;const t=Date.parse(e.ts);if(!Number.isFinite(t)||new Date(t).toISOString()!==e.ts)return;let r;try{r=C(e.operation_id)}catch{return}const i={v:1,ts:e.ts,level:e.level,event:e.event,component:e.component,operation_id:r};if(m(e,"node_id")){if(!_(e.node_id,128,!0))return;i.node_id=e.node_id}if(m(e,"stream_id")){if(!_(e.stream_id,128,!0))return;i.stream_id=e.stream_id}if(m(e,"duration_ms")){if(typeof e.duration_ms!="number"||!Number.isFinite(e.duration_ms)||e.duration_ms<0)return;i.duration_ms=e.duration_ms}if(m(e,"outcome")){if(typeof e.outcome!="string"||!ye.has(e.outcome))return;i.outcome=e.outcome}if(m(e,"attempt")){if(typeof e.attempt!="number"||!Number.isSafeInteger(e.attempt)||e.attempt<=0)return;i.attempt=e.attempt}if(m(e,"error_class")){if(!Oe(e.error_class))return;i.error_class=e.error_class}if(m(e,"error")){if(!Le(e.error))return;i.error=e.error}if(m(e,"fields")){if(!w(e.fields)||!v(e.fields,0,{remaining:5e3}))return;i.fields=e.fields}const o=m(e,"record_truncated"),d=m(e,"original_bytes"),f=m(e,"dropped_fields_count");if(o){if(e.record_truncated!==!0||!d||!f||!z(e.original_bytes)||!z(e.dropped_fields_count))return;i.record_truncated=!0,i.original_bytes=e.original_bytes,i.dropped_fields_count=e.dropped_fields_count}else if(d||f)return;return{envelope:i,effectiveTsMs:t}}s(Ne,"parseCanonicalLine");function Te(n,e){return e.component!==n.component?!1:n.component==="daemon"?e.stream_id===void 0:n.component==="broker"?e.node_id===n.boundNodeId&&e.stream_id===void 0:e.stream_id===n.boundStreamId}s(Te,"descriptorMatches");function ve(n,e,t){const{envelope:r,effectiveTsMs:i}=t;return!(n.nodeId!==void 0&&e.component!=="broker"&&r.node_id!==n.nodeId||n.operationId!==void 0&&r.operation_id!==n.operationId||n.sinceMs!==void 0&&i<n.sinceMs||n.untilMs!==void 0&&i>n.untilMs||n.minimumLevel!==void 0&&$[r.level]<$[n.minimumLevel]||n.pattern!==void 0&&!JSON.stringify(r).toLocaleLowerCase("en-US").includes(n.pattern.toLocaleLowerCase("en-US")))}s(ve,"matchesCanonical");function K(n,e){return n.item.effectiveTsMs!==e.item.effectiveTsMs?n.item.effectiveTsMs-e.item.effectiveTsMs:n.orderSlot!==e.orderSlot?n.orderSlot<e.orderSlot?-1:1:n.generationRank!==e.generationRank?n.generationRank-e.generationRank:n.byteOffset-e.byteOffset}s(K,"compareCandidates");function Y(n,e){const t=n.candidates;if(t.length===k&&K(e,t[0])<=0)return;let r=0,i=t.length;for(;r<i;){const o=Math.floor((r+i)/2);K(t[o],e)<=0?r=o+1:i=o}t.splice(r,0,e),t.length>k&&t.shift()}s(Y,"retainCandidate");function Ie(n){for(const e of n)if(e!==32&&e!==9&&e!==13)return!0;return!1}s(Ie,"hasNonblank");function Ae(n){return n.generation===0?n.activeAbsolute:`${n.activeAbsolute}.${n.generation}`}s(Ae,"sourcePath");function Ce(n,e,t,r){const i=Ae(r),o=j(e,t,i,oe);if(o===void 0)return;const d=N(e,i);let f=0;const c=o.start>0;let a=!1;const l=[];if(c){const u=o.bytes.indexOf(10);if(u<0)return;f=u+1}for(;;){const u=o.bytes.indexOf(10,f);if(u<0)break;const p=Ne(o.bytes.subarray(f,u));if(p===void 0||!Te(r,p.envelope))n.invalidCanonicalLines+=1,a=!0;else if(ve(n.query,r,p)){const y=o.start+f,E={kind:"event",envelope:p.envelope,source:d,streamKey:r.streamKey,generation:r.generation,byteOffset:y,effectiveTsMs:p.effectiveTsMs,truncated:!1};l.push(E),Y(n,{item:E,orderSlot:r.streamKey,generationRank:Ee[r.generation],byteOffset:y})}f=u+1}if(f<o.bytes.length&&Ie(o.bytes.subarray(f))&&(n.invalidCanonicalLines+=1,a=!0),c||a)for(const u of l)u.truncated=!0}s(Ce,"scanCanonicalSource");function ke(n,e){const t=/^(\S+)/u.exec(n)?.[1];if(t!==void 0&&ue.test(t)){const r=Date.parse(t);if(Number.isFinite(r))return r}return e}s(ke,"rawTimestamp");function Re(n,e,t,r){return n.nodeId!==void 0&&e==="daemon"&&!t.includes(n.nodeId)||n.sinceMs!==void 0&&r<n.sinceMs||n.untilMs!==void 0&&r>n.untilMs?!1:n.pattern===void 0||t.toLocaleLowerCase("en-US").includes(n.pattern.toLocaleLowerCase("en-US"))}s(Re,"matchesRaw");function U(n,e,t,r,i){const o=j(e,t,r,se);if(o===void 0)return;const d=N(e,r),f=o.start>0;let c=0;if(f){const a=o.bytes.indexOf(10);if(a<0)return;c=a+1}for(;;){const a=o.bytes.indexOf(10,c);if(a<0&&c===o.bytes.length)return;const l=a<0?o.bytes.length:a,u=o.bytes.subarray(c,l).toString("utf8"),p=ke(u,o.mtimeMs);if(Re(n.query,i,u,p)){const y=o.start+c;Y(n,{item:{kind:"raw",component:i,source:d,content:u,effectiveTsMs:p,truncated:f},orderSlot:d,generationRank:2,byteOffset:y})}if(a<0)return;c=a+1}}s(U,"scanRawSource");function P(n,e,t,r){for(const i of[2,1,0])Ce(n,e,t,{...r,generation:i})}s(P,"scanCanonicalGenerations");function V(n,e,t,r,i,o){const d=S(t[t.length-1].absolute,r),f=M(e,d,t,!0);if(f===void 0)return;const c=S(d,"job"),a=M(e,c,[...t,f],!0);if(a===void 0)return;const l=[...t,f,a];i&&P(n,e,l,{component:"broker",streamKey:`broker:${r}`,activeAbsolute:S(c,"log.jsonl"),boundNodeId:r}),o&&U(n,e,l,S(c,"broker.log"),"broker")}s(V,"scanBrokerNode");function Ue(n,e){const t=we(n);if(typeof e!="boolean")throw new TypeError("includeRaw must be boolean");const r={query:t,candidates:[],invalidCanonicalLines:0},i=I(ee()),o=M(i,i,[],!0);if(o===void 0)return{items:[],invalidCanonicalLines:0};const d=[o],f=e&&t.operationId===void 0&&t.minimumLevel===void 0;T(t,"daemon")&&(P(r,i,d,{component:"daemon",streamKey:"daemon",activeAbsolute:S(i,"crtrd.log")}),f&&U(r,i,d,S(i,"crtrd.err"),"daemon"));const c=T(t,"broker"),a=f&&T(t,"broker");if(c||a){const l=I(ne()),u=M(i,l,d,!0);if(u!==void 0){const p=[o,u];t.nodeId!==void 0?V(r,i,p,t.nodeId,c,a):ge(u,(y,E)=>{E&&A(y)&&V(r,i,p,y,c,a)})}}return{items:r.candidates.map(l=>l.item),invalidCanonicalLines:r.invalidCanonicalLines}}s(Ue,"readLogSnapshot");function Pe(n){const e={v:n.v,ts:n.ts,level:n.level,event:n.event,component:n.component,operation_id:n.operation_id};return n.node_id!==void 0&&(e.node_id=n.node_id),n.stream_id!==void 0&&(e.stream_id=n.stream_id),n.duration_ms!==void 0&&(e.duration_ms=n.duration_ms),n.outcome!==void 0&&(e.outcome=n.outcome),n.attempt!==void 0&&(e.attempt=n.attempt),n.error_class!==void 0&&(e.error_class=n.error_class),e}s(Pe,"projectTier1Event");export{Pe as projectTier1Event,Ue as readLogSnapshot};
@@ -1,2 +1,2 @@
1
- import{jobRow as d}from"../jobs.js";import{objectDTO as c}from"./dto.js";import{withBase as u}from"./kind.js";import{recordLiveRead as p}from"./live-read.js";import{attr as r,header as l}from"./render.js";const v={name:"job",addressable:"id",events:u({exited:{level:7},killed:{level:7},silent:{level:7}}),async read(a,i,e,s){const t=await d(a,e);if(!t)throw new Error(`job object ${e.id} has no jobs row`);await p(a,i,e,s.reposFn);const n=await c(a,e),o={command:t.command,status:t.status,exit_code:t.exit_code,output_key:t.output_key},m=[l("object",n,r("status",o.status)+r("exit_code",o.exit_code)),`Command: ${o.command}`,`Output: ${o.output_key}`].join(`
2
- `);return{data:o,text:m}}};export{v as jobKind};
1
+ import{jobRow as m}from"../jobs.js";import{objectDTO as c}from"./dto.js";import{withBase as l}from"./kind.js";import{recordLiveRead as u}from"./live-read.js";import{attr as a,header as w}from"./render.js";const v={name:"job",addressable:"id",events:l({exited:{level:7},killed:{level:7},silent:{level:7}}),async read(r,n,t,i){const e=await m(r,t);if(!e)throw new Error(`job object ${t.id} has no jobs row`);await u(r,n,t,i.reposFn);const s=await c(r,t),o={command:e.command,status:e.status,exit_code:e.exit_code,output_key:e.output_key},d=[w("object",s,a("status",o.status)+a("exit_code",o.exit_code)),`Command: ${o.command}`,`Output: \`crtr node bash log ${t.id}${t.owner===null?"":` --node ${t.owner}`}\``].join(`
2
+ `);return{data:o,text:d}}};export{v as jobKind};
@@ -1,4 +1,7 @@
1
1
  import { type QuarantinedPlugin } from '../plugin-quarantine.js';
2
+ import { type PackageFile } from './package-files.js';
3
+ import type { ReposFn } from './reader.js';
4
+ import type { ObjectRow } from './types.js';
2
5
  import type { CanvasDb } from '../canvas/db.js';
3
6
  export interface PackageSource {
4
7
  plugin: string;
@@ -28,3 +31,11 @@ export interface PackageReconcileReport {
28
31
  * document's single revision and publishes `edited`; a vanished file (or plugin) marks its
29
32
  * document deleted and publishes `deleted`. */
30
33
  export declare function reconcilePackageDocuments(db: CanvasDb, explicitSources?: readonly PackageSource[]): Promise<PackageReconcileReport>;
34
+ /** Record a package document's `links-to` edges the way a runtime document gets them, so delivery
35
+ * attaches the link previews. Each `[[name]]` resolves in its own app's view and links only when it
36
+ * names another shipped document (builtin or plugin): a package never links to a user's or a
37
+ * runtime document that happens to share a bare name. A name that resolves to nothing stays text.
38
+ * Edges are rewritten only when they differ, so an unchanged pass writes nothing. */
39
+ export declare function deriveLinks(db: CanvasDb, owner: ObjectRow, doc: ObjectRow, body: string): Promise<void>;
40
+ export declare function convergeFile(db: CanvasDb, owner: ObjectRow, file: PackageFile, report: PackageReconcileReport, reposFn: ReposFn): Promise<string>;
41
+ export declare function deleteMissing(db: CanvasDb, owner: ObjectRow, keep: ReadonlySet<string>, report: PackageReconcileReport, reposFn: ReposFn): Promise<void>;
@@ -1,6 +1,6 @@
1
- var S=Object.defineProperty;var u=(e,a)=>S(e,"name",{value:a,configurable:!0});import{randomUUID as _}from"node:crypto";import{CrtrError as g}from"../errors.js";import{emitEvent as O}from"../events/emit.js";import{quarantineBrokenPlugins as I}from"../plugin-quarantine.js";import{listInstalledPlugins as D}from"../resolver.js";import{builtinMemoryRoot as L,pluginMemoryDir as R}from"../scope.js";import{documentFields as T}from"../../migrations/004-canvas-documents/fields.js";import{put as A}from"./bodies.js";import{applyDocumentFields as k,deleteDocument as j,documentRecord as F,heads as x,importDocument as C,linksIn as U,moveDocument as H}from"./documents.js";import{deleteShippedReader as M,writeShippedReader as W}from"../permissions/facts.js";import{setLinks as J}from"./edges.js";import{publish as b}from"./events.js";import{resolveName as P}from"./names.js";import{packageFiles as q}from"./package-files.js";import{ensureOwner as B,getById as h,setObjectKind as K}from"./objects.js";import{checkoutsForCwd as $}from"./repo-sync/repos.js";import{rulesOf as z}from"./rules.js";function G(){return[{plugin:"crtr",dir:L()},...D("user").filter(e=>e.enabled).map(e=>({plugin:e.name,dir:R(e)}))]}u(G,"packageSources");async function Ee(e,a){const i={created:0,updated:0,deleted:0,conflicts:[],invalid:[],quarantined:[]};if(a===void 0){i.quarantined=I();for(const n of i.quarantined)O({level:"error",event:"daemon.plugins.quarantined",fields:{plugin:n.name,file:n.file,reason:n.reason,...n.movedTo!==void 0?{moved_to:n.movedTo}:{}}})}const l=a??G(),m=u(async n=>await $(e,n),"reposFn"),o=new Set,r=[],f=new Set;for(const n of l){let s;try{if(f.has(n.plugin))throw new g("conflict",`plugin "${n.plugin}" is already provided by another package`);f.add(n.plugin),s=await B(e,"app","app",`app:${n.plugin}`,n.plugin)}catch(p){if(!(p instanceof g)||p.code!=="conflict")throw p;i.conflicts.push({plugin:n.plugin,message:p.message});continue}o.add(s.id);const{files:c,invalid:w}=q(n.dir);i.invalid.push(...w);const E=new Set;for(const p of c){const d=await X(e,s,p,i,m);E.add(d),r.push({owner:s,id:d,body:p.body})}await v(e,s,E,i,m)}const t=await e.prepare(`SELECT DISTINCT o.owner AS id FROM objects o JOIN documents d ON d.object = o.id
2
- WHERE o.type = 'document' AND o.deleted_at IS NULL AND d.source_path IS NOT NULL`).all();for(const{id:n}of t)o.has(n)||await v(e,await h(e,n),new Set,i,m);for(const{owner:n,id:s,body:c}of r)await Q(e,n,await h(e,s),c);return i}u(Ee,"reconcilePackageDocuments");async function Q(e,a,i,l){if(i.deleted_at!==null)return;const m={class:"app",app:a,scopes:[]},o=[];for(const t of U(l)){const n=await P(e,m,t);"object"in n&&n.object.id!==i.id&&await V(e,n.object.id)&&o.push({written_as:t,dst:n.object})}const r=u(t=>JSON.stringify(t.map(n=>[n.dst,n.written_as]).sort()),"key"),f=await e.prepare("SELECT dst, written_as FROM edges WHERE type = 'links-to' AND src = ?").all(i.id);r(f)!==r(o.map(t=>({dst:t.dst.id,written_as:t.written_as})))&&await J(e,i,o)}u(Q,"deriveLinks");async function V(e,a){return await e.prepare("SELECT 1 FROM documents WHERE object = ? AND source_path IS NOT NULL").get(a)!==void 0}u(V,"shippedDocument");async function X(e,a,i,l,m){const o=T(i.frontmatter,i.body,{missingPrivacy:"public"}),r={preview:o.preview.trim()===""?i.name:o.preview,summary:o.summary,whyItExists:o.whyItExists,unlisted:o.unlisted,rules:o.rules,lintIgnore:Array.isArray(o.lintIgnore)?o.lintIgnore.filter(d=>typeof d=="string"):null,extensions:o.extensions!==null&&typeof o.extensions=="object"&&!Array.isArray(o.extensions)?o.extensions:null},f=await A(e,i.body);let t=await e.prepare(`SELECT o.* FROM objects o JOIN documents d ON d.object = o.id
3
- WHERE o.type = 'document' AND o.owner = ? AND o.deleted_at IS NULL AND d.source_path = ?`).get(a.id,i.path);if(t||(t=await e.prepare(`SELECT o.* FROM objects o JOIN documents d ON d.object = o.id
4
- WHERE o.type = 'document' AND o.owner = ? AND o.deleted_at IS NULL AND o.name = ? AND d.source_path IS NOT NULL`).get(a.id,i.name),t&&await e.prepare("UPDATE documents SET source_path = ? WHERE object = ?").run(i.path,t.id)),!t){const d=await C(e,{owner:a,name:i.name,kind:o.kind,privacy:"public",...r,revisions:[{body_hash:f,author:a,rationale:null,created_at:new Date().toISOString()}]});return await e.prepare("UPDATE documents SET source_path = ? WHERE object = ?").run(i.path,d.id),await W(e,d.id),l.created+=1,d.id}const n=t.name!==i.name||t.kind!==o.kind;t.name!==i.name&&(t=await H(e,null,t,{name:i.name})),t.kind!==o.kind&&(await K(e,t,o.kind),t=await h(e,t.id));const s=await F(e,t),[c]=await x(e,t),w=c?.body_hash!==f,E=t.preview!==r.preview||s.summary!==r.summary||s.why_it_exists!==r.whyItExists||s.unlisted!==r.unlisted||JSON.stringify(s.lint_ignore)!==JSON.stringify(r.lintIgnore)||JSON.stringify(s.extensions)!==JSON.stringify(r.extensions)||JSON.stringify((await z(e,t)).map(N))!==JSON.stringify(r.rules.map(N));if(!w&&!E&&!n)return t.id;const p=await k(e,t,r);if(w&&c){const d=_(),y=c.number+1;await e.prepare(`INSERT INTO revisions (rev_id, document, number, body_hash, author, rationale, verbatim, created_at)
5
- SELECT ?, document, ?, ?, author, rationale, verbatim, ? FROM revisions WHERE rev_id = ?`).run(d,y,f,new Date().toISOString(),c.rev_id),await e.prepare("INSERT INTO revision_parents (child, parent) SELECT ?, parent FROM revision_parents WHERE child = ?").run(d,c.rev_id),await e.prepare("DELETE FROM revisions WHERE rev_id = ?").run(c.rev_id),await e.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ?").run(d,y,p.id),await b(e,{event:"edited",object:p,actor:a,pointer:d,attrs:{revision:y,from_revision:c.number,rationale:"package file changed"}},m)}return l.updated+=1,p.id}u(X,"convergeFile");function N(e){return[e.on,e.deliver,e.match??null,e.matchFrontmatter??null,e.if??null]}u(N,"ruleKey");async function v(e,a,i,l,m){const o=await e.prepare(`SELECT o.* FROM objects o JOIN documents d ON d.object = o.id
6
- WHERE o.type = 'document' AND o.owner = ? AND o.deleted_at IS NULL AND d.source_path IS NOT NULL`).all(a.id);for(const r of o)i.has(r.id)||(await j(e,null,r,m),await M(e,r.id),l.deleted+=1)}u(v,"deleteMissing");export{G as packageSources,Ee as reconcilePackageDocuments};
1
+ var S=Object.defineProperty;var u=(e,a)=>S(e,"name",{value:a,configurable:!0});import{randomUUID as _}from"node:crypto";import{CrtrError as g}from"../errors.js";import{emitEvent as O}from"../events/emit.js";import{quarantineBrokenPlugins as I}from"../plugin-quarantine.js";import{listInstalledPlugins as D}from"../resolver.js";import{builtinMemoryRoot as T,pluginMemoryDir as R}from"../scope.js";import{documentFields as L}from"../../migrations/004-canvas-documents/fields.js";import{put as A}from"./bodies.js";import{applyDocumentFields as k,deleteDocument as j,documentRecord as F,heads as x,importDocument as C,linksIn as M,moveDocument as U}from"./documents.js";import{deleteShippedReader as H,writeShippedReader as W}from"../permissions/facts.js";import{setLinks as J}from"./edges.js";import{publish as b}from"./events.js";import{resolveName as P}from"./names.js";import{packageFiles as q}from"./package-files.js";import{ensureOwner as B,getById as y,setObjectKind as K}from"./objects.js";import{checkoutsForCwd as $}from"./repo-sync/repos.js";import{rulesOf as z}from"./rules.js";function G(){return[{plugin:"crtr",dir:T()},...D("user").filter(e=>e.enabled).map(e=>({plugin:e.name,dir:R(e)}))]}u(G,"packageSources");async function Ee(e,a){const n={created:0,updated:0,deleted:0,conflicts:[],invalid:[],quarantined:[]};if(a===void 0){n.quarantined=I();for(const t of n.quarantined)O({level:"error",event:"daemon.plugins.quarantined",fields:{plugin:t.name,file:t.file,reason:t.reason,...t.movedTo!==void 0?{moved_to:t.movedTo}:{}}})}const l=a??G(),m=u(async t=>await $(e,t),"reposFn"),o=new Set,r=[],f=new Set;for(const t of l){let s;try{if(f.has(t.plugin))throw new g("conflict",`plugin "${t.plugin}" is already provided by another package`);f.add(t.plugin),s=await B(e,"app","app",`app:${t.plugin}`,t.plugin)}catch(p){if(!(p instanceof g)||p.code!=="conflict")throw p;n.conflicts.push({plugin:t.plugin,message:p.message});continue}o.add(s.id);const{files:c,invalid:w}=q(t.dir);n.invalid.push(...w);const E=new Set;for(const p of c){const d=await X(e,s,p,n,m);E.add(d),r.push({owner:s,id:d,body:p.body})}await v(e,s,E,n,m)}const i=await e.prepare(`SELECT DISTINCT o.owner AS id FROM objects o JOIN documents d ON d.object = o.id
2
+ WHERE o.type = 'document' AND o.deleted_at IS NULL AND d.source_path IS NOT NULL`).all();for(const{id:t}of i)o.has(t)||await v(e,await y(e,t),new Set,n,m);for(const{owner:t,id:s,body:c}of r)await Q(e,t,await y(e,s),c);return n}u(Ee,"reconcilePackageDocuments");async function Q(e,a,n,l){if(n.deleted_at!==null)return;const m={class:"app",app:a,scopes:[]},o=[];for(const i of M(l)){const t=await P(e,m,i);"object"in t&&t.object.id!==n.id&&await V(e,t.object.id)&&o.push({written_as:i,dst:t.object})}const r=u(i=>JSON.stringify(i.map(t=>[t.dst,t.written_as]).sort()),"key"),f=await e.prepare("SELECT dst, written_as FROM edges WHERE type = 'links-to' AND src = ?").all(n.id);r(f)!==r(o.map(i=>({dst:i.dst.id,written_as:i.written_as})))&&await J(e,n,o)}u(Q,"deriveLinks");async function V(e,a){return await e.prepare("SELECT 1 FROM documents WHERE object = ? AND source_path IS NOT NULL").get(a)!==void 0}u(V,"shippedDocument");async function X(e,a,n,l,m){const o=L(n.frontmatter,n.body,{missingPrivacy:"public"}),r={preview:o.preview.trim()===""?n.name:o.preview,summary:o.summary,whyItExists:o.whyItExists,unlisted:o.unlisted,rules:o.rules,lintIgnore:Array.isArray(o.lintIgnore)?o.lintIgnore.filter(d=>typeof d=="string"):null,extensions:o.extensions!==null&&typeof o.extensions=="object"&&!Array.isArray(o.extensions)?o.extensions:null},f=await A(e,n.body);let i=await e.prepare(`SELECT o.* FROM objects o JOIN documents d ON d.object = o.id
3
+ WHERE o.type = 'document' AND o.owner = ? AND o.deleted_at IS NULL AND o.name = ? AND d.source_path IS NOT NULL`).get(a.id,n.name);if(i?await e.prepare("UPDATE documents SET source_path = ? WHERE object = ? AND source_path IS DISTINCT FROM ?").run(n.path,i.id,n.path):i=await e.prepare(`SELECT o.* FROM objects o JOIN documents d ON d.object = o.id
4
+ WHERE o.type = 'document' AND o.owner = ? AND o.deleted_at IS NULL AND d.source_path = ?`).get(a.id,n.path),!i){const d=await C(e,{owner:a,name:n.name,kind:o.kind,privacy:"public",...r,revisions:[{body_hash:f,author:a,rationale:null,created_at:new Date().toISOString()}]});return await e.prepare("UPDATE documents SET source_path = ? WHERE object = ?").run(n.path,d.id),await W(e,d.id),l.created+=1,d.id}const t=i.name!==n.name||i.kind!==o.kind;i.name!==n.name&&(i=await U(e,null,i,{name:n.name})),i.kind!==o.kind&&(await K(e,i,o.kind),i=await y(e,i.id));const s=await F(e,i),[c]=await x(e,i),w=c?.body_hash!==f,E=i.preview!==r.preview||s.summary!==r.summary||s.why_it_exists!==r.whyItExists||s.unlisted!==r.unlisted||JSON.stringify(s.lint_ignore)!==JSON.stringify(r.lintIgnore)||JSON.stringify(s.extensions)!==JSON.stringify(r.extensions)||JSON.stringify((await z(e,i)).map(N))!==JSON.stringify(r.rules.map(N));if(!w&&!E&&!t)return i.id;const p=await k(e,i,r);if(w&&c){const d=_(),h=c.number+1;await e.prepare(`INSERT INTO revisions (rev_id, document, number, body_hash, author, rationale, verbatim, created_at)
5
+ SELECT ?, document, ?, ?, author, rationale, verbatim, ? FROM revisions WHERE rev_id = ?`).run(d,h,f,new Date().toISOString(),c.rev_id),await e.prepare("INSERT INTO revision_parents (child, parent) SELECT ?, parent FROM revision_parents WHERE child = ?").run(d,c.rev_id),await e.prepare("DELETE FROM revisions WHERE rev_id = ?").run(c.rev_id),await e.prepare("UPDATE documents SET heads_key = ?, revision = ? WHERE object = ?").run(d,h,p.id),await b(e,{event:"edited",object:p,actor:a,pointer:d,attrs:{revision:h,from_revision:c.number,rationale:"package file changed"}},m)}return l.updated+=1,p.id}u(X,"convergeFile");function N(e){return[e.on,e.deliver,e.match??null,e.matchFrontmatter??null,e.if??null]}u(N,"ruleKey");async function v(e,a,n,l,m){const o=await e.prepare(`SELECT o.* FROM objects o JOIN documents d ON d.object = o.id
6
+ WHERE o.type = 'document' AND o.owner = ? AND o.deleted_at IS NULL AND d.source_path IS NOT NULL`).all(a.id);for(const r of o)n.has(r.id)||(await j(e,null,r,m),await H(e,r.id),l.deleted+=1)}u(v,"deleteMissing");export{X as convergeFile,v as deleteMissing,Q as deriveLinks,G as packageSources,Ee as reconcilePackageDocuments};
@@ -0,0 +1,30 @@
1
+ import { type PackageReconcileReport } from './package-docs.js';
2
+ import { type PackageFile } from './package-files.js';
3
+ import type { CanvasDb } from '../canvas/db.js';
4
+ /** What one app's package folder holds right now, read before the publishing transaction opens. */
5
+ export interface PackageSnapshot {
6
+ app: string;
7
+ origin: 'builtin' | 'provider' | 'plugin';
8
+ /** crtr release (builtin), bundle generation (provider), manifest version (plugin) */
9
+ version: string;
10
+ /** when this process obtained the bytes; null for the builtin package */
11
+ fetchedAt: string | null;
12
+ digest: string;
13
+ /** files with `path` already package-relative, prefixed by the app: `<app>/<relative path>` */
14
+ files: PackageFile[];
15
+ invalid: Array<{
16
+ path: string;
17
+ error: string;
18
+ }>;
19
+ }
20
+ export interface PublishReport extends PackageReconcileReport {
21
+ /** the row's release is newer than this snapshot: nothing was written */
22
+ superseded: boolean;
23
+ }
24
+ /** The package folder `dir` as a snapshot of app `app`. Throws when the folder or any directory in it cannot be read. The digest covers the
25
+ * path and raw bytes of every markdown file, invalid ones included, so removing or changing an invalid file changes it. */
26
+ export declare function readPackageSnapshot(dir: string, meta: Pick<PackageSnapshot, 'app' | 'origin' | 'version' | 'fetchedAt'>): PackageSnapshot;
27
+ /** App `crtr` as this process's release ships it. */
28
+ export declare function readBuiltinSnapshot(): PackageSnapshot;
29
+ /** Publish one snapshot inside the caller's write transaction. Serialized per app on the publication row. Returns what changed. */
30
+ export declare function publish(db: CanvasDb, snapshot: PackageSnapshot, publishedBy: string): Promise<PublishReport>;
@@ -0,0 +1,3 @@
1
+ var g=Object.defineProperty;var p=(i,e)=>g(i,"name",{value:e,configurable:!0});import{createHash as w}from"node:crypto";import{readdirSync as v,readFileSync as y,statSync as h}from"node:fs";import{join as E,relative as u,sep as m}from"node:path";import{compareReleaseVersions as S}from"../../api/runtime-version.js";import{crtrVersion as _}from"../../shared/crtr-version.js";import{emitEvent as N}from"../events/emit.js";import{builtinMemoryRoot as k}from"../scope.js";import{deleteMissing as F,convergeFile as L,deriveLinks as O}from"./package-docs.js";import{packageFiles as T}from"./package-files.js";import{ensureOwner as D,getById as I}from"./objects.js";import{checkoutsForCwd as R}from"./repo-sync/repos.js";function A(i){if(!h(i,{throwIfNoEntry:!1})?.isDirectory())throw new Error(`package folder is missing or not a directory: ${i}`);const e=[],o=[i];for(;o.length>0;){const r=o.pop();for(const t of v(r,{withFileTypes:!0})){const n=E(r,t.name);t.isDirectory()?t.name.startsWith(".")||o.push(n):t.isFile()&&t.name.endsWith(".md")&&t.name!=="SKILL.md"&&e.push([u(i,n).split(m).join("/"),y(n)])}}return e.sort((r,t)=>r[0]<t[0]?-1:1)}p(A,"strictPackageBytes");function b(i,e){const o=A(i),{files:r,invalid:t}=T(i),n=r.map(c=>({...c,path:`${e.app}/${u(i,c.path).split(m).join("/")}`})),a=w("sha256");for(const[c,d]of o)a.update(c).update("\0").update(d).update("\0");return{...e,digest:a.digest("hex"),files:n,invalid:t}}p(b,"readPackageSnapshot");function G(){return b(k(),{app:"crtr",origin:"builtin",version:_(),fetchedAt:null})}p(G,"readBuiltinSnapshot");async function K(i,e,o){const r={created:0,updated:0,deleted:0,conflicts:[],invalid:[...e.invalid],quarantined:[],superseded:!1},t=await D(i,"app","app",`app:${e.app}`,e.app),n=new Date().toISOString();await i.prepare(`INSERT INTO package_publications (app, origin, version, digest, published_at, published_by)
2
+ VALUES (?, ?, '', '', ?, ?) ON CONFLICT (app) DO NOTHING`).run(t.id,e.origin,n,o);const a=await i.prepare("SELECT version, digest, withdrawn_at FROM package_publications WHERE app = ? FOR UPDATE").get(t.id);if(S(e.version,a.version)<0)return r.superseded=!0,N({level:"warn",event:"daemon.packages.superseded",fields:{app:e.app,snapshot_version:e.version,published_version:a.version}}),r;if(a.digest===e.digest&&a.withdrawn_at===null){if(a.version===e.version)return r}else{const d=p(async s=>await R(i,s),"reposFn"),l=new Set;for(const s of e.files)l.add(await L(i,t,s,r,d));if(e.invalid.length===0&&await F(i,t,l,r,d),r.created+r.updated+r.deleted>0)for(const s of e.files){const f=await i.prepare("SELECT o.id FROM objects o WHERE o.type = 'document' AND o.owner = ? AND o.name = ? AND o.deleted_at IS NULL").get(t.id,s.name);f!==void 0&&await O(i,t,await I(i,f.id),s.body)}}return await i.prepare(`UPDATE package_publications SET origin = ?, version = ?, fetched_at = ?, digest = ?, published_at = ?, published_by = ?, withdrawn_at = NULL
3
+ WHERE app = ?`).run(e.origin,e.version,e.fetchedAt,e.digest,n,o,t.id),r}p(K,"publish");export{K as publish,G as readBuiltinSnapshot,b as readPackageSnapshot};
@@ -1,4 +1,6 @@
1
1
  export declare const MAX_ROTATING_RECORD_BYTES = 65536;
2
+ /** The daemon log is the one place a day-old incident is diagnosed from; broker and job logs keep the smaller bound. */
3
+ export declare const MAX_DAEMON_ACTIVE_BYTES: number;
2
4
  /** Synchronously appends one complete NDJSON record, retaining active + .1 + .2. */
3
5
  export declare function appendRotatingRecord(path: string, recordWithoutLf: string): boolean;
4
6
  /** One timed/bounded daemon batch instead of a mkdir/stat/open/write per event.
package/dist/core/log.js CHANGED
@@ -1,5 +1,5 @@
1
- var S=Object.defineProperty;var f=(e,n)=>S(e,"name",{value:n,configurable:!0});import{appendFileSync as o,existsSync as d,mkdirSync as B,renameSync as l,rmSync as _,statSync as g}from"node:fs";import{dirname as R}from"node:path";const m=65536,T=5*1024*1024,b=128*1024,x=200;function y(e,n){let t="";try{B(R(e),{recursive:!0});let i=d(e)?g(e).size:0;for(const p of n){const u=`${p}
2
- `,c=Buffer.byteLength(u,"utf8");i+c>T&&i>0&&(t!==""&&(o(e,t,"utf8"),t=""),_(`${e}.2`,{force:!0}),d(`${e}.1`)&&l(`${e}.1`,`${e}.2`),l(e,`${e}.1`),i=0),t+=u,i+=c}return t!==""&&o(e,t,"utf8"),!0}catch{return!1}}f(y,"appendRotatingRecords");function D(e,n){return n.includes(`
3
- `)||n.includes("\r")||Buffer.byteLength(n,"utf8")+1>m?!1:y(e,[n])}f(D,"appendRotatingRecord");let r,a=!1;function I(e,n){if(n.includes(`
4
- `)||n.includes("\r"))return!1;const t=Buffer.byteLength(n,"utf8")+1;return t>m?!1:(r!==void 0&&r.path!==e&&s(),r===void 0&&(r={path:e,records:[],bytes:0},a||(process.on("exit",s),a=!0)),r.path=e,r.records.push(n),r.bytes+=t,r.bytes>=b?s():r.timer===void 0&&(r.timer=setTimeout(s,x),r.timer.unref()),!0)}f(I,"appendBufferedDaemonRecord");function s(){const e=r;if(!(e===void 0||e.records.length===0)&&(e.timer!==void 0&&clearTimeout(e.timer),r=void 0,!y(e.path,e.records)))for(const n of e.records)process.stderr.write(`[crtrd] event log append failed: ${n}
5
- `)}f(s,"flushDaemonLog");export{m as MAX_ROTATING_RECORD_BYTES,I as appendBufferedDaemonRecord,D as appendRotatingRecord,s as flushDaemonLog};
1
+ var S=Object.defineProperty;var f=(e,n)=>S(e,"name",{value:n,configurable:!0});import{appendFileSync as d,existsSync as l,mkdirSync as B,renameSync as m,rmSync as T,statSync as E}from"node:fs";import{dirname as g}from"node:path";const y=65536,A=5*1024*1024,R=16*1024*1024,b=128*1024,x=200;function a(e,n,i=A){let t="";try{B(g(e),{recursive:!0});let s=l(e)?E(e).size:0;for(const p of n){const o=`${p}
2
+ `,c=Buffer.byteLength(o,"utf8");s+c>i&&s>0&&(t!==""&&(d(e,t,"utf8"),t=""),T(`${e}.2`,{force:!0}),l(`${e}.1`)&&m(`${e}.1`,`${e}.2`),m(e,`${e}.1`),s=0),t+=o,s+=c}return t!==""&&d(e,t,"utf8"),!0}catch{return!1}}f(a,"appendRotatingRecords");function M(e,n){return n.includes(`
3
+ `)||n.includes("\r")||Buffer.byteLength(n,"utf8")+1>y?!1:a(e,[n])}f(M,"appendRotatingRecord");let r,_=!1;function N(e,n){if(n.includes(`
4
+ `)||n.includes("\r"))return!1;const i=Buffer.byteLength(n,"utf8")+1;return i>y?!1:(r!==void 0&&r.path!==e&&u(),r===void 0&&(r={path:e,records:[],bytes:0},_||(process.on("exit",u),_=!0)),r.path=e,r.records.push(n),r.bytes+=i,r.bytes>=b?u():r.timer===void 0&&(r.timer=setTimeout(u,x),r.timer.unref()),!0)}f(N,"appendBufferedDaemonRecord");function u(){const e=r;if(!(e===void 0||e.records.length===0)&&(e.timer!==void 0&&clearTimeout(e.timer),r=void 0,!a(e.path,e.records,R)))for(const n of e.records)process.stderr.write(`[crtrd] event log append failed: ${n}
5
+ `)}f(u,"flushDaemonLog");export{R as MAX_DAEMON_ACTIVE_BYTES,y as MAX_ROTATING_RECORD_BYTES,N as appendBufferedDaemonRecord,M as appendRotatingRecord,u as flushDaemonLog};