@north-light/cli 0.3.551 → 0.3.553

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 (110) hide show
  1. package/dist/api/client.d.ts +11 -0
  2. package/dist/api/client.js +3 -3
  3. package/dist/api/contract.d.ts +1 -1
  4. package/dist/api/contract.js +1 -1
  5. package/dist/api/dto/charges.d.ts +122 -0
  6. package/dist/api/dto/charges.js +0 -0
  7. package/dist/api/dto/usage.d.ts +3 -3
  8. package/dist/api/index.d.ts +1 -0
  9. package/dist/api/index.js +1 -1
  10. package/dist/api/routes.d.ts +3 -0
  11. package/dist/api/routes.js +1 -1
  12. package/dist/builtin-memory/crouter-sdk/release-notes.md +10 -0
  13. package/dist/clients/attach/viewer.js +50 -50
  14. package/dist/commands/sys/spend.d.ts +1 -0
  15. package/dist/commands/sys/spend.js +3 -0
  16. package/dist/commands/sys.js +1 -1
  17. package/dist/core/canvas/canvas.js +4 -4
  18. package/dist/core/canvas/db.js +4 -4
  19. package/dist/core/canvas/export-sqlite.js +2 -2
  20. package/dist/core/canvas/import-sqlite.d.ts +2 -0
  21. package/dist/core/canvas/import-sqlite.js +4 -4
  22. package/dist/core/canvas/person.js +7 -16
  23. package/dist/core/canvas/schema-chain.d.ts +34 -0
  24. package/dist/core/canvas/schema-chain.js +1 -0
  25. package/dist/core/canvas/schema-pg/.gitkeep +0 -0
  26. package/dist/core/canvas/schema-pg.sql +61 -0
  27. package/dist/core/canvas/schema-version.d.ts +29 -3
  28. package/dist/core/canvas/schema-version.js +1 -1
  29. package/dist/core/canvas/types.d.ts +4 -0
  30. package/dist/core/config.js +1 -1
  31. package/dist/core/grants/mirror.d.ts +2 -1
  32. package/dist/core/grants/mirror.js +2 -2
  33. package/dist/core/ledger/backfill.d.ts +12 -0
  34. package/dist/core/ledger/backfill.js +22 -0
  35. package/dist/core/ledger/charges.d.ts +18 -0
  36. package/dist/core/ledger/charges.js +1 -0
  37. package/dist/core/ledger/check.d.ts +44 -0
  38. package/dist/core/ledger/check.js +7 -0
  39. package/dist/core/ledger/index.d.ts +9 -0
  40. package/dist/core/ledger/index.js +1 -0
  41. package/dist/core/ledger/limits.d.ts +15 -0
  42. package/dist/core/ledger/limits.js +3 -0
  43. package/dist/core/ledger/months.d.ts +6 -0
  44. package/dist/core/ledger/months.js +1 -0
  45. package/dist/core/ledger/payer.d.ts +34 -0
  46. package/dist/core/ledger/payer.js +1 -0
  47. package/dist/core/ledger/read.d.ts +44 -0
  48. package/dist/core/ledger/read.js +3 -0
  49. package/dist/core/ledger/totals.d.ts +19 -0
  50. package/dist/core/ledger/totals.js +4 -0
  51. package/dist/core/ledger/types.d.ts +66 -0
  52. package/dist/core/ledger/types.js +0 -0
  53. package/dist/core/llm/run.d.ts +17 -5
  54. package/dist/core/llm/run.js +2 -2
  55. package/dist/core/providers/call.d.ts +7 -1
  56. package/dist/core/providers/call.js +1 -1
  57. package/dist/core/providers/errors.js +1 -1
  58. package/dist/core/providers/receipts.d.ts +7 -1
  59. package/dist/core/providers/receipts.js +1 -3
  60. package/dist/core/runs/operations.d.ts +1 -0
  61. package/dist/core/runs/operations.js +7 -7
  62. package/dist/core/runtime/broker/cli-relay.js +1 -1
  63. package/dist/core/runtime/broker/model-adapter.js +2 -2
  64. package/dist/core/runtime/broker/turn-cause.d.ts +7 -0
  65. package/dist/core/runtime/broker/turn-cause.js +1 -0
  66. package/dist/core/runtime/broker.js +1 -1
  67. package/dist/core/runtime/nodes.d.ts +2 -0
  68. package/dist/core/runtime/nodes.js +1 -1
  69. package/dist/core/runtime/recycle.js +1 -1
  70. package/dist/core/runtime/reset.js +1 -1
  71. package/dist/core/runtime/revive.js +2 -2
  72. package/dist/core/runtime/spawn.d.ts +2 -0
  73. package/dist/core/runtime/spawn.js +2 -2
  74. package/dist/core/runtime/start-check.d.ts +15 -0
  75. package/dist/core/runtime/start-check.js +1 -0
  76. package/dist/core/runtime/warm-pool.js +1 -1
  77. package/dist/core/user-settings.d.ts +4 -0
  78. package/dist/core/user-settings.js +1 -1
  79. package/dist/daemon/api/app-listener.js +13 -13
  80. package/dist/daemon/api/handlers/bash-jobs.js +1 -1
  81. package/dist/daemon/api/handlers/charges.d.ts +3 -0
  82. package/dist/daemon/api/handlers/charges.js +1 -0
  83. package/dist/daemon/api/handlers/llm.d.ts +9 -5
  84. package/dist/daemon/api/handlers/llm.js +3 -3
  85. package/dist/daemon/api/handlers/messages.js +2 -2
  86. package/dist/daemon/api/handlers/providers.d.ts +2 -0
  87. package/dist/daemon/api/handlers/providers.js +1 -1
  88. package/dist/daemon/api/handlers/run-routes.js +1 -1
  89. package/dist/daemon/api/handlers/runs.d.ts +1 -1
  90. package/dist/daemon/api/handlers/runs.js +1 -1
  91. package/dist/daemon/api/operations.js +1 -1
  92. package/dist/daemon/api/router.d.ts +8 -0
  93. package/dist/daemon/api/router.js +1 -1
  94. package/dist/daemon/api/server.js +1 -1
  95. package/dist/daemon/cron/authority.d.ts +1 -1
  96. package/dist/daemon/cron-run.js +2 -2
  97. package/dist/daemon/crtrd.js +10 -10
  98. package/dist/daemon/fleet.js +4 -4
  99. package/dist/daemon/reconcilers/action-lane.js +1 -1
  100. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.d.ts +2 -0
  101. package/dist/daemon/reconcilers/node-lifecycle/freeze-lane.js +1 -1
  102. package/dist/types.d.ts +4 -0
  103. package/docs/sdk/release-notes.md +10 -0
  104. package/package.json +3 -2
  105. package/packages/crouter-identity/package.json +1 -1
  106. package/runtime.lock.json +13 -13
  107. package/scripts/install-runtime.mjs +6 -20
  108. package/scripts/lib/canvas-schema-gate.mjs +20 -0
  109. package/dist/core/usage/ledger.d.ts +0 -49
  110. package/dist/core/usage/ledger.js +0 -10
@@ -939,6 +939,67 @@ CREATE TABLE peer_ingested_destinations (
939
939
  ingested_at bigint NOT NULL
940
940
  );
941
941
 
942
+ -- ---------------------------------------------------------------- money ledger (cycle 4)
943
+
944
+ -- One `charges` row per charge, each naming its cause (`caused_by`); one `charge_totals` rollup upserted in the same
945
+ -- transaction as each charge; one `limits` row shape. Added after schema version 1 shipped: `ensureSchema` runs this block
946
+ -- alone against an older database (idempotent, additive; the version stays 1). It sits BEFORE the permissions block on
947
+ -- purpose: that block drops and recreates `node_rows` (`SELECT n.*`), which only carries `nodes.caused_by` when the column
948
+ -- exists first. The whole block is one multi-statement query, so it applies entirely or not at all.
949
+ -- ledger:begin
950
+ CREATE TABLE IF NOT EXISTS charges (
951
+ id text PRIMARY KEY,
952
+ request text,
953
+ at text NOT NULL,
954
+ month text NOT NULL,
955
+ kind text NOT NULL CHECK (kind IN ('model','tool','sandbox','job','storage','take')),
956
+ what text NOT NULL,
957
+ units jsonb,
958
+ usd numeric(14,6),
959
+ payer text NOT NULL,
960
+ funding text NOT NULL CHECK (funding IN ('bill','subscription','key','endpoint','take','legacy')),
961
+ sub text,
962
+ grantee text NOT NULL,
963
+ node text,
964
+ run text,
965
+ provider text,
966
+ caused_by text,
967
+ vendor_ref text,
968
+ refused jsonb
969
+ );
970
+ CREATE INDEX IF NOT EXISTS charges_month_payer ON charges(month, payer);
971
+ CREATE INDEX IF NOT EXISTS charges_request ON charges(request) WHERE request IS NOT NULL;
972
+ CREATE INDEX IF NOT EXISTS charges_caused_by ON charges(caused_by) WHERE caused_by IS NOT NULL;
973
+ CREATE INDEX IF NOT EXISTS charges_run ON charges(run) WHERE run IS NOT NULL;
974
+ CREATE INDEX IF NOT EXISTS charges_grantee_month ON charges(grantee, month);
975
+
976
+ CREATE TABLE IF NOT EXISTS charge_totals (
977
+ month text NOT NULL,
978
+ payer text NOT NULL,
979
+ funding text NOT NULL,
980
+ sub text NOT NULL DEFAULT '',
981
+ grantee text NOT NULL,
982
+ provider text NOT NULL DEFAULT '',
983
+ usd numeric(16,6) NOT NULL DEFAULT 0,
984
+ PRIMARY KEY (month, payer, funding, sub, grantee, provider)
985
+ );
986
+
987
+ CREATE TABLE IF NOT EXISTS limits (
988
+ id text PRIMARY KEY,
989
+ payer text NOT NULL,
990
+ funding text,
991
+ grantee text,
992
+ provider text,
993
+ sub text,
994
+ usd_per_month numeric(14,6) NOT NULL CHECK (usd_per_month >= 0),
995
+ source text NOT NULL CHECK (source IN ('grant_sync','person','operator')),
996
+ updated_at text NOT NULL
997
+ );
998
+ CREATE INDEX IF NOT EXISTS limits_payer ON limits(payer);
999
+
1000
+ ALTER TABLE nodes ADD COLUMN IF NOT EXISTS caused_by text;
1001
+ -- ledger:end
1002
+
942
1003
  -- ---------------------------------------------------------------- permissions (cycle 3)
943
1004
 
944
1005
  -- One permission model: relation facts, app schemas, the columns the evaluator and owner tree need, the narrowed privacy
@@ -1,4 +1,30 @@
1
- /** The canvas schema version `schema-pg.sql` writes to `canvas_meta.schema_version`. `db.ts` refuses a database at
2
- * any other version, and `scripts/install-runtime.mjs` reads this module from a candidate generation to refuse one older
3
- * than the live cluster. Kept free of imports so the installer can load it alone. */
1
+ /** The canvas schema version this build supports: the number of the highest delta in `schema-pg/` (the baseline
2
+ * `schema-pg.sql` is version 1). `scripts/install-runtime.mjs` reads this module from a candidate generation to refuse
3
+ * one below the live database's `schema_min_reader`. */
4
4
  export declare const CANVAS_SCHEMA_VERSION = 1;
5
+ /** The Postgres schema holding the canvas tables: `CRTR_DATABASE_SCHEMA` (`crtr` in the cloud deploy). Unset or empty
6
+ * means the role's own search_path (`public` on a Mac and on a VM). A name that is not a plain identifier is an error:
7
+ * it is put in a `search_path` connection option. */
8
+ export declare function canvasDatabaseSchema(env?: Record<string, string | undefined>): string | undefined;
9
+ /** The direct, non-pooled connection the schema chain runs on when `CRTR_SCHEMA_DATABASE_URL` is set (its advisory lock
10
+ * needs one session). Unset: the chain runs on the daemon's own connection. */
11
+ export declare function canvasSchemaDatabaseUrl(env?: Record<string, string | undefined>): string | undefined;
12
+ /** The pg `options` connection parameter that makes every connection resolve unqualified names in the canvas schema. */
13
+ export declare function canvasSearchPathOptions(env?: Record<string, string | undefined>): string | undefined;
14
+ interface Queryable {
15
+ query(text: string): Promise<{
16
+ rows: Array<Record<string, unknown>>;
17
+ }>;
18
+ }
19
+ export interface StoredVersions {
20
+ /** `canvas_meta.schema_version`: the number of the last committed step. */
21
+ version: number;
22
+ /** `canvas_meta.schema_min_reader`: the lowest build version that may open the database; absent means `version`. */
23
+ minReader: number;
24
+ }
25
+ /** The versions stored in `schema` (default: the session's search_path), or undefined when the database has no canvas schema yet. */
26
+ export declare function readStoredVersions(client: Queryable, schema?: string): Promise<StoredVersions | undefined>;
27
+ /** The installer's rollback gate: refuse a candidate whose version is below the live database's `schema_min_reader`. A
28
+ * database with no canvas schema yet accepts any candidate. */
29
+ export declare function assertCandidateMayOpen(client: Queryable, supported: number, schema?: string): Promise<void>;
30
+ export {};
@@ -1 +1 @@
1
- const A=1;export{A as CANVAS_SCHEMA_VERSION};
1
+ var m=Object.defineProperty;var r=(n,e)=>m(n,"name",{value:e,configurable:!0});const A=1;function _(n=process.env){const e=n.CRTR_DATABASE_SCHEMA?.trim();if(e){if(!/^[A-Za-z_][A-Za-z0-9_]*$/.test(e))throw new Error(`CRTR_DATABASE_SCHEMA must be a plain identifier (letters, digits, underscore); got ${JSON.stringify(e)}`);return e}}r(_,"canvasDatabaseSchema");function h(n=process.env){return n.CRTR_SCHEMA_DATABASE_URL?.trim()||void 0}r(h,"canvasSchemaDatabaseUrl");function E(n=process.env){const e=_(n);return e===void 0?void 0:`-c search_path=${e}`}r(E,"canvasSearchPathOptions");async function v(n,e){const t=e===void 0?"canvas_meta":`"${e.replace(/"/g,'""')}".canvas_meta`;if((await n.query(`SELECT to_regclass('${t.replace(/'/g,"''")}') IS NOT NULL AS found`)).rows[0].found!==!0)return;const u=(await n.query(`SELECT key, value FROM ${t} WHERE key IN ('schema_version', 'schema_min_reader')`)).rows,o=r(c=>{const i=u.find(f=>f.key===c);if(i===void 0)return;const d=Number(i.value);if(!Number.isSafeInteger(d))throw new Error(`canvas_meta.${c} is not an integer: ${String(i.value)}`);return d},"value"),s=o("schema_version");return s===void 0?void 0:{version:s,minReader:o("schema_min_reader")??s}}r(v,"readStoredVersions");async function l(n,e,t){const a=await v(n,t);if(a!==void 0&&a.minReader>e)throw new Error(`refusing to select runtime with canvas schema ${e}: the live database requires readers at version ${a.minReader} or above (it is at ${a.version}); install a descendant build instead`)}r(l,"assertCandidateMayOpen");export{A as CANVAS_SCHEMA_VERSION,l as assertCandidateMayOpen,_ as canvasDatabaseSchema,h as canvasSchemaDatabaseUrl,E as canvasSearchPathOptions,v as readStoredVersions};
@@ -264,6 +264,8 @@ export interface NodeIdentity {
264
264
  event_cursor?: number | null;
265
265
  last_activity_at?: string | null;
266
266
  usage_json?: string | null;
267
+ /** The reference that caused this run (`message:<id>`, `request:<id>`, `cron_run:<id>`), set on a run's root from `run.start`; null for a root. */
268
+ caused_by?: string | null;
267
269
  /** A run root's latest turn error (`run.get` `last_turn_error`), JSON; null once a turn completes without one. */
268
270
  last_turn_error_json?: string | null;
269
271
  deleting_at?: string | null;
@@ -384,6 +386,8 @@ export interface NodeRow {
384
386
  event_cursor?: number | null;
385
387
  last_activity_at?: string | null;
386
388
  usage_json?: string | null;
389
+ /** The reference that caused this run (`message:<id>`, `request:<id>`, `cron_run:<id>`), set on a run's root from `run.start`; null for a root. */
390
+ caused_by?: string | null;
387
391
  /** A run root's latest turn error (`run.get` `last_turn_error`), JSON; null once a turn completes without one. */
388
392
  last_turn_error_json?: string | null;
389
393
  deleting_at?: string | null;
@@ -1 +1 @@
1
- var Ke=Object.defineProperty;var s=(e,n)=>Ke(e,"name",{value:n,configurable:!0});import{envProfileId as qe}from"../shared/env.js";import{basename as X,join as Q}from"node:path";import{existsSync as Ge,mkdirSync as Be,statSync as We}from"node:fs";import{isDeepStrictEqual as He}from"node:util";import{CONFIG_FILE as Je,STATE_FILE as Ze,CONDENSED_HISTORY_MODES as Ye,MOUSE_MODE_DEFAULTS as Ve,WHIP_MESSAGE_MODES as Xe,DEFAULT_WHIP_MESSAGES as ee,defaultScopeConfig as k,defaultScopeState as ne,defaultModelLaddersConfig as Qe,defaultKindsConfig as en,defaultRemoteCanvasConfig as nn}from"../types.js";import{BINDING_CATALOG as on,BINDING_IDS as tn,isAttachPaneBinding as rn}from"./keybindings/catalog.js";import{notFound as sn}from"./errors.js";import{emitEvent as cn}from"./events/emit.js";import{atomicWriteJson as oe,readJsonIfExists as K,ensureDir as un}from"./fs-utils.js";import{scopeRoot as y,requireScopeRoot as w,findProjectScopeRoots as dn,userScopeRoot as E}from"./scope.js";import{listInstalledPluginsInRoot as te}from"./installed-plugins.js";import{profileDir as q,listProfiles as re}from"./profiles/manifest.js";import{canonicalRoot as A,projectStore as fn,userStore as ie}from"./scoped-state/paths.js";import{convertLegacyScopedStore as ln,initializeFreshScopedStore as an}from"./scoped-state/migrate.js";import{mutateRawSettings as mn,readBookkeeping as pn,readRawSettings as gn,readRawSettingsWithRevision as yn,replaceRawSettings as hn,writeBookkeeping as bn,writeRawSettings as _n}from"./scoped-state/settings.js";import{withExclusiveDirectoryLock as vn}from"./exclusive-lock.js";import{SCOPE_CONFIG_KEYS as kn}from"./user-settings.js";import{normalizeWorkingGerunds as wn}from"../shared/working-activity.js";import{mergePageComponentLayers as An,normalizePageComponents as se}from"./human/page-catalog.js";function b(e){return Q(e,Je)}s(b,"configPathFor");function ce(e,n){return vn(`${e}.lock`,()=>n(e),{timeoutError:s(()=>new Error(`timed out waiting for config lock: ${e}`),"timeoutError")})}s(ce,"withConfigPathLock");function Sn(e){return Q(e,Ze)}s(Sn,"statePathFor");function vo(e){const n=y(e);return n?b(n):null}s(vo,"configPath");function ko(e){const n=y(e);return n?Sn(n):null}s(ko,"statePath");function g(e){const n=A(e),o=A(E());if(n===o)return{store:ie(o),key:"user",kind:"user",root:o};if(X(n)===".crouter")return ln({kind:"project",root:n}),{store:fn(n),key:"project",kind:"project",root:n};for(const t of re()){const r=A(q(t.profileId));if(n===r)return{store:ie(o),key:`profile:${t.profileId}`,kind:"profile",root:r,profileId:t.profileId}}throw new Error(`settings root is not a recognized owner: ${e}`)}s(g,"settingsOwnerForRoot");function I(e){if(X(e)===".crouter"){let n;try{n=A(e)}catch(o){if(o.code==="ENOENT")return null;throw o}if(n!==A(E()))return K(b(e))}return gn(g(e))}s(I,"readRawAtRoot");function G(e,n){const o=g(e);if(o.kind==="project"){ce(b(o.root),t=>oe(t,n,{createParent:!1}));return}_n(o,n)}s(G,"writeRawAtRoot");function S(e,n){const o=g(e);return o.kind==="project"?ce(b(o.root),t=>{const r=n(K(t)??{});return oe(t,r.next,{createParent:!1}),r.value}):mn(o,t=>n(t))}s(S,"mutateRawAtRoot");const P=globalThis,T=Symbol.for("crtr.broker.config-snapshot");function wo(e){P[T]=e}s(wo,"installBrokerConfigSnapshot");function Ao(){return P[T]}s(Ao,"brokerConfigSnapshot");function jn(e){const n=P[T];if(n){if(e!=="user")throw new Error(`broker config snapshot has no ${e} scope`);return n.user}const o=y(e);return o?Rn(o):k()}s(jn,"readConfig");function Rn(e){const n=I(e);return n?z(n):k()}s(Rn,"readConfigAtRoot");function xn(e){const n=y(e);if(!n)return ne();try{return pn(g(n))}catch(o){if(e==="project"&&!Ge(n))return ne();throw o}}s(xn,"readState");class On extends Error{static{s(this,"InvalidRawSettingsError")}constructor(n){super(`settings cannot be merged: ${n instanceof Error?n.message:String(n)}`),this.name="InvalidRawSettingsError"}}function ue(e){if(e.scope==="user")return g(E());if(!re().some(n=>n.profileId===e.profileId))throw sn(`profile not found: ${e.profileId}`,{received:e.profileId,next:"Run `crtr profile list` to see available profiles."});return g(q(e.profileId))}s(ue,"rawSettingsOwnerForTarget");function So(e){return yn(ue(e))}s(So,"readRawSettingsForTarget");function jo(e,n,o){return hn(ue(e),n,o,t=>{try{z(t)}catch(r){throw new On(r)}})}s(jo,"replaceRawSettingsForTarget");function Ro(e,n){const o=w(e);return S(o,t=>{const r=n(t);return{value:r,next:r}})}s(Ro,"updateRawConfigAtomically");function xo(e,n){G(w(e),n)}s(xo,"writeConfig");function Oo(e,n){G(w(e),n)}s(Oo,"writeRawConfig");function En(e,n){const o=w(e);bn(g(o),n)}s(En,"writeState");function Eo(e,n){if(e==="project")try{Be(n)}catch(t){if(t.code!=="EEXIST"||!We(n).isDirectory())throw t}else un(n);const o=g(e==="user"?E():n);an({kind:o.kind==="user"?"user":"project",root:o.store.root}),o.kind==="project"&&K(b(o.root))===null&&G(o.root,{schema_version:k().schema_version})}s(Eo,"ensureScopeInitialized");function In(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(In,"normalizeKeybindings");const Pn=new Set(on.filter(rn).map(e=>e.id));function Tn(e){return Array.isArray(e)?[...new Set(e.filter(n=>typeof n=="string"&&tn.has(n)&&Pn.has(n)))]:[]}s(Tn,"normalizeTmuxPassthrough");function C(e,n=Qe()){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(C,"mergeModelLadders");function de(e){return e==="ultra"||e==="strong"||e==="medium"||e==="light"}s(de,"validStrength");const B=["ultra","strong","medium","light"];function W(){return Object.create(null)}s(W,"routeRecord");function j(e,n,o){Object.defineProperty(e,n,{value:o,writable:!0,enumerable:!0,configurable:!0})}s(j,"setRouteRecord");function fe(e){const n=W();for(const[o,t]of Object.entries(e??{}))j(n,o,t);return n}s(fe,"copyRouteRecord");function R(e,n){cn({level:"warn",event:"config.model_routes.invalid",fields:{route_id:e,rule:n}})}s(R,"routeInvalid");function $(e,n={}){const o=fe(n);if(e===null||typeof e!="object"||Array.isArray(e))return o;for(const[t,r]of Object.entries(e)){if(t.trim()===""){R(t,"route id must be a non-empty string");continue}if(r===null||typeof r!="object"||Array.isArray(r)){R(t,"route must be an object");continue}const i=r,c=o[t],u={...c};let d;for(const l of["family","credentialSource","providerId"]){const f=i[l];if(f!==void 0){if(typeof f!="string"||f.trim()===""){d=`${l} must be a non-empty string`;break}u[l]=f}}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 l={...c?.models};for(const[f,a]of Object.entries(i.models)){if(!de(f)){d=`models.${f} is not a model strength`;break}if(typeof a!="string"||a.trim()===""){d=`models.${f} must be a non-empty provider/id ref`;break}const m=a.indexOf("/");if(m<=0||m===a.length-1){d=`models.${f} must be provider/id`;break}l[f]=a}u.models=l}if(d===void 0&&u.models!==void 0&&u.providerId!==void 0){for(const[l,f]of Object.entries(u.models))if(f.slice(0,f.indexOf("/"))!==u.providerId){d=`models.${l} provider prefix must equal providerId`;break}}if(d!==void 0){R(t,d);continue}j(o,t,u)}return o}s($,"mergeModelRoutePatches");function le(e){const n=W(),o=W();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){R(t,"route must include family, credentialSource, providerId, and models");continue}const i=B.filter(c=>r.models[c]===void 0);if(i.length>0){R(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(le,"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=B.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 B){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=>de(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 $n(e){return e!==""&&e.replace(/\*/g,"")===""}s($n,"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($n(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 ae=/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/,Mn=new Set(["crtr","crtrd"]);function F(e,n){if(n==="bin"&&Mn.has(e))return`${n}.${e} is reserved by crouter \u2014 pick another bare command name`;if(!ae.test(e))return`${n}.${e} is not a safe bare command name (expected ${ae.source})`}s(F,"bareCommandNameIssue");function Nn(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(Nn,"binDeclarationIssues");function Ln(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(Ln,"normalizeBin");const me=/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;function Fn(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(me.test(o)||r.push(`action name must match ${me.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(Fn,"humanActionDeclarationIssues");function zn(e){if(e===null||typeof e!="object"||Array.isArray(e))return{};const n=new Set(Fn(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(zn,"normalizeHumanActions");function Io(e){return Nn(e).map(n=>n.message)}s(Io,"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 To(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(To,"invalidPluginRequiresReasons");function pe(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(pe,"normalizeKindFields");function x(e,n=en()){const o={...n};if(e!==null&&typeof e=="object")for(const[t,r]of Object.entries(e)){const i=pe(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(x,"mergeKinds");function ge(e,n,o){if(n===null)return o;const t=te(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=x(i.manifest.kinds,r);return r}s(ge,"layerPluginKinds");function Co(){const e=[{origin:"config.json",components:jn("user").page_components}],n=y("user");if(n!==null){const o=te("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:se(t.manifest.page_components,r)})}}return An(e)}s(Co,"resolvePageComponents");const H=new Set(["whenToUse","model","orchestratorModel","tools","extensions","availableTo"]);function $o(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))H.has(c)||n.push(`kinds.${t}.${c} is not a KindConfig field (expected one of: ${[...H].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`);pe(r)===null&&n.push(`kinds.${t} has no valid KindConfig field \u2014 an entry must set at least one of: ${[...H].join(", ")} (whenToUse is required when the kind exists in no lower layer)`)}return n}s($o,"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 Un(e,n=nn()){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(Un,"mergeRemoteCanvas");function ye(e,n){return e===!0?"notify":e===!1?!1:e==="notify"||e==="apply"?e:n}s(ye,"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 qn(e,n){return{unattendedParkMs:O((e!==null&&typeof e=="object"?e:{}).unattendedParkMs,n.unattendedParkMs)}}s(qn,"mergeLifecycle");function Gn(e){if(!Array.isArray(e))return[...ee];const n=e.filter(o=>typeof o=="string"&&o.trim()!=="").map(o=>o.trim());return n.length>0?n:[...ee]}s(Gn,"normalizeWhipMessages");function Bn(e,n){if(e===null||typeof e!="object")return{...n};const o=e.tcp;return{tcp:typeof o=="string"?o:n.tcp}}s(Bn,"mergeApi");function z(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:ye(i?.crtr,n.auto_update.crtr),content:ye(i?.content,n.auto_update.content),interval_hours:u},l=e.max_panes_per_window,f=O(l,n.max_panes_per_window),a=typeof e.auto_open_child_viewers=="boolean"?e.auto_open_child_viewers:n.auto_open_child_viewers,m=Kn(e.brokerThresholds,n.brokerThresholds),h=qn(e.lifecycle,n.lifecycle),D=typeof e.completion_bell=="boolean"?e.completion_bell:n.completion_bell,p=e.working_gerunds===void 0?[...n.working_gerunds]:wn(e.working_gerunds),be=typeof e.whip_mode=="boolean"?e.whip_mode:n.whip_mode,_e=se(e.page_components),ve=typeof e.page_surface=="boolean"?e.page_surface:n.page_surface,ke=Gn(e.whip_messages),we=Xe.includes(e.whip_message_mode)?e.whip_message_mode:n.whip_message_mode,Ae=Ve.includes(e.mouse_mode_default)?e.mouse_mode_default:n.mouse_mode_default,Se=O(e.live_cycles,n.live_cycles),je=Ye.includes(e.condensed_history)?e.condensed_history:n.condensed_history,Re=typeof e.bash_tool_purpose=="boolean"?e.bash_tool_purpose:n.bash_tool_purpose,xe=typeof e.fold_finished_tools=="boolean"?e.fold_finished_tools:n.fold_finished_tools,Oe=typeof e.summarize_tool_calls=="boolean"?e.summarize_tool_calls:n.summarize_tool_calls,Ee=typeof e.detailed_tool_recaps=="boolean"?e.detailed_tool_recaps:n.detailed_tool_recaps,Ie=Number.isInteger(e.document_port)&&e.document_port>=1&&e.document_port<=65535?e.document_port:n.document_port,Pe=typeof e.consolidate_replies=="boolean"?e.consolidate_replies:n.consolidate_replies,Te=In(e.keybindings),Ce=Tn(e.tmux_passthrough),$e=C(e.modelLadders),Me=L(e.providerOptions,n.providerOptions),U=le($(e.modelRoutes)),J=U.complete,Z=M(e.modelRouting,void 0),Ne=x(e.kinds),Le=Un(e.remoteCanvas),Fe=Bn(e.api,n.api),_=e.paths;if(_?.user_files!==void 0&&(typeof _.user_files!="string"||!_.user_files.startsWith("/")))throw new Error("paths.user_files must be an absolute path");const Y=e.store,v=e.runtime;if(v?.person_uids!==void 0&&(!Array.isArray(v.person_uids)||!v.person_uids.every(V=>Number.isSafeInteger(V)&&V>=0)))throw new Error("runtime.person_uids must be an array of nonnegative integer uids");const ze=N(e.spawnEnv,n.spawnEnv?.allow),De=Ln(e.bin),Ue=zn(e.humanActions);return{bin:De,humanActions:Ue,schema_version:o,marketplaces:t,plugins:r,auto_update:d,max_panes_per_window:f,auto_open_child_viewers:a,brokerThresholds:m,lifecycle:h,completion_bell:D,working_gerunds:p,whip_mode:be,page_components:_e,page_surface:ve,whip_messages:ke,whip_message_mode:we,mouse_mode_default:Ae,live_cycles:Se,condensed_history:je,bash_tool_purpose:Re,fold_finished_tools:xe,summarize_tool_calls:Oe,detailed_tool_recaps:Ee,document_port:Ie,consolidate_replies:Pe,keybindings:Te,tmux_passthrough:Ce,modelLadders:$e,providerOptions:Me,...Object.keys(J).length>0?{modelRoutes:J}:{},...Object.keys(U.legacy).length>0?{legacyModelRoutes:U.legacy}:{},...Z!==void 0?{modelRouting:Z}:{},kinds:Ne,remoteCanvas:Le,api:Fe,..._?{paths:_}:{},...Y?{store:Y}:{},...v?{runtime:v}:{},spawnEnv:ze}}s(z,"mergeConfig");function Wn(e){return I(e)}s(Wn,"readRawConfigAtRoot");function Hn(e){const n=y(e);return n?Wn(n):null}s(Hn,"readRawScopeConfig");function Jn(e){if(e===null||e==="")return null;try{return I(q(e))}catch{return null}}s(Jn,"readRawProfileConfig");function Zn(e,n){return[...dn(e,n)].reverse().map(r=>({root:r,raw:I(r)}))}s(Zn,"readRawProjectScopeConfigs");function he(e=process.cwd(),n=qe()||null){const o=P[T];if(o)return o.launch;const t=k(),r=Hn("user"),i=Jn(n),c=Zn(e,n);let u=x(r?.kinds,ge("user",y("user"),t.kinds)),d=C(r?.modelLadders,t.modelLadders),l=L(r?.providerOptions,t.providerOptions),f=$(r?.modelRoutes),a=M(r?.modelRouting,void 0),m=N(r?.spawnEnv,t.spawnEnv?.allow);i!==null&&(u=x(i.kinds,u),d=C(i.modelLadders,d),l=L(i.providerOptions,l),f=$(i.modelRoutes,f),a=M(i.modelRouting,a),m=N(i.spawnEnv,m.allow));for(const{root:D,raw:p}of c)u=x(p?.kinds,ge("project",D,u)),p!==null&&(d=C(p.modelLadders,d),l=L(p.providerOptions,l),f=$(p.modelRoutes,f),a=M(p.modelRouting,a),m=N(p.spawnEnv,m.allow));const h=le(f);return{kinds:u,modelLadders:d,providerOptions:l,...Object.keys(h.complete).length>0?{modelRoutes:h.complete}:{},...Object.keys(h.legacy).length>0?{legacyModelRoutes:h.legacy}:{},...a!==void 0?{modelRouting:a}:{},spawnEnv:m}}s(he,"readMergedLaunchConfig");function Mo(e){return he().kinds[e]}s(Mo,"resolveKindConfig");function No(e){const n=he().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(No,"subKindsAvailableTo");function Yn(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(Yn,"sparseModelLaddersToPersist");function Vn(e,n){return S(e,o=>{const t=z(o),r=z(o);n(r);const i={...o};for(const u of kn)u!=="modelLadders"&&!He(r[u],t[u])&&(i[u]=r[u]);const c=Yn(o.modelLadders,t.modelLadders,r.modelLadders);return c!==void 0?i.modelLadders=c:delete i.modelLadders,{value:r,next:i}})}s(Vn,"mutateConfigAtRoot");function Lo(e,n){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(Lo,"persistModelLadders");function Fo(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");S(e,t=>{const r=fe(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(Fo,"persistModelRoute");function Xn(e,n){return Vn(e,n)}s(Xn,"updateConfigAtRoot");function zo(e,n){return Xn(w(e),n)}s(zo,"updateConfig");function Do(e){const n=e.orchestrator===!0?"orchestratorModel":"model";return 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:b(e.root),field:n}}s(Do,"persistDefaultKindModel");function Uo(e,n){const o=xn(e);return n(o),En(e,o),o}s(Uo,"updateState");export{On as InvalidRawSettingsError,Nn as binDeclarationIssues,Ao as brokerConfigSnapshot,vo as configPath,Eo as ensureScopeInitialized,Fn as humanActionDeclarationIssues,wo as installBrokerConfigSnapshot,Io as invalidPluginBinReasons,$o as invalidPluginKindsReasons,To as invalidPluginRequiresReasons,Ln as normalizeBin,zn as normalizeHumanActions,Po as normalizeRequires,Tn as normalizeTmuxPassthrough,Do as persistDefaultKindModel,Lo as persistModelLadders,Fo as persistModelRoute,jn as readConfig,Rn as readConfigAtRoot,he as readMergedLaunchConfig,Wn as readRawConfigAtRoot,Hn as readRawScopeConfig,So as readRawSettingsForTarget,xn as readState,jo as replaceRawSettingsForTarget,Mo as resolveKindConfig,Co as resolvePageComponents,ko as statePath,No as subKindsAvailableTo,zo as updateConfig,Xn as updateConfigAtRoot,Ro as updateRawConfigAtomically,Uo as updateState,xo as writeConfig,Oo as writeRawConfig,En as writeState};
1
+ var qe=Object.defineProperty;var s=(e,n)=>qe(e,"name",{value:n,configurable:!0});import{envProfileId as Ge}from"../shared/env.js";import{basename as Q,join as ee}from"node:path";import{existsSync as Be,mkdirSync as We,statSync as He}from"node:fs";import{isDeepStrictEqual as Je}from"node:util";import{CONFIG_FILE as Ze,STATE_FILE as Ye,CONDENSED_HISTORY_MODES as Ve,MOUSE_MODE_DEFAULTS as Xe,WHIP_MESSAGE_MODES as Qe,DEFAULT_WHIP_MESSAGES as ne,defaultScopeConfig as w,defaultScopeState as oe,defaultModelLaddersConfig as en,defaultKindsConfig as nn,defaultRemoteCanvasConfig as on}from"../types.js";import{BINDING_CATALOG as tn,BINDING_IDS as rn,isAttachPaneBinding as sn}from"./keybindings/catalog.js";import{notFound as cn}from"./errors.js";import{emitEvent as un}from"./events/emit.js";import{atomicWriteJson as te,readJsonIfExists as q,ensureDir as dn}from"./fs-utils.js";import{scopeRoot as h,requireScopeRoot as A,findProjectScopeRoots as fn,userScopeRoot as I}from"./scope.js";import{listInstalledPluginsInRoot as re}from"./installed-plugins.js";import{profileDir as G,listProfiles as ie}from"./profiles/manifest.js";import{canonicalRoot as S,projectStore as ln,userStore as se}from"./scoped-state/paths.js";import{convertLegacyScopedStore as an,initializeFreshScopedStore as mn}from"./scoped-state/migrate.js";import{mutateRawSettings as pn,readBookkeeping as gn,readRawSettings as yn,readRawSettingsWithRevision as hn,replaceRawSettings as bn,writeBookkeeping as _n,writeRawSettings as vn}from"./scoped-state/settings.js";import{withExclusiveDirectoryLock as kn}from"./exclusive-lock.js";import{SCOPE_CONFIG_KEYS as wn}from"./user-settings.js";import{normalizeWorkingGerunds as An}from"../shared/working-activity.js";import{mergePageComponentLayers as Sn,normalizePageComponents as ce}from"./human/page-catalog.js";function _(e){return ee(e,Ze)}s(_,"configPathFor");function ue(e,n){return kn(`${e}.lock`,()=>n(e),{timeoutError:s(()=>new Error(`timed out waiting for config lock: ${e}`),"timeoutError")})}s(ue,"withConfigPathLock");function jn(e){return ee(e,Ye)}s(jn,"statePathFor");function ko(e){const n=h(e);return n?_(n):null}s(ko,"configPath");function wo(e){const n=h(e);return n?jn(n):null}s(wo,"statePath");function g(e){const n=S(e),o=S(I());if(n===o)return{store:se(o),key:"user",kind:"user",root:o};if(Q(n)===".crouter")return an({kind:"project",root:n}),{store:ln(n),key:"project",kind:"project",root:n};for(const t of ie()){const r=S(G(t.profileId));if(n===r)return{store:se(o),key:`profile:${t.profileId}`,kind:"profile",root:r,profileId:t.profileId}}throw new Error(`settings root is not a recognized owner: ${e}`)}s(g,"settingsOwnerForRoot");function P(e){if(Q(e)===".crouter"){let n;try{n=S(e)}catch(o){if(o.code==="ENOENT")return null;throw o}if(n!==S(I()))return q(_(e))}return yn(g(e))}s(P,"readRawAtRoot");function B(e,n){const o=g(e);if(o.kind==="project"){ue(_(o.root),t=>te(t,n,{createParent:!1}));return}vn(o,n)}s(B,"writeRawAtRoot");function j(e,n){const o=g(e);return o.kind==="project"?ue(_(o.root),t=>{const r=n(q(t)??{});return te(t,r.next,{createParent:!1}),r.value}):pn(o,t=>n(t))}s(j,"mutateRawAtRoot");const T=globalThis,C=Symbol.for("crtr.broker.config-snapshot");function Ao(e){T[C]=e}s(Ao,"installBrokerConfigSnapshot");function So(){return T[C]}s(So,"brokerConfigSnapshot");function Rn(e){const n=T[C];if(n){if(e!=="user")throw new Error(`broker config snapshot has no ${e} scope`);return n.user}const o=h(e);return o?xn(o):w()}s(Rn,"readConfig");function xn(e){const n=P(e);return n?D(n):w()}s(xn,"readConfigAtRoot");function On(e){const n=h(e);if(!n)return oe();try{return gn(g(n))}catch(o){if(e==="project"&&!Be(n))return oe();throw o}}s(On,"readState");class En extends Error{static{s(this,"InvalidRawSettingsError")}constructor(n){super(`settings cannot be merged: ${n instanceof Error?n.message:String(n)}`),this.name="InvalidRawSettingsError"}}function de(e){if(e.scope==="user")return g(I());if(!ie().some(n=>n.profileId===e.profileId))throw cn(`profile not found: ${e.profileId}`,{received:e.profileId,next:"Run `crtr profile list` to see available profiles."});return g(G(e.profileId))}s(de,"rawSettingsOwnerForTarget");function jo(e){return hn(de(e))}s(jo,"readRawSettingsForTarget");function Ro(e,n,o){return bn(de(e),n,o,t=>{try{D(t)}catch(r){throw new En(r)}})}s(Ro,"replaceRawSettingsForTarget");function xo(e,n){const o=A(e);return j(o,t=>{const r=n(t);return{value:r,next:r}})}s(xo,"updateRawConfigAtomically");function Oo(e,n){B(A(e),n)}s(Oo,"writeConfig");function Eo(e,n){B(A(e),n)}s(Eo,"writeRawConfig");function In(e,n){const o=A(e);_n(g(o),n)}s(In,"writeState");function Io(e,n){if(e==="project")try{We(n)}catch(t){if(t.code!=="EEXIST"||!He(n).isDirectory())throw t}else dn(n);const o=g(e==="user"?I():n);mn({kind:o.kind==="user"?"user":"project",root:o.store.root}),o.kind==="project"&&q(_(o.root))===null&&B(o.root,{schema_version:w().schema_version})}s(Io,"ensureScopeInitialized");function Pn(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(Pn,"normalizeKeybindings");const Tn=new Set(tn.filter(sn).map(e=>e.id));function Cn(e){return Array.isArray(e)?[...new Set(e.filter(n=>typeof n=="string"&&rn.has(n)&&Tn.has(n)))]:[]}s(Cn,"normalizeTmuxPassthrough");function $(e,n=en()){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($,"mergeModelLadders");function fe(e){return e==="ultra"||e==="strong"||e==="medium"||e==="light"}s(fe,"validStrength");const W=["ultra","strong","medium","light"];function H(){return Object.create(null)}s(H,"routeRecord");function R(e,n,o){Object.defineProperty(e,n,{value:o,writable:!0,enumerable:!0,configurable:!0})}s(R,"setRouteRecord");function le(e){const n=H();for(const[o,t]of Object.entries(e??{}))R(n,o,t);return n}s(le,"copyRouteRecord");function x(e,n){un({level:"warn",event:"config.model_routes.invalid",fields:{route_id:e,rule:n}})}s(x,"routeInvalid");function M(e,n={}){const o=le(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 l of["family","credentialSource","providerId"]){const f=i[l];if(f!==void 0){if(typeof f!="string"||f.trim()===""){d=`${l} must be a non-empty string`;break}u[l]=f}}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 l={...c?.models};for(const[f,a]of Object.entries(i.models)){if(!fe(f)){d=`models.${f} is not a model strength`;break}if(typeof a!="string"||a.trim()===""){d=`models.${f} must be a non-empty provider/id ref`;break}const m=a.indexOf("/");if(m<=0||m===a.length-1){d=`models.${f} must be provider/id`;break}l[f]=a}u.models=l}if(d===void 0&&u.models!==void 0&&u.providerId!==void 0){for(const[l,f]of Object.entries(u.models))if(f.slice(0,f.indexOf("/"))!==u.providerId){d=`models.${l} provider prefix must equal providerId`;break}}if(d!==void 0){x(t,d);continue}R(o,t,u)}return o}s(M,"mergeModelRoutePatches");function ae(e){const n=H(),o=H();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=W.filter(c=>r.models[c]===void 0);if(i.length>0){x(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(ae,"resolveModelRoutes");function $n(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=W.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 W){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($n,"assertCompleteModelRoute");function N(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=>fe(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(N,"mergeModelRouting");function Mn(e){return e!==""&&e.replace(/\*/g,"")===""}s(Mn,"isInheritAllGlob");function L(e,n=[]){const o=e,t=Array.isArray(o?.allow)?o.allow.filter(r=>typeof r=="string"):[];for(const r of t)if(Mn(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(L,"mergeSpawnEnv");function F(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(F,"mergeProviderOptions");const me=/^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$/,Nn=new Set(["crtr","crtrd"]);function z(e,n){if(n==="bin"&&Nn.has(e))return`${n}.${e} is reserved by crouter \u2014 pick another bare command name`;if(!me.test(e))return`${n}.${e} is not a safe bare command name (expected ${me.source})`}s(z,"bareCommandNameIssue");function Ln(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=z(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(Ln,"binDeclarationIssues");function Fn(e){if(e===null||typeof e!="object"||Array.isArray(e))return{};const n={};for(const[o,t]of Object.entries(e))z(o,"bin")===void 0&&(typeof t!="string"||t.trim()===""||(n[o]=t));return n}s(Fn,"normalizeBin");const pe=/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;function zn(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(pe.test(o)||r.push(`action name must match ${pe.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(zn,"humanActionDeclarationIssues");function Dn(e){if(e===null||typeof e!="object"||Array.isArray(e))return{};const n=new Set(zn(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(Dn,"normalizeHumanActions");function Po(e){return Ln(e).map(n=>n.message)}s(Po,"invalidPluginBinReasons");function To(e){if(e===null||typeof e!="object"||Array.isArray(e))return{};const n={};for(const[o,t]of Object.entries(e))z(o,"requires")===void 0&&(typeof t!="string"||t.trim()===""||/[\r\n]/.test(t)||(n[o]=t));return n}s(To,"normalizeRequires");function Co(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=z(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(Co,"invalidPluginRequiresReasons");function ge(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(ge,"normalizeKindFields");function O(e,n=nn()){const o={...n};if(e!==null&&typeof e=="object")for(const[t,r]of Object.entries(e)){const i=ge(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 ye(e,n,o){if(n===null)return o;const t=re(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(ye,"layerPluginKinds");function $o(){const e=[{origin:"config.json",components:Rn("user").page_components}],n=h("user");if(n!==null){const o=re("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:ce(t.manifest.page_components,r)})}}return Sn(e)}s($o,"resolvePageComponents");const J=new Set(["whenToUse","model","orchestratorModel","tools","extensions","availableTo"]);function Mo(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))J.has(c)||n.push(`kinds.${t}.${c} is not a KindConfig field (expected one of: ${[...J].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`);ge(r)===null&&n.push(`kinds.${t} has no valid KindConfig field \u2014 an entry must set at least one of: ${[...J].join(", ")} (whenToUse is required when the kind exists in no lower layer)`)}return n}s(Mo,"invalidPluginKindsReasons");function Un(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(Un,"normalizeRemoteCanvasTarget");function Kn(e,n=on()){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=Un(i);c!==null&&(o[r]=c)}}return{targets:o}}s(Kn,"mergeRemoteCanvas");function he(e,n){return e===!0?"notify":e===!1?!1:e==="notify"||e==="apply"?e:n}s(he,"normalizeMode");function E(e,n){return typeof e=="number"&&Number.isSafeInteger(e)&&e>=1?e:n}s(E,"normalizePositiveInteger");function qn(e,n){const o=e!==null&&typeof e=="object"?e:{},t=E(o.warning,n.warning),r=E(o.automaticReviveCap,n.automaticReviveCap);return t<=r?{warning:t,automaticReviveCap:r}:{...n}}s(qn,"mergeBrokerThresholds");function Gn(e,n){return{unattendedParkMs:E((e!==null&&typeof e=="object"?e:{}).unattendedParkMs,n.unattendedParkMs)}}s(Gn,"mergeLifecycle");function Bn(e){if(!Array.isArray(e))return[...ne];const n=e.filter(o=>typeof o=="string"&&o.trim()!=="").map(o=>o.trim());return n.length>0?n:[...ne]}s(Bn,"normalizeWhipMessages");function Wn(e,n){if(e===null||typeof e!="object")return{...n};const o=e.tcp;return{tcp:typeof o=="string"?o:n.tcp}}s(Wn,"mergeApi");function D(e){const n=w(),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:he(i?.crtr,n.auto_update.crtr),content:he(i?.content,n.auto_update.content),interval_hours:u},l=e.max_panes_per_window,f=E(l,n.max_panes_per_window),a=typeof e.auto_open_child_viewers=="boolean"?e.auto_open_child_viewers:n.auto_open_child_viewers,m=qn(e.brokerThresholds,n.brokerThresholds),b=Gn(e.lifecycle,n.lifecycle),U=typeof e.completion_bell=="boolean"?e.completion_bell:n.completion_bell,p=e.working_gerunds===void 0?[...n.working_gerunds]:An(e.working_gerunds),_e=typeof e.whip_mode=="boolean"?e.whip_mode:n.whip_mode,ve=ce(e.page_components),ke=typeof e.page_surface=="boolean"?e.page_surface:n.page_surface,we=Bn(e.whip_messages),Ae=Qe.includes(e.whip_message_mode)?e.whip_message_mode:n.whip_message_mode,Se=Xe.includes(e.mouse_mode_default)?e.mouse_mode_default:n.mouse_mode_default,je=E(e.live_cycles,n.live_cycles),Re=Ve.includes(e.condensed_history)?e.condensed_history:n.condensed_history,xe=typeof e.bash_tool_purpose=="boolean"?e.bash_tool_purpose:n.bash_tool_purpose,Oe=typeof e.fold_finished_tools=="boolean"?e.fold_finished_tools:n.fold_finished_tools,Ee=typeof e.summarize_tool_calls=="boolean"?e.summarize_tool_calls:n.summarize_tool_calls,Ie=typeof e.detailed_tool_recaps=="boolean"?e.detailed_tool_recaps:n.detailed_tool_recaps,Pe=Number.isInteger(e.document_port)&&e.document_port>=1&&e.document_port<=65535?e.document_port:n.document_port,Te=typeof e.consolidate_replies=="boolean"?e.consolidate_replies:n.consolidate_replies,Ce=Pn(e.keybindings),$e=Cn(e.tmux_passthrough),Me=$(e.modelLadders),Ne=F(e.providerOptions,n.providerOptions),K=ae(M(e.modelRoutes)),Z=K.complete,Y=N(e.modelRouting,void 0),Le=O(e.kinds),Fe=Kn(e.remoteCanvas),ze=Wn(e.api,n.api),v=e.paths;if(v?.user_files!==void 0&&(typeof v.user_files!="string"||!v.user_files.startsWith("/")))throw new Error("paths.user_files must be an absolute path");const y=e.ledger;if(y?.take_pct!==void 0&&(typeof y.take_pct!="number"||!Number.isFinite(y.take_pct)||y.take_pct<0||y.take_pct>100))throw new Error("ledger.take_pct must be a number from 0 to 100");const V=e.store,k=e.runtime;if(k?.person_uids!==void 0&&(!Array.isArray(k.person_uids)||!k.person_uids.every(X=>Number.isSafeInteger(X)&&X>=0)))throw new Error("runtime.person_uids must be an array of nonnegative integer uids");const De=L(e.spawnEnv,n.spawnEnv?.allow),Ue=Fn(e.bin),Ke=Dn(e.humanActions);return{bin:Ue,humanActions:Ke,schema_version:o,marketplaces:t,plugins:r,auto_update:d,max_panes_per_window:f,auto_open_child_viewers:a,brokerThresholds:m,lifecycle:b,completion_bell:U,working_gerunds:p,whip_mode:_e,page_components:ve,page_surface:ke,whip_messages:we,whip_message_mode:Ae,mouse_mode_default:Se,live_cycles:je,condensed_history:Re,bash_tool_purpose:xe,fold_finished_tools:Oe,summarize_tool_calls:Ee,detailed_tool_recaps:Ie,document_port:Pe,consolidate_replies:Te,keybindings:Ce,tmux_passthrough:$e,modelLadders:Me,providerOptions:Ne,...Object.keys(Z).length>0?{modelRoutes:Z}:{},...Object.keys(K.legacy).length>0?{legacyModelRoutes:K.legacy}:{},...Y!==void 0?{modelRouting:Y}:{},kinds:Le,remoteCanvas:Fe,api:ze,...v?{paths:v}:{},...y?{ledger:y}:{},...V?{store:V}:{},...k?{runtime:k}:{},spawnEnv:De}}s(D,"mergeConfig");function Hn(e){return P(e)}s(Hn,"readRawConfigAtRoot");function Jn(e){const n=h(e);return n?Hn(n):null}s(Jn,"readRawScopeConfig");function Zn(e){if(e===null||e==="")return null;try{return P(G(e))}catch{return null}}s(Zn,"readRawProfileConfig");function Yn(e,n){return[...fn(e,n)].reverse().map(r=>({root:r,raw:P(r)}))}s(Yn,"readRawProjectScopeConfigs");function be(e=process.cwd(),n=Ge()||null){const o=T[C];if(o)return o.launch;const t=w(),r=Jn("user"),i=Zn(n),c=Yn(e,n);let u=O(r?.kinds,ye("user",h("user"),t.kinds)),d=$(r?.modelLadders,t.modelLadders),l=F(r?.providerOptions,t.providerOptions),f=M(r?.modelRoutes),a=N(r?.modelRouting,void 0),m=L(r?.spawnEnv,t.spawnEnv?.allow);i!==null&&(u=O(i.kinds,u),d=$(i.modelLadders,d),l=F(i.providerOptions,l),f=M(i.modelRoutes,f),a=N(i.modelRouting,a),m=L(i.spawnEnv,m.allow));for(const{root:U,raw:p}of c)u=O(p?.kinds,ye("project",U,u)),p!==null&&(d=$(p.modelLadders,d),l=F(p.providerOptions,l),f=M(p.modelRoutes,f),a=N(p.modelRouting,a),m=L(p.spawnEnv,m.allow));const b=ae(f);return{kinds:u,modelLadders:d,providerOptions:l,...Object.keys(b.complete).length>0?{modelRoutes:b.complete}:{},...Object.keys(b.legacy).length>0?{legacyModelRoutes:b.legacy}:{},...a!==void 0?{modelRouting:a}:{},spawnEnv:m}}s(be,"readMergedLaunchConfig");function No(e){return be().kinds[e]}s(No,"resolveKindConfig");function Lo(e){const n=be().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(Lo,"subKindsAvailableTo");function Vn(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(Vn,"sparseModelLaddersToPersist");function Xn(e,n){return j(e,o=>{const t=D(o),r=D(o);n(r);const i={...o};for(const u of wn)u!=="modelLadders"&&!Je(r[u],t[u])&&(i[u]=r[u]);const c=Vn(o.modelLadders,t.modelLadders,r.modelLadders);return c!==void 0?i.modelLadders=c:delete i.modelLadders,{value:r,next:i}})}s(Xn,"mutateConfigAtRoot");function Fo(e,n){j(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(Fo,"persistModelLadders");function zo(e,n,o){if(o!==void 0)$n(n,o);else if(n.trim()==="")throw new Error("model route id must be a non-empty string");j(e,t=>{const r=le(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(zo,"persistModelRoute");function Qn(e,n){return Xn(e,n)}s(Qn,"updateConfigAtRoot");function Do(e,n){return Qn(A(e),n)}s(Do,"updateConfig");function Uo(e){const n=e.orchestrator===!0?"orchestratorModel":"model";return j(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(Uo,"persistDefaultKindModel");function Ko(e,n){const o=On(e);return n(o),In(e,o),o}s(Ko,"updateState");export{En as InvalidRawSettingsError,Ln as binDeclarationIssues,So as brokerConfigSnapshot,ko as configPath,Io as ensureScopeInitialized,zn as humanActionDeclarationIssues,Ao as installBrokerConfigSnapshot,Po as invalidPluginBinReasons,Mo as invalidPluginKindsReasons,Co as invalidPluginRequiresReasons,Fn as normalizeBin,Dn as normalizeHumanActions,To as normalizeRequires,Cn as normalizeTmuxPassthrough,Uo as persistDefaultKindModel,Fo as persistModelLadders,zo as persistModelRoute,Rn as readConfig,xn as readConfigAtRoot,be as readMergedLaunchConfig,Hn as readRawConfigAtRoot,Jn as readRawScopeConfig,jo as readRawSettingsForTarget,On as readState,Ro as replaceRawSettingsForTarget,No as resolveKindConfig,$o as resolvePageComponents,wo as statePath,Lo as subKindsAvailableTo,Do as updateConfig,Qn as updateConfigAtRoot,xo as updateRawConfigAtomically,Ko as updateState,Oo as writeConfig,Eo as writeRawConfig,In as writeState};
@@ -108,7 +108,8 @@ export interface TeamRecord {
108
108
  }
109
109
  /** The `teams` a grant sync answer carried; absent (an unjoined install, an older directory) reads as none. */
110
110
  export declare function teamsOf(value: unknown): TeamRecord[];
111
- /** Replace the mirror with `grants` and the `written_by = 'directory'` team `member` facts with `teams`, in one transaction. */
111
+ /** Replace the mirror with `grants`, the ledger's `grant:<id>` limit rows from their caps, and the `written_by = 'directory'` team
112
+ * `member` facts with `teams`, in one transaction. */
112
113
  export declare function replaceGrants(grants: readonly GrantRecord[], syncedAt: string, source: string, teams?: readonly TeamRecord[]): Promise<void>;
113
114
  /** Bumped by every `replaceGrants`. A launch compiled against one generation
114
115
  * and started under another may hold mounts the new mirror no longer grants. */
@@ -1,4 +1,4 @@
1
- var m=Object.defineProperty;var t=(e,r)=>m(e,"name",{value:r,configurable:!0});import{createHash as S}from"node:crypto";import{parseScope as T}from"@north-light/identity";import{openDb as c,withCanvasWrite as O}from"../canvas/db.js";import{isUnsandboxedMachine as v}from"../runtime/unsandboxed.js";import{replaceWrittenBy as w,userSubject as x}from"../permissions/facts.js";import{GROUP_TYPE as R}from"../permissions/builtin.js";const A=["not_started","pending","ready","failed","unavailable"],N=["ultra","strong","medium","light"];function p(e){if(!Array.isArray(e))throw new TypeError("grant scopes must be a list");return e.map(r=>{if(!r||typeof r!="object"||typeof r.scope!="string"||!["always","ask"].includes(r.mode))throw new TypeError("invalid grant scope entry");return{scope:r.scope,mode:r.mode}})}t(p,"entries");function h(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{state:r,message:n,next:i,step:o}=e;return typeof r!="string"||!A.includes(r)||n!==void 0&&typeof n!="string"||o!==void 0&&typeof o!="string"||typeof i!="string"||!i?null:{state:r,...n===void 0?{}:{message:n},next:i,...o===void 0?{}:{step:o}}}t(h,"dependencyOf");function y(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r={};for(const[n,i]of Object.entries(e)){const o=h(i);o&&(r[n]=o)}return r}t(y,"dependenciesOf");function b(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{base_url:r,rungs:n}=e;if(typeof r!="string"||!r||!n||typeof n!="object"||Array.isArray(n))return null;const i={};for(const o of N){const a=n[o];typeof a=="string"&&a&&(i[o]=a)}return Object.keys(i).length?{base_url:r.replace(/\/+$/,""),rungs:i}:null}t(b,"inferenceEndpointOf");function f(e){return{id:e.id,runtime:e.runtime,sub:e.sub,grantee:e.grantee,rev:e.rev,scopes:p(JSON.parse(e.scopes_json)),resolved_scopes:p(JSON.parse(e.resolved_scopes_json)),spend_cap:e.spend_cap,expires_at:e.expires_at,status:e.status,revoked_at:e.revoked_at,approved_at:e.approved_at,payer:e.payer,dependencies:y(JSON.parse(e.dependencies_json)),run_notice_url:e.run_notice_url??null,inference_endpoint:e.inference_endpoint_json?b(JSON.parse(e.inference_endpoint_json)):null,synced_at:e.synced_at}}t(f,"record");function u(e){if(!e||e.status!=="active")return null;try{return F(f(e))}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(u,"active");function l(e){return e.grantee!=="app:terminal"&&v()}t(l,"mirrorOnly");async function I(){return(await L()).filter(e=>!l(e))}t(I,"localGrants");async function L(){return(await(await c()).prepare("SELECT * FROM grants").all()).flatMap(e=>{try{return[f(e)]}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return[];throw r}})}t(L,"allGrants");function j(e){return e.expires_at!==null&&!(Date.parse(e.expires_at)>Date.now())}t(j,"grantExpired");function F(e){if(e.status!=="active"||j(e))return null;try{const r=e.resolved_scopes.map(({scope:n})=>T(n));return{...e,resolved:e.resolved_scopes.flatMap(({mode:n},i)=>n==="always"?[r[i]]:[])}}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(F,"activeRecord");function d(e){return Array.isArray(e)?`[${e.map(d).join(",")}]`:e&&typeof e=="object"?`{${Object.keys(e).sort().filter(r=>e[r]!==void 0).map(r=>`${JSON.stringify(r)}:${d(e[r])}`).join(",")}}`:JSON.stringify(e)??"null"}t(d,"canonicalJson");function U(e){const r={rev:e.rev,resolved_scopes:e.resolved_scopes,spend_cap:e.spend_cap,payer:e.payer,dependencies:e.dependencies??{},...e.inference_endpoint?{inference_endpoint:e.inference_endpoint}:{}};return S("sha256").update(d(r)).digest("base64url").slice(0,22)}t(U,"grantVersion");async function $(e){return!!await(await c()).prepare("SELECT 1 FROM grants WHERE id = ?").get(e)}t($,"hasGrant");async function P(e){return await(await c()).prepare("SELECT rev, status FROM grants WHERE id = ?").get(e)??null}t(P,"grantSyncState");async function V(e,r){return await(await c()).prepare("SELECT rev, status FROM grants WHERE grantee = ? AND sub = ?").get(e,r)??null}t(V,"grantSyncStateFor");async function M(e){const r=await(await c()).prepare("SELECT * FROM grants WHERE id = ?").get(e);if(!r)return null;try{return f(r)}catch(n){if(n instanceof TypeError||n instanceof SyntaxError)return null;throw n}}t(M,"grantRecord");async function B(e){return(e?(await M(e))?.payer:null)??"user"}t(B,"payerOfGrant");async function X(e){return u(await(await c()).prepare("SELECT * FROM grants WHERE id = ?").get(e))}t(X,"activeGrant");async function Y(e,r){return u(await(await c()).prepare("SELECT * FROM grants WHERE grantee = ? AND sub = ?").get(e,r))}t(Y,"grantFor");async function q(e){if(l({grantee:e}))return null;const r=await(await c()).prepare("SELECT * FROM grants WHERE grantee = ? AND status = 'active'").all(e);if(r.length>1)throw new Error(`multiple active grants for ${e}`);return u(r[0])}t(q,"activeGrantForApp");async function z(e){const r=await(await c()).prepare("SELECT run_notice_url AS url, sub, grantee FROM grants WHERE id = ? AND status = 'active'").get(e);return r&&r.url&&!l(r)?{url:r.url,sub:r.sub,grantee:r.grantee}:null}t(z,"runNoticeTarget");async function K(){const e=await(await c()).prepare("SELECT * FROM grants WHERE grantee = 'app:terminal' AND status = 'active'").all();if(e.length>1)throw new Error("multiple active terminal grants");return u(e[0])}t(K,"terminalGrant");async function Q(){return(await(await c()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value??null}t(Q,"grantsSource");async function Z(e){return(await(await c()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value===e}t(Z,"hasSyncedGrants");async function ee(e){return(await e.prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value!=="local"?0:(await e.prepare("DELETE FROM runtime_state WHERE key = 'grants_source'").run(),Number((await e.prepare("DELETE FROM grants").run()).changes))}t(ee,"dropLocalGrantMirror");function re(e){if(e===void 0)return[];if(!Array.isArray(e))throw new TypeError("invalid grant sync response");return e.map(r=>{const{team:n,members:i}=r??{};if(typeof n!="string"||!/^team:\S+$/.test(n)||!Array.isArray(i)||i.some(o=>typeof o!="string"||!/^user:[^\s*]+$/.test(o)))throw new TypeError("invalid grant sync response");return{team:n,members:i}})}t(re,"teamsOf");async function te(e,r,n,i=[]){const o=new Set;for(const a of e){if(o.has(a.id)||!a.id||!a.runtime||!a.sub||!a.grantee||!Number.isInteger(a.rev)||!["active","revoked","removed"].includes(a.status))throw new TypeError("invalid grant sync response");o.add(a.id),p(a.scopes),p(a.resolved_scopes)}await O(async a=>{await a.exec("DELETE FROM grants");const E=a.prepare(`INSERT INTO grants
1
+ var m=Object.defineProperty;var t=(e,r)=>m(e,"name",{value:r,configurable:!0});import{createHash as S}from"node:crypto";import{parseScope as T}from"@north-light/identity";import{openDb as p,withCanvasWrite as w}from"../canvas/db.js";import{isUnsandboxedMachine as O}from"../runtime/unsandboxed.js";import{replaceWrittenBy as v,userSubject as R}from"../permissions/facts.js";import{GROUP_TYPE as x}from"../permissions/builtin.js";import{replaceGrantLimits as A}from"../ledger/index.js";const N=["not_started","pending","ready","failed","unavailable"],h=["ultra","strong","medium","light"];function u(e){if(!Array.isArray(e))throw new TypeError("grant scopes must be a list");return e.map(r=>{if(!r||typeof r!="object"||typeof r.scope!="string"||!["always","ask"].includes(r.mode))throw new TypeError("invalid grant scope entry");return{scope:r.scope,mode:r.mode}})}t(u,"entries");function L(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{state:r,message:n,next:i,step:o}=e;return typeof r!="string"||!N.includes(r)||n!==void 0&&typeof n!="string"||o!==void 0&&typeof o!="string"||typeof i!="string"||!i?null:{state:r,...n===void 0?{}:{message:n},next:i,...o===void 0?{}:{step:o}}}t(L,"dependencyOf");function E(e){if(!e||typeof e!="object"||Array.isArray(e))return{};const r={};for(const[n,i]of Object.entries(e)){const o=L(i);o&&(r[n]=o)}return r}t(E,"dependenciesOf");function b(e){if(!e||typeof e!="object"||Array.isArray(e))return null;const{base_url:r,rungs:n}=e;if(typeof r!="string"||!r||!n||typeof n!="object"||Array.isArray(n))return null;const i={};for(const o of h){const s=n[o];typeof s=="string"&&s&&(i[o]=s)}return Object.keys(i).length?{base_url:r.replace(/\/+$/,""),rungs:i}:null}t(b,"inferenceEndpointOf");function l(e){return{id:e.id,runtime:e.runtime,sub:e.sub,grantee:e.grantee,rev:e.rev,scopes:u(JSON.parse(e.scopes_json)),resolved_scopes:u(JSON.parse(e.resolved_scopes_json)),spend_cap:e.spend_cap,expires_at:e.expires_at,status:e.status,revoked_at:e.revoked_at,approved_at:e.approved_at,payer:e.payer,dependencies:E(JSON.parse(e.dependencies_json)),run_notice_url:e.run_notice_url??null,inference_endpoint:e.inference_endpoint_json?b(JSON.parse(e.inference_endpoint_json)):null,synced_at:e.synced_at}}t(l,"record");function f(e){if(!e||e.status!=="active")return null;try{return j(l(e))}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(f,"active");function d(e){return e.grantee!=="app:terminal"&&O()}t(d,"mirrorOnly");async function P(){return(await F()).filter(e=>!d(e))}t(P,"localGrants");async function F(){return(await(await p()).prepare("SELECT * FROM grants").all()).flatMap(e=>{try{return[l(e)]}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return[];throw r}})}t(F,"allGrants");function M(e){return e.expires_at!==null&&!(Date.parse(e.expires_at)>Date.now())}t(M,"grantExpired");function j(e){if(e.status!=="active"||M(e))return null;try{const r=e.resolved_scopes.map(({scope:n})=>T(n));return{...e,resolved:e.resolved_scopes.flatMap(({mode:n},i)=>n==="always"?[r[i]]:[])}}catch(r){if(r instanceof TypeError||r instanceof SyntaxError)return null;throw r}}t(j,"activeRecord");function y(e){return Array.isArray(e)?`[${e.map(y).join(",")}]`:e&&typeof e=="object"?`{${Object.keys(e).sort().filter(r=>e[r]!==void 0).map(r=>`${JSON.stringify(r)}:${y(e[r])}`).join(",")}}`:JSON.stringify(e)??"null"}t(y,"canonicalJson");function V(e){const r={rev:e.rev,resolved_scopes:e.resolved_scopes,spend_cap:e.spend_cap,payer:e.payer,dependencies:e.dependencies??{},...e.inference_endpoint?{inference_endpoint:e.inference_endpoint}:{}};return S("sha256").update(y(r)).digest("base64url").slice(0,22)}t(V,"grantVersion");async function B(e){return!!await(await p()).prepare("SELECT 1 FROM grants WHERE id = ?").get(e)}t(B,"hasGrant");async function X(e){return await(await p()).prepare("SELECT rev, status FROM grants WHERE id = ?").get(e)??null}t(X,"grantSyncState");async function Y(e,r){return await(await p()).prepare("SELECT rev, status FROM grants WHERE grantee = ? AND sub = ?").get(e,r)??null}t(Y,"grantSyncStateFor");async function C(e){const r=await(await p()).prepare("SELECT * FROM grants WHERE id = ?").get(e);if(!r)return null;try{return l(r)}catch(n){if(n instanceof TypeError||n instanceof SyntaxError)return null;throw n}}t(C,"grantRecord");async function q(e){return(e?(await C(e))?.payer:null)??"user"}t(q,"payerOfGrant");async function z(e){return f(await(await p()).prepare("SELECT * FROM grants WHERE id = ?").get(e))}t(z,"activeGrant");async function K(e,r){return f(await(await p()).prepare("SELECT * FROM grants WHERE grantee = ? AND sub = ?").get(e,r))}t(K,"grantFor");async function Q(e){if(d({grantee:e}))return null;const r=await(await p()).prepare("SELECT * FROM grants WHERE grantee = ? AND status = 'active'").all(e);if(r.length>1)throw new Error(`multiple active grants for ${e}`);return f(r[0])}t(Q,"activeGrantForApp");async function Z(e){const r=await(await p()).prepare("SELECT run_notice_url AS url, sub, grantee FROM grants WHERE id = ? AND status = 'active'").get(e);return r&&r.url&&!d(r)?{url:r.url,sub:r.sub,grantee:r.grantee}:null}t(Z,"runNoticeTarget");async function ee(){const e=await(await p()).prepare("SELECT * FROM grants WHERE grantee = 'app:terminal' AND status = 'active'").all();if(e.length>1)throw new Error("multiple active terminal grants");return f(e[0])}t(ee,"terminalGrant");async function re(){return(await(await p()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value??null}t(re,"grantsSource");async function te(e){return(await(await p()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value===e}t(te,"hasSyncedGrants");async function ne(e){return(await e.prepare("SELECT value FROM runtime_state WHERE key = 'grants_source'").get())?.value!=="local"?0:(await e.prepare("DELETE FROM runtime_state WHERE key = 'grants_source'").run(),Number((await e.prepare("DELETE FROM grants").run()).changes))}t(ne,"dropLocalGrantMirror");function ae(e){if(e===void 0)return[];if(!Array.isArray(e))throw new TypeError("invalid grant sync response");return e.map(r=>{const{team:n,members:i}=r??{};if(typeof n!="string"||!/^team:\S+$/.test(n)||!Array.isArray(i)||i.some(o=>typeof o!="string"||!/^user:[^\s*]+$/.test(o)))throw new TypeError("invalid grant sync response");return{team:n,members:i}})}t(ae,"teamsOf");function k(e){return e.status!=="active"||e.spend_cap===null||e.payer==="app"||e.grantee==="app:terminal"?null:{payer:e.sub,grantee:e.grantee,usd_per_month:e.spend_cap}}t(k,"grantLimit");async function se(e,r,n,i=[]){const o=new Set;for(const s of e){if(o.has(s.id)||!s.id||!s.runtime||!s.sub||!s.grantee||!Number.isInteger(s.rev)||!["active","revoked","removed"].includes(s.status))throw new TypeError("invalid grant sync response");o.add(s.id),u(s.scopes),u(s.resolved_scopes)}await w(async s=>{await s.exec("DELETE FROM grants");const _=s.prepare(`INSERT INTO grants
2
2
  (id, runtime, sub, grantee, rev, scopes_json, resolved_scopes_json, spend_cap, expires_at,
3
3
  status, revoked_at, approved_at, payer, dependencies_json, run_notice_url, inference_endpoint_json, synced_at)
4
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);for(const s of e)await E.run(s.id,s.runtime,s.sub,s.grantee,s.rev,JSON.stringify(s.scopes),JSON.stringify(s.resolved_scopes),s.spend_cap,s.expires_at,s.status,s.revoked_at,s.approved_at,s.payer,JSON.stringify(y(s.dependencies)),s.run_notice_url??null,s.inference_endpoint?JSON.stringify(s.inference_endpoint):null,s.synced_at);await w(a,"directory",R,"member",i.flatMap(({team:s,members:_})=>_.map(g=>({resourceId:s,subject:x(g)})))),await a.prepare("INSERT INTO runtime_state (key, value) VALUES ('grants_source', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(n),await a.prepare("INSERT INTO runtime_state (key, value) VALUES ('grants_generation', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(runtime_state.value AS INTEGER) + 1 AS TEXT)").run()})}t(te,"replaceGrants");async function ne(){const e=await(await c()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_generation'").get();return e?Number(e.value):0}t(ne,"grantsGeneration");export{X as activeGrant,q as activeGrantForApp,F as activeRecord,L as allGrants,y as dependenciesOf,ee as dropLocalGrantMirror,j as grantExpired,Y as grantFor,M as grantRecord,P as grantSyncState,V as grantSyncStateFor,U as grantVersion,ne as grantsGeneration,Q as grantsSource,$ as hasGrant,Z as hasSyncedGrants,b as inferenceEndpointOf,I as localGrants,l as mirrorOnly,B as payerOfGrant,te as replaceGrants,z as runNoticeTarget,re as teamsOf,K as terminalGrant};
4
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`);for(const a of e)await _.run(a.id,a.runtime,a.sub,a.grantee,a.rev,JSON.stringify(a.scopes),JSON.stringify(a.resolved_scopes),a.spend_cap,a.expires_at,a.status,a.revoked_at,a.approved_at,a.payer,JSON.stringify(E(a.dependencies)),a.run_notice_url??null,a.inference_endpoint?JSON.stringify(a.inference_endpoint):null,a.synced_at);await s.prepare("DELETE FROM limits WHERE source = 'grant_sync'").run();for(const a of e){const c=k(a);c&&await A(a.id,[c],s)}await v(s,"directory",x,"member",i.flatMap(({team:a,members:c})=>c.map(g=>({resourceId:a,subject:R(g)})))),await s.prepare("INSERT INTO runtime_state (key, value) VALUES ('grants_source', ?) ON CONFLICT(key) DO UPDATE SET value = excluded.value").run(n),await s.prepare("INSERT INTO runtime_state (key, value) VALUES ('grants_generation', '1') ON CONFLICT(key) DO UPDATE SET value = CAST(CAST(runtime_state.value AS INTEGER) + 1 AS TEXT)").run()})}t(se,"replaceGrants");async function ie(){const e=await(await p()).prepare("SELECT value FROM runtime_state WHERE key = 'grants_generation'").get();return e?Number(e.value):0}t(ie,"grantsGeneration");export{z as activeGrant,Q as activeGrantForApp,j as activeRecord,F as allGrants,E as dependenciesOf,ne as dropLocalGrantMirror,M as grantExpired,K as grantFor,C as grantRecord,X as grantSyncState,Y as grantSyncStateFor,V as grantVersion,ie as grantsGeneration,re as grantsSource,B as hasGrant,te as hasSyncedGrants,b as inferenceEndpointOf,P as localGrants,d as mirrorOnly,q as payerOfGrant,se as replaceGrants,Z as runNoticeTarget,ae as teamsOf,ee as terminalGrant};
@@ -0,0 +1,12 @@
1
+ export declare const LEDGER_MIGRATED_KEY = "ledger_migrated";
2
+ /** The id of a migrated `usage` row `u`, as SQL: `legacy:<grantee>:<month>:<payer>:<source>:<served_model>`. */
3
+ export declare const LEGACY_ID_SQL = "'legacy:' || u.grantee || ':' || u.month || ':' || u.payer || ':' || u.source || ':' || u.served_model";
4
+ export interface BackfillResult {
5
+ migrated: boolean;
6
+ charges: number;
7
+ limits: number;
8
+ }
9
+ /** Copy `usage` into `charges` as legacy rows, rebuild `charge_totals` from them and fill `limits` from the grant mirror, then
10
+ * verify per `(grantee, month, payer, source, served_model)` that usd and token sums equal the `usage` row's, null kept. A
11
+ * second call sees the marker and does nothing. A failed verification rolls the whole thing back and throws. */
12
+ export declare function backfillLedger(person: string): Promise<BackfillResult>;
@@ -0,0 +1,22 @@
1
+ var s=Object.defineProperty;var u=(t,e)=>s(t,"name",{value:e,configurable:!0});import{withCanvasWrite as E}from"../canvas/db.js";import{rebuildTotals as o}from"./totals.js";const a="ledger_migrated",r="'legacy:' || u.grantee || ':' || u.month || ':' || u.payer || ':' || u.source || ':' || u.served_model";async function L(t){return await E(async e=>{if(await e.prepare("SELECT value FROM canvas_meta WHERE key = ?").get(a)!==void 0)return{migrated:!1,charges:0,limits:0};const n=await e.prepare(`INSERT INTO charges (id, request, at, month, kind, what, units, usd, payer, funding, sub, grantee, provider)
2
+ SELECT ${r},
3
+ NULL, u.month || '-01T00:00:00.000Z', u.month,
4
+ CASE WHEN u.source = 'model' THEN 'model' ELSE 'tool' END,
5
+ CASE WHEN u.source = 'model' THEN u.served_model ELSE substr(u.source, 10) END,
6
+ CASE WHEN u.source = 'model' THEN jsonb_build_object('input', u.input_tokens, 'output', u.output_tokens, 'cache_read', u.cached_tokens, 'cache_write', 0) END,
7
+ u.usd_equivalent,
8
+ CASE WHEN u.payer = 'app' THEN u.grantee ELSE ?::text END,
9
+ CASE WHEN u.payer = 'app' THEN 'endpoint' WHEN u.source = 'model' THEN 'legacy' ELSE 'bill' END,
10
+ ?::text, u.grantee,
11
+ CASE WHEN u.source = 'model' THEN NULL ELSE 'app:' || substr(u.source, 10) END
12
+ FROM usage u ON CONFLICT (id) DO NOTHING`).run(t,t);await o(e);const i=await c(e);return await d(e),await e.prepare("INSERT INTO canvas_meta (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value = excluded.value").run(a,new Date().toISOString()),{migrated:!0,charges:n.changes,limits:i}})}u(L,"backfillLedger");async function c(t){return(await t.prepare(`INSERT INTO limits (id, payer, funding, grantee, provider, sub, usd_per_month, source, updated_at)
13
+ SELECT 'grant:' || id, sub, NULL, grantee, NULL, NULL, spend_cap, 'grant_sync', ?
14
+ FROM grants WHERE status = 'active' AND spend_cap IS NOT NULL AND COALESCE(payer, 'user') <> 'app' AND grantee <> 'app:terminal'
15
+ ON CONFLICT (id) DO NOTHING`).run(new Date().toISOString())).changes}u(c,"backfillLimits");async function d(t){const e=await t.prepare(`SELECT u.grantee, u.month, u.payer, u.source, u.served_model FROM usage u
16
+ LEFT JOIN charges c ON c.id = ${r}
17
+ WHERE c.id IS NULL
18
+ OR c.usd IS DISTINCT FROM u.usd_equivalent::numeric(14,6)
19
+ OR (u.source = 'model' AND (COALESCE((c.units ->> 'input')::bigint, -1) <> u.input_tokens
20
+ OR COALESCE((c.units ->> 'output')::bigint, -1) <> u.output_tokens
21
+ OR COALESCE((c.units ->> 'cache_read')::bigint, -1) <> u.cached_tokens))
22
+ LIMIT 5`).all();if(e.length>0)throw new Error(`ledger backfill verification failed; first differing usage rows: ${JSON.stringify(e)}`)}u(d,"verify");export{a as LEDGER_MIGRATED_KEY,r as LEGACY_ID_SQL,L as backfillLedger};
@@ -0,0 +1,18 @@
1
+ import type { Charge, ChargeInput } from './types.js';
2
+ /** The id of a tool charge: the receipt-level idempotency key, so two providers' receipt ids never collide. */
3
+ export declare const receiptChargeId: (provider: string, receiptId: string) => string;
4
+ export interface WriteResult {
5
+ /** False when `id` already existed: a repeat delivery adds nothing, to the row or the rollup. */
6
+ written: boolean;
7
+ id: string;
8
+ /** The id of the take row written beside it, when `ledger.take_pct` is above 0. */
9
+ takeId: string | null;
10
+ }
11
+ /** The take percentage of `ledger.take_pct`; 0 (the default) writes no take rows. */
12
+ export declare function takePct(): number;
13
+ /** Write one charge row and, in the same transaction, add its usd to its `charge_totals` row. 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
15
+ * caller's `withCanvasWrite` it joins that transaction. */
16
+ export declare function writeCharge(input: ChargeInput): Promise<WriteResult>;
17
+ /** A stored row as the `Charge` shape (jsonb arrives as text). */
18
+ export declare function chargeOf(row: Record<string, unknown>): Charge;
@@ -0,0 +1 @@
1
+ var k=Object.defineProperty;var t=(e,r)=>k(e,"name",{value:r,configurable:!0});import{randomUUID as w}from"node:crypto";import{withCanvasWrite as I}from"../canvas/db.js";import{readConfig as y}from"../config.js";import{usageMonth as S}from"./months.js";import{addToTotals as C}from"./totals.js";const x=t((e,r)=>`receipt:${e}:${r}`,"receiptChargeId"),f="id, request, at, month, kind, what, units, usd, payer, funding, sub, grantee, node, run, provider, caused_by, vendor_ref, refused",c="?, ?, ?, ?, ?, ?, ?::jsonb, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?::jsonb";function T(){const e=y("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}t(T,"takePct");async function R(e){const r=e.at??new Date,n=e.refused??null,s=e.id??w(),d=S(r),o=n?0:e.usd,u=T();return await I(async a=>{const i=t((g,m,N,O,h)=>[g,e.request??null,r.toISOString(),d,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,n===null?null:JSON.stringify(n)],"row");if((await a.prepare(`INSERT INTO charges (${f}) VALUES (${c}) ON CONFLICT (id) DO NOTHING`).run(...i(s,e.kind,e.what,o,e.funding))).changes===0)return{written:!1,id:s,takeId:null};n===null&&e.kind!=="take"&&await C(a,{month:d,payer:e.payer,funding:e.funding,sub:e.sub??null,grantee:e.grantee,provider:e.provider??null,usd:o});let l=null;return u>0&&n===null&&(e.kind==="model"||e.kind==="tool")&&o!==null&&o>0&&(l=`take:${s}`,await a.prepare(`INSERT INTO charges (${f}) VALUES (${c}) ON CONFLICT (id) DO NOTHING`).run(...i(l,"take",e.what,o*u/100,"take"))),{written:!0,id:s,takeId:l}})}t(R,"writeCharge");function _(e){const r=t(n=>typeof n=="string"?JSON.parse(n):null,"json");return{...e,units:r(e.units),refused:r(e.refused)}}t(_,"chargeOf");export{_ as chargeOf,x as receiptChargeId,T as takePct,R as writeCharge};
@@ -0,0 +1,44 @@
1
+ import { RunError } from '../runs/operations.js';
2
+ import type { Funding, Limit } from './types.js';
3
+ /** A model or tool call, checked before it is sent: everything about it is known. A limit matches when its `payer` equals
4
+ * the candidate's and each of its non-null filters equals the candidate's value. */
5
+ export interface CallCandidate {
6
+ scope: 'call';
7
+ payer: string;
8
+ funding: Funding;
9
+ sub: string | null;
10
+ grantee: string;
11
+ provider: string | null;
12
+ }
13
+ /** A node birth, `run.start`, cron fire, job start or dormant revive. `payer` is whoever pays the node's model calls. A limit
14
+ * matches only when its `funding` and `provider` are both null and its other filters match: only a limit that would refuse
15
+ * every charge of that payer (a $0 stop, or app Y's cap on the person) can stop a start. */
16
+ export interface StartCandidate {
17
+ scope: 'start';
18
+ payer: string;
19
+ sub: string | null;
20
+ grantee: string;
21
+ }
22
+ export type Candidate = CallCandidate | StartCandidate;
23
+ /** The limit that refused and what had been spent against it. */
24
+ export interface Refusal {
25
+ limit: Limit;
26
+ usd: number;
27
+ month: string;
28
+ reset_at: string;
29
+ }
30
+ /** The first limit (by id) that refuses `candidate` this month, or null. One statement. */
31
+ export declare function findRefusal(candidate: Candidate, at?: Date): Promise<Refusal | null>;
32
+ /** The `user_action` a person can take for a limit of this source; absent for an operator limit. */
33
+ export declare function userActionOf(source: Limit['source']): 'raise_cap' | 'clear_limit' | undefined;
34
+ /** `402 cap_exhausted` for a refusal. Details: `limit` (id, payer, filters, usd_per_month, source), `usd`, `reset_at`, and
35
+ * `user_action` when the person can act; plus the deprecated `spend_cap` (= `limit.usd_per_month`) and `usd_equivalent` (= `usd`)
36
+ * that the refusal carried before the ledger, kept so a caller that reads them still gets the numbers. */
37
+ export declare function refusalError(refusal: Refusal): RunError;
38
+ /** Throw `refusalError` when a limit refuses `candidate`. */
39
+ export declare function check(candidate: Candidate, at?: Date): Promise<void>;
40
+ /** The mark stored on the refused row a site writes before it moves to the next candidate. */
41
+ export declare const refusedMark: (refusal: Refusal) => {
42
+ limit: string;
43
+ reason: string;
44
+ };
@@ -0,0 +1,7 @@
1
+ var a=Object.defineProperty;var n=(e,r)=>a(e,"name",{value:r,configurable:!0});import{openDb as d}from"../canvas/db.js";import{RunError as c}from"../runs/operations.js";import{resetAt as L,usageMonth as m}from"./months.js";const g=`(SELECT COALESCE(SUM(t.usd), 0) FROM charge_totals t WHERE t.month = ? AND t.payer = l.payer
2
+ AND (l.funding IS NULL OR t.funding = l.funding) AND (l.grantee IS NULL OR t.grantee = l.grantee)
3
+ AND (l.provider IS NULL OR t.provider = l.provider) AND (l.sub IS NULL OR t.sub = l.sub))`;async function N(e,r=new Date){const t=m(r),s=e.sub??"",o=e.scope==="call"?`l.payer = ? AND (l.funding IS NULL OR l.funding = ?) AND (l.grantee IS NULL OR l.grantee = ?)
4
+ AND (l.provider IS NULL OR l.provider = ?) AND (l.sub IS NULL OR l.sub = ?)`:"l.payer = ? AND l.funding IS NULL AND l.provider IS NULL AND (l.grantee IS NULL OR l.grantee = ?) AND (l.sub IS NULL OR l.sub = ?)",u=e.scope==="call"?[e.payer,e.funding,e.grantee,e.provider??"",s]:[e.payer,e.grantee,s],i=await(await d()).prepareUncached(`SELECT * FROM (
5
+ SELECT l.id, l.payer, l.funding, l.grantee, l.provider, l.sub, l.usd_per_month, l.source, l.updated_at, ${g} AS spent
6
+ FROM limits l WHERE ${o}) q
7
+ WHERE q.spent >= q.usd_per_month ORDER BY q.id LIMIT 1`).get(t,...u);if(i===void 0)return null;const{spent:l,...p}=i;return{limit:p,usd:l,month:t,reset_at:L(t)}}n(N,"findRefusal");function f(e){return e==="grant_sync"?"raise_cap":e==="person"?"clear_limit":void 0}n(f,"userActionOf");function _(e){const{limit:r}=e,t=f(r.source);return new c(402,"cap_exhausted",`${r.payer} reached its monthly limit (${r.id})`,void 0,{limit:{id:r.id,payer:r.payer,filters:{funding:r.funding,grantee:r.grantee,provider:r.provider,sub:r.sub},usd_per_month:r.usd_per_month,source:r.source},usd:e.usd,reset_at:e.reset_at,spend_cap:r.usd_per_month,usd_equivalent:e.usd,...t===void 0?{}:{user_action:t}})}n(_,"refusalError");async function E(e,r){const t=await N(e,r);if(t!==null)throw _(t)}n(E,"check");const D=n(e=>({limit:e.limit.id,reason:`${e.limit.payer} reached ${e.limit.usd_per_month} USD this month (spent ${e.usd})`}),"refusedMark");export{E as check,N as findRefusal,_ as refusalError,D as refusedMark,f as userActionOf};
@@ -0,0 +1,9 @@
1
+ export * from './types.js';
2
+ export * from './months.js';
3
+ export { payerOf, type Payer, type PayerCall } from './payer.js';
4
+ export { writeCharge, receiptChargeId, takePct, chargeOf, type WriteResult } from './charges.js';
5
+ export { check, findRefusal, refusalError, refusedMark, userActionOf, type Candidate, type CallCandidate, type StartCandidate, type Refusal } from './check.js';
6
+ export { upsertLimit, deleteLimit, replaceGrantLimits, listLimits, grantLimitId } from './limits.js';
7
+ export { usageFor, listCharges, monthTotals, assertReference, type ChargePage, type ListFilter, type MonthTotals } from './read.js';
8
+ export { backfillLedger, LEDGER_MIGRATED_KEY, type BackfillResult } from './backfill.js';
9
+ export { rebuildTotals, rebuildTotalsFor } from './totals.js';
@@ -0,0 +1 @@
1
+ export*from"./types.js";export*from"./months.js";import{payerOf as i}from"./payer.js";import{writeCharge as f,receiptChargeId as s,takePct as m,chargeOf as l}from"./charges.js";import{check as c,findRefusal as x,refusalError as d,refusedMark as u,userActionOf as g}from"./check.js";import{upsertLimit as h,deleteLimit as n,replaceGrantLimits as E,listLimits as k,grantLimitId as R}from"./limits.js";import{usageFor as b,listCharges as C,monthTotals as G,assertReference as I}from"./read.js";import{backfillLedger as A,LEDGER_MIGRATED_KEY as D}from"./backfill.js";import{rebuildTotals as M,rebuildTotalsFor as _}from"./totals.js";export{D as LEDGER_MIGRATED_KEY,I as assertReference,A as backfillLedger,l as chargeOf,c as check,n as deleteLimit,x as findRefusal,R as grantLimitId,C as listCharges,k as listLimits,G as monthTotals,i as payerOf,M as rebuildTotals,_ as rebuildTotalsFor,s as receiptChargeId,d as refusalError,u as refusedMark,E as replaceGrantLimits,m as takePct,h as upsertLimit,b as usageFor,g as userActionOf,f as writeCharge};
@@ -0,0 +1,15 @@
1
+ import { type CanvasDb } from '../canvas/db.js';
2
+ import type { Limit, LimitInput, LimitSource } from './types.js';
3
+ export declare const grantLimitId: (grantId: string) => string;
4
+ /** Insert or replace one limit row by id. */
5
+ export declare function upsertLimit(input: LimitInput): Promise<Limit>;
6
+ /** Delete one limit row; true when it existed. */
7
+ export declare function deleteLimit(id: string): Promise<boolean>;
8
+ /** A grant's limit rows in one transaction: delete every `grant_sync` row of this grant, then write `rows`. The first row's id
9
+ * is `grant:<grant id>`, later ones `grant:<grant id>#<n>`. Only `grant_sync` rows are touched. */
10
+ export declare function replaceGrantLimits(grantId: string, rows: ReadonlyArray<Omit<LimitInput, 'id' | 'source'>>, db?: CanvasDb): Promise<void>;
11
+ /** Limit rows, optionally of one source or one payer, in id order. */
12
+ export declare function listLimits(filter?: {
13
+ source?: LimitSource;
14
+ payer?: string;
15
+ }): Promise<Limit[]>;
@@ -0,0 +1,3 @@
1
+ var l=Object.defineProperty;var a=(n,e)=>l(n,"name",{value:e,configurable:!0});import{openDb as m,withCanvasWrite as i}from"../canvas/db.js";const u=a(n=>`grant:${n}`,"grantLimitId"),s="id, payer, funding, grantee, provider, sub, usd_per_month, source, updated_at";async function E(n){if(!Number.isFinite(n.usd_per_month)||n.usd_per_month<0)throw new RangeError("usd_per_month must be a number of at least 0");const e={funding:null,grantee:null,provider:null,sub:null,...n,updated_at:new Date().toISOString()};return await i(async r=>{await r.prepare(`INSERT INTO limits (${s}) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
2
+ ON CONFLICT (id) DO UPDATE SET payer = excluded.payer, funding = excluded.funding, grantee = excluded.grantee,
3
+ provider = excluded.provider, sub = excluded.sub, usd_per_month = excluded.usd_per_month, source = excluded.source, updated_at = excluded.updated_at`).run(e.id,e.payer,e.funding,e.grantee,e.provider,e.sub,e.usd_per_month,e.source,e.updated_at)}),e}a(E,"upsertLimit");async function w(n){return await i(async e=>(await e.prepare("DELETE FROM limits WHERE id = ?").run(n)).changes>0)}a(w,"deleteLimit");async function f(n,e,r){const d=a(async o=>{await o.prepare("DELETE FROM limits WHERE source = 'grant_sync' AND (id = ? OR starts_with(id, ?))").run(u(n),`${u(n)}#`);const c=new Date().toISOString();for(const[p,t]of e.entries()){if(!Number.isFinite(t.usd_per_month)||t.usd_per_month<0)throw new RangeError("usd_per_month must be a number of at least 0");await o.prepare(`INSERT INTO limits (${s}) VALUES (?, ?, ?, ?, ?, ?, ?, 'grant_sync', ?)`).run(p===0?u(n):`${u(n)}#${p}`,t.payer,t.funding??null,t.grantee??null,t.provider??null,t.sub??null,t.usd_per_month,c)}},"body");r!==void 0&&r.inTransaction?await d(r):await i(d)}a(f,"replaceGrantLimits");async function g(n={}){const e=[],r=[];return n.source!==void 0&&(e.push("source = ?"),r.push(n.source)),n.payer!==void 0&&(e.push("payer = ?"),r.push(n.payer)),await(await m()).prepareUncached(`SELECT ${s} FROM limits${e.length?` WHERE ${e.join(" AND ")}`:""} ORDER BY id`).all(...r)}a(g,"listLimits");export{w as deleteLimit,u as grantLimitId,g as listLimits,f as replaceGrantLimits,E as upsertLimit};
@@ -0,0 +1,6 @@
1
+ /** The UTC calendar month `YYYY-MM` of `at`. */
2
+ export declare function usageMonth(at?: Date): string;
3
+ /** 00:00Z on the first day of the UTC month after `month`. */
4
+ export declare function resetAt(month: string): string;
5
+ /** A `month` query value: `YYYY-MM` with a real month, years 1000-9999; absent means the current month. */
6
+ export declare function parseUsageMonth(value: string | null): string;
@@ -0,0 +1 @@
1
+ var o=Object.defineProperty;var n=(t,r)=>o(t,"name",{value:r,configurable:!0});import{RunError as i}from"../runs/operations.js";function u(t=new Date){return t.toISOString().slice(0,7)}n(u,"usageMonth");function a(t){const[r,e]=t.split("-").map(Number);return new Date(Date.UTC(r,e,1)).toISOString()}n(a,"resetAt");function p(t){if(t===null)return u();if(!/^[1-9]\d{3}-(0[1-9]|1[0-2])$/.test(t))throw new i(400,"invalid_request","month must be YYYY-MM","month");return t}n(p,"parseUsageMonth");export{p as parseUsageMonth,a as resetAt,u as usageMonth};
@@ -0,0 +1,34 @@
1
+ import type { Funding } from './types.js';
2
+ export interface Payer {
3
+ payer: string;
4
+ funding: Funding;
5
+ }
6
+ export type PayerCall =
7
+ /** A model call on an app-paid grant: the app's inference endpoint pays. `grantee` is the app principal (`app:<id>`). */
8
+ {
9
+ call: 'model';
10
+ route: 'endpoint';
11
+ grantee: string;
12
+ }
13
+ /** A model call on a managed subscription account from the pool, or on an API key (incl. a managed provider reached
14
+ * through a key, which is `key`, never `subscription`). `sub` is the person the work is for. */
15
+ | {
16
+ call: 'model';
17
+ route: 'subscription' | 'key';
18
+ sub: string;
19
+ }
20
+ /** A tool call over HTTP: the outbound token's `payer` claim (`app:<id>` when the app pays, else the person). */
21
+ | {
22
+ call: 'tool';
23
+ tokenPayer: string;
24
+ sub: string;
25
+ }
26
+ /** The payer of a node's model calls, for the start check: `app:<grantee>` when the grant is app-paid, else the person. */
27
+ | {
28
+ call: 'start';
29
+ grantPayer: 'user' | 'app' | null | undefined;
30
+ sub: string;
31
+ grantee: string;
32
+ };
33
+ /** Who pays, and with what funding. A device call is not priced and never asks. */
34
+ export declare function payerOf(call: PayerCall): Payer;
@@ -0,0 +1 @@
1
+ var t=Object.defineProperty;var n=(e,r)=>t(e,"name",{value:r,configurable:!0});function i(e){switch(e.call){case"model":return e.route==="endpoint"?{payer:e.grantee,funding:"endpoint"}:{payer:e.sub,funding:e.route};case"tool":return e.tokenPayer.startsWith("app:")?{payer:e.tokenPayer,funding:"endpoint"}:{payer:e.sub,funding:"bill"};case"start":return e.grantPayer==="app"?{payer:e.grantee,funding:"endpoint"}:{payer:e.sub,funding:"bill"}}}n(i,"payerOf");export{i as payerOf};
@@ -0,0 +1,44 @@
1
+ import type { SpendUsageDTO } from '../../api/dto/usage.js';
2
+ import type { Charge, Limit } from './types.js';
3
+ /** `usage.get` for one grantee and month, computed from `charges`. `by_model` and `app_paid.by_model` group the `model` rows
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
+ * `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>;
8
+ export interface ListFilter {
9
+ /** Only rows of this month. */
10
+ month?: string;
11
+ /** Only rows of this grantee (an app caller sees its own). */
12
+ grantee?: string;
13
+ /** A reference: `message:<id>`, `request:<id>`, `cron_run:<id>`, `run:<id>`, or a charge id. */
14
+ under?: string;
15
+ /** Page size, 1 to 10,000; default 1,000. */
16
+ limit?: number;
17
+ /** The `next` of the previous page: continue after that page's last row. */
18
+ after?: string;
19
+ }
20
+ /** One page of rows in `(at, id)` order. `next` continues after the last row; null when this page is the last. */
21
+ export interface ChargePage {
22
+ charges: Charge[];
23
+ next: string | null;
24
+ }
25
+ /** A page of rows in `(at, id)` order, newest last: the rows under a reference when `under` is set (every month unless `month`
26
+ * is given), otherwise the month's rows (default the current month). */
27
+ export declare function listCharges(filter?: ListFilter): Promise<ChargePage>;
28
+ export interface MonthTotals {
29
+ month: string;
30
+ by_payer: Array<{
31
+ payer: string;
32
+ usd: number;
33
+ }>;
34
+ by_funding: Array<{
35
+ payer: string;
36
+ funding: string;
37
+ usd: number;
38
+ }>;
39
+ limits: Limit[];
40
+ }
41
+ /** One month's dollars per payer and per payer and funding, from the rollup, with the limit rows. Optionally for one payer. */
42
+ export declare function monthTotals(month?: string, payer?: string): Promise<MonthTotals>;
43
+ /** A reference's shape, or a 400. */
44
+ export declare function assertReference(value: string): string;