@north-light/cli 0.3.459 → 0.3.460
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/builtin-memory/crouter-sdk/guides/apps-on-northlight.md +1 -1
- package/dist/builtin-memory/crouter-sdk/release-notes.md +6 -0
- package/dist/clients/attach/viewer.js +177 -177
- package/dist/commands/sys/join.js +1 -1
- package/dist/core/command-plugins/compose.js +1 -1
- package/dist/core/directory/join.d.ts +4 -2
- package/dist/core/directory/join.js +1 -1
- package/dist/core/io.d.ts +12 -2
- package/dist/core/io.js +7 -7
- package/dist/core/providers/device.js +1 -1
- package/dist/core/render.d.ts +1 -1
- package/dist/core/render.js +6 -6
- package/dist/daemon/api/router.js +1 -1
- package/docs/sdk/guides/apps-on-northlight.md +1 -1
- package/docs/sdk/release-notes.md +6 -0
- package/package.json +2 -2
- package/packages/crouter-identity/package.json +1 -1
- package/runtime.lock.json +12 -12
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var s=Object.defineProperty;var n=(e,r)=>s(e,"name",{value:r,configurable:!0});import{defineLeaf as o}from"../../core/command.js";import{existsSync as a}from"node:fs";import{join as d}from"node:path";import{pluginsDir as l}from"../../core/scope.js";import{joinDirectory as u}from"../../core/directory/join.js";import{apiSocketPath as c}from"../../core/canvas/paths.js";import{listProcessTable as m}from"../../core/canvas/pid.js";import{daemonAnswers as h}from"../../core/layout-migrate/index.js";import{envNodeId as p}from"../../shared/env.js";import{isDaemonRunning as f}from"../../daemon/pidfile.js";import{findDaemonPids as w,spawnDaemon as y,stopAllDaemons as g}from"../../daemon/manage.js";function k(){const e=m();if(e===null||(p()??"")!=="")return!0;const r=new Map(e.map(t=>[t.pid,t])),i=new Set;for(let t=process.ppid;t>1&&!i.has(t);t=r.get(t)?.ppid??0)if(i.add(t),r.get(t)?.command.includes("broker-cli"))return!0;return!1}n(k,"insideBroker");function b(){if(!(process.platform==="linux"&&a("/run/crtr/launcher.sock")))return{running:n(async()=>f()||w().length>0||await h(c()),"running"),stop:n(async()=>{await g()},"stop"),start:n(async()=>{await y()},"start"),firstSync:n(async()=>{const e=l("user"),r=Date.now()+2e4;for(;e&&Date.now()<r&&!a(d(e,"cloud","commands.json"));)await new Promise(i=>setTimeout(i,250))},"firstSync"),insideNode:k}}n(b,"daemonControl");const T=o({name:"join",description:"register this runtime with its directory using an enrollment token, offline",whenToUse:"on your own computer, with a token from `<issuer>/account/machines`: run it from a plain terminal, with any viewers open. It stops the daemon, joins, and starts it again. Also at a hosted runtime's first boot, before the daemon starts, with the enrollment token provisioning passed in.",help:{name:"sys join",summary:"exchange a directory enrollment token for a new runtime client key and record the returned identity",guide:"Generates a new ES256 client key, sends only its public key to `PUT <directory>/v1/runtimes/registration` with the token as bearer, and learns the runtime id, client id, issuer, owner and URL from the answer. Only after that answer does it write the key file, set the user settings `runtime.issuer` and `runtime.audience`, and rewrite `user:local` profile grantees to `app:terminal`. canvas.db belongs to the daemon: its next start rewrites `user:local` there, before it serves anything. On your own computer a running daemon is no obstacle: join takes a `join.lock` in the runtime directory (so no open viewer's redial can autostart a daemon while it works), registers with the directory while the daemon still serves, stops the daemon, writes the identity, and starts the daemon again, whose start applies the canvas.db rewrite. Run it from a plain terminal, not from inside a node: a node's broker dies with the daemon, so join refuses `join_inside_node` instead. Where the launcher owns the daemon (a hosted container) join keeps the offline contract and refuses `daemon_running`. Presenting the token of the stored successful registration again finishes those local writes without calling the directory. Any other token generates a new key and registers again.",params:[{kind:"flag",name:"directory",type:"string",required:!0,constraint:"Directory base URL (http or https) that minted the token."},{kind:"flag",name:"token",type:"string",required:!0,sensitive:!0,constraint:"Single-use enrollment token. Sensitive: never echoed in output or errors."},{kind:"flag",name:"name",type:"string",required:!1,constraint:"Label shown for this machine on the account page. Default: this computer's hostname."}],output:[{name:"runtime",type:"string",required:!0,constraint:"Runtime principal from the directory, `runtime:<id>`."},{name:"client_id",type:"string",required:!0,constraint:"OAuth client id registered for the new key."},{name:"issuer",type:"string",required:!0,constraint:"Canonical issuer, written to `runtime.issuer`."},{name:"owner",type:"string",required:!0,constraint:"Owner principal, `user:<id>`, that replaces `user:local` subjects."},{name:"canvas_rewrite",type:"string",required:!0,constraint:"Always `next daemon start`: when canvas.db identity fields are rewritten."},{name:"url",type:"string",required:!0,constraint:"Runtime URL, written to `runtime.audience`."},{name:"registered",type:"boolean",required:!0,constraint:"False when the token matched the stored registration and the directory was not called."},{name:"daemon",type:"string",required:!0,constraint:"`restarted` (a daemon was stopped and started on the joined settings), `started` (none was running; join started one and waited, up to 20s, for its first grant sync to install the account's provider plugins, so `crtr cloud \u2026` resolves), `not_running` (none was started: a hosted container's launcher owns its daemon), or `restart_failed` (joined, but the new daemon did not start: see `daemon_error`, then run `crtr sys daemon start`)."},{name:"daemon_error",type:"string",required:!1,constraint:"Why the new daemon did not start, with `daemon: restart_failed`."}],outputKind:"object",effects:["Holds `<runtime>/join.lock` (its pid) from the start until the daemon is back; `ensureDaemon` and `sys daemon start` (without `--force`) decline to start a daemon while a live process holds it (including a start already in flight when the join began). A lock whose holder died is reclaimed.","A running daemon is stopped (SIGTERM, then SIGKILL, plus strays) after the directory accepts the registration and before the local writes, then started again. Every broker dies with it and is revived by the new daemon. If the join fails after the stop, the daemon is started again anyway. With no daemon running join starts one anyway, because a bare `crtr cloud \u2026` is an unknown command (it never autostarts a daemon) until a daemon's grant sync has installed the provider plugins. Refuses `join_inside_node` when run from inside a node and a daemon is running; refuses `daemon_running` where a container launcher owns the daemon.","On a new token: calls the directory once; on success writes `<runtime>/secrets/runtime-client.json` (mode 0600) with the private key, registration and the token's SHA-256, replacing any earlier key file. A refused or failed call writes nothing.","Sends name, platform, crtr version, installed plugin ids and sandbox state with the public key. Before the request it writes `runtime-client.pending.json` (mode 0600); a rerun with the same token and directory reuses that key, so a lost reply is retried safely. The file is deleted on success or a refusal.","Sets `runtime.issuer` and `runtime.audience` in user settings. When the directory says this machine is not the account's default (`is_default: false`) it replaces the whole `runtime` setting with `{issuer, audience, unsandboxed: true}`: no app listener, and only `app:terminal` nodes run.","Rewrites `user:local` profile grantees to `app:terminal`.","At every later daemon start, before any listener: exact `user:local` subjects in canvas.db nodes and node_tokens become the owner (as do missing subjects on `app:terminal` nodes), `user:local` grantees in nodes, run_events, idempotency_keys and node_tokens become `app:terminal`, references to the unjoined runtime's `local:terminal` grant are cleared, and that local grant mirror is deleted so grant sync installs the directory's. Application grantees and free text are unchanged; a start with nothing left to rewrite changes no rows."]},run:n(async e=>({...await u(e.directory,e.token,e.name||void 0,b()),canvas_rewrite:"next daemon start"}),"run")});export{T as sysJoinLeaf};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var
|
|
1
|
+
var k=Object.defineProperty;var u=(n,e)=>k(n,"name",{value:e,configurable:!0});import{resolve as x}from"node:path";import{envNodeId as C}from"../../shared/env.js";import{cliClient as f}from"../../commands/api-client.js";import{apiErrorToCliError as j}from"../io.js";import{ApiError as L}from"../../api/index.js";import{defineBranch as h,defineLeaf as T}from"../command.js";import{resolveCommandRegistry as U}from"../command-manifests/registry.js";import{executeExternalLeaf as M}from"./transport/exec-invoke.js";import{invokeHttpLeaf as A}from"./transport/http-invoke.js";async function S(n){const e=await f().request("GET",`/v1/providers/${encodeURIComponent(n)}/tools`);return new Set(e.groups.flatMap(r=>r.leaves.map(a=>a.tool)))}u(S,"allowedProviderTools");function m(n,e,r){return n.children.some(a=>a.kind==="leaf"?r.has([...e,a.name].join(" ")):m(a,[...e,a.name],r))}u(m,"hasAllowedLeaf");function N(n,e,r){return n.children.filter(a=>a.tier!=="hidden"&&(a.kind==="leaf"?r.has([...e,a.name].join(" ")):m(a,[...e,a.name],r))).length}u(N,"visibleChildCount");function _(n,e,r){return async()=>{if(!C())return n.help;const a=await S(e.id);return{...n.help,listing:n.help.listing?.flatMap(t=>{const i=n.children.find(s=>s.name===t.name);if(i?.kind==="leaf")return a.has([...r,i.name].join(" "))?[t]:[];if(i?.kind!=="branch")return[];const o=[...r,i.name];if(!m(i,o,a))return[];const p=N(i,o,a);return[{...t,...t.subCount!==void 0?{subCount:p}:{}}]})}}}u(_,"providerHelp");const b="Runs on one of your devices. Add `--machine <machine_id>` to pick which; without it the call goes to your only device, and with several the answer is `machine_required` listing each one's machine_id. The `runtime:` prefix on the id is optional.";function g(n,e,r,a=!1){return{outputKind:"object",...a?{machineSelector:!0}:{},run:u(async(t,i)=>{const o={...t};for(const p of r.params){if(!("type"in p)||p.type!=="file")continue;const s=p.name.replace(/-([a-z])/g,(c,w)=>w.toUpperCase()),d=o[s];typeof d=="string"&&(o[s]=x(d))}try{return await f({timeoutMs:0}).request("POST","/v1/provider/call",{provider:n,tool:e,arguments:o,...i?.machine===void 0?{}:{machine:i.machine}},{timeout:0})}catch(p){throw p instanceof L?j(p,void 0,{providerCall:!0}):p}},"run")}}u(g,"providerLeafAdapter");function v(n,e){return/^[A-Za-z0-9_-]{1,64}$/.test(n)&&n!=="crtr"&&!e.has(n)}u(v,"validRootName");function O(n,e){const r=[],a=[];for(const t of n){const{provider:i,commands:o}=t;if(!Array.isArray(o)||t.status!=="available"&&!v(i,e))continue;if(t.status!=="available"){const s=t.error??"bundle installation pending",d=`Unavailable (${t.status}): ${s}`,c=h({name:i,rootEntry:{concept:t.display_name,desc:d,useWhen:d},help:{name:i,summary:d},children:[]});c.unavailable=d,a.push(c);continue}const p=u(s=>s.kind==="branch"?{...s,children:s.children.map(p)}:{...s,rest:{method:"GET",path:"/",params:{}}},"helpNode");for(const s of o)s.kind!=="branch"||!Array.isArray(s.children)||!v(s.name,e)||r.push({contributor:{name:i,scope:"user"},manifestName:s.name,node:p(s),adaptLeaf:u((d,c)=>g(i,c.slice(1).join(" "),d,t.transport==="machine"),"adaptLeaf")})}return[...I(U(r,e)),...a]}u(O,"composeSandboxProviderSubtrees");function y(n,e){return(r,a)=>{const t=n.transport,i=e(a);if(n.provider!==void 0)return g(n.provider.id,i.slice(1).join(" "),r,t.kind==="machine");if(t.kind==="exec"){const p=t.executable;if(p===void 0)throw new Error(`exec plugin "${n.plugin.name}" has an executable leaf without transport.executable`);return{outputKind:"object",run:u(async s=>M({plugin:n.plugin,executable:p,commandPath:i,output:r.output},s),"run")}}if(t.kind==="machine")throw new Error(`machine plugin "${n.plugin.name}" is not a capability provider`);const o=r;return{outputKind:o.rest.streaming===!0?"jsonl":"object",run:u((p,s)=>A({registration:{name:n.plugin.name,endpoint:t.endpoint,...t.authEnv!==void 0?{authEnv:t.authEnv}:{}},...n.manifest?.baseUrl!==void 0?{baseUrl:n.manifest.baseUrl}:{},...n.manifest?.timeouts!==void 0?{timeouts:n.manifest.timeouts}:{},params:o.params,rest:o.rest,output:o.output,commandPath:i},p,s),"run")}}}u(y,"adaptLeaf");function W(n){return{contributions:n.map(e=>({contributor:{name:e.plugin.name,scope:e.plugin.scope,sourceMarketplace:e.plugin.sourceMarketplace},node:e.node,manifestName:e.manifestName,adaptLeaf:y(e,r=>[e.manifestName,...r.slice(1)]),...e.provider!==void 0?{provider:e.provider}:{}})),coreMounts:[],issues:[]}}u(W,"adaptPluginContributions");function F(n){return n.map(e=>({parent:e.parent,plugin:e.plugin.name,contributor:{name:e.plugin.name,scope:e.plugin.scope,sourceMarketplace:e.plugin.sourceMarketplace},node:e.node,...e.provider!==void 0?{provider:e.provider}:{},adaptLeaf:y({...e,manifestName:e.node.name},r=>e.provider===void 0?[...r]:[e.provider.id,...r.slice(e.parent.length)])}))}u(F,"adaptPluginCoreMounts");function I(n){return n.contributions.map(e=>l(e,e.node,[e.node.name]))}u(I,"composeExternalSubtrees");function Z(n,e){return n.filter(r=>r.parent.join("\0")===e.join("\0")).map(r=>r.node.kind==="branch"?l(r,r.node,[...e,r.node.name],r.provider===void 0?1:e.length):E(r,r.node,[...e,r.node.name]))}u(Z,"composeCoreBranchMounts");function l(n,e,r,a=1){const t=e.rootEntry===void 0?void 0:{concept:e.rootEntry.concept,desc:e.rootEntry.description,useWhen:e.rootEntry.whenToUse},i=h({name:e.name,description:e.description,whenToUse:e.whenToUse,...e.tier!==void 0?{tier:e.tier}:{},...n.plugin!==void 0?{plugin:n.plugin}:{},...t!==void 0?{rootEntry:t}:{},help:{name:r.join(" "),summary:e.summary,...e.model!==void 0?{model:e.model}:{},...r.length===1&&n.extensionIssue!==void 0?{extensionIssue:n.extensionIssue}:{}},...e.passthrough!==void 0?{passthrough:e.passthrough}:{},children:e.children.map(o=>o.kind==="branch"?l(n,o,[...r,o.name],a):E(n,o,[...r,o.name]))});return n.provider!==void 0&&(i.effectiveHelp=_(i,n.provider,r.slice(a))),i}u(l,"buildBranch");function E(n,e,r){const a=n.adaptLeaf(e,r);return T({name:e.name,description:e.description,whenToUse:e.whenToUse,...e.tier!==void 0?{tier:e.tier}:{},...n.plugin!==void 0?{plugin:n.plugin}:{},...a.machineSelector===!0?{machineSelector:!0}:{},help:{name:r.join(" "),summary:e.summary,...a.machineSelector===!0?{guide:b}:{},params:e.params,output:e.output,outputKind:a.outputKind,effects:[...e.effects]},run:a.run})}u(E,"buildLeaf");export{W as adaptPluginContributions,F as adaptPluginCoreMounts,Z as composeCoreBranchMounts,I as composeExternalSubtrees,O as composeSandboxProviderSubtrees};
|
|
@@ -20,8 +20,8 @@ type Registration = Pick<JoinedClient, 'runtime' | 'client_id' | 'issuer' | 'own
|
|
|
20
20
|
export interface JoinResult extends Registration {
|
|
21
21
|
/** False when the token matched the stored successful registration and no directory call was made. */
|
|
22
22
|
registered: boolean;
|
|
23
|
-
/** `restarted`: a daemon was running, join stopped it and started a new one on the joined config. `
|
|
24
|
-
daemon: 'restarted' | 'not_running' | 'restart_failed';
|
|
23
|
+
/** `restarted`: a daemon was running, join stopped it and started a new one on the joined config. `started`: none was running and join started one, whose first grant sync installs the account's provider plugins (`crtr cloud …`). `not_running`: none was started (a hosted container's launcher owns its daemon). `restart_failed`: the join is committed but the new daemon did not start (see `daemon_error`). */
|
|
24
|
+
daemon: 'restarted' | 'started' | 'not_running' | 'restart_failed';
|
|
25
25
|
daemon_error?: string;
|
|
26
26
|
}
|
|
27
27
|
/** How join takes a running daemon down and brings it back. Absent, join keeps
|
|
@@ -33,6 +33,8 @@ export interface JoinDaemonControl {
|
|
|
33
33
|
stop(): Promise<void>;
|
|
34
34
|
/** Start a daemon on the settings now on disk and wait until it serves. */
|
|
35
35
|
start(): Promise<void>;
|
|
36
|
+
/** Waits, bounded, for a daemon that join started with none running to finish its first grant sync, which installs the account's provider plugins. Best effort: the join is committed either way. */
|
|
37
|
+
firstSync?(): Promise<void>;
|
|
36
38
|
/** Whether this process runs inside a node, whose broker the stop kills. */
|
|
37
39
|
insideNode(): boolean;
|
|
38
40
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var J=Object.defineProperty;var s=(r
|
|
1
|
+
var J=Object.defineProperty;var s=(t,r)=>J(t,"name",{value:r,configurable:!0});import{createHash as N,generateKeyPairSync as R,randomUUID as U}from"node:crypto";import{existsSync as I,mkdirSync as D,readdirSync as L,rmSync as y}from"node:fs";import{hostname as O}from"node:os";import{join as b}from"node:path";import{InputError as p}from"../io.js";import{network as k}from"../errors.js";import{secretsDir as j}from"../layout.js";import{apiSocketPath as C}from"../canvas/paths.js";import{daemonAnswers as K}from"../layout-migrate/index.js";import{atomicWriteJson as _,readJsonIfExists as x}from"../fs-utils.js";import{updateConfig as T}from"../config.js";import{launcherPresent as A}from"../grants/sandboxes.js";import{crtrVersion as W}from"../../shared/crtr-version.js";import{pluginsDir as q}from"../scope.js";import{userScopeRoot as z}from"../scope.js";import{rewriteLocalProfileGrantees as B}from"../scoped-state/profiles.js";import{withJoinLock as F}from"./join-lock.js";function l(){return b(j(),"runtime-client.json")}s(l,"clientPath");function S(t){if(typeof t!="string")return!1;try{const{protocol:r}=new URL(t);return r==="http:"||r==="https:"}catch{return!1}}s(S,"isUrl");function $(t){const r=t??{},{runtime:u,client_id:e,issuer:a,owner:n,url:o}=r;if(typeof u!="string"||!/^runtime:.+/.test(u)||typeof e!="string"||e===""||!S(a)||typeof n!="string"||!/^user:.+/.test(n)||!S(o))throw k("directory registration answer is missing runtime, client_id, issuer, owner, or url");const{machine_id:i,is_default:c}=r;return{runtime:u,client_id:e,issuer:a,owner:n,url:o,...typeof i=="string"?{machine_id:i}:{},...typeof c=="boolean"?{is_default:c}:{}}}s($,"registration");function w(){return b(j(),"runtime-client.pending.json")}s(w,"pendingPath");function G(){try{const t=q("user");return!t||!I(t)?[]:L(t,{withFileTypes:!0}).filter(r=>r.isDirectory()&&!r.name.startsWith(".")).map(r=>r.name).sort()}catch{return[]}}s(G,"installedPluginIds");async function V(t,r,u,e){const a=`${t.replace(/\/$/,"")}/v1/runtimes/registration`;let n;try{n=await fetch(a,{method:"PUT",headers:{authorization:`Bearer ${r}`,"content-type":"application/json"},body:JSON.stringify({jwk:u,name:e,platform:`${process.platform}-${process.arch}`,crtr_version:W(),plugins:G(),sandboxed:A()}),signal:AbortSignal.timeout(3e4)})}catch(i){throw k(`directory registration request to ${a} failed: ${i instanceof Error?i.message:String(i)}`)}const o=await n.text();if(!n.ok)throw n.status<500&&y(w(),{force:!0}),new p({error:"join_refused",message:`directory refused the registration: ${n.status} ${o.slice(0,500)}`,next:`Create a new join token at ${t.replace(/\/$/,"")}/account/machines (tokens are single-use and expire after 10 minutes), and run \`crtr sys join\` again.`});try{return $(JSON.parse(o))}catch(i){throw i instanceof SyntaxError?k("directory registration answer is not JSON"):i}}s(V,"register");function P(t){T("user",r=>{r.runtime=t.is_default===!1?{issuer:t.issuer,audience:t.url,unsandboxed:!0}:{...r.runtime,issuer:t.issuer,audience:t.url}}),B(z())}s(P,"commit");function mt(){const t=x(l());if(t?.token_sha256===void 0)return null;const{owner:r}=t;if(typeof r!="string"||!/^user:.+/.test(r))throw new Error(`${l()} was written by join but has a missing or invalid owner`);return r}s(mt,"joinedOwner");function v(t){return new p({error:"daemon_running",message:`cannot join while a daemon answers on ${t}`,next:"This daemon is managed outside crtr, so join cannot restart it: stop the daemon that owns this volume, then run `crtr sys join` again."})}s(v,"daemonRunningError");async function E(t,r,u,e){const a=x(l());if(a?.token_sha256===e)return{joined:$(a),registered:!1};D(j(),{recursive:!0,mode:448});let n=x(w());if(n?.token_sha256!==e||n.directory!==t||!n.jwk||!n.public_jwk){const{privateKey:h,publicKey:f}=R("ec",{namedCurve:"P-256"}),d=U();n={token_sha256:e,directory:t,jwk:{...h.export({format:"jwk"}),kid:d,alg:"ES256",use:"sig"},public_jwk:{...f.export({format:"jwk"}),kid:d,alg:"ES256",use:"sig"}},_(w(),n,{mode:384})}const{jwk:o,public_jwk:i}=n,c=await V(t,r,i,u);return{joined:c,registered:!0,client:{jwk:o,kid:o.kid,alg:"ES256",...c,token_sha256:e}}}s(E,"obtainRegistration");async function pt(t,r,u=O(),e){if(!S(t))throw new p({error:"invalid_directory",message:"directory must be an http or https URL",received:t,field:"directory",next:"Pass the directory base URL, for example https://directory.example."});if(r==="")throw new p({error:"invalid_token",message:"token is empty",field:"token",next:"Pass the enrollment token provisioning supplied."});const a=C(),n=N("sha256").update(r).digest("hex");if(e===void 0){if(await K(a))throw v(a);const{joined:o,registered:i,client:c}=await E(t,r,u,n);return c&&(_(l(),c,{mode:384}),y(w(),{force:!0})),P(o),{...o,registered:i,daemon:"not_running"}}return F(async()=>{const o=s(()=>new p({error:"join_inside_node",message:"a daemon is running and this command is inside a node: stopping the daemon would kill this node's broker and this command with it",next:"Run `crtr sys join` from a plain terminal outside any crtr node. It stops the daemon, joins, and starts it again for you."}),"refuseInsideNode");if(await e.running()&&e.insideNode())throw o();const{joined:i,registered:c,client:h}=await E(t,r,u,n);let f=!1,d;try{if(await e.running()){if(e.insideNode())throw o();if(f=!0,await e.stop(),await e.running())throw v(a)}if(h&&(_(l(),h,{mode:384}),y(w(),{force:!0})),P(i),await e.running()){if(e.insideNode())throw o();f=!0,await e.stop()}}catch(m){d=m}let g={daemon:"not_running"};if(d===void 0||f)try{await e.start(),g={daemon:f?"restarted":"started"}}catch(m){g={daemon:"restart_failed",daemon_error:m instanceof Error?m.message:String(m)}}if(d!==void 0)throw d;return g.daemon==="started"&&await e.firstSync?.(),{...i,registered:c,...g}})}s(pt,"joinDirectory");export{G as installedPluginIds,pt as joinDirectory,mt as joinedOwner};
|
package/dist/core/io.d.ts
CHANGED
|
@@ -59,6 +59,9 @@ interface ErrorPayloadBase {
|
|
|
59
59
|
http_status?: number;
|
|
60
60
|
/** Seconds the provider asked the caller to wait before retrying (a rate limit). */
|
|
61
61
|
retry_after_s?: number;
|
|
62
|
+
/** The structured details the runtime attached to a refusal the caller must act on (a `machine_required`
|
|
63
|
+
* answer's `candidates`). Rendered below the message and kept in `--json`. */
|
|
64
|
+
details?: Record<string, unknown>;
|
|
62
65
|
next: string;
|
|
63
66
|
}
|
|
64
67
|
export type ErrorPayload = ErrorPayloadBase & ({
|
|
@@ -116,9 +119,16 @@ export declare function diag(message: string): void;
|
|
|
116
119
|
* under the daemon's coarse `provider_*` code and the provider's HTTP
|
|
117
120
|
* status. The provider's code, `next`, `field` and `Retry-After` reach the
|
|
118
121
|
* caller unchanged; the status→code map would flatten them to `conflict`.
|
|
119
|
-
* 4.
|
|
122
|
+
* 4. A refusal the runtime itself rendered for `provider.call` (`options.providerCall`):
|
|
123
|
+
* its envelope code came from the runtime's own table (`machine_required`,
|
|
124
|
+
* `device_offline`, …), not from the HTTP status, so it is kept together with
|
|
125
|
+
* its `details`. The caller of `provider.call` is the only one who knows the
|
|
126
|
+
* envelope is that endpoint's, which is why this is an option and not inferred.
|
|
127
|
+
* 5. Otherwise map status→code (`STATUS_TO_CLI_CODE`) with the server message
|
|
120
128
|
* and a caller-supplied-or-default `next`. */
|
|
121
|
-
export declare function apiErrorToCliError(err: ApiError, next?: string
|
|
129
|
+
export declare function apiErrorToCliError(err: ApiError, next?: string, options?: {
|
|
130
|
+
providerCall?: boolean;
|
|
131
|
+
}): CrtrError;
|
|
122
132
|
/** Terminal error handler. Command-level failures (bad input, not-found,
|
|
123
133
|
* ambiguous) surface as the JSON response on stdout so the caller parses one
|
|
124
134
|
* contract. A daemon `ApiError` is translated to that same clean contract
|
package/dist/core/io.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
var
|
|
2
|
-
`;try{if((w(t,{throwIfNoEntry:!1})?.size??0)+Buffer.byteLength(
|
|
3
|
-
`)}
|
|
4
|
-
`)}
|
|
5
|
-
`)}
|
|
6
|
-
`),process.exitCode=e.exitCode;return}const t=e,
|
|
7
|
-
`),process.exitCode=u.GENERAL}
|
|
1
|
+
var b=Object.defineProperty;var i=(e,t)=>b(e,"name",{value:t,configurable:!0});import{appendFileSync as _,statSync as w}from"node:fs";import{CrtrError as c}from"./errors.js";import{ExitCode as u}from"../types.js";import{PREVIEW_RESULT_PATH_ENV as m}from"./preview-result-path.js";import{renderError as E}from"./render.js";import{ApiError as v}from"../api/index.js";let p=!1,d;const A=256*1024;function B(e){const t=process.env[m];d=t===void 0||t===""?void 0:{path:e,jsonl:[]}}i(B,"beginPreview");function y(e){const t=process.env[m];if(t===void 0||t==="")return;const s=`${JSON.stringify({path:d?.path??"",...e})}
|
|
2
|
+
`;try{if((w(t,{throwIfNoEntry:!1})?.size??0)+Buffer.byteLength(s,"utf8")>A)return;_(t,s,"utf8")}catch{}}i(y,"publishPreview");function q(e){d!==void 0&&(e!=null?y({result:e}):(d.jsonl.length>0||d.error!==void 0)&&y({...d.jsonl.length>0?{jsonl:d.jsonl}:{},...d.error===void 0?{}:{error:d.error}}))}i(q,"publishPreviewResult");function h(e){d?.jsonl.push(e)}i(h,"recordPreviewLine");function F(e){try{const t=JSON.parse(e);t!==null&&typeof t=="object"&&!Array.isArray(t)&&h(t)}catch{}}i(F,"recordPreviewJsonLine");function G(e){d!==void 0&&(d.error=e)}i(G,"recordPreviewError");function K(e){p=e}i(K,"setJsonOutput");function z(){return p}i(z,"isJsonOutput");class g extends c{static{i(this,"InputError")}payload;constructor(t,s=u.USAGE){super(t.error,t.message,s,{...t}),this.name="InputError",this.payload=t}}async function D(){if(process.stdin.isTTY)return"";const e=[];for await(const t of process.stdin)e.push(t);return Buffer.concat(e).toString("utf8")}i(D,"readStdinRaw");async function V(e=200){return process.stdin.isTTY?"":await new Promise(t=>{const s=[];let n=!1;const r=i(()=>{n||(n=!0,clearTimeout(a),process.stdin.removeListener("data",o),process.stdin.removeListener("end",l),process.stdin.removeListener("error",f),process.stdin.pause(),t(Buffer.concat(s).toString("utf8")))},"finish"),o=i(x=>s.push(x),"onData"),l=i(()=>r(),"onEnd"),f=i(()=>r(),"onError"),a=setTimeout(r,e);process.stdin.on("data",o),process.stdin.on("end",l),process.stdin.on("error",f),process.stdin.resume()})}i(V,"peekStdinRaw");function Y(e){process.stdout.write(JSON.stringify(e,null,2)+`
|
|
3
|
+
`)}i(Y,"emit");function H(e){h(e),process.stdout.write(JSON.stringify(e)+`
|
|
4
|
+
`)}i(H,"emitLine");function M(e){return new Promise(t=>{let s=!1;const n=i(o=>{o.code==="EPIPE"&&r(!1)},"onErr"),r=i(o=>{s||(s=!0,process.stdout.off("error",n),t(o))},"finish");process.stdout.on("error",n);try{process.stdout.write(e,o=>r(o==null))}catch{r(!1)}})}i(M,"writeStdout");function X(e){process.stderr.write(e+`
|
|
5
|
+
`)}i(X,"diag");function S(e){return e!==null&&typeof e=="object"&&!Array.isArray(e)}i(S,"isRecord");function T(e){if(e instanceof g)return e.payload;const t=e.details!==void 0?e.details:{},s=t.primaryCompleted===!0&&t.result!==null&&typeof t.result=="object"&&!Array.isArray(t.result)?O(t.result):void 0,n=t.next!==void 0?t.next:"Inspect the error and adjust the call. See -h for the schema.",r={error:e.code,message:e.message,received:t.received,field:t.field,...typeof t.http_status=="number"?{http_status:t.http_status}:{},...typeof t.retry_after_s=="number"?{retry_after_s:t.retry_after_s}:{},...S(t.details)?{details:t.details}:{}};return s===void 0?{...r,next:n}:{...r,primaryCompleted:!0,result:s,next:n}}i(T,"payloadOf");function O(e){const t=new WeakSet;try{const s=JSON.stringify(e,(n,r)=>{if(typeof r=="bigint")return{$crtrType:"bigint",value:r.toString()};if(typeof r=="number"&&!Number.isFinite(r))return{$crtrType:"number",value:String(r)};if(typeof r>"u"||typeof r=="function"||typeof r=="symbol")return{$crtrType:typeof r};if(r!==null&&typeof r=="object"){if(t.has(r))return{$crtrType:"circular"};t.add(r)}return r});if(s!==void 0){const n=JSON.parse(s);if(n!==null&&typeof n=="object"&&!Array.isArray(n))return n}}catch{}return{$crtrType:"unavailable",message:"The completed primary result could not be represented as JSON."}}i(O,"jsonSafeResult");const N={400:"invalid_request",404:"not_found",409:"conflict",422:"unprocessable"};function R(e,t,s={}){if(e.code==="daemon_restarting")return new c("daemon_restarting",e.message,u.NETWORK,{next:"crtrd is now on the new generation \u2014 retry the command."});if(e.code==="layout_migration_required")return new c("layout_migration_required",e.message,u.NETWORK,{next:"Run `crtr sys migrate` while the runtime is offline, then restart it."});if(e.code==="daemon_unavailable")return new c("daemon_unavailable",e.message,u.NETWORK,{next:"Start it with `crtr sys daemon start`, then retry."});const n=e.details,r=j(e,n,s.providerCall===!0);if(e.status===503||s.providerCall===!0&&e.status===504&&r.code!==void 0){const f=r.code??e.code,a={next:r.next??t??"Retry the command; if it persists, inspect the unavailable service named in the error."};return r.details!==void 0&&(a.details=r.details),r.code!==void 0&&(r.field!==void 0&&(a.field=r.field),r.received!==void 0&&(a.received=r.received),r.retryAfterS!==void 0&&(a.retry_after_s=r.retryAfterS)),new c(f,e.message,u.NETWORK,a)}const o=Array.isArray(n?.violations)?n.violations.map(f=>`${String(f.field)} ${String(f.problem)}${f.received!==void 0?` (received ${JSON.stringify(f.received)})`:""}`):[],l=n&&typeof n.error=="string"&&typeof n.message=="string"&&typeof n.next=="string"?n:{error:r.code??N[e.status]??e.code,...r.field!==void 0?{field:r.field}:{},...r.received!==void 0?{received:r.received}:{},...r.retryAfterS!==void 0?{retry_after_s:r.retryAfterS}:{},...r.details!==void 0?{details:r.details}:{},message:o.length>0?`${e.message}: ${o.join("; ")}`:e.message.trim()!==""?e.message:`request refused (HTTP ${e.status}, ${e.code}) with no message`,next:r.next??t??(r.retryAfterS!==void 0?`Wait ${r.retryAfterS}s, then retry; do not retry sooner.`:e.status>=500?"This is a crtr bug, not a bad call. Retry; if it persists, report it (the daemon log holds the full cause).":e.status===404?"List nodes with `crtr node inspect list`.":"Inspect the error and adjust the call. See -h for the schema.")};return new g(l,P(l.error,e.status))}i(R,"apiErrorToCliError");function j(e,t,s){const n=i(o=>typeof o=="string"&&/^[a-z][a-z0-9_]*$/.test(o),"snake");if(s&&e.origin!==void 0&&e.origin!=="provider"&&n(e.code)){const o={code:e.code};return(e.status===503||e.status===504)&&(o.next=e.retryable===!0?"Retry the command; if it persists, the machine or service named in the error is unavailable.":"Read the error before retrying: the action may already have run."),S(e.details)&&(o.details=e.details),e.param!==void 0&&(o.field=e.param),typeof e.retryAfterS=="number"&&(o.retryAfterS=e.retryAfterS),o}if(e.origin!=="provider"||t===void 0||t===null||typeof t!="object")return{};const r={};return typeof t.status=="number"&&n(t.code)&&(r.code=t.code),typeof t.next=="string"&&t.next.trim()!==""&&(r.next=t.next),typeof t.field=="string"&&t.field!==""?r.field=t.field:e.param!==void 0&&(r.field=e.param),t.received!==void 0&&t.received!==null&&(r.received=t.received),typeof e.retryAfterS=="number"&&(r.retryAfterS=e.retryAfterS),r}i(j,"providerSupplied");function P(e,t){return e==="not_found"||t===404?u.NOT_FOUND:e==="ambiguous"?u.AMBIGUOUS:u.USAGE}i(P,"exitForCliCode");function $(e){if(e instanceof v){$(R(e));return}if(e instanceof c){const n=T(e);d!==void 0&&y({error:n});const r=p?JSON.stringify(n,null,2):E(n);process.stdout.write(r+`
|
|
6
|
+
`),process.exitCode=e.exitCode;return}const t=e,s=t!=null&&typeof t.message=="string"?t.message:String(e);process.stderr.write(JSON.stringify({error:"internal",message:s,next:"This is a crtr bug, not a bad call. Retry; if it persists, report it."},null,2)+`
|
|
7
|
+
`),process.exitCode=u.GENERAL}i($,"handle");export{g as InputError,A as PREVIEW_RESULT_MAX_BYTES,R as apiErrorToCliError,B as beginPreview,X as diag,Y as emit,H as emitLine,$ as handle,z as isJsonOutput,V as peekStdinRaw,q as publishPreviewResult,D as readStdinRaw,G as recordPreviewError,F as recordPreviewJsonLine,h as recordPreviewLine,K as setJsonOutput,M as writeStdout};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var _=Object.defineProperty;var a=(e,
|
|
1
|
+
var _=Object.defineProperty;var a=(e,n)=>_(e,"name",{value:n,configurable:!0});import{directory as c,normalizeMachineId as h}from"../peer/directory.js";import{peerCall as m}from"../peer/client.js";import{ProviderError as t}from"./errors.js";const l={current:{list:a(()=>c.list(),"list"),lastList:a(()=>c.lastList(),"lastList"),call:a((e,n,i)=>m(e,n,i),"call")}},p="/v1/device/call",D=6e4,f=a(e=>({machine_id:e.machine_id,name:e.name,last_seen:e.last_seen}),"candidateView");function s(e,n){const i=e.map(r=>`${r.name} (${r.machine_id})`).join(", ");return new t("machine_required",n===void 0?`This account has ${e.length} devices: ${i}. Pick one with --machine <machine_id>.`:`${n} is not one of this account's devices. Devices: ${i}. Pick one with --machine <machine_id>.`,{param:"machine",details:{candidates:e.map(f)}})}a(s,"machineRequired");async function x(e,n=l.current){let i;try{i=await n.list()}catch(o){const d=n.lastList();if(d===null)throw new t("provider_unavailable",`The directory could not be reached to find your devices: ${o instanceof Error?o.message:String(o)}`,{origin:"directory"});i=d}const r=i.filter(o=>o.kind==="device");if(r.length===0)throw new t("provider_not_connected","No device is connected to this account. Install Northlight Desktop on your Mac and sign in.",{origin:"directory"});if(e!==void 0){const o=h(e),d=r.find(u=>u.machine_id===o);if(!d)throw s(r,e);return d}if(r.length>1)throw s(r,void 0);return r[0]}a(x,"selectDevice");function v(e){return{tool:e.tool,arguments:e.arguments,version:e.version,request_id:e.requestId,...e.runId===null?{}:{run_id:e.runId},deadline_at:e.deadlineAt,...e.upload===null?{}:{upload:e.upload}}}a(v,"deviceBody");async function A(e,n,i,r,o=l.current){const d=Math.max(1,n.deadlineAt-Date.now());return o.call(e.machine_id,{method:"POST",path:p,body:v(n)},{envelope:i,device:!0,signal:AbortSignal.any([r,AbortSignal.timeout(d)])})}a(A,"sendDeviceCall");function E(e,n,i){switch(n){case"timeout":return new t("device_timeout",`${e.name} did not answer in time.`,{origin:"runtime",details:{machine_id:e.machine_id}});case"directory_unreachable":case"no_directory":return new t("provider_unavailable",i,{origin:"directory"});case"unknown_machine":return new t("provider_not_connected",`${e.name} is no longer on this account.`,{origin:"directory"});case"not_a_crtr_peer":return new t("server_error",i);default:return new t("device_offline",i.trim()===""?`${e.name} is offline.`:i,{origin:"runtime",details:{machine_id:e.machine_id}})}}a(E,"deviceUnreachable");function b(e,n,i,r,o){if(n===401)return new t("device_offline",`${e.name}: Desktop is signed out. Sign in to Northlight Desktop again.`,{origin:"runtime",details:{machine_id:e.machine_id}});if(n===426){const d=o?.error?.details?.supported;return new t("peer_version_too_old",r??`${e.name} does not serve this device major. Update Northlight Desktop.`,{origin:"runtime",details:{machine_id:e.machine_id,...Array.isArray(d)?{supported:d}:{}}})}return n===502&&i==="machine_disconnected"?new t("device_disconnected",`${e.name} disconnected during the call; the action may have run.`,{origin:"runtime",details:{machine_id:e.machine_id}}):i==="payload_too_large"||i==="response_too_large"?new t("provider_error",r??"The call or its answer was too large for the relay (16 MiB).",{origin:"runtime",status:413,details:{code:i,message:r??null,status:n}}):null}a(b,"deviceFailure");export{D as DEFAULT_DEVICE_REQUEST_MS,p as DEVICE_CALL_PATH,v as deviceBody,l as deviceDeps,b as deviceFailure,E as deviceUnreachable,x as selectDevice,A as sendDeviceCall};
|
package/dist/core/render.d.ts
CHANGED
|
@@ -5,7 +5,7 @@ import type { ErrorPayload } from './io.js';
|
|
|
5
5
|
* after. Scalars (and nested objects) list as `- name: value` bullets, prose
|
|
6
6
|
* fields render as paragraphs, and arrays become a count lead-in plus a table
|
|
7
7
|
* or bullets. No root tag — the result is read as a continuation of the prompt. */
|
|
8
|
-
export declare function renderResult(result: Record<string, unknown>, help: LeafHelp): string;
|
|
8
|
+
export declare function renderResult(result: Record<string, unknown>, help: Pick<LeafHelp, 'output'>): string;
|
|
9
9
|
/** Render a structured failure as an instruction-shaped block: what broke, what
|
|
10
10
|
* was received, and the recovery road sign — the same recovery info the JSON
|
|
11
11
|
* payload carries, shaped for the model to act on. */
|
package/dist/core/render.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
var h=Object.defineProperty;var u=(e,n)=>h(e,"name",{value:n,configurable:!0});const
|
|
1
|
+
var h=Object.defineProperty;var u=(e,n)=>h(e,"name",{value:n,configurable:!0});const y=new Set(["follow_up","digest","message","next","result","content","body","guide","summary","note","reason","detail","details","hint","instruction","instructions","prompt","roadmap","report","answer","response","text"]);function $(e){return String(e).replace(/"/g,"'").replace(/\n/g," ")}u($,"attrEsc");function f(e){return e==null?"":Array.isArray(e)?e.map(f).join(", "):typeof e=="object"?JSON.stringify(e):String(e)}u(f,"scalarStr");function m(e){const n=JSON.stringify(e,null,2);let s="```";for(;n.includes(s);)s+="`";return`${s}json
|
|
2
2
|
${n}
|
|
3
|
-
${s}`}u(m,"fencedJson");function g(e,n){return typeof n!="string"?!1
|
|
3
|
+
${s}`}u(m,"fencedJson");function g(e,n){return typeof n!="string"?!1:y.has(e)?!0:n.includes(`
|
|
4
4
|
`)||n.length>80}u(g,"isProse");function j(e){return f(e).replace(/\|/g,"\\|").replace(/\n/g," ")}u(j,"cellEsc");function b(e,n){if(n.length===0)return`0 ${e}.`;const s=`${n.length} ${e}:`;if(n.every(o=>o!==null&&typeof o=="object"&&!Array.isArray(o))){const o=n,i=[];for(const p of o)for(const a of Object.keys(p))i.includes(a)||i.push(a);const d=`| ${i.join(" | ")} |`,t=`| ${i.map(()=>"---").join(" | ")} |`,r=o.map(p=>`| ${i.map(a=>j(p[a])).join(" | ")} |`).join(`
|
|
5
5
|
`);return`${s}
|
|
6
6
|
|
|
@@ -8,11 +8,11 @@ ${d}
|
|
|
8
8
|
${t}
|
|
9
9
|
${r}`}const c=n.map(o=>`- ${f(o)}`).join(`
|
|
10
10
|
`);return`${s}
|
|
11
|
-
${c}`}u(b,"renderArray");function
|
|
11
|
+
${c}`}u(b,"renderArray");function O(e,n){const s=n.output.map(t=>t.name);for(const t of Object.keys(e))s.includes(t)||s.push(t);const l=[],c=[],o=[];for(const t of s){const r=e[t];if(r!=null){if(Array.isArray(r)){o.push(b(t,r));continue}if(g(t,r)){c.push({name:t,text:r});continue}l.push(`- ${t}: ${f(r)}`)}}const i=[];l.length>0&&i.push(l.join(`
|
|
12
12
|
`));const d=c.length>1;for(const{name:t,text:r}of c)i.push(d?`**${t}:** ${r}`:r);for(const t of o)i.push(t);return i.join(`
|
|
13
13
|
|
|
14
|
-
`)}u(
|
|
15
|
-
${m(e.result)}`)),n.push(`Next: ${e.next}`),`<error code="${
|
|
14
|
+
`)}u(O,"renderResult");function _(e){const n=[e.message];return e.received!==void 0&&e.received!==null&&e.received!==""&&n.push(`received: ${f(e.received)}`),e.field!==void 0&&n.push(`field: ${e.field}`),e.http_status!==void 0&&n.push(`http status: ${e.http_status}`),e.retry_after_s!==void 0&&n.push(`retry after: ${e.retry_after_s}s`),e.details!==void 0&&Object.keys(e.details).length>0&&n.push(O(e.details,{output:[]})),"primaryCompleted"in e&&e.primaryCompleted===!0&&(n.push("primaryCompleted: true"),n.push(`Primary result (the command already completed):
|
|
15
|
+
${m(e.result)}`)),n.push(`Next: ${e.next}`),`<error code="${$(e.error)}">
|
|
16
16
|
${n.join(`
|
|
17
17
|
`)}
|
|
18
|
-
</error>`}u(
|
|
18
|
+
</error>`}u(_,"renderError");export{_ as renderError,O as renderResult};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
var W=Object.defineProperty;var a=(e,t)=>W(e,"name",{value:t,configurable:!0});import{toErrorBody as j}from"./map.js";import{emitEvent as N}from"../../core/events/emit.js";import{scopeAllowed as h,ScopeDeniedError as p}from"../../core/scopes.js";import{coveredByAny as J}from"../../shared/env.js";import{ApiError as d}from"../../api/errors.js";import{activeGrant as B,grantFor as x}from"../../core/grants/mirror.js";import{getNode as _}from"../../core/canvas/canvas.js";import{ownedRun as z,RunError as g}from"../../core/runs/operations.js";import{nodeMayReach as H,placeMayReach as S}from"../../core/runs/confinement.js";import{checkCronFire as M}from"../cron/authority.js";import{readConfig as P}from"../../core/config.js";import{SCOPE_VOCABULARY as I}from"../../core/scopes.js";import{operationFor as L}from"./operations.js";import{CONTROL_BODY_MAX_BYTES as F}from"../../core/llm/body-limits.js";import{getRequest as Y,getRequestByInboxTicket as V}from"../../core/human/requests.js";import{requireVisibleReview as R}from"../../core/review/store.js";import{getComment as O}from"../../core/review/comments.js";function D(e,t,o,s,r){const n=e.path,u=r&&typeof r=="object"&&!Array.isArray(r)?r:{};if(n==="/v1/human/requests"||n==="/v1/human/reviews"){if((n.endsWith("/reviews")?u.origin_node_id:u.requester_node_id)!==t.node)throw new d(403,"other_app_run","a broker may create a human request only for its own node");const f=u.source;if(f&&typeof f=="object"&&"nodeId"in f&&f.nodeId!==t.node)throw new d(403,"other_app_run","request source must name the calling node")}if(n==="/v1/human/reviews/self/comments"&&e.method==="GET"&&s.get("actor_node_id")!==t.node)throw new d(403,"other_app_run","self review requires the calling node");if(n.includes("/comments")&&e.method!=="GET"&&u.actor_node_id!==t.node)throw new d(403,"other_app_run","comment author must be the calling node");if(o.comment_id&&!O(o.comment_id))throw new d(404,"not_found","comment not found")}a(D,"authorizeHumanBody");function y(e){return e.class==="broker"||e.class==="cron"}a(y,"boundedByStoredScopes");function b(e){return e.class==="person"||e.class==="anonymous"||e.class==="machine"?null:e.class==="forwarded"?e.actor.kind==="app"?e.actor.app:null:e.app}a(b,"appOf");function q(e){return e.class==="person"||e.class==="forwarded"&&e.actor.kind==="person"}a(q,"actsAsPerson");function $(e){const t=x(e.actor.app,e.sub);if(!t)throw new d(403,"app_not_granted",`${e.actor.app} holds no active grant for ${e.sub} on this machine`);return t}a($,"forwardedGrant");function v(e){if(e.class==="person"||e.class==="anonymous"||e.class==="machine")return[];if(e.class==="forwarded"){if(e.actor.kind==="person")return[];const{resolved:o}=$(e),s=e.actor.node_scopes;return s===null?o:s.filter(r=>J(o,r))}if(e.class==="app")return e.scopes;if(e.class==="cron"){const o=M({grantee:e.app,sub:e.sub,grant_id:e.grant,scopes:e.scopes});if(o.allowed)return o.effectiveScopes;throw o.reason==="grant_missing"||o.reason==="grant_revoked"?new d(401,"grant_revoked",o.message):new d(403,"scope_missing",o.message)}const t=e.grant?B(e.grant):x(e.app,e.sub);if(t&&t.grantee===e.app&&t.sub===e.sub)return t.resolved;if(e.app==="app:terminal"&&!P("user").runtime?.issuer)return I;throw new d(401,"grant_revoked","the node grant is not active")}a(v,"resolvedScopes");function k(e){return"class"in e?b(e):e.grantee}a(k,"runCallerApp");function l(e,t,o){return t===void 0||!("class"in e)?!0:e.class==="cron"?S({run:e.origin_run??"",isolated:e.isolated},e.app,t,o):e.class==="forwarded"?e.actor.kind!=="app"||S(e.actor.place,e.actor.app,t,o):e.class!=="broker"?!0:H(e.node,e.app,t,o)}a(l,"brokerMayReach");function X(e,t,o){const s=k(e);if(s===t)return l(e,o,t);if(s===null)return!0;const r="class"in e?null:B(e.grant),n="class"in e?v(e):r?.sub===e.sub&&r.grantee===e.grantee?r.resolved:[];return h(n,`crtr:conversations:read:${t}`)&&(!("class"in e)||!y(e)||h(e.scopes,`crtr:conversations:read:${t}`))}a(X,"canReadRun");function Be(e,t,o=!1,s=!1){const r=_(t);if(!r||r.parent!==null||r.run_id!==t||!r.grantee||r.deleting_at&&!s)throw new g(404,"not_found","run not found");if(!X(e,r.grantee,t))throw new g(404,"not_found","run not found");if(o&&k(e)!==null&&k(e)!==r.grantee)throw new g(403,"other_app_run","cannot change another app's run");return z(t,r.grantee,s)}a(Be,"requireRun");function C(e,t,o){return q(e)?!0:e.class==="anonymous"||e.class==="machine"?!1:b(e)===t?l(e,o,t):h(v(e),`crtr:objects:read:${t}`)&&(!y(e)||h(e.scopes,`crtr:objects:read:${t}`))}a(C,"canReadNode");const K=new Set(["messages.post.58","messages.post.59"]);function Q(e,t,o){if(!e.classes.includes(t.class)&&!(t.class!=="anonymous"&&t.class!=="machine"&&t.class!=="forwarded"&&e.classes.includes("anonymous")))throw new d(403,"permission_error","this operation is not available to the caller");if(t.class==="forwarded"&&t.actor.kind==="app"&&$(t),t.class==="person"||t.class==="anonymous"||t.class==="machine"||q(t))return;const s=v(t);if(e.scope&&(!h(s,e.scope)||y(t)&&!h(t.scopes,e.scope)))throw new d(403,"scope_missing",`missing scope ${e.scope}`);if(t.class==="app")return;const r=b(t);t.class==="broker"&&Z(e,t,o,s);const n=o.id;if((e.resource==="node-read"||e.resource==="node-write")&&n!==void 0){const u=_(n);if(!u)return;if(e.resource==="node-write"&&u.grantee!==r&&!(K.has(e.name)&&h(s,`crtr:objects:message:${u.grantee??"app:terminal"}`)))throw new d(403,"other_app_run","cannot change another app's node");if(e.resource==="node-write"&&!l(t,n,u.grantee??"app:terminal"))throw new d(404,"not_found","node not found");if(e.resource==="node-read"&&!C(t,u.grantee??"app:terminal",n))throw new d(404,"not_found","node not found")}}a(Q,"authorizeOperation");function Z(e,t,o,s){if(e.resource==="daemon-bash"&&(!h(s,"crtr:files:rw:user:.")||!h(t.scopes,"crtr:files:rw:user:.")))throw new d(403,"scope_missing","missing scope crtr:files:rw:user:.");if(e.resource==="human"){const n=e.path,u=e.method==="GET";if(!u&&(n.endsWith("/respond")||n.endsWith("/submit")||n.endsWith("/dismiss")||n.endsWith("/progress")||n.endsWith("/comment-ranges")||n.includes("/feedback-comments")))throw new d(403,"permission_error","only the person may answer or edit the human inbox");const c=o.request_id?Y(o.request_id):o.ticket_id?V(o.ticket_id):null,f=o.comment_id?O(o.comment_id):null,w=o.review_id?R(o.review_id):f?R(f.review_id):null,i=c?.requester_node_id??w?.origin_node_id;if(i!=null){const m=_(i);if(m&&(u?!C(t,m.grantee??"app:terminal",i):m.grantee!==t.app))throw new d(u?404:403,u?"not_found":"other_app_run","human request belongs to another app");if(m&&!u&&!l(t,i,m.grantee??"app:terminal"))throw new d(404,"not_found","human request not found")}else if(c||w)throw new d(403,"permission_error","only the person may access a request without a node")}const r=o.id;if(e.resource==="node-own"&&r!==void 0&&r!==t.node)throw new d(403,"other_app_run","the operation requires the calling node")}a(Z,"authorizeBrokerOnly");function ee(e,t){if(!h(e,t))throw new p("the bearer token",t)}a(ee,"assertCeiling");function te(){return new d(403,"owner_only","this operation is owner-only; a scoped token does not reach it")}a(te,"ownerOnlyError");function xe(e){return e.map(t=>({...t,ownerOnly:!0}))}a(xe,"ownerOnly");class Se{static{a(this,"Router")}routes=[];register(t){const o=L(t.method,t.pattern);if(!o)throw new Error(`route has no operation row: ${t.method} ${t.pattern}`);if(this.routes.some(s=>s.method===t.method.toUpperCase()&&s.operation.path===t.pattern))throw new Error(`duplicate route: ${t.method} ${t.pattern}`);return this.routes.push({operation:o,method:t.method.toUpperCase(),segments:A(t.pattern),handler:t.handler,maxBodyBytes:t.maxBodyBytes??F}),this}registerAll(t){for(const o of t)this.register(o);return this}match(t,o){const s=t.toUpperCase(),r=A(o);for(const n of this.routes){if(n.method!==s)continue;const u=oe(n.segments,r);if(u!==null)return{handler:n.handler,params:u,operation:n.operation,maxBodyBytes:n.maxBodyBytes}}return null}async handle(t,o,s,r={class:"person"}){try{const n=new URL(t.url??"/","http://127.0.0.1"),u=(t.method??"GET").toUpperCase(),c=this.match(u,n.pathname);if(c===null){U(o,404,"not_found",`no route for ${u} ${n.pathname}`);return}if(Q(c.operation,r,c.params),s!==null){if(c.operation.classes.length===1&&c.operation.classes[0]==="person")throw te();c.operation.scope&&ee(s,c.operation.scope)}let f;try{f=await re(t,c.maxBodyBytes)}catch(m){const E=m instanceof T?"payload_too_large":"invalid_request";U(o,E==="payload_too_large"?413:400,E,m.message);return}r.class==="broker"&&c.operation.resource==="human"&&D(c.operation,r,c.params,n.searchParams,f);const w={method:u,path:n.pathname,params:c.params,query:n.searchParams,body:f,ceiling:s,principal:r,...r.class==="anonymous"&&typeof t.headers.authorization=="string"?{publishToken:t.headers.authorization.slice(7)}:{},req:t,res:o},i=await c.handler(w);o.writableEnded||ne(o,i)}catch(n){const{status:u,body:c}=j(n);u>=500&&N({event:"api.request.failed",level:"error",error:n instanceof Error?n:new Error(String(n)),...r.class==="broker"?{node_id:r.node}:{},fields:{method:(t.method??"GET").toUpperCase(),path:t.url??""}}),G(o,u,c)}}}function A(e){return e.split("/").filter(t=>t.length>0)}a(A,"splitPath");function oe(e,t){if(e.length!==t.length)return null;const o={};for(let s=0;s<e.length;s++){const r=e[s],n=t[s];if(r.startsWith(":"))o[r.slice(1)]=decodeURIComponent(n);else if(r!==n)return null}return o}a(oe,"matchSegments");class T extends Error{static{a(this,"BodyTooLargeError")}}async function re(e,t){const o=[];let s=0;for await(const n of e){const u=n;if(s+=u.length,s>t)throw new T(`request body exceeds ${t} bytes`);o.push(u)}if(s===0)return;const r=Buffer.concat(o).toString("utf8").trim();if(r!=="")try{return JSON.parse(r)}catch{throw new Error("request body is not valid JSON")}}a(re,"readJsonBody");function ne(e,t){if(e.headersSent)return;const o={...t.headers??{}};if(t.rawBody!==void 0){e.writeHead(t.status,o),e.end(t.rawBody);return}if(t.status===204||t.body===void 0){e.writeHead(t.status,o),e.end();return}o["content-type"]===void 0&&(o["content-type"]="application/json; charset=utf-8"),e.writeHead(t.status,o),e.end(JSON.stringify(t.body))}a(ne,"writeResult");function G(e,t,o){if(e.headersSent){e.end();return}e.writeHead(t,{"content-type":"application/json; charset=utf-8"}),e.end(JSON.stringify(o))}a(G,"writeJson");function U(e,t,o,s){G(e,t,{error:{code:o,message:s}})}a(U,"writeError");export{Se as Router,q as actsAsPerson,b as appOf,ee as assertCeiling,l as brokerMayReach,C as canReadNode,X as canReadRun,xe as ownerOnly,te as ownerOnlyError,Be as requireRun,v as resolvedScopes,k as runCallerApp};
|
|
1
|
+
var W=Object.defineProperty;var a=(e,t)=>W(e,"name",{value:t,configurable:!0});import{toErrorBody as j}from"./map.js";import{emitEvent as p}from"../../core/events/emit.js";import{scopeAllowed as f,ScopeDeniedError as N}from"../../core/scopes.js";import{coveredByAny as J}from"../../shared/env.js";import{ApiError as c}from"../../api/errors.js";import{activeGrant as B,grantFor as x}from"../../core/grants/mirror.js";import{getNode as _}from"../../core/canvas/canvas.js";import{ownedRun as z,RunError as g}from"../../core/runs/operations.js";import{nodeMayReach as H,placeMayReach as S}from"../../core/runs/confinement.js";import{checkCronFire as M}from"../cron/authority.js";import{readConfig as P}from"../../core/config.js";import{SCOPE_VOCABULARY as I}from"../../core/scopes.js";import{operationFor as L}from"./operations.js";import{CONTROL_BODY_MAX_BYTES as F}from"../../core/llm/body-limits.js";import{getRequest as Y,getRequestByInboxTicket as V}from"../../core/human/requests.js";import{requireVisibleReview as R}from"../../core/review/store.js";import{getComment as O}from"../../core/review/comments.js";function D(e,t,o,s,n){const r=e.path,u=n&&typeof n=="object"&&!Array.isArray(n)?n:{};if(r==="/v1/human/requests"||r==="/v1/human/reviews"){const d=r.endsWith("/reviews")?u.origin_node_id:u.requester_node_id,m=r==="/v1/human/requests"&&d==null;if(d!==t.node&&!m)throw new c(403,"other_app_run","a broker may create a human request only for its own node");const h=u.source;if(h&&typeof h=="object"&&"nodeId"in h&&h.nodeId!==t.node)throw new c(403,"other_app_run","request source must name the calling node")}if(r==="/v1/human/reviews/self/comments"&&e.method==="GET"&&s.get("actor_node_id")!==t.node)throw new c(403,"other_app_run","self review requires the calling node");if(r.includes("/comments")&&e.method!=="GET"&&u.actor_node_id!==t.node)throw new c(403,"other_app_run","comment author must be the calling node");if(o.comment_id&&!O(o.comment_id))throw new c(404,"not_found","comment not found")}a(D,"authorizeHumanBody");function y(e){return e.class==="broker"||e.class==="cron"}a(y,"boundedByStoredScopes");function b(e){return e.class==="person"||e.class==="anonymous"||e.class==="machine"?null:e.class==="forwarded"?e.actor.kind==="app"?e.actor.app:null:e.app}a(b,"appOf");function q(e){return e.class==="person"||e.class==="forwarded"&&e.actor.kind==="person"}a(q,"actsAsPerson");function $(e){const t=x(e.actor.app,e.sub);if(!t)throw new c(403,"app_not_granted",`${e.actor.app} holds no active grant for ${e.sub} on this machine`);return t}a($,"forwardedGrant");function v(e){if(e.class==="person"||e.class==="anonymous"||e.class==="machine")return[];if(e.class==="forwarded"){if(e.actor.kind==="person")return[];const{resolved:o}=$(e),s=e.actor.node_scopes;return s===null?o:s.filter(n=>J(o,n))}if(e.class==="app")return e.scopes;if(e.class==="cron"){const o=M({grantee:e.app,sub:e.sub,grant_id:e.grant,scopes:e.scopes});if(o.allowed)return o.effectiveScopes;throw o.reason==="grant_missing"||o.reason==="grant_revoked"?new c(401,"grant_revoked",o.message):new c(403,"scope_missing",o.message)}const t=e.grant?B(e.grant):x(e.app,e.sub);if(t&&t.grantee===e.app&&t.sub===e.sub)return t.resolved;if(e.app==="app:terminal"&&!P("user").runtime?.issuer)return I;throw new c(401,"grant_revoked","the node grant is not active")}a(v,"resolvedScopes");function k(e){return"class"in e?b(e):e.grantee}a(k,"runCallerApp");function l(e,t,o){return t===void 0||!("class"in e)?!0:e.class==="cron"?S({run:e.origin_run??"",isolated:e.isolated},e.app,t,o):e.class==="forwarded"?e.actor.kind!=="app"||S(e.actor.place,e.actor.app,t,o):e.class!=="broker"?!0:H(e.node,e.app,t,o)}a(l,"brokerMayReach");function X(e,t,o){const s=k(e);if(s===t)return l(e,o,t);if(s===null)return!0;const n="class"in e?null:B(e.grant),r="class"in e?v(e):n?.sub===e.sub&&n.grantee===e.grantee?n.resolved:[];return f(r,`crtr:conversations:read:${t}`)&&(!("class"in e)||!y(e)||f(e.scopes,`crtr:conversations:read:${t}`))}a(X,"canReadRun");function Be(e,t,o=!1,s=!1){const n=_(t);if(!n||n.parent!==null||n.run_id!==t||!n.grantee||n.deleting_at&&!s)throw new g(404,"not_found","run not found");if(!X(e,n.grantee,t))throw new g(404,"not_found","run not found");if(o&&k(e)!==null&&k(e)!==n.grantee)throw new g(403,"other_app_run","cannot change another app's run");return z(t,n.grantee,s)}a(Be,"requireRun");function C(e,t,o){return q(e)?!0:e.class==="anonymous"||e.class==="machine"?!1:b(e)===t?l(e,o,t):f(v(e),`crtr:objects:read:${t}`)&&(!y(e)||f(e.scopes,`crtr:objects:read:${t}`))}a(C,"canReadNode");const K=new Set(["messages.post.58","messages.post.59"]);function Q(e,t,o){if(!e.classes.includes(t.class)&&!(t.class!=="anonymous"&&t.class!=="machine"&&t.class!=="forwarded"&&e.classes.includes("anonymous")))throw new c(403,"permission_error","this operation is not available to the caller");if(t.class==="forwarded"&&t.actor.kind==="app"&&$(t),t.class==="person"||t.class==="anonymous"||t.class==="machine"||q(t))return;const s=v(t);if(e.scope&&(!f(s,e.scope)||y(t)&&!f(t.scopes,e.scope)))throw new c(403,"scope_missing",`missing scope ${e.scope}`);if(t.class==="app")return;const n=b(t);t.class==="broker"&&Z(e,t,o,s);const r=o.id;if((e.resource==="node-read"||e.resource==="node-write")&&r!==void 0){const u=_(r);if(!u)return;if(e.resource==="node-write"&&u.grantee!==n&&!(K.has(e.name)&&f(s,`crtr:objects:message:${u.grantee??"app:terminal"}`)))throw new c(403,"other_app_run","cannot change another app's node");if(e.resource==="node-write"&&!l(t,r,u.grantee??"app:terminal"))throw new c(404,"not_found","node not found");if(e.resource==="node-read"&&!C(t,u.grantee??"app:terminal",r))throw new c(404,"not_found","node not found")}}a(Q,"authorizeOperation");function Z(e,t,o,s){if(e.resource==="daemon-bash"&&(!f(s,"crtr:files:rw:user:.")||!f(t.scopes,"crtr:files:rw:user:.")))throw new c(403,"scope_missing","missing scope crtr:files:rw:user:.");if(e.resource==="human"){const r=e.path,u=e.method==="GET";if(!u&&(r.endsWith("/respond")||r.endsWith("/submit")||r.endsWith("/dismiss")||r.endsWith("/progress")||r.endsWith("/comment-ranges")||r.includes("/feedback-comments")))throw new c(403,"permission_error","only the person may answer or edit the human inbox");const d=o.request_id?Y(o.request_id):o.ticket_id?V(o.ticket_id):null,m=o.comment_id?O(o.comment_id):null,h=o.review_id?R(o.review_id):m?R(m.review_id):null,w=d?.requester_node_id??h?.origin_node_id;if(w!=null){const i=_(w);if(i&&(u?!C(t,i.grantee??"app:terminal",w):i.grantee!==t.app))throw new c(u?404:403,u?"not_found":"other_app_run","human request belongs to another app");if(i&&!u&&!l(t,w,i.grantee??"app:terminal"))throw new c(404,"not_found","human request not found")}else if(d||h)throw new c(403,"permission_error","only the person may access a request without a node")}const n=o.id;if(e.resource==="node-own"&&n!==void 0&&n!==t.node)throw new c(403,"other_app_run","the operation requires the calling node")}a(Z,"authorizeBrokerOnly");function ee(e,t){if(!f(e,t))throw new N("the bearer token",t)}a(ee,"assertCeiling");function te(){return new c(403,"owner_only","this operation is owner-only; a scoped token does not reach it")}a(te,"ownerOnlyError");function xe(e){return e.map(t=>({...t,ownerOnly:!0}))}a(xe,"ownerOnly");class Se{static{a(this,"Router")}routes=[];register(t){const o=L(t.method,t.pattern);if(!o)throw new Error(`route has no operation row: ${t.method} ${t.pattern}`);if(this.routes.some(s=>s.method===t.method.toUpperCase()&&s.operation.path===t.pattern))throw new Error(`duplicate route: ${t.method} ${t.pattern}`);return this.routes.push({operation:o,method:t.method.toUpperCase(),segments:A(t.pattern),handler:t.handler,maxBodyBytes:t.maxBodyBytes??F}),this}registerAll(t){for(const o of t)this.register(o);return this}match(t,o){const s=t.toUpperCase(),n=A(o);for(const r of this.routes){if(r.method!==s)continue;const u=oe(r.segments,n);if(u!==null)return{handler:r.handler,params:u,operation:r.operation,maxBodyBytes:r.maxBodyBytes}}return null}async handle(t,o,s,n={class:"person"}){try{const r=new URL(t.url??"/","http://127.0.0.1"),u=(t.method??"GET").toUpperCase(),d=this.match(u,r.pathname);if(d===null){U(o,404,"not_found",`no route for ${u} ${r.pathname}`);return}if(Q(d.operation,n,d.params),s!==null){if(d.operation.classes.length===1&&d.operation.classes[0]==="person")throw te();d.operation.scope&&ee(s,d.operation.scope)}let m;try{m=await ne(t,d.maxBodyBytes)}catch(i){const E=i instanceof T?"payload_too_large":"invalid_request";U(o,E==="payload_too_large"?413:400,E,i.message);return}n.class==="broker"&&d.operation.resource==="human"&&D(d.operation,n,d.params,r.searchParams,m);const h={method:u,path:r.pathname,params:d.params,query:r.searchParams,body:m,ceiling:s,principal:n,...n.class==="anonymous"&&typeof t.headers.authorization=="string"?{publishToken:t.headers.authorization.slice(7)}:{},req:t,res:o},w=await d.handler(h);o.writableEnded||re(o,w)}catch(r){const{status:u,body:d}=j(r);u>=500&&p({event:"api.request.failed",level:"error",error:r instanceof Error?r:new Error(String(r)),...n.class==="broker"?{node_id:n.node}:{},fields:{method:(t.method??"GET").toUpperCase(),path:t.url??""}}),G(o,u,d)}}}function A(e){return e.split("/").filter(t=>t.length>0)}a(A,"splitPath");function oe(e,t){if(e.length!==t.length)return null;const o={};for(let s=0;s<e.length;s++){const n=e[s],r=t[s];if(n.startsWith(":"))o[n.slice(1)]=decodeURIComponent(r);else if(n!==r)return null}return o}a(oe,"matchSegments");class T extends Error{static{a(this,"BodyTooLargeError")}}async function ne(e,t){const o=[];let s=0;for await(const r of e){const u=r;if(s+=u.length,s>t)throw new T(`request body exceeds ${t} bytes`);o.push(u)}if(s===0)return;const n=Buffer.concat(o).toString("utf8").trim();if(n!=="")try{return JSON.parse(n)}catch{throw new Error("request body is not valid JSON")}}a(ne,"readJsonBody");function re(e,t){if(e.headersSent)return;const o={...t.headers??{}};if(t.rawBody!==void 0){e.writeHead(t.status,o),e.end(t.rawBody);return}if(t.status===204||t.body===void 0){e.writeHead(t.status,o),e.end();return}o["content-type"]===void 0&&(o["content-type"]="application/json; charset=utf-8"),e.writeHead(t.status,o),e.end(JSON.stringify(t.body))}a(re,"writeResult");function G(e,t,o){if(e.headersSent){e.end();return}e.writeHead(t,{"content-type":"application/json; charset=utf-8"}),e.end(JSON.stringify(o))}a(G,"writeJson");function U(e,t,o,s){G(e,t,{error:{code:o,message:s}})}a(U,"writeError");export{Se as Router,q as actsAsPerson,b as appOf,ee as assertCeiling,l as brokerMayReach,C as canReadNode,X as canReadRun,xe as ownerOnly,te as ownerOnlyError,Be as requireRun,v as resolvedScopes,k as runCallerApp};
|
|
@@ -32,7 +32,7 @@ Then join your laptop to the account:
|
|
|
32
32
|
crtr sys join --directory https://account.northlight.ai --token <token> --name my-laptop
|
|
33
33
|
```
|
|
34
34
|
|
|
35
|
-
It answers with `registered: true`, the machine id (`runtime:<id>`), your user id and `daemon: restarted` (`daemon:
|
|
35
|
+
It answers with `registered: true`, the machine id (`runtime:<id>`), your user id and `daemon: restarted` (`daemon: started` when crtr was not running: join starts the daemon, and its first grant sync installs `crtr cloud …` and your other provider plugins). Your open viewers and nodes reconnect on their own.
|
|
36
36
|
3. Check it: `crtr cloud apps mine` lists the apps you manage. Before you have joined, it answers `not_joined` and prints these same steps.
|
|
37
37
|
|
|
38
38
|
Things to know before you join:
|
|
@@ -7,6 +7,12 @@ description: Behavior changes in @north-light/sdk that an app must act on, newes
|
|
|
7
7
|
|
|
8
8
|
Changes to `@north-light/sdk` that can change what an existing app sees. Newest first.
|
|
9
9
|
|
|
10
|
+
## 0.3.460
|
|
11
|
+
|
|
12
|
+
### `crouter-sdk keygen` releases its key file when the add fails (no app change)
|
|
13
|
+
|
|
14
|
+
When `keygen` cannot add the public key to the app (the machine is not joined, or the directory refuses), it deletes the `local/app-key.json` it had reserved, so a rerun after fixing the cause starts clean. It keeps the file when the add timed out or returned no key, because the add may have landed.
|
|
15
|
+
|
|
10
16
|
## 0.3.459
|
|
11
17
|
|
|
12
18
|
### `ProfileDTO.profile_path` replaces `memory_path` (additive, no app change)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@north-light/cli",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.460",
|
|
4
4
|
"description": "crtr — agent runtime with memory, plugins, and marketplaces",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -137,7 +137,7 @@
|
|
|
137
137
|
},
|
|
138
138
|
"license": "UNLICENSED",
|
|
139
139
|
"dependencies": {
|
|
140
|
-
"@north-light/identity": "0.3.
|
|
140
|
+
"@north-light/identity": "0.3.460",
|
|
141
141
|
"@earendil-works/pi-agent-core": "1.1.0",
|
|
142
142
|
"@earendil-works/pi-ai": "1.1.0",
|
|
143
143
|
"@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.460",
|
|
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.460",
|
|
10
10
|
"hasInstallScript": true,
|
|
11
11
|
"license": "UNLICENSED",
|
|
12
12
|
"workspaces": [
|
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"@earendil-works/pi-ai": "1.1.0",
|
|
19
19
|
"@earendil-works/pi-coding-agent": "1.1.0",
|
|
20
20
|
"@earendil-works/pi-tui": "1.1.0",
|
|
21
|
-
"@north-light/identity": "0.3.
|
|
21
|
+
"@north-light/identity": "0.3.460",
|
|
22
22
|
"cron-parser": "^5.6.0",
|
|
23
23
|
"esbuild": "^0.27.7",
|
|
24
24
|
"jose": "^6.2.12",
|
|
@@ -12095,17 +12095,17 @@
|
|
|
12095
12095
|
},
|
|
12096
12096
|
"packages/crouter-api": {
|
|
12097
12097
|
"name": "@north-light/api",
|
|
12098
|
-
"version": "0.3.
|
|
12098
|
+
"version": "0.3.460",
|
|
12099
12099
|
"license": "UNLICENSED"
|
|
12100
12100
|
},
|
|
12101
12101
|
"packages/crouter-env-docker": {
|
|
12102
12102
|
"name": "@north-light/env-docker",
|
|
12103
|
-
"version": "0.3.
|
|
12103
|
+
"version": "0.3.460",
|
|
12104
12104
|
"license": "UNLICENSED"
|
|
12105
12105
|
},
|
|
12106
12106
|
"packages/crouter-identity": {
|
|
12107
12107
|
"name": "@north-light/identity",
|
|
12108
|
-
"version": "0.3.
|
|
12108
|
+
"version": "0.3.460",
|
|
12109
12109
|
"license": "UNLICENSED",
|
|
12110
12110
|
"dependencies": {
|
|
12111
12111
|
"jose": "^6.2.1"
|
|
@@ -12113,20 +12113,20 @@
|
|
|
12113
12113
|
},
|
|
12114
12114
|
"packages/crouter-plugin": {
|
|
12115
12115
|
"name": "@north-light/plugin",
|
|
12116
|
-
"version": "0.3.
|
|
12116
|
+
"version": "0.3.460",
|
|
12117
12117
|
"license": "UNLICENSED",
|
|
12118
12118
|
"dependencies": {
|
|
12119
|
-
"@north-light/api": "0.3.
|
|
12120
|
-
"@north-light/identity": "0.3.
|
|
12119
|
+
"@north-light/api": "0.3.460",
|
|
12120
|
+
"@north-light/identity": "0.3.460"
|
|
12121
12121
|
}
|
|
12122
12122
|
},
|
|
12123
12123
|
"packages/crouter-sdk": {
|
|
12124
12124
|
"name": "@north-light/sdk",
|
|
12125
|
-
"version": "0.3.
|
|
12125
|
+
"version": "0.3.460",
|
|
12126
12126
|
"license": "UNLICENSED",
|
|
12127
12127
|
"dependencies": {
|
|
12128
|
-
"@north-light/api": "0.3.
|
|
12129
|
-
"@north-light/identity": "0.3.
|
|
12128
|
+
"@north-light/api": "0.3.460",
|
|
12129
|
+
"@north-light/identity": "0.3.460",
|
|
12130
12130
|
"jose": "^6.2.1"
|
|
12131
12131
|
},
|
|
12132
12132
|
"bin": {
|