@north-light/cli 0.3.566 → 0.3.568

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 (56) hide show
  1. package/dist/api/contract.d.ts +1 -1
  2. package/dist/api/contract.js +1 -1
  3. package/dist/clients/attach/viewer.js +440 -440
  4. package/dist/core/config.js +1 -1
  5. package/dist/core/directory/issuer.d.ts +5 -0
  6. package/dist/core/directory/issuer.js +1 -0
  7. package/dist/core/graph/objects.d.ts +3 -0
  8. package/dist/core/graph/objects.js +8 -8
  9. package/dist/core/graph/reader.js +3 -3
  10. package/dist/core/ledger/charges.d.ts +2 -4
  11. package/dist/core/ledger/charges.js +1 -1
  12. package/dist/core/ledger/index.d.ts +1 -1
  13. package/dist/core/ledger/index.js +1 -1
  14. package/dist/core/ledger/infra-allowance.d.ts +0 -2
  15. package/dist/core/ledger/infra-allowance.js +1 -1
  16. package/dist/core/ledger/rates.d.ts +24 -0
  17. package/dist/core/ledger/rates.js +1 -0
  18. package/dist/core/ledger/read.d.ts +5 -2
  19. package/dist/core/ledger/read.js +2 -2
  20. package/dist/core/peer/client.js +1 -1
  21. package/dist/core/peer/directory.d.ts +0 -2
  22. package/dist/core/peer/directory.js +1 -1
  23. package/dist/core/peer/home.js +2 -2
  24. package/dist/core/providers/audit.d.ts +2 -0
  25. package/dist/core/providers/audit.js +2 -2
  26. package/dist/core/providers/bundles.js +2 -2
  27. package/dist/core/providers/call.js +1 -1
  28. package/dist/core/providers/mint.d.ts +1 -1
  29. package/dist/core/providers/mint.js +1 -1
  30. package/dist/core/runs/operations.d.ts +4 -0
  31. package/dist/core/runs/operations.js +7 -7
  32. package/dist/core/storage/client.js +2 -2
  33. package/dist/core/user-settings.d.ts +0 -4
  34. package/dist/core/user-settings.js +1 -1
  35. package/dist/daemon/api/app-listener.d.ts +8 -0
  36. package/dist/daemon/api/app-listener.js +13 -13
  37. package/dist/daemon/api/handlers/charges.js +1 -1
  38. package/dist/daemon/api/handlers/plugin-app-token.js +1 -1
  39. package/dist/daemon/api/handlers/providers.js +1 -1
  40. package/dist/daemon/api/handlers/runs.js +1 -1
  41. package/dist/daemon/api/handlers/transfer.js +1 -1
  42. package/dist/daemon/api/reader.js +1 -1
  43. package/dist/daemon/api/server.js +1 -1
  44. package/dist/daemon/cron/authority.js +1 -1
  45. package/dist/daemon/crtrd.js +10 -10
  46. package/dist/daemon/peer/relay-service.js +1 -1
  47. package/dist/daemon/readiness.d.ts +2 -1
  48. package/dist/daemon/readiness.js +1 -1
  49. package/dist/daemon/reconcilers/ledger-r2.js +5 -5
  50. package/dist/daemon/reconcilers/ledger-sandbox.d.ts +0 -1
  51. package/dist/daemon/reconcilers/ledger-sandbox.js +4 -4
  52. package/dist/daemon/reconcilers/ledger-storage.js +3 -3
  53. package/dist/types.d.ts +0 -16
  54. package/package.json +2 -2
  55. package/packages/crouter-identity/package.json +1 -1
  56. package/runtime.lock.json +13 -13
@@ -1 +1 @@
1
- var Ze=Object.defineProperty;var s=(e,n)=>Ze(e,"name",{value:n,configurable:!0});import{envProfileId as Ye}from"../shared/env.js";import{basename as re,join as ie}from"node:path";import{existsSync as Ve,mkdirSync as Xe,statSync as Qe}from"node:fs";import{isDeepStrictEqual as en}from"node:util";import{CONFIG_FILE as nn,STATE_FILE as on,CONDENSED_HISTORY_MODES as tn,MOUSE_MODE_DEFAULTS as rn,WHIP_MESSAGE_MODES as sn,DEFAULT_WHIP_MESSAGES as se,defaultScopeConfig as k,defaultScopeState as ce,defaultModelLaddersConfig as cn,defaultKindsConfig as un,defaultRemoteCanvasConfig as dn}from"../types.js";import{BINDING_CATALOG as an,BINDING_IDS as fn,isAttachPaneBinding as ln}from"./keybindings/catalog.js";import{notFound as mn}from"./errors.js";import{emitEvent as pn}from"./events/emit.js";import{atomicWriteJson as B,readJsonIfExists as C,ensureDir as gn}from"./fs-utils.js";import{scopeRoot as y,requireScopeRoot as A,findProjectScopeRoots as yn,userScopeRoot as ue}from"./scope.js";import{listInstalledPluginsInRoot as de}from"./installed-plugins.js";import{profileDir as ae,listProfiles as fe}from"./profiles/manifest.js";import{canonicalRoot as S}from"./scoped-state/paths.js";import{readCachedSettings as H,readCachedState as hn,revisionOf as J,SettingsRevisionConflictError as le,writeSettings as W}from"./scoped-state/person-store.js";import{withExclusiveDirectoryLock as _n}from"./exclusive-lock.js";import{SCOPE_CONFIG_KEYS as bn}from"./user-settings.js";import{normalizeWorkingGerunds as vn}from"../shared/working-activity.js";import{mergePageComponentLayers as wn,normalizePageComponents as me}from"./human/page-catalog.js";function _(e){return ie(e,nn)}s(_,"configPathFor");function Z(e,n){return _n(`${e}.lock`,()=>n(e),{timeoutError:s(()=>new Error(`timed out waiting for config lock: ${e}`),"timeoutError")})}s(Z,"withConfigPathLock");function Y(e){return ie(e,on)}s(Y,"statePathFor");function _o(e){const n=y(e);return n?_(n):null}s(_o,"configPath");function bo(e){const n=y(e);return n?Y(n):null}s(bo,"statePath");function j(e){const n=S(e),o=S(ue());if(n===o)return{key:"user",kind:"user",root:o};if(re(n)===".crouter")return{key:"project",kind:"project",root:n};for(const t of fe()){const r=S(ae(t.profileId));if(n===r)return{key:`profile:${t.profileId}`,kind:"profile",root:r,profileId:t.profileId}}throw new Error(`settings root is not a recognized owner: ${e}`)}s(j,"settingsOwnerForRoot");function T(e){if(re(e)===".crouter"){let n;try{n=S(e)}catch(o){if(o.code==="ENOENT")return null;throw o}if(n!==S(ue()))return C(_(e))}return H(j(e).key).settings}s(T,"readRawAtRoot");async function pe(e,n){let o;return await W(t=>{const r=t.settings[e],i=n(structuredClone(r??{}));return o=i.value,[{op:"settings.replace",owner:e,raw:i.next,ifRevision:J(r)}]}),o}s(pe,"mutatePersonSettings");async function V(e,n){const o=j(e);if(o.kind==="project"){Z(_(o.root),t=>B(t,n,{createParent:!1}));return}await pe(o.key,()=>({value:void 0,next:n}))}s(V,"writeRawAtRoot");async function x(e,n){const o=j(e);return o.kind==="project"?Z(_(o.root),t=>{const r=n(C(t)??{});return B(t,r.next,{createParent:!1}),r.value}):await pe(o.key,n)}s(x,"mutateRawAtRoot");const $=globalThis,M=Symbol.for("crtr.broker.config-snapshot");function vo(e){$[M]=e}s(vo,"installBrokerConfigSnapshot");function wo(){return $[M]}s(wo,"brokerConfigSnapshot");function kn(e){const n=$[M];if(n){if(e!=="user")throw new Error(`broker config snapshot has no ${e} scope`);return n.user}const o=y(e);return o?An(o):k()}s(kn,"readConfig");function An(e){const n=T(e);return n?U(n):k()}s(An,"readConfigAtRoot");function Sn(e){const n=C(Y(e));return{marketplaces:n?.marketplaces??{},plugins:n?.plugins??{},activeCanvas:n?.activeCanvas??null,...n?.last_self_check===void 0?{}:{last_self_check:n.last_self_check}}}s(Sn,"readProjectState");function jn(e){const n=y(e);return n?e==="project"?Ve(n)?Sn(n):ce():hn(j(n).key):ce()}s(jn,"readState");class xn extends Error{static{s(this,"InvalidRawSettingsError")}constructor(n){super(`settings cannot be merged: ${n instanceof Error?n.message:String(n)}`),this.name="InvalidRawSettingsError"}}function ge(e){if(e.scope==="user")return"user";if(!fe().some(n=>n.profileId===e.profileId))throw mn(`profile not found: ${e.profileId}`,{received:e.profileId,next:"Run `crtr profile list` to see available profiles."});return`profile:${e.profileId}`}s(ge,"rawSettingsKeyForTarget");function ko(e){return H(ge(e))}s(ko,"readRawSettingsForTarget");async function Ao(e,n,o){const t=ge(e);try{U(n)}catch(u){throw new xn(u)}if(H(t).revision!==o)throw new le;const i=await W(u=>J(u.settings[t])===o?[{op:"settings.replace",owner:t,raw:n,ifRevision:o}]:(()=>{throw new le})()),c=J(i.settings[t]);return{revision:c,changed:c!==o}}s(Ao,"replaceRawSettingsForTarget");async function So(e,n){const o=A(e);return await x(o,t=>{const r=n(t);return{value:r,next:r}})}s(So,"updateRawConfigAtomically");async function jo(e,n){await V(A(e),n)}s(jo,"writeConfig");async function xo(e,n){await V(A(e),n)}s(xo,"writeRawConfig");function Rn(e,n){Z(Y(e),o=>B(o,n,{createParent:!1}))}s(Rn,"writeProjectState");async function En(e,n){const o=A(e);if(e==="project"){Rn(o,n);return}const t=j(o).key;await W(()=>[{op:"state.write",owner:t,state:n}])}s(En,"writeState");async function Ro(e,n){if(e!=="project"){gn(n);return}try{Xe(n)}catch(o){if(o.code!=="EEXIST"||!Qe(n).isDirectory())throw o}C(_(n))===null&&await V(n,{schema_version:k().schema_version})}s(Ro,"ensureScopeInitialized");function On(e){if(e===null||typeof e!="object"||Array.isArray(e))return{};const n={};for(const[o,t]of Object.entries(e))Array.isArray(t)&&t.every(r=>typeof r=="string")&&(n[o]=[...t]);return n}s(On,"normalizeKeybindings");const Pn=new Set(an.filter(ln).map(e=>e.id));function In(e){return Array.isArray(e)?[...new Set(e.filter(n=>typeof n=="string"&&fn.has(n)&&Pn.has(n)))]:[]}s(In,"normalizeTmuxPassthrough");function N(e,n=cn()){const o=n;if(e===null||typeof e!="object")return o;const t=e,r={anthropic:{...o.anthropic},openai:{...o.openai},...o.defaultProvider!==void 0?{defaultProvider:o.defaultProvider}:{}};(t.defaultProvider==="anthropic"||t.defaultProvider==="openai")&&(r.defaultProvider=t.defaultProvider);for(const i of["anthropic","openai"]){const c=t[i];if(!(c===null||typeof c!="object"))for(const[u,d]of Object.entries(c))(u==="ultra"||u==="strong"||u==="medium"||u==="light")&&typeof d=="string"&&(r[i][u]=d)}return r}s(N,"mergeModelLadders");function ye(e){return e==="ultra"||e==="strong"||e==="medium"||e==="light"}s(ye,"validStrength");const X=["ultra","strong","medium","light"];function Q(){return Object.create(null)}s(Q,"routeRecord");function R(e,n,o){Object.defineProperty(e,n,{value:o,writable:!0,enumerable:!0,configurable:!0})}s(R,"setRouteRecord");function he(e){const n=Q();for(const[o,t]of Object.entries(e??{}))R(n,o,t);return n}s(he,"copyRouteRecord");function E(e,n){pn({level:"warn",event:"config.model_routes.invalid",fields:{route_id:e,rule:n}})}s(E,"routeInvalid");function L(e,n={}){const o=he(n);if(e===null||typeof e!="object"||Array.isArray(e))return o;for(const[t,r]of Object.entries(e)){if(t.trim()===""){E(t,"route id must be a non-empty string");continue}if(r===null||typeof r!="object"||Array.isArray(r)){E(t,"route must be an object");continue}const i=r,c=o[t],u={...c};let d;for(const f of["family","credentialSource","providerId"]){const a=i[f];if(a!==void 0){if(typeof a!="string"||a.trim()===""){d=`${f} must be a non-empty string`;break}u[f]=a}}if(d===void 0&&i.models!==void 0)if(i.models===null||typeof i.models!="object"||Array.isArray(i.models))d="models must be an object";else{const f={...c?.models};for(const[a,l]of Object.entries(i.models)){if(!ye(a)){d=`models.${a} is not a model strength`;break}if(typeof l!="string"||l.trim()===""){d=`models.${a} must be a non-empty provider/id ref`;break}const m=l.indexOf("/");if(m<=0||m===l.length-1){d=`models.${a} must be provider/id`;break}f[a]=l}u.models=f}if(d===void 0&&u.models!==void 0&&u.providerId!==void 0){for(const[f,a]of Object.entries(u.models))if(a.slice(0,a.indexOf("/"))!==u.providerId){d=`models.${f} provider prefix must equal providerId`;break}}if(d!==void 0){E(t,d);continue}R(o,t,u)}return o}s(L,"mergeModelRoutePatches");function _e(e){const n=Q(),o=Q();for(const[t,r]of Object.entries(e)){if(r.family===void 0||r.credentialSource===void 0||r.providerId===void 0||r.models===void 0){E(t,"route must include family, credentialSource, providerId, and models");continue}const i=X.filter(c=>r.models[c]===void 0);if(i.length>0){E(t,`incomplete route; missing strengths: ${i.join(", ")}`),R(o,t,{family:r.family,credentialSource:r.credentialSource,providerId:r.providerId,models:r.models});continue}R(n,t,{family:r.family,credentialSource:r.credentialSource,providerId:r.providerId,models:r.models})}return{complete:n,legacy:o}}s(_e,"resolveModelRoutes");function Cn(e,n){if(e.trim()==="")throw new Error("model route id must be a non-empty string");for(const t of["family","credentialSource","providerId"])if(typeof n[t]!="string"||n[t].trim()==="")throw new Error(`model route "${e}" ${t} must be a non-empty string`);const o=X.filter(t=>n.models?.[t]===void 0);if(o.length>0)throw new Error(`model route "${e}" is incomplete; missing strengths: ${o.join(", ")}`);for(const t of X){const r=n.models[t];if(typeof r!="string"||r.trim()==="")throw new Error(`model route "${e}" models.${t} must be a non-empty provider/id ref`);const i=r.indexOf("/");if(i<=0||i===r.length-1)throw new Error(`model route "${e}" models.${t} must be provider/id`);if(r.slice(0,i)!==n.providerId)throw new Error(`model route "${e}" models.${t} provider prefix must equal providerId`)}}s(Cn,"assertCompleteModelRoute");function F(e,n){if(e===void 0||e===null||typeof e!="object"||Array.isArray(e))return n;const o=e,t=s(i=>Array.isArray(i)&&i.every(c=>typeof c=="string"&&c.trim()!=="")?[...i]:void 0,"strings"),r=Array.isArray(o.strengthFallback)&&o.strengthFallback.every(i=>ye(i))?[...o.strengthFallback]:void 0;return{...t(o.credentialSourceOrder)!==void 0?{credentialSourceOrder:t(o.credentialSourceOrder)}:{},...t(o.familyOrder)!==void 0?{familyOrder:t(o.familyOrder)}:{},...r!==void 0?{strengthFallback:r}:{}}}s(F,"mergeModelRouting");function Tn(e){return e!==""&&e.replace(/\*/g,"")===""}s(Tn,"isInheritAllGlob");function z(e,n=[]){const o=e,t=Array.isArray(o?.allow)?o.allow.filter(r=>typeof r=="string"):[];for(const r of t)if(Tn(r))throw new Error(`spawnEnv.allow: "${r}" matches every env var name (inherit-all) \u2014 this is exactly the leak the spawn-env boundary closes. Name the specific vars/prefixes you actually need instead (e.g. "AWS_*", "GIT_ASKPASS").`);return{allow:[...new Set([...n,...t])]}}s(z,"mergeSpawnEnv");function D(e,n={}){const o={};for(const[t,r]of Object.entries(n))o[t]={...r};if(e!==null&&typeof e=="object")for(const[t,r]of Object.entries(e)){if(r===null||typeof r!="object")continue;const i={...o[t]},c=r.fastMode;typeof c=="boolean"&&(i.fastMode=c),o[t]=i}return o}s(D,"mergeProviderOptions");const be=/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/,$n=new Set(["crtr","crtrd"]);function K(e,n){if(n==="bin"&&$n.has(e))return`${n}.${e} is reserved by crouter \u2014 pick another bare command name`;if(!be.test(e))return`${n}.${e} is not a safe bare command name (expected ${be.source})`}s(K,"bareCommandNameIssue");function Mn(e){if(e===null||typeof e!="object"||Array.isArray(e))return[{name:"bin",message:"bin must be a JSON object mapping bare command name to a relative path"}];const n=[];for(const[o,t]of Object.entries(e)){const r=K(o,"bin");r!==void 0&&n.push({name:o,message:r}),(typeof t!="string"||t.trim()==="")&&n.push({name:o,message:`bin.${o} must be a non-empty relative path to an executable inside the contributor root`})}return n}s(Mn,"binDeclarationIssues");function Nn(e){if(e===null||typeof e!="object"||Array.isArray(e))return{};const n={};for(const[o,t]of Object.entries(e))K(o,"bin")===void 0&&(typeof t!="string"||t.trim()===""||(n[o]=t));return n}s(Nn,"normalizeBin");const ve=/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;function Ln(e){if(e===null||typeof e!="object"||Array.isArray(e))return[{name:"humanActions",reason:"humanActions must be a JSON object mapping action names to {argv, cwd}",block:!0}];const n=[];for(const[o,t]of Object.entries(e)){const r=[];if(ve.test(o)||r.push(`action name must match ${ve.source}`),t===null||typeof t!="object"||Array.isArray(t))r.push("action must be an object with argv and cwd");else{const i=t,c=Object.keys(i).filter(u=>u!=="argv"&&u!=="cwd");c.length>0&&r.push(`action has unsupported field${c.length===1?"":"s"}: ${c.join(", ")}`),(!Array.isArray(i.argv)||i.argv.length===0||!i.argv.every(u=>typeof u=="string"&&u.length>0))&&r.push("argv must be a non-empty array of non-empty strings"),(typeof i.cwd!="string"||i.cwd.length===0)&&r.push("cwd is required and must be a non-empty string")}r.length>0&&n.push({name:o,reason:r.join("; ")})}return n}s(Ln,"humanActionDeclarationIssues");function Fn(e){if(e===null||typeof e!="object"||Array.isArray(e))return{};const n=new Set(Ln(e).map(t=>t.name)),o={};for(const[t,r]of Object.entries(e)){if(n.has(t))continue;const i=r;o[t]={argv:[...i.argv],cwd:i.cwd}}return o}s(Fn,"normalizeHumanActions");function Eo(e){return Mn(e).map(n=>n.message)}s(Eo,"invalidPluginBinReasons");function Oo(e){if(e===null||typeof e!="object"||Array.isArray(e))return{};const n={};for(const[o,t]of Object.entries(e))K(o,"requires")===void 0&&(typeof t!="string"||t.trim()===""||/[\r\n]/.test(t)||(n[o]=t));return n}s(Oo,"normalizeRequires");function Po(e){if(e===null||typeof e!="object"||Array.isArray(e))return["requires must be a JSON object mapping a bare executable name to a one-line install hint"];const n=[];for(const[o,t]of Object.entries(e)){const r=K(o,"requires");r!==void 0&&n.push(r),typeof t!="string"||t.trim()===""?n.push(`requires.${o} must be a non-empty one-line install hint`):/[\r\n]/.test(t)&&n.push(`requires.${o} must be a one-line install hint`)}return n}s(Po,"invalidPluginRequiresReasons");function we(e){if(e===null||typeof e!="object")return null;const n=e,o={};typeof n.whenToUse=="string"&&n.whenToUse.trim()!==""&&(o.whenToUse=n.whenToUse),typeof n.model=="string"&&n.model.trim()!==""&&(o.model=n.model),typeof n.orchestratorModel=="string"&&n.orchestratorModel.trim()!==""&&(o.orchestratorModel=n.orchestratorModel);const t=s(r=>Array.isArray(r)&&r.every(i=>typeof i=="string"),"isStringArray");return t(n.tools)&&(o.tools=n.tools),t(n.extensions)&&(o.extensions=n.extensions),t(n.availableTo)&&(o.availableTo=n.availableTo),Object.keys(o).length>0?o:null}s(we,"normalizeKindFields");function O(e,n=un()){const o={...n};if(e!==null&&typeof e=="object")for(const[t,r]of Object.entries(e)){const i=we(r);if(i===null)continue;const c=o[t];c!==void 0?o[t]={...c,...i}:i.whenToUse!==void 0&&(o[t]=i)}return o}s(O,"mergeKinds");function ke(e,n,o){if(n===null)return o;const t=de(e,n).filter(i=>i.enabled&&i.manifest.kinds!==void 0).sort((i,c)=>i.name.localeCompare(c.name));let r=o;for(const i of t)r=O(i.manifest.kinds,r);return r}s(ke,"layerPluginKinds");function Io(){const e=[{origin:"config.json",components:kn("user").page_components}],n=y("user");if(n!==null){const o=de("user",n).filter(t=>t.enabled&&t.manifest.page_components!==void 0).sort((t,r)=>t.name.localeCompare(r.name));for(const t of o){const r=`plugin "${t.name}" page_components`;e.push({origin:`plugin "${t.name}"`,components:me(t.manifest.page_components,r)})}}return wn(e)}s(Io,"resolvePageComponents");const ee=new Set(["whenToUse","model","orchestratorModel","tools","extensions","availableTo"]);function Co(e){if(e===null||typeof e!="object"||Array.isArray(e))return["kinds must be a JSON object keyed by kind name"];const n=[],o=s(t=>Array.isArray(t)&&t.every(r=>typeof r=="string"),"isStringArray");for(const[t,r]of Object.entries(e)){if(t.trim()===""){n.push("kinds contains an empty kind name");continue}if(r===null||typeof r!="object"||Array.isArray(r)){n.push(`kinds.${t} must be a JSON object`);continue}const i=r;for(const c of Object.keys(i))ee.has(c)||n.push(`kinds.${t}.${c} is not a KindConfig field (expected one of: ${[...ee].join(", ")})`);i.whenToUse!==void 0&&(typeof i.whenToUse!="string"||i.whenToUse.trim()==="")&&n.push(`kinds.${t}.whenToUse must be a non-empty string`);for(const c of["model","orchestratorModel"])i[c]!==void 0&&(typeof i[c]!="string"||i[c].trim()==="")&&n.push(`kinds.${t}.${c} must be a non-empty string`);for(const c of["tools","extensions","availableTo"])i[c]!==void 0&&!o(i[c])&&n.push(`kinds.${t}.${c} must be an array of strings`);we(r)===null&&n.push(`kinds.${t} has no valid KindConfig field \u2014 an entry must set at least one of: ${[...ee].join(", ")} (whenToUse is required when the kind exists in no lower layer)`)}return n}s(Co,"invalidPluginKindsReasons");function zn(e){if(e===null||typeof e!="object")return null;const n=e;if(typeof n.previewEndpoint!="string"||n.previewEndpoint.trim()===""||typeof n.relayTokenRef!="string"||n.relayTokenRef.trim()==="")return null;const o={previewEndpoint:n.previewEndpoint,relayTokenRef:n.relayTokenRef};return typeof n.cpOrigin=="string"&&n.cpOrigin.trim()!==""&&(o.cpOrigin=n.cpOrigin),o}s(zn,"normalizeRemoteCanvasTarget");function Dn(e,n=dn()){const o={...n.targets};if(e!==null&&typeof e=="object"){const t=e;if(t.targets!==null&&typeof t.targets=="object")for(const[r,i]of Object.entries(t.targets)){const c=zn(i);c!==null&&(o[r]=c)}}return{targets:o}}s(Dn,"mergeRemoteCanvas");function Ae(e,n){return e===!0?"notify":e===!1?!1:e==="notify"||e==="apply"?e:n}s(Ae,"normalizeMode");function P(e,n){return typeof e=="number"&&Number.isSafeInteger(e)&&e>=1?e:n}s(P,"normalizePositiveInteger");function Kn(e,n){const o=e!==null&&typeof e=="object"?e:{},t=P(o.warning,n.warning),r=P(o.automaticReviveCap,n.automaticReviveCap);return t<=r?{warning:t,automaticReviveCap:r}:{...n}}s(Kn,"mergeBrokerThresholds");function Un(e,n){return{unattendedParkMs:P((e!==null&&typeof e=="object"?e:{}).unattendedParkMs,n.unattendedParkMs)}}s(Un,"mergeLifecycle");function qn(e){if(!Array.isArray(e))return[...se];const n=e.filter(o=>typeof o=="string"&&o.trim()!=="").map(o=>o.trim());return n.length>0?n:[...se]}s(qn,"normalizeWhipMessages");function Gn(e,n){if(e===null||typeof e!="object")return{...n};const o=e.tcp;return{tcp:typeof o=="string"?o:n.tcp}}s(Gn,"mergeApi");function U(e){const n=k(),o=n.schema_version,t=e.marketplaces===void 0?{}:e.marketplaces,r=e.plugins===void 0?{}:e.plugins,i=e.auto_update,c=i&&typeof i.interval_hours=="number"?i.interval_hours:void 0,u=c!==void 0&&Number.isFinite(c)&&c>=0?c:n.auto_update.interval_hours,d={crtr:Ae(i?.crtr,n.auto_update.crtr),content:Ae(i?.content,n.auto_update.content),interval_hours:u},f=e.max_panes_per_window,a=P(f,n.max_panes_per_window),l=typeof e.auto_open_child_viewers=="boolean"?e.auto_open_child_viewers:n.auto_open_child_viewers,m=Kn(e.brokerThresholds,n.brokerThresholds),h=Un(e.lifecycle,n.lifecycle),q=typeof e.completion_bell=="boolean"?e.completion_bell:n.completion_bell,g=e.working_gerunds===void 0?[...n.working_gerunds]:vn(e.working_gerunds),je=typeof e.whip_mode=="boolean"?e.whip_mode:n.whip_mode,xe=me(e.page_components),Re=typeof e.page_surface=="boolean"?e.page_surface:n.page_surface,Ee=qn(e.whip_messages),Oe=sn.includes(e.whip_message_mode)?e.whip_message_mode:n.whip_message_mode,Pe=rn.includes(e.mouse_mode_default)?e.mouse_mode_default:n.mouse_mode_default,Ie=P(e.live_cycles,n.live_cycles),Ce=tn.includes(e.condensed_history)?e.condensed_history:n.condensed_history,Te=typeof e.bash_tool_purpose=="boolean"?e.bash_tool_purpose:n.bash_tool_purpose,$e=typeof e.fold_finished_tools=="boolean"?e.fold_finished_tools:n.fold_finished_tools,Me=typeof e.summarize_tool_calls=="boolean"?e.summarize_tool_calls:n.summarize_tool_calls,Ne=typeof e.detailed_tool_recaps=="boolean"?e.detailed_tool_recaps:n.detailed_tool_recaps,Le=Number.isInteger(e.document_port)&&e.document_port>=1&&e.document_port<=65535?e.document_port:n.document_port,Fe=typeof e.consolidate_replies=="boolean"?e.consolidate_replies:n.consolidate_replies,ze=On(e.keybindings),De=In(e.tmux_passthrough),Ke=N(e.modelLadders),Ue=D(e.providerOptions,n.providerOptions),G=_e(L(e.modelRoutes)),ne=G.complete,oe=F(e.modelRouting,void 0),qe=O(e.kinds),Ge=Dn(e.remoteCanvas),Be=Gn(e.api,n.api),b=e.paths;if(b?.user_files!==void 0&&(typeof b.user_files!="string"||!b.user_files.startsWith("/")))throw new Error("paths.user_files must be an absolute path");const p=e.ledger;if(p?.take_pct!==void 0&&(typeof p.take_pct!="number"||!Number.isFinite(p.take_pct)||p.take_pct<0||p.take_pct>100))throw new Error("ledger.take_pct must be a number from 0 to 100");for(const w of["sandbox_usd_per_gb_hour","sandbox_standby_s","storage_usd_per_gb_month","sandbox_disk_usd_per_gb_month","infra_free_usd_per_month"]){const I=p?.[w];if(I!==void 0&&(typeof I!="number"||!Number.isFinite(I)||I<0))throw new Error(`ledger.${w} must be a number of at least 0`)}if(p?.r2_move!==void 0&&typeof p.r2_move!="boolean")throw new Error("ledger.r2_move must be true or false");const te=e.store,v=e.runtime;if(v?.person_uids!==void 0&&(!Array.isArray(v.person_uids)||!v.person_uids.every(w=>Number.isSafeInteger(w)&&w>=0)))throw new Error("runtime.person_uids must be an array of nonnegative integer uids");const He=z(e.spawnEnv,n.spawnEnv?.allow),Je=Nn(e.bin),We=Fn(e.humanActions);return{bin:Je,humanActions:We,schema_version:o,marketplaces:t,plugins:r,auto_update:d,max_panes_per_window:a,auto_open_child_viewers:l,brokerThresholds:m,lifecycle:h,completion_bell:q,working_gerunds:g,whip_mode:je,page_components:xe,page_surface:Re,whip_messages:Ee,whip_message_mode:Oe,mouse_mode_default:Pe,live_cycles:Ie,condensed_history:Ce,bash_tool_purpose:Te,fold_finished_tools:$e,summarize_tool_calls:Me,detailed_tool_recaps:Ne,document_port:Le,consolidate_replies:Fe,keybindings:ze,tmux_passthrough:De,modelLadders:Ke,providerOptions:Ue,...Object.keys(ne).length>0?{modelRoutes:ne}:{},...Object.keys(G.legacy).length>0?{legacyModelRoutes:G.legacy}:{},...oe!==void 0?{modelRouting:oe}:{},kinds:qe,remoteCanvas:Ge,api:Be,...b?{paths:b}:{},...p?{ledger:p}:{},...te?{store:te}:{},...v?{runtime:v}:{},spawnEnv:He}}s(U,"mergeConfig");function Bn(e){return T(e)}s(Bn,"readRawConfigAtRoot");function Hn(e){const n=y(e);return n?Bn(n):null}s(Hn,"readRawScopeConfig");function Jn(e){if(e===null||e==="")return null;try{return T(ae(e))}catch{return null}}s(Jn,"readRawProfileConfig");function Wn(e,n){return[...yn(e,n)].reverse().map(r=>({root:r,raw:T(r)}))}s(Wn,"readRawProjectScopeConfigs");function Se(e=process.cwd(),n=Ye()||null){const o=$[M];if(o)return o.launch;const t=k(),r=Hn("user"),i=Jn(n),c=Wn(e,n);let u=O(r?.kinds,ke("user",y("user"),t.kinds)),d=N(r?.modelLadders,t.modelLadders),f=D(r?.providerOptions,t.providerOptions),a=L(r?.modelRoutes),l=F(r?.modelRouting,void 0),m=z(r?.spawnEnv,t.spawnEnv?.allow);i!==null&&(u=O(i.kinds,u),d=N(i.modelLadders,d),f=D(i.providerOptions,f),a=L(i.modelRoutes,a),l=F(i.modelRouting,l),m=z(i.spawnEnv,m.allow));for(const{root:q,raw:g}of c)u=O(g?.kinds,ke("project",q,u)),g!==null&&(d=N(g.modelLadders,d),f=D(g.providerOptions,f),a=L(g.modelRoutes,a),l=F(g.modelRouting,l),m=z(g.spawnEnv,m.allow));const h=_e(a);return{kinds:u,modelLadders:d,providerOptions:f,...Object.keys(h.complete).length>0?{modelRoutes:h.complete}:{},...Object.keys(h.legacy).length>0?{legacyModelRoutes:h.legacy}:{},...l!==void 0?{modelRouting:l}:{},spawnEnv:m}}s(Se,"readMergedLaunchConfig");function To(e){return Se().kinds[e]}s(To,"resolveKindConfig");function $o(e){const n=Se().kinds,o=[];for(const[t,r]of Object.entries(n)){const i=t.indexOf("/");if(i===-1)continue;const c=t.slice(0,i),u=r.availableTo??[c];(u.includes("*")||u.includes(e))&&o.push({kind:t,whenToUse:r.whenToUse})}return o.sort((t,r)=>t.kind.localeCompare(r.kind))}s($o,"subKindsAvailableTo");function Zn(e,n,o){const t={...e??{}};t.anthropic!==void 0&&(t.anthropic={...t.anthropic}),t.openai!==void 0&&(t.openai={...t.openai}),o.defaultProvider!==n.defaultProvider&&o.defaultProvider!==void 0&&(t.defaultProvider=o.defaultProvider);for(const i of["anthropic","openai"])for(const c of["ultra","strong","medium","light"])o[i][c]!==n[i][c]&&(t[i]={...t[i]??{},[c]:o[i][c]});return t.defaultProvider!==void 0||t.anthropic!==void 0||t.openai!==void 0?t:void 0}s(Zn,"sparseModelLaddersToPersist");async function Yn(e,n){return await x(e,o=>{const t=U(o),r=U(o);n(r);const i={...o};for(const u of bn)u!=="modelLadders"&&!en(r[u],t[u])&&(i[u]=r[u]);const c=Zn(o.modelLadders,t.modelLadders,r.modelLadders);return c!==void 0?i.modelLadders=c:delete i.modelLadders,{value:r,next:i}})}s(Yn,"mutateConfigAtRoot");async function Mo(e,n){await x(e,o=>{const t=o.modelLadders??{},r={...t};n.defaultProvider!==void 0&&(r.defaultProvider=n.defaultProvider);for(const i of["anthropic","openai"])n[i]!==void 0&&(r[i]={...t[i]??{},...n[i]});return{value:void 0,next:{...o,modelLadders:r}}})}s(Mo,"persistModelLadders");async function No(e,n,o){if(o!==void 0)Cn(n,o);else if(n.trim()==="")throw new Error("model route id must be a non-empty string");await x(e,t=>{const r=he(t.modelRoutes??{});o===void 0?delete r[n]:R(r,n,{...o,models:{...o.models}});const i={...t};return Object.keys(r).length===0?delete i.modelRoutes:i.modelRoutes=r,{value:void 0,next:i}})}s(No,"persistModelRoute");async function Vn(e,n){return await Yn(e,n)}s(Vn,"updateConfigAtRoot");async function Lo(e,n){return await Vn(A(e),n)}s(Lo,"updateConfig");async function Fo(e){const n=e.orchestrator===!0?"orchestratorModel":"model";return await x(e.root,o=>{const t={...o.kinds??{},[e.kind]:{...(o.kinds??{})[e.kind],[n]:e.model}};return{value:void 0,next:{...o,kinds:t}}}),{path:_(e.root),field:n}}s(Fo,"persistDefaultKindModel");async function zo(e,n){const o=jn(e);return n(o),await En(e,o),o}s(zo,"updateState");export{xn as InvalidRawSettingsError,Mn as binDeclarationIssues,wo as brokerConfigSnapshot,_o as configPath,Ro as ensureScopeInitialized,Ln as humanActionDeclarationIssues,vo as installBrokerConfigSnapshot,Eo as invalidPluginBinReasons,Co as invalidPluginKindsReasons,Po as invalidPluginRequiresReasons,Nn as normalizeBin,Fn as normalizeHumanActions,Oo as normalizeRequires,In as normalizeTmuxPassthrough,Fo as persistDefaultKindModel,Mo as persistModelLadders,No as persistModelRoute,kn as readConfig,An as readConfigAtRoot,Se as readMergedLaunchConfig,Bn as readRawConfigAtRoot,Hn as readRawScopeConfig,ko as readRawSettingsForTarget,jn as readState,Ao as replaceRawSettingsForTarget,To as resolveKindConfig,Io as resolvePageComponents,bo as statePath,$o as subKindsAvailableTo,Lo as updateConfig,Vn as updateConfigAtRoot,So as updateRawConfigAtomically,zo as updateState,jo as writeConfig,xo as writeRawConfig,En as writeState};
1
+ var Ze=Object.defineProperty;var s=(e,n)=>Ze(e,"name",{value:n,configurable:!0});import{envProfileId as Ye}from"../shared/env.js";import{basename as oe,join as te}from"node:path";import{existsSync as Ve,mkdirSync as Xe,statSync as Qe}from"node:fs";import{isDeepStrictEqual as en}from"node:util";import{CONFIG_FILE as nn,STATE_FILE as on,CONDENSED_HISTORY_MODES as tn,MOUSE_MODE_DEFAULTS as rn,WHIP_MESSAGE_MODES as sn,DEFAULT_WHIP_MESSAGES as re,defaultScopeConfig as v,defaultScopeState as ie,defaultModelLaddersConfig as cn,defaultKindsConfig as un,defaultRemoteCanvasConfig as dn}from"../types.js";import{BINDING_CATALOG as an,BINDING_IDS as fn,isAttachPaneBinding as ln}from"./keybindings/catalog.js";import{notFound as mn}from"./errors.js";import{emitEvent as se}from"./events/emit.js";import{LEDGER_ENV as pn}from"./ledger/rates.js";import{atomicWriteJson as U,readJsonIfExists as E,ensureDir as gn}from"./fs-utils.js";import{scopeRoot as g,requireScopeRoot as w,findProjectScopeRoots as yn,userScopeRoot as ce}from"./scope.js";import{listInstalledPluginsInRoot as ue}from"./installed-plugins.js";import{profileDir as de,listProfiles as ae}from"./profiles/manifest.js";import{canonicalRoot as k}from"./scoped-state/paths.js";import{readCachedSettings as G,readCachedState as hn,revisionOf as q,SettingsRevisionConflictError as fe,writeSettings as B}from"./scoped-state/person-store.js";import{withExclusiveDirectoryLock as bn}from"./exclusive-lock.js";import{SCOPE_CONFIG_KEYS as _n}from"./user-settings.js";import{normalizeWorkingGerunds as vn}from"../shared/working-activity.js";import{mergePageComponentLayers as wn,normalizePageComponents as le}from"./human/page-catalog.js";function h(e){return te(e,nn)}s(h,"configPathFor");function W(e,n){return bn(`${e}.lock`,()=>n(e),{timeoutError:s(()=>new Error(`timed out waiting for config lock: ${e}`),"timeoutError")})}s(W,"withConfigPathLock");function H(e){return te(e,on)}s(H,"statePathFor");function _o(e){const n=g(e);return n?h(n):null}s(_o,"configPath");function vo(e){const n=g(e);return n?H(n):null}s(vo,"statePath");function A(e){const n=k(e),o=k(ce());if(n===o)return{key:"user",kind:"user",root:o};if(oe(n)===".crouter")return{key:"project",kind:"project",root:n};for(const t of ae()){const r=k(de(t.profileId));if(n===r)return{key:`profile:${t.profileId}`,kind:"profile",root:r,profileId:t.profileId}}throw new Error(`settings root is not a recognized owner: ${e}`)}s(A,"settingsOwnerForRoot");function P(e){if(oe(e)===".crouter"){let n;try{n=k(e)}catch(o){if(o.code==="ENOENT")return null;throw o}if(n!==k(ce()))return E(h(e))}return G(A(e).key).settings}s(P,"readRawAtRoot");async function me(e,n){let o;return await B(t=>{const r=t.settings[e],i=n(structuredClone(r??{}));return o=i.value,[{op:"settings.replace",owner:e,raw:i.next,ifRevision:q(r)}]}),o}s(me,"mutatePersonSettings");async function J(e,n){const o=A(e);if(o.kind==="project"){W(h(o.root),t=>U(t,n,{createParent:!1}));return}await me(o.key,()=>({value:void 0,next:n}))}s(J,"writeRawAtRoot");async function S(e,n){const o=A(e);return o.kind==="project"?W(h(o.root),t=>{const r=n(E(t)??{});return U(t,r.next,{createParent:!1}),r.value}):await me(o.key,n)}s(S,"mutateRawAtRoot");const I=globalThis,C=Symbol.for("crtr.broker.config-snapshot");function wo(e){I[C]=e}s(wo,"installBrokerConfigSnapshot");function ko(){return I[C]}s(ko,"brokerConfigSnapshot");function kn(e){const n=I[C];if(n){if(e!=="user")throw new Error(`broker config snapshot has no ${e} scope`);return n.user}const o=g(e);return o?An(o):v()}s(kn,"readConfig");function An(e){const n=P(e);return n?D(n):v()}s(An,"readConfigAtRoot");function Sn(e){const n=E(H(e));return{marketplaces:n?.marketplaces??{},plugins:n?.plugins??{},activeCanvas:n?.activeCanvas??null,...n?.last_self_check===void 0?{}:{last_self_check:n.last_self_check}}}s(Sn,"readProjectState");function jn(e){const n=g(e);return n?e==="project"?Ve(n)?Sn(n):ie():hn(A(n).key):ie()}s(jn,"readState");class xn extends Error{static{s(this,"InvalidRawSettingsError")}constructor(n){super(`settings cannot be merged: ${n instanceof Error?n.message:String(n)}`),this.name="InvalidRawSettingsError"}}function pe(e){if(e.scope==="user")return"user";if(!ae().some(n=>n.profileId===e.profileId))throw mn(`profile not found: ${e.profileId}`,{received:e.profileId,next:"Run `crtr profile list` to see available profiles."});return`profile:${e.profileId}`}s(pe,"rawSettingsKeyForTarget");function Ao(e){return G(pe(e))}s(Ao,"readRawSettingsForTarget");async function So(e,n,o){const t=pe(e);try{D(n)}catch(u){throw new xn(u)}if(G(t).revision!==o)throw new fe;const i=await B(u=>q(u.settings[t])===o?[{op:"settings.replace",owner:t,raw:n,ifRevision:o}]:(()=>{throw new fe})()),c=q(i.settings[t]);return{revision:c,changed:c!==o}}s(So,"replaceRawSettingsForTarget");async function jo(e,n){const o=w(e);return await S(o,t=>{const r=n(t);return{value:r,next:r}})}s(jo,"updateRawConfigAtomically");async function xo(e,n){await J(w(e),n)}s(xo,"writeConfig");async function Ro(e,n){await J(w(e),n)}s(Ro,"writeRawConfig");function Rn(e,n){W(H(e),o=>U(o,n,{createParent:!1}))}s(Rn,"writeProjectState");async function On(e,n){const o=w(e);if(e==="project"){Rn(o,n);return}const t=A(o).key;await B(()=>[{op:"state.write",owner:t,state:n}])}s(On,"writeState");async function Oo(e,n){if(e!=="project"){gn(n);return}try{Xe(n)}catch(o){if(o.code!=="EEXIST"||!Qe(n).isDirectory())throw o}E(h(n))===null&&await J(n,{schema_version:v().schema_version})}s(Oo,"ensureScopeInitialized");function En(e){if(e===null||typeof e!="object"||Array.isArray(e))return{};const n={};for(const[o,t]of Object.entries(e))Array.isArray(t)&&t.every(r=>typeof r=="string")&&(n[o]=[...t]);return n}s(En,"normalizeKeybindings");const Pn=new Set(an.filter(ln).map(e=>e.id));function In(e){return Array.isArray(e)?[...new Set(e.filter(n=>typeof n=="string"&&fn.has(n)&&Pn.has(n)))]:[]}s(In,"normalizeTmuxPassthrough");function T(e,n=cn()){const o=n;if(e===null||typeof e!="object")return o;const t=e,r={anthropic:{...o.anthropic},openai:{...o.openai},...o.defaultProvider!==void 0?{defaultProvider:o.defaultProvider}:{}};(t.defaultProvider==="anthropic"||t.defaultProvider==="openai")&&(r.defaultProvider=t.defaultProvider);for(const i of["anthropic","openai"]){const c=t[i];if(!(c===null||typeof c!="object"))for(const[u,d]of Object.entries(c))(u==="ultra"||u==="strong"||u==="medium"||u==="light")&&typeof d=="string"&&(r[i][u]=d)}return r}s(T,"mergeModelLadders");function ge(e){return e==="ultra"||e==="strong"||e==="medium"||e==="light"}s(ge,"validStrength");const Z=["ultra","strong","medium","light"];function Y(){return Object.create(null)}s(Y,"routeRecord");function j(e,n,o){Object.defineProperty(e,n,{value:o,writable:!0,enumerable:!0,configurable:!0})}s(j,"setRouteRecord");function ye(e){const n=Y();for(const[o,t]of Object.entries(e??{}))j(n,o,t);return n}s(ye,"copyRouteRecord");function x(e,n){se({level:"warn",event:"config.model_routes.invalid",fields:{route_id:e,rule:n}})}s(x,"routeInvalid");function $(e,n={}){const o=ye(n);if(e===null||typeof e!="object"||Array.isArray(e))return o;for(const[t,r]of Object.entries(e)){if(t.trim()===""){x(t,"route id must be a non-empty string");continue}if(r===null||typeof r!="object"||Array.isArray(r)){x(t,"route must be an object");continue}const i=r,c=o[t],u={...c};let d;for(const f of["family","credentialSource","providerId"]){const a=i[f];if(a!==void 0){if(typeof a!="string"||a.trim()===""){d=`${f} must be a non-empty string`;break}u[f]=a}}if(d===void 0&&i.models!==void 0)if(i.models===null||typeof i.models!="object"||Array.isArray(i.models))d="models must be an object";else{const f={...c?.models};for(const[a,l]of Object.entries(i.models)){if(!ge(a)){d=`models.${a} is not a model strength`;break}if(typeof l!="string"||l.trim()===""){d=`models.${a} must be a non-empty provider/id ref`;break}const m=l.indexOf("/");if(m<=0||m===l.length-1){d=`models.${a} must be provider/id`;break}f[a]=l}u.models=f}if(d===void 0&&u.models!==void 0&&u.providerId!==void 0){for(const[f,a]of Object.entries(u.models))if(a.slice(0,a.indexOf("/"))!==u.providerId){d=`models.${f} provider prefix must equal providerId`;break}}if(d!==void 0){x(t,d);continue}j(o,t,u)}return o}s($,"mergeModelRoutePatches");function he(e){const n=Y(),o=Y();for(const[t,r]of Object.entries(e)){if(r.family===void 0||r.credentialSource===void 0||r.providerId===void 0||r.models===void 0){x(t,"route must include family, credentialSource, providerId, and models");continue}const i=Z.filter(c=>r.models[c]===void 0);if(i.length>0){x(t,`incomplete route; missing strengths: ${i.join(", ")}`),j(o,t,{family:r.family,credentialSource:r.credentialSource,providerId:r.providerId,models:r.models});continue}j(n,t,{family:r.family,credentialSource:r.credentialSource,providerId:r.providerId,models:r.models})}return{complete:n,legacy:o}}s(he,"resolveModelRoutes");function Cn(e,n){if(e.trim()==="")throw new Error("model route id must be a non-empty string");for(const t of["family","credentialSource","providerId"])if(typeof n[t]!="string"||n[t].trim()==="")throw new Error(`model route "${e}" ${t} must be a non-empty string`);const o=Z.filter(t=>n.models?.[t]===void 0);if(o.length>0)throw new Error(`model route "${e}" is incomplete; missing strengths: ${o.join(", ")}`);for(const t of Z){const r=n.models[t];if(typeof r!="string"||r.trim()==="")throw new Error(`model route "${e}" models.${t} must be a non-empty provider/id ref`);const i=r.indexOf("/");if(i<=0||i===r.length-1)throw new Error(`model route "${e}" models.${t} must be provider/id`);if(r.slice(0,i)!==n.providerId)throw new Error(`model route "${e}" models.${t} provider prefix must equal providerId`)}}s(Cn,"assertCompleteModelRoute");function M(e,n){if(e===void 0||e===null||typeof e!="object"||Array.isArray(e))return n;const o=e,t=s(i=>Array.isArray(i)&&i.every(c=>typeof c=="string"&&c.trim()!=="")?[...i]:void 0,"strings"),r=Array.isArray(o.strengthFallback)&&o.strengthFallback.every(i=>ge(i))?[...o.strengthFallback]:void 0;return{...t(o.credentialSourceOrder)!==void 0?{credentialSourceOrder:t(o.credentialSourceOrder)}:{},...t(o.familyOrder)!==void 0?{familyOrder:t(o.familyOrder)}:{},...r!==void 0?{strengthFallback:r}:{}}}s(M,"mergeModelRouting");function Tn(e){return e!==""&&e.replace(/\*/g,"")===""}s(Tn,"isInheritAllGlob");function N(e,n=[]){const o=e,t=Array.isArray(o?.allow)?o.allow.filter(r=>typeof r=="string"):[];for(const r of t)if(Tn(r))throw new Error(`spawnEnv.allow: "${r}" matches every env var name (inherit-all) \u2014 this is exactly the leak the spawn-env boundary closes. Name the specific vars/prefixes you actually need instead (e.g. "AWS_*", "GIT_ASKPASS").`);return{allow:[...new Set([...n,...t])]}}s(N,"mergeSpawnEnv");function L(e,n={}){const o={};for(const[t,r]of Object.entries(n))o[t]={...r};if(e!==null&&typeof e=="object")for(const[t,r]of Object.entries(e)){if(r===null||typeof r!="object")continue;const i={...o[t]},c=r.fastMode;typeof c=="boolean"&&(i.fastMode=c),o[t]=i}return o}s(L,"mergeProviderOptions");const be=/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/,$n=new Set(["crtr","crtrd"]);function F(e,n){if(n==="bin"&&$n.has(e))return`${n}.${e} is reserved by crouter \u2014 pick another bare command name`;if(!be.test(e))return`${n}.${e} is not a safe bare command name (expected ${be.source})`}s(F,"bareCommandNameIssue");function Mn(e){if(e===null||typeof e!="object"||Array.isArray(e))return[{name:"bin",message:"bin must be a JSON object mapping bare command name to a relative path"}];const n=[];for(const[o,t]of Object.entries(e)){const r=F(o,"bin");r!==void 0&&n.push({name:o,message:r}),(typeof t!="string"||t.trim()==="")&&n.push({name:o,message:`bin.${o} must be a non-empty relative path to an executable inside the contributor root`})}return n}s(Mn,"binDeclarationIssues");function Nn(e){if(e===null||typeof e!="object"||Array.isArray(e))return{};const n={};for(const[o,t]of Object.entries(e))F(o,"bin")===void 0&&(typeof t!="string"||t.trim()===""||(n[o]=t));return n}s(Nn,"normalizeBin");const _e=/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;function Ln(e){if(e===null||typeof e!="object"||Array.isArray(e))return[{name:"humanActions",reason:"humanActions must be a JSON object mapping action names to {argv, cwd}",block:!0}];const n=[];for(const[o,t]of Object.entries(e)){const r=[];if(_e.test(o)||r.push(`action name must match ${_e.source}`),t===null||typeof t!="object"||Array.isArray(t))r.push("action must be an object with argv and cwd");else{const i=t,c=Object.keys(i).filter(u=>u!=="argv"&&u!=="cwd");c.length>0&&r.push(`action has unsupported field${c.length===1?"":"s"}: ${c.join(", ")}`),(!Array.isArray(i.argv)||i.argv.length===0||!i.argv.every(u=>typeof u=="string"&&u.length>0))&&r.push("argv must be a non-empty array of non-empty strings"),(typeof i.cwd!="string"||i.cwd.length===0)&&r.push("cwd is required and must be a non-empty string")}r.length>0&&n.push({name:o,reason:r.join("; ")})}return n}s(Ln,"humanActionDeclarationIssues");function Fn(e){if(e===null||typeof e!="object"||Array.isArray(e))return{};const n=new Set(Ln(e).map(t=>t.name)),o={};for(const[t,r]of Object.entries(e)){if(n.has(t))continue;const i=r;o[t]={argv:[...i.argv],cwd:i.cwd}}return o}s(Fn,"normalizeHumanActions");function Eo(e){return Mn(e).map(n=>n.message)}s(Eo,"invalidPluginBinReasons");function Po(e){if(e===null||typeof e!="object"||Array.isArray(e))return{};const n={};for(const[o,t]of Object.entries(e))F(o,"requires")===void 0&&(typeof t!="string"||t.trim()===""||/[\r\n]/.test(t)||(n[o]=t));return n}s(Po,"normalizeRequires");function Io(e){if(e===null||typeof e!="object"||Array.isArray(e))return["requires must be a JSON object mapping a bare executable name to a one-line install hint"];const n=[];for(const[o,t]of Object.entries(e)){const r=F(o,"requires");r!==void 0&&n.push(r),typeof t!="string"||t.trim()===""?n.push(`requires.${o} must be a non-empty one-line install hint`):/[\r\n]/.test(t)&&n.push(`requires.${o} must be a one-line install hint`)}return n}s(Io,"invalidPluginRequiresReasons");function ve(e){if(e===null||typeof e!="object")return null;const n=e,o={};typeof n.whenToUse=="string"&&n.whenToUse.trim()!==""&&(o.whenToUse=n.whenToUse),typeof n.model=="string"&&n.model.trim()!==""&&(o.model=n.model),typeof n.orchestratorModel=="string"&&n.orchestratorModel.trim()!==""&&(o.orchestratorModel=n.orchestratorModel);const t=s(r=>Array.isArray(r)&&r.every(i=>typeof i=="string"),"isStringArray");return t(n.tools)&&(o.tools=n.tools),t(n.extensions)&&(o.extensions=n.extensions),t(n.availableTo)&&(o.availableTo=n.availableTo),Object.keys(o).length>0?o:null}s(ve,"normalizeKindFields");function R(e,n=un()){const o={...n};if(e!==null&&typeof e=="object")for(const[t,r]of Object.entries(e)){const i=ve(r);if(i===null)continue;const c=o[t];c!==void 0?o[t]={...c,...i}:i.whenToUse!==void 0&&(o[t]=i)}return o}s(R,"mergeKinds");function we(e,n,o){if(n===null)return o;const t=ue(e,n).filter(i=>i.enabled&&i.manifest.kinds!==void 0).sort((i,c)=>i.name.localeCompare(c.name));let r=o;for(const i of t)r=R(i.manifest.kinds,r);return r}s(we,"layerPluginKinds");function Co(){const e=[{origin:"config.json",components:kn("user").page_components}],n=g("user");if(n!==null){const o=ue("user",n).filter(t=>t.enabled&&t.manifest.page_components!==void 0).sort((t,r)=>t.name.localeCompare(r.name));for(const t of o){const r=`plugin "${t.name}" page_components`;e.push({origin:`plugin "${t.name}"`,components:le(t.manifest.page_components,r)})}}return wn(e)}s(Co,"resolvePageComponents");const V=new Set(["whenToUse","model","orchestratorModel","tools","extensions","availableTo"]);function To(e){if(e===null||typeof e!="object"||Array.isArray(e))return["kinds must be a JSON object keyed by kind name"];const n=[],o=s(t=>Array.isArray(t)&&t.every(r=>typeof r=="string"),"isStringArray");for(const[t,r]of Object.entries(e)){if(t.trim()===""){n.push("kinds contains an empty kind name");continue}if(r===null||typeof r!="object"||Array.isArray(r)){n.push(`kinds.${t} must be a JSON object`);continue}const i=r;for(const c of Object.keys(i))V.has(c)||n.push(`kinds.${t}.${c} is not a KindConfig field (expected one of: ${[...V].join(", ")})`);i.whenToUse!==void 0&&(typeof i.whenToUse!="string"||i.whenToUse.trim()==="")&&n.push(`kinds.${t}.whenToUse must be a non-empty string`);for(const c of["model","orchestratorModel"])i[c]!==void 0&&(typeof i[c]!="string"||i[c].trim()==="")&&n.push(`kinds.${t}.${c} must be a non-empty string`);for(const c of["tools","extensions","availableTo"])i[c]!==void 0&&!o(i[c])&&n.push(`kinds.${t}.${c} must be an array of strings`);ve(r)===null&&n.push(`kinds.${t} has no valid KindConfig field \u2014 an entry must set at least one of: ${[...V].join(", ")} (whenToUse is required when the kind exists in no lower layer)`)}return n}s(To,"invalidPluginKindsReasons");function Dn(e){if(e===null||typeof e!="object")return null;const n=e;if(typeof n.previewEndpoint!="string"||n.previewEndpoint.trim()===""||typeof n.relayTokenRef!="string"||n.relayTokenRef.trim()==="")return null;const o={previewEndpoint:n.previewEndpoint,relayTokenRef:n.relayTokenRef};return typeof n.cpOrigin=="string"&&n.cpOrigin.trim()!==""&&(o.cpOrigin=n.cpOrigin),o}s(Dn,"normalizeRemoteCanvasTarget");function zn(e,n=dn()){const o={...n.targets};if(e!==null&&typeof e=="object"){const t=e;if(t.targets!==null&&typeof t.targets=="object")for(const[r,i]of Object.entries(t.targets)){const c=Dn(i);c!==null&&(o[r]=c)}}return{targets:o}}s(zn,"mergeRemoteCanvas");function ke(e,n){return e===!0?"notify":e===!1?!1:e==="notify"||e==="apply"?e:n}s(ke,"normalizeMode");function O(e,n){return typeof e=="number"&&Number.isSafeInteger(e)&&e>=1?e:n}s(O,"normalizePositiveInteger");function Kn(e,n){const o=e!==null&&typeof e=="object"?e:{},t=O(o.warning,n.warning),r=O(o.automaticReviveCap,n.automaticReviveCap);return t<=r?{warning:t,automaticReviveCap:r}:{...n}}s(Kn,"mergeBrokerThresholds");function Un(e,n){return{unattendedParkMs:O((e!==null&&typeof e=="object"?e:{}).unattendedParkMs,n.unattendedParkMs)}}s(Un,"mergeLifecycle");function Gn(e){if(!Array.isArray(e))return[...re];const n=e.filter(o=>typeof o=="string"&&o.trim()!=="").map(o=>o.trim());return n.length>0?n:[...re]}s(Gn,"normalizeWhipMessages");function qn(e,n){if(e===null||typeof e!="object")return{...n};const o=e.tcp;return{tcp:typeof o=="string"?o:n.tcp}}s(qn,"mergeApi");let Ae=!1;function D(e){!Ae&&e.ledger!==void 0&&(Ae=!0,se({level:"warn",event:"config.ledger.ignored",fields:{variables:Object.values(pn)}}));const n=v(),o=n.schema_version,t=e.marketplaces===void 0?{}:e.marketplaces,r=e.plugins===void 0?{}:e.plugins,i=e.auto_update,c=i&&typeof i.interval_hours=="number"?i.interval_hours:void 0,u=c!==void 0&&Number.isFinite(c)&&c>=0?c:n.auto_update.interval_hours,d={crtr:ke(i?.crtr,n.auto_update.crtr),content:ke(i?.content,n.auto_update.content),interval_hours:u},f=e.max_panes_per_window,a=O(f,n.max_panes_per_window),l=typeof e.auto_open_child_viewers=="boolean"?e.auto_open_child_viewers:n.auto_open_child_viewers,m=Kn(e.brokerThresholds,n.brokerThresholds),y=Un(e.lifecycle,n.lifecycle),z=typeof e.completion_bell=="boolean"?e.completion_bell:n.completion_bell,p=e.working_gerunds===void 0?[...n.working_gerunds]:vn(e.working_gerunds),je=typeof e.whip_mode=="boolean"?e.whip_mode:n.whip_mode,xe=le(e.page_components),Re=typeof e.page_surface=="boolean"?e.page_surface:n.page_surface,Oe=Gn(e.whip_messages),Ee=sn.includes(e.whip_message_mode)?e.whip_message_mode:n.whip_message_mode,Pe=rn.includes(e.mouse_mode_default)?e.mouse_mode_default:n.mouse_mode_default,Ie=O(e.live_cycles,n.live_cycles),Ce=tn.includes(e.condensed_history)?e.condensed_history:n.condensed_history,Te=typeof e.bash_tool_purpose=="boolean"?e.bash_tool_purpose:n.bash_tool_purpose,$e=typeof e.fold_finished_tools=="boolean"?e.fold_finished_tools:n.fold_finished_tools,Me=typeof e.summarize_tool_calls=="boolean"?e.summarize_tool_calls:n.summarize_tool_calls,Ne=typeof e.detailed_tool_recaps=="boolean"?e.detailed_tool_recaps:n.detailed_tool_recaps,Le=Number.isInteger(e.document_port)&&e.document_port>=1&&e.document_port<=65535?e.document_port:n.document_port,Fe=typeof e.consolidate_replies=="boolean"?e.consolidate_replies:n.consolidate_replies,De=En(e.keybindings),ze=In(e.tmux_passthrough),Ke=T(e.modelLadders),Ue=L(e.providerOptions,n.providerOptions),K=he($(e.modelRoutes)),X=K.complete,Q=M(e.modelRouting,void 0),Ge=R(e.kinds),qe=zn(e.remoteCanvas),Be=qn(e.api,n.api),b=e.paths;if(b?.user_files!==void 0&&(typeof b.user_files!="string"||!b.user_files.startsWith("/")))throw new Error("paths.user_files must be an absolute path");const ee=e.store,_=e.runtime;if(_?.person_uids!==void 0&&(!Array.isArray(_.person_uids)||!_.person_uids.every(ne=>Number.isSafeInteger(ne)&&ne>=0)))throw new Error("runtime.person_uids must be an array of nonnegative integer uids");const We=N(e.spawnEnv,n.spawnEnv?.allow),He=Nn(e.bin),Je=Fn(e.humanActions);return{bin:He,humanActions:Je,schema_version:o,marketplaces:t,plugins:r,auto_update:d,max_panes_per_window:a,auto_open_child_viewers:l,brokerThresholds:m,lifecycle:y,completion_bell:z,working_gerunds:p,whip_mode:je,page_components:xe,page_surface:Re,whip_messages:Oe,whip_message_mode:Ee,mouse_mode_default:Pe,live_cycles:Ie,condensed_history:Ce,bash_tool_purpose:Te,fold_finished_tools:$e,summarize_tool_calls:Me,detailed_tool_recaps:Ne,document_port:Le,consolidate_replies:Fe,keybindings:De,tmux_passthrough:ze,modelLadders:Ke,providerOptions:Ue,...Object.keys(X).length>0?{modelRoutes:X}:{},...Object.keys(K.legacy).length>0?{legacyModelRoutes:K.legacy}:{},...Q!==void 0?{modelRouting:Q}:{},kinds:Ge,remoteCanvas:qe,api:Be,...b?{paths:b}:{},...ee?{store:ee}:{},..._?{runtime:_}:{},spawnEnv:We}}s(D,"mergeConfig");function Bn(e){return P(e)}s(Bn,"readRawConfigAtRoot");function Wn(e){const n=g(e);return n?Bn(n):null}s(Wn,"readRawScopeConfig");function Hn(e){if(e===null||e==="")return null;try{return P(de(e))}catch{return null}}s(Hn,"readRawProfileConfig");function Jn(e,n){return[...yn(e,n)].reverse().map(r=>({root:r,raw:P(r)}))}s(Jn,"readRawProjectScopeConfigs");function Se(e=process.cwd(),n=Ye()||null){const o=I[C];if(o)return o.launch;const t=v(),r=Wn("user"),i=Hn(n),c=Jn(e,n);let u=R(r?.kinds,we("user",g("user"),t.kinds)),d=T(r?.modelLadders,t.modelLadders),f=L(r?.providerOptions,t.providerOptions),a=$(r?.modelRoutes),l=M(r?.modelRouting,void 0),m=N(r?.spawnEnv,t.spawnEnv?.allow);i!==null&&(u=R(i.kinds,u),d=T(i.modelLadders,d),f=L(i.providerOptions,f),a=$(i.modelRoutes,a),l=M(i.modelRouting,l),m=N(i.spawnEnv,m.allow));for(const{root:z,raw:p}of c)u=R(p?.kinds,we("project",z,u)),p!==null&&(d=T(p.modelLadders,d),f=L(p.providerOptions,f),a=$(p.modelRoutes,a),l=M(p.modelRouting,l),m=N(p.spawnEnv,m.allow));const y=he(a);return{kinds:u,modelLadders:d,providerOptions:f,...Object.keys(y.complete).length>0?{modelRoutes:y.complete}:{},...Object.keys(y.legacy).length>0?{legacyModelRoutes:y.legacy}:{},...l!==void 0?{modelRouting:l}:{},spawnEnv:m}}s(Se,"readMergedLaunchConfig");function $o(e){return Se().kinds[e]}s($o,"resolveKindConfig");function Mo(e){const n=Se().kinds,o=[];for(const[t,r]of Object.entries(n)){const i=t.indexOf("/");if(i===-1)continue;const c=t.slice(0,i),u=r.availableTo??[c];(u.includes("*")||u.includes(e))&&o.push({kind:t,whenToUse:r.whenToUse})}return o.sort((t,r)=>t.kind.localeCompare(r.kind))}s(Mo,"subKindsAvailableTo");function Zn(e,n,o){const t={...e??{}};t.anthropic!==void 0&&(t.anthropic={...t.anthropic}),t.openai!==void 0&&(t.openai={...t.openai}),o.defaultProvider!==n.defaultProvider&&o.defaultProvider!==void 0&&(t.defaultProvider=o.defaultProvider);for(const i of["anthropic","openai"])for(const c of["ultra","strong","medium","light"])o[i][c]!==n[i][c]&&(t[i]={...t[i]??{},[c]:o[i][c]});return t.defaultProvider!==void 0||t.anthropic!==void 0||t.openai!==void 0?t:void 0}s(Zn,"sparseModelLaddersToPersist");async function Yn(e,n){return await S(e,o=>{const t=D(o),r=D(o);n(r);const i={...o};for(const u of _n)u!=="modelLadders"&&!en(r[u],t[u])&&(i[u]=r[u]);const c=Zn(o.modelLadders,t.modelLadders,r.modelLadders);return c!==void 0?i.modelLadders=c:delete i.modelLadders,{value:r,next:i}})}s(Yn,"mutateConfigAtRoot");async function No(e,n){await S(e,o=>{const t=o.modelLadders??{},r={...t};n.defaultProvider!==void 0&&(r.defaultProvider=n.defaultProvider);for(const i of["anthropic","openai"])n[i]!==void 0&&(r[i]={...t[i]??{},...n[i]});return{value:void 0,next:{...o,modelLadders:r}}})}s(No,"persistModelLadders");async function Lo(e,n,o){if(o!==void 0)Cn(n,o);else if(n.trim()==="")throw new Error("model route id must be a non-empty string");await S(e,t=>{const r=ye(t.modelRoutes??{});o===void 0?delete r[n]:j(r,n,{...o,models:{...o.models}});const i={...t};return Object.keys(r).length===0?delete i.modelRoutes:i.modelRoutes=r,{value:void 0,next:i}})}s(Lo,"persistModelRoute");async function Vn(e,n){return await Yn(e,n)}s(Vn,"updateConfigAtRoot");async function Fo(e,n){return await Vn(w(e),n)}s(Fo,"updateConfig");async function Do(e){const n=e.orchestrator===!0?"orchestratorModel":"model";return await S(e.root,o=>{const t={...o.kinds??{},[e.kind]:{...(o.kinds??{})[e.kind],[n]:e.model}};return{value:void 0,next:{...o,kinds:t}}}),{path:h(e.root),field:n}}s(Do,"persistDefaultKindModel");async function zo(e,n){const o=jn(e);return n(o),await On(e,o),o}s(zo,"updateState");export{xn as InvalidRawSettingsError,Mn as binDeclarationIssues,ko as brokerConfigSnapshot,_o as configPath,Oo as ensureScopeInitialized,Ln as humanActionDeclarationIssues,wo as installBrokerConfigSnapshot,Eo as invalidPluginBinReasons,To as invalidPluginKindsReasons,Io as invalidPluginRequiresReasons,Nn as normalizeBin,Fn as normalizeHumanActions,Po as normalizeRequires,In as normalizeTmuxPassthrough,Do as persistDefaultKindModel,No as persistModelLadders,Lo as persistModelRoute,kn as readConfig,An as readConfigAtRoot,Se as readMergedLaunchConfig,Bn as readRawConfigAtRoot,Wn as readRawScopeConfig,Ao as readRawSettingsForTarget,jn as readState,So as replaceRawSettingsForTarget,$o as resolveKindConfig,Co as resolvePageComponents,vo as statePath,Mo as subKindsAvailableTo,Fo as updateConfig,Vn as updateConfigAtRoot,jo as updateRawConfigAtomically,zo as updateState,xo as writeConfig,Ro as writeRawConfig,On as writeState};
@@ -0,0 +1,5 @@
1
+ /** The directory this process mints tokens against and reads provider bundles and sign-in pages from, without a trailing slash; null
2
+ * when it has none. A cloud role (`crtrd --role api|reconcile`) has no person outside a request, so it takes the issuer from the
3
+ * deployment's `CRTR_DIRECTORY_URL` (the daemon refuses to boot without it), never from one person's settings. Anything else (a
4
+ * Mac or a legacy single-person install) reads the user config's `runtime.issuer`, null while the runtime has not joined. */
5
+ export declare function directoryIssuer(): string | null;
@@ -0,0 +1 @@
1
+ var s=Object.defineProperty;var e=(r,o)=>s(r,"name",{value:o,configurable:!0});import{isCloudRole as u}from"../canvas/person-state.js";import{readConfig as i}from"../config.js";function R(){const r=u()?process.env.CRTR_DIRECTORY_URL:i("user").runtime?.issuer;return r?r.replace(/\/+$/,""):null}e(R,"directoryIssuer");export{R as directoryIssuer};
@@ -28,6 +28,9 @@ export declare function ensureOwner(db: CanvasDb, type: OwnerType, scheme: strin
28
28
  * A profile with no object yet has nothing to rename (its object is created from the manifest name
29
29
  * on first use). Returns whether an object was renamed. Links are edges to ids, so they keep resolving. */
30
30
  export declare function renameProfileObject(db: CanvasDb, profileId: string, handle: string): Promise<boolean>;
31
+ /** The handle a profile's object carries. Handles are unique per owner type in the one database, and on a cloud role many people each hold
32
+ * a profile of the same name (every person's default `app-<app>`, or a `work`): the profile id is unique, so it is the handle there. A Mac keeps the name. */
33
+ export declare function profileHandle(profileId: string, name: string): string;
31
34
  /** The owner object of a principal: `app:<x>` → app `<x>`; `user:<id>` → person (handle `user` for this install's person, else the id).
32
35
  * A cloud role serves many people and `personId()` is the request's person, so every person there gets the id as its handle: the one
33
36
  * `user` handle would otherwise go to whichever person was created first and refuse every other person with a `conflict`. */
@@ -1,17 +1,17 @@
1
- var F=Object.defineProperty;var s=(e,t)=>F(e,"name",{value:t,configurable:!0});import{CrtrError as j,notFound as M}from"../errors.js";import{ExitCode as y}from"../../types.js";import{newNodeId as H}from"../runtime/nodes.js";import{kindOf as W}from"./kinds/registry.js";import{bodyOf as v}from"./bodies.js";import{isCloudRole as k,personId as J,personIdAsync as P}from"../canvas/person.js";import{check as K,permits as L,resolveReader as I}from"../permissions/evaluator.js";import{userSubject as b,writeActsFor as D}from"../permissions/facts.js";const Y=new Set(["person","app","repo","profile"]);function T(e){return e||null}s(T,"rowFrom");async function X(e,t){return T(await e.prepare("SELECT * FROM objects WHERE id = ?").get(t))}s(X,"getById");async function z(e,t){return await W(e,t.type),await e.prepare(`INSERT INTO objects (id, type, kind, owner, privacy, name, preview, created_at, home_machine, schema_type)
2
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(t.id??await H(),t.type,t.kind,t.owner,t.privacy,t.name,t.preview,new Date().toISOString(),t.home_machine??null,t.schema_type??null)}s(z,"createObject");const B=s((e,t)=>`owner:${e}:${t}`,"ownerLockKey");async function x(e,t,o,i,n,c){if(!Y.has(t))throw new Error(`ensureOwner: ${t} is not an owner type`);const d=s(async()=>await e.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
1
+ var M=Object.defineProperty;var s=(e,t)=>M(e,"name",{value:t,configurable:!0});import{CrtrError as y,notFound as H}from"../errors.js";import{ExitCode as I}from"../../types.js";import{newNodeId as W}from"../runtime/nodes.js";import{kindOf as v}from"./kinds/registry.js";import{bodyOf as k}from"./bodies.js";import{isCloudRole as L,personId as J,personIdAsync as P}from"../canvas/person.js";import{check as K,permits as O,resolveReader as b}from"../permissions/evaluator.js";import{userSubject as D,writeActsFor as T}from"../permissions/facts.js";const Y=new Set(["person","app","repo","profile"]);function x(e){return e||null}s(x,"rowFrom");async function X(e,t){return x(await e.prepare("SELECT * FROM objects WHERE id = ?").get(t))}s(X,"getById");async function z(e,t){return await v(e,t.type),await e.prepare(`INSERT INTO objects (id, type, kind, owner, privacy, name, preview, created_at, home_machine, schema_type)
2
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING *`).get(t.id??await W(),t.type,t.kind,t.owner,t.privacy,t.name,t.preview,new Date().toISOString(),t.home_machine??null,t.schema_type??null)}s(z,"createObject");const B=s((e,t)=>`owner:${e}:${t}`,"ownerLockKey");async function $(e,t,o,i,n,c){if(!Y.has(t))throw new Error(`ensureOwner: ${t} is not an owner type`);const d=s(async()=>await e.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
3
3
  WHERE i.scheme = ? AND i.external_id = ?`).get(o,i),"find"),E=await d();if(E)return E;await e.prepare("SELECT pg_advisory_xact_lock(hashtext(?))").get(B(o,i));const w=await d();if(w)return w;const p=await e.prepare(`SELECT o.id, o.type, i.scheme, i.external_id FROM objects o
4
4
  LEFT JOIN identities i ON i.object = o.id
5
- WHERE o.name = ? AND o.type = ? AND o.home_machine IS NULL`).get(n,t);if(p)throw new j("conflict",`handle "${n}" is held by ${p.type} ${p.id} (${p.scheme}:${p.external_id}); cannot give it to ${t} ${o}:${i}`,y.GENERAL,{handle:n,holder:p.id});const N=await z(e,{type:t,kind:null,owner:t==="profile"?c?.id??null:null,privacy:"public",name:n,preview:""});return await e.prepare("INSERT INTO identities (object, scheme, external_id) VALUES (?, ?, ?)").run(N.id,o,i),t==="person"&&o==="user"?await D(e,N.id,b(i).id):(t==="profile"||t==="repo")&&await D(e,N.id,b(await P()).id),N}s(x,"ensureOwner");async function se(e,t,o){const i=await e.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
5
+ WHERE o.name = ? AND o.type = ? AND o.home_machine IS NULL`).get(n,t);if(p)throw new y("conflict",`handle "${n}" is held by ${p.type} ${p.id} (${p.scheme}:${p.external_id}); cannot give it to ${t} ${o}:${i}`,I.GENERAL,{handle:n,holder:p.id});const N=await z(e,{type:t,kind:null,owner:t==="profile"?c?.id??null:null,privacy:"public",name:n,preview:""});return await e.prepare("INSERT INTO identities (object, scheme, external_id) VALUES (?, ?, ?)").run(N.id,o,i),t==="person"&&o==="user"?await T(e,N.id,D(i).id):(t==="profile"||t==="repo")&&await T(e,N.id,D(await P()).id),N}s($,"ensureOwner");async function se(e,t,o){if(L())return!1;const i=await e.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
6
6
  WHERE i.scheme = 'profile' AND i.external_id = ? AND o.type = 'profile' AND o.deleted_at IS NULL`).get(t),n=await e.prepare(`SELECT o.id FROM objects o
7
- WHERE o.name = ? AND o.type = 'profile' AND o.deleted_at IS NULL AND o.home_machine IS NULL AND o.id <> ?`).get(o,i?i.id:"");if(n)throw new j("conflict",`handle "${o}" is already held by profile object ${n.id}; cannot rename profile ${t} to it`,y.GENERAL,{handle:o,holder:n.id});if(!i)return!1;const c=i;return c.name===o?!1:(await G(e,c,o),!0)}s(se,"renameProfileObject");async function ae(e,t){if(/^app:[a-z0-9][a-z0-9._-]*$/.test(t))return await x(e,"app","app",t,t.slice(4));if(/^user:[a-z0-9][a-z0-9._-]*$/.test(t))return await x(e,"person","user",t,!k()&&t===J()?"user":t.slice(5));throw new Error(`principal ${JSON.stringify(t)} is neither app:<name> nor user:<id>`)}s(ae,"ownerForGrantee");async function ce(e,t,o){return T(await e.prepare("SELECT * FROM objects WHERE (type = 'document' OR strpos(type, '.') > 0) AND owner = ? AND name = ? AND deleted_at IS NULL AND home_machine IS NULL").get(t,o))}s(ce,"liveDocumentNamed");async function de(e,t){const o=await X(e,t);if(!o||o.type!=="node")throw M(`no node object ${t}`);return o}s(de,"nodeObject");async function G(e,t,o){await e.prepare("UPDATE objects SET name = ? WHERE id = ?").run(o,t.id)}s(G,"setObjectName");async function pe(e,t,o){await e.prepare("UPDATE objects SET kind = ? WHERE id = ?").run(o,t.id)}s(pe,"setObjectKind");function O(e,t){const o=[];return e.type!==void 0&&(o.push("o.type = ?"),t.push(e.type)),e.kind!==void 0&&(o.push("o.kind = ?"),t.push(e.kind)),e.owner!==void 0&&(o.push("o.owner = ?"),t.push(e.owner.id)),e.namePrefix!==void 0&&(o.push("o.name LIKE ? ESCAPE '\\'"),t.push(`${e.namePrefix.replace(/[\\%_]/g,i=>`\\${i}`)}%`)),e.schemaType!==void 0&&(o.push("o.schema_type = ?"),t.push(e.schemaType)),e.extensions!==void 0&&(o.push("EXISTS (SELECT 1 FROM documents x WHERE x.object = o.id AND x.extensions_json::jsonb @> ?::jsonb)"),t.push(JSON.stringify(e.extensions))),o}s(O,"narrowing");function V(e,t){const o="WITH RECURSIVE sa(id, owner, depth) AS (SELECT o.id, o.owner, 0 UNION ALL SELECT p.id, p.owner, sa.depth + 1 FROM sa JOIN objects p ON p.id = sa.owner WHERE sa.depth < 12)";t.push(e.subjects.userIds,e.subjects.groupIds);const i=["o.schema_type IS NULL",`EXISTS (${o} SELECT 1 FROM sa JOIN relations r ON r.resource_type = 'crtr/object' AND r.resource_id = sa.id
7
+ WHERE o.name = ? AND o.type = 'profile' AND o.deleted_at IS NULL AND o.home_machine IS NULL AND o.id <> ?`).get(o,i?i.id:"");if(n)throw new y("conflict",`handle "${o}" is already held by profile object ${n.id}; cannot rename profile ${t} to it`,I.GENERAL,{handle:o,holder:n.id});if(!i)return!1;const c=i;return c.name===o?!1:(await G(e,c,o),!0)}s(se,"renameProfileObject");function ae(e,t){return L()?e:t}s(ae,"profileHandle");async function ce(e,t){if(/^app:[a-z0-9][a-z0-9._-]*$/.test(t))return await $(e,"app","app",t,t.slice(4));if(/^user:[a-z0-9][a-z0-9._-]*$/.test(t))return await $(e,"person","user",t,!L()&&t===J()?"user":t.slice(5));throw new Error(`principal ${JSON.stringify(t)} is neither app:<name> nor user:<id>`)}s(ce,"ownerForGrantee");async function de(e,t,o){return x(await e.prepare("SELECT * FROM objects WHERE (type = 'document' OR strpos(type, '.') > 0) AND owner = ? AND name = ? AND deleted_at IS NULL AND home_machine IS NULL").get(t,o))}s(de,"liveDocumentNamed");async function pe(e,t){const o=await X(e,t);if(!o||o.type!=="node")throw H(`no node object ${t}`);return o}s(pe,"nodeObject");async function G(e,t,o){await e.prepare("UPDATE objects SET name = ? WHERE id = ?").run(o,t.id)}s(G,"setObjectName");async function ue(e,t,o){await e.prepare("UPDATE objects SET kind = ? WHERE id = ?").run(o,t.id)}s(ue,"setObjectKind");function A(e,t){const o=[];return e.type!==void 0&&(o.push("o.type = ?"),t.push(e.type)),e.kind!==void 0&&(o.push("o.kind = ?"),t.push(e.kind)),e.owner!==void 0&&(o.push("o.owner = ?"),t.push(e.owner.id)),e.namePrefix!==void 0&&(o.push("o.name LIKE ? ESCAPE '\\'"),t.push(`${e.namePrefix.replace(/[\\%_]/g,i=>`\\${i}`)}%`)),e.schemaType!==void 0&&(o.push("o.schema_type = ?"),t.push(e.schemaType)),e.extensions!==void 0&&(o.push("EXISTS (SELECT 1 FROM documents x WHERE x.object = o.id AND x.extensions_json::jsonb @> ?::jsonb)"),t.push(JSON.stringify(e.extensions))),o}s(A,"narrowing");function V(e,t){const o="WITH RECURSIVE sa(id, owner, depth) AS (SELECT o.id, o.owner, 0 UNION ALL SELECT p.id, p.owner, sa.depth + 1 FROM sa JOIN objects p ON p.id = sa.owner WHERE sa.depth < 12)";t.push(e.subjects.userIds,e.subjects.groupIds);const i=["o.schema_type IS NULL",`EXISTS (${o} SELECT 1 FROM sa JOIN relations r ON r.resource_type = 'crtr/object' AND r.resource_id = sa.id
8
8
  AND r.relation IN ('reader', 'editor', 'messenger', 'user_of') AND r.written_by <> 'package'
9
9
  AND ((r.subject_type = 'user' AND r.subject_id = ANY(?::text[])) OR (r.subject_type = 'crtr/group' AND r.subject_relation = 'member' AND r.subject_id = ANY(?::text[]))))`];return e.userId===null?i:(t.push(e.userId),[...i,`NOT EXISTS (${o} SELECT 1 FROM sa JOIN relations r ON r.resource_type = 'crtr/object' AND r.resource_id = sa.id
10
- AND r.relation = 'acts_for' AND r.subject_type = 'user' AND r.subject_id = ?)`])}s(V,"sharedOnly");async function ue(e,t,o,i={}){const n=["o.seq > ?"],c=[i.after_seq??0];o.privacy!==void 0&&(n.push("o.privacy = ?"),c.push(o.privacy)),o.name!==void 0&&(n.push("o.name = ?"),c.push(o.name)),n.push("o.home_machine IS NULL"),n.push(o.deleted?"o.deleted_at IS NOT NULL":"o.deleted_at IS NULL"),n.push(...O(o,c));const d=await I(e,t);o.shared&&n.push(...V(d,c));const E=await L(e,d,"read","o"),w=Math.min(i.limit??100,100);return await e.prepareUncached(`SELECT o.* FROM objects o WHERE ${n.join(" AND ")} AND (${E.sql}) ORDER BY o.seq LIMIT ${w}`).all(...c,...E.params)}s(ue,"listObjects");async function Ee(e,t,o){const n=[],c=`%${o.text.replace(/[\\%_]/g,r=>`\\${r}`)}%`,d=await I(e,t);if(!o.grep){const r=[c],u=O(o,r).map(l=>` AND ${l}`).join(""),a=await L(e,d,"read","o"),m=await e.prepareUncached(`SELECT o.*, (SELECT d.summary FROM documents d WHERE d.object = o.id) AS _summary FROM objects o
10
+ AND r.relation = 'acts_for' AND r.subject_type = 'user' AND r.subject_id = ?)`])}s(V,"sharedOnly");async function Ee(e,t,o,i={}){const n=["o.seq > ?"],c=[i.after_seq??0];o.privacy!==void 0&&(n.push("o.privacy = ?"),c.push(o.privacy)),o.name!==void 0&&(n.push("o.name = ?"),c.push(o.name)),n.push("o.home_machine IS NULL"),n.push(o.deleted?"o.deleted_at IS NOT NULL":"o.deleted_at IS NULL"),n.push(...A(o,c));const d=await b(e,t);o.shared&&n.push(...V(d,c));const E=await O(e,d,"read","o"),w=Math.min(i.limit??100,100);return await e.prepareUncached(`SELECT o.* FROM objects o WHERE ${n.join(" AND ")} AND (${E.sql}) ORDER BY o.seq LIMIT ${w}`).all(...c,...E.params)}s(Ee,"listObjects");async function me(e,t,o){const n=[],c=`%${o.text.replace(/[\\%_]/g,r=>`\\${r}`)}%`,d=await b(e,t);if(!o.grep){const r=[c],u=A(o,r).map(l=>` AND ${l}`).join(""),a=await O(e,d,"read","o"),m=await e.prepareUncached(`SELECT o.*, (SELECT d.summary FROM documents d WHERE d.object = o.id) AS _summary FROM objects o
11
11
  WHERE o.deleted_at IS NULL AND o.home_machine IS NULL AND (o.name ILIKE ?1 ESCAPE '\\' OR o.preview ILIKE ?1 ESCAPE '\\'
12
12
  OR EXISTS (SELECT 1 FROM documents d WHERE d.object = o.id AND d.summary ILIKE ?1 ESCAPE '\\'))${u}
13
- AND (${a.sql}) ORDER BY o.seq LIMIT 100`).all(...r,...a.params),f=o.text.toLowerCase();for(const l of m){const{_summary:U,...g}=l,S=g,R=S.name.toLowerCase().includes(f)?"name":S.preview.toLowerCase().includes(f)?"preview":"summary";n.push({object:S,field:R,...R==="summary"?{text:String(U)}:{}})}if(n.length>=100)return n}if(!o.body&&!o.grep)return n;const E=new Set(n.map(r=>r.object.id)),w=o.grep?(r=>u=>r.test(u))(new RegExp(o.text)):r=>r.includes(o.text),p=[],N=O(o,p).map(r=>` AND ${r}`).join(""),$=await e.prepareUncached(`SELECT o.id, r.body_hash AS body_hash, d.source_path AS source_path FROM objects o
13
+ AND (${a.sql}) ORDER BY o.seq LIMIT 100`).all(...r,...a.params),f=o.text.toLowerCase();for(const l of m){const{_summary:g,...F}=l,S=F,j=S.name.toLowerCase().includes(f)?"name":S.preview.toLowerCase().includes(f)?"preview":"summary";n.push({object:S,field:j,...j==="summary"?{text:String(g)}:{}})}if(n.length>=100)return n}if(!o.body&&!o.grep)return n;const E=new Set(n.map(r=>r.object.id)),w=o.grep?(r=>u=>r.test(u))(new RegExp(o.text)):r=>r.includes(o.text),p=[],N=A(o,p).map(r=>` AND ${r}`).join(""),C=await e.prepareUncached(`SELECT o.id, r.body_hash AS body_hash, d.source_path AS source_path FROM objects o
14
14
  JOIN documents d ON d.object = o.id JOIN revisions r ON r.document = o.id
15
15
  WHERE o.type = 'document' AND o.deleted_at IS NULL AND NOT EXISTS (SELECT 1 FROM revision_parents p WHERE p.parent = r.rev_id)${N}
16
- ORDER BY o.seq, r.number`).all(...p),A=await L(e,d,"read","o"),C=200;let h=[];const _=s(async()=>{if(h.length===0)return!1;const r=await e.prepareUncached(`SELECT o.* FROM objects o WHERE o.id = ANY(?::text[]) AND (${A.sql})`).all(h.map(a=>a.id),...A.params),u=new Map(r.map(a=>[a.id,a]));for(const a of h){const m=u.get(a.id);if(m!==void 0&&(n.push({object:m,field:"body",line:a.line,text:a.text}),n.length>=100))return!0}return h=[],!1},"flush");for(const r of $){if(E.has(r.id))continue;let u;try{u=v(r.body_hash,r.source_path)}catch(f){if(await K(e,d,"read",r.id))throw f;continue}if(!o.grep&&!u.includes(o.text))continue;const a=u.split(`
17
- `),m=a.findIndex(w);if(!(m<0)&&(E.add(r.id),h.push({id:r.id,line:m+1,text:a[m]}),h.length>=C&&await _()))return n}return await _(),n}s(Ee,"searchObjects");export{z as createObject,x as ensureOwner,X as getById,ue as listObjects,ce as liveDocumentNamed,de as nodeObject,ae as ownerForGrantee,B as ownerLockKey,se as renameProfileObject,Ee as searchObjects,pe as setObjectKind,G as setObjectName};
16
+ ORDER BY o.seq, r.number`).all(...p),_=await O(e,d,"read","o"),U=200;let h=[];const R=s(async()=>{if(h.length===0)return!1;const r=await e.prepareUncached(`SELECT o.* FROM objects o WHERE o.id = ANY(?::text[]) AND (${_.sql})`).all(h.map(a=>a.id),..._.params),u=new Map(r.map(a=>[a.id,a]));for(const a of h){const m=u.get(a.id);if(m!==void 0&&(n.push({object:m,field:"body",line:a.line,text:a.text}),n.length>=100))return!0}return h=[],!1},"flush");for(const r of C){if(E.has(r.id))continue;let u;try{u=k(r.body_hash,r.source_path)}catch(f){if(await K(e,d,"read",r.id))throw f;continue}if(!o.grep&&!u.includes(o.text))continue;const a=u.split(`
17
+ `),m=a.findIndex(w);if(!(m<0)&&(E.add(r.id),h.push({id:r.id,line:m+1,text:a[m]}),h.length>=U&&await R()))return n}return await R(),n}s(me,"searchObjects");export{z as createObject,$ as ensureOwner,X as getById,Ee as listObjects,de as liveDocumentNamed,pe as nodeObject,ce as ownerForGrantee,B as ownerLockKey,ae as profileHandle,se as renameProfileObject,me as searchObjects,ue as setObjectKind,G as setObjectName};
@@ -1,3 +1,3 @@
1
- var b=Object.defineProperty;var p=(o,e)=>b(o,"name",{value:e,configurable:!0});import{coveredByAny as d,isSandboxedNode as C}from"../../shared/env.js";import{existsSync as S}from"node:fs";import{CrtrError as L}from"../errors.js";import{getCachedProfile as M}from"../scoped-state/person-store.js";import{parseScope as R}from"@north-light/identity";import{activeGrant as O,grantFor as v}from"../grants/mirror.js";import{ApiError as $}from"../../api/errors.js";import{pluginAppToken as A}from"../secrets.js";import{loadExactProfileManifest as T}from"../profiles/manifest.js";import{ensureOwner as y,getById as H,nodeObject as W,ownerForGrantee as D}from"./objects.js";import{personId as P}from"../canvas/person.js";import{warmCheckoutIdentities as F}from"./repo-sync/repos.js";async function N(o,e,n,a){const t=e===null?[]:a.projectPaths(e),r=await n(o),l=new Set(r.map(s=>s.repo.id)),c=[];for(const s of t){if(!S(s))continue;let i;try{i=await n(s)}catch(u){if(u instanceof L&&u.code==="git-lookup-failed")continue;throw u}for(const u of i)l.has(u.repo.id)||(l.add(u.repo.id),c.push(u))}return{repos:r.map(s=>s.repo),checkouts:r,projectRepos:c.map(s=>s.repo),projectCheckouts:c}}p(N,"viewRepos");async function te(o,e){const n=await o.prepare("SELECT cwd, profile_id FROM nodes WHERE node_id = ?").get(e);if(n!==void 0&&(await F(n.cwd),n.profile_id))for(const a of w(o).projectPaths(n.profile_id))S(a)&&await F(a)}p(te,"warmNodeRepos");async function ne(o,e,n){if(e.class==="anonymous")throw new Error("a publish token cannot read as a principal");if(e.class==="person")return{class:"person",person:await j(o)};if(e.class==="app")return{class:"app",app:await E(o,e.app),scopes:e.scopes,...e.sub===void 0?{}:{sub:e.sub}};if(e.class==="machine")throw new Error("a machine token cannot read as a principal");if(e.class==="forwarded"){if(e.actor.kind==="person")return{class:"person",person:await j(o)};const c=await v(e.actor.app,e.sub);if(!c)throw new $(403,"app_not_granted",`${e.actor.app} holds no active grant for ${e.sub} on this machine`);const s=e.actor.node_scopes;return{class:"app",app:await E(o,e.actor.app),place:e.actor.place,launched:s,scopes:s===null?c.resolved:s.filter(i=>d(c.resolved,i))}}if(e.class==="cron"){const c=e.grant?(await O(e.grant))?.resolved??[]:[],s=e.scopes;return{class:"app",app:await E(o,e.app),scopes:s===null?c:s.filter(i=>d(c,i)),launched:s,place:{run:e.origin_run??"",isolated:e.isolated}}}const a=w(o),t=await W(o,e.node),r=await o.prepare("SELECT cwd, profile_id, app FROM nodes WHERE node_id = ?").get(e.node),l=r.app===null?null:await a.object(r.app);if(!l)throw new Error(`node ${e.node} has no app`);return{class:"node",node:t,app:l,scopes:await x(e.app,e.sub,e.grant,e.scopes,a),launched:e.scopes,cwd:r.cwd,...await N(r.cwd,r.profile_id,n,a),profile:r.profile_id===null?null:await a.profile(r.profile_id,l)}}p(ne,"readerFor");async function B(o,e,n,a=w(o),t=!1){const r=await o.prepare("SELECT o.* FROM objects o WHERE o.id = ? AND o.type = 'node'").get(e),l=await o.prepare(`SELECT (SELECT external_id FROM identities WHERE object = nodes.app) AS grantee,
2
- sub, grant_id, scopes_json, cwd, profile_id, app FROM nodes WHERE node_id = ?`).get(e);if(!r||!l)throw new Error(`no node ${e}`);const{grantee:c,sub:s,grant_id:i,scopes_json:u,cwd:f,profile_id:g,app:h}=l,m=h===null?null:await a.object(h);if(!m)throw new Error(`node ${e} has no app`);const _=u===null?null:JSON.parse(u);return{class:"node",node:r,app:m,scopes:await x(c,s??"",i,_,a,t),launched:_,cwd:f,...await N(f,g,n,a),profile:g===null?null:await a.profile(g,m)}}p(B,"readerForNode");async function re(o,e,n=async()=>[],a=w(o)){const t=await o.prepare("SELECT envelope_json FROM peer_stubs WHERE object = ?").get(e.id);if(t!==void 0)return await J(o,e,t.envelope_json===null?null:JSON.parse(t.envelope_json),a);if(e.type==="node")return await B(o,e.id,n,a,!0);const r=e.owner===null?null:await a.object(e.owner);if(!r)throw new Error(`watcher ${e.id} has no owner`);const c=(r.type==="app"?r:r.type==="profile"&&r.owner!==null?await a.object(r.owner):null)??await o.prepare("SELECT o.* FROM identities i JOIN objects o ON o.id = i.object WHERE i.external_id = 'app:terminal' AND i.scheme = 'app'").get()??await o.prepare("SELECT * FROM objects WHERE type = 'person' AND name = 'user' AND home_machine IS NULL").get();if(!c)throw new Error("no terminal app or person object");const s=(await o.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(c.id))?.external_id??"app:terminal",i=await o.prepare("SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(s),u=r.type==="person"?null:i.length===0?a.pluginScopes(s):i.length===1?JSON.parse(i[0].resolved_scopes_json).filter(f=>f.mode==="always").map(f=>R(f.scope)):null;return{class:"object",object:e,node:e,owner:r,app:c,scopes:u,repos:r.type==="repo"?[r]:[]}}p(re,"readerForObject");async function ae(o,e,n){const a=(await o.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(e.id))?.external_id;if(a===void 0)return null;const t=await o.prepare("SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(a);return t.length===0?n.pluginScopes(a):t.length>1?null:JSON.parse(t[0].resolved_scopes_json).filter(r=>r.mode==="always").map(r=>R(r.scope))}p(ae,"appScopesNow");async function J(o,e,n,a){const t=await j(o);if(n!==null&&n.app===null)return{class:"object",object:e,node:e,owner:t,app:t,scopes:null,repos:[]};const r=n?.app==null?null:await E(o,n.app),l=n?.app==null?null:await a.grantFor(n.app,n.sub),c=n===null||l===null?null:n.node_scopes===null?l.resolved:n.node_scopes.filter(i=>d(l.resolved,i)),s=r??(e.owner===null?t:await a.object(e.owner)??t);return{class:"object",object:e,node:e,owner:s,app:s,scopes:c,launched:n?.node_scopes??null,repos:[],...n?.place==null?{}:{place:n.place}}}p(J,"readerForStub");function w(o){return{grantById:O,grantFor:v,pluginScopes:p(e=>A(e.replace(/^app:/,""))?.scopes??null,"pluginScopes"),object:p(async e=>await H(o,e),"object"),profile:p(async(e,n)=>await I(o,e,n),"profile"),projectPaths:p(e=>C()?[]:M(e)?.manifest.projects.map(n=>n.path)??[],"projectPaths")}}p(w,"liveLookups");function se(o){const e=w(o),n=new Map,a=p((t,r)=>(n.has(t)||n.set(t,r()),n.get(t)),"memo");return{grantById:p(t=>a(`g\0${t}`,()=>e.grantById(t)),"grantById"),grantFor:p((t,r)=>a(`a\0${t}\0${r}`,()=>e.grantFor(t,r)),"grantFor"),pluginScopes:p(t=>a(`t\0${t}`,()=>e.pluginScopes(t)),"pluginScopes"),object:p(async t=>await a(`o\0${t}`,async()=>await e.object(t)),"object"),profile:p(async(t,r)=>await a(`p\0${t}\0${r.id}`,async()=>await e.profile(t,r)),"profile"),projectPaths:p(t=>a(`j\0${t}`,()=>e.projectPaths(t)),"projectPaths")}}p(se,"publishLookups");async function x(o,e,n,a,t,r=!1){const l=n?await t.grantById(n):await t.grantFor(o,e),c=l&&l.grantee===o?l.resolved:null;if(c===null){if(r)return null;throw new Error(`the grant of ${o} is not active`)}return a===null?c:a.filter(s=>d(c,s))}p(x,"nodeScopes");async function j(o){return await D(o,P())}p(j,"personObject");async function E(o,e){return await y(o,"app","app",e,e.replace(/^app:/,""))}p(E,"appObject");async function I(o,e,n){return await o.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
3
- WHERE i.scheme = 'profile' AND i.external_id = ?`).get(e)??await y(o,"profile","profile",e,T(e).manifest.name,n)}p(I,"profileObject");export{ae as appScopesNow,w as liveLookups,I as profileObject,se as publishLookups,ne as readerFor,B as readerForNode,re as readerForObject,N as viewRepos,te as warmNodeRepos};
1
+ var b=Object.defineProperty;var p=(o,e)=>b(o,"name",{value:e,configurable:!0});import{coveredByAny as d,isSandboxedNode as C}from"../../shared/env.js";import{existsSync as S}from"node:fs";import{CrtrError as L}from"../errors.js";import{getCachedProfile as M}from"../scoped-state/person-store.js";import{parseScope as R}from"@north-light/identity";import{activeGrant as O,grantFor as v}from"../grants/mirror.js";import{ApiError as $}from"../../api/errors.js";import{pluginAppToken as A}from"../secrets.js";import{loadExactProfileManifest as H}from"../profiles/manifest.js";import{ensureOwner as y,getById as T,nodeObject as W,ownerForGrantee as D,profileHandle as P}from"./objects.js";import{personId as B}from"../canvas/person.js";import{warmCheckoutIdentities as F}from"./repo-sync/repos.js";async function N(o,e,n,r){const t=e===null?[]:r.projectPaths(e),a=await n(o),l=new Set(a.map(s=>s.repo.id)),c=[];for(const s of t){if(!S(s))continue;let i;try{i=await n(s)}catch(u){if(u instanceof L&&u.code==="git-lookup-failed")continue;throw u}for(const u of i)l.has(u.repo.id)||(l.add(u.repo.id),c.push(u))}return{repos:a.map(s=>s.repo),checkouts:a,projectRepos:c.map(s=>s.repo),projectCheckouts:c}}p(N,"viewRepos");async function ne(o,e){const n=await o.prepare("SELECT cwd, profile_id FROM nodes WHERE node_id = ?").get(e);if(n!==void 0&&(await F(n.cwd),n.profile_id))for(const r of w(o).projectPaths(n.profile_id))S(r)&&await F(r)}p(ne,"warmNodeRepos");async function ae(o,e,n){if(e.class==="anonymous")throw new Error("a publish token cannot read as a principal");if(e.class==="person")return{class:"person",person:await j(o)};if(e.class==="app")return{class:"app",app:await E(o,e.app),scopes:e.scopes,...e.sub===void 0?{}:{sub:e.sub}};if(e.class==="machine")throw new Error("a machine token cannot read as a principal");if(e.class==="forwarded"){if(e.actor.kind==="person")return{class:"person",person:await j(o)};const c=await v(e.actor.app,e.sub);if(!c)throw new $(403,"app_not_granted",`${e.actor.app} holds no active grant for ${e.sub} on this machine`);const s=e.actor.node_scopes;return{class:"app",app:await E(o,e.actor.app),place:e.actor.place,launched:s,scopes:s===null?c.resolved:s.filter(i=>d(c.resolved,i))}}if(e.class==="cron"){const c=e.grant?(await O(e.grant))?.resolved??[]:[],s=e.scopes;return{class:"app",app:await E(o,e.app),scopes:s===null?c:s.filter(i=>d(c,i)),launched:s,place:{run:e.origin_run??"",isolated:e.isolated}}}const r=w(o),t=await W(o,e.node),a=await o.prepare("SELECT cwd, profile_id, app FROM nodes WHERE node_id = ?").get(e.node),l=a.app===null?null:await r.object(a.app);if(!l)throw new Error(`node ${e.node} has no app`);return{class:"node",node:t,app:l,scopes:await x(e.app,e.sub,e.grant,e.scopes,r),launched:e.scopes,cwd:a.cwd,...await N(a.cwd,a.profile_id,n,r),profile:a.profile_id===null?null:await r.profile(a.profile_id,l)}}p(ae,"readerFor");async function J(o,e,n,r=w(o),t=!1){const a=await o.prepare("SELECT o.* FROM objects o WHERE o.id = ? AND o.type = 'node'").get(e),l=await o.prepare(`SELECT (SELECT external_id FROM identities WHERE object = nodes.app) AS grantee,
2
+ sub, grant_id, scopes_json, cwd, profile_id, app FROM nodes WHERE node_id = ?`).get(e);if(!a||!l)throw new Error(`no node ${e}`);const{grantee:c,sub:s,grant_id:i,scopes_json:u,cwd:f,profile_id:g,app:h}=l,m=h===null?null:await r.object(h);if(!m)throw new Error(`node ${e} has no app`);const _=u===null?null:JSON.parse(u);return{class:"node",node:a,app:m,scopes:await x(c,s??"",i,_,r,t),launched:_,cwd:f,...await N(f,g,n,r),profile:g===null?null:await r.profile(g,m)}}p(J,"readerForNode");async function re(o,e,n=async()=>[],r=w(o)){const t=await o.prepare("SELECT envelope_json FROM peer_stubs WHERE object = ?").get(e.id);if(t!==void 0)return await k(o,e,t.envelope_json===null?null:JSON.parse(t.envelope_json),r);if(e.type==="node")return await J(o,e.id,n,r,!0);const a=e.owner===null?null:await r.object(e.owner);if(!a)throw new Error(`watcher ${e.id} has no owner`);const c=(a.type==="app"?a:a.type==="profile"&&a.owner!==null?await r.object(a.owner):null)??await o.prepare("SELECT o.* FROM identities i JOIN objects o ON o.id = i.object WHERE i.external_id = 'app:terminal' AND i.scheme = 'app'").get()??await o.prepare("SELECT * FROM objects WHERE type = 'person' AND name = 'user' AND home_machine IS NULL").get();if(!c)throw new Error("no terminal app or person object");const s=(await o.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(c.id))?.external_id??"app:terminal",i=await o.prepare("SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(s),u=a.type==="person"?null:i.length===0?r.pluginScopes(s):i.length===1?JSON.parse(i[0].resolved_scopes_json).filter(f=>f.mode==="always").map(f=>R(f.scope)):null;return{class:"object",object:e,node:e,owner:a,app:c,scopes:u,repos:a.type==="repo"?[a]:[]}}p(re,"readerForObject");async function se(o,e,n){const r=(await o.prepare("SELECT external_id FROM identities WHERE object = ? AND scheme = 'app'").get(e.id))?.external_id;if(r===void 0)return null;const t=await o.prepare("SELECT resolved_scopes_json FROM grants WHERE grantee = ? AND status = 'active'").all(r);return t.length===0?n.pluginScopes(r):t.length>1?null:JSON.parse(t[0].resolved_scopes_json).filter(a=>a.mode==="always").map(a=>R(a.scope))}p(se,"appScopesNow");async function k(o,e,n,r){const t=await j(o);if(n!==null&&n.app===null)return{class:"object",object:e,node:e,owner:t,app:t,scopes:null,repos:[]};const a=n?.app==null?null:await E(o,n.app),l=n?.app==null?null:await r.grantFor(n.app,n.sub),c=n===null||l===null?null:n.node_scopes===null?l.resolved:n.node_scopes.filter(i=>d(l.resolved,i)),s=a??(e.owner===null?t:await r.object(e.owner)??t);return{class:"object",object:e,node:e,owner:s,app:s,scopes:c,launched:n?.node_scopes??null,repos:[],...n?.place==null?{}:{place:n.place}}}p(k,"readerForStub");function w(o){return{grantById:O,grantFor:v,pluginScopes:p(e=>A(e.replace(/^app:/,""))?.scopes??null,"pluginScopes"),object:p(async e=>await T(o,e),"object"),profile:p(async(e,n)=>await G(o,e,n),"profile"),projectPaths:p(e=>C()?[]:M(e)?.manifest.projects.map(n=>n.path)??[],"projectPaths")}}p(w,"liveLookups");function pe(o){const e=w(o),n=new Map,r=p((t,a)=>(n.has(t)||n.set(t,a()),n.get(t)),"memo");return{grantById:p(t=>r(`g\0${t}`,()=>e.grantById(t)),"grantById"),grantFor:p((t,a)=>r(`a\0${t}\0${a}`,()=>e.grantFor(t,a)),"grantFor"),pluginScopes:p(t=>r(`t\0${t}`,()=>e.pluginScopes(t)),"pluginScopes"),object:p(async t=>await r(`o\0${t}`,async()=>await e.object(t)),"object"),profile:p(async(t,a)=>await r(`p\0${t}\0${a.id}`,async()=>await e.profile(t,a)),"profile"),projectPaths:p(t=>r(`j\0${t}`,()=>e.projectPaths(t)),"projectPaths")}}p(pe,"publishLookups");async function x(o,e,n,r,t,a=!1){const l=n?await t.grantById(n):await t.grantFor(o,e),c=l&&l.grantee===o?l.resolved:null;if(c===null){if(a)return null;throw new Error(`the grant of ${o} is not active`)}return r===null?c:r.filter(s=>d(c,s))}p(x,"nodeScopes");async function j(o){return await D(o,B())}p(j,"personObject");async function E(o,e){return await y(o,"app","app",e,e.replace(/^app:/,""))}p(E,"appObject");async function G(o,e,n){return await o.prepare(`SELECT o.* FROM identities i JOIN objects o ON o.id = i.object
3
+ WHERE i.scheme = 'profile' AND i.external_id = ?`).get(e)??await y(o,"profile","profile",e,P(e,H(e).manifest.name),n)}p(G,"profileObject");export{se as appScopesNow,w as liveLookups,G as profileObject,pe as publishLookups,ae as readerFor,J as readerForNode,re as readerForObject,N as viewRepos,ne as warmNodeRepos};
@@ -5,13 +5,11 @@ export interface WriteResult {
5
5
  /** False when `id` already existed: a repeat delivery adds nothing, to the row or the rollup. */
6
6
  written: boolean;
7
7
  id: string;
8
- /** The id of the take row written beside it, when `ledger.take_pct` is above 0. */
8
+ /** The id of the take row written beside it, when `CRTR_LEDGER_TAKE_PCT` is above 0. */
9
9
  takeId: string | null;
10
10
  }
11
- /** The take percentage of `ledger.take_pct`; 0 (the default) writes no take rows. */
12
- export declare function takePct(): number;
13
11
  /** Write one charge row; the `charges` trigger (schema delta 007) adds it to `charge_totals`. A refused row is stored with
14
- * usd 0 and never rolled up; a take row (written only while `ledger.take_pct` > 0) is stored and never rolled up. Inside a
12
+ * usd 0 and never rolled up; a take row (written only while `CRTR_LEDGER_TAKE_PCT` > 0) is stored and never rolled up. Inside a
15
13
  * caller's `withCanvasWrite` it joins that transaction. */
16
14
  export declare function writeCharge(input: ChargeInput): Promise<WriteResult>;
17
15
  /** A stored row as the `Charge` shape (jsonb arrives as text from `charges`, as an object from a moved chunk). */
@@ -1 +1 @@
1
- var I=Object.defineProperty;var n=(e,r)=>I(e,"name",{value:r,configurable:!0});import{randomUUID as b}from"node:crypto";import{withCanvasWrite as k}from"../canvas/db.js";import{readConfig as w}from"../config.js";import{usageMonth as y}from"./months.js";const U=n((e,r)=>`receipt:${e}:${r}`,"receiptChargeId"),c="id, request, at, month, kind, what, units, usd, payer, funding, sub, grantee, node, run, provider, caused_by, vendor_ref, refused, account, billed_usd, usd_rolled_by_trigger",i="?, ?, ?, ?, ?, ?, ?::jsonb, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?::jsonb, ?, ?, true";function S(){const e=w("user").ledger?.take_pct??0;if(typeof e!="number"||!Number.isFinite(e)||e<0||e>100)throw new Error("ledger.take_pct must be a number from 0 to 100");return e}n(S,"takePct");async function D(e){const r=e.at??new Date,t=e.refused??null,o=e.id??b(),f=y(r),s=t?0:e.usd,a=S();return await k(async d=>{const u=n((g,m,N,O,h)=>[g,e.request??null,r.toISOString(),f,m,N,e.units==null?null:JSON.stringify(e.units),O,e.payer,h,e.sub??null,e.grantee,e.node??null,e.run??null,e.provider??null,e.causedBy??null,e.vendorRef??null,t===null?null:JSON.stringify(t),e.account??null,e.billedUsd??null],"row");if((await d.prepare(`INSERT INTO charges (${c}) VALUES (${i}) ON CONFLICT (id) DO NOTHING`).run(...u(o,e.kind,e.what,s,e.funding))).changes===0)return{written:!1,id:o,takeId:null};let l=null;return a>0&&t===null&&(e.kind==="model"||e.kind==="tool")&&s!==null&&s>0&&(l=`take:${o}`,await d.prepare(`INSERT INTO charges (${c}) VALUES (${i}) ON CONFLICT (id) DO NOTHING`).run(...u(l,"take",e.what,s*a/100,"take"))),{written:!0,id:o,takeId:l}})}n(D,"writeCharge");function j(e){const r=n(t=>typeof t=="string"?JSON.parse(t):typeof t=="object"?t:null,"json");return{...e,units:r(e.units),refused:r(e.refused)}}n(j,"chargeOf");export{j as chargeOf,U as receiptChargeId,S as takePct,D as writeCharge};
1
+ var m=Object.defineProperty;var t=(e,r)=>m(e,"name",{value:r,configurable:!0});import{randomUUID as y}from"node:crypto";import{withCanvasWrite as S}from"../canvas/db.js";import{takePercent as k}from"./rates.js";import{usageMonth as w}from"./months.js";const U=t((e,r)=>`receipt:${e}:${r}`,"receiptChargeId"),c="id, request, at, month, kind, what, units, usd, payer, funding, sub, grantee, node, run, provider, caused_by, vendor_ref, refused, account, billed_usd, usd_rolled_by_trigger",i="?, ?, ?, ?, ?, ?, ?::jsonb, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?::jsonb, ?, ?, true";async function _(e){const r=e.at??new Date,n=e.refused??null,s=e.id??y(),f=w(r),o=n?0:e.usd,a=k();return await S(async d=>{const u=t((g,O,N,h,I)=>[g,e.request??null,r.toISOString(),f,O,N,e.units==null?null:JSON.stringify(e.units),h,e.payer,I,e.sub??null,e.grantee,e.node??null,e.run??null,e.provider??null,e.causedBy??null,e.vendorRef??null,n===null?null:JSON.stringify(n),e.account??null,e.billedUsd??null],"row");if((await d.prepare(`INSERT INTO charges (${c}) VALUES (${i}) ON CONFLICT (id) DO NOTHING`).run(...u(s,e.kind,e.what,o,e.funding))).changes===0)return{written:!1,id:s,takeId:null};let l=null;return a>0&&n===null&&(e.kind==="model"||e.kind==="tool")&&o!==null&&o>0&&(l=`take:${s}`,await d.prepare(`INSERT INTO charges (${c}) VALUES (${i}) ON CONFLICT (id) DO NOTHING`).run(...u(l,"take",e.what,o*a/100,"take"))),{written:!0,id:s,takeId:l}})}t(_,"writeCharge");function D(e){const r=t(n=>typeof n=="string"?JSON.parse(n):typeof n=="object"?n:null,"json");return{...e,units:r(e.units),refused:r(e.refused)}}t(D,"chargeOf");export{D as chargeOf,U as receiptChargeId,_ as writeCharge};
@@ -1,7 +1,7 @@
1
1
  export * from './types.js';
2
2
  export * from './months.js';
3
3
  export { payerOf, type Payer, type PayerCall } from './payer.js';
4
- export { writeCharge, receiptChargeId, takePct, chargeOf, type WriteResult } from './charges.js';
4
+ export { writeCharge, receiptChargeId, chargeOf, type WriteResult } from './charges.js';
5
5
  export { check, findRefusal, refusalError, refusedMark, userActionOf, type Candidate, type CallCandidate, type StartCandidate, type Refusal } from './check.js';
6
6
  export { upsertLimit, deleteLimit, listLimits } from './limits.js';
7
7
  export { usageFor, listCharges, monthTotals, assertReference, type Reader, type ChargePage, type ListFilter, type MonthTotals } from './read.js';
@@ -1 +1 @@
1
- export*from"./types.js";export*from"./months.js";import{payerOf as f}from"./payer.js";import{writeCharge as a,receiptChargeId as p,takePct as i,chargeOf as m}from"./charges.js";import{check as x,findRefusal as c,refusalError as u,refusedMark as L,userActionOf as d}from"./check.js";import{upsertLimit as g,deleteLimit as n,listLimits as O}from"./limits.js";import{usageFor as k,listCharges as A,monthTotals as C,assertReference as R}from"./read.js";import{REBUILD_ALL_TOTALS as B,rebuildTotalsFor as E}from"./totals.js";import{appsHeldBy as I}from"./account.js";export{B as REBUILD_ALL_TOTALS,I as appsHeldBy,R as assertReference,m as chargeOf,x as check,n as deleteLimit,c as findRefusal,A as listCharges,O as listLimits,C as monthTotals,f as payerOf,E as rebuildTotalsFor,p as receiptChargeId,u as refusalError,L as refusedMark,i as takePct,g as upsertLimit,k as usageFor,d as userActionOf,a as writeCharge};
1
+ export*from"./types.js";export*from"./months.js";import{payerOf as f}from"./payer.js";import{writeCharge as p,receiptChargeId as a,chargeOf as i}from"./charges.js";import{check as l,findRefusal as x,refusalError as u,refusedMark as L,userActionOf as c}from"./check.js";import{upsertLimit as h,deleteLimit as g,listLimits as n}from"./limits.js";import{usageFor as T,listCharges as A,monthTotals as C,assertReference as R}from"./read.js";import{REBUILD_ALL_TOTALS as y,rebuildTotalsFor as B}from"./totals.js";import{appsHeldBy as F}from"./account.js";export{y as REBUILD_ALL_TOTALS,F as appsHeldBy,R as assertReference,i as chargeOf,l as check,g as deleteLimit,x as findRefusal,A as listCharges,n as listLimits,C as monthTotals,f as payerOf,B as rebuildTotalsFor,a as receiptChargeId,u as refusalError,L as refusedMark,h as upsertLimit,T as usageFor,c as userActionOf,p as writeCharge};
@@ -1,7 +1,5 @@
1
1
  import { type WriteResult } from './charges.js';
2
2
  import type { ChargeInput } from './types.js';
3
- export declare const DEFAULT_INFRA_FREE_USD = 50;
4
- export declare const infraFreeUsd: () => number;
5
3
  /** Write a `sandbox` or `storage` row whose `billed_usd` is the part of its usd above the account's monthly allowance. A row with no
6
4
  * billable account (house-paid, or no holder) bills 0 and takes no lock. */
7
5
  export declare function writeInfraCharge(input: ChargeInput & {
@@ -1 +1 @@
1
- var h=Object.defineProperty;var t=(r,a)=>h(r,"name",{value:a,configurable:!0});import{withCanvasWrite as l}from"../canvas/db.js";import{readConfig as E}from"../config.js";import{writeCharge as d}from"./charges.js";import{usageMonth as g}from"./months.js";const _=50,i=t(r=>Math.round(r*1e6)/1e6,"round6"),p=t(()=>E("user").ledger?.infra_free_usd_per_month??_,"infraFreeUsd");async function D(r,a){const e=i(r.usd),o=r.account??null;if(!a||o===null)return await d({...r,usd:e,billedUsd:0});const c=p(),n=g(r.at??new Date);return await l(async s=>{await s.prepare("SELECT pg_advisory_xact_lock(hashtextextended(?, 0))").get(`infra:${o}:${n}`);const u=await s.prepare("SELECT COALESCE(sum(usd), 0) AS usd FROM charges WHERE account = ? AND month = ? AND kind IN ('sandbox', 'storage') AND id <> ?").get(o,n,r.id??""),m=Number(u.usd),f=i(Math.min(e,Math.max(0,m+e-c)));return await d({...r,usd:e,billedUsd:f})})}t(D,"writeInfraCharge");export{_ as DEFAULT_INFRA_FREE_USD,p as infraFreeUsd,D as writeInfraCharge};
1
+ var f=Object.defineProperty;var o=(r,a)=>f(r,"name",{value:a,configurable:!0});import{withCanvasWrite as l}from"../canvas/db.js";import{writeCharge as i}from"./charges.js";import{usageMonth as w}from"./months.js";import{infraFreeUsdPerMonth as g}from"./rates.js";const c=o(r=>Math.round(r*1e6)/1e6,"round6");async function x(r,a){const t=c(r.usd),e=r.account??null;if(!a||e===null)return await i({...r,usd:t,billedUsd:0});const d=g(),n=w(r.at??new Date);return await l(async s=>{await s.prepare("SELECT pg_advisory_xact_lock(hashtextextended(?, 0))").get(`infra:${e}:${n}`);const m=await s.prepare("SELECT COALESCE(sum(usd), 0) AS usd FROM charges WHERE account = ? AND month = ? AND kind IN ('sandbox', 'storage') AND id <> ?").get(e,n,r.id??""),u=Number(m.usd),h=c(Math.min(t,Math.max(0,u+t-d)));return await i({...r,usd:t,billedUsd:h})})}o(x,"writeInfraCharge");export{x as writeInfraCharge};
@@ -0,0 +1,24 @@
1
+ export declare const LEDGER_ENV: {
2
+ readonly sandboxUsdPerGbHour: "CRTR_LEDGER_SANDBOX_USD_PER_GB_HOUR";
3
+ readonly sandboxStandbyS: "CRTR_LEDGER_SANDBOX_STANDBY_S";
4
+ readonly storageUsdPerGbMonth: "CRTR_LEDGER_STORAGE_USD_PER_GB_MONTH";
5
+ readonly sandboxDiskUsdPerGbMonth: "CRTR_LEDGER_SANDBOX_DISK_USD_PER_GB_MONTH";
6
+ readonly infraFreeUsdPerMonth: "CRTR_LEDGER_INFRA_FREE_USD_PER_MONTH";
7
+ readonly takePct: "CRTR_LEDGER_TAKE_PCT";
8
+ readonly r2Move: "CRTR_LEDGER_R2_MOVE";
9
+ };
10
+ export declare const DEFAULT_STANDBY_S = 30;
11
+ export declare const DEFAULT_INFRA_FREE_USD = 50;
12
+ export declare const sandboxUsdPerGbHour: () => number | null;
13
+ export declare const sandboxStandbyS: () => number;
14
+ export declare const storageUsdPerGbMonth: () => number | null;
15
+ export declare const sandboxDiskUsdPerGbMonth: () => number | null;
16
+ export declare const infraFreeUsdPerMonth: () => number;
17
+ /** Northlight's take on each priced model or tool charge, in percent; 0 (the default) writes no take rows. */
18
+ export declare const takePercent: () => number;
19
+ /** The `ledger-r2` lane moves closed months into the blob store only while this is true (off by default until 5b is accepted). */
20
+ export declare const r2MoveEnabled: () => boolean;
21
+ /** Throws on the first malformed ledger variable. Called once at daemon boot. */
22
+ export declare function assertLedgerEnv(): void;
23
+ /** Say once per process that a lane writes nothing because its rate is unset. */
24
+ export declare function warnRateUnset(lane: string, ...names: string[]): void;
@@ -0,0 +1 @@
1
+ var E=Object.defineProperty;var t=(r,e)=>E(r,"name",{value:e,configurable:!0});import{emitEvent as a}from"../events/emit.js";const o={sandboxUsdPerGbHour:"CRTR_LEDGER_SANDBOX_USD_PER_GB_HOUR",sandboxStandbyS:"CRTR_LEDGER_SANDBOX_STANDBY_S",storageUsdPerGbMonth:"CRTR_LEDGER_STORAGE_USD_PER_GB_MONTH",sandboxDiskUsdPerGbMonth:"CRTR_LEDGER_SANDBOX_DISK_USD_PER_GB_MONTH",infraFreeUsdPerMonth:"CRTR_LEDGER_INFRA_FREE_USD_PER_MONTH",takePct:"CRTR_LEDGER_TAKE_PCT",r2Move:"CRTR_LEDGER_R2_MOVE"},R=30,i=50;function s(r,e=1/0){const n=process.env[r];if(n===void 0||n==="")return null;const _=/^\d+(\.\d+)?$/.test(n)?Number(n):NaN;if(!Number.isFinite(_)||_>e)throw new Error(`${r} must be a number from 0${e===1/0?" up":` to ${e}`}, got ${JSON.stringify(n)}`);return _}t(s,"number");function u(r){const e=process.env[r];if(e===void 0||e==="")return!1;if(e==="true"||e==="1")return!0;if(e==="false"||e==="0")return!1;throw new Error(`${r} must be true, false, 1 or 0, got ${JSON.stringify(e)}`)}t(u,"flag");const b=t(()=>s(o.sandboxUsdPerGbHour),"sandboxUsdPerGbHour"),f=t(()=>s(o.sandboxStandbyS)??R,"sandboxStandbyS"),c=t(()=>s(o.storageUsdPerGbMonth),"storageUsdPerGbMonth"),D=t(()=>s(o.sandboxDiskUsdPerGbMonth),"sandboxDiskUsdPerGbMonth"),S=t(()=>s(o.infraFreeUsdPerMonth)??i,"infraFreeUsdPerMonth"),G=t(()=>s(o.takePct,100)??0,"takePercent"),U=t(()=>u(o.r2Move),"r2MoveEnabled");function N(){b(),f(),c(),D(),S(),G(),U()}t(N,"assertLedgerEnv");const d=new Set;function T(r,...e){const n=`${r}:${e.join(",")}`;d.has(n)||(d.add(n),a({level:"warn",event:"ledger.rate_unset",fields:{lane:r,unset:e}}))}t(T,"warnRateUnset");export{i as DEFAULT_INFRA_FREE_USD,R as DEFAULT_STANDBY_S,o as LEDGER_ENV,N as assertLedgerEnv,S as infraFreeUsdPerMonth,U as r2MoveEnabled,D as sandboxDiskUsdPerGbMonth,f as sandboxStandbyS,b as sandboxUsdPerGbHour,c as storageUsdPerGbMonth,G as takePercent,T as warnRateUnset};
@@ -3,14 +3,17 @@ import type { Charge, Limit } from './types.js';
3
3
  /** `usage.get` for one grantee and month, computed from `charges`. `by_model` and `app_paid.by_model` group the `model` rows
4
4
  * of a `user:` / `app:` payer by `what`, with token sums from `units` and `usd_equivalent` null when every row's `usd` is null.
5
5
  * `usd_equivalent` is what the grant's limit reads: every `user:` payer charge for this grantee, model and tool, refused and
6
- * take rows excluded. `spend_cap` is the grant limit's `usd_per_month` for this grantee, null when none exists. */
7
- export declare function usageFor(grantee: string, month?: string): Promise<SpendUsageDTO>;
6
+ * take rows excluded. `spend_cap` is the grant limit's `usd_per_month` for this grantee, null when none exists. With `sub` (a cloud
7
+ * role, where one app serves many people) only that person's rows and that person's cap count; without it, every row of the grantee. */
8
+ export declare function usageFor(grantee: string, month?: string, sub?: string): Promise<SpendUsageDTO>;
8
9
  /** Who is reading: a person sees the rows where they are `sub`, `payer` or `account`; an app the rows where it is `grantee`
9
10
  * or `payer`. Absent means the install's person, who sees every row. */
11
+ /** `sub` on an app reader (a cloud role, where one app serves many people) keeps only that person's rows. */
10
12
  export type Reader = {
11
13
  person: string;
12
14
  } | {
13
15
  app: string;
16
+ sub?: string;
14
17
  };
15
18
  export interface ListFilter {
16
19
  /** Only rows of this month. */
@@ -1,2 +1,2 @@
1
- var O=Object.defineProperty;var o=(t,e)=>O(t,"name",{value:e,configurable:!0});import{openDb as m}from"../canvas/db.js";import{subjectSet as S}from"../permissions/evaluator.js";import{RunError as E}from"../runs/operations.js";import{appsHeldBy as A}from"./account.js";import{chargeOf as w}from"./charges.js";import{ChunkRows as R}from"./chunks.js";import{listLimits as b}from"./limits.js";import{resetAt as L,usageMonth as _}from"./months.js";const N="id, request, at, month, kind, what, units, usd, payer, funding, sub, grantee, node, run, provider, caused_by, vendor_ref, refused, account, billed_usd";async function J(t,e=_()){const r=await m(),a=await r.prepare(`SELECT payer, what, kind, units, usd FROM charges
2
- WHERE grantee = ? AND month = ? AND refused IS NULL AND kind <> 'take'`).all(t,e);for(const n of await new R().rows(e))n.grantee===t&&n.refused===null&&n.kind!=="take"&&a.push(n);const i=o(n=>{const u=new Map;for(const s of a){if(s.kind!=="model"||!s.payer.startsWith(n))continue;const f=s.units===null?{}:typeof s.units=="string"?JSON.parse(s.units):s.units,h=u.get(s.what)??{served_model:s.what,input_tokens:0,output_tokens:0,usd_equivalent:null};h.input_tokens+=f.input??0,h.output_tokens+=f.output??0,s.usd!==null&&(h.usd_equivalent=(h.usd_equivalent??0)+Number(s.usd)),u.set(s.what,h)}return[...u.values()].sort((s,f)=>s.served_model<f.served_model?-1:1)},"byModel"),p=o(n=>a.filter(u=>u.payer.startsWith(n)).reduce((u,s)=>u+Number(s.usd??0),0),"sum"),d=await r.prepare("SELECT usd_per_month FROM limits WHERE source = 'grant_sync' AND grantee = ? AND payer LIKE 'user:%' AND funding IS NULL AND provider IS NULL ORDER BY id LIMIT 1").get(t);return{grantee:t,month:e,usd_equivalent:p("user:"),by_model:i("user:"),app_paid:{usd_equivalent:p("app:"),by_model:i("app:")},spend_cap:d===void 0?null:Number(d.usd_per_month),reset_at:L(e)}}o(J,"usageFor");const M=/^(message|request|cron_run|run):(.+)$/;async function v(t,e){const r=M.exec(t);if(r===null)return{sql:"id = ?",params:[t],test:o(n=>n.id===t,"test")};if(r[1]==="message"||r[1]==="cron_run")return{sql:"caused_by = ?",params:[t],test:o(n=>n.caused_by===t,"test")};if(r[1]==="request")return{sql:"(request = ? OR caused_by = ?)",params:[r[2],t],test:o(n=>n.request===r[2]||n.caused_by===t,"test")};const a=r[2],i=await m(),p=new Set((await i.prepare("SELECT DISTINCT request FROM charges WHERE run = ? AND request IS NOT NULL").all(a)).map(n=>n.request));for(const n of await e.rows())n.run===a&&n.request!==null&&p.add(n.request);const d=new Set([a]);if(p.size>0){const n=await i.prepare("SELECT n.run_id FROM nodes n WHERE n.run_id IS NOT NULL AND n.node_id = n.run_id AND n.caused_by = ANY(?::text[])").all([...p].map(u=>`request:${u}`));for(const u of n)d.add(u.run_id)}return{sql:"run = ANY(?::text[])",params:[[...d]],test:o(n=>n.run!==null&&d.has(n.run),"test")}}o(v,"referenceMatch");function D(t){return"person"in t?{sql:"(sub = ? OR payer = ? OR account = ?)",params:[t.person,t.person,t.person]}:{sql:"(grantee = ? OR payer = ?)",params:[t.app,t.app]}}o(D,"readerCondition");const g=o((t,e)=>"person"in e?t.sub===e.person||t.payer===e.person||t.account===e.person:t.grantee===e.app||t.payer===e.app,"mayRead");async function C(t,e,r){const a=await m(),i=new Map,p=o(async d=>{const n=d.caused_by;if(n===null||!n.startsWith("request:"))return;const u=n.slice(8),s=[...(await a.prepare(`SELECT ${N} FROM charges WHERE request = ? AND id <> ?`).all(u,d.id)).map(w),...(await r.rows()).filter(f=>f.request===u&&f.id!==d.id)].sort(q);return s.find(f=>g(f,e))??s[0]},"parentOf");for(const d of t){let n=d;for(let u=0;n!==void 0&&!g(n,e)&&u<64;u+=1)n=await p(n);n!==void 0&&g(n,e)&&i.set(n.id,n)}return[...i.values()]}o(C,"visibleChain");const q=o((t,e)=>t.at<e.at?-1:t.at>e.at?1:t.id<e.id?-1:t.id>e.id?1:0,"byAtId"),T=o(t=>Buffer.from(JSON.stringify([t.at,t.id]),"utf8").toString("base64url"),"encodeCursor");function k(t){try{const e=JSON.parse(Buffer.from(t,"base64url").toString("utf8"));if(Array.isArray(e)&&e.length===2&&typeof e[0]=="string"&&typeof e[1]=="string")return[e[0],e[1]]}catch{}throw new E(400,"invalid_request","cursor must be a `next` value from a previous page","after")}o(k,"decodeCursor");async function P(t={}){const e=Math.min(Math.max(Math.trunc(t.limit??1e3),1),1e4),r=new R,a=[],i=[],p=[];let d;if(t.under!==void 0){const c=await v(t.under,r);a.push(c.sql),i.push(...c.params),p.push(c.test),t.month!==void 0&&(a.push("month = ?"),i.push(t.month),p.push(l=>l.month===t.month),d=t.month)}else{const c=t.month??_();a.push("month = ?"),i.push(c),d=c}if(t.under===void 0&&t.reader!==void 0){const c=D(t.reader);a.push(c.sql),i.push(...c.params);const l=t.reader;p.push(y=>g(y,l))}if(t.after!==void 0){const[c,l]=k(t.after);a.push("(at, id) > (?, ?)"),i.push(c,l),p.push(y=>y.at>c||y.at===c&&y.id>l)}const n=(await(await m()).prepare(`SELECT ${N} FROM charges WHERE ${a.join(" AND ")} ORDER BY at, id LIMIT ${e+1}`).all(...i)).map(w),u=(await r.rows(d)).filter(c=>p.every(l=>l(c))),s=[...n,...u].sort(q).slice(0,e+1),f=s.slice(0,e);return{charges:t.under!==void 0&&t.reader!==void 0?await C(f,t.reader,r):f,next:s.length>e?T(f[e-1]):null}}o(P,"listCharges");async function I(t){const e=await m(),{groupIds:r}=await S(e,t.slice(5));return[t,...await A(e,[t,...r])]}o(I,"payersOf");async function j(t=_(),e){const r=await m(),a=e===void 0?void 0:await I(e),i=a===void 0?"":" AND payer = ANY(?::text[])",p=a===void 0?[t]:[t,a],d=await r.prepare(`SELECT payer, SUM(usd) AS usd FROM charge_totals WHERE month = ?${i} GROUP BY payer ORDER BY payer`).all(...p),n=await r.prepare(`SELECT payer, funding, SUM(usd) AS usd FROM charge_totals WHERE month = ?${i} GROUP BY payer, funding ORDER BY payer, funding`).all(...p),u=a===void 0?await b():(await b()).filter(s=>s.payer!==null&&a.includes(s.payer)||s.account===e);return{month:t,by_payer:d.map(s=>({payer:s.payer,usd:Number(s.usd)})),by_funding:n.map(s=>({payer:s.payer,funding:s.funding,usd:Number(s.usd)})),limits:u}}o(j,"monthTotals");function z(t){if(t.length===0||t.length>512)throw new E(400,"invalid_request","under must be a reference or a charge id","under");return t}o(z,"assertReference");export{z as assertReference,P as listCharges,j as monthTotals,I as payersOf,J as usageFor};
1
+ var O=Object.defineProperty;var d=(n,e)=>O(n,"name",{value:e,configurable:!0});import{openDb as m}from"../canvas/db.js";import{subjectSet as S}from"../permissions/evaluator.js";import{RunError as E}from"../runs/operations.js";import{appsHeldBy as A}from"./account.js";import{chargeOf as R}from"./charges.js";import{ChunkRows as w}from"./chunks.js";import{listLimits as N}from"./limits.js";import{resetAt as L,usageMonth as g}from"./months.js";const b="id, request, at, month, kind, what, units, usd, payer, funding, sub, grantee, node, run, provider, caused_by, vendor_ref, refused, account, billed_usd";async function J(n,e=g(),s){const o=await m(),p=await o.prepare(`SELECT payer, what, kind, units, usd FROM charges
2
+ WHERE grantee = ? AND month = ? AND refused IS NULL AND kind <> 'take'${s===void 0?"":" AND sub = ?"}`).all(...s===void 0?[n,e]:[n,e,s]);for(const r of await new w().rows(e))r.grantee===n&&(s===void 0||r.sub===s)&&r.refused===null&&r.kind!=="take"&&p.push(r);const f=d(r=>{const a=new Map;for(const u of p){if(u.kind!=="model"||!u.payer.startsWith(r))continue;const h=u.units===null?{}:typeof u.units=="string"?JSON.parse(u.units):u.units,i=a.get(u.what)??{served_model:u.what,input_tokens:0,output_tokens:0,usd_equivalent:null};i.input_tokens+=h.input??0,i.output_tokens+=h.output??0,u.usd!==null&&(i.usd_equivalent=(i.usd_equivalent??0)+Number(u.usd)),a.set(u.what,i)}return[...a.values()].sort((u,h)=>u.served_model<h.served_model?-1:1)},"byModel"),c=d(r=>p.filter(a=>a.payer.startsWith(r)).reduce((a,u)=>a+Number(u.usd??0),0),"sum"),t=await o.prepare(`SELECT usd_per_month FROM limits WHERE source = 'grant_sync' AND grantee = ? AND payer LIKE 'user:%' AND funding IS NULL AND provider IS NULL${s===void 0?"":" AND payer = ?"} ORDER BY id LIMIT 1`).get(...s===void 0?[n]:[n,s]);return{grantee:n,month:e,usd_equivalent:c("user:"),by_model:f("user:"),app_paid:{usd_equivalent:c("app:"),by_model:f("app:")},spend_cap:t===void 0?null:Number(t.usd_per_month),reset_at:L(e)}}d(J,"usageFor");const D=/^(message|request|cron_run|run):(.+)$/;async function M(n,e){const s=D.exec(n);if(s===null)return{sql:"id = ?",params:[n],test:d(t=>t.id===n,"test")};if(s[1]==="message"||s[1]==="cron_run")return{sql:"caused_by = ?",params:[n],test:d(t=>t.caused_by===n,"test")};if(s[1]==="request")return{sql:"(request = ? OR caused_by = ?)",params:[s[2],n],test:d(t=>t.request===s[2]||t.caused_by===n,"test")};const o=s[2],p=await m(),f=new Set((await p.prepare("SELECT DISTINCT request FROM charges WHERE run = ? AND request IS NOT NULL").all(o)).map(t=>t.request));for(const t of await e.rows())t.run===o&&t.request!==null&&f.add(t.request);const c=new Set([o]);if(f.size>0){const t=await p.prepare("SELECT n.run_id FROM nodes n WHERE n.run_id IS NOT NULL AND n.node_id = n.run_id AND n.caused_by = ANY(?::text[])").all([...f].map(r=>`request:${r}`));for(const r of t)c.add(r.run_id)}return{sql:"run = ANY(?::text[])",params:[[...c]],test:d(t=>t.run!==null&&c.has(t.run),"test")}}d(M,"referenceMatch");function v(n){return"person"in n?{sql:"(sub = ? OR payer = ? OR account = ?)",params:[n.person,n.person,n.person]}:n.sub===void 0?{sql:"(grantee = ? OR payer = ?)",params:[n.app,n.app]}:{sql:"((grantee = ? OR payer = ?) AND sub = ?)",params:[n.app,n.app,n.sub]}}d(v,"readerCondition");const _=d((n,e)=>"person"in e?n.sub===e.person||n.payer===e.person||n.account===e.person:(n.grantee===e.app||n.payer===e.app)&&(e.sub===void 0||n.sub===e.sub),"mayRead");async function C(n,e,s){const o=await m(),p=new Map,f=d(async c=>{const t=c.caused_by;if(t===null||!t.startsWith("request:"))return;const r=t.slice(8),a=[...(await o.prepare(`SELECT ${b} FROM charges WHERE request = ? AND id <> ?`).all(r,c.id)).map(R),...(await s.rows()).filter(u=>u.request===r&&u.id!==c.id)].sort(q);return a.find(u=>_(u,e))??a[0]},"parentOf");for(const c of n){let t=c;for(let r=0;t!==void 0&&!_(t,e)&&r<64;r+=1)t=await f(t);t!==void 0&&_(t,e)&&p.set(t.id,t)}return[...p.values()]}d(C,"visibleChain");const q=d((n,e)=>n.at<e.at?-1:n.at>e.at?1:n.id<e.id?-1:n.id>e.id?1:0,"byAtId"),T=d(n=>Buffer.from(JSON.stringify([n.at,n.id]),"utf8").toString("base64url"),"encodeCursor");function k(n){try{const e=JSON.parse(Buffer.from(n,"base64url").toString("utf8"));if(Array.isArray(e)&&e.length===2&&typeof e[0]=="string"&&typeof e[1]=="string")return[e[0],e[1]]}catch{}throw new E(400,"invalid_request","cursor must be a `next` value from a previous page","after")}d(k,"decodeCursor");async function P(n={}){const e=Math.min(Math.max(Math.trunc(n.limit??1e3),1),1e4),s=new w,o=[],p=[],f=[];let c;if(n.under!==void 0){const i=await M(n.under,s);o.push(i.sql),p.push(...i.params),f.push(i.test),n.month!==void 0&&(o.push("month = ?"),p.push(n.month),f.push(l=>l.month===n.month),c=n.month)}else{const i=n.month??g();o.push("month = ?"),p.push(i),c=i}if(n.under===void 0&&n.reader!==void 0){const i=v(n.reader);o.push(i.sql),p.push(...i.params);const l=n.reader;f.push(y=>_(y,l))}if(n.after!==void 0){const[i,l]=k(n.after);o.push("(at, id) > (?, ?)"),p.push(i,l),f.push(y=>y.at>i||y.at===i&&y.id>l)}const t=(await(await m()).prepare(`SELECT ${b} FROM charges WHERE ${o.join(" AND ")} ORDER BY at, id LIMIT ${e+1}`).all(...p)).map(R),r=(await s.rows(c)).filter(i=>f.every(l=>l(i))),a=[...t,...r].sort(q).slice(0,e+1),u=a.slice(0,e);return{charges:n.under!==void 0&&n.reader!==void 0?await C(u,n.reader,s):u,next:a.length>e?T(u[e-1]):null}}d(P,"listCharges");async function I(n){const e=await m(),{groupIds:s}=await S(e,n.slice(5));return[n,...await A(e,[n,...s])]}d(I,"payersOf");async function j(n=g(),e){const s=await m(),o=e===void 0?void 0:await I(e),p=o===void 0?"":" AND payer = ANY(?::text[])",f=o===void 0?[n]:[n,o],c=await s.prepare(`SELECT payer, SUM(usd) AS usd FROM charge_totals WHERE month = ?${p} GROUP BY payer ORDER BY payer`).all(...f),t=await s.prepare(`SELECT payer, funding, SUM(usd) AS usd FROM charge_totals WHERE month = ?${p} GROUP BY payer, funding ORDER BY payer, funding`).all(...f),r=o===void 0?await N():(await N()).filter(a=>a.payer!==null&&o.includes(a.payer)||a.account===e);return{month:n,by_payer:c.map(a=>({payer:a.payer,usd:Number(a.usd)})),by_funding:t.map(a=>({payer:a.payer,funding:a.funding,usd:Number(a.usd)})),limits:r}}d(j,"monthTotals");function z(n){if(n.length===0||n.length>512)throw new E(400,"invalid_request","under must be a reference or a charge id","under");return n}d(z,"assertReference");export{z as assertReference,P as listCharges,j as monthTotals,I as payersOf,J as usageFor};
@@ -1 +1 @@
1
- var T=Object.defineProperty;var c=(e,r)=>T(e,"name",{value:r,configurable:!0});import{crtrVersion as $}from"../../shared/crtr-version.js";import{directory as p,directoryIssuer as v,normalizeMachineId as A}from"./directory.js";import{ENVELOPE_HEADER as N,encodeEnvelope as O}from"./envelope.js";import{peerTokens as P}from"./token.js";import{PEER_VERSION_FLOOR as y,PEER_VERSION_HEADER as x,peerVersionRefusal as U}from"./version.js";const k=12e4,B=6e4,S={first:1e3,max:8e3},M=16*1024*1024,L=new Set(["GET","PUT","DELETE"]),Y=c(e=>"unreachable"in e,"isUnreachable"),i=c((e,r)=>({unreachable:{reason:e,message:r}}),"unreachable");function I(e){const r=e?.error;if(typeof r=="string")return r;const s=r?.code;return typeof s=="string"?s:null}c(I,"errorCode");async function D(e){if(Number(e.headers.get("content-length")??"0")>M)throw new RangeError("the peer answer is too large");const s=new Uint8Array(await e.arrayBuffer());if(s.byteLength>M)throw new RangeError("the peer answer is too large");const o=new TextDecoder().decode(s);if(o==="")return null;try{return JSON.parse(o)}catch{return o}}c(D,"readBody");function C(e,r){const s=new URL(`${e.url}${r.path.startsWith("/")?"":"/"}${r.path}`),o=r.query instanceof URLSearchParams?r.query:Object.entries(r.query??{});for(const[u,l]of o instanceof URLSearchParams?o.entries():o)l!==void 0&&s.searchParams.append(u,String(l));return s.toString()}c(C,"targetUrl");function V(e){return c(async function(s,o,u={}){const l=e.issuer();if(l===null)return i("no_directory","this machine is not joined to a Northlight account, so it has no peers");let t;try{t=await e.directory.reach(s)}catch(a){return i("directory_unreachable",a instanceof Error?a.message:String(a))}if(t===null)return i("unknown_machine",`No machine ${A(s)} on your account.`);if(t.kind==="device"&&u.device!==!0)return i("not_a_crtr_peer",`${t.name} is a device, not a crtr peer.`);const w=t.crtr_version===null?null:U(t.crtr_version,y,e.version());if(w!==null)return{status:426,headers:{},body:{error:{code:w.code,message:`${t.name} runs crtr ${t.crtr_version}; peers below ${y} cannot take part. Update it with \`crtr sys update\`.`,details:{peer_version:t.crtr_version,floor:y,receiver_version:e.version()}}}};const R=C(t,o),h=e.now();let f=S.first,b=!1,d=!1;for(;;){let a;try{const n=await e.tokens.get(l,t.url);a=await e.fetch(R,{method:o.method,redirect:"manual",headers:{authorization:`Bearer ${n}`,[x]:e.version(),...u.envelope?{[N]:O(u.envelope)}:{},...o.body===void 0?{}:{"content-type":"application/json"}},...o.body===void 0?{}:{body:JSON.stringify(o.body)},signal:AbortSignal.any([AbortSignal.timeout(d?Math.max(1e3,Math.min(e.timeoutMs,e.startingBudgetMs-(e.now()-h))):e.timeoutMs),...u.signal?[u.signal]:[]])})}catch(n){return n instanceof Error&&(n.name==="TimeoutError"||n.name==="AbortError")?i("timeout",`${t.name} did not answer within ${Math.round(e.timeoutMs/1e3)} s.`):n instanceof Error&&/directory/.test(n.message)?i("directory_unreachable",n.message):i("network",`${t.name} could not be reached: ${n instanceof Error?n.message:String(n)}`)}let E;try{E=await D(a)}catch(n){return n instanceof Error&&(n.name==="TimeoutError"||n.name==="AbortError")?i("timeout",`${t.name} did not finish its answer within ${Math.round(e.timeoutMs/1e3)} s.`):i("network",`${t.name}'s answer could not be read: ${n instanceof Error?n.message:String(n)}`)}const _=I(E),g=Object.fromEntries(a.headers.entries());if(a.status===401&&!b){b=!0,e.tokens.drop(l,t.url);continue}if(a.status===503&&_==="machine_offline")return i("machine_offline",`${t.name} is offline.`);if(a.status===504)return i("timeout",`${t.name} did not answer in time.`);if(a.status===503&&_==="runtime_starting"){if(e.now()-h+f>e.startingBudgetMs)return i("runtime_starting",`${t.name} is still starting.`);await e.sleep(f),f=Math.min(f*2,S.max),d=!0;continue}const m=Number(g["retry-after"]);if(a.status===503&&_==="runtime_unreachable"&&L.has(o.method)&&g["retry-after"]!==void 0&&Number.isFinite(m)&&m>=0&&e.now()-h+m*1e3<=e.startingBudgetMs){await e.sleep(m*1e3),d=!0;continue}return{status:a.status,headers:g,body:E}}},"peerCall")}c(V,"createPeerCall");const K=V({issuer:v,directory:p,tokens:P,fetch:c((e,r)=>fetch(e,r),"fetch"),sleep:c(e=>new Promise(r=>setTimeout(r,e)),"sleep"),now:Date.now,version:$,timeoutMs:k,startingBudgetMs:B});export{M as MAX_PEER_RESPONSE_BYTES,k as PEER_CALL_TIMEOUT_MS,S as RUNTIME_STARTING_BACKOFF_MS,B as RUNTIME_STARTING_RETRY_BUDGET_MS,V as createPeerCall,Y as isUnreachable,K as peerCall};
1
+ var T=Object.defineProperty;var c=(e,r)=>T(e,"name",{value:r,configurable:!0});import{crtrVersion as p}from"../../shared/crtr-version.js";import{directory as $,normalizeMachineId as v}from"./directory.js";import{directoryIssuer as A}from"../directory/issuer.js";import{ENVELOPE_HEADER as N,encodeEnvelope as O}from"./envelope.js";import{peerTokens as P}from"./token.js";import{PEER_VERSION_FLOOR as y,PEER_VERSION_HEADER as x,peerVersionRefusal as U}from"./version.js";const k=12e4,B=6e4,S={first:1e3,max:8e3},M=16*1024*1024,L=new Set(["GET","PUT","DELETE"]),K=c(e=>"unreachable"in e,"isUnreachable"),i=c((e,r)=>({unreachable:{reason:e,message:r}}),"unreachable");function I(e){const r=e?.error;if(typeof r=="string")return r;const s=r?.code;return typeof s=="string"?s:null}c(I,"errorCode");async function D(e){if(Number(e.headers.get("content-length")??"0")>M)throw new RangeError("the peer answer is too large");const s=new Uint8Array(await e.arrayBuffer());if(s.byteLength>M)throw new RangeError("the peer answer is too large");const o=new TextDecoder().decode(s);if(o==="")return null;try{return JSON.parse(o)}catch{return o}}c(D,"readBody");function C(e,r){const s=new URL(`${e.url}${r.path.startsWith("/")?"":"/"}${r.path}`),o=r.query instanceof URLSearchParams?r.query:Object.entries(r.query??{});for(const[u,l]of o instanceof URLSearchParams?o.entries():o)l!==void 0&&s.searchParams.append(u,String(l));return s.toString()}c(C,"targetUrl");function V(e){return c(async function(s,o,u={}){const l=e.issuer();if(l===null)return i("no_directory","this machine is not joined to a Northlight account, so it has no peers");let t;try{t=await e.directory.reach(s)}catch(a){return i("directory_unreachable",a instanceof Error?a.message:String(a))}if(t===null)return i("unknown_machine",`No machine ${v(s)} on your account.`);if(t.kind==="device"&&u.device!==!0)return i("not_a_crtr_peer",`${t.name} is a device, not a crtr peer.`);const w=t.crtr_version===null?null:U(t.crtr_version,y,e.version());if(w!==null)return{status:426,headers:{},body:{error:{code:w.code,message:`${t.name} runs crtr ${t.crtr_version}; peers below ${y} cannot take part. Update it with \`crtr sys update\`.`,details:{peer_version:t.crtr_version,floor:y,receiver_version:e.version()}}}};const R=C(t,o),h=e.now();let m=S.first,b=!1,d=!1;for(;;){let a;try{const n=await e.tokens.get(l,t.url);a=await e.fetch(R,{method:o.method,redirect:"manual",headers:{authorization:`Bearer ${n}`,[x]:e.version(),...u.envelope?{[N]:O(u.envelope)}:{},...o.body===void 0?{}:{"content-type":"application/json"}},...o.body===void 0?{}:{body:JSON.stringify(o.body)},signal:AbortSignal.any([AbortSignal.timeout(d?Math.max(1e3,Math.min(e.timeoutMs,e.startingBudgetMs-(e.now()-h))):e.timeoutMs),...u.signal?[u.signal]:[]])})}catch(n){return n instanceof Error&&(n.name==="TimeoutError"||n.name==="AbortError")?i("timeout",`${t.name} did not answer within ${Math.round(e.timeoutMs/1e3)} s.`):n instanceof Error&&/directory/.test(n.message)?i("directory_unreachable",n.message):i("network",`${t.name} could not be reached: ${n instanceof Error?n.message:String(n)}`)}let E;try{E=await D(a)}catch(n){return n instanceof Error&&(n.name==="TimeoutError"||n.name==="AbortError")?i("timeout",`${t.name} did not finish its answer within ${Math.round(e.timeoutMs/1e3)} s.`):i("network",`${t.name}'s answer could not be read: ${n instanceof Error?n.message:String(n)}`)}const _=I(E),g=Object.fromEntries(a.headers.entries());if(a.status===401&&!b){b=!0,e.tokens.drop(l,t.url);continue}if(a.status===503&&_==="machine_offline")return i("machine_offline",`${t.name} is offline.`);if(a.status===504)return i("timeout",`${t.name} did not answer in time.`);if(a.status===503&&_==="runtime_starting"){if(e.now()-h+m>e.startingBudgetMs)return i("runtime_starting",`${t.name} is still starting.`);await e.sleep(m),m=Math.min(m*2,S.max),d=!0;continue}const f=Number(g["retry-after"]);if(a.status===503&&_==="runtime_unreachable"&&L.has(o.method)&&g["retry-after"]!==void 0&&Number.isFinite(f)&&f>=0&&e.now()-h+f*1e3<=e.startingBudgetMs){await e.sleep(f*1e3),d=!0;continue}return{status:a.status,headers:g,body:E}}},"peerCall")}c(V,"createPeerCall");const W=V({issuer:A,directory:$,tokens:P,fetch:c((e,r)=>fetch(e,r),"fetch"),sleep:c(e=>new Promise(r=>setTimeout(r,e)),"sleep"),now:Date.now,version:p,timeoutMs:k,startingBudgetMs:B});export{M as MAX_PEER_RESPONSE_BYTES,k as PEER_CALL_TIMEOUT_MS,S as RUNTIME_STARTING_BACKOFF_MS,B as RUNTIME_STARTING_RETRY_BUDGET_MS,V as createPeerCall,K as isUnreachable,W as peerCall};
@@ -61,8 +61,6 @@ export { normalizeMachineId };
61
61
  export declare function createDirectoryClient(deps: DirectoryDeps, ownId: () => string): DirectoryClient;
62
62
  /** This machine's id as the directory names it (`runtime:<id>`). */
63
63
  export declare function ownMachineId(): string;
64
- /** The directory this daemon belongs to, or null when it has none (an unjoined runtime). */
65
- export declare function directoryIssuer(): string | null;
66
64
  export declare const directory: DirectoryClient;
67
65
  export declare const listMachines: () => Promise<Machine[]>;
68
66
  export declare const machineReach: (machineId: string) => Promise<Machine | null>;
@@ -1 +1 @@
1
- var v=Object.defineProperty;var o=(t,e)=>v(t,"name",{value:e,configurable:!0});import{normalizeMachineId as m,rememberMachineName as p}from"./names.js";import{openDb as _}from"../canvas/db.js";import{readConfig as M}from"../config.js";import{runtimeClient as g}from"../grants/sync.js";import{crtrVersion as C}from"../../shared/crtr-version.js";import{installedPluginIds as I}from"../directory/join.js";import{peerTokens as R}from"./token.js";const S=5*6e4,A=1e4;class c extends Error{static{o(this,"DirectoryError")}status;constructor(e,i){super(i),this.status=e,this.name="DirectoryError"}}function E(t){if(!t||typeof t!="object")return null;const{kind:e,relay:i}=t;return e==="stable"?{kind:"stable"}:e==="relay"&&typeof i=="string"?{kind:"relay",relay:i}:null}o(E,"asReach");function T(t){const e=t,i=E(e?.reach);if(!e||typeof e.machine_id!="string"||typeof e.url!="string"||i===null)throw new c(502,"the directory answered a machine record with a missing machine_id, url or reach");return{machine_id:e.machine_id,name:typeof e.name=="string"?e.name:e.machine_id,url:e.url.replace(/\/$/,""),reach:i,crtr_version:typeof e.crtr_version=="string"?e.crtr_version:null,last_seen:typeof e.last_seen=="string"?e.last_seen:null,kind:e.kind==="device"?"device":"runtime",plugins:Array.isArray(e.plugins)?e.plugins.filter(u=>typeof u=="string"):[],...typeof e.is_default=="boolean"?{is_default:e.is_default}:{}}}o(T,"asMachine");function b(t,e){const i=new Map;let u=null;async function f(s,a,r){const n=t.issuer();if(n===null)throw new c(0,"this daemon has no directory");const d=n.replace(/\/$/,"");for(let y=0;;y++){let l;try{const h=await t.tokens.get(d);l=await t.fetch(`${d}${a}`,{method:s,headers:{authorization:`Bearer ${h}`,...r===void 0?{}:{"content-type":"application/json"}},...r===void 0?{}:{body:JSON.stringify(r)},signal:AbortSignal.timeout(A)})}catch(h){throw new c(0,`the directory could not be reached: ${h instanceof Error?h.message:String(h)}`)}if(l.status===401&&y===0){t.tokens.drop(d);continue}if(!l.ok)throw new c(l.status,`directory ${s} ${a} answered ${l.status}`);try{return await l.json()}catch{throw new c(502,`directory ${s} ${a} answered a body that is not JSON`)}}}return o(f,"call"),{async list(){const s=await f("GET","/v1/machines");if(!Array.isArray(s))throw new c(502,"the directory answered a machine list that is not an array");const a=s.map(T),r=t.now();for(const n of a)i.set(n.machine_id,{machine:n,at:r}),p(n.machine_id,n.name);return u={machines:a,at:r},a},lastList(){return u===null?null:u.machines},async reach(s){const a=m(s),r=i.get(a);if(r&&t.now()-r.at<S)return r.machine;try{const n=T(await f("GET",`/v1/machines/${encodeURIComponent(a)}/reach`));return i.set(a,{machine:n,at:t.now()}),p(n.machine_id,n.name),n}catch(n){if(n instanceof c&&n.status===404)return i.delete(a),null;if(r&&n instanceof c&&n.status===0)return r.machine;throw n}},async status(s){const a=e(),r=await f("PUT",`/v1/machines/${encodeURIComponent(a)}/status`,s);if(!r||typeof r.machine_id!="string"||typeof r.url!="string")throw new c(502,"the directory answered a status record with a missing machine_id or url");return{machine_id:r.machine_id,url:r.url.replace(/\/$/,""),reach:E(r.reach)}},invalidate(s){s===void 0?(i.clear(),u=null):i.delete(m(s))}}}o(b,"createDirectoryClient");function k(){return m(g().runtime)}o(k,"ownMachineId");function x(){return M("user").runtime?.issuer??null}o(x,"directoryIssuer");const w=b({issuer:x,tokens:R,fetch:o((t,e)=>fetch(t,e),"fetch"),now:Date.now},k),F=o(()=>w.list(),"listMachines"),G=o(t=>w.reach(t),"machineReach"),$="machine_url";async function J(){return(await(await _()).prepare("SELECT value FROM runtime_state WHERE key = ?").get($))?.value??null}o(J,"machineUrl");async function O(t){await(await _()).prepare("INSERT INTO runtime_state (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run($,t)}o(O,"storeMachineUrl");async function V(t=w){if(x()===null)return null;try{g()}catch{return null}const e=await t.status({plugins:I(),crtr_version:C()});if(e.machine_id!==k())throw new c(502,"the directory answered a status for another machine");return await O(e.url),e}o(V,"reportMachineStatus");export{A as DIRECTORY_CALL_TIMEOUT_MS,c as DirectoryError,S as REACH_TTL_MS,b as createDirectoryClient,w as directory,x as directoryIssuer,F as listMachines,G as machineReach,J as machineUrl,m as normalizeMachineId,k as ownMachineId,V as reportMachineStatus};
1
+ var x=Object.defineProperty;var s=(t,e)=>x(t,"name",{value:e,configurable:!0});import{normalizeMachineId as m,rememberMachineName as p}from"./names.js";import{openDb as _}from"../canvas/db.js";import{directoryIssuer as g}from"../directory/issuer.js";import{runtimeClient as E}from"../grants/sync.js";import{crtrVersion as M}from"../../shared/crtr-version.js";import{installedPluginIds as C}from"../directory/join.js";import{peerTokens as I}from"./token.js";const R=5*6e4,S=1e4;class c extends Error{static{s(this,"DirectoryError")}status;constructor(e,i){super(i),this.status=e,this.name="DirectoryError"}}function T(t){if(!t||typeof t!="object")return null;const{kind:e,relay:i}=t;return e==="stable"?{kind:"stable"}:e==="relay"&&typeof i=="string"?{kind:"relay",relay:i}:null}s(T,"asReach");function k(t){const e=t,i=T(e?.reach);if(!e||typeof e.machine_id!="string"||typeof e.url!="string"||i===null)throw new c(502,"the directory answered a machine record with a missing machine_id, url or reach");return{machine_id:e.machine_id,name:typeof e.name=="string"?e.name:e.machine_id,url:e.url.replace(/\/$/,""),reach:i,crtr_version:typeof e.crtr_version=="string"?e.crtr_version:null,last_seen:typeof e.last_seen=="string"?e.last_seen:null,kind:e.kind==="device"?"device":"runtime",plugins:Array.isArray(e.plugins)?e.plugins.filter(u=>typeof u=="string"):[],...typeof e.is_default=="boolean"?{is_default:e.is_default}:{}}}s(k,"asMachine");function A(t,e){const i=new Map;let u=null;async function d(o,a,r){const n=t.issuer();if(n===null)throw new c(0,"this daemon has no directory");const f=n.replace(/\/$/,"");for(let y=0;;y++){let l;try{const h=await t.tokens.get(f);l=await t.fetch(`${f}${a}`,{method:o,headers:{authorization:`Bearer ${h}`,...r===void 0?{}:{"content-type":"application/json"}},...r===void 0?{}:{body:JSON.stringify(r)},signal:AbortSignal.timeout(S)})}catch(h){throw new c(0,`the directory could not be reached: ${h instanceof Error?h.message:String(h)}`)}if(l.status===401&&y===0){t.tokens.drop(f);continue}if(!l.ok)throw new c(l.status,`directory ${o} ${a} answered ${l.status}`);try{return await l.json()}catch{throw new c(502,`directory ${o} ${a} answered a body that is not JSON`)}}}return s(d,"call"),{async list(){const o=await d("GET","/v1/machines");if(!Array.isArray(o))throw new c(502,"the directory answered a machine list that is not an array");const a=o.map(k),r=t.now();for(const n of a)i.set(n.machine_id,{machine:n,at:r}),p(n.machine_id,n.name);return u={machines:a,at:r},a},lastList(){return u===null?null:u.machines},async reach(o){const a=m(o),r=i.get(a);if(r&&t.now()-r.at<R)return r.machine;try{const n=k(await d("GET",`/v1/machines/${encodeURIComponent(a)}/reach`));return i.set(a,{machine:n,at:t.now()}),p(n.machine_id,n.name),n}catch(n){if(n instanceof c&&n.status===404)return i.delete(a),null;if(r&&n instanceof c&&n.status===0)return r.machine;throw n}},async status(o){const a=e(),r=await d("PUT",`/v1/machines/${encodeURIComponent(a)}/status`,o);if(!r||typeof r.machine_id!="string"||typeof r.url!="string")throw new c(502,"the directory answered a status record with a missing machine_id or url");return{machine_id:r.machine_id,url:r.url.replace(/\/$/,""),reach:T(r.reach)}},invalidate(o){o===void 0?(i.clear(),u=null):i.delete(m(o))}}}s(A,"createDirectoryClient");function $(){return m(E().runtime)}s($,"ownMachineId");const w=A({issuer:g,tokens:I,fetch:s((t,e)=>fetch(t,e),"fetch"),now:Date.now},$),z=s(()=>w.list(),"listMachines"),F=s(t=>w.reach(t),"machineReach"),v="machine_url";async function G(){return(await(await _()).prepare("SELECT value FROM runtime_state WHERE key = ?").get(v))?.value??null}s(G,"machineUrl");async function b(t){await(await _()).prepare("INSERT INTO runtime_state (key, value) VALUES (?, ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(v,t)}s(b,"storeMachineUrl");async function J(t=w){if(g()===null)return null;try{E()}catch{return null}const e=await t.status({plugins:C(),crtr_version:M()});if(e.machine_id!==$())throw new c(502,"the directory answered a status for another machine");return await b(e.url),e}s(J,"reportMachineStatus");export{S as DIRECTORY_CALL_TIMEOUT_MS,c as DirectoryError,R as REACH_TTL_MS,A as createDirectoryClient,w as directory,z as listMachines,F as machineReach,G as machineUrl,m as normalizeMachineId,$ as ownMachineId,J as reportMachineStatus};
@@ -1,2 +1,2 @@
1
- var d=Object.defineProperty;var c=(e,n)=>d(e,"name",{value:n,configurable:!0});import{getById as p}from"../graph/objects.js";import{isUnreachable as _,peerCall as y}from"./client.js";import{directoryIssuer as E,listMachines as x,normalizeMachineId as w,ownMachineId as b}from"./directory.js";import{stubOf as O}from"./stubs.js";const l={call:c((e,n)=>y(e,n),"call"),machines:x,self:b,now:Date.now,joined:c(()=>E()!==null,"joined")};async function T(e,n){return(await e.prepare("SELECT machine_id FROM peer_locations WHERE id = ?").get(n))?.machine_id??null}c(T,"cachedLocation");async function I(e,n,a,i=Date.now()){await e.prepare(`INSERT INTO peer_locations (id, machine_id, learned_at) VALUES (?, ?, ?)
2
- ON CONFLICT(id) DO UPDATE SET machine_id = excluded.machine_id, learned_at = excluded.learned_at`).run(n,a,i)}c(I,"rememberLocation");async function L(e,n){await e.prepare("DELETE FROM peer_locations WHERE id = ?").run(n)}c(L,"dropLocation");async function g(e,n){if(!e.joined())return[];let a;try{a=await e.machines()}catch(r){return{where:"unknown",unreachable:[`directory (${r instanceof Error?r.message:String(r)})`]}}const i=e.self();return a.filter(r=>r.machine_id!==i&&r.machine_id!==n&&r.kind!=="device")}c(g,"peersToAsk");async function f(e,n,a=l,i){const r=await g(a,i);if("where"in r)return r;const o=await Promise.all(r.map(async t=>{const h=await a.call(t.machine_id,{method:"POST",path:"/v1/peer/locate",body:{id:n}});if(_(h))return{m:t,state:"unreachable"};const s=h.body;return h.status===200&&s?.answer==="here"?{m:t,state:"here",machine:t.machine_id}:h.status===200&&s?.answer==="moved"&&typeof s.machine=="string"?{m:t,state:"here",machine:w(s.machine)}:h.status===200&&s?.answer==="no"?{m:t,state:"no"}:{m:t,state:"unreachable"}})),u=o.find(t=>t.state==="here");if(u&&"machine"in u)return await I(e,n,u.machine,a.now()),{where:"machine",machine:u.machine,source:"locate"};const m=o.filter(t=>t.state==="unreachable").map(t=>t.m.name||t.m.machine_id);return m.length>0?{where:"unknown",unreachable:m}:{where:"nowhere"}}c(f,"locate");async function C(e,n,a={}){const i=await p(e,n);if(i!==null){const o=await O(e,i);return o===null?{where:"here"}:{where:"machine",machine:o.home_machine,source:"stub"}}if(!(a.deps??l).joined())return{where:"nowhere"};if(a.hint)return{where:"machine",machine:w(a.hint),source:"hint"};const r=await T(e,n);return r!==null?{where:"machine",machine:r,source:"cache"}:f(e,n,a.deps)}c(C,"homeOf");async function N(e,n,a,i=l){return await L(e,n),f(e,n,i,a)}c(N,"relocate");export{T as cachedLocation,l as defaultHomeDeps,L as dropLocation,C as homeOf,f as locate,g as peersToAsk,N as relocate,I as rememberLocation};
1
+ var d=Object.defineProperty;var c=(e,n)=>d(e,"name",{value:n,configurable:!0});import{getById as p}from"../graph/objects.js";import{isUnreachable as _,peerCall as y}from"./client.js";import{listMachines as E,normalizeMachineId as w,ownMachineId as x}from"./directory.js";import{directoryIssuer as b}from"../directory/issuer.js";import{stubOf as O}from"./stubs.js";const l={call:c((e,n)=>y(e,n),"call"),machines:E,self:x,now:Date.now,joined:c(()=>b()!==null,"joined")};async function T(e,n){return(await e.prepare("SELECT machine_id FROM peer_locations WHERE id = ?").get(n))?.machine_id??null}c(T,"cachedLocation");async function I(e,n,r,i=Date.now()){await e.prepare(`INSERT INTO peer_locations (id, machine_id, learned_at) VALUES (?, ?, ?)
2
+ ON CONFLICT(id) DO UPDATE SET machine_id = excluded.machine_id, learned_at = excluded.learned_at`).run(n,r,i)}c(I,"rememberLocation");async function L(e,n){await e.prepare("DELETE FROM peer_locations WHERE id = ?").run(n)}c(L,"dropLocation");async function g(e,n){if(!e.joined())return[];let r;try{r=await e.machines()}catch(a){return{where:"unknown",unreachable:[`directory (${a instanceof Error?a.message:String(a)})`]}}const i=e.self();return r.filter(a=>a.machine_id!==i&&a.machine_id!==n&&a.kind!=="device")}c(g,"peersToAsk");async function f(e,n,r=l,i){const a=await g(r,i);if("where"in a)return a;const o=await Promise.all(a.map(async t=>{const h=await r.call(t.machine_id,{method:"POST",path:"/v1/peer/locate",body:{id:n}});if(_(h))return{m:t,state:"unreachable"};const s=h.body;return h.status===200&&s?.answer==="here"?{m:t,state:"here",machine:t.machine_id}:h.status===200&&s?.answer==="moved"&&typeof s.machine=="string"?{m:t,state:"here",machine:w(s.machine)}:h.status===200&&s?.answer==="no"?{m:t,state:"no"}:{m:t,state:"unreachable"}})),u=o.find(t=>t.state==="here");if(u&&"machine"in u)return await I(e,n,u.machine,r.now()),{where:"machine",machine:u.machine,source:"locate"};const m=o.filter(t=>t.state==="unreachable").map(t=>t.m.name||t.m.machine_id);return m.length>0?{where:"unknown",unreachable:m}:{where:"nowhere"}}c(f,"locate");async function N(e,n,r={}){const i=await p(e,n);if(i!==null){const o=await O(e,i);return o===null?{where:"here"}:{where:"machine",machine:o.home_machine,source:"stub"}}if(!(r.deps??l).joined())return{where:"nowhere"};if(r.hint)return{where:"machine",machine:w(r.hint),source:"hint"};const a=await T(e,n);return a!==null?{where:"machine",machine:a,source:"cache"}:f(e,n,r.deps)}c(N,"homeOf");async function j(e,n,r,i=l){return await L(e,n),f(e,n,i,r)}c(j,"relocate");export{T as cachedLocation,l as defaultHomeDeps,L as dropLocation,N as homeOf,f as locate,g as peersToAsk,j as relocate,I as rememberLocation};
@@ -21,6 +21,8 @@ export interface AuditFilters {
21
21
  run_id?: string;
22
22
  since?: string;
23
23
  until?: string;
24
+ /** Only rows written for this person (the `sub` column). */
25
+ sub?: string;
24
26
  }
25
27
  export declare function listAudit(grantee: string | null, filters: AuditFilters): Promise<{
26
28
  rows: Record<string, unknown>[];