@north-light/cli 0.3.565 → 0.3.566
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/clients/attach/viewer.js +346 -346
- package/dist/core/environments.d.ts +11 -0
- package/dist/core/environments.js +1 -1
- package/dist/core/profiles/manifest.d.ts +9 -0
- package/dist/core/profiles/manifest.js +1 -1
- package/dist/core/secrets/blaxel-register.d.ts +5 -1
- package/dist/core/secrets/blaxel-register.js +1 -1
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +13 -13
|
@@ -1,9 +1,20 @@
|
|
|
1
|
+
import type { CanvasDb } from './canvas/db.js';
|
|
1
2
|
/** Find the `environments` row for `installId`, or create its object and row; either way set `last_seen_at`, and cache
|
|
2
3
|
* the environment id and install id for {@link machineEnvironmentId} and {@link touchMachineEnvironment}. */
|
|
3
4
|
export declare function registerMachineEnvironment(installId: string): Promise<{
|
|
4
5
|
environmentId: string;
|
|
5
6
|
created: boolean;
|
|
6
7
|
}>;
|
|
8
|
+
/** A cloud profile's own `blaxel` environment (D105): an `environment` object owned by the profile's object, acting for `person`, and
|
|
9
|
+
* its `environments` row with the default `memory_mb` (2048), no recipe (so the Blaxel base image) and no machine id. Runs in the
|
|
10
|
+
* caller's write, before the profile row exists, so the profile and its environment commit together. The profile object is made
|
|
11
|
+
* here under `grantee`'s app, with the profile id as its handle: a cloud role serves many people and a profile name is not unique
|
|
12
|
+
* across them, while the handle is unique per object type. Returns the environment's object id. */
|
|
13
|
+
export declare function createProfileBlaxelEnvironment(db: CanvasDb, spec: {
|
|
14
|
+
profileId: string;
|
|
15
|
+
grantee: string;
|
|
16
|
+
person: string;
|
|
17
|
+
}): Promise<string>;
|
|
7
18
|
/** Stamp `last_seen_at` on the machine environment: one statement, no read. A no-op before {@link registerMachineEnvironment}. */
|
|
8
19
|
export declare function touchMachineEnvironment(now: string): Promise<void>;
|
|
9
20
|
/** The machine environment's object id; throws before {@link registerMachineEnvironment} has run. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var u=Object.defineProperty;var a=(e,n)=>u(e,"name",{value:n,configurable:!0});import{openDb as d,withCanvasWrite as c}from"./canvas/db.js";import{createObject as p,ensureOwner as f,ownerForGrantee as s}from"./graph/objects.js";import{writeActsFor as w}from"./permissions/facts.js";import{isCloudRole as v,personIdAsync as E}from"./canvas/person.js";import{setCloudProfileWriter as h}from"./profiles/manifest.js";let o=null;async function j(e){const n=new Date().toISOString(),r=await c(async t=>{const i=await t.prepare("SELECT object FROM environments WHERE machine_id = ?").get(e);if(i!==void 0)return await t.prepare("UPDATE environments SET last_seen_at = ? WHERE machine_id = ?").run(n,e),{environmentId:i.object,created:!1};const l=await s(t,"app:terminal"),m=await p(t,{type:"environment",kind:"machine",owner:l.id,privacy:"public",name:e,preview:"machine"});return v()||await w(t,m.id,await E()),await t.prepare("INSERT INTO environments (object, kind, machine_id, last_seen_at) VALUES (?, 'machine', ?, ?)").run(m.id,e,n),{environmentId:m.id,created:!0}});return o={environmentId:r.environmentId,machineId:e},r}a(j,"registerMachineEnvironment");async function I(e,n){const r=await s(e,n.grantee),t=await f(e,"profile","profile",n.profileId,n.profileId,r),i=await p(e,{type:"environment",kind:"blaxel",owner:t.id,privacy:"private",name:n.profileId,preview:"blaxel"});return await w(e,i.id,n.person),await e.prepare("INSERT INTO environments (object, kind, machine_id, memory_mb, last_seen_at) VALUES (?, 'blaxel', NULL, 2048, ?)").run(i.id,new Date().toISOString()),i.id}a(I,"createProfileBlaxelEnvironment");async function O(e){o!==null&&await(await d()).prepare("UPDATE environments SET last_seen_at = ? WHERE machine_id = ?").run(e,o.machineId)}a(O,"touchMachineEnvironment");function R(){if(o===null)throw new Error("the machine environment is not registered yet");return o.environmentId}a(R,"machineEnvironmentId"),h((e,n)=>c(async r=>{await n(await I(r,e))}));export{I as createProfileBlaxelEnvironment,R as machineEnvironmentId,j as registerMachineEnvironment,O as touchMachineEnvironment};
|
|
@@ -55,6 +55,15 @@ export declare function resolveProfileOperand(operand: string): ProfileEntry;
|
|
|
55
55
|
/** `resolveProfileOperand` over any candidate list — the CLI passes the
|
|
56
56
|
* profiles the daemon lists for its caller. */
|
|
57
57
|
export declare function matchProfileOperand<T>(all: readonly T[], operand: string, key: CandidateOf<T>): T;
|
|
58
|
+
/** On a cloud role (D105) a new profile gets its own Blaxel environment, created in the same canvas write as the profile row. This file
|
|
59
|
+
* cannot open the database (V-1), so `environments.ts` registers the writer when the daemon loads it: it runs `insert` inside one write
|
|
60
|
+
* after making the environment, and `insert` receives the environment's object id. */
|
|
61
|
+
export type CloudProfileWriter = (spec: {
|
|
62
|
+
profileId: string;
|
|
63
|
+
grantee: string;
|
|
64
|
+
person: string;
|
|
65
|
+
}, insert: (environmentId: string) => Promise<void>) => Promise<void>;
|
|
66
|
+
export declare function setCloudProfileWriter(writer: CloudProfileWriter): void;
|
|
58
67
|
export declare function ensureRootProfile(): Promise<ProfileEntry>;
|
|
59
68
|
/** The env name a metadata entry surfaces as (spawn-env source F). Also the
|
|
60
69
|
* uniqueness domain for keys: the fold is lossy (`a-b`, `a_b`, `A_B` →
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var F=Object.defineProperty;var o=(e,t)=>F(e,"name",{value:t,configurable:!0});import{randomUUID as K}from"node:crypto";import{existsSync as q,realpathSync as b,rmSync as A,statSync as H}from"node:fs";import{resolve as E}from"node:path";import{SCHEMA_VERSION as S}from"../../types.js";import{profileSpace as v}from"../layout.js";import{PROFILE_PROJECT_MEMORY_VALUES as O}from"../../api/dto/profiles.js";import{ensureDir as Z,nowIso as m}from"../fs-utils.js";import{cachedPausedProfileIds as V,cachedProfilePaused as Y,getCachedProfile as d,listCachedProfiles as G,writeSettings as h}from"../scoped-state/person-store.js";import{usage as f,notFound as u,ambiguous as R,general as W}from"../errors.js";import{fuzzyMatchProfile as B}from"./fuzzy-match.js";const x=/^[a-z0-9]+(?:-[a-z0-9]+)*-[0-9a-f]{8}$/,p="root-00000000",Q="root";function w(e){return e.grantee??"app:terminal"}o(w,"profileApp");function g(e,t){Z(v(w(t),e).dir)}o(g,"scaffoldProfile");function P(e){if(!x.test(e))throw f(`invalid profile id: ${e}`,{received:e,next:"Profile ids look like <slug>-<8-hex-chars> (as printed by `crtr profile new`). Pass an exact id or a profile name, not a raw path."})}o(P,"assertSafeProfileId");function we(e){P(e);const t=d(e);if(t===null)throw u(`profile not found: ${e}`,{received:e});return v(w(t.manifest),e).dir}o(we,"profileDir");function M(e){if(!Array.isArray(e))throw f("projects must be an array of {path} objects",{received:typeof e,field:"projects",next:'Pass entries like {"path": "/abs/dir"}.'});for(const t of e){if(typeof t!="object"||t===null||Array.isArray(t))throw f(`invalid project entry: ${JSON.stringify(t)}`,{field:"projects",next:'Every project is an object like {"path": "/abs/dir"}.'});const{path:r}=t;if(typeof r!="string"||r.trim()==="")throw f(`project entry has no path: ${JSON.stringify(t)}`,{field:"projects",next:"Give every project entry a non-empty directory path."})}}o(M,"assertProfileProjects");function X(e){const t=O.join(", ");if(typeof e!="object"||e===null||Array.isArray(e))throw f("delivery_limits must be an object mapping owner handles to a level",{received:typeof e,field:"delivery_limits",next:`Pass e.g. {"crouter": "preview"} \u2014 each level is one of: ${t}.`});for(const[r,n]of Object.entries(e)){if(r.trim()==="")throw f("delivery_limits has an empty owner handle",{field:"delivery_limits",next:"Key each limit by an owner handle such as a repo handle or `user`, or exactly by `<type>:<handle>` such as `repo:crouter`."});if(typeof n!="string"||!O.includes(n))throw f(`invalid delivery limit for ${r}: ${JSON.stringify(n)}`,{received:String(n),field:"delivery_limits",next:`Set ${r} to one of: ${t}.`})}}o(X,"assertDeliveryLimits");function I(){return{entries:G(),unreadableIds:[]}}o(I,"enumerateProfiles");function y(){return I().entries}o(y,"listProfiles");function ee(e){const t=y(),r=C(t,e,$);return r?{kind:"resolved",entry:r}:{kind:"absent"}}o(ee,"lookupProfile");function k(e){P(e);const t=d(e);if(t!==null)return t;throw u(`profile not found: ${e}`,{received:e,next:"Run `crtr profile list` to see available profiles."})}o(k,"loadExactProfileManifest");const $=o(e=>({profileId:e.profileId,name:e.manifest.name}),"entryCandidate");function C(e,t,r){if(x.test(t)){const i=e.find(a=>r(a).profileId===t);if(i!==void 0)return i}const n=e.filter(i=>r(i).name===t);if(n.length===1)return n[0];if(n.length>1){const i=n.map(a=>r(a).profileId);throw R(`profile name "${t}" matches ${i.length} profiles: ${i.join(", ")}`,{received:t,candidates:i,next:`Re-run with one exact profile id: ${i.join(", ")}.`})}return null}o(C,"findExactProfile");function D(e,t,r){const n=e.map(i=>`${r(i).profileId} (${r(i).name})`);return u(`profile not found: ${t}`,{received:t,available:n,next:n.length>0?`Re-run with one of the profiles on this host: ${n.join(", ")} \u2014 or create one with \`crtr profile new --name <name>\`.`:"No profiles exist on this host yet \u2014 create one with `crtr profile new --name <name>`."})}o(D,"profileNotFound");function te(e){const t=ee(e);if(t.kind==="resolved")return t.entry;throw D(y(),e,$)}o(te,"loadProfileManifest");function ge(e){return re(y(),e,$)}o(ge,"resolveProfileOperand");function re(e,t,r){const n=C(e,t,r);if(n!==null)return n;const{match:i,tied:a}=B(t,e.map(r));if(i!==null){const s=e.find(c=>r(c).profileId===i.profileId);return console.error(`crtr: no profile named "${t}" \u2014 using the closest match: ${r(s).name} (${r(s).profileId})`),s}if(a.length>1){const s=a.map(c=>`${c.profileId} (${c.name})`);throw R(`"${t}" is equally close to ${a.length} profiles: ${s.join(", ")}`,{received:t,candidates:a.map(c=>c.profileId),next:`Re-run with one exact profile id or name: ${s.join(", ")}.`})}throw D(e,t,r)}o(re,"matchProfileOperand");function L(e){const t=E(e);if(!q(t)||!H(t).isDirectory())throw f(`project directory does not exist or is not a directory: ${e}`,{received:e,next:"Pass an existing directory path."});try{return b(t)}catch{return t}}o(L,"resolveExistingProjectDir");function N(e){const t=new Set,r=[];for(const n of e)t.has(n.path)||(t.add(n.path),r.push(n));return r}o(N,"dedupeProjects");function ne(e){let t=e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"");return t.length>40&&(t=t.slice(0,40).replace(/-+$/g,"")),t.length>0?t:"profile"}o(ne,"slugify");function oe(e){const t=ne(e);for(let r=0;r<20;r++){const n=K().replace(/-/g,"").slice(0,8),i=`${t}-${n}`;if(d(i)===null)return i}throw W(`failed to generate a unique profile id for "${e}" after 20 attempts`)}o(oe,"generateProfileId");async function Pe(){const e=d(p);if(e!==null)return g(p,e.manifest),e;const t={schema_version:S,name:Q,projects:[],grantee:"app:terminal",paused_at:null,created_at:m(),last_used_at:m()};return g(p,t),await h(r=>r.profiles.some(n=>n.profileId===p)?[]:[{op:"profile.insert",profile:{profileId:p,manifest:t}}]),d(p)}o(Pe,"ensureRootProfile");const z=/^[A-Za-z0-9][A-Za-z0-9_-]*$/;function J(e){return`CRTR_PROFILE_META_${e.toUpperCase().replace(/[^A-Z0-9]+/g,"_")}`}o(J,"metadataEnvName");function _(e){if(typeof e!="object"||e===null||Array.isArray(e))throw f("metadata must be a map of string keys to string values",{field:"metadata",next:'Pass an object, e.g. {"role": "support"}.'});const t=new Map;for(const[r,n]of Object.entries(e)){if(!z.test(r))throw f(`invalid metadata key: ${JSON.stringify(r)}`,{received:r,field:"metadata",next:"Keys start with a letter or digit, followed by letters, digits, `_`, or `-`."});if(typeof n!="string")throw f(`metadata value for ${r} must be a string`,{received:typeof n,field:"metadata",next:"Every metadata value is an env-var value \u2014 pass strings only."});if(n.includes("\0"))throw f(`metadata value for ${r} contains a NUL byte, which no OS accepts in an environment variable`,{field:"metadata",next:"Remove NUL bytes from the value and retry."});const i=J(r),a=t.get(i);if(a!==void 0)throw f(`metadata keys ${JSON.stringify(a)} and ${JSON.stringify(r)} both surface as ${i}`,{field:"metadata",next:"Rename or drop one \u2014 keys must stay distinct after uppercasing and `_`-folding."});t.set(i,r)}}o(_,"assertProfileMetadata");function T(e){if(typeof e!="object"||e===null||Array.isArray(e))return{};const t={},r=new Set;for(const[n,i]of Object.entries(e)){if(typeof i!="string"||!z.test(n)||i.includes("\0"))continue;const a=J(n);r.has(a)||(r.add(a),t[n]=i)}return t}o(T,"sanitizeProfileMetadata");async function ie(e,t=[],r={}){const n=e.trim();if(n==="")throw f("profile name must not be empty");r.metadata!==void 0&&_(r.metadata),M(t);const i=N(t.map(c=>({path:L(c.path)}))),a=oe(n),s={schema_version:S,name:n,projects:i,paused_at:null,...r.grantee===void 0?{}:{grantee:r.grantee},...r.defaultKind!==void 0?{default_kind:r.defaultKind}:{},...r.metadata!==void 0&&Object.keys(r.metadata).length>0?{metadata:{...r.metadata}}:{},created_at:m(),last_used_at:null};return g(a,s),await h(()=>[{op:"profile.insert",profile:{profileId:a,manifest:s}}]),{profileId:a,manifest:s}}o(ie,"createProfile");async function $e(e){if(!e.startsWith("app:")||e.length<=4)throw f("an app grantee is required");const t=`app-${e.slice(4)}`,r=y().filter(n=>n.manifest.name===t&&n.manifest.grantee===e);if(r.length>1)throw new Error(`duplicate default profile for ${e}`);return r[0]?r[0]:await ie(t,[],{grantee:e})}o($e,"ensureAppProfile");function U(e){if(P(e),d(e)===null)throw u(`profile not found: ${e}`,{received:e,next:"Run `crtr profile list` to see available profiles."})}o(U,"requireExistingProfile");async function l(e,t){U(e);let r;return await h(n=>{const i=n.profiles.find(a=>a.profileId===e);if(i===void 0)throw u(`profile not found: ${e}`,{received:e});return r={profileId:e,manifest:t(structuredClone(i.manifest))},[{op:"profile.update",profile:r}]}),r}o(l,"mutateManifest");async function _e(e){return await l(e,t=>({...t,last_used_at:m()}))}o(_e,"updateProfileLastUsed");function ae(e){if(e==null||e==="")return null;try{return te(e)}catch{return null}}o(ae,"loadProfileManifestOrNull");function je(e){return e==null||e===""||!x.test(e)?!1:Y(e)}o(je,"isProfilePaused");function be(){return V()}o(be,"pausedCanvasProfiles");function se(e){if(e.manifest.paused_at!==null)throw f(`profile "${e.manifest.name}" is paused and cannot start nodes`,{received:e.profileId,next:`Resume it with \`crtr profile resume ${e.profileId}\`.`})}o(se,"assertProfileEntryActive");function Ae(e){const t=ae(e);return t===null?null:(se(t),t)}o(Ae,"assertProfileActive");async function Ee(e){return await l(e,t=>({...t,paused_at:t.paused_at??m()}))}o(Ee,"pauseProfile");async function Se(e){let t=null;return{...await l(e,n=>(t=n.paused_at,{...n,paused_at:null})),pausedAt:t}}o(Se,"resumeProfile");async function Oe(e,t){return await l(e,r=>({...r,default_kind:t}))}o(Oe,"setProfileDefaultKind");async function Re(e,t,r=[]){return _(t),await l(e,n=>{const i={...T(n.metadata),...t};for(const c of r)delete i[c];_(i);const{metadata:a,...s}=n;return{...s,...Object.keys(i).length>0?{metadata:i}:{}}})}o(Re,"updateProfileMetadata");function Me(e){if(e===null||e==="")return{};try{return T(k(e).manifest.metadata)}catch{return{}}}o(Me,"readProfileMetadata");async function ke(e,t){const r=t.trim();if(r==="")throw f("profile name must not be empty");return await l(e,i=>({...i,name:r}))}o(ke,"renameProfile");async function Ce(e,t){M([{path:t}]);const r=L(t);return await l(e,i=>({...i,projects:N([...i.projects,{path:r}])}))}o(Ce,"addProfileProject");async function De(e,t,r){return X({[t]:r}),await l(e,n=>{const{[t]:i,...a}=n.delivery_limits??{},s=r==="content"?a:{...a,[t]:r},{delivery_limits:c,...j}=n;return Object.keys(s).length===0?j:{...j,delivery_limits:s}})}o(De,"setProfileDeliveryLimit");async function Le(e,t){const r=E(t);let n=r;try{n=b(r)}catch{}return await l(e,a=>{const s=a.projects.filter(c=>c.path!==n&&c.path!==r);if(s.length===a.projects.length)throw u(`project not found on profile: ${t}`,{received:t,next:`Current projects: ${a.projects.length>0?a.projects.map(c=>c.path).join(", "):"(none)"}.`});return{...a,projects:s}})}o(Le,"removeProfileProject");async function Ne(e){U(e);const t=v(w(k(e).manifest),e);await h(r=>{if(!r.profiles.some(n=>n.profileId===e))throw u(`profile not found: ${e}`,{received:e});return[{op:"profile.delete",profileId:e}]}),A(t.dir,{recursive:!0,force:!0}),A(t.env,{force:!0})}o(Ne,"deleteProfile");export{p as ROOT_PROFILE_ID,Ce as addProfileProject,X as assertDeliveryLimits,Ae as assertProfileActive,se as assertProfileEntryActive,_ as assertProfileMetadata,M as assertProfileProjects,ie as createProfile,Ne as deleteProfile,$e as ensureAppProfile,Pe as ensureRootProfile,je as isProfilePaused,y as listProfiles,k as loadExactProfileManifest,te as loadProfileManifest,ee as lookupProfile,re as matchProfileOperand,J as metadataEnvName,Ee as pauseProfile,be as pausedCanvasProfiles,we as profileDir,Me as readProfileMetadata,Le as removeProfileProject,ke as renameProfile,ge as resolveProfileOperand,Se as resumeProfile,T as sanitizeProfileMetadata,Oe as setProfileDefaultKind,De as setProfileDeliveryLimit,_e as updateProfileLastUsed,Re as updateProfileMetadata};
|
|
1
|
+
var W=Object.defineProperty;var i=(e,t)=>W(e,"name",{value:t,configurable:!0});import{randomUUID as Z}from"node:crypto";import{existsSync as V,realpathSync as E,rmSync as S,statSync as Y}from"node:fs";import{resolve as O}from"node:path";import{SCHEMA_VERSION as R}from"../../types.js";import{profileSpace as g}from"../layout.js";import{PROFILE_PROJECT_MEMORY_VALUES as M}from"../../api/dto/profiles.js";import{ensureDir as G,nowIso as h}from"../fs-utils.js";import{cachedPausedProfileIds as B,cachedProfilePaused as Q,getCachedProfile as p,listCachedProfiles as X,writeSettings as w}from"../scoped-state/person-store.js";import{usage as l,notFound as u,ambiguous as k,general as I}from"../errors.js";import{fuzzyMatchProfile as ee}from"./fuzzy-match.js";import{isCloudRole as C,personId as te}from"../canvas/person-state.js";const P=/^[a-z0-9]+(?:-[a-z0-9]+)*-[0-9a-f]{8}$/,m="root-00000000",$="root";function y(e){return e.grantee??"app:terminal"}i(y,"profileApp");function x(e,t){G(g(y(t),e).dir)}i(x,"scaffoldProfile");function _(e){if(!P.test(e))throw l(`invalid profile id: ${e}`,{received:e,next:"Profile ids look like <slug>-<8-hex-chars> (as printed by `crtr profile new`). Pass an exact id or a profile name, not a raw path."})}i(_,"assertSafeProfileId");function _e(e){_(e);const t=p(e);if(t===null)throw u(`profile not found: ${e}`,{received:e});return g(y(t.manifest),e).dir}i(_e,"profileDir");function D(e){if(!Array.isArray(e))throw l("projects must be an array of {path} objects",{received:typeof e,field:"projects",next:'Pass entries like {"path": "/abs/dir"}.'});for(const t of e){if(typeof t!="object"||t===null||Array.isArray(t))throw l(`invalid project entry: ${JSON.stringify(t)}`,{field:"projects",next:'Every project is an object like {"path": "/abs/dir"}.'});const{path:r}=t;if(typeof r!="string"||r.trim()==="")throw l(`project entry has no path: ${JSON.stringify(t)}`,{field:"projects",next:"Give every project entry a non-empty directory path."})}}i(D,"assertProfileProjects");function re(e){const t=M.join(", ");if(typeof e!="object"||e===null||Array.isArray(e))throw l("delivery_limits must be an object mapping owner handles to a level",{received:typeof e,field:"delivery_limits",next:`Pass e.g. {"crouter": "preview"} \u2014 each level is one of: ${t}.`});for(const[r,n]of Object.entries(e)){if(r.trim()==="")throw l("delivery_limits has an empty owner handle",{field:"delivery_limits",next:"Key each limit by an owner handle such as a repo handle or `user`, or exactly by `<type>:<handle>` such as `repo:crouter`."});if(typeof n!="string"||!M.includes(n))throw l(`invalid delivery limit for ${r}: ${JSON.stringify(n)}`,{received:String(n),field:"delivery_limits",next:`Set ${r} to one of: ${t}.`})}}i(re,"assertDeliveryLimits");function ne(){return{entries:X(),unreadableIds:[]}}i(ne,"enumerateProfiles");function v(){return ne().entries}i(v,"listProfiles");function ie(e){const t=v(),r=N(t,e,j);return r?{kind:"resolved",entry:r}:{kind:"absent"}}i(ie,"lookupProfile");function L(e){_(e);const t=p(e);if(t!==null)return t;throw u(`profile not found: ${e}`,{received:e,next:"Run `crtr profile list` to see available profiles."})}i(L,"loadExactProfileManifest");const j=i(e=>({profileId:e.profileId,name:e.manifest.name}),"entryCandidate");function N(e,t,r){if(P.test(t)){const o=e.find(a=>r(a).profileId===t);if(o!==void 0)return o}const n=e.filter(o=>r(o).name===t);if(n.length===1)return n[0];if(n.length>1){const o=n.map(a=>r(a).profileId);throw k(`profile name "${t}" matches ${o.length} profiles: ${o.join(", ")}`,{received:t,candidates:o,next:`Re-run with one exact profile id: ${o.join(", ")}.`})}return null}i(N,"findExactProfile");function z(e,t,r){const n=e.map(o=>`${r(o).profileId} (${r(o).name})`);return u(`profile not found: ${t}`,{received:t,available:n,next:n.length>0?`Re-run with one of the profiles on this host: ${n.join(", ")} \u2014 or create one with \`crtr profile new --name <name>\`.`:"No profiles exist on this host yet \u2014 create one with `crtr profile new --name <name>`."})}i(z,"profileNotFound");function oe(e){const t=ie(e);if(t.kind==="resolved")return t.entry;throw z(v(),e,j)}i(oe,"loadProfileManifest");function je(e){return ae(v(),e,j)}i(je,"resolveProfileOperand");function ae(e,t,r){const n=N(e,t,r);if(n!==null)return n;const{match:o,tied:a}=ee(t,e.map(r));if(o!==null){const s=e.find(f=>r(f).profileId===o.profileId);return console.error(`crtr: no profile named "${t}" \u2014 using the closest match: ${r(s).name} (${r(s).profileId})`),s}if(a.length>1){const s=a.map(f=>`${f.profileId} (${f.name})`);throw k(`"${t}" is equally close to ${a.length} profiles: ${s.join(", ")}`,{received:t,candidates:a.map(f=>f.profileId),next:`Re-run with one exact profile id or name: ${s.join(", ")}.`})}throw z(e,t,r)}i(ae,"matchProfileOperand");function J(e){const t=O(e);if(!V(t)||!Y(t).isDirectory())throw l(`project directory does not exist or is not a directory: ${e}`,{received:e,next:"Pass an existing directory path."});try{return E(t)}catch{return t}}i(J,"resolveExistingProjectDir");function T(e){const t=new Set,r=[];for(const n of e)t.has(n.path)||(t.add(n.path),r.push(n));return r}i(T,"dedupeProjects");function se(e){let t=e.toLowerCase().replace(/[^a-z0-9]+/g,"-").replace(/^-+|-+$/g,"");return t.length>40&&(t=t.slice(0,40).replace(/-+$/g,"")),t.length>0?t:"profile"}i(se,"slugify");function fe(e){const t=se(e);for(let r=0;r<20;r++){const n=Z().replace(/-/g,"").slice(0,8),o=`${t}-${n}`;if(p(o)===null)return o}throw I(`failed to generate a unique profile id for "${e}" after 20 attempts`)}i(fe,"generateProfileId");let b=null;function be(e){b=e}i(be,"setCloudProfileWriter");async function Ae(){if(C()){const r=v().find(n=>n.manifest.name===$&&y(n.manifest)==="app:terminal");return r!==void 0?(x(r.profileId,r.manifest),r):await q($,[],{grantee:"app:terminal"})}const e=p(m);if(e!==null)return x(m,e.manifest),e;const t={schema_version:R,name:$,projects:[],grantee:"app:terminal",paused_at:null,created_at:h(),last_used_at:h()};return x(m,t),await w(r=>r.profiles.some(n=>n.profileId===m)?[]:[{op:"profile.insert",profile:{profileId:m,manifest:t}}]),p(m)}i(Ae,"ensureRootProfile");const U=/^[A-Za-z0-9][A-Za-z0-9_-]*$/;function F(e){return`CRTR_PROFILE_META_${e.toUpperCase().replace(/[^A-Z0-9]+/g,"_")}`}i(F,"metadataEnvName");function A(e){if(typeof e!="object"||e===null||Array.isArray(e))throw l("metadata must be a map of string keys to string values",{field:"metadata",next:'Pass an object, e.g. {"role": "support"}.'});const t=new Map;for(const[r,n]of Object.entries(e)){if(!U.test(r))throw l(`invalid metadata key: ${JSON.stringify(r)}`,{received:r,field:"metadata",next:"Keys start with a letter or digit, followed by letters, digits, `_`, or `-`."});if(typeof n!="string")throw l(`metadata value for ${r} must be a string`,{received:typeof n,field:"metadata",next:"Every metadata value is an env-var value \u2014 pass strings only."});if(n.includes("\0"))throw l(`metadata value for ${r} contains a NUL byte, which no OS accepts in an environment variable`,{field:"metadata",next:"Remove NUL bytes from the value and retry."});const o=F(r),a=t.get(o);if(a!==void 0)throw l(`metadata keys ${JSON.stringify(a)} and ${JSON.stringify(r)} both surface as ${o}`,{field:"metadata",next:"Rename or drop one \u2014 keys must stay distinct after uppercasing and `_`-folding."});t.set(o,r)}}i(A,"assertProfileMetadata");function K(e){if(typeof e!="object"||e===null||Array.isArray(e))return{};const t={},r=new Set;for(const[n,o]of Object.entries(e)){if(typeof o!="string"||!U.test(n)||o.includes("\0"))continue;const a=F(n);r.has(a)||(r.add(a),t[n]=o)}return t}i(K,"sanitizeProfileMetadata");async function q(e,t=[],r={}){const n=e.trim();if(n==="")throw l("profile name must not be empty");r.metadata!==void 0&&A(r.metadata),D(t);const o=T(t.map(d=>({path:J(d.path)}))),a=fe(n),s={schema_version:R,name:n,projects:o,paused_at:null,...r.grantee===void 0?{}:{grantee:r.grantee},...r.defaultKind!==void 0?{default_kind:r.defaultKind}:{},...r.metadata!==void 0&&Object.keys(r.metadata).length>0?{metadata:{...r.metadata}}:{},created_at:h(),last_used_at:null};x(a,s);const f=i(async()=>{await w(()=>[{op:"profile.insert",profile:{profileId:a,manifest:s}}])},"insert");if(!C())await f();else{if(b===null)throw new Error("a cloud role has no profile environment writer registered");await b({profileId:a,grantee:y(s),person:te()},async d=>{s.environment_id=d,await f()})}return{profileId:a,manifest:s}}i(q,"createProfile");async function Ee(e){if(!e.startsWith("app:")||e.length<=4)throw l("an app grantee is required");const t=`app-${e.slice(4)}`,r=v().filter(n=>n.manifest.name===t&&n.manifest.grantee===e);if(r.length>1)throw new Error(`duplicate default profile for ${e}`);return r[0]?r[0]:await q(t,[],{grantee:e})}i(Ee,"ensureAppProfile");function H(e){if(_(e),p(e)===null)throw u(`profile not found: ${e}`,{received:e,next:"Run `crtr profile list` to see available profiles."})}i(H,"requireExistingProfile");async function c(e,t){H(e);let r;return await w(n=>{const o=n.profiles.find(a=>a.profileId===e);if(o===void 0)throw u(`profile not found: ${e}`,{received:e});return r={profileId:e,manifest:t(structuredClone(o.manifest))},[{op:"profile.update",profile:r}]}),r}i(c,"mutateManifest");async function Se(e){return await c(e,t=>({...t,last_used_at:h()}))}i(Se,"updateProfileLastUsed");function le(e){if(e==null||e==="")return null;try{return oe(e)}catch{return null}}i(le,"loadProfileManifestOrNull");function Oe(e){return e==null||e===""||!P.test(e)?!1:Q(e)}i(Oe,"isProfilePaused");function Re(){return B()}i(Re,"pausedCanvasProfiles");function ce(e){if(e.manifest.paused_at!==null)throw l(`profile "${e.manifest.name}" is paused and cannot start nodes`,{received:e.profileId,next:`Resume it with \`crtr profile resume ${e.profileId}\`.`})}i(ce,"assertProfileEntryActive");function Me(e){const t=le(e);return t===null?null:(ce(t),t)}i(Me,"assertProfileActive");async function ke(e){return await c(e,t=>({...t,paused_at:t.paused_at??h()}))}i(ke,"pauseProfile");async function Ce(e){let t=null;return{...await c(e,n=>(t=n.paused_at,{...n,paused_at:null})),pausedAt:t}}i(Ce,"resumeProfile");async function De(e,t){return await c(e,r=>({...r,default_kind:t}))}i(De,"setProfileDefaultKind");async function Le(e,t,r=[]){return A(t),await c(e,n=>{const o={...K(n.metadata),...t};for(const f of r)delete o[f];A(o);const{metadata:a,...s}=n;return{...s,...Object.keys(o).length>0?{metadata:o}:{}}})}i(Le,"updateProfileMetadata");function Ne(e){if(e===null||e==="")return{};try{return K(L(e).manifest.metadata)}catch{return{}}}i(Ne,"readProfileMetadata");async function ze(e,t){const r=t.trim();if(r==="")throw l("profile name must not be empty");return await c(e,o=>({...o,name:r}))}i(ze,"renameProfile");async function Je(e,t){D([{path:t}]);const r=J(t);return await c(e,o=>({...o,projects:T([...o.projects,{path:r}])}))}i(Je,"addProfileProject");async function Te(e,t,r){return re({[t]:r}),await c(e,n=>{const{[t]:o,...a}=n.delivery_limits??{},s=r==="content"?a:{...a,[t]:r},{delivery_limits:f,...d}=n;return Object.keys(s).length===0?d:{...d,delivery_limits:s}})}i(Te,"setProfileDeliveryLimit");async function Ue(e,t){const r=O(t);let n=r;try{n=E(r)}catch{}return await c(e,a=>{const s=a.projects.filter(f=>f.path!==n&&f.path!==r);if(s.length===a.projects.length)throw u(`project not found on profile: ${t}`,{received:t,next:`Current projects: ${a.projects.length>0?a.projects.map(f=>f.path).join(", "):"(none)"}.`});return{...a,projects:s}})}i(Ue,"removeProfileProject");async function Fe(e){H(e);const t=g(y(L(e).manifest),e);await w(r=>{if(!r.profiles.some(n=>n.profileId===e))throw u(`profile not found: ${e}`,{received:e});return[{op:"profile.delete",profileId:e}]}),S(t.dir,{recursive:!0,force:!0}),S(t.env,{force:!0})}i(Fe,"deleteProfile");export{m as ROOT_PROFILE_ID,Je as addProfileProject,re as assertDeliveryLimits,Me as assertProfileActive,ce as assertProfileEntryActive,A as assertProfileMetadata,D as assertProfileProjects,q as createProfile,Fe as deleteProfile,Ee as ensureAppProfile,Ae as ensureRootProfile,Oe as isProfilePaused,v as listProfiles,L as loadExactProfileManifest,oe as loadProfileManifest,ie as lookupProfile,ae as matchProfileOperand,F as metadataEnvName,ke as pauseProfile,Re as pausedCanvasProfiles,_e as profileDir,Ne as readProfileMetadata,Ue as removeProfileProject,ze as renameProfile,je as resolveProfileOperand,Ce as resumeProfile,K as sanitizeProfileMetadata,be as setCloudProfileWriter,De as setProfileDefaultKind,Te as setProfileDeliveryLimit,Se as updateProfileLastUsed,Le as updateProfileMetadata};
|
|
@@ -1,2 +1,6 @@
|
|
|
1
|
-
/**
|
|
1
|
+
/** A fleet role (`CRTR_RUNTIME` set) booted without `BLAXEL_API_KEY`: it would run agent nodes as processes beside the control plane's database URLs and `CRTR_SECRETS_KEY`. */
|
|
2
|
+
export declare class FleetRoleWithoutSandboxRunnerError extends Error {
|
|
3
|
+
constructor();
|
|
4
|
+
}
|
|
5
|
+
/** Registers the Blaxel runner when `BLAXEL_API_KEY` is set; returns whether it did (false only for a role with neither `BLAXEL_API_KEY` nor `CRTR_RUNTIME`). A fleet role without the key, or any other missing setting, throws, so a misconfigured control plane fails at boot. */
|
|
2
6
|
export declare function registerBlaxelRunnerFromEnv(env?: NodeJS.ProcessEnv): boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var t=Object.defineProperty;var
|
|
1
|
+
var t=Object.defineProperty;var r=(e,o)=>t(e,"name",{value:o,configurable:!0});import{readFileSync as n}from"node:fs";import{join as l}from"node:path";import{emitEvent as s}from"../events/emit.js";import{assetRoot as i}from"../asset-root.js";import{createBlaxelClient as a,blaxelConfigFromEnv as u}from"../runtime/runner/blaxel/client.js";import{registerBlaxelRunner as m}from"../runtime/runner/index.js";import{createBlaxelRunner as p}from"../runtime/runner/blaxel/runner.js";import{blaxelLaunchDepsFromEnv as E,createBlaxelLaunchContext as R}from"./blaxel-launch.js";class c extends Error{static{r(this,"FleetRoleWithoutSandboxRunnerError")}constructor(){super("CRTR_RUNTIME is set but BLAXEL_API_KEY is not: a fleet role needs a sandbox runner, and without one it would run agent nodes inside the control-plane process environment. Set BLAXEL_API_KEY or unset CRTR_RUNTIME."),this.name="FleetRoleWithoutSandboxRunnerError"}}function C(e=process.env){if(!e.BLAXEL_API_KEY){if(e.CRTR_RUNTIME)throw new c;return!1}const o=n(l(i(),"..","scripts","sandbox-bootstrap.sh"),"utf8");return m(p({client:a(u(e)),launchContext:R(E(e)),bootstrap:r(()=>o,"bootstrap")})),s({level:"info",event:"daemon.blaxel_runner.registered",fields:{workspace:e.BLAXEL_WORKSPACE??null,region:e.BLAXEL_REGION??null}}),!0}r(C,"registerBlaxelRunnerFromEnv");export{c as FleetRoleWithoutSandboxRunnerError,C as registerBlaxelRunnerFromEnv};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.566",
|
|
4
4
|
"description": "crtr — agent runtime with memory, plugins, and marketplaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
},
|
|
145
145
|
"license": "UNLICENSED",
|
|
146
146
|
"dependencies": {
|
|
147
|
-
"@north-light/identity": "0.3.
|
|
147
|
+
"@north-light/identity": "0.3.566",
|
|
148
148
|
"@earendil-works/pi-agent-core": "1.1.0",
|
|
149
149
|
"@earendil-works/pi-ai": "1.1.0",
|
|
150
150
|
"@earendil-works/pi-coding-agent": "1.1.0",
|
package/runtime.lock.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.566",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@north-light/cli",
|
|
9
|
-
"version": "0.3.
|
|
9
|
+
"version": "0.3.566",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "UNLICENSED",
|
|
12
12
|
"workspaces": [
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
"@milkdown/preset-gfm": "^7.22.3",
|
|
25
25
|
"@milkdown/prose": "^7.22.3",
|
|
26
26
|
"@milkdown/utils": "^7.22.3",
|
|
27
|
-
"@north-light/identity": "0.3.
|
|
27
|
+
"@north-light/identity": "0.3.566",
|
|
28
28
|
"cron-parser": "^5.6.0",
|
|
29
29
|
"esbuild": "^0.27.7",
|
|
30
30
|
"jose": "^6.2.12",
|
|
@@ -12455,17 +12455,17 @@
|
|
|
12455
12455
|
},
|
|
12456
12456
|
"packages/crouter-api": {
|
|
12457
12457
|
"name": "@north-light/api",
|
|
12458
|
-
"version": "0.3.
|
|
12458
|
+
"version": "0.3.566",
|
|
12459
12459
|
"license": "UNLICENSED"
|
|
12460
12460
|
},
|
|
12461
12461
|
"packages/crouter-env-docker": {
|
|
12462
12462
|
"name": "@north-light/env-docker",
|
|
12463
|
-
"version": "0.3.
|
|
12463
|
+
"version": "0.3.566",
|
|
12464
12464
|
"license": "UNLICENSED"
|
|
12465
12465
|
},
|
|
12466
12466
|
"packages/crouter-identity": {
|
|
12467
12467
|
"name": "@north-light/identity",
|
|
12468
|
-
"version": "0.3.
|
|
12468
|
+
"version": "0.3.566",
|
|
12469
12469
|
"license": "UNLICENSED",
|
|
12470
12470
|
"dependencies": {
|
|
12471
12471
|
"jose": "^6.2.1"
|
|
@@ -12473,21 +12473,21 @@
|
|
|
12473
12473
|
},
|
|
12474
12474
|
"packages/crouter-plugin": {
|
|
12475
12475
|
"name": "@north-light/plugin",
|
|
12476
|
-
"version": "0.3.
|
|
12476
|
+
"version": "0.3.566",
|
|
12477
12477
|
"license": "UNLICENSED",
|
|
12478
12478
|
"dependencies": {
|
|
12479
|
-
"@north-light/api": "0.3.
|
|
12480
|
-
"@north-light/identity": "0.3.
|
|
12479
|
+
"@north-light/api": "0.3.566",
|
|
12480
|
+
"@north-light/identity": "0.3.566"
|
|
12481
12481
|
}
|
|
12482
12482
|
},
|
|
12483
12483
|
"packages/crouter-sdk": {
|
|
12484
12484
|
"name": "@north-light/sdk",
|
|
12485
|
-
"version": "0.3.
|
|
12485
|
+
"version": "0.3.566",
|
|
12486
12486
|
"license": "UNLICENSED",
|
|
12487
12487
|
"dependencies": {
|
|
12488
|
-
"@north-light/api": "0.3.
|
|
12489
|
-
"@north-light/identity": "0.3.
|
|
12490
|
-
"@north-light/plugin": "0.3.
|
|
12488
|
+
"@north-light/api": "0.3.566",
|
|
12489
|
+
"@north-light/identity": "0.3.566",
|
|
12490
|
+
"@north-light/plugin": "0.3.566",
|
|
12491
12491
|
"jose": "^6.2.1"
|
|
12492
12492
|
},
|
|
12493
12493
|
"bin": {
|