@north-light/cli 0.3.580 → 0.3.582
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/api/client.d.ts +6 -1
- package/dist/api/client.js +3 -3
- package/dist/api/contract.d.ts +1 -1
- package/dist/api/contract.js +1 -1
- package/dist/api/dto/execution-start.d.ts +14 -0
- package/dist/api/dto/health.d.ts +22 -0
- package/dist/api/dto/imports.d.ts +142 -0
- package/dist/api/dto/imports.js +0 -0
- package/dist/api/error-codes.d.ts +5 -0
- package/dist/api/error-codes.js +1 -1
- package/dist/api/errors.js +1 -1
- package/dist/api/index.d.ts +1 -0
- package/dist/api/index.js +1 -1
- package/dist/api/routes.d.ts +2 -0
- package/dist/api/routes.js +1 -1
- package/dist/build-root.d.ts +11 -4
- package/dist/build-root.js +1 -1
- package/dist/cli.js +7 -7
- package/dist/clients/attach/session/connection.d.ts +3 -1
- package/dist/clients/attach/session/connection.js +1 -1
- package/dist/clients/attach/viewer.js +529 -529
- package/dist/commands/api-client.d.ts +12 -4
- package/dist/commands/api-client.js +3 -3
- package/dist/commands/sys/daemon.js +1 -1
- package/dist/commands/sys/desktop.d.ts +5 -0
- package/dist/commands/sys/desktop.js +1 -0
- package/dist/commands/sys/import/move.d.ts +55 -0
- package/dist/commands/sys/import/move.js +8 -0
- package/dist/commands/sys/import/resume.d.ts +1 -0
- package/dist/commands/sys/import/resume.js +1 -0
- package/dist/commands/sys/import/start.d.ts +4 -0
- package/dist/commands/sys/import/start.js +1 -0
- package/dist/commands/sys/join.d.ts +2 -0
- package/dist/commands/sys/join.js +1 -1
- package/dist/commands/sys/machine/rotate.d.ts +1 -0
- package/dist/commands/sys/machine/rotate.js +1 -0
- package/dist/commands/sys/signin.d.ts +7 -0
- package/dist/commands/sys/signin.js +2 -0
- package/dist/commands/sys.js +1 -1
- package/dist/core/blobs/r2.d.ts +4 -0
- package/dist/core/blobs/r2.js +7 -4
- package/dist/core/canvas/daemon-owner.d.ts +1 -1
- package/dist/core/canvas/executions.d.ts +1 -1
- package/dist/core/canvas/node-tokens.d.ts +2 -0
- package/dist/core/canvas/node-tokens.js +3 -3
- package/dist/core/canvas/pid.d.ts +6 -0
- package/dist/core/canvas/pid.js +5 -5
- package/dist/core/command-plugins/compose.js +1 -1
- package/dist/core/command.d.ts +17 -8
- package/dist/core/command.js +9 -9
- package/dist/core/directory/issuer.d.ts +3 -0
- package/dist/core/directory/issuer.js +1 -1
- package/dist/core/directory/person-credential.d.ts +61 -0
- package/dist/core/directory/person-credential.js +1 -0
- package/dist/core/directory/reach.d.ts +3 -0
- package/dist/core/directory/reach.js +1 -0
- package/dist/core/grants/mirror.d.ts +3 -0
- package/dist/core/grants/mirror.js +2 -2
- package/dist/core/graph/bodies-boot-copy.d.ts +16 -2
- package/dist/core/graph/bodies-boot-copy.js +1 -1
- package/dist/core/graph/bodies.d.ts +13 -4
- package/dist/core/graph/bodies.js +1 -1
- package/dist/core/graph/deletion.d.ts +4 -1
- package/dist/core/graph/deletion.js +3 -3
- package/dist/core/graph/documents.d.ts +4 -0
- package/dist/core/graph/documents.js +9 -9
- package/dist/core/graph/reader.js +2 -2
- package/dist/core/graph/repo-sync/exchange.js +2 -2
- package/dist/core/graph/repo-sync/ref-format.d.ts +7 -2
- package/dist/core/graph/repo-sync/ref-format.js +2 -2
- package/dist/core/import/bundle/blob-keys.d.ts +10 -0
- package/dist/core/import/bundle/blob-keys.js +1 -0
- package/dist/core/import/bundle/layout.d.ts +90 -0
- package/dist/core/import/bundle/layout.js +5 -0
- package/dist/core/import/export/archive.d.ts +11 -0
- package/dist/core/import/export/archive.js +1 -0
- package/dist/core/import/export/cloud.d.ts +23 -0
- package/dist/core/import/export/cloud.js +1 -0
- package/dist/core/import/export/dump.d.ts +34 -0
- package/dist/core/import/export/dump.js +3 -0
- package/dist/core/import/export/files.d.ts +22 -0
- package/dist/core/import/export/files.js +1 -0
- package/dist/core/import/export/inline.d.ts +3 -0
- package/dist/core/import/export/inline.js +1 -0
- package/dist/core/import/export/source.d.ts +2 -0
- package/dist/core/import/export/source.js +1 -0
- package/dist/core/import/export/stores.d.ts +9 -0
- package/dist/core/import/export/stores.js +1 -0
- package/dist/core/import/load/bind.d.ts +19 -0
- package/dist/core/import/load/bind.js +15 -0
- package/dist/core/import/load/blobs.d.ts +10 -0
- package/dist/core/import/load/blobs.js +1 -0
- package/dist/core/import/load/catalog.d.ts +32 -0
- package/dist/core/import/load/catalog.js +9 -0
- package/dist/core/import/load/copy.d.ts +26 -0
- package/dist/core/import/load/copy.js +6 -0
- package/dist/core/import/load/path-map.d.ts +7 -0
- package/dist/core/import/load/path-map.js +1 -0
- package/dist/core/import/load/prepare.d.ts +37 -0
- package/dist/core/import/load/prepare.js +10 -0
- package/dist/core/import/load/rewrite.d.ts +39 -0
- package/dist/core/import/load/rewrite.js +13 -0
- package/dist/core/import/load/rollback.d.ts +3 -0
- package/dist/core/import/load/rollback.js +6 -0
- package/dist/core/import/load/run.d.ts +22 -0
- package/dist/core/import/load/run.js +3 -0
- package/dist/core/import/load/stage.d.ts +15 -0
- package/dist/core/import/load/stage.js +1 -0
- package/dist/core/import/load/stores.d.ts +24 -0
- package/dist/core/import/load/stores.js +2 -0
- package/dist/core/import/load/verify.d.ts +4 -0
- package/dist/core/import/load/verify.js +12 -0
- package/dist/core/io.js +4 -4
- package/dist/core/northlight/previews.d.ts +22 -0
- package/dist/core/northlight/previews.js +1 -0
- package/dist/core/providers/call.js +1 -1
- package/dist/core/providers/device.js +1 -1
- package/dist/core/providers/files.d.ts +2 -1
- package/dist/core/providers/files.js +1 -1
- package/dist/core/runtime/broker/cli-relay.js +1 -1
- package/dist/core/runtime/broker/model-adapter.js +3 -3
- package/dist/core/runtime/broker-ownership.d.ts +1 -0
- package/dist/core/runtime/broker-ownership.js +1 -0
- package/dist/core/runtime/broker.js +1 -1
- package/dist/core/runtime/first-run-offer.d.ts +7 -2
- package/dist/core/runtime/first-run-offer.js +10 -2
- package/dist/core/runtime/front-door.js +4 -4
- package/dist/core/runtime/revive.d.ts +5 -2
- package/dist/core/runtime/revive.js +2 -2
- package/dist/core/runtime/runner/blaxel/runner.js +3 -3
- package/dist/core/runtime/runner/index.d.ts +1 -0
- package/dist/core/runtime/runner/index.js +1 -1
- package/dist/core/runtime/runner/machine/call.d.ts +8 -0
- package/dist/core/runtime/runner/machine/call.js +3 -0
- package/dist/core/runtime/runner/machine/environment.d.ts +1 -0
- package/dist/core/runtime/runner/machine/environment.js +1 -0
- package/dist/core/runtime/runner/machine/executor.d.ts +29 -0
- package/dist/core/runtime/runner/machine/executor.js +1 -0
- package/dist/core/runtime/runner/machine/machine-executor.test.d.ts +1 -0
- package/dist/core/runtime/runner/machine/machine-executor.test.js +4 -0
- package/dist/core/runtime/runner/machine/ops.d.ts +127 -0
- package/dist/core/runtime/runner/machine/ops.js +1 -0
- package/dist/core/runtime/runner/machine/runner.d.ts +12 -0
- package/dist/core/runtime/runner/machine/runner.js +1 -0
- package/dist/core/runtime/runner/types.d.ts +7 -1
- package/dist/core/runtime/warm-pool.d.ts +2 -0
- package/dist/core/runtime/warm-pool.js +1 -1
- package/dist/core/secrets/env-rows.d.ts +1 -0
- package/dist/core/secrets/env-rows.js +1 -1
- package/dist/core/storage/purge.d.ts +6 -0
- package/dist/core/storage/purge.js +4 -0
- package/dist/core/subscription-state.d.ts +1 -0
- package/dist/core/subscription-state.js +1 -1
- package/dist/daemon/api/bridge.js +3 -3
- package/dist/daemon/api/handlers/attach.js +1 -1
- package/dist/daemon/api/handlers/crons.js +1 -1
- package/dist/daemon/api/handlers/daemon.js +1 -1
- package/dist/daemon/api/handlers/execution-start.js +2 -2
- package/dist/daemon/api/handlers/files.js +1 -1
- package/dist/daemon/api/handlers/imports.d.ts +2 -0
- package/dist/daemon/api/handlers/imports.js +1 -0
- package/dist/daemon/api/handlers/machine-credential.d.ts +2 -0
- package/dist/daemon/api/handlers/machine-credential.js +1 -0
- package/dist/daemon/api/handlers/profiles.js +1 -1
- package/dist/daemon/api/handlers/providers.js +1 -1
- package/dist/daemon/api/handlers/runs.js +1 -1
- package/dist/daemon/api/import-auth.d.ts +6 -0
- package/dist/daemon/api/import-auth.js +1 -0
- package/dist/daemon/api/operations.d.ts +2 -2
- package/dist/daemon/api/operations.js +1 -1
- package/dist/daemon/api/router.d.ts +7 -1
- package/dist/daemon/api/router.js +1 -1
- package/dist/daemon/api/server.js +1 -1
- package/dist/daemon/boot-migrations.js +1 -1
- package/dist/daemon/broker-signals/bus.d.ts +3 -2
- package/dist/daemon/broker-signals/bus.js +1 -1
- package/dist/daemon/cron/armed-context.d.ts +1 -0
- package/dist/daemon/cron/armed-context.js +1 -1
- package/dist/daemon/cron/closed.d.ts +31 -0
- package/dist/daemon/cron/closed.js +1 -0
- package/dist/daemon/cron/ended.js +2 -2
- package/dist/daemon/cron/lease-recovery.js +1 -1
- package/dist/daemon/cron/machine-exec.d.ts +11 -0
- package/dist/daemon/cron/machine-exec.js +1 -0
- package/dist/daemon/cron/machine-fire.d.ts +26 -0
- package/dist/daemon/cron/machine-fire.js +1 -0
- package/dist/daemon/cron/recurrence.d.ts +14 -0
- package/dist/daemon/cron/recurrence.js +1 -0
- package/dist/daemon/cron/run-token.d.ts +7 -0
- package/dist/daemon/cron/run-token.js +1 -0
- package/dist/daemon/cron-run.js +2 -2
- package/dist/daemon/crtrd-cli.js +1 -1
- package/dist/daemon/crtrd.d.ts +3 -3
- package/dist/daemon/crtrd.js +14 -14
- package/dist/daemon/drain.d.ts +25 -0
- package/dist/daemon/drain.js +9 -0
- package/dist/daemon/due-sources.d.ts +1 -1
- package/dist/daemon/due-sources.js +1 -1
- package/dist/daemon/due.js +8 -8
- package/dist/daemon/fleet.js +5 -5
- package/dist/daemon/import-dump-run.d.ts +1 -0
- package/dist/daemon/import-dump-run.js +5 -0
- package/dist/daemon/machine-daemon.d.ts +10 -0
- package/dist/daemon/machine-daemon.js +1 -0
- package/dist/daemon/machine-link/client.d.ts +29 -0
- package/dist/daemon/machine-link/client.js +1 -0
- package/dist/daemon/machine-link/config.d.ts +45 -0
- package/dist/daemon/machine-link/config.js +3 -0
- package/dist/daemon/machine-link/credential.d.ts +7 -0
- package/dist/daemon/machine-link/credential.js +2 -0
- package/dist/daemon/machine-link/forward.d.ts +16 -0
- package/dist/daemon/machine-link/forward.js +1 -0
- package/dist/daemon/machine-link/frames.d.ts +49 -0
- package/dist/daemon/machine-link/frames.js +1 -0
- package/dist/daemon/machine-link/holder.d.ts +73 -0
- package/dist/daemon/machine-link/holder.js +9 -0
- package/dist/daemon/machine-link/move-notify.d.ts +4 -0
- package/dist/daemon/machine-link/move-notify.js +1 -0
- package/dist/daemon/manage.d.ts +19 -3
- package/dist/daemon/manage.js +3 -3
- package/dist/daemon/reconcilers/cron-lane.js +1 -1
- package/dist/daemon/reconcilers/delivery-lane.js +2 -2
- package/dist/daemon/reconcilers/import-lane.d.ts +9 -0
- package/dist/daemon/reconcilers/import-lane.js +1 -0
- package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +1 -1
- package/dist/daemon/reconcilers/node-lifecycle/remote-wake.d.ts +3 -0
- package/dist/daemon/reconcilers/node-lifecycle/remote-wake.js +1 -1
- package/dist/daemon/reconcilers/node-lifecycle/tick.js +1 -1
- package/dist/daemon/reconcilers/purge-due-work.d.ts +13 -0
- package/dist/daemon/reconcilers/purge-due-work.js +1 -0
- package/dist/daemon/reconcilers/row-claim.d.ts +1 -0
- package/dist/daemon/reconcilers/row-claim.js +2 -2
- package/dist/daemon/runs/observer.js +3 -3
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +13 -13
- package/scripts/sandbox-start.mjs +185 -2
package/dist/core/canvas/pid.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
var
|
|
2
|
-
`)}s(p,"logPidLivenessDecision");function
|
|
3
|
-
`)){const r=/^\s*(\d+)\s+(\d+)\s+(.*)$/.exec(
|
|
4
|
-
`)){const o=r.trim();if(o==="")continue;const i=/^(\d+)\s+(\d+)\s+(\S+)\s+(\S+(?:\s+\S+){4})\s+(.+)$/.exec(o);if(i===null)continue;const u=Number(i[1]),c=Number(i[2]),l=i[3],
|
|
5
|
-
`)){const i=o.trim();if(i==="")continue;const u=i.indexOf(" ");if(u===-1)continue;const c=Number(i.slice(0,u)),l=i.slice(u+1).trim();!Number.isInteger(c)||c<=0||l===""||
|
|
1
|
+
var V=Object.defineProperty;var s=(n,e)=>V(n,"name",{value:e,configurable:!0});import{spawn as K,spawnSync as g}from"node:child_process";import{readFileSync as h,readdirSync as X,statSync as q}from"node:fs";import{readFile as T}from"node:fs/promises";import{readKernelBootId as v}from"./boot-id.js";function W(n){if(m())return D(n);try{const e=g("ps",["-o","stat=","-p",String(n)],{encoding:"utf8",timeout:2e3});if(e.error!=null||e.signal!=null||typeof e.stdout!="string"||typeof e.stderr=="string"&&e.stderr.trim()!=="")return"unknown";const t=e.stdout.trim();return t===""?"gone":t.charAt(0)==="Z"?"zombie":"live"}catch{return"unknown"}}s(W,"psProcessState");function S(n){if(n==null)return!1;try{process.kill(n,0)}catch(t){return t.code==="EPERM"}const e=W(n);return e!=="zombie"&&e!=="gone"}s(S,"isPidAlive");function dn(n){if(n==null)return!1;try{return process.kill(n,0),!0}catch(e){return e.code==="EPERM"}}s(dn,"isPidPresent");const y=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function w(n){const e=n.indexOf("#");if(e===-1)return{base:n,ticks:void 0};const t=n.slice(e+1);return{base:n.slice(0,e),ticks:/^\d+$/.test(t)?t:void 0}}s(w,"splitIdentity");function k(n,e){const t=w(n),r=w(e);return t.ticks!==void 0&&r.ticks!==void 0?y.test(t.base)&&y.test(r.base)?t.base===r.base&&t.ticks===r.ticks:t.ticks===r.ticks:t.base===r.base}s(k,"identitiesMatch");function an(n,e,t){const r=!!process.env.CRTR_DEBUG_PID_LIVENESS;if(n==null)return"dead";if(t===null)return r&&p(n,!1,e,"probe-null","ps snapshot failed \u2192 indeterminate","indeterminate"),"indeterminate";const o=t?.processes.get(n),i=t===void 0?S(n):o!==void 0&&!o.zombie;if(!i)return r&&p(n,i,e,t===void 0?"no-probe":"no-entry","process absent/zombie \u2192 dead","dead"),"dead";if(e==null)return r&&p(n,i,e,"no-probe","no baseline \u2192 trust process presence","alive"),"alive";let u;if(t===void 0){const l=J([n]);if(l==null)return r&&p(n,i,e,"probe-null","ps probe failed \u2192 indeterminate","indeterminate"),"indeterminate";if(u=l.get(n),u===void 0)return r&&p(n,i,e,"no-entry","no identity row for pid (race) \u2192 dead","dead"),"dead"}else u=o.identity;const c=k(u,e);return r&&p(n,i,e,"value",c?"identity matches expected \u2192 alive":"identity differs from expected \u2192 REUSED \u2192 dead",c?"alive":"dead",u),c?"alive":"dead"}s(an,"recordedPidLiveness");function p(n,e,t,r,o,i,u){const c=[`pid=${String(n)}`,`isPidAlive=${e}`,`expectedIdentity=${t===void 0?"undefined":t===null?"null":JSON.stringify(t)}`,`capturePidIdentities=${r}${r==="value"?`(${JSON.stringify(u)})`:""}`,`branch=${JSON.stringify(o)}`,`returned=${i}`];process.stderr.write(`[recordedPidLiveness] ${c.join(" ")}
|
|
2
|
+
`)}s(p,"logPidLivenessDecision");function Q(n,e="SIGTERM"){if(!(!Number.isInteger(n)||n<=0))try{process.kill(-n,e)}catch{}}s(Q,"killProcessGroup");function Y(n){try{const e=h(`/proc/${n}/stat`,"utf8"),t=e.lastIndexOf(")");if(t===-1)return null;const o=e.slice(t+2).trim().split(/\s+/)[19];return o!==void 0&&/^\d+$/.test(o)?o:null}catch{return null}}s(Y,"procStartTicks");const nn=6e4;function en(n,e=Date.now,t=nn){let r=null,o=0;return()=>{if(r!==null)return r;const i=e();if(i<o)return null;const u=n();return u!==null?r=u:(o=i+t,null)}}s(en,"makeBootIdCache");const M=en(v);function pn(n){const{base:e,ticks:t}=w(n);return t!==void 0&&!y.test(e)}s(pn,"isLegacyTicksIdentity");function mn(n){const{base:e,ticks:t}=w(n);return t!==void 0&&y.test(e)}s(mn,"isBootScopedIdentity");function A(n,e,t={}){const r=t.ticks!==void 0?t.ticks:Y(n);return r===null?e:`${(t.bootId!==void 0?t.bootId:M())??e}#${r}`}s(A,"composeIdentity");const R=["-Ao","pid=,ppid=,pgid=,stat=,lstart=,command="],_=2e3;function I(){if(m())return E();let n;try{const e=g("ps",[...R],{encoding:"utf8",timeout:_,maxBuffer:8388608});if(e.status!==0||typeof e.stdout!="string")return null;n=e.stdout}catch{return null}return Z(n)}s(I,"captureLivenessSnapshot");async function gn(){if(m())return F();const n=await new Promise(e=>{let t;try{t=K("ps",[...R])}catch{return e(null)}let r="",o=!1;const i=s(c=>{o||(o=!0,clearTimeout(u),e(c))},"settle"),u=setTimeout(()=>{try{t.kill("SIGKILL")}catch{}i(null)},_);u.unref?.(),t.stdout.on("data",c=>r+=c.toString()),t.stderr.on("data",()=>{}),t.on("error",()=>i(null)),t.on("close",c=>i(c===0?r:null))});return n===null?null:Z(n)}s(gn,"captureLivenessSnapshotAsync");function L(n){const e=n.indexOf("("),t=n.lastIndexOf(")");if(e===-1||t===-1||t<e)return null;const r=n.slice(t+2).trim().split(/\s+/),o=r[0],i=Number(r[1]),u=Number(r[2]),c=r[19];return o===void 0||o===""||!Number.isInteger(i)||i<0||!Number.isInteger(u)||u<0||c===void 0||!/^\d+$/.test(c)?null:{comm:n.slice(e+1,t),state:o,ppid:i,pgid:u,starttime:c}}s(L,"parseProcStat");function B(n,e){const t=n.replace(/\0+$/,"").split("\0").join(" ").trim();return t===""?`[${e}]`:t}s(B,"procCommandLine");let $;function m(){return $===void 0&&($=process.platform==="linux"&&v()!==null&&tn(process.pid)!==null),$}s(m,"procfsUsable");const a={root:"/proc",bootId:s(()=>M(),"bootId")};function tn(n,e=a){const t=b(n,e);return t==="unreadable"||t==="denied"||t==="gone"?null:t}s(tn,"procStatOf");function b(n,e=a){let t;try{t=h(`${e.root}/${n}/stat`,"utf8")}catch(r){return N(r)}return L(t)??"unreadable"}s(b,"readProcStat");function N(n){const e=n.code;return e==="ENOENT"||e==="ESRCH"?"gone":e==="EACCES"||e==="EPERM"?"denied":"unreadable"}s(N,"classifyProcReadError");function rn(n,e){const t=e.selfUid!==void 0?e.selfUid():process.getuid?.()??null;if(t===null)return"own-or-unknown";try{return q(`${e.root}/${n}`).uid===t?"own-or-unknown":"foreign"}catch(r){return N(r)==="gone"?"gone":"own-or-unknown"}}s(rn,"procOwnership");function C(n,e,t){return n==="gone"?"skip":n==="unreadable"||rn(e,t)==="own-or-unknown"?"fail":"skip"}s(C,"scanDisposition");function z(n,e=a){const t=e.bootId();return t===null?null:`${t}#${n.starttime}`}s(z,"procIdentity");function D(n,e=a){const t=b(n,e);return t==="gone"?"gone":t==="denied"||t==="unreadable"?"unknown":t.state==="Z"||t.state==="X"?"zombie":"live"}s(D,"procProcessState");function G(n,e=a){const t=new Map;for(const r of n){const o=b(r,e);if(o==="gone")continue;if(o==="unreadable"||o==="denied")return null;const i=z(o,e);if(i===null)return null;t.set(r,i)}return t}s(G,"procIdentities");function H(n,e=a){const t=b(n,e);if(t==="gone"||t==="unreadable"||t==="denied")return null;try{const r=B(h(`${e.root}/${n}/cmdline`,"utf8"),t.comm);return r===""?null:r}catch{return null}}s(H,"procCommand");function U(n,e,t,r,o){const i=z(r,n);if(i===null)return!1;e.processes.set(t,{zombie:r.state==="Z"||r.state==="X",pgid:r.pgid,identity:i,command:B(o,r.comm)});const u=e.childrenOf.get(r.ppid);return u===void 0?e.childrenOf.set(r.ppid,[t]):u.push(t),!0}s(U,"addProcfsRow");function j(n){try{return X(n).filter(e=>/^\d+$/.test(e)).map(Number).filter(e=>e>0)}catch{return null}}s(j,"procPids");function E(n=a){const e=j(n.root);if(e===null)return null;const t={processes:new Map,childrenOf:new Map};for(const r of e){const o=b(r,n);if(o==="gone"||o==="denied"||o==="unreadable"){if(C(o==="gone"?"gone":o,r,n)==="skip")continue;return null}let i="";try{i=h(`${n.root}/${r}/cmdline`,"utf8")}catch(u){const c=u.code;if(c==="ENOENT"||c==="ESRCH")continue}if(!U(n,t,r,o,i))return null}return t}s(E,"readProcfsSnapshot");async function F(n=a){const e=j(n.root);if(e===null)return null;const t={processes:new Map,childrenOf:new Map},r=64;for(let o=0;o<e.length;o+=r){const i=e.slice(o,o+r),u=await Promise.all(i.map(async c=>{try{const l=L(await T(`${n.root}/${c}/stat`,"utf8"));if(l===null)return"failed";const f=await T(`${n.root}/${c}/cmdline`,"utf8").catch(d=>{if(d.code==="ENOENT"||d.code==="ESRCH")throw d;return""});return{pid:c,stat:l,cmdline:f}}catch(l){return C(N(l),c,n)==="skip"?"gone":"failed"}}));for(const c of u)if(c!=="gone"&&(c==="failed"||!U(n,t,c.pid,c.stat,c.cmdline)))return null}return t}s(F,"readProcfsSnapshotAsync");function bn(){if(m()){const n=E();if(n===null)return null;const e=new Map;for(const[t,r]of n.childrenOf)for(const o of r)e.set(o,t);return[...n.processes].map(([t,r])=>({pid:t,ppid:e.get(t)??0,command:r.command}))}try{const n=g("ps",["-ax","-o","pid=,ppid=,command="],{encoding:"utf8",timeout:2e3,maxBuffer:8388608});if(n.status!==0||typeof n.stdout!="string")return null;const e=[];for(const t of n.stdout.split(`
|
|
3
|
+
`)){const r=/^\s*(\d+)\s+(\d+)\s+(.*)$/.exec(t);r!==null&&e.push({pid:Number(r[1]),ppid:Number(r[2]),command:r[3].trim()})}return e}catch{return null}}s(bn,"listProcessTable");const hn={procIdentities:G,procProcessState:D,procCommand:H,readProcfsSnapshot:E,readProcfsSnapshotAsync:F};function Z(n){const e=new Map,t=new Map;for(const r of n.split(`
|
|
4
|
+
`)){const o=r.trim();if(o==="")continue;const i=/^(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+(?:\s+\S+){4})\s+(.+)$/.exec(o);if(i===null)continue;const u=Number(i[1]),c=Number(i[2]),l=Number(i[3]),f=i[4],d=i[5],O=i[6].trim();if(!Number.isInteger(u)||u<=0||!Number.isInteger(c)||c<0||O==="")continue;e.set(u,{zombie:f.startsWith("Z"),pgid:l,identity:A(u,d),command:O});const P=t.get(c);P===void 0?t.set(c,[u]):P.push(u)}return{processes:e,childrenOf:t}}s(Z,"parseLivenessTable");function x(n,e){const t=[],r=new Set([n]),o=[...e.get(n)??[]];for(;o.length>0;){const i=o.shift();if(r.has(i))continue;r.add(i),t.push(i);const u=e.get(i);u!==void 0&&o.push(...u)}return t}s(x,"descendantPidsFrom");function on(n){if(!Number.isInteger(n)||n<=0)return[];const e=I();return e===null?[]:x(n,e.childrenOf)}s(on,"descendantPids");function Sn(n){const e=I();return e===null?null:[...e.processes].filter(([,t])=>t.pgid===n&&!t.zombie).map(([t])=>t)}s(Sn,"pidsInProcessGroup");function J(n){const e=n.filter(r=>Number.isInteger(r)&&r>0),t=new Map;if(e.length===0)return t;if(m())return G(e);try{const r=g("ps",["-o","pid=,lstart=","-p",e.join(",")],{encoding:"utf8",timeout:2e3});if(r.error!=null||r.signal!=null||typeof r.stdout!="string"||typeof r.stderr=="string"&&r.stderr.trim()!=="")return null;for(const o of r.stdout.split(`
|
|
5
|
+
`)){const i=o.trim();if(i==="")continue;const u=i.indexOf(" ");if(u===-1)continue;const c=Number(i.slice(0,u)),l=i.slice(u+1).trim();!Number.isInteger(c)||c<=0||l===""||t.set(c,A(c,l))}return t}catch{return null}}s(J,"capturePidIdentities");function yn(n){if(!Number.isInteger(n)||n<=0)return null;if(m())return H(n);try{const e=g("ps",["-o","command=","-p",String(n)],{encoding:"utf8",timeout:2e3});if(e.status!==0||typeof e.stdout!="string")return null;const t=e.stdout.trim();return t===""?null:t}catch{return null}}s(yn,"capturePidCommand");function wn(n,e,t){if(!Number.isInteger(n)||n<=0)return{tree:[],identities:null,reused:!1};const r=I();if(r===null)return{tree:[n],identities:null,reused:!1};const o=r.processes.get(n)?.identity;if(e!=null&&o!==void 0&&!k(o,e))return{tree:[],identities:null,reused:!0};const u=t!==void 0&&Number.isInteger(t)&&t>0?new Set([t,...x(t,r.childrenOf)]):void 0,c=[n,...x(n,r.childrenOf)].filter(f=>!u?.has(f)),l=new Map;for(const f of c){const d=r.processes.get(f)?.identity;d!==void 0&&l.set(f,d)}return{tree:c,identities:l,reused:!1}}s(wn,"captureTeardownSnapshot");function kn(n,e="SIGTERM",t){const r=t!=null?J(n):void 0;for(const o of n){if(t!=null&&r!=null){const i=t.get(o),u=r.get(o);if(i!==void 0&&u!==void 0&&!k(u,i))continue}if(Q(o,e),!(!Number.isInteger(o)||o<=0))try{process.kill(o,e)}catch{}}}s(kn,"killProcessTreePids");function In(n){return S(n)?!0:on(n).some(e=>S(e))}s(In,"isProcessTreeAlive");function $n(n){return n.some(e=>S(e))}s($n,"isAnyPidAlive");export{I as captureLivenessSnapshot,gn as captureLivenessSnapshotAsync,yn as capturePidCommand,J as capturePidIdentities,wn as captureTeardownSnapshot,A as composeIdentity,on as descendantPids,k as identitiesMatch,$n as isAnyPidAlive,mn as isBootScopedIdentity,pn as isLegacyTicksIdentity,S as isPidAlive,dn as isPidPresent,In as isProcessTreeAlive,Q as killProcessGroup,kn as killProcessTreePids,bn as listProcessTable,en as makeBootIdCache,L as parseProcStat,Sn as pidsInProcessGroup,B as procCommandLine,hn as procfsBackendForTest,E as readProcfsSnapshot,F as readProcfsSnapshotAsync,an as recordedPidLiveness};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var b=Object.defineProperty;var o=(n,e)=>b(n,"name",{value:e,configurable:!0});import{resolve as _}from"node:path";import{ExitCode as j}from"../../types.js";import{envNodeId as A}from"../../shared/env.js";import{cliClient as f}from"../../commands/api-client.js";import{apiErrorToCliError as h}from"../io.js";import{ApiError as l}from"../../api/index.js";import{defineBranch as y,defineLeaf as L}from"../command.js";import{qualifiedTopLevelCommandName as M,resolveCommandRegistry as N}from"../command-manifests/registry.js";import{executeExternalLeaf as S}from"./transport/exec-invoke.js";import{invokeHttpLeaf as U}from"./transport/http-invoke.js";import{readPathParam as P}from"./transport/http-plan.js";import{sha256Hex as H,signedHeaderDigest as R}from"./transport/sign.js";import{needsAppToken as K}from"../plugin-objects-manifest.js";async function $(n){const e=await f().request("GET",`/v1/providers/${encodeURIComponent(n)}/tools`);return new Set(e.groups.flatMap(a=>a.leaves.map(t=>t.tool)))}o($,"allowedProviderTools");function g(n,e,a){return n.children.some(t=>t.kind==="leaf"?a.has([...e,t.name].join(" ")):g(t,[...e,t.name],a))}o(g,"hasAllowedLeaf");function I(n,e,a){return n.children.filter(t=>t.tier!=="hidden"&&(t.kind==="leaf"?a.has([...e,t.name].join(" ")):g(t,[...e,t.name],a))).length}o(I,"visibleChildCount");function E(n,e,a){return async()=>{if(!A())return n.help;const t=await a();return{...n.help,listing:n.help.listing?.flatMap(d=>{const i=n.children.find(c=>c.name===d.name);if(i?.kind==="leaf")return t.has([...e,i.name].join(" "))?[d]:[];if(i?.kind!=="branch")return[];const s=[...e,i.name];if(!g(i,s,t))return[];const p=I(i,s,t);return[{...d,...d.subCount!==void 0?{subCount:p}:{}}]})}}}o(E,"providerHelp");const O="Runs on one of your devices. Add `--machine <machine_id>` to pick which; without it the call goes to your only device, and with several the answer is `machine_required` listing each one's machine_id. The `runtime:` prefix on the id is optional.";function w(n,e,a,t=!1){return{outputKind:"object",...t?{machineSelector:!0}:{},run:o(async(d,i)=>{const s={...d};for(const p of a.params){if(!("type"in p))continue;const c=p.name.replace(/-([a-z])/g,(u,m)=>m.toUpperCase()),r=s[c];p.type==="file"&&typeof r=="string"?s[c]=_(r):p.type==="path"&&r!==void 0&&(s[c]=P(`provider command \`${n} ${e}\``,p,r))}try{return await f({timeoutMs:0}).request("POST","/v1/provider/call",{provider:n,tool:e,arguments:s,...i?.machine===void 0?{}:{machine:i.machine}},{timeout:0})}catch(p){throw p instanceof l?h(p,void 0,{providerCall:!0}):p}},"run")}}o(w,"providerLeafAdapter");function k(n,e){return/^[A-Za-z0-9_-]{1,64}$/.test(n)&&n!=="crtr"&&!e.has(n)}o(k,"validRootName");function re(n,e){const a=[],t=[];for(const i of n){const{provider:s,commands:p}=i;if(!Array.isArray(p)||i.status!=="available"&&!k(s,e))continue;if(i.status!=="available"){const r=i.error??"bundle installation pending",u=`Unavailable (${i.status}): ${r}`,m=y({name:s,rootEntry:{concept:i.display_name,desc:u,useWhen:u},help:{name:s,summary:u},children:[],refusal:{code:"provider_unavailable",message:u,exit:j.NETWORK,next:"The daemon retries bundle installation on grant sync; retry after the provider becomes available."}});t.push(m);continue}const c=o(r=>r.kind==="branch"?{...r,children:r.children.map(c)}:{...r,rest:{method:"GET",path:"/",params:{}}},"helpNode");for(const r of p){if(r.kind!=="branch"||!Array.isArray(r.children)||!k(r.name,e))continue;const u=c(r);a.push({contributor:{name:s,scope:"user"},manifestName:r.name,node:i.groups.length===0?{...u,rootEntry:void 0}:u,adaptLeaf:o((m,T)=>w(s,T.slice(1).join(" "),m,i.transport==="machine"),"adaptLeaf")})}}const d=D(N(a,e));for(const i of d){const s=n.find(r=>r.status==="available"&&r.commands.some(u=>u.name===i.name||M({name:r.provider},u.name)===i.name));if(s?.status!=="available")continue;const p=new Set(s.groups.flatMap(r=>r.leaves.map(u=>u.tool))),c=o((r,u)=>{r.effectiveHelp=E(r,u,async()=>p);for(const m of r.children)m.kind==="branch"&&c(m,[...u,m.name])},"attachHelp");c(i,[])}return[...d,...t]}o(re,"composeSandboxProviderSubtrees");async function q(n){try{const{token:e,socket:a}=await f().plugins.appToken(n);return{CRTR_APP_TOKEN:e,CRTR_DAEMON_SOCKET:a}}catch(e){if(e instanceof l&&(e.status===404||e.status===403))return;throw e instanceof l?h(e):e}}o(q,"appEnvFor");function x(n,e){return(a,t)=>{const d=n.transport,i=e(t);if(n.provider!==void 0)return w(n.provider.id,i.slice(1).join(" "),a,d.kind==="machine");if(d.kind==="exec"){const p=d.executable;if(p===void 0)throw new Error(`exec plugin "${n.plugin.name}" has an executable leaf without transport.executable`);return{outputKind:"object",run:o(async c=>{const r=K(n.plugin.manifest)?await q(n.plugin.name):void 0;return S({plugin:n.plugin,executable:p,commandPath:i,output:a.output,...r!==void 0?{appEnv:r}:{}},c)},"run")}}if(d.kind==="machine")throw new Error(`machine plugin "${n.plugin.name}" is not a capability provider`);const s=a;return{outputKind:s.rest.streaming===!0?"jsonl":"object",run:o((p,c)=>U({registration:{name:n.plugin.name,endpoint:d.endpoint,...d.authEnv!==void 0?{authEnv:d.authEnv}:{}},...n.manifest?.baseUrl!==void 0?{baseUrl:n.manifest.baseUrl}:{},...n.manifest?.timeouts!==void 0?{timeouts:n.manifest.timeouts}:{},params:s.params,rest:s.rest,output:s.output,commandPath:i},p,c,{callHeaders:o(r=>f().plugins.callHeaders(n.plugin.name,{method:r.method,url:r.url.href,body_sha256:H(r.body),...R(r.headers)}).then(u=>u.headers,u=>{throw u instanceof l?h(u):u}),"callHeaders")}),"run")}}}o(x,"adaptLeaf");function te(n){return{contributions:n.map(e=>({contributor:{name:e.plugin.name,scope:e.plugin.scope,sourceMarketplace:e.plugin.sourceMarketplace},node:e.node,manifestName:e.manifestName,adaptLeaf:x(e,a=>[e.manifestName,...a.slice(1)]),...e.provider!==void 0?{provider:e.provider}:{}})),coreMounts:[],issues:[]}}o(te,"adaptPluginContributions");function ie(n){return n.map(e=>({parent:e.parent,plugin:e.plugin.name,contributor:{name:e.plugin.name,scope:e.plugin.scope,sourceMarketplace:e.plugin.sourceMarketplace},node:e.node,...e.provider!==void 0?{provider:e.provider}:{},adaptLeaf:x({...e,manifestName:e.node.name},a=>e.provider===void 0?[...a]:[e.provider.id,...a.slice(e.parent.length)])}))}o(ie,"adaptPluginCoreMounts");function D(n){return n.contributions.map(e=>v(e,e.node,[e.node.name]))}o(D,"composeExternalSubtrees");function se(n,e){return n.filter(a=>a.parent.join("\0")===e.join("\0")).map(a=>a.node.kind==="branch"?v(a,a.node,[...e,a.node.name],a.provider===void 0?1:e.length):C(a,a.node,[...e,a.node.name]))}o(se,"composeCoreBranchMounts");function v(n,e,a,t=1){const d=e.rootEntry===void 0?void 0:{concept:e.rootEntry.concept,desc:e.rootEntry.description,useWhen:e.rootEntry.whenToUse},i=y({name:e.name,description:e.description,whenToUse:e.whenToUse,...e.tier!==void 0?{tier:e.tier}:{},...n.plugin!==void 0?{plugin:n.plugin}:{},...d!==void 0?{rootEntry:d}:{},help:{name:a.join(" "),summary:e.summary,...e.model!==void 0?{model:e.model}:{},...a.length===1&&n.extensionIssue!==void 0?{extensionIssue:n.extensionIssue}:{}},...e.passthrough!==void 0?{passthrough:e.passthrough}:{},children:e.children.map(s=>s.kind==="branch"?v(n,s,[...a,s.name],t):C(n,s,[...a,s.name]))});return n.provider!==void 0&&(i.effectiveHelp=E(i,a.slice(t),()=>$(n.provider.id))),i}o(v,"buildBranch");function C(n,e,a){const t=n.adaptLeaf(e,a);return L({name:e.name,description:e.description,whenToUse:e.whenToUse,...e.tier!==void 0?{tier:e.tier}:{},...n.plugin!==void 0?{plugin:n.plugin}:{},...t.machineSelector===!0?{machineSelector:!0}:{},help:{name:a.join(" "),summary:e.summary,...t.machineSelector===!0?{guide:O}:{},params:e.params,output:e.output,outputKind:t.outputKind,effects:[...e.effects]},run:t.run})}o(C,"buildLeaf");export{te as adaptPluginContributions,ie as adaptPluginCoreMounts,se as composeCoreBranchMounts,D as composeExternalSubtrees,re as composeSandboxProviderSubtrees};
|
package/dist/core/command.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import type { RootHelp, RootEntry, BranchHelp, LeafHelp, EffectiveLeafHelp, AsyncHelpState, InputParam, SubTier } from './help.js';
|
|
2
2
|
import { CrtrError } from './errors.js';
|
|
3
|
+
import type { ApiError } from '../api/errors.js';
|
|
4
|
+
import { type ExitCodeValue } from '../types.js';
|
|
3
5
|
/** Runtime context passed to a leaf's run function. Includes metadata about
|
|
4
6
|
* which parameters were explicitly provided vs. defaulted. Used by configured
|
|
5
7
|
* CLIs to distinguish intentionally-empty stdin from absent stdin. */
|
|
@@ -71,6 +73,12 @@ export interface LeafDef {
|
|
|
71
73
|
/** Suppress the dispatcher's trailing newline for byte-exact stdout. */
|
|
72
74
|
stdoutExact?: (result: Record<string, unknown>) => boolean;
|
|
73
75
|
}
|
|
76
|
+
export interface BranchRefusal {
|
|
77
|
+
code: string;
|
|
78
|
+
message: string;
|
|
79
|
+
next: string;
|
|
80
|
+
exit: ExitCodeValue;
|
|
81
|
+
}
|
|
74
82
|
export interface BranchDef {
|
|
75
83
|
kind: 'branch';
|
|
76
84
|
name: string;
|
|
@@ -109,14 +117,18 @@ export interface BranchDef {
|
|
|
109
117
|
bin: string;
|
|
110
118
|
installHint: string;
|
|
111
119
|
};
|
|
112
|
-
/**
|
|
113
|
-
|
|
120
|
+
/** Every invocation of this branch except its own `-h` throws this refusal before any client is built: a granted provider
|
|
121
|
+
* without a usable bundle (`provider_unavailable`), or a Northlight preview before sign-in (`needs_northlight_account`). */
|
|
122
|
+
refusal?: BranchRefusal;
|
|
114
123
|
children: (LeafDef | BranchDef)[];
|
|
115
124
|
}
|
|
116
125
|
export interface RootDef {
|
|
117
126
|
kind: 'root';
|
|
118
127
|
help: RootHelp;
|
|
119
128
|
subtrees: BranchDef[];
|
|
129
|
+
/** Why a moved computer could not read the account's provider commands (`not_signed_in`, `northlight_unreachable`, `move_unfinished`). The tree
|
|
130
|
+
* was built without them, so a top-level name it lacks may be one of those commands: the dispatcher raises this instead of `unknown_path`. */
|
|
131
|
+
providerCatalogFailure?: ApiError | CrtrError;
|
|
120
132
|
}
|
|
121
133
|
export declare function defineLeaf(opts: {
|
|
122
134
|
name: string;
|
|
@@ -153,6 +165,7 @@ export declare function defineBranch(opts: {
|
|
|
153
165
|
bin: string;
|
|
154
166
|
installHint: string;
|
|
155
167
|
};
|
|
168
|
+
refusal?: BranchRefusal;
|
|
156
169
|
children: (LeafDef | BranchDef)[];
|
|
157
170
|
}): BranchDef;
|
|
158
171
|
/** Walk the whole tree and collect every node's SlashSpec (depth-first). Used
|
|
@@ -187,12 +200,8 @@ export declare function renderNode(node: AnyNode): string;
|
|
|
187
200
|
* the full path to the matched node (not just its local name), so the recovery
|
|
188
201
|
* hint is a command that actually exists. No fuzzy matching. */
|
|
189
202
|
export declare function unknownPathError(node: AnyNode, path: string[], bad: string): CrtrError;
|
|
190
|
-
/**
|
|
191
|
-
|
|
192
|
-
/** An unknown top-level command on a runtime never joined to a directory
|
|
193
|
-
* (no `runtime.issuer`). Every `cloud` leaf and every other provider command
|
|
194
|
-
* is installed by grant sync, which runs only on a joined runtime, so the
|
|
195
|
-
* likeliest cause of a missing command there is the missing join. */
|
|
203
|
+
/** An unknown top-level command on a computer with no Northlight account (no `runtime/machine.json`, no `runtime.issuer`):
|
|
204
|
+
* a command that comes from Northlight needs the account. */
|
|
196
205
|
export declare function notJoinedError(bad: string): CrtrError;
|
|
197
206
|
/** Options for parseArgv. */
|
|
198
207
|
export interface ParseArgvOptions {
|
package/dist/core/command.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
var z=Object.defineProperty;var f=(e,i)=>z(e,"name",{value:i,configurable:!0});import{renderRoot as
|
|
1
|
+
var z=Object.defineProperty;var f=(e,i)=>z(e,"name",{value:i,configurable:!0});import{renderRoot as q,renderBranch as V,renderLeafArgv as T,withAsyncHelpState as G,collapseWideChildren as L,rootPromotedChildren as B}from"./help.js";import{beginPreview as K,publishPreviewResult as D,readStdinRaw as J,peekStdinRaw as Q,emit as X,handle as Y,setJsonOutput as Z,isJsonOutput as ee}from"./io.js";import{renderResult as ne}from"./render.js";import{CrtrError as R}from"./errors.js";import{validateValue as te}from"../api/command-manifest/result.js";import{isManifestStaleError as ie}from"./manifest-stale.js";import{operationIdContext as re}from"./events/operation-id.js";import{ExitCode as _}from"../types.js";import{envNodeId as ae,providerProjectionOverBrokerRelay as oe}from"../shared/env.js";import{readFileSync as se}from"node:fs";import{spawnSync as de}from"node:child_process";import{constants as le}from"node:os";import{resolve as fe}from"node:path";function F(e){if(e===null||typeof e!="object"||Object.isFrozen(e))return e;for(const i of Object.values(e))F(i);return Object.freeze(e)}f(F,"freezeCommandMetadata");function qe(e){const i=F(e.help),{helpState:r}=e;if(r!==void 0&&e.effectiveHelp!==void 0)throw new Error(`leaf ${e.name} declares both helpState and effectiveHelp`);return Object.freeze({kind:"leaf",name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:i,effectiveHelp:e.effectiveHelp??(r===void 0?void 0:n=>G(i,r,n)),slash:e.slash===void 0?void 0:F(e.slash),hookEligible:e.hookEligible,plugin:e.plugin,machineSelector:e.machineSelector,run:e.run,render:e.render,preview:e.preview,stdoutExact:e.stdoutExact})}f(qe,"defineLeaf");function A(e){return e.kind==="branch"&&e.children.length>0&&M(e)===0?"hidden":e.tier??"normal"}f(A,"effectiveTier");function M(e){return e.kind!=="branch"?0:(e.help.listing??[]).filter(i=>i.tier!=="hidden").length}f(M,"visibleSubCount");function Ve(e){if(e.passthrough!==void 0&&e.children.length>0)throw new Error(`defineBranch("${e.name}"): a passthrough branch must declare no children (found ${e.children.length})`);return e.help.listing=e.children.map(i=>{const r=M(i);return{name:i.name,description:i.description??"",whenToUse:i.whenToUse??"",tier:A(i),kind:i.kind,...i.plugin!==void 0?{plugin:i.plugin}:{},...r>0?{subCount:r}:{}}}),{kind:"branch",name:e.name,description:e.description,whenToUse:e.whenToUse,tier:e.tier,help:e.help,effectiveHelp:e.effectiveHelp,rootEntry:e.rootEntry,extensible:e.extensible,plugin:e.plugin,slash:e.slash,passthrough:e.passthrough,refusal:e.refusal,children:e.children}}f(Ve,"defineBranch");function Be(e){const i=[],r=f(n=>{if(n.slash!==void 0&&i.push(n.slash),n.kind==="branch")for(const a of n.children)r(a)},"visit");for(const n of e.subtrees)r(n);return i}f(Be,"collectSlashSpecs");function Je(e){const i=e.subtrees.filter(n=>n.rootEntry!==void 0&&A(n)!=="hidden").map(n=>{const a=(n.help.listing??[]).filter(s=>s.tier!=="hidden"),l=B(a).map(s=>({path:`${n.name} ${s.name}`,desc:s.tier==="important"?s.description:void 0}));return{name:n.name,concept:n.rootEntry.concept,desc:n.rootEntry.desc,useWhen:n.rootEntry.useWhen,dynamicState:n.rootEntry.dynamicState,subcommands:l.length>0?l:void 0,otherSubcommandCount:a.length-l.length}});return{kind:"root",help:{tagline:e.tagline,commands:i,globals:e.globals},subtrees:e.subtrees}}f(Je,"defineRoot");function ce(e){if(e.kind==="root")return e.subtrees.filter(i=>A(i)!=="hidden").map(i=>i.name);if(e.kind==="branch"){const{listed:i,unlisted:r}=L(e.children.filter(n=>A(n)!=="hidden"));return[...i.map(n=>n.name),...r>0?[`(and ${r} more)`]:[]]}return[]}f(ce,"childNames");function ue(e,i){let r=e;const n=[];let a=0;for(;a<i.length;){const l=i[a];if(l==="-h"||l==="--help")break;if(r.kind==="root"){const s=r.subtrees.find(o=>o.name===l);if(s===void 0)break;r=s,n.push(s.name),a++}else if(r.kind==="branch"){const s=r.children.find(o=>o.name===l);if(s===void 0)break;r=s,n.push(s.name),a++}else break}return{node:r,path:n,remaining:i.slice(a)}}f(ue,"walk");function he(e){return e.kind==="root"?q(e.help):e.kind==="branch"?V(e.help):T(e.help)}f(he,"renderNode");async function I(e,i,r){if(r!==void 0&&e.kind==="leaf")return T(e.effectiveHelp!==void 0?await e.effectiveHelp(r):e.help,r);const n=e.kind==="leaf"&&e.effectiveHelp!==void 0?T(await e.effectiveHelp()):e.kind==="branch"&&e.effectiveHelp!==void 0?V(await e.effectiveHelp()):he(e);if(i.length===0)return n;const{hasEffectiveCommandPlugins:a}=await import("./command-plugins/presence.js");if(!a())return n;const[{collectHelpAddenda:l},{SUBTREE_NAMES:s,coreCommandPathsCovering:o,extensibleCoreBranches:k}]=await Promise.all([import("./command-plugins/help-addenda.js"),import("../build-root.js")]),v=await l(i.join(" "),{reservedCoreNames:new Set(s),coreCommandPaths:o,extensibleCoreBranches:k});return n+v.map(C=>`
|
|
2
2
|
|
|
3
|
-
<plugin-help plugin="${
|
|
4
|
-
${
|
|
5
|
-
</plugin-help>`).join("")}f(
|
|
6
|
-
`),process.exitCode=
|
|
7
|
-
`),process.exitCode=
|
|
8
|
-
`),process.exitCode=
|
|
9
|
-
`),process.exitCode=
|
|
10
|
-
`))}}catch(o){if(ie(o))throw o;
|
|
3
|
+
<plugin-help plugin="${C.plugin}">
|
|
4
|
+
${C.text}
|
|
5
|
+
</plugin-help>`).join("")}f(I,"renderNodeWithAddenda");function me(e){return e.some(i=>i==="-h"||i==="--help")}f(me,"helpRequested");function pe(e,i){if(e.kind!=="leaf"||i.length!==2)return;const[r,n]=i;if(n!=="-h"&&n!=="--help"||!r.startsWith("--")||r.includes("="))return;const a=r.slice(2);return(e.help.params??[]).find(s=>s.kind==="flag"&&s.name===a)?.focusedHelp!==void 0?a:void 0}f(pe,"focusedHelpFlag");function ge(e,i){let r=0;for(let n=0;n<e.length;n++){if(r===i.length)return e.slice(n);e[n]!=="--json"&&e[n]===i[r]&&r++}return[]}f(ge,"rawArgsAfterPath");function we(e,i){const r=de(e.bin,i,{stdio:"inherit"});if(r.error!==void 0){const n=r.error;throw new R("binary_missing",`${e.bin} could not be run (${n.code??n.message})`,_.USAGE,{received:e.bin,next:e.installHint})}if(r.status!==null&&process.exit(r.status),r.signal!==null){const n=le.signals[r.signal];n!==void 0&&process.exit(128+n)}process.exit(1)}f(we,"runPassthrough");function ve(e,i,r){const n=ce(e),a=n.length>0?n.join(", "):"(none)",l=i.length>0?`crtr ${i.join(" ")} -h`:"crtr -h",s=n.some(o=>o.startsWith("(and "));return new R("unknown_path",`unknown subcommand: ${r}`,_.USAGE,{received:r,next:s?`Valid children include: ${a}. Run \`${l}\` for how to find the rest.`:`Valid children: ${a}. Run \`${l}\` for the full list.`})}f(ve,"unknownPathError");function be(e){const i="If it comes from Northlight, it needs a Northlight account: `crtr sys signin`.";return new R("not_joined",`\`crtr ${e}\` isn't a command here. ${i}`,_.USAGE,{received:e,next:i})}f(be,"notJoinedError");async function xe(){if(oe())return!1;try{const{readConfig:e}=await import("./config.js"),{readMachineConfig:i}=await import("../daemon/machine-link/config.js");return i()===void 0&&!e("user").runtime?.issuer}catch{return!1}}f(xe,"runtimeUnjoined");function p(e,i,r,n,a){return new R(e,i,_.USAGE,{received:r,field:n,next:a!==void 0?a:"Run the command with -h to see the parameter schema."})}f(p,"parseArgvError");function y(e){return e.replace(/-([a-z])/g,(i,r)=>r.toUpperCase())}f(y,"flagNameToKey");async function ke(e,i,r){const n={},a=new Set,l=new Map,s=e.filter(t=>t.kind==="positional"),o=e.find(t=>t.kind==="stdin"),k=e.find(t=>t.kind==="context-file"),v=e.filter(t=>t.kind==="flag"),C=new Map(v.map(t=>[t.name,t])),S=v.some(t=>t.sensitive===!0);for(const t of v)t.type==="bool"?n[y(t.name)]=t.default!==void 0?t.default:!1:t.default!==void 0&&(n[y(t.name)]=t.default);const w=[];let b=0,N=!1,u=0;for(;u<i.length;){const t=i[u];if(t==="--"&&!N){N=!0,u++;continue}if(!N&&t.startsWith("--")){const h=t.indexOf("=");let d,E;if(h!==-1?(d=t.slice(2,h),E=t.slice(h+1)):d=t.slice(2),d==="context-file"){if(k===void 0)throw p("unknown_flag","unknown flag: --context-file","--context-file",void 0,"This leaf does not accept --context-file.");const c=E!==void 0?E:i[++u];if(c===void 0)throw p("missing_parameter","--context-file requires a PATH argument","--context-file","context-file");let x;try{x=se(c,"utf8")}catch{throw p("invalid_type",`--context-file: cannot read file: ${c}`,c,"context-file","Provide a readable file path.")}let W;try{W=JSON.parse(x)}catch{throw p("invalid_type",`--context-file: file is not valid JSON: ${c}`,c,"context-file","Ensure the file contains a valid JSON object.")}const O=y(k.name);n[O]=W,l.set(O,fe(c)),a.add(O),u++;continue}const P=C.get(d);if(P===void 0)throw p("unknown_flag",`unknown flag: --${d}`,`--${d}`,void 0,"Use --<flag-name> for declared flags only. Run -h for the schema.");const H=y(d);if(a.add(H),P.type==="bool"){if(E!==void 0)throw p("invalid_type",`boolean flag --${d} takes no value`,`--${d}=${E}`,d,`Use --${d} (presence = true) with no value.`);n[H]=!0,u++;continue}const $=E!==void 0?E:i[++u];if($===void 0||E===void 0&&$.startsWith("--")){const c=P.focusedHelp!==void 0&&(r?.leafPath?.length??0)>0?`Run \`crtr ${r.leafPath.join(" ")} --${d} -h\` and read the focused value contract before retrying.`:void 0;throw p("missing_parameter",`--${d} requires a value`,`--${d}`,d,c)}let j;if(P.type==="int"){const c=Number($);if(!Number.isInteger(c))throw p("invalid_type",`--${d} must be an integer`,$,d,`Provide an integer value for --${d}.`);j=c}else if(P.type==="enum"){const c=P.choices;if(c!==void 0&&!c.includes($))throw p("invalid_type",`--${d} must be one of: ${c.join(", ")}`,$,d,`Retry with one of: ${c.join(", ")}.`);j=$}else if(P.type==="json"){let c;try{c=JSON.parse($)}catch{throw p("invalid_type",`--${d} is not valid JSON`,$,d,`Pass one JSON value for --${d}; run -h for its shape.`)}const x=P.shape===void 0?null:te(P.shape,c,`--${d}`);if(x!==null)throw p("invalid_type",`${x.path} must be ${x.expected}, got ${x.receivedType}`,$,x.path,`Fix ${x.path}; run -h for the shape of --${d}.`);j=c}else j=$;if(P.repeatable===!0){const c=n[H],x=Array.isArray(c)?c:[];x.push(j),n[H]=x}else n[H]=j;u++;continue}const m=s[b];if(m!==void 0){const h=y(m.name);if(m.repeatable===!0){const d=n[h]??[];d.push(t),n[h]=d}else n[h]=t,b++;w.push(t),a.add(h),u++;continue}if(w.length>0){if(o!==void 0&&s.length===0){w.push(t),u++;continue}throw o!==void 0?p("bad_invocation",`unexpected extra positional argument (value withheld: this leaf reads "${o.name}" from stdin, never argv)`,void 0,void 0,"Pipe it on stdin instead of passing it as a positional argument."):p("bad_invocation",S?"unexpected extra positional argument (value withheld: this leaf takes a secret)":`unexpected extra positional argument: ${t}`,S?void 0:i.join(" "),void 0,"Use declared flags or positional parameters. Run -h for the schema.")}if(s.length===0&&(o===void 0||o.allowPositional===!1))throw p("bad_invocation",S?"this leaf takes no positional arguments (value withheld: this leaf takes a secret)":`this leaf takes no positional arguments: ${t}`,S?void 0:t,void 0,"Use --flag for parameters. Run -h for the schema.");w.push(t),u++}let g=!1;if(o!==void 0){if(w.length>1&&s.length===0){const t=o.positionalStdinConflict?.(n);throw t!==void 0?p("bad_invocation",t.message,void 0,t.field,t.next):p("bad_invocation",`unexpected extra positional argument (value withheld: this leaf reads "${o.name}" from stdin, never argv)`,void 0,void 0,"Pipe it on stdin instead of passing it as a positional argument.")}if(w.length>0&&s.length===0&&o.allowPositional!==!1){const t=w[0];if(t==="-"){const m=await J();(m.trim()!==""||!o.required)&&(n[y(o.name)]=m),g=!0}else{if((await Q()).trim()!==""){const h=o.positionalStdinConflict?.(n);throw p("bad_invocation",h?.message??`both a positional argument ("${t}") and piped stdin were provided for ${o.name}`,t,h?.field??o.name,h?.next??"Pass exactly one: either the positional argument or piped stdin, not both.")}n[y(o.name)]=t,g=!0}}else{const t=await J();t!==""&&(t.trim()!==""||!o.required)&&(n[y(o.name)]=t,g=!0)}}g&&o!==void 0&&a.add(y(o.name));for(const t of e){if(t.kind!=="flag"&&t.kind!=="positional"||t.defaultFromEnv===void 0)continue;const m=y(t.name);if(a.has(m))continue;const h=process.env[t.defaultFromEnv];h===void 0||h===""||(n[m]=t.kind==="positional"&&t.repeatable===!0?[h]:h,a.add(m))}const U=[];for(const t of e){const m=y(t.name),h=t.kind==="flag"&&t.type==="bool"&&!a.has(m);if(t.required&&(h||n[m]===void 0||n[m]===null)){const d=t.kind==="positional"?`positional ${t.name.toUpperCase()}`:t.kind==="flag"?`--${t.name}`:t.kind==="context-file"?"--context-file":"stdin";U.push({display:d,name:t.name})}}if(U.length>0){const t=U.map(d=>d.display).join(", "),m=r?.leafPath,h=m!==void 0&&m.length>0?`\`crtr ${m.join(" ")} -h\``:"this command with -h";throw p("missing_parameter",U.length===1?`required parameter is missing: ${t}`:`required parameters are missing: ${t}`,void 0,U.map(d=>d.name).join(", "),`Run ${h} and read the full schema before re-issuing \u2014 do not guess parameters one at a time.`)}return r?.onProvidedParams!==void 0&&r.onProvidedParams(a),r?.onContextFilePaths!==void 0&&r.onContextFilePaths(l),n}f(ke,"parseArgv");function $e(e){const i=[];let r;for(let n=0;n<e.length;n++){const a=e[n];if(a==="--"){i.push(...e.slice(n));break}const l=a.startsWith("--machine=")?a.slice(10):void 0;if(a!=="--machine"&&l===void 0){i.push(a);continue}const s=l??e[n+1];if(s===void 0||s===""||l===void 0&&s.startsWith("-"))throw p("missing_parameter","--machine requires a machine id","--machine","machine","Pass the machine_id of one of the candidates a `machine_required` answer lists.");l===void 0&&n++,r=s}return r===void 0?{rest:i}:{rest:i,machine:r}}f($e,"takeMachineSelector");const ye=new Set(["--json","--no-autostart"]);async function Me(e,i){const r=i.slice(2);r.includes("--json")&&Z(!0);const n=r.filter(o=>!ye.has(o));if(n.length===0||n.length===1&&(n[0]==="-h"||n[0]==="--help")){const o=ae()?{...e.help,commands:await Promise.all(e.help.commands.map(async k=>{const v=e.subtrees.find(b=>b.name===k.name);if(v?.effectiveHelp===void 0)return k;const S=((await v.effectiveHelp()).listing??[]).filter(b=>b.tier!=="hidden"),w=B(S).map(b=>({path:`${v.name} ${b.name}`,desc:b.tier==="important"?b.description:void 0}));return{...k,subcommands:w.length?w:void 0,otherSubcommandCount:S.length-w.length}}))}:e.help;process.stdout.write(q(o)+`
|
|
6
|
+
`),process.exitCode=_.SUCCESS;return}if(n.length===1&&(n[0]==="--version"||n[0]==="-v")){const{readPackageVersion:o}=await import("../commands/sys/shared.js");process.stdout.write(o()+`
|
|
7
|
+
`),process.exitCode=_.SUCCESS;return}let{node:a,path:l,remaining:s}=ue(e,n);try{if(a.kind==="branch"&&a.passthrough!==void 0&&we(a.passthrough,ge(r,l)),a.kind==="branch"&&a.refusal!==void 0&&s[0]!=="-h"&&s[0]!=="--help")throw new R(a.refusal.code,a.refusal.message,a.refusal.exit,{next:a.refusal.next});if((a.kind==="root"||a.kind==="branch")&&s.length>0&&s[0]!=="-h"&&s[0]!=="--help")throw a.kind==="root"&&!s[0].startsWith("-")&&a.providerCatalogFailure!==void 0?a.providerCatalogFailure:a.kind==="root"&&!s[0].startsWith("-")&&await xe()?be(s[0]):ve(a,l,s[0]);if(me(s)){process.stdout.write(await I(a,l,pe(a,s))+`
|
|
8
|
+
`),process.exitCode=_.SUCCESS;return}if(a.kind==="root"||a.kind==="branch"){process.stdout.write(await I(a,l)+`
|
|
9
|
+
`),process.exitCode=_.SUCCESS;return}const o=a;K(l.join(" "));const k=o.help.params!==void 0?o.help.params:[];let v=new Set,C=new Map,S=s,w;if(o.machineSelector===!0&&!k.some(g=>g.kind==="flag"&&g.name==="machine")){const g=$e(s);S=g.rest,w=g.machine}const b=await ke(k,S,{leafPath:l,onProvidedParams:f(g=>{v=g},"onProvidedParams"),onContextFilePaths:f(g=>{C=g},"onContextFilePaths")}),N={providedParams:v,contextFilePaths:C,...w===void 0?{}:{machine:w}},u=await re.fresh(()=>o.run(b,N));if(D(u==null?u:o.preview?.(u)??u),u!=null)if(ee())X(u);else{const g=o.render!==void 0?o.render(u):ne(u,o.help);process.stdout.write(g+(o.stdoutExact?.(u)?"":`
|
|
10
|
+
`))}}catch(o){if(ie(o))throw o;Y(o)}}f(Me,"runCli");export{ye as GLOBAL_TOKENS,Be as collectSlashSpecs,Ve as defineBranch,qe as defineLeaf,Je as defineRoot,be as notJoinedError,ke as parseArgv,he as renderNode,Me as runCli,$e as takeMachineSelector,ve as unknownPathError,ue as walk};
|
|
@@ -3,3 +3,6 @@
|
|
|
3
3
|
* deployment's `CRTR_DIRECTORY_URL` (the daemon refuses to boot without it), never from one person's settings. Anything else (a
|
|
4
4
|
* Mac or a legacy single-person install) reads the user config's `runtime.issuer`, null while the runtime has not joined. */
|
|
5
5
|
export declare function directoryIssuer(): string | null;
|
|
6
|
+
/** Northlight's directory: where a person signs in or creates an account, and the default for `crtr sys signin` and
|
|
7
|
+
* `crtr sys import start` when nothing names another (`--directory-url`, `runtime/machine.json`, the stored sign-in). */
|
|
8
|
+
export declare const NORTHLIGHT_DIRECTORY_URL = "https://account.northlight.ai";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var e=Object.defineProperty;var o=(r,t)=>e(r,"name",{value:t,configurable:!0});import{isCloudRole as n}from"../canvas/person-state.js";import{readConfig as s}from"../config.js";function c(){const r=n()?process.env.CRTR_DIRECTORY_URL:s("user").runtime?.issuer;return r?r.replace(/\/+$/,""):null}o(c,"directoryIssuer");const l="https://account.northlight.ai";export{l as NORTHLIGHT_DIRECTORY_URL,c as directoryIssuer};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { CrtrError } from '../errors.js';
|
|
2
|
+
export declare const personCredentialPath: () => string;
|
|
3
|
+
export declare const personBearerPath: () => string;
|
|
4
|
+
/** What the credential file holds: the secret and the directory that issued it (a Mac needs the directory before it has a `machine.json`). */
|
|
5
|
+
export interface PersonCredential {
|
|
6
|
+
directoryUrl: string;
|
|
7
|
+
secret: string;
|
|
8
|
+
}
|
|
9
|
+
export declare const notSignedIn: (why: string) => CrtrError;
|
|
10
|
+
export declare function readPersonCredential(): PersonCredential | undefined;
|
|
11
|
+
export interface SignInPrompt {
|
|
12
|
+
verificationUri: string;
|
|
13
|
+
userCode: string;
|
|
14
|
+
}
|
|
15
|
+
/** What the directory approved: the account, and a bearer the new secret was just exchanged for (not cached until `signIn` writes it). */
|
|
16
|
+
export interface ApprovedSignIn {
|
|
17
|
+
account: {
|
|
18
|
+
id: string;
|
|
19
|
+
display: string;
|
|
20
|
+
};
|
|
21
|
+
bearer: string;
|
|
22
|
+
}
|
|
23
|
+
export interface SignInOptions {
|
|
24
|
+
sleep?: (ms: number) => Promise<void>;
|
|
25
|
+
/** Called after approval and before anything is written. A returned error refuses the sign-in: the new credential is revoked at the directory,
|
|
26
|
+
* nothing is written, and `signIn` throws that error. */
|
|
27
|
+
accept?: (approved: ApprovedSignIn) => Promise<CrtrError | undefined>;
|
|
28
|
+
}
|
|
29
|
+
/** Pair this computer with the directory: start a developer login of kind `crtr`, show the code, poll until the person allows it, then store the secret.
|
|
30
|
+
* Nothing durable is written until the directory has approved, so an abandoned sign-in leaves the previous state untouched. */
|
|
31
|
+
export declare function signIn(directoryUrl: string, show: (prompt: SignInPrompt & {
|
|
32
|
+
complete: string;
|
|
33
|
+
}) => void, opts?: SignInOptions): Promise<{
|
|
34
|
+
account: string;
|
|
35
|
+
credentialId: string;
|
|
36
|
+
}>;
|
|
37
|
+
/** A person bearer for the control plane: the cached one while it has more than 30 seconds left, else a fresh exchange of the stored secret.
|
|
38
|
+
* A revoked, expired or unknown credential is `not_signed_in`. */
|
|
39
|
+
export declare function personBearer(directoryUrl?: string): Promise<string>;
|
|
40
|
+
/** A `fetch` for the control plane at `apiUrl`: each request carries the person's bearer for `directoryUrl` (fetched per request, so a long command never sends
|
|
41
|
+
* an ended one), a sign-in refusal crosses as its own `CrtrError`, and a request that never got an HTTP answer is `northlight_unreachable` (a caller's own abort
|
|
42
|
+
* stays what it was). */
|
|
43
|
+
export declare function northlightFetch(directoryUrl: string, apiUrl: string): typeof fetch;
|
|
44
|
+
/** The account the stored sign-in acts as: a fresh exchange (the token answer names it), whose bearer is cached like `personBearer`'s. */
|
|
45
|
+
export declare function personAccount(directoryUrl?: string): Promise<{
|
|
46
|
+
id: string;
|
|
47
|
+
display: string;
|
|
48
|
+
}>;
|
|
49
|
+
/** The control plane a person bearer is good for: its `aud`, which the directory signs as its `CRTR_PUBLIC_API_URL` (design D6). */
|
|
50
|
+
export declare function controlPlaneUrl(directoryUrl?: string): Promise<string>;
|
|
51
|
+
/** Delete the stored credential and cached bearer without calling the directory. */
|
|
52
|
+
export declare function forgetSignIn(): void;
|
|
53
|
+
/** Revoke the stored credential at the directory (best effort when it is already gone) and delete both files. Returns whether one was stored. */
|
|
54
|
+
export declare function signOut(): Promise<{
|
|
55
|
+
signedOut: boolean;
|
|
56
|
+
revoked: boolean;
|
|
57
|
+
}>;
|
|
58
|
+
/** The sign-in a command needs when the person has none yet: pairs once with `directoryUrl`, then returns a bearer. */
|
|
59
|
+
export declare function ensureSignedIn(directoryUrl: string, show: (prompt: SignInPrompt & {
|
|
60
|
+
complete: string;
|
|
61
|
+
}) => void): Promise<string>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var C=Object.defineProperty;var o=(e,n)=>C(e,"name",{value:n,configurable:!0});import{createHash as J,randomBytes as N}from"node:crypto";import{renameSync as R,rmSync as _}from"node:fs";import{hostname as z}from"node:os";import{join as x}from"node:path";import{CrtrError as M,general as f}from"../errors.js";import{atomicWriteJson as E,readJsonOrNull as A}from"../fs-utils.js";import{secretsDir as b}from"../layout.js";import{ExitCode as F}from"../../types.js";import{northlightUnreachable as D}from"./reach.js";const k=o(()=>x(b(),"person-credential"),"personCredentialPath"),y=o(()=>x(b(),"person-bearer.json"),"personBearerPath"),H=o(()=>x(b(),"person-credential.pending"),"pendingPath"),G=3e4,K=o(e=>J("sha256").update(e).digest("hex"),"sha256Hex"),l=o(e=>e.replace(/\/+$/,""),"trimUrl"),v=o(e=>new M("not_signed_in",e,F.USAGE,{next:"Run `crtr sys signin`, then retry."}),"notSignedIn");function h(){const e=A(k());if(!(e===null||typeof e.secret!="string"||typeof e.directoryUrl!="string"))return{directoryUrl:e.directoryUrl,secret:e.secret}}o(h,"readPersonCredential");function p(e,n){const r=e!==null&&typeof e=="object"?e:{},t=r.error;if(t!==null&&typeof t=="object"){const s=t;return{code:String(s.code??"error"),message:String(s.message??`HTTP ${n}`)}}return typeof t=="string"?{code:t,message:String(r.error_description??r.message??t)}:{code:"error",message:`the directory answered HTTP ${n}`}}o(p,"errorOf");async function w(e,n,r,t){let s;try{s=await fetch(`${l(e)}${n}`,{method:"POST",headers:{"content-type":"application/json",...t===void 0?{}:{authorization:`Bearer ${t}`}},body:JSON.stringify(r),signal:AbortSignal.timeout(3e4)})}catch(d){throw D(d,e)}const i=await s.text();let c={};try{c=i===""?{}:JSON.parse(i)}catch{}return{status:s.status,body:c}}o(w,"call");const g=o((e,n)=>{const r=p(n,e);throw f(`the directory refused: ${r.message}`,{code:r.code})},"refuse");async function V(e,n,r={}){const t=l(e),s=r.sleep??(a=>new Promise(u=>setTimeout(u,a))),i=`nld_${N(32).toString("base64url")}`,c=await w(t,"/v1/developer/logins",{credential_hash:K(i),name:z().slice(0,64),platform:process.platform==="darwin"||process.platform==="win32"?process.platform:"linux",sdk_version:"crtr",kind:"crtr"});c.status!==200&&c.status!==201&&g(c.status,c.body);const d=c.body;n({verificationUri:d.verification_uri,userCode:d.user_code,complete:d.verification_uri_complete});let $=Math.max(1,d.interval)*1e3;const j=Date.now()+d.expires_in*1e3;for(;;){if(Date.now()>j)throw f("the sign-in code expired before it was allowed: run `crtr sys signin` again",{code:"expired_token"});await s($);const a=await w(t,"/v1/developer/logins/poll",{device_code:d.device_code});if(a.status===200){const m=a.body,P=await S(t,i),B=await r.accept?.({account:m.account,bearer:P.access_token});if(B!==void 0)throw await T(t,i),B;return E(H(),{directoryUrl:t,secret:i},{mode:384}),R(H(),k()),U(t,P),{account:m.account.display,credentialId:m.credential_id}}const u=p(a.body,a.status).code;if(!(a.status===400&&u==="authorization_pending")){if(a.status===400&&u==="slow_down"){$+=5e3;continue}if(a.status===400&&u==="access_denied")throw f("the sign-in was declined in the browser",{code:u});if(a.status===400&&u==="expired_token")throw f("the sign-in code expired before it was allowed: run `crtr sys signin` again",{code:u});g(a.status,a.body)}}}o(V,"signIn");const I=new Set(["credential_revoked","credential_expired","credential_unknown"]);async function S(e,n,r=!1){const t=await w(e,"/v1/developer/token",{},n);if(t.status===401&&I.has(p(t.body,401).code))throw r&&_(y(),{force:!0}),v(`the sign-in on this computer is no longer valid (${p(t.body,401).code})`);t.status!==200&&g(t.status,t.body);const s=t.body;if(typeof s.access_token!="string"||typeof s.expires_in!="number")throw f("the directory answered without a bearer token");const i=s.account;return{access_token:s.access_token,expires_in:s.expires_in,...typeof i?.id=="string"&&typeof i.display=="string"?{account:{id:i.id,display:i.display}}:{}}}o(S,"exchange");function U(e,n){E(y(),{directoryUrl:e,access_token:n.access_token,expires_at_ms:Date.now()+n.expires_in*1e3},{mode:384})}o(U,"writeBearer");async function O(e){const n=h();if(n===void 0)throw v("this computer is not signed in");const r=l(e??n.directoryUrl),t=A(y());if(t!==null&&t.directoryUrl===r&&typeof t.access_token=="string"&&t.expires_at_ms-Date.now()>G)return t.access_token;const s=await S(r,n.secret,!0);return U(r,s),s.access_token}o(O,"personBearer");function oe(e,n){return async(r,t)=>{const s=new Headers(t?.headers);s.set("authorization",`Bearer ${await O(e)}`);try{return await fetch(r,{...t,headers:s})}catch(i){throw t?.signal?.aborted===!0?i:D(i,n)}}}o(oe,"northlightFetch");async function se(e){const n=h();if(n===void 0)throw v("this computer is not signed in");const r=l(e??n.directoryUrl),t=await S(r,n.secret,!0);if(U(r,t),t.account===void 0)throw f("the directory's token answer names no account");return t.account}o(se,"personAccount");async function ie(e){const n=await O(e);let r;try{r=JSON.parse(Buffer.from(n.split(".")[1]??"","base64url").toString("utf8")).aud}catch{r=void 0}const t=Array.isArray(r)?r[0]:r;if(typeof t!="string"||!/^https?:\/\//.test(t))throw f("the directory's sign-in token names no control plane (no http(s) `aud`)");return l(t)}o(ie,"controlPlaneUrl");async function T(e,n){const r=await w(e,"/v1/developer/token/revoke",{},n);return r.status===200?!0:r.status===401&&I.has(p(r.body,401).code)?!1:g(r.status,r.body)}o(T,"revoke");function W(){_(k(),{force:!0}),_(y(),{force:!0})}o(W,"forgetSignIn");async function ae(){const e=h();if(e===void 0)return{signedOut:!1,revoked:!1};const n=await T(e.directoryUrl,e.secret);return W(),{signedOut:!0,revoked:n}}o(ae,"signOut");async function ce(e,n){return h()===void 0&&await V(e,n),O(e)}o(ce,"ensureSignedIn");export{ie as controlPlaneUrl,ce as ensureSignedIn,W as forgetSignIn,oe as northlightFetch,v as notSignedIn,se as personAccount,O as personBearer,y as personBearerPath,k as personCredentialPath,h as readPersonCredential,V as signIn,ae as signOut};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
var c=Object.defineProperty;var o=(t,n)=>c(t,"name",{value:n,configurable:!0});import{CrtrError as i}from"../errors.js";import{ExitCode as s}from"../../types.js";const h="northlight_unreachable",a="Check this computer's internet connection, then retry.";function f(t){const n=t?.cause,e=t;for(const r of[n?.code,n?.message,e?.code,e?.message])if(typeof r=="string"&&r!=="")return r;return String(t)}o(f,"causeText");function p(t,n){const e=`crtr cannot reach Northlight (${new URL(n).host}): ${f(t)}.`;return new i(h,e,s.NETWORK,{next:a})}o(p,"northlightUnreachable");export{h as NORTHLIGHT_UNREACHABLE,p as northlightUnreachable};
|
|
@@ -68,6 +68,9 @@ export declare function isProviderBundleRow(row: {
|
|
|
68
68
|
source_marketplace: string | null;
|
|
69
69
|
source_path: string | null;
|
|
70
70
|
}, granted: ReadonlySet<string>): boolean;
|
|
71
|
+
/** `person`'s (`user:<id>`) active, unexpired grants on this runtime, every grantee: what a cloud role reads for one request's person, where `localGrants()`
|
|
72
|
+
* would hand back every person the fleet serves. */
|
|
73
|
+
export declare function activeGrantsOfPerson(person: string): Promise<ActiveGrant[]>;
|
|
71
74
|
/** Every mirrored grant, whatever its status. */
|
|
72
75
|
export declare function allGrants(): Promise<GrantRecord[]>;
|
|
73
76
|
/** Whether a grant's `expires_at` has passed. An unparsable time reads as expired (fail closed). */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
var w=Object.defineProperty;var t=(e,r)=>w(e,"name",{value:r,configurable:!0});import{createHash as O}from"node:crypto";import{parseScope as h}from"@north-light/identity";import{openDb as d,withCanvasWrite as m}from"../canvas/db.js";import{isUnsandboxedMachine as j}from"../runtime/unsandboxed.js";import{replaceWrittenBy as A,userSubject as b}from"../permissions/facts.js";import{GROUP_TYPE as R}from"../permissions/builtin.js";const D=["not_started","pending","ready","failed","unavailable"],k=["ultra","strong","medium","light"];function I(){const e=process.env.CRTR_RUNTIME;return e?{sql:" AND runtime = ?",args:[e]}:{sql:"",args:[]}}t(I,"runtimeScope");async function
|
|
1
|
+
var w=Object.defineProperty;var t=(e,r)=>w(e,"name",{value:r,configurable:!0});import{createHash as O}from"node:crypto";import{parseScope as h}from"@north-light/identity";import{openDb as d,withCanvasWrite as m}from"../canvas/db.js";import{isUnsandboxedMachine as j}from"../runtime/unsandboxed.js";import{replaceWrittenBy as A,userSubject as b}from"../permissions/facts.js";import{GROUP_TYPE as R}from"../permissions/builtin.js";const D=["not_started","pending","ready","failed","unavailable"],k=["ultra","strong","medium","light"];function I(){const e=process.env.CRTR_RUNTIME;return e?{sql:" AND runtime = ?",args:[e]}:{sql:"",args:[]}}t(I,"runtimeScope");async function i(e,r,n=[]){const s=I();return await(await d()).prepare(`SELECT ${e} FROM grants WHERE ${r}${s.sql}`).all(...n,...s.args)}t(i,"grantRows");async function u(e,r,n=[]){return(await i(e,r,n))[0]}t(u,"grantRow");function l(e){if(!Array.isArray(e))throw new TypeError("grant scopes must be a list");return e.map(r=>{if(!r||typeof r!="object"||typeof r.scope!="string"||!["always","ask"].includes(r.mode))throw new TypeError("invalid grant scope entry");return{scope:r.scope,mode:r.mode}})}t(l,"entries");function L(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{state:r,message:n,next:s,step:a}=e;return typeof r!="string"||!D.includes(r)||n!==void 0&&typeof n!="string"||a!==void 0&&typeof a!="string"||typeof s!="string"||!s?null:{state:r,...n===void 0?{}:{message:n},next:s,...a===void 0?{}:{step:a}}}t(L,"dependencyOf");function E(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r={};for(const[n,s]of Object.entries(e)){const a=L(s);a&&(r[n]=a)}return r}t(E,"dependenciesOf");function C(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{base_url:r,rungs:n}=e;if(typeof r!="string"||!r||!n||typeof n!="object"||Array.isArray(n))return null;const s={};for(const a of k){const o=n[a];typeof o=="string"&&o&&(s[a]=o)}return Object.keys(s).length?{base_url:r.replace(/\/+$/,""),rungs:s}:null}t(C,"inferenceEndpointOf");function _(e){if(e.computed_rev!==e.rev||e.scopes_json===null||e.resolved_scopes_json===null)throw new TypeError("grant columns are not computed for this revision");return{id:e.id,runtime:e.runtime,sub:e.sub,grantee:e.grantee,rev:e.rev,scopes:l(JSON.parse(e.scopes_json)),resolved_scopes:l(JSON.parse(e.resolved_scopes_json)),spend_cap:e.spend_cap,expires_at:e.expires_at,status:e.status,revoked_at:e.revoked_at,approved_at:e.approved_at,payer:e.payer,dependencies:E(JSON.parse(e.dependencies_json??"{}")),run_notice_url:e.run_notice_url??null,inference_endpoint:e.inference_endpoint_json?C(JSON.parse(e.inference_endpoint_json)):null,synced_at:e.synced_at??""}}t(_,"record");function p(e){if(!e||e.status!=="active")return null;try{return $(_(e))}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(p,"active");function g(e){return e.grantee!=="app:terminal"&&j()}t(g,"mirrorOnly");async function Y(){return(await G()).filter(e=>!g(e))}t(Y,"localGrants");const x="[A-Za-z0-9_-]{1,64}",M=new RegExp(`^(${x}):(${x})$`);function F(e){const r=new Set;for(const n of e)if(n.status==="active")for(const{scope:s}of n.resolved_scopes){const a=M.exec(s);a&&a[1]!=="crtr"&&r.add(a[1])}return[...r].sort()}t(F,"providerIdsFromGrants");async function Z(e){const r=await i("*","sub = ? AND status = 'active'",[e]);return new Set(F(r.flatMap(n=>{try{return[_(n)]}catch(s){if(s instanceof TypeError||s instanceof SyntaxError)return[];throw s}})))}t(Z,"grantedProviderIds");function K(e,r){return e.source_marketplace===null&&e.source_path===null&&r.has(e.name)}t(K,"isProviderBundleRow");async function Q(e){return(await i("*","sub = ? AND status = 'active'",[e])).flatMap(n=>{const s=p(n);return s===null?[]:[s]})}t(Q,"activeGrantsOfPerson");async function G(){return(await i("*","true")).flatMap(e=>{try{return[_(e)]}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return[];throw r}})}t(G,"allGrants");function U(e){return e.expires_at!==null&&!(Date.parse(e.expires_at)>Date.now())}t(U,"grantExpired");function $(e){if(e.status!=="active"||U(e))return null;try{const r=e.resolved_scopes.map(({scope:n})=>h(n));return{...e,resolved:e.resolved_scopes.flatMap(({mode:n},s)=>n==="always"?[r[s]]:[])}}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t($,"activeRecord");function v(e){return Array.isArray(e)?`[${e.map(v).join(",")}]`:e&&typeof e=="object"?`{${Object.keys(e).sort().filter(r=>e[r]!==void 0).map(r=>`${JSON.stringify(r)}:${v(e[r])}`).join(",")}}`:JSON.stringify(e)??"null"}t(v,"canonicalJson");function ee(e){const r={rev:e.rev,resolved_scopes:e.resolved_scopes,spend_cap:e.spend_cap,payer:e.payer,dependencies:e.dependencies??{},...e.inference_endpoint?{inference_endpoint:e.inference_endpoint}:{}};return O("sha256").update(v(r)).digest("base64url").slice(0,22)}t(ee,"grantVersion");async function re(e){return!!await u("1","id = ?",[e])}t(re,"hasGrant");async function ne(e){return await u("rev, status","id = ?",[e])??null}t(ne,"grantSyncState");async function te(e,r){return await u("rev, status","grantee = ? AND sub = ?",[e,r])??null}t(te,"grantSyncStateFor");async function H(e){const r=await u("*","id = ?",[e]);if(!r)return null;try{return _(r)}catch(n){if(n instanceof TypeError||n instanceof SyntaxError)return null;throw n}}t(H,"grantRecord");async function se(e){const r=await u("*","id = ?",[e]);if(!r)return{standing:"inactive",sub:null,grantee:null,status:null,removal_done_at:null,record:null};const n={sub:r.sub,grantee:r.grantee,status:r.status,removal_done_at:r.removal_done_at??null};if(r.status!=="active")return{...n,standing:"inactive",record:null};if(r.computed_rev!==r.rev)return{...n,standing:"lagging",record:null};const s=p(r);return s===null?{...n,standing:"inactive",record:null}:{...n,standing:"active",record:s}}t(se,"grantStanding");async function ae(e){return(await i("id",e)).map(r=>r.id)}t(ae,"grantIdsWhere");async function oe(e){return(e?(await H(e))?.payer:null)??"user"}t(oe,"payerOfGrant");async function W(e){return p(await u("*","id = ?",[e]))}t(W,"activeGrant");async function ce(e,r){return p(await u("*","grantee = ? AND sub = ?",[e,r]))}t(ce,"grantFor");async function ie(e){if(g({grantee:e}))return null;const r=await i("*","grantee = ? AND status = 'active'",[e]);if(r.length>1)throw new Error(`multiple active grants for ${e}`);return p(r[0])}t(ie,"activeGrantForApp");async function ue(e){const r=await W(e);return r&&r.run_notice_url&&!g(r)?{url:r.run_notice_url,sub:r.sub,grantee:r.grantee}:null}t(ue,"runNoticeTarget");async function pe(e){const r=await i("*","grantee = 'app:terminal' AND sub = ? AND status = 'active'",[e]);if(r.length>1)throw new Error(`multiple active terminal grants for ${e}`);return p(r[0])}t(pe,"terminalGrant");async function de(){return(await(await d()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value??null}t(de,"grantsSource");async function le(e){return(await(await d()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value===e}t(le,"hasSyncedGrants");async function _e(e){return(await e.prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value!=="local"?0:(await e.prepare("DELETE FROM runtime_state WHERE key = 'grants_source'").run(),Number((await e.prepare("DELETE FROM grants").run()).changes))}t(_e,"dropLocalGrantMirror");async function fe(){return await m(async e=>Number((await e.prepare("UPDATE grants SET computed_rev = rev WHERE computed_rev IS NULL AND scopes_json IS NOT NULL AND resolved_scopes_json IS NOT NULL").run()).changes))}t(fe,"backfillComputedRev");function ye(e){if(e===void 0)return[];if(!Array.isArray(e))throw new TypeError("invalid grant sync response");return e.map(r=>{const{team:n,members:s}=r??{};if(typeof n!="string"||!/^team:\S+$/.test(n)||!Array.isArray(s)||s.some(a=>typeof a!="string"||!/^user:[^\s*]+$/.test(a)))throw new TypeError("invalid grant sync response");return{team:n,members:s}})}t(ye,"teamsOf");async function ge(e,r,n,s=[]){const a=new Set;for(const o of e){if(a.has(o.id)||!o.id||!o.runtime||!o.sub||!o.grantee||!Number.isInteger(o.rev)||!["active","revoked","removed"].includes(o.status))throw new TypeError("invalid grant sync response");a.add(o.id),l(o.scopes),l(o.resolved_scopes)}await m(async o=>{let f=0;const y=[...a];f+=Number((await(y.length?o.prepareUncached(`DELETE FROM grants WHERE id NOT IN (${y.map(()=>"?").join(",")})`).run(...y):o.prepareUncached("DELETE FROM grants").run())).changes);const S=o.prepare(`INSERT INTO grants
|
|
2
2
|
(id, runtime, sub, grantee, rev, scopes_json, resolved_scopes_json, spend_cap, expires_at,
|
|
3
3
|
status, revoked_at, approved_at, payer, dependencies_json, run_notice_url, inference_endpoint_json, synced_at, computed_rev)
|
|
4
4
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
@@ -13,4 +13,4 @@ var w=Object.defineProperty;var t=(e,r)=>w(e,"name",{value:r,configurable:!0});i
|
|
|
13
13
|
grants.run_notice_url, grants.inference_endpoint_json, grants.computed_rev)
|
|
14
14
|
IS DISTINCT FROM (excluded.runtime, excluded.sub, excluded.grantee, excluded.rev, excluded.scopes_json, excluded.resolved_scopes_json,
|
|
15
15
|
excluded.spend_cap, excluded.expires_at, excluded.status, excluded.revoked_at, excluded.approved_at, excluded.payer,
|
|
16
|
-
excluded.dependencies_json, excluded.run_notice_url, excluded.inference_endpoint_json, excluded.computed_rev)`);for(const
|
|
16
|
+
excluded.dependencies_json, excluded.run_notice_url, excluded.inference_endpoint_json, excluded.computed_rev)`);for(const c of e)f+=Number((await S.run(c.id,c.runtime,c.sub,c.grantee,c.rev,JSON.stringify(c.scopes),JSON.stringify(c.resolved_scopes),c.spend_cap,c.expires_at,c.status,c.revoked_at,c.approved_at,c.payer,JSON.stringify(E(c.dependencies)),c.run_notice_url??null,c.inference_endpoint?JSON.stringify(c.inference_endpoint):null,c.synced_at,c.rev)).changes);await A(o,"directory",R,"member",s.flatMap(({team:c,members:T})=>T.map(N=>({resourceId:c,subject:b(N)})))),await o.prepare("INSERT INTO runtime_state (key, value) VALUES ('grants_source', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(n),f>0&&await o.prepare("INSERT INTO runtime_state (key, value) VALUES ('grants_generation', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(runtime_state.value AS INTEGER) + 1 AS TEXT)").run()})}t(ge,"replaceGrants");async function ve(){const e=await(await d()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_generation'").get();return e?Number(e.value):0}t(ve,"grantsGeneration");export{W as activeGrant,ie as activeGrantForApp,Q as activeGrantsOfPerson,$ as activeRecord,G as allGrants,fe as backfillComputedRev,E as dependenciesOf,_e as dropLocalGrantMirror,U as grantExpired,ce as grantFor,ae as grantIdsWhere,H as grantRecord,se as grantStanding,ne as grantSyncState,te as grantSyncStateFor,ee as grantVersion,Z as grantedProviderIds,ve as grantsGeneration,de as grantsSource,re as hasGrant,le as hasSyncedGrants,C as inferenceEndpointOf,K as isProviderBundleRow,Y as localGrants,g as mirrorOnly,oe as payerOfGrant,F as providerIdsFromGrants,ge as replaceGrants,ue as runNoticeTarget,ye as teamsOf,pe as terminalGrant};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** D106 first boot on a Mac: copy every document text still in the old `<canvas home>/bodies/<hh>/<hash>` files into the `bodies` table.
|
|
2
|
-
* A file is copied only when the sha256 of its text equals its name; any other is skipped and reported.
|
|
3
|
-
*
|
|
2
|
+
* A file is copied only when the sha256 of its text equals its name; any other is skipped and reported. A text with NULs is stored as `canonical`
|
|
3
|
+
* makes it (NUL → U+FFFD, under the hash of that text) and every revision naming the file's hash is rekeyed to the canonical hash in the same transaction.
|
|
4
|
+
* Rows go in with `ON CONFLICT DO NOTHING` in batches of one transaction each, so an interrupted run resumes and a finished one changes nothing.
|
|
4
5
|
* The files stay in place for a rollback to the previous build. Cloud roles never call this: their text was never on a disk that survives. */
|
|
5
6
|
export interface BodiesBootCopyReport {
|
|
6
7
|
scanned: number;
|
|
@@ -12,3 +13,16 @@ export interface BodiesBootCopyReport {
|
|
|
12
13
|
}>;
|
|
13
14
|
}
|
|
14
15
|
export declare function copyBodyFilesToTable(root?: string): Promise<BodiesBootCopyReport>;
|
|
16
|
+
/** Rows #822 stored under the hash of the text before its NULs became U+FFFD. */
|
|
17
|
+
export interface BodyHashRepairReport {
|
|
18
|
+
rekeyed: number;
|
|
19
|
+
removed: number;
|
|
20
|
+
unresolved: string[];
|
|
21
|
+
}
|
|
22
|
+
/** D106 correction, every role, before readers serve: a `bodies` row whose key is not the sha256 of its text is rekeyed to the canonical hash.
|
|
23
|
+
* Rows are found by the database hashing every text and paged by key. A mismatch is repaired only when an intact original — the retained
|
|
24
|
+
* `<home>/bodies/<hh>/<hash>` file, else a repo ref's body that hashes to its own `body_hash` — becomes exactly the stored text under `storable`.
|
|
25
|
+
* Each page is one transaction: the canonical row is stored (or confirmed to hold the same text), every revision naming the old hash is rekeyed,
|
|
26
|
+
* and the invalid old-key row is deleted — the one place a body row is removed, as correction of an invalid duplicate, because leaving it would
|
|
27
|
+
* keep every future dump invalid. A mismatch with no such original is never rehashed: it is returned in `unresolved`. A re-run changes nothing. */
|
|
28
|
+
export declare function repairBodyHashes(root?: string): Promise<BodyHashRepairReport>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var S=Object.defineProperty;var d=(n,s)=>S(n,"name",{value:s,configurable:!0});import{existsSync as u,readdirSync as E,readFileSync as b}from"node:fs";import{join as f}from"node:path";import{openDb as w,withCanvasWrite as R}from"../canvas/db.js";import{crtrHome as m}from"../canvas/paths.js";import{canonical as x,hashOf as y,store as g,storable as v}from"./bodies.js";import{readTree as D}from"./repo-sync/mirror.js";import{parseDoc as F}from"./repo-sync/ref-format.js";const O=200;async function T(n,s,p){return(await n.prepare("UPDATE revisions SET body_hash = ? WHERE body_hash = ?").run(p,s)).changes}d(T,"rekeyRevisions");async function P(n=f(m(),"bodies")){const s={scanned:0,copied:0,mismatched:[],nulReplaced:[]};if(!u(n))return s;const p=new Set((await(await w()).prepare("SELECT hash FROM bodies").all()).map(o=>o.hash));let r=[];const h=d(async()=>{if(r.length===0)return;const o=r;r=[],await R(async t=>{for(const a of o)await g(t,a.hash,a.text)&&(s.copied+=1),a.file!==a.hash&&await T(t,a.file,a.hash)})},"flush");for(const o of E(n,{withFileTypes:!0}))if(!(!o.isDirectory()||!/^[0-9a-f]{2}$/.test(o.name)))for(const t of E(f(n,o.name))){if(!/^[0-9a-f]{64}$/.test(t)||(s.scanned+=1,p.has(t)))continue;const a=b(f(n,o.name,t),"utf8");if(y(a)!==t||!t.startsWith(o.name)){s.mismatched.push(t);continue}const{text:c,hash:e}=x(a,t);c!==a&&s.nulReplaced.push({hash:t,replaced:a.split("\0").length-1}),r.push({file:t,hash:e,text:c}),r.length>=O&&await h()}return await h(),s}d(P,"copyBodyFilesToTable");const H=["refs/crtr/docs","refs/crtr/remote-docs","refs/crtr/imported"];async function M(n){const s=new Map,p=await(await w()).prepare("SELECT id FROM objects WHERE type = 'repo' AND owner IS NULL").all();for(const r of p){const h=f(m(),"repo-sync",r.id);if(u(f(h,"HEAD")))for(const o of H)for(const[t,a]of await D({dir:h},o)??[]){if(!F(t,a).ok)continue;const c=JSON.parse(a);for(const e of c.revisions)n.has(e.body_hash)&&s.set(e.body_hash,e.body)}}return s}d(M,"peerOriginals");const A=50;async function U(n=f(m(),"bodies")){const s={rekeyed:0,removed:0,unresolved:[]},r=(await w()).prepare(`SELECT hash, text FROM bodies WHERE hash > ? AND encode(sha256(convert_to(text, 'UTF8')), 'hex') <> hash ORDER BY hash LIMIT ${A}`);for(let h="";;){const o=await r.all(h);if(o.length===0)break;h=o[o.length-1].hash;const t=new Map;for(const e of o){const i=f(n,e.hash.slice(0,2),e.hash);if(!u(i))continue;const l=b(i,"utf8");y(l)===e.hash&&t.set(e.hash,l)}const a=new Set(o.filter(e=>!t.has(e.hash)).map(e=>e.hash));if(a.size>0)for(const[e,i]of await M(a))t.set(e,i);const c=o.filter(e=>t.has(e.hash)&&v(t.get(e.hash))===e.text);for(const e of o)c.includes(e)||s.unresolved.push(e.hash);c.length!==0&&await R(async e=>{for(const i of c){const l=y(i.text);await g(e,l,i.text),s.rekeyed+=await T(e,i.hash,l),s.removed+=(await e.prepare("DELETE FROM bodies WHERE hash = ?").run(i.hash)).changes}})}return s}d(U,"repairBodyHashes");export{P as copyBodyFilesToTable,U as repairBodyHashes};
|
|
@@ -1,11 +1,20 @@
|
|
|
1
1
|
import type { CanvasDb } from '../canvas/db.js';
|
|
2
2
|
export declare function hashOf(body: string): string;
|
|
3
|
-
/** Postgres `text` cannot hold U+0000, so each NUL is stored as U+FFFD. The one rule for a new write
|
|
3
|
+
/** Postgres `text` cannot hold U+0000, so each NUL is stored as U+FFFD. The one rule for a new write, the Mac boot copy and repo sync. */
|
|
4
4
|
export declare function storable(text: string): string;
|
|
5
|
-
/**
|
|
5
|
+
/** The pair a body is stored and exchanged as: the text `storable` makes of `original` and the hash of that text.
|
|
6
|
+
* A `suppliedHash` is the hash `original` arrived with (a repo ref, a retained file); it is checked against `original`, never used as the key. */
|
|
7
|
+
export declare function canonical(original: string, suppliedHash?: string): {
|
|
8
|
+
text: string;
|
|
9
|
+
hash: string;
|
|
10
|
+
};
|
|
11
|
+
/** Insert `text` under `hash`, or confirm the row already there holds exactly that text: a different text under a key is corruption, not a duplicate.
|
|
12
|
+
* Returns whether a row was added. */
|
|
13
|
+
export declare function store(db: CanvasDb, hash: string, text: string): Promise<boolean>;
|
|
14
|
+
/** Store a body and return its hash, which is the hash of the text as stored (see `canonical`). Call inside the transaction that writes the
|
|
6
15
|
* revision naming it, so a rolled-back write leaves neither.
|
|
7
|
-
* `
|
|
8
|
-
export declare function put(db: CanvasDb, body: string,
|
|
16
|
+
* `suppliedHash` is for text that arrives with the hash its source named (repo sync): it must be the hash of `body` as given; the row is keyed by the hash of the stored text. */
|
|
17
|
+
export declare function put(db: CanvasDb, body: string, suppliedHash?: string): Promise<string>;
|
|
9
18
|
export declare function get(db: CanvasDb, hash: string): Promise<string>;
|
|
10
19
|
/** The bodies that exist among `hashes`, in one query. A hash with no row is absent from the map. */
|
|
11
20
|
export declare function findMany(db: CanvasDb, hashes: readonly string[]): Promise<Map<string, string>>;
|