@north-light/cli 0.3.581 → 0.3.583

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (85) hide show
  1. package/dist/api/contract.d.ts +1 -1
  2. package/dist/api/contract.js +1 -1
  3. package/dist/api/dto/health.d.ts +2 -0
  4. package/dist/api/error-codes.d.ts +5 -0
  5. package/dist/api/error-codes.js +1 -1
  6. package/dist/api/errors.js +1 -1
  7. package/dist/build-root.d.ts +11 -4
  8. package/dist/build-root.js +1 -1
  9. package/dist/cli.js +7 -7
  10. package/dist/clients/attach/viewer.js +428 -428
  11. package/dist/commands/api-client.d.ts +5 -0
  12. package/dist/commands/api-client.js +3 -3
  13. package/dist/commands/sys/desktop.d.ts +5 -0
  14. package/dist/commands/sys/desktop.js +1 -0
  15. package/dist/commands/sys/import/move.d.ts +55 -0
  16. package/dist/commands/sys/import/move.js +8 -0
  17. package/dist/commands/sys/import/resume.js +1 -1
  18. package/dist/commands/sys/import/start.d.ts +3 -4
  19. package/dist/commands/sys/import/start.js +1 -4
  20. package/dist/commands/sys/signin.js +2 -2
  21. package/dist/commands/sys.js +1 -1
  22. package/dist/core/canvas/pid.d.ts +6 -0
  23. package/dist/core/canvas/pid.js +5 -5
  24. package/dist/core/command-plugins/compose.js +1 -1
  25. package/dist/core/command.d.ts +17 -8
  26. package/dist/core/command.js +9 -9
  27. package/dist/core/directory/issuer.d.ts +3 -0
  28. package/dist/core/directory/issuer.js +1 -1
  29. package/dist/core/directory/person-credential.d.ts +28 -3
  30. package/dist/core/directory/person-credential.js +1 -1
  31. package/dist/core/directory/reach.d.ts +3 -0
  32. package/dist/core/directory/reach.js +1 -0
  33. package/dist/core/grants/mirror.d.ts +3 -0
  34. package/dist/core/grants/mirror.js +2 -2
  35. package/dist/core/graph/bodies-boot-copy.d.ts +16 -2
  36. package/dist/core/graph/bodies-boot-copy.js +1 -1
  37. package/dist/core/graph/bodies.d.ts +13 -4
  38. package/dist/core/graph/bodies.js +1 -1
  39. package/dist/core/graph/documents.js +10 -10
  40. package/dist/core/graph/repo-sync/exchange.js +2 -2
  41. package/dist/core/graph/repo-sync/ref-format.d.ts +7 -2
  42. package/dist/core/graph/repo-sync/ref-format.js +2 -2
  43. package/dist/core/import/bundle/layout.d.ts +2 -1
  44. package/dist/core/import/export/cloud.d.ts +4 -2
  45. package/dist/core/import/export/cloud.js +1 -1
  46. package/dist/core/import/export/dump.js +3 -3
  47. package/dist/core/import/load/copy.js +5 -5
  48. package/dist/core/import/load/rewrite.js +9 -5
  49. package/dist/core/import/load/stage.js +1 -1
  50. package/dist/core/import/load/verify.js +8 -7
  51. package/dist/core/io.js +4 -4
  52. package/dist/core/northlight/previews.d.ts +22 -0
  53. package/dist/core/northlight/previews.js +1 -0
  54. package/dist/core/providers/call.js +1 -1
  55. package/dist/core/providers/device.js +1 -1
  56. package/dist/core/providers/files.d.ts +2 -1
  57. package/dist/core/providers/files.js +1 -1
  58. package/dist/core/runtime/broker/cli-relay.js +1 -1
  59. package/dist/core/runtime/broker/model-adapter.js +3 -3
  60. package/dist/core/runtime/broker-ownership.d.ts +1 -0
  61. package/dist/core/runtime/broker-ownership.js +1 -0
  62. package/dist/core/runtime/first-run-offer.d.ts +7 -2
  63. package/dist/core/runtime/first-run-offer.js +10 -2
  64. package/dist/core/runtime/front-door.js +4 -4
  65. package/dist/core/runtime/runner/machine/executor.d.ts +7 -1
  66. package/dist/core/runtime/runner/machine/executor.js +1 -1
  67. package/dist/core/worktree-mutation-async.js +1 -1
  68. package/dist/daemon/api/handlers/daemon.js +1 -1
  69. package/dist/daemon/api/handlers/providers.js +1 -1
  70. package/dist/daemon/api/router.js +1 -1
  71. package/dist/daemon/boot-migrations.js +1 -1
  72. package/dist/daemon/cron/machine-exec.d.ts +6 -0
  73. package/dist/daemon/cron/machine-exec.js +1 -1
  74. package/dist/daemon/crtrd.d.ts +1 -1
  75. package/dist/daemon/crtrd.js +14 -14
  76. package/dist/daemon/machine-daemon.d.ts +7 -4
  77. package/dist/daemon/machine-daemon.js +1 -1
  78. package/dist/daemon/machine-link/config.d.ts +32 -1
  79. package/dist/daemon/machine-link/config.js +3 -3
  80. package/dist/daemon/machine-link/move-notify.d.ts +4 -0
  81. package/dist/daemon/machine-link/move-notify.js +1 -0
  82. package/package.json +2 -2
  83. package/packages/crouter-identity/package.json +1 -1
  84. package/runtime.lock.json +13 -13
  85. package/scripts/sandbox-bootstrap.sh +2 -1
@@ -1,4 +1,5 @@
1
1
  import { CrtrClient } from '../api/index.js';
2
+ import { CrtrError } from '../core/errors.js';
2
3
  import type { CanvasSource } from '../core/canvas/source.js';
3
4
  import type { NodeStatus, SubscriptionRef } from '../core/canvas/types.js';
4
5
  import type { Scope } from '../types.js';
@@ -39,6 +40,10 @@ export declare function readColdStartDiagnostic(): string | undefined;
39
40
  export declare function remoteTarget(): {
40
41
  client(timeoutMs?: number): CrtrClient;
41
42
  } | undefined;
43
+ /** A move whose cloud run reached `done` but whose computer has not switched over: the account holds the records, the local daemon stays drained,
44
+ * and no command may run against either side until `crtr sys signin` finishes it (design D14). The record is deleted only once the machine-role daemon
45
+ * is up (`finishMove`, or that daemon's own start), so a `machine.json` written before a failed restart does not lift this. */
46
+ export declare function moveUnfinished(): CrtrError | undefined;
42
47
  /** The local-socket `CrtrClient` every CLI verb uses. Autostart is on by
43
48
  * default: on a cold socket the injected `onColdSocket` hook fires
44
49
  * `ensureDaemon()` (spawns crtrd detached via the branded host), then the
@@ -1,4 +1,4 @@
1
- var y=Object.defineProperty;var a=(r,t)=>y(r,"name",{value:t,configurable:!0});import{closeSync as C,existsSync as v,fstatSync as R,openSync as N,readFileSync as m,readSync as T}from"node:fs";import{dirname as E,join as l}from"node:path";import{gunzipSync as A}from"node:zlib";import{CrtrClient as p,ApiError as u}from"../api/index.js";import{localClient as f}from"../api/node-transport.js";import{personBearer as O,notSignedIn as I,readPersonCredential as M}from"../core/directory/person-credential.js";import{readMachineConfig as x}from"../daemon/machine-link/config.js";import{apiErrorToCliError as w}from"../core/io.js";import{autostartDisabled as _,daemonAutostartStartupFailure as F,ensureDaemon as D}from"../daemon/manage.js";import{DAEMON_STARTUP_WINDOW_MS as b}from"../daemon/startup-policy.js";import{readState as k}from"../core/config.js";import{resolveRemoteCanvasTarget as L}from"../core/remote-canvas-target.js";import{RemoteCanvasSource as P}from"../core/canvas/remote-canvas-source.js";import{BROKER_RELEASED_MARKER as B,BROKER_RELAY_GENERATION as U,crtrHome as K}from"../core/canvas/paths.js";import{envTestNodeSocketWindowMs as W,isSandboxedNode as z}from"../shared/env.js";import{ownerOnly as j}from"../core/errors.js";function d(r){return{...r,fault:r.fault??null}}a(d,"withRowFault");function G(r,t){throw r instanceof u?w(r,t):r}a(G,"rethrowAsCliError");async function mt(r){try{return await g().getNode(r)}catch(t){if(t instanceof u&&t.status===404)return null;G(t)}}a(mt,"getNodeOrNull");const H="crtr_run_",$=4096,Y=20;function q(){let r;try{r=N(l(K(),"crtrd.log"),"r");const{size:t}=R(r),n=Math.max(0,t-$),s=t-n;if(s<=0)return;const e=Buffer.alloc(s);T(r,e,0,s,n);const o=e.toString("utf8").split(`
2
- `).filter(c=>c.trim()!=="").slice(-Y).join(`
1
+ var v=Object.defineProperty;var c=(r,t)=>v(r,"name",{value:t,configurable:!0});import{closeSync as S,existsSync as R,fstatSync as N,openSync as T,readFileSync as m,readSync as E}from"node:fs";import{dirname as O,join as l}from"node:path";import{gunzipSync as A}from"node:zlib";import{CrtrClient as p,ApiError as u}from"../api/index.js";import{localClient as f}from"../api/node-transport.js";import{northlightFetch as x,notSignedIn as M,readPersonCredential as I}from"../core/directory/person-credential.js";import{isMoveCommitted as F,moveUnfinishedError as D,readMachineConfig as U}from"../daemon/machine-link/config.js";import{northlightUnreachable as P}from"../core/directory/reach.js";import{apiErrorToCliError as w}from"../core/io.js";import{autostartDisabled as b,daemonAutostartStartupFailure as k,ensureDaemon as L}from"../daemon/manage.js";import{DAEMON_STARTUP_WINDOW_MS as _}from"../daemon/startup-policy.js";import{readState as K}from"../core/config.js";import{resolveRemoteCanvasTarget as W}from"../core/remote-canvas-target.js";import{RemoteCanvasSource as B}from"../core/canvas/remote-canvas-source.js";import{BROKER_RELEASED_MARKER as j,BROKER_RELAY_GENERATION as z,crtrHome as G}from"../core/canvas/paths.js";import{envTestNodeSocketWindowMs as q,isSandboxedNode as H}from"../shared/env.js";import{CrtrError as Y,ownerOnly as $}from"../core/errors.js";import{ExitCode as J}from"../types.js";function d(r){return{...r,fault:r.fault??null}}c(d,"withRowFault");function V(r,t){throw r instanceof u?w(r,t):r}c(V,"rethrowAsCliError");async function Rt(r){try{return await y().getNode(r)}catch(t){if(t instanceof u&&t.status===404)return null;V(t)}}c(Rt,"getNodeOrNull");const X="crtr_run_",Q=4096,Z=20;function tt(){let r;try{r=T(l(G(),"crtrd.log"),"r");const{size:t}=N(r),e=Math.max(0,t-Q),s=t-e;if(s<=0)return;const n=Buffer.alloc(s);E(r,n,0,s,e);const o=n.toString("utf8").split(`
2
+ `).filter(a=>a.trim()!=="").slice(-Z).join(`
3
3
  `).trim();return o===""?void 0:`crtrd.log (tail):
4
- ${o}`}catch{return}finally{if(r!==void 0)try{C(r)}catch{}}}a(q,"readColdStartDiagnostic");function J(){if(process.env.CRTR_API_SOCKET)return;const r=process.env.CRTR_API_TOKEN,t=process.env.CRTR_API_URL;if(r!==void 0&&r.startsWith(H)&&t)return{client:a(e=>new p({baseUrl:t,headers:{authorization:`Bearer ${r}`},...e!==void 0?{timeoutMs:e}:{}}),"client")};const n=x();if(n===void 0)return;if(M()===void 0)throw I("this computer is imported into Northlight, so crtr needs your sign-in");const s=a(async(e,i)=>{const o=new Headers(i?.headers);return o.set("authorization",`Bearer ${await O(n.directoryUrl)}`),fetch(e,{...i,headers:o})},"authed");return{client:a(e=>new p({baseUrl:n.apiUrl,fetch:s,...e!==void 0?{timeoutMs:e}:{}}),"client")}}a(J,"remoteTarget");function g(r){const t=r?.local===!0?void 0:J();if(t!==void 0)return t.client(r?.timeoutMs);const n=process.env.CRTR_API_SOCKET;if(n){const s=a(()=>{if(!v(n))try{const i=E(n),o=m(l(i,B),"utf8"),c=m(l(i,U),"utf8");if(o===c&&o!=="")return new u(503,"broker_released","This node's broker has been released, so crtr commands from its background jobs are not available.")}catch(i){if(i.code!=="ENOENT")throw i}},"availabilityFailure"),e=s();if(e!==void 0)throw w(e,"Run the command from the node's own turn instead.");return f({autostart:!1,availabilityFailure:s,coldStartPollWindowMs:(_()?W():void 0)??b,...r?.timeoutMs!==void 0?{timeoutMs:r.timeoutMs}:{}})}return _()?f({autostart:!1,...r?.timeoutMs!==void 0?{timeoutMs:r.timeoutMs}:{}}):f({...r?.timeoutMs!==void 0?{timeoutMs:r.timeoutMs}:{},onColdSocket:a(async()=>{D()},"onColdSocket"),coldStartDiagnostic:q,coldStartFailure:F,coldStartPollWindowMs:b})}a(g,"cliClient");const V=2e3;class S{static{a(this,"ApiCanvasSource")}client;rosterCache=null;rosterInFlight=null;constructor(t=g()){this.client=t}async roster(){const t=Date.now();return this.rosterCache!==null&&t-this.rosterCache.at<V?this.rosterCache.index:this.rosterInFlight!==null?this.rosterInFlight:(this.rosterInFlight=this.client.canvasRoster().then(n=>{const s={snapshot:n,subscriptions:new Map,subscribers:new Map};for(const e of n.edges){const i={node_id:e.to_id,active:e.active,created:e.created},o={node_id:e.from_id,active:e.active,created:e.created},c=s.subscriptions.get(e.from_id);c===void 0?s.subscriptions.set(e.from_id,[i]):c.push(i);const h=s.subscribers.get(e.to_id);h===void 0?s.subscribers.set(e.to_id,[o]):h.push(o)}return this.rosterCache={at:Date.now(),index:s},s}).finally(()=>{this.rosterInFlight=null}),this.rosterInFlight)}async orNull(t){try{return await t()}catch(n){if(n instanceof u&&n.status===404)return null;throw n}}async getNode(t){const n=await this.orNull(()=>this.client.getNode(t));return n===null?null:d(n)}async getRow(t){const n=await this.orNull(()=>this.client.getNode(t));return n===null?null:d(n)}async listNodes(t){const n=t?.status,s=t?.under===void 0?{}:{under:t.under,under_inclusive:!0};let e;if(Array.isArray(n)){const i=new Set(n);e=(await this.client.listNodes(s)).filter(c=>i.has(c.status))}else e=await this.client.listNodes({...n!==void 0?{status:n}:{},...s});return e.map(i=>d(i))}async browseSnapshot(){const t=await this.client.canvasBrowseCompressed(),n=JSON.parse(A(t).toString("utf8")),s={subscriptions:new Map,subscribers:new Map};for(const e of n.edges){const i=s.subscriptions.get(e.from_id)??[];i.push({node_id:e.to_id,active:e.active,created:e.created}),s.subscriptions.set(e.from_id,i);const o=s.subscribers.get(e.to_id)??[];o.push({node_id:e.from_id,active:e.active,created:e.created}),s.subscribers.set(e.to_id,o)}return this.rosterCache={at:Date.now(),index:s},n.nodes.map(e=>d(e))}async panelRoster(){const t=await this.roster();return t.snapshot!==void 0?t.snapshot:(this.rosterCache=null,(await this.roster()).snapshot)}async subscriptionsOf(t){return this.roster().then(n=>n.subscriptions.get(t)??[])}async subscribersOf(t){return this.roster().then(n=>n.subscribers.get(t)??[])}async view(t){const n=new Set([t]),s=[],e=(await this.subscriptionsOf(t)).map(i=>i.node_id);for(;e.length>0;){const i=e.shift();if(!n.has(i)){n.add(i),s.push(i);for(const o of await this.subscriptionsOf(i))n.has(o.node_id)||e.push(o.node_id)}}return s}async ticketCountsForNodes(t){return(await this.client.attentionCounts([...t])).counts}async ticketCountsForView(t){return this.ticketCountsForNodes([t,...await this.view(t)])}async newestReport(t){return(await this.client.getReports(t,{limit:1}))[0]??null}async hasActiveLiveSubscription(t){const n=(await this.subscriptionsOf(t)).filter(s=>s.active);for(const s of n){const e=await this.getRow(s.node_id);if(e&&(e.status==="active"||e.status==="idle"))return!0}return!1}}function pt(r,t=z()){const n=r?.scope??"user";if(t){if(r?.canvasName!==void 0)throw j("--canvas selects the person's remote canvas targets, which an app's node cannot reach","Omit --canvas; this node reads its own runtime's canvas.");return new S}const s=r?.canvasName??k(n).activeCanvas??null;if(!s)return new S;const{previewEndpoint:e,relayToken:i}=L(s,n);return new P(e,i)}a(pt,"cliCanvasSource");export{S as ApiCanvasSource,$ as COLD_START_DIAGNOSTIC_TAIL_BYTES,pt as cliCanvasSource,g as cliClient,mt as getNodeOrNull,q as readColdStartDiagnostic,J as remoteTarget,G as rethrowAsCliError};
4
+ ${o}`}catch{return}finally{if(r!==void 0)try{S(r)}catch{}}}c(tt,"readColdStartDiagnostic");function et(){if(process.env.CRTR_API_SOCKET)return;const r=process.env.CRTR_API_TOKEN,t=process.env.CRTR_API_URL;if(r!==void 0&&r.startsWith(X)&&t)return{client:c(i=>new p({baseUrl:t,headers:{authorization:`Bearer ${r}`},...i!==void 0?{timeoutMs:i}:{}}),"client")};const e=U();if(e===void 0)return;if(I()===void 0)throw M("this computer is imported into Northlight, so crtr needs your sign-in");const s=x(e.directoryUrl,e.apiUrl),n=c((i,o)=>s(i,o).catch(a=>{throw a instanceof Y?g(a):a}),"authed");return{client:c(i=>nt(new p({baseUrl:e.apiUrl,fetch:n,...i!==void 0?{timeoutMs:i}:{}}),e.apiUrl),"client")}}c(et,"remoteTarget");function nt(r,t){return new Proxy(r,{get(e,s){const n=Reflect.get(e,s,e);return typeof n!="function"?n:(...i)=>{const o=n.apply(e,i);return o instanceof Promise?o.catch(a=>{throw a instanceof u&&a.code==="request_timeout"?g(P(a,t)):a}):o}}})}c(nt,"unreachableOnTimeout");function g(r){const t=typeof r.details?.next=="string"?r.details.next:void 0;return new u(r.exitCode===J.NETWORK?503:401,r.code,r.message,{error:r.code,message:r.message,...t===void 0?{}:{next:t}})}c(g,"asApiError");function rt(){return F()?D():void 0}c(rt,"moveUnfinished");function y(r){if(r?.local!==!0){const s=rt();if(s!==void 0)throw s}const t=r?.local===!0?void 0:et();if(t!==void 0)return t.client(r?.timeoutMs);const e=process.env.CRTR_API_SOCKET;if(e){const s=c(()=>{if(!R(e))try{const i=O(e),o=m(l(i,j),"utf8"),a=m(l(i,z),"utf8");if(o===a&&o!=="")return new u(503,"broker_released","This node's broker has been released, so crtr commands from its background jobs are not available.")}catch(i){if(i.code!=="ENOENT")throw i}},"availabilityFailure"),n=s();if(n!==void 0)throw w(n,"Run the command from the node's own turn instead.");return f({autostart:!1,availabilityFailure:s,coldStartPollWindowMs:(b()?q():void 0)??_,...r?.timeoutMs!==void 0?{timeoutMs:r.timeoutMs}:{}})}return b()?f({autostart:!1,...r?.timeoutMs!==void 0?{timeoutMs:r.timeoutMs}:{}}):f({...r?.timeoutMs!==void 0?{timeoutMs:r.timeoutMs}:{},onColdSocket:c(async()=>{L()},"onColdSocket"),coldStartDiagnostic:tt,coldStartFailure:k,coldStartPollWindowMs:_})}c(y,"cliClient");const st=2e3;class C{static{c(this,"ApiCanvasSource")}client;rosterCache=null;rosterInFlight=null;constructor(t=y()){this.client=t}async roster(){const t=Date.now();return this.rosterCache!==null&&t-this.rosterCache.at<st?this.rosterCache.index:this.rosterInFlight!==null?this.rosterInFlight:(this.rosterInFlight=this.client.canvasRoster().then(e=>{const s={snapshot:e,subscriptions:new Map,subscribers:new Map};for(const n of e.edges){const i={node_id:n.to_id,active:n.active,created:n.created},o={node_id:n.from_id,active:n.active,created:n.created},a=s.subscriptions.get(n.from_id);a===void 0?s.subscriptions.set(n.from_id,[i]):a.push(i);const h=s.subscribers.get(n.to_id);h===void 0?s.subscribers.set(n.to_id,[o]):h.push(o)}return this.rosterCache={at:Date.now(),index:s},s}).finally(()=>{this.rosterInFlight=null}),this.rosterInFlight)}async orNull(t){try{return await t()}catch(e){if(e instanceof u&&e.status===404)return null;throw e}}async getNode(t){const e=await this.orNull(()=>this.client.getNode(t));return e===null?null:d(e)}async getRow(t){const e=await this.orNull(()=>this.client.getNode(t));return e===null?null:d(e)}async listNodes(t){const e=t?.status,s=t?.under===void 0?{}:{under:t.under,under_inclusive:!0};let n;if(Array.isArray(e)){const i=new Set(e);n=(await this.client.listNodes(s)).filter(a=>i.has(a.status))}else n=await this.client.listNodes({...e!==void 0?{status:e}:{},...s});return n.map(i=>d(i))}async browseSnapshot(){const t=await this.client.canvasBrowseCompressed(),e=JSON.parse(A(t).toString("utf8")),s={subscriptions:new Map,subscribers:new Map};for(const n of e.edges){const i=s.subscriptions.get(n.from_id)??[];i.push({node_id:n.to_id,active:n.active,created:n.created}),s.subscriptions.set(n.from_id,i);const o=s.subscribers.get(n.to_id)??[];o.push({node_id:n.from_id,active:n.active,created:n.created}),s.subscribers.set(n.to_id,o)}return this.rosterCache={at:Date.now(),index:s},e.nodes.map(n=>d(n))}async panelRoster(){const t=await this.roster();return t.snapshot!==void 0?t.snapshot:(this.rosterCache=null,(await this.roster()).snapshot)}async subscriptionsOf(t){return this.roster().then(e=>e.subscriptions.get(t)??[])}async subscribersOf(t){return this.roster().then(e=>e.subscribers.get(t)??[])}async view(t){const e=new Set([t]),s=[],n=(await this.subscriptionsOf(t)).map(i=>i.node_id);for(;n.length>0;){const i=n.shift();if(!e.has(i)){e.add(i),s.push(i);for(const o of await this.subscriptionsOf(i))e.has(o.node_id)||n.push(o.node_id)}}return s}async ticketCountsForNodes(t){return(await this.client.attentionCounts([...t])).counts}async ticketCountsForView(t){return this.ticketCountsForNodes([t,...await this.view(t)])}async newestReport(t){return(await this.client.getReports(t,{limit:1}))[0]??null}async hasActiveLiveSubscription(t){const e=(await this.subscriptionsOf(t)).filter(s=>s.active);for(const s of e){const n=await this.getRow(s.node_id);if(n&&(n.status==="active"||n.status==="idle"))return!0}return!1}}function Nt(r,t=H()){const e=r?.scope??"user";if(t){if(r?.canvasName!==void 0)throw $("--canvas selects the person's remote canvas targets, which an app's node cannot reach","Omit --canvas; this node reads its own runtime's canvas.");return new C}const s=r?.canvasName??K(e).activeCanvas??null;if(!s)return new C;const{previewEndpoint:n,relayToken:i}=W(s,e);return new B(n,i)}c(Nt,"cliCanvasSource");export{C as ApiCanvasSource,Q as COLD_START_DIAGNOSTIC_TAIL_BYTES,Nt as cliCanvasSource,y as cliClient,Rt as getNodeOrNull,rt as moveUnfinished,tt as readColdStartDiagnostic,et as remoteTarget,V as rethrowAsCliError};
@@ -0,0 +1,5 @@
1
+ export declare const DESKTOP_DMG_URL = "https://downloads.northlight.ai/desktop/mac/Northlight-Desktop.dmg";
2
+ /** Northlight Desktop's Developer ID team (Vallum Security Inc.). */
3
+ export declare const DESKTOP_TEAM_ID = "BRL7SHLNLU";
4
+ export declare const sysDesktopInstallLeaf: import("../../core/command.js").LeafDef;
5
+ export declare const sysDesktopBranch: import("../../core/command.js").BranchDef;
@@ -0,0 +1 @@
1
+ var x=Object.defineProperty;var n=(t,e)=>x(t,"name",{value:e,configurable:!0});import{execFile as T}from"node:child_process";import{existsSync as f,mkdtempSync as $,readFileSync as g,rmSync as I}from"node:fs";import{homedir as O,tmpdir as P}from"node:os";import{join as d}from"node:path";import{promisify as L}from"node:util";import{defineBranch as M,defineLeaf as U}from"../../core/command.js";import{CrtrError as j,general as p,usage as C}from"../../core/errors.js";import{atomicWriteJson as E}from"../../core/fs-utils.js";import{northlightUnreachable as w}from"../../core/directory/reach.js";import{notSignedIn as y,readPersonCredential as k,personAccount as F}from"../../core/directory/person-credential.js";import{readMachineConfig as R}from"../../daemon/machine-link/config.js";import{ExitCode as B}from"../../types.js";const D=L(T),S="https://downloads.northlight.ai/desktop/mac/Northlight-Desktop.dmg",_="BRL7SHLNLU",N="Northlight Desktop.app",J="/Applications",m=d(O(),"Library","Application Support","Northlight Desktop"),A=3e4,K=12e4,W=/^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i,b=n(t=>new Promise(e=>setTimeout(e,t)),"sleep"),h=n(()=>d(J,N),"appPath"),l=n(async(t,e)=>(await D(t,e,{maxBuffer:16*1024*1024})).stdout,"sh");async function q(){try{return await l("pgrep",["-f",`${h()}/Contents/MacOS/`]),!0}catch{return!1}}n(q,"desktopRunning");function v(t){try{return JSON.parse(g(d(m,"machine.json"),"utf8"))?.owner===t}catch{return!1}}n(v,"desktopPaired");async function G(t){const e=d(t,"Northlight-Desktop.dmg");try{await l("curl",["-fsSL","--max-time","600","-o",e,S])}catch(o){const a=String(o.stderr??"");throw/Could not resolve|Failed to connect|timed out|SSL|Connection|resolve host/i.test(a)?w(o,S):p(`the Desktop download failed: ${a.trim()||o.message}`)}return e}n(G,"download");async function H(t,e){await l("hdiutil",["attach","-nobrowse","-readonly","-mountpoint",e,t]);try{const o=d(e,N);try{await l("codesign",["--verify","--deep","--strict",o])}catch(i){throw p(`the downloaded Desktop failed its signature check, so it was not installed: ${String(i.stderr??i.message).trim()}`)}const a=(await D("codesign",["-dv","--verbose=4",o])).stderr,s=/^TeamIdentifier=(\S+)$/m.exec(a)?.[1];if(s!==_)throw p(`the downloaded Desktop is signed by team ${s??"none"}, not Northlight's ${_}, so it was not installed`);await l("ditto",[o,h()])}finally{await l("hdiutil",["detach",e,"-quiet"]).catch(()=>{})}}n(H,"installFrom");async function z(){const t=d(m,"install.json");for(const e=Date.now()+A;Date.now()<e;await b(500))try{const o=JSON.parse(g(t,"utf8")).install_id;if(typeof o=="string"&&W.test(o))return o}catch{}throw p(`Desktop opened but did not write ${t} within ${A/1e3} seconds`,{next:"Open Northlight Desktop yourself, then run `crtr sys desktop install` again."})}n(z,"waitForInstallId");async function V(t,e){const o=k(),a=`${t.replace(/\/+$/,"")}/v1/developer/desktop-enrollment`;let s;try{s=await fetch(a,{method:"POST",headers:{"content-type":"application/json",authorization:`Bearer ${o.secret}`},body:JSON.stringify({install_id:e}),signal:AbortSignal.timeout(3e4)})}catch(c){throw w(c,a)}const i=await s.text();let r={};try{r=i===""?{}:JSON.parse(i)}catch{}if(s.status===401){const c=typeof r.error=="object"?r.error?.code:r.error;if(c==="credential_revoked"||c==="credential_expired"||c==="credential_unknown")throw y(`the sign-in on this computer is no longer valid (${String(c)})`)}if(!s.ok||typeof r.device_code!="string"||typeof r.expires_in!="number"){const c=typeof r.error=="object"&&typeof r.error?.message=="string"?r.error.message:`HTTP ${s.status}`;throw p(`Northlight refused to pair Desktop: ${c}`)}return{device_code:r.device_code,expires_in:r.expires_in}}n(V,"mintPairing");const u=n(()=>l("open",["-a",h()]),"openApp"),Q=U({name:"install",description:"download, install, open and pair Northlight Desktop to this account",whenToUse:"a `device` command answered that no Northlight app is paired to this account, and the person said yes to installing it",help:{name:"sys desktop install",summary:"install Northlight Desktop in Applications and pair it to the account crtr is signed in to, with no code and no Allow click",guide:"macOS only, and only on a computer signed in with `crtr sys signin` and moved to Northlight. Downloads Desktop from downloads.northlight.ai, checks it is signed by Northlight, installs it, opens it, pairs it, then waits up to two minutes until Desktop on this Mac has registered to the account. Safe to run again: a Desktop on this Mac that is already registered to the account is only opened. macOS permission prompts (Full Disk Access, Messages) still appear when a feature needs them.",params:[],output:[{name:"installed",type:"boolean",required:!0,constraint:"True when this run installed Desktop; false when it was already installed and paired."}],outputKind:"object",effects:["Downloads and mounts the Desktop image, then copies the app to `/Applications/Northlight Desktop.app` (a copy that is running is left as it is and only paired). Opens Desktop. Calls `POST /v1/developer/desktop-enrollment` with the stored sign-in and writes `~/Library/Application Support/Northlight Desktop/pairing.json` (mode 0600), which Desktop deletes when it reads it."]},run:n(async()=>{if(process.platform!=="darwin")throw C("Northlight Desktop is a Mac app: this command only runs on macOS");const t=k();if(t===void 0||R()===void 0)throw y("this computer is not signed in to Northlight");const{id:e}=await F(t.directoryUrl);if(f(h())&&v(e))return await u(),{installed:!1};if(!(f(h())&&await q())){const i=$(d(P(),"crtr-desktop-"));try{await H(await G(i),d(i,"mnt"))}finally{I(i,{recursive:!0,force:!0})}}await u();const a=await z(),s=await V(t.directoryUrl,a);E(d(m,"pairing.json"),{issuer:t.directoryUrl.replace(/\/+$/,""),device_code:s.device_code},{mode:384}),await u();for(const i=Date.now()+K;Date.now()<i;await b(3e3))if(v(e))return{installed:!0};throw new j("desktop_not_paired","Desktop opened but did not pair.",B.GENERAL,{next:"If Desktop is signed in to another account, sign out there and run `crtr sys desktop install` again."})},"run"),render:n(t=>`<desktop installed="${t.installed}"/>`,"render")}),lt=M({name:"desktop",description:"Northlight Desktop on this Mac",whenToUse:"Northlight Desktop must be installed and paired to this account",help:{name:"sys desktop",summary:"install and pair Northlight Desktop"},children:[Q]});export{S as DESKTOP_DMG_URL,_ as DESKTOP_TEAM_ID,lt as sysDesktopBranch,Q as sysDesktopInstallLeaf};
@@ -0,0 +1,55 @@
1
+ import { ImportCloud } from '../../../core/import/export/cloud.js';
2
+ import { type MoveRecord } from '../../../daemon/machine-link/config.js';
3
+ import { type SignInPrompt } from '../../../core/directory/person-credential.js';
4
+ /** The directory a sign-in or move uses when none is typed: `runtime/machine.json`'s, else the stored sign-in's, else Northlight's (design D5). */
5
+ export declare function defaultDirectoryUrl(): string;
6
+ /** End the daemon's drain and confirm the daemon answers `drained: false`, retrying a lost answer: the daemon persists its drain before it waits for
7
+ * busy nodes, so a request that timed out or was lost may have left one on. Throws `drain_not_ended` when it cannot be confirmed ended.
8
+ * The daemon refuses to end a drain while a move record exists (the cloud outcome may be unknown), so every caller here has a positive answer that
9
+ * the run failed or never loaded: the record is settled (deleted) first, and no other path ends a computer's drain. */
10
+ export declare function endDrainConfirmed(client: {
11
+ endDaemonDrain(): Promise<unknown>;
12
+ }): Promise<void>;
13
+ /** The drain's end once the exporter stops: a run that never reached `done` ends it, confirmed. A run that reached `done` leaves it on — a hosted
14
+ * runtime for hosting's quiesce, a computer because its records now live in the account and local agents must not carry on against the local database. */
15
+ export declare function settleDrainAtEnd(done: boolean, client: {
16
+ endDaemonDrain(): Promise<unknown>;
17
+ }): Promise<void>;
18
+ export interface ExportStep {
19
+ name: string;
20
+ timings: string[];
21
+ }
22
+ /** One export: open the run (or use hosting's), upload blobs, drain, dump, upload rows and stores, load, wait for `done`. `step.name` is the step in
23
+ * progress, so a failure can name it. The drain ends here when the run did not reach `done`, and stays on when it did. */
24
+ export declare function exportRun(kind: 'machine' | 'hosted', cloud: ImportCloud, existingRun: string | undefined, step: ExportStep, onStarted?: (runId: string) => void, onFailed?: () => void): Promise<string>;
25
+ /** Activation, the second commit boundary: the asking node's message first (stored in the account as mail; the node can wake only once the machine
26
+ * link is up), then the machine credential (re-minted with `rotate` when an earlier attempt already minted one), `machine.json`, a restart of
27
+ * crtrd in machine role, and the move record's deletion. Safe to repeat. */
28
+ export declare function finishMove(move: MoveRecord): Promise<void>;
29
+ /** Move this computer into the signed-in person's account. Before the cloud run reaches `done`, any failure undoes the move: the drain ends, the
30
+ * sign-in is revoked and deleted, the move record goes, and `notify` (a node) is told through the local daemon. After `done` the move record says
31
+ * `committed` and activation is retried three times over a minute; still failing, the record, the sign-in and the drain stay for `crtr sys signin`. */
32
+ export declare function runMove(opts: {
33
+ directoryUrl: string;
34
+ apiUrl?: string;
35
+ notify: string | null;
36
+ }): Promise<{
37
+ runId: string;
38
+ account: string;
39
+ timings: string;
40
+ }>;
41
+ /** Where an earlier move stands before a new sign-in or move starts, and when the local daemon starts. A `running` record whose exporter is alive
42
+ * refuses; one whose exporter died is settled against the control plane, and only its positive answer moves it: a run that reached `done` becomes
43
+ * `committed` (returned, so the caller finishes the switch-over); a run still loading there refuses until it settles; a run that failed or never
44
+ * loaded is a failed move, undone here (record deleted and drain ended and confirmed, sign-in revoked, the asking node told). Any other outcome —
45
+ * the control plane unreachable or refusing, no working sign-in — leaves the record and the drain as they are and throws. With no working sign-in
46
+ * `show` signs back in first, and only to the account that owns the move (another account is refused, nothing written); without `show` (the daemon) it throws `not_signed_in`.
47
+ * Answers the committed record, or undefined when no move is left (an `activated` record only awaits its notification, which the machine daemon sends). */
48
+ export declare function settleEarlierMove(show?: (prompt: SignInPrompt & {
49
+ complete: string;
50
+ }) => void): Promise<MoveRecord | undefined>;
51
+ /** The local daemon's start is a starter of the move record (design-crtr-northlight-signin component 4): a `running` record whose exporter died (a crash,
52
+ * a reboot) leaves the daemon drained and the asking node untold until someone runs `crtr sys signin`, so the daemon settles it itself once it serves.
53
+ * Retries every minute while the settle cannot finish (the control plane unreachable, the run still loading); a run that reached `done` stays `committed`
54
+ * for `crtr sys signin`. Returns when there is nothing to settle, the exporter is alive, or `stopped()` says the daemon is closing. */
55
+ export declare function settleMoveAtDaemonStart(stopped: () => boolean, onRetry: (error: unknown) => void): Promise<void>;
@@ -0,0 +1,8 @@
1
+ var Y=Object.defineProperty;var l=(e,t)=>Y(e,"name",{value:t,configurable:!0});import{mkdtempSync as q,readFileSync as U,readdirSync as R,rmSync as K}from"node:fs";import{tmpdir as X}from"node:os";import{join as p}from"node:path";import{CrtrError as s,general as Q}from"../../../core/errors.js";import{ExitCode as h}from"../../../types.js";import{cliClient as T}from"../../api-client.js";import{ImportCloud as x,STORE_FILE as Z,STORE_ORDER as tt}from"../../../core/import/export/cloud.js";import{Uploader as et,localObjects as F,reviewSourceObject as rt}from"../../../core/import/export/files.js";import{bundleKeys as $}from"../../../core/import/bundle/layout.js";import{crtrVersion as nt}from"../../../shared/crtr-version.js";import{nodeArchive as ot}from"../../../core/import/export/archive.js";import{buildSourceJson as it}from"../../../core/import/export/source.js";import{runDaemonBodiesDump as at,runDaemonImportDump as st,runDaemonInstallId as ct,spawnDaemon as dt,stopAllDaemons as lt}from"../../../daemon/manage.js";import{deleteMoveRecord as N,moveUnfinishedError as ut,readMachineConfig as ft,readMoveRecord as A,writeMachineConfig as mt,writeMachineCredential as wt,writeMoveRecord as M}from"../../../daemon/machine-link/config.js";import{notifyMoveFinished as pt}from"../../../daemon/machine-link/move-notify.js";import{controlPlaneUrl as ht,forgetSignIn as gt,personAccount as G,personBearer as D,readPersonCredential as yt,signIn as _t,signOut as Et}from"../../../core/directory/person-credential.js";import{NORTHLIGHT_DIRECTORY_URL as vt}from"../../../core/directory/issuer.js";import{isPidAlive as L}from"../../../core/canvas/pid.js";const g=l(e=>new Promise(t=>setTimeout(t,e)),"sleep");function Kt(){return ft()?.directoryUrl??yt()?.directoryUrl??vt}l(Kt,"defaultDirectoryUrl");async function B(e){let t="the daemon still reports a drain";for(let r=1;r<=3;r++){try{if((await e.endDaemonDrain()).drained===!1)return}catch(n){t=n instanceof Error?n.message:String(n)}r<3&&await g(1e3)}throw new s("drain_not_ended",`the local daemon's pause could not be confirmed ended: ${t}`,h.GENERAL,{next:"Run `crtr sys import resume`."})}l(B,"endDrainConfirmed");async function $t(e,t){e||await B(t)}l($t,"settleDrainAtEnd");const St=3e4;async function bt(e,t,r){let n=2e3;for(;;){let i;try{i=await e.get(t)}catch(c){const u=c instanceof s?c.details?.status:void 0;if(c instanceof s&&(c.code==="not_signed_in"||typeof u=="number"&&u<500&&u!==408&&u!==429))throw new s("move_outcome_unknown",`could not confirm whether Northlight finished loading this computer's records: ${c.message}`,h.GENERAL,{next:"Run `crtr sys signin` to settle the move."});process.stderr.write(`waiting for Northlight: ${c instanceof Error?c.message:String(c)}
2
+ `),await g(n),n=Math.min(n*2,St);continue}if(n=2e3,i.state==="done")return;if(i.state==="failed"||i.state==="rolled_back")throw Q(`the import failed at ${i.failure?.step??i.step??"an unknown step"}: ${i.failure?.message??i.state}`,{code:i.failure?.code??"import_failed"});if(i.state==="uploading"&&r!==void 0)throw r;await g(2e3)}}l(bt,"awaitRunOutcome");async function Ut(e,t,r,n,i=()=>{},c=()=>{}){let u=Date.now();const o=l((m,f)=>{const w=Date.now(),v=`${m}=${((w-u)/1e3).toFixed(1)}s`;n.timings.push(v),process.stderr.write(`${m} ${((w-u)/1e3).toFixed(1)}s
3
+ `),u=w,n.name=f},"next"),d=q(p(X(),"crtr-import-")),S=T({local:!0});let y=!1,I=!1,_=!1,E;try{n.name="start";const{installId:m}=await ct(),f=r===void 0?await t.start({source_install_id:m,source_kind:e,release:nt()}):{run_id:r};i(f.run_id),n.name="blobs";const w=new et(t,f.run_id,a=>process.stderr.write(`${a}
4
+ `));await w.upload(F()),await at(d);for(const a of R(p(d,"rows","bodies")))await w.uploadBytes($.bodyRows(f.run_id,a.replace(/\.jsonl\.zst$/,"")),U(p(d,"rows","bodies",a)));o("blobs","drain"),y=!0,await S.drainDaemon({settle_limit_ms:6e5}),o("drain","dump"),await w.upload(F());const v=await st(d,e),k=[];for(const a of v.requests){const b=rt(a.id,a.dir,a.file);b!==null&&k.push(b)}await w.upload(k),o("dump","upload");for(const a of R(p(d,"rows"),{withFileTypes:!0}))a.isFile()&&await w.uploadBytes($.rows(f.run_id,a.name.replace(/\.jsonl\.zst$/,"")),U(p(d,"rows",a.name)));const V=new Set(R(p(d,"rows","bodies")).filter(a=>a.startsWith("pre-")));for(const a of R(p(d,"rows","bodies")))V.has(a)||await w.uploadBytes($.bodyRows(f.run_id,a.replace(/\.jsonl\.zst$/,"")),U(p(d,"rows","bodies",a)));const O=[];if(e==="hosted")for(const a of v.runningNodes){const{data:b,prefixes:W}=await ot(a);O.push(...W),await w.uploadBytes($.nodeFiles(f.run_id,a.node),b)}await w.uploadBytes($.source(f.run_id),Buffer.from(JSON.stringify(it(v.source,O))));const H=JSON.parse(U(p(d,Z),"utf8"));for(const a of tt)await t.putStore(f.run_id,a,H[a]);o("upload","load"),_=!0;let C;try{await t.load(f.run_id)}catch(a){C=a}try{await bt(t,f.run_id,C)}catch(a){throw a instanceof s&&a.code==="move_outcome_unknown"||(_=!1),a}return _=!1,I=!0,o("load","restart"),f.run_id}catch(m){throw E=m,m}finally{if(K(d,{recursive:!0,force:!0}),!I&&!_&&c(),y&&!_)try{await $t(I,S)}catch(m){if(E===void 0)throw m;const f=m instanceof s?m.code:"drain_not_ended";throw new s(f,`${E instanceof Error?E.message:String(E)} (and ${m instanceof Error?m.message:String(m)})`,h.GENERAL,{next:"Run `crtr sys import resume`."})}}}l(Ut,"exportRun");async function Rt(e){await pt(e);const t=new x(e.apiUrl,()=>D(e.directoryUrl));let r;try{r=await t.machineCredential(e.runId)}catch(n){if(!(n instanceof s)||n.details?.code!=="credential_exists")throw n;r=await t.machineCredential(e.runId,!0)}wt(r.credential),mt({apiUrl:e.apiUrl,environmentId:r.environment_id,runId:e.runId,directoryUrl:e.directoryUrl,account:e.account}),await lt(),await dt(),N()}l(Rt,"finishMove");const P=3,Nt=3e4;async function Xt(e){const t={name:"sign-in",timings:[]};let r,n="",i;try{const o=await G(e.directoryUrl),d=e.apiUrl??await ht(e.directoryUrl),S=new x(d,()=>D(e.directoryUrl));i={apiUrl:d,directoryUrl:e.directoryUrl,account:o,notify:e.notify,notified:!1,pid:process.pid},await Ut("machine",S,void 0,t,y=>{n=y,M({...i,runId:y,state:"running"})},N)}catch(o){throw o instanceof s&&o.code==="move_outcome_unknown"?o:await j(t.name,o,e.notify)}r={...i,runId:n,state:"committed"},M(r);let c;const u=Date.now();for(let o=1;o<=P;o++)try{return await Rt(r),t.timings.push(`restart=${((Date.now()-u)/1e3).toFixed(1)}s`),process.stderr.write(`restart ${((Date.now()-u)/1e3).toFixed(1)}s
5
+ `),{runId:r.runId,account:r.account.display,timings:t.timings.join(" ")}}catch(d){c=d,process.stderr.write(`switch-over attempt ${o} failed: ${d instanceof Error?d.message:String(d)}
6
+ `),o<P&&await g(Nt),r=A()??r}throw ut(c instanceof Error?c.message:String(c))}l(Xt,"runMove");async function j(e,t,r){const n=t instanceof Error?t.message:String(t),i=t instanceof s&&t.code==="drain_not_ended"?"crtr is still local, but its pause could not be ended: run `crtr sys import resume`":"crtr is still local and working as before",c=`The move to Northlight failed at ${e}: ${n}. ${i}.`,u=await J(c,r),o=t instanceof s&&t.code!=="error"?t.code:t instanceof s&&typeof t.details?.code=="string"?t.details.code:"move_failed";return new s(o,`the move to Northlight failed at ${e}: ${n}. ${i}, and ${z(u)}`,h.GENERAL)}l(j,"undoMove");const z=l(e=>e===void 0?"this computer is signed out.":`this computer's sign-in was removed here but could not be revoked at Northlight (${e}); revoke it on your Northlight account page.`,"signedOutText");async function J(e,t){N();let r;try{await Et()}catch(n){gt(),r=n instanceof Error?n.message:String(n),process.stderr.write(`could not revoke the sign-in at Northlight: ${r}
7
+ `)}if(t===null)return r;try{await T({local:!0}).sendMessage(t,{from:null,body:`${e} Tell the person the move failed.${r===void 0?"":` ${z(r)}`}`})}catch(n){process.stderr.write(`could not message ${t}: ${n instanceof Error?n.message:String(n)}
8
+ `)}return r}l(J,"dropMove");async function It(e){const t=A();if(t===void 0||t.state==="activated")return;const r=l(async()=>{await _t(t.directoryUrl,e,{accept:l(async o=>o.account.id===t.account.id?void 0:new s("signin_other_account",`This computer's move belongs to the Northlight account ${t.account.display}. Sign in to that account.`,h.USAGE),"accept")})},"signBackIn");if(t.state==="committed"){if(e!==void 0)try{await G(t.directoryUrl)}catch(o){if(!(o instanceof s&&o.code==="not_signed_in"))throw o;await r()}return t}if(t.pid>0&&L(t.pid))throw new s("move_running",`a move to Northlight is already running (process ${t.pid}).`,h.USAGE,{next:"Wait for it to finish; the node that asked is messaged when it does."});const n=l(()=>new x(t.apiUrl,()=>D(t.directoryUrl)).get(t.runId),"ask");let i;try{i=await n()}catch(o){if(!(o instanceof s&&o.code==="not_signed_in")||e===void 0)throw o;await r(),i=await n()}if(i.state==="done"){const o={...t,state:"committed"};return M(o),o}if(i.state==="loading"||i.state==="committed")throw new s("move_running","Northlight is still loading this computer's records from an earlier move.",h.USAGE,{next:"Retry in a minute."});if(i.state!=="failed"&&i.state!=="rolled_back"&&i.state!=="uploading")throw new s("move_outcome_unknown","Northlight did not report a settled outcome for this move.",h.GENERAL,{next:"Run `crtr sys signin` to settle the move."});const c=i.failure?.step??"an earlier step",u=i.failure==null?"the process that was moving this computer stopped before Northlight had the records":i.failure.message;N();try{await B(T({local:!0}))}catch(o){throw await j(c,new s("drain_not_ended",`${u} (and ${o instanceof Error?o.message:String(o)})`,h.GENERAL),t.notify)}await J(`The move to Northlight failed at ${c}: ${u}. crtr is still local and working as before.`,t.notify)}l(It,"settleEarlierMove");const Tt=6e4,xt=5e3;async function Qt(e,t){for(await g(xt);!e();){const r=A();if(r===void 0||r.state!=="running"||r.pid>0&&L(r.pid))return;try{await It();return}catch(n){if(t(n),n instanceof s&&n.code==="not_signed_in")return}await g(Tt)}}l(Qt,"settleMoveAtDaemonStart");export{Kt as defaultDirectoryUrl,B as endDrainConfirmed,Ut as exportRun,Rt as finishMove,Xt as runMove,$t as settleDrainAtEnd,It as settleEarlierMove,Qt as settleMoveAtDaemonStart};
@@ -1 +1 @@
1
- var t=Object.defineProperty;var a=(e,n)=>t(e,"name",{value:n,configurable:!0});import{defineLeaf as r}from"../../../core/command.js";import{cliClient as i}from"../../api-client.js";const m=r({name:"resume",description:"end the pause an import left on this daemon",whenToUse:"an import failed or its exporter was killed, so this runtime stayed paused and should carry on",help:{name:"sys import resume",summary:"end the drain so the daemon takes wakes, cron fires and deliveries again",guide:"Safe to repeat: when the daemon is not drained it says so and exits 0.",params:[],output:[{name:"was_drained",type:"boolean",required:!0,constraint:"Whether the daemon was drained before this call."}],outputKind:"object",effects:["Deletes the `drain` row of `runtime_state`; nothing else changes."]},run:a(async()=>({was_drained:(await i().endDaemonDrain()).was_drained===!0}),"run"),render:a(e=>e.was_drained===!0?'<import drain="ended"/>':'<import drain="none">the daemon was not drained</import>',"render")});export{m as resume};
1
+ var n=Object.defineProperty;var t=(e,a)=>n(e,"name",{value:a,configurable:!0});import{defineLeaf as r}from"../../../core/command.js";import{cliClient as s}from"../../api-client.js";const m=r({name:"resume",description:"end the pause an import left on this daemon",whenToUse:"an import failed or its exporter was killed, so this runtime stayed paused and should carry on",help:{name:"sys import resume",summary:"end the drain so the daemon takes wakes, cron fires and deliveries again",guide:"Safe to repeat: when the daemon is not drained it says so and exits 0. Refused (409 `conflict`) while a move record exists: the account may hold this computer's records, so agents here must not carry on against the local database until `crtr sys signin` settles the move.",params:[],output:[{name:"was_drained",type:"boolean",required:!0,constraint:"Whether the daemon was drained before this call."}],outputKind:"object",effects:["Deletes the `drain` row of `runtime_state`; nothing else changes. Refused while `runtime/import/move.json` exists."]},run:t(async()=>({was_drained:(await s({local:!0}).endDaemonDrain()).was_drained===!0}),"run"),render:t(e=>e.was_drained===!0?'<import drain="ended"/>':'<import drain="none">the daemon was not drained</import>',"render")});export{m as resume};
@@ -1,5 +1,4 @@
1
- /** The drain's end: a failed or errored run ends it; a hosted run that reached `done` leaves it on for hosting's quiesce (`crtr sys import resume` ends it). */
2
- export declare function settleDrainAtEnd(kind: 'machine' | 'hosted', done: boolean, client: {
3
- endDaemonDrain(): Promise<unknown>;
4
- }): Promise<void>;
1
+ /** Set by `crtr sys signin` on the detached exporter it starts from inside a node: the signin process, which the exporter outlives before it drains,
2
+ * so it is never in a broker's process tree. */
3
+ export declare const WAIT_FOR_PID_ENV = "CRTR_IMPORT_WAIT_PID";
5
4
  export declare const sysImportBranch: import("../../../core/command.js").BranchDef;
@@ -1,4 +1 @@
1
- var j=Object.defineProperty;var l=(t,s)=>j(t,"name",{value:s,configurable:!0});import{mkdtempSync as B,readFileSync as f,readdirSync as w,rmSync as C}from"node:fs";import{tmpdir as E}from"node:os";import{join as o}from"node:path";import{defineBranch as F,defineLeaf as M}from"../../../core/command.js";import{general as x,usage as k}from"../../../core/errors.js";import{cliClient as U}from"../../api-client.js";import{ImportCloud as P,STORE_FILE as A,STORE_ORDER as K}from"../../../core/import/export/cloud.js";import{Uploader as L,localObjects as I,reviewSourceObject as z}from"../../../core/import/export/files.js";import{bundleKeys as h}from"../../../core/import/bundle/layout.js";import{crtrVersion as J}from"../../../shared/crtr-version.js";import{nodeArchive as V}from"../../../core/import/export/archive.js";import{buildSourceJson as W}from"../../../core/import/export/source.js";import{runDaemonBodiesDump as G,runDaemonImportDump as H,runDaemonInstallId as Q,spawnDaemon as X,stopAllDaemons as Y}from"../../../daemon/manage.js";import{writeMachineConfig as Z,writeMachineCredential as ee}from"../../../daemon/machine-link/config.js";import{ensureSignedIn as te}from"../../../core/directory/person-credential.js";import{showSignIn as re}from"../signin.js";import{insideBroker as ne}from"../join.js";import{resume as ie}from"./resume.js";const oe=l(t=>new Promise(s=>setTimeout(s,t)),"sleep");async function se(t,s,i){t==="hosted"&&s||await i.endDaemonDrain().catch(a=>{process.stderr.write(`warning: the daemon is still drained (ending the drain failed: ${a instanceof Error?a.message:String(a)}); run \`crtr sys import resume\` to end it
2
- `)})}l(se,"settleDrainAtEnd");const ae=M({name:"start",description:"export this runtime into the person's Northlight account",whenToUse:"moving this computer or hosted runtime's canvas into Northlight: run it from a plain terminal, not from inside a node",help:{name:"sys import start",summary:"upload blobs, pause the daemon, dump rows and file stores, and have the control plane load them",guide:"Order: open the run, upload blobs and request files, drain the daemon (turns in flight settle for up to 10 minutes), upload them again, dump every table and the file stores, upload the rows, request the load and wait for `done` or `failed`, printing each step's time. On `done` for a machine this install writes `runtime/machine.json` and the machine credential and restarts the daemon in machine role. On `done` for a hosted runtime the daemon stays drained, because hosting quiesces and stops the VM next and a turn taken in between would write rows the shared database never sees. On `failed`, or any error, the drain ends and this runtime carries on as before; a killed exporter never ends it, so `crtr sys import resume` does. A machine import signs the person in first when this computer has no stored sign-in (`crtr sys signin`: a code and the directory's confirm page, which you allow in the browser) and uses that sign-in's bearer. With `--run`, hosting has already opened the run: the exporter uses it and reads that run's import token from the environment variable `CRTR_IMPORT_TOKEN`.",params:[{kind:"flag",name:"api-url",type:"string",required:!0,constraint:"Control plane base URL (http or https)."},{kind:"flag",name:"kind",type:"enum",choices:["machine","hosted"],required:!0,constraint:"`machine` for a person's computer, `hosted` for a hosted runtime (also archives running nodes' files)."},{kind:"flag",name:"run",type:"string",required:!1,constraint:"An import run hosting opened for this runtime (only with `--kind hosted`); its import token is read from `CRTR_IMPORT_TOKEN`."},{kind:"flag",name:"directory-url",type:"string",required:!1,constraint:"Directory base URL (http or https) the person signs in against; required with `--kind machine` (stored in `runtime/machine.json`). Not used with `--kind hosted`."}],output:[{name:"run_id",type:"string",required:!0,constraint:"The import run."},{name:"state",type:"string",required:!0,constraint:"Final state of the run, `done`."},{name:"timings",type:"string",required:!0,constraint:"Per-step seconds."}],outputKind:"object",effects:["Drains the local daemon (no new wake or cron fire) until the load finishes; a failed run ends the drain. A hosted run that reaches `done` leaves the daemon drained (the drain is a `runtime_state` row, so it survives a daemon restart) until `crtr sys import resume`.","Machine, on `done`: writes `runtime/machine.json` and `runtime/secrets/machine-credential` (0600) and restarts the daemon in machine role.","Opens a temporary directory holding the opened file stores (0600), removed before the command ends."]},run:l(async t=>{const s=String(t["api-url"]),i=t.kind,a=t.run===void 0?void 0:String(t.run);if(a!==void 0&&i!=="hosted")throw k("--run takes an existing run, which only a hosted import has: use --kind hosted");const g=t["directory-url"]===void 0?void 0:String(t["directory-url"]);if(i==="machine"&&g===void 0)throw k("--kind machine needs --directory-url: the directory the person signs in against");if(ne())throw x("import_inside_node: run this from a plain terminal; a node's broker ends with the daemon restart",{code:"import_inside_node"});const v=(a===void 0?await te(g,re):process.env.CRTR_IMPORT_TOKEN??"").trim();if(v==="")throw k("set CRTR_IMPORT_TOKEN to the run's import token");const m=new P(s,v),R=[];let y=Date.now();const u=l(c=>{const r=Date.now();R.push(`${c}=${((r-y)/1e3).toFixed(1)}s`),process.stderr.write(`${c} ${((r-y)/1e3).toFixed(1)}s
3
- `),y=r},"step"),n=B(o(E(),"crtr-import-")),O=U({local:!0});let _=!1,T=!1;try{const{installId:c}=await Q(),r=a===void 0?await m.start({source_install_id:c,source_kind:i,release:J()}):{run_id:a},d=new L(m,r.run_id,e=>process.stderr.write(`${e}
4
- `));await d.upload(I()),await G(n);for(const e of w(o(n,"rows","bodies")))await d.uploadBytes(h.bodyRows(r.run_id,e.replace(/\.jsonl\.zst$/,"")),f(o(n,"rows","bodies",e)));u("blobs"),await O.drainDaemon({settle_limit_ms:6e5}),_=!0,u("drain"),await d.upload(I());const b=await H(n,i),S=[];for(const e of b.requests){const p=z(e.id,e.dir,e.file);p!==null&&S.push(p)}await d.upload(S),u("dump");for(const e of w(o(n,"rows"),{withFileTypes:!0}))e.isFile()&&await d.uploadBytes(h.rows(r.run_id,e.name.replace(/\.jsonl\.zst$/,"")),f(o(n,"rows",e.name)));const $=new Set(w(o(n,"rows","bodies")).filter(e=>e.startsWith("pre-")));for(const e of w(o(n,"rows","bodies")))$.has(e)||await d.uploadBytes(h.bodyRows(r.run_id,e.replace(/\.jsonl\.zst$/,"")),f(o(n,"rows","bodies",e)));const D=[];if(i==="hosted")for(const e of b.runningNodes){const{data:p,prefixes:q}=await V(e);D.push(...q),await d.uploadBytes(h.nodeFiles(r.run_id,e.node),p)}await d.uploadBytes(h.source(r.run_id),Buffer.from(JSON.stringify(W(b.source,D))));const N=JSON.parse(f(o(n,A),"utf8"));for(const e of K)await m.putStore(r.run_id,e,N[e]);for(u("upload"),await m.load(r.run_id);;){const e=await m.get(r.run_id);if(e.state==="done")break;if(e.state==="failed"||e.state==="rolled_back")throw x(`the import failed at ${e.failure?.step??e.step??"an unknown step"}: ${e.failure?.message??e.state}`,{code:e.failure?.code??"import_failed"});await oe(2e3)}if(u("load"),T=!0,i==="machine"){const e=await m.machineCredential(r.run_id);ee(e.credential),Z({apiUrl:s,environmentId:e.environment_id,runId:r.run_id,directoryUrl:g}),_=!1,await Y(),await X(),u("restart")}return{run_id:r.run_id,state:"done",timings:R.join(" ")}}finally{C(n,{recursive:!0,force:!0}),_&&await se(i,T,O)}},"run"),render:l(t=>`<import run="${t.run_id}" state="${t.state}" timings="${t.timings}"/>`,"render")}),De=F({name:"import",description:"move this runtime into the person's Northlight account",whenToUse:"importing this computer or hosted runtime into Northlight",help:{name:"sys import",summary:"export this runtime to Northlight",model:"The exporter pauses the local daemon, uploads a bundle the control plane loads into the shared database, and on a computer switches it to the machine role."},children:[ae,ie]});export{se as settleDrainAtEnd,De as sysImportBranch};
1
+ var g=Object.defineProperty;var i=(e,t)=>g(e,"name",{value:t,configurable:!0});import{defineBranch as w,defineLeaf as y}from"../../../core/command.js";import{general as v,usage as r}from"../../../core/errors.js";import{ImportCloud as k}from"../../../core/import/export/cloud.js";import{ensureSignedIn as p}from"../../../core/directory/person-credential.js";import{isPidAlive as _}from"../../../core/canvas/pid.js";import{showSignIn as d}from"../signin.js";import{insideBroker as R}from"../join.js";import{defaultDirectoryUrl as f,exportRun as T,settleEarlierMove as b,finishMove as I,runMove as O}from"./move.js";import{resume as N}from"./resume.js";const q="CRTR_IMPORT_WAIT_PID";async function U(e){for(;_(e);)await new Promise(t=>setTimeout(t,200))}i(U,"waitForExit");const x=y({name:"start",description:"export this runtime into the person's Northlight account",whenToUse:"moving this computer or hosted runtime's canvas into Northlight: run it from a plain terminal, not from inside a node",help:{name:"sys import start",summary:"upload blobs, pause the daemon, dump rows and file stores, and have the control plane load them",guide:"Order: open the run, upload blobs and request files, drain the daemon (turns in flight settle for up to 10 minutes), upload them again, dump every table and the file stores, upload the rows, request the load and wait for `done` or `failed`, printing each step's time. Before `done`, a failure ends the drain and this runtime carries on as before; a computer's move also signs it out. On `done` the daemon stays drained: a hosted runtime until hosting quiesces it (`crtr sys import resume` ends it), a computer until it has switched over \u2014 machine credential, `runtime/machine.json`, daemon restarted in machine role \u2014 which is retried three times over a minute and otherwise finished by `crtr sys signin`. A machine import signs the person in first when this computer has no stored sign-in. `crtr sys signin` normally starts a computer's move itself. With `--run`, hosting has already opened the run: the exporter uses it and reads that run's import token from the environment variable `CRTR_IMPORT_TOKEN`.",params:[{kind:"flag",name:"kind",type:"enum",choices:["machine","hosted"],required:!0,constraint:"`machine` for a person's computer, `hosted` for a hosted runtime (also archives running nodes' files)."},{kind:"flag",name:"api-url",type:"string",required:!1,constraint:"Control plane base URL (http or https). Required with `--kind hosted`; with `--kind machine` the default is the control plane the person's sign-in is for."},{kind:"flag",name:"run",type:"string",required:!1,constraint:"An import run hosting opened for this runtime (only with `--kind hosted`); its import token is read from `CRTR_IMPORT_TOKEN`."},{kind:"flag",name:"directory-url",type:"string",required:!1,constraint:"Directory base URL (http or https) the person signs in against, with `--kind machine`. Default: the one in `runtime/machine.json`, else the stored sign-in's, else Northlight's."},{kind:"flag",name:"notify",type:"string",required:!1,constraint:"With `--kind machine`: a node id told by message when the move fails or finishes."}],output:[{name:"run_id",type:"string",required:!0,constraint:"The import run."},{name:"state",type:"string",required:!0,constraint:"Final state of the run, `done`."},{name:"timings",type:"string",required:!0,constraint:"Per-step seconds."}],outputKind:"object",effects:["Drains the local daemon (no new wake or cron fire); the drain is a `runtime_state` row, so it survives a daemon restart. Ended on a failure before `done`; kept after `done`.","Machine: writes `runtime/import/move.json` (0600) from the run's start until the switch-over, then deletes it. On `done`: writes `runtime/machine.json` and `runtime/secrets/machine-credential` (0600) and restarts the daemon in machine role. On a failure before `done`: revokes and deletes the stored sign-in.","Machine with `--notify`: sends that node one message when the move fails or finishes.","Opens a temporary directory holding the opened file stores (0600), removed before the command ends."]},run:i(async e=>{const t=e.kind,o=e.run===void 0?void 0:String(e.run),n=e.apiUrl===void 0?void 0:String(e.apiUrl),h=e.notify===void 0?null:String(e.notify);if(t==="hosted"){if(n===void 0)throw r("--kind hosted needs --api-url: the control plane hosting opened the run on");if(h!==null)throw r("--notify names the node that asked for a computer's move: use it only with --kind machine")}else if(o!==void 0)throw r("--run takes an existing run, which only a hosted import has: use --kind hosted");const s=Number(process.env[q]??"");if(Number.isInteger(s)&&s>0&&await U(s),R())throw v("import_inside_node: run this from a plain terminal; a node's broker ends with the daemon restart",{code:"import_inside_node"});if(t==="machine"){const a=await b(d);if(a!==void 0)return await I(a),{run_id:a.runId,state:"done",timings:""};const l=e.directoryUrl===void 0?f():String(e.directoryUrl);await p(l,d);const c=await O({directoryUrl:l,...n===void 0?{}:{apiUrl:n},notify:h});return{run_id:c.runId,state:"done",timings:c.timings}}const m=(o===void 0?await p(f(),d):process.env.CRTR_IMPORT_TOKEN??"").trim();if(m==="")throw r("set CRTR_IMPORT_TOKEN to the run's import token");const u={name:"start",timings:[]};return{run_id:await T("hosted",new k(n,m),o,u),state:"done",timings:u.timings.join(" ")}},"run"),render:i(e=>`<import run="${e.run_id}" state="${e.state}" timings="${e.timings}"/>`,"render")}),W=w({name:"import",description:"move this runtime into the person's Northlight account",whenToUse:"importing this computer or hosted runtime into Northlight",help:{name:"sys import",summary:"export this runtime to Northlight",model:"The exporter pauses the local daemon, uploads a bundle the control plane loads into the shared database, and on a computer switches it to the machine role."},children:[x,N]});export{q as WAIT_FOR_PID_ENV,W as sysImportBranch};
@@ -1,2 +1,2 @@
1
- var s=Object.defineProperty;var n=(e,t)=>s(e,"name",{value:t,configurable:!0});import{spawn as i}from"node:child_process";import{defineLeaf as o}from"../../core/command.js";import{usage as a}from"../../core/errors.js";import{readPersonCredential as d,signIn as c,signOut as u}from"../../core/directory/person-credential.js";import{readMachineConfig as h}from"../../daemon/machine-link/config.js";function l(e){const[t,r]=process.platform==="darwin"?["open",[e]]:["xdg-open",[e]];try{i(t,r,{stdio:"ignore",detached:!0}).on("error",()=>{}).unref()}catch{}}n(l,"openBrowser");function p(e){process.stderr.write(`Confirm this code at ${e.verificationUri}: ${e.userCode}
2
- `),l(e.complete)}n(p,"showSignIn");const k=o({name:"signin",description:"sign this computer's crtr in to the person's Northlight account",whenToUse:"this computer was imported into Northlight, so its `crtr` commands go to the control plane and answer `not_signed_in` until the person has signed in; or the sign-in was revoked or ended",help:{name:"sys signin",summary:"pair this computer with the directory in the browser and store a revocable credential",guide:"Prints a code and opens the directory's confirm page; sign in there if asked, check the code and click Allow. The command waits for that. The credential does not end on a fixed date: it ends after 90 days without use, or when you revoke it on the account page or run `crtr sys signout`. `crtr sys import start` signs in itself when none is stored.",params:[{kind:"flag",name:"directory-url",type:"string",required:!1,constraint:"Directory base URL (http or https). Default: the one in `runtime/machine.json`, else the one of the stored sign-in."}],output:[{name:"account",type:"string",required:!0,constraint:"The account this computer is now signed in as."}],outputKind:"object",effects:["Calls the directory (`POST /v1/developer/logins`, then polls `/v1/developer/logins/poll`) and, once approved, writes `runtime/secrets/person-credential` (0600) and removes any cached bearer. An abandoned or declined sign-in writes nothing and leaves an existing sign-in as it was."]},run:n(async e=>{const t=e["directory-url"]===void 0?(h()??d())?.directoryUrl:String(e["directory-url"]);if(t===void 0)throw a("name the directory: pass --directory-url");const{account:r}=await c(t,p);return{account:r}},"run"),render:n(e=>`<signin account="${e.account}"/>`,"render")}),b=o({name:"signout",description:"sign this computer's crtr out and revoke its credential",whenToUse:"this computer should stop acting as the person on Northlight, or its stored sign-in may have leaked",help:{name:"sys signout",summary:"revoke the stored credential at the directory and delete it from this computer",params:[],output:[{name:"signed_out",type:"boolean",required:!0,constraint:"False when no sign-in was stored."},{name:"revoked",type:"boolean",required:!0,constraint:"True when the directory ended the credential now; false when it was already ended or none was stored."}],outputKind:"object",effects:["Calls `POST /v1/developer/token/revoke` with the stored secret, then deletes `runtime/secrets/person-credential` and `person-bearer.json`. A bearer already issued stays valid for at most 5 minutes. After this, `crtr` on an imported computer answers `not_signed_in`."]},run:n(async()=>{const{signedOut:e,revoked:t}=await u();return{signed_out:e,revoked:t}},"run"),render:n(e=>`<signout signed_out="${e.signed_out}" revoked="${e.revoked}"/>`,"render")});export{p as showSignIn,k as sysSigninLeaf,b as sysSignoutLeaf};
1
+ var y=Object.defineProperty;var o=(e,n)=>y(e,"name",{value:n,configurable:!0});import{spawn as c}from"node:child_process";import{mkdirSync as k,openSync as _}from"node:fs";import{dirname as d,join as b}from"node:path";import{defineLeaf as h}from"../../core/command.js";import{CrtrError as u}from"../../core/errors.js";import{signIn as m,signOut as T}from"../../core/directory/person-credential.js";import{moveRecordPath as N,readMachineConfig as l}from"../../daemon/machine-link/config.js";import{sanitizedDaemonEnv as S,spawnDaemon as p,stopAllDaemons as g}from"../../daemon/manage.js";import{northlightUnreachable as U}from"../../core/directory/reach.js";import{envNodeId as A}from"../../shared/env.js";import{ExitCode as f}from"../../types.js";import{insideBroker as v}from"./join.js";import{cliClient as C}from"../api-client.js";import{defaultDirectoryUrl as $,finishMove as O,runMove as R,settleEarlierMove as I}from"./import/move.js";import{WAIT_FOR_PID_ENV as P}from"./import/start.js";function E(e){const[n,t]=process.platform==="darwin"?["open",[e]]:["xdg-open",[e]];try{c(n,t,{stdio:"ignore",detached:!0}).on("error",()=>{}).unref()}catch{}}o(E,"openBrowser");function a(e){process.stderr.write(`Confirm this code at ${e.verificationUri}: ${e.userCode}
2
+ `),E(e.complete)}o(a,"showSignIn");function x(e){return async n=>{let t;try{t=await fetch(new URL(`/v1/imports/${e.runId}`,e.apiUrl),{headers:{authorization:`Bearer ${n.bearer}`},signal:AbortSignal.timeout(3e4)})}catch(r){return U(r,e.apiUrl)}if(!t.ok)return t.status===404?new u("signin_other_account",`This computer's crtr belongs to the Northlight account ${e.account?.display??"it was moved into"}. Sign in to that account.`,f.USAGE):new u("signin_unchecked",`the control plane did not say which account owns this computer (HTTP ${t.status}); nothing was written.`,f.GENERAL,{next:"Retry `crtr sys signin`."})}}o(x,"sameAccountOnly");function w(e){const n=b(d(N()),"move.log");k(d(n),{recursive:!0});const t=_(n,"a",384),r=S();for(const s of["CRTR_API_SOCKET","CRTR_API_TOKEN","CRTR_API_URL"])delete r[s];r[P]=String(process.pid);const i=[...process.execArgv,process.argv[1],"sys","import","start","--kind","machine",...e===void 0?[]:["--notify",e]];c(process.execPath,i,{detached:!0,stdio:["ignore",t,t],env:r}).unref()}o(w,"startDetachedMove");const D=o(e=>`Signed in as ${e}. crtr is now moving this computer's records to Northlight; every agent here pauses for seconds to minutes. End your turn now; this conversation carries on when the move finishes.`,"MOVING"),ee=h({name:"signin",description:"sign this computer's crtr in to the person's Northlight account",whenToUse:"the person agreed to sign in to Northlight (a Northlight command needs the account, or they asked), or a command answered `not_signed_in` or `move_unfinished`",help:{name:"sys signin",summary:"pair this computer with the person's Northlight account in the browser, then move this computer's records into it",guide:"Prints a code and opens the directory's confirm page; the person signs in or signs up there if asked, checks the code and clicks Allow. The command waits for that. On a computer whose records are still local it then moves them into the account: run from a terminal it prints each step and returns when done; run from inside a node it returns at once and the node is messaged when the move finishes or fails. A computer already moved accepts only the account it moved into. For an unfinished move (`move_unfinished`), it checks the account's move result and finishes the switch-over if the records arrived. If the stored sign-in is missing or revoked, it first pairs only the account recorded for that move. Until the result is known, agents remain paused. The sign-in ends after 90 days without use, when revoked on the account page, or with `crtr sys signout`.",params:[{kind:"flag",name:"directory-url",type:"string",required:!1,constraint:"Directory base URL (http or https). Default: the one in `runtime/machine.json`, else the stored sign-in's, else Northlight's."}],output:[{name:"account",type:"string",required:!0,constraint:"The account this computer is now signed in as."},{name:"moved",type:"boolean",required:!1,constraint:"Present when this command moved the records, or finished an earlier move's switch-over."},{name:"moving",type:"boolean",required:!1,constraint:"Present when the move runs on in its own process (run from inside a node): end the turn; the node is messaged when it finishes or fails."}],outputKind:"object",effects:["Calls the directory (`POST /v1/developer/logins`, then polls `/v1/developer/logins/poll`) and, once approved, writes `runtime/secrets/person-credential` (0600) and replaces the cached bearer. A declined, abandoned or refused sign-in writes nothing and leaves an existing sign-in as it was.","On a computer whose records are local: the move of `crtr sys import start --kind machine` (drains the daemon, uploads, restarts it in machine role); a move confirmed failed before the account holds the records ends the drain and signs this computer out again. If the account's result is unknown, the move record and drain remain for recovery. From inside a node it runs as a detached process logging to `runtime/import/move.log`.","On a moved computer: restarts the daemon so it reconnects to Northlight."]},run:o(async e=>{const n=e.directoryUrl===void 0?$():String(e.directoryUrl),t=await I(a);if(t!==void 0)return v()?(w(void 0),{account:t.account.display,moving:!0}):(await O(t),{account:t.account.display,moved:!0});const r=l();if(r!==void 0){const{account:s}=await m(n,a,{accept:x(r)});return await g(),await p(),{account:s}}const{account:i}=await m(n,a);return v()?(w(A()),{account:i,moving:!0}):(await R({directoryUrl:n,notify:null}),{account:i,moved:!0})},"run"),render:o(e=>e.moving===!0?`<signin account="${e.account}" moving="true">${D(String(e.account))}</signin>`:`<signin account="${e.account}"${e.moved===!0?' moved="true"':""}/>`,"render")});async function q(){return await g(),await p(),(await C({local:!0}).healthz()).signed_in===!1}o(q,"stopMovedComputer");const te=h({name:"signout",description:"sign this computer's crtr out, revoke its credential, and stop its agents",whenToUse:"this computer should stop acting as the person on Northlight, or its stored sign-in may have leaked",help:{name:"sys signout",summary:"revoke the stored credential, delete it from this computer, and on a moved computer stop every agent and cron here",params:[],output:[{name:"signed_out",type:"boolean",required:!0,constraint:"False when no sign-in was stored."},{name:"revoked",type:"boolean",required:!0,constraint:"True when the directory ended the credential now; false when it was already ended or none was stored."},{name:"stopped",type:"boolean",required:!0,constraint:"True when this computer's records are in Northlight and its agents and running crons have stopped; false on a computer whose records are local, where nothing else is tied to the sign-in."}],outputKind:"object",effects:["Calls `POST /v1/developer/token/revoke` with the stored secret, then deletes `runtime/secrets/person-credential` and `person-bearer.json`. A bearer already issued stays valid for at most 5 minutes.","On a computer whose records are in Northlight: restarts the daemon, which stops every agent and running cron here and takes no new work until `crtr sys signin`; agents keep their mail on Northlight and wake after it. Commands answer `not_signed_in`."]},run:o(async()=>{const{signedOut:e,revoked:n}=await T(),t=l()===void 0?!1:await q();return{signed_out:e,revoked:n,stopped:t}},"run"),render:o(e=>`<signout signed_out="${e.signed_out}" revoked="${e.revoked}" stopped="${e.stopped}"/>`,"render")});export{a as showSignIn,ee as sysSigninLeaf,te as sysSignoutLeaf};
@@ -1 +1 @@
1
- var s=Object.defineProperty;var r=(o,m)=>s(o,"name",{value:m,configurable:!0});import{defineSysBranch as t}from"./sys/branch.js";import{configBranch as f}from"./sys/config.js";import{sysEnvBranch as i}from"./sys/env.js";import{daemonBranch as e}from"./sys/daemon.js";import{sysDoctorLeaf as a}from"./sys/doctor.js";import{sysFeedbackLeaf as p}from"./sys/feedback.js";import{sysLogsLeaf as n}from"./sys/logs.js";import{sysRetryDeliveryLeaf as y}from"./sys/retry-delivery.js";import{sysWorktreesLeaf as c}from"./sys/worktrees.js";import{sysSpacesLeaf as L}from"./sys/spaces.js";import{sysLimitsBranch as h}from"./sys/limits.js";import{sysSpendBranch as B}from"./sys/spend.js";import{sysSettingsLeaf as S}from"./sys/settings.js";import{sysConnectLeaf as g}from"./sys/connect.js";import{sysSetupLeaf as u}from"./sys/setup.js";import{tutorialBranch as d}from"./sys/tutorial/branch.js";import{supportBranch as D}from"./sys/support.js";import{contextBranch as b}from"./sys/context.js";import{sysSyncBranch as k}from"./sys/sync.js";import{sysMigrateLeaf as l}from"./sys/migrate.js";import{sysJoinLeaf as v}from"./sys/join.js";import{sysDbBranch as x}from"./sys/db.js";import{sysImportBranch as M}from"./sys/import/start.js";import{sysMachineBranch as C}from"./sys/machine/rotate.js";import{sysSigninLeaf as E,sysSignoutLeaf as F}from"./sys/signin.js";import{sysUpdateLeaf as I,sysVersionLeaf as J}from"./sys/update.js";function fr(){return t([S,f,i,g,u,d,a,b,p,n,c,y,L,h,B,D,I,J,l,v,k,x,M,C,E,F,e])}r(fr,"registerSys");export{fr as registerSys};
1
+ var s=Object.defineProperty;var r=(o,m)=>s(o,"name",{value:m,configurable:!0});import{defineSysBranch as t}from"./sys/branch.js";import{configBranch as f}from"./sys/config.js";import{sysEnvBranch as i}from"./sys/env.js";import{daemonBranch as e}from"./sys/daemon.js";import{sysDoctorLeaf as a}from"./sys/doctor.js";import{sysFeedbackLeaf as p}from"./sys/feedback.js";import{sysLogsLeaf as n}from"./sys/logs.js";import{sysRetryDeliveryLeaf as y}from"./sys/retry-delivery.js";import{sysWorktreesLeaf as c}from"./sys/worktrees.js";import{sysSpacesLeaf as L}from"./sys/spaces.js";import{sysLimitsBranch as h}from"./sys/limits.js";import{sysSpendBranch as B}from"./sys/spend.js";import{sysSettingsLeaf as S}from"./sys/settings.js";import{sysConnectLeaf as g}from"./sys/connect.js";import{sysSetupLeaf as u}from"./sys/setup.js";import{tutorialBranch as d}from"./sys/tutorial/branch.js";import{supportBranch as D}from"./sys/support.js";import{contextBranch as k}from"./sys/context.js";import{sysSyncBranch as b}from"./sys/sync.js";import{sysMigrateLeaf as l}from"./sys/migrate.js";import{sysJoinLeaf as v}from"./sys/join.js";import{sysDbBranch as x}from"./sys/db.js";import{sysImportBranch as M}from"./sys/import/start.js";import{sysMachineBranch as C}from"./sys/machine/rotate.js";import{sysDesktopBranch as E}from"./sys/desktop.js";import{sysSigninLeaf as F,sysSignoutLeaf as I}from"./sys/signin.js";import{sysUpdateLeaf as J,sysVersionLeaf as R}from"./sys/update.js";function er(){return t([S,f,i,g,u,d,a,k,p,n,c,y,L,h,B,D,J,R,l,v,b,x,M,C,F,I,E,e])}r(er,"registerSys");export{er as registerSys};
@@ -153,6 +153,8 @@ export declare function composeIdentity(pid: number, lstart: string, deps?: {
153
153
  * probe itself failed; callers preserve that as unknown rather than absence. */
154
154
  export interface PsLivenessProcess {
155
155
  zombie: boolean;
156
+ /** The process group the process is in. */
157
+ pgid: number;
156
158
  identity: string;
157
159
  command: string;
158
160
  }
@@ -173,6 +175,7 @@ interface ProcStat {
173
175
  comm: string;
174
176
  state: string;
175
177
  ppid: number;
178
+ pgid: number;
176
179
  starttime: string;
177
180
  }
178
181
  export declare function parseProcStat(raw: string): ProcStat | null;
@@ -233,6 +236,9 @@ export declare const procfsBackendForTest: {
233
236
  * effort: returns `[]` (never throws) on any `ps` failure, exactly like
234
237
  * `killProcessGroup`'s ESRCH-swallowing. */
235
238
  export declare function descendantPids(rootPid: number): number[];
239
+ /** The pids of the live (not zombie) processes in process group `pgid`, from one process-table sample. `null` when the table cannot be read: an unreadable
240
+ * table is unknown, never an empty group. */
241
+ export declare function pidsInProcessGroup(pgid: number): number[] | null;
236
242
  /** Capture a portable process-identity fingerprint — `ps`'s `lstart` column,
237
243
  * the process's full wall-clock start time — for each of `pids`, in ONE
238
244
  * batched `ps -p <list>` call. `lstart` is a standard format keyword on BOTH
@@ -1,5 +1,5 @@
1
- var J=Object.defineProperty;var s=(n,t)=>J(n,"name",{value:t,configurable:!0});import{spawn as V,spawnSync as b}from"node:child_process";import{readFileSync as h,readdirSync as K,statSync as X}from"node:fs";import{readFile as x}from"node:fs/promises";import{readKernelBootId as P}from"./boot-id.js";function q(n){if(m())return D(n);try{const t=b("ps",["-o","stat=","-p",String(n)],{encoding:"utf8",timeout:2e3});if(t.error!=null||t.signal!=null||typeof t.stdout!="string"||typeof t.stderr=="string"&&t.stderr.trim()!=="")return"unknown";const e=t.stdout.trim();return e===""?"gone":e.charAt(0)==="Z"?"zombie":"live"}catch{return"unknown"}}s(q,"psProcessState");function S(n){if(n==null)return!1;try{process.kill(n,0)}catch(e){return e.code==="EPERM"}const t=q(n);return t!=="zombie"&&t!=="gone"}s(S,"isPidAlive");function fn(n){if(n==null)return!1;try{return process.kill(n,0),!0}catch(t){return t.code==="EPERM"}}s(fn,"isPidPresent");const y=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function w(n){const t=n.indexOf("#");if(t===-1)return{base:n,ticks:void 0};const e=n.slice(t+1);return{base:n.slice(0,t),ticks:/^\d+$/.test(e)?e:void 0}}s(w,"splitIdentity");function k(n,t){const e=w(n),r=w(t);return e.ticks!==void 0&&r.ticks!==void 0?y.test(e.base)&&y.test(r.base)?e.base===r.base&&e.ticks===r.ticks:e.ticks===r.ticks:e.base===r.base}s(k,"identitiesMatch");function dn(n,t,e){const r=!!process.env.CRTR_DEBUG_PID_LIVENESS;if(n==null)return"dead";if(e===null)return r&&p(n,!1,t,"probe-null","ps snapshot failed \u2192 indeterminate","indeterminate"),"indeterminate";const o=e?.processes.get(n),i=e===void 0?S(n):o!==void 0&&!o.zombie;if(!i)return r&&p(n,i,t,e===void 0?"no-probe":"no-entry","process absent/zombie \u2192 dead","dead"),"dead";if(t==null)return r&&p(n,i,t,"no-probe","no baseline \u2192 trust process presence","alive"),"alive";let u;if(e===void 0){const l=Z([n]);if(l==null)return r&&p(n,i,t,"probe-null","ps probe failed \u2192 indeterminate","indeterminate"),"indeterminate";if(u=l.get(n),u===void 0)return r&&p(n,i,t,"no-entry","no identity row for pid (race) \u2192 dead","dead"),"dead"}else u=o.identity;const c=k(u,t);return r&&p(n,i,t,"value",c?"identity matches expected \u2192 alive":"identity differs from expected \u2192 REUSED \u2192 dead",c?"alive":"dead",u),c?"alive":"dead"}s(dn,"recordedPidLiveness");function p(n,t,e,r,o,i,u){const c=[`pid=${String(n)}`,`isPidAlive=${t}`,`expectedIdentity=${e===void 0?"undefined":e===null?"null":JSON.stringify(e)}`,`capturePidIdentities=${r}${r==="value"?`(${JSON.stringify(u)})`:""}`,`branch=${JSON.stringify(o)}`,`returned=${i}`];process.stderr.write(`[recordedPidLiveness] ${c.join(" ")}
2
- `)}s(p,"logPidLivenessDecision");function W(n,t="SIGTERM"){if(!(!Number.isInteger(n)||n<=0))try{process.kill(-n,t)}catch{}}s(W,"killProcessGroup");function Q(n){try{const t=h(`/proc/${n}/stat`,"utf8"),e=t.lastIndexOf(")");if(e===-1)return null;const o=t.slice(e+2).trim().split(/\s+/)[19];return o!==void 0&&/^\d+$/.test(o)?o:null}catch{return null}}s(Q,"procStartTicks");const Y=6e4;function nn(n,t=Date.now,e=Y){let r=null,o=0;return()=>{if(r!==null)return r;const i=t();if(i<o)return null;const u=n();return u!==null?r=u:(o=i+e,null)}}s(nn,"makeBootIdCache");const T=nn(P);function an(n){const{base:t,ticks:e}=w(n);return e!==void 0&&!y.test(t)}s(an,"isLegacyTicksIdentity");function pn(n){const{base:t,ticks:e}=w(n);return e!==void 0&&y.test(t)}s(pn,"isBootScopedIdentity");function v(n,t,e={}){const r=e.ticks!==void 0?e.ticks:Q(n);return r===null?t:`${(e.bootId!==void 0?e.bootId:T())??t}#${r}`}s(v,"composeIdentity");const M=["-Ao","pid=,ppid=,stat=,lstart=,command="],A=2e3;function R(){if(m())return N();let n;try{const t=b("ps",[...M],{encoding:"utf8",timeout:A,maxBuffer:8388608});if(t.status!==0||typeof t.stdout!="string")return null;n=t.stdout}catch{return null}return F(n)}s(R,"captureLivenessSnapshot");async function mn(){if(m())return j();const n=await new Promise(t=>{let e;try{e=V("ps",[...M])}catch{return t(null)}let r="",o=!1;const i=s(c=>{o||(o=!0,clearTimeout(u),t(c))},"settle"),u=setTimeout(()=>{try{e.kill("SIGKILL")}catch{}i(null)},A);u.unref?.(),e.stdout.on("data",c=>r+=c.toString()),e.stderr.on("data",()=>{}),e.on("error",()=>i(null)),e.on("close",c=>i(c===0?r:null))});return n===null?null:F(n)}s(mn,"captureLivenessSnapshotAsync");function _(n){const t=n.indexOf("("),e=n.lastIndexOf(")");if(t===-1||e===-1||e<t)return null;const r=n.slice(e+2).trim().split(/\s+/),o=r[0],i=Number(r[1]),u=r[19];return o===void 0||o===""||!Number.isInteger(i)||i<0||u===void 0||!/^\d+$/.test(u)?null:{comm:n.slice(t+1,e),state:o,ppid:i,starttime:u}}s(_,"parseProcStat");function L(n,t){const e=n.replace(/\0+$/,"").split("\0").join(" ").trim();return e===""?`[${t}]`:e}s(L,"procCommandLine");let I;function m(){return I===void 0&&(I=process.platform==="linux"&&P()!==null&&tn(process.pid)!==null),I}s(m,"procfsUsable");const a={root:"/proc",bootId:s(()=>T(),"bootId")};function tn(n,t=a){const e=g(n,t);return e==="unreadable"||e==="denied"||e==="gone"?null:e}s(tn,"procStatOf");function g(n,t=a){let e;try{e=h(`${t.root}/${n}/stat`,"utf8")}catch(r){return $(r)}return _(e)??"unreadable"}s(g,"readProcStat");function $(n){const t=n.code;return t==="ENOENT"||t==="ESRCH"?"gone":t==="EACCES"||t==="EPERM"?"denied":"unreadable"}s($,"classifyProcReadError");function en(n,t){const e=t.selfUid!==void 0?t.selfUid():process.getuid?.()??null;if(e===null)return"own-or-unknown";try{return X(`${t.root}/${n}`).uid===e?"own-or-unknown":"foreign"}catch(r){return $(r)==="gone"?"gone":"own-or-unknown"}}s(en,"procOwnership");function B(n,t,e){return n==="gone"?"skip":n==="unreadable"||en(t,e)==="own-or-unknown"?"fail":"skip"}s(B,"scanDisposition");function C(n,t=a){const e=t.bootId();return e===null?null:`${e}#${n.starttime}`}s(C,"procIdentity");function D(n,t=a){const e=g(n,t);return e==="gone"?"gone":e==="denied"||e==="unreadable"?"unknown":e.state==="Z"||e.state==="X"?"zombie":"live"}s(D,"procProcessState");function z(n,t=a){const e=new Map;for(const r of n){const o=g(r,t);if(o==="gone")continue;if(o==="unreadable"||o==="denied")return null;const i=C(o,t);if(i===null)return null;e.set(r,i)}return e}s(z,"procIdentities");function G(n,t=a){const e=g(n,t);if(e==="gone"||e==="unreadable"||e==="denied")return null;try{const r=L(h(`${t.root}/${n}/cmdline`,"utf8"),e.comm);return r===""?null:r}catch{return null}}s(G,"procCommand");function H(n,t,e,r,o){const i=C(r,n);if(i===null)return!1;t.processes.set(e,{zombie:r.state==="Z"||r.state==="X",identity:i,command:L(o,r.comm)});const u=t.childrenOf.get(r.ppid);return u===void 0?t.childrenOf.set(r.ppid,[e]):u.push(e),!0}s(H,"addProcfsRow");function U(n){try{return K(n).filter(t=>/^\d+$/.test(t)).map(Number).filter(t=>t>0)}catch{return null}}s(U,"procPids");function N(n=a){const t=U(n.root);if(t===null)return null;const e={processes:new Map,childrenOf:new Map};for(const r of t){const o=g(r,n);if(o==="gone"||o==="denied"||o==="unreadable"){if(B(o==="gone"?"gone":o,r,n)==="skip")continue;return null}let i="";try{i=h(`${n.root}/${r}/cmdline`,"utf8")}catch(u){const c=u.code;if(c==="ENOENT"||c==="ESRCH")continue}if(!H(n,e,r,o,i))return null}return e}s(N,"readProcfsSnapshot");async function j(n=a){const t=U(n.root);if(t===null)return null;const e={processes:new Map,childrenOf:new Map},r=64;for(let o=0;o<t.length;o+=r){const i=t.slice(o,o+r),u=await Promise.all(i.map(async c=>{try{const l=_(await x(`${n.root}/${c}/stat`,"utf8"));if(l===null)return"failed";const d=await x(`${n.root}/${c}/cmdline`,"utf8").catch(f=>{if(f.code==="ENOENT"||f.code==="ESRCH")throw f;return""});return{pid:c,stat:l,cmdline:d}}catch(l){return B($(l),c,n)==="skip"?"gone":"failed"}}));for(const c of u)if(c!=="gone"&&(c==="failed"||!H(n,e,c.pid,c.stat,c.cmdline)))return null}return e}s(j,"readProcfsSnapshotAsync");function bn(){if(m()){const n=N();if(n===null)return null;const t=new Map;for(const[e,r]of n.childrenOf)for(const o of r)t.set(o,e);return[...n.processes].map(([e,r])=>({pid:e,ppid:t.get(e)??0,command:r.command}))}try{const n=b("ps",["-ax","-o","pid=,ppid=,command="],{encoding:"utf8",timeout:2e3,maxBuffer:8388608});if(n.status!==0||typeof n.stdout!="string")return null;const t=[];for(const e of n.stdout.split(`
3
- `)){const r=/^\s*(\d+)\s+(\d+)\s+(.*)$/.exec(e);r!==null&&t.push({pid:Number(r[1]),ppid:Number(r[2]),command:r[3].trim()})}return t}catch{return null}}s(bn,"listProcessTable");const gn={procIdentities:z,procProcessState:D,procCommand:G,readProcfsSnapshot:N,readProcfsSnapshotAsync:j};function F(n){const t=new Map,e=new Map;for(const r of n.split(`
4
- `)){const o=r.trim();if(o==="")continue;const i=/^(\d+)\s+(\d+)\s+(\S+)\s+(\S+(?:\s+\S+){4})\s+(.+)$/.exec(o);if(i===null)continue;const u=Number(i[1]),c=Number(i[2]),l=i[3],d=i[4],f=i[5].trim();if(!Number.isInteger(u)||u<=0||!Number.isInteger(c)||c<0||f==="")continue;t.set(u,{zombie:l.startsWith("Z"),identity:v(u,d),command:f});const O=e.get(c);O===void 0?e.set(c,[u]):O.push(u)}return{processes:t,childrenOf:e}}s(F,"parseLivenessTable");function E(n,t){const e=[],r=new Set([n]),o=[...t.get(n)??[]];for(;o.length>0;){const i=o.shift();if(r.has(i))continue;r.add(i),e.push(i);const u=t.get(i);u!==void 0&&o.push(...u)}return e}s(E,"descendantPidsFrom");function rn(n){if(!Number.isInteger(n)||n<=0)return[];const t=R();return t===null?[]:E(n,t.childrenOf)}s(rn,"descendantPids");function Z(n){const t=n.filter(r=>Number.isInteger(r)&&r>0),e=new Map;if(t.length===0)return e;if(m())return z(t);try{const r=b("ps",["-o","pid=,lstart=","-p",t.join(",")],{encoding:"utf8",timeout:2e3});if(r.error!=null||r.signal!=null||typeof r.stdout!="string"||typeof r.stderr=="string"&&r.stderr.trim()!=="")return null;for(const o of r.stdout.split(`
5
- `)){const i=o.trim();if(i==="")continue;const u=i.indexOf(" ");if(u===-1)continue;const c=Number(i.slice(0,u)),l=i.slice(u+1).trim();!Number.isInteger(c)||c<=0||l===""||e.set(c,v(c,l))}return e}catch{return null}}s(Z,"capturePidIdentities");function hn(n){if(!Number.isInteger(n)||n<=0)return null;if(m())return G(n);try{const t=b("ps",["-o","command=","-p",String(n)],{encoding:"utf8",timeout:2e3});if(t.status!==0||typeof t.stdout!="string")return null;const e=t.stdout.trim();return e===""?null:e}catch{return null}}s(hn,"capturePidCommand");function Sn(n,t,e){if(!Number.isInteger(n)||n<=0)return{tree:[],identities:null,reused:!1};const r=R();if(r===null)return{tree:[n],identities:null,reused:!1};const o=r.processes.get(n)?.identity;if(t!=null&&o!==void 0&&!k(o,t))return{tree:[],identities:null,reused:!0};const u=e!==void 0&&Number.isInteger(e)&&e>0?new Set([e,...E(e,r.childrenOf)]):void 0,c=[n,...E(n,r.childrenOf)].filter(d=>!u?.has(d)),l=new Map;for(const d of c){const f=r.processes.get(d)?.identity;f!==void 0&&l.set(d,f)}return{tree:c,identities:l,reused:!1}}s(Sn,"captureTeardownSnapshot");function yn(n,t="SIGTERM",e){const r=e!=null?Z(n):void 0;for(const o of n){if(e!=null&&r!=null){const i=e.get(o),u=r.get(o);if(i!==void 0&&u!==void 0&&!k(u,i))continue}if(W(o,t),!(!Number.isInteger(o)||o<=0))try{process.kill(o,t)}catch{}}}s(yn,"killProcessTreePids");function wn(n){return S(n)?!0:rn(n).some(t=>S(t))}s(wn,"isProcessTreeAlive");function kn(n){return n.some(t=>S(t))}s(kn,"isAnyPidAlive");export{R as captureLivenessSnapshot,mn as captureLivenessSnapshotAsync,hn as capturePidCommand,Z as capturePidIdentities,Sn as captureTeardownSnapshot,v as composeIdentity,rn as descendantPids,k as identitiesMatch,kn as isAnyPidAlive,pn as isBootScopedIdentity,an as isLegacyTicksIdentity,S as isPidAlive,fn as isPidPresent,wn as isProcessTreeAlive,W as killProcessGroup,yn as killProcessTreePids,bn as listProcessTable,nn as makeBootIdCache,_ as parseProcStat,L as procCommandLine,gn as procfsBackendForTest,N as readProcfsSnapshot,j as readProcfsSnapshotAsync,dn as recordedPidLiveness};
1
+ var V=Object.defineProperty;var s=(n,e)=>V(n,"name",{value:e,configurable:!0});import{spawn as K,spawnSync as g}from"node:child_process";import{readFileSync as h,readdirSync as X,statSync as q}from"node:fs";import{readFile as T}from"node:fs/promises";import{readKernelBootId as v}from"./boot-id.js";function W(n){if(m())return D(n);try{const e=g("ps",["-o","stat=","-p",String(n)],{encoding:"utf8",timeout:2e3});if(e.error!=null||e.signal!=null||typeof e.stdout!="string"||typeof e.stderr=="string"&&e.stderr.trim()!=="")return"unknown";const t=e.stdout.trim();return t===""?"gone":t.charAt(0)==="Z"?"zombie":"live"}catch{return"unknown"}}s(W,"psProcessState");function S(n){if(n==null)return!1;try{process.kill(n,0)}catch(t){return t.code==="EPERM"}const e=W(n);return e!=="zombie"&&e!=="gone"}s(S,"isPidAlive");function dn(n){if(n==null)return!1;try{return process.kill(n,0),!0}catch(e){return e.code==="EPERM"}}s(dn,"isPidPresent");const y=/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;function w(n){const e=n.indexOf("#");if(e===-1)return{base:n,ticks:void 0};const t=n.slice(e+1);return{base:n.slice(0,e),ticks:/^\d+$/.test(t)?t:void 0}}s(w,"splitIdentity");function k(n,e){const t=w(n),r=w(e);return t.ticks!==void 0&&r.ticks!==void 0?y.test(t.base)&&y.test(r.base)?t.base===r.base&&t.ticks===r.ticks:t.ticks===r.ticks:t.base===r.base}s(k,"identitiesMatch");function an(n,e,t){const r=!!process.env.CRTR_DEBUG_PID_LIVENESS;if(n==null)return"dead";if(t===null)return r&&p(n,!1,e,"probe-null","ps snapshot failed \u2192 indeterminate","indeterminate"),"indeterminate";const o=t?.processes.get(n),i=t===void 0?S(n):o!==void 0&&!o.zombie;if(!i)return r&&p(n,i,e,t===void 0?"no-probe":"no-entry","process absent/zombie \u2192 dead","dead"),"dead";if(e==null)return r&&p(n,i,e,"no-probe","no baseline \u2192 trust process presence","alive"),"alive";let u;if(t===void 0){const l=J([n]);if(l==null)return r&&p(n,i,e,"probe-null","ps probe failed \u2192 indeterminate","indeterminate"),"indeterminate";if(u=l.get(n),u===void 0)return r&&p(n,i,e,"no-entry","no identity row for pid (race) \u2192 dead","dead"),"dead"}else u=o.identity;const c=k(u,e);return r&&p(n,i,e,"value",c?"identity matches expected \u2192 alive":"identity differs from expected \u2192 REUSED \u2192 dead",c?"alive":"dead",u),c?"alive":"dead"}s(an,"recordedPidLiveness");function p(n,e,t,r,o,i,u){const c=[`pid=${String(n)}`,`isPidAlive=${e}`,`expectedIdentity=${t===void 0?"undefined":t===null?"null":JSON.stringify(t)}`,`capturePidIdentities=${r}${r==="value"?`(${JSON.stringify(u)})`:""}`,`branch=${JSON.stringify(o)}`,`returned=${i}`];process.stderr.write(`[recordedPidLiveness] ${c.join(" ")}
2
+ `)}s(p,"logPidLivenessDecision");function Q(n,e="SIGTERM"){if(!(!Number.isInteger(n)||n<=0))try{process.kill(-n,e)}catch{}}s(Q,"killProcessGroup");function Y(n){try{const e=h(`/proc/${n}/stat`,"utf8"),t=e.lastIndexOf(")");if(t===-1)return null;const o=e.slice(t+2).trim().split(/\s+/)[19];return o!==void 0&&/^\d+$/.test(o)?o:null}catch{return null}}s(Y,"procStartTicks");const nn=6e4;function en(n,e=Date.now,t=nn){let r=null,o=0;return()=>{if(r!==null)return r;const i=e();if(i<o)return null;const u=n();return u!==null?r=u:(o=i+t,null)}}s(en,"makeBootIdCache");const M=en(v);function pn(n){const{base:e,ticks:t}=w(n);return t!==void 0&&!y.test(e)}s(pn,"isLegacyTicksIdentity");function mn(n){const{base:e,ticks:t}=w(n);return t!==void 0&&y.test(e)}s(mn,"isBootScopedIdentity");function A(n,e,t={}){const r=t.ticks!==void 0?t.ticks:Y(n);return r===null?e:`${(t.bootId!==void 0?t.bootId:M())??e}#${r}`}s(A,"composeIdentity");const R=["-Ao","pid=,ppid=,pgid=,stat=,lstart=,command="],_=2e3;function I(){if(m())return E();let n;try{const e=g("ps",[...R],{encoding:"utf8",timeout:_,maxBuffer:8388608});if(e.status!==0||typeof e.stdout!="string")return null;n=e.stdout}catch{return null}return Z(n)}s(I,"captureLivenessSnapshot");async function gn(){if(m())return F();const n=await new Promise(e=>{let t;try{t=K("ps",[...R])}catch{return e(null)}let r="",o=!1;const i=s(c=>{o||(o=!0,clearTimeout(u),e(c))},"settle"),u=setTimeout(()=>{try{t.kill("SIGKILL")}catch{}i(null)},_);u.unref?.(),t.stdout.on("data",c=>r+=c.toString()),t.stderr.on("data",()=>{}),t.on("error",()=>i(null)),t.on("close",c=>i(c===0?r:null))});return n===null?null:Z(n)}s(gn,"captureLivenessSnapshotAsync");function L(n){const e=n.indexOf("("),t=n.lastIndexOf(")");if(e===-1||t===-1||t<e)return null;const r=n.slice(t+2).trim().split(/\s+/),o=r[0],i=Number(r[1]),u=Number(r[2]),c=r[19];return o===void 0||o===""||!Number.isInteger(i)||i<0||!Number.isInteger(u)||u<0||c===void 0||!/^\d+$/.test(c)?null:{comm:n.slice(e+1,t),state:o,ppid:i,pgid:u,starttime:c}}s(L,"parseProcStat");function B(n,e){const t=n.replace(/\0+$/,"").split("\0").join(" ").trim();return t===""?`[${e}]`:t}s(B,"procCommandLine");let $;function m(){return $===void 0&&($=process.platform==="linux"&&v()!==null&&tn(process.pid)!==null),$}s(m,"procfsUsable");const a={root:"/proc",bootId:s(()=>M(),"bootId")};function tn(n,e=a){const t=b(n,e);return t==="unreadable"||t==="denied"||t==="gone"?null:t}s(tn,"procStatOf");function b(n,e=a){let t;try{t=h(`${e.root}/${n}/stat`,"utf8")}catch(r){return N(r)}return L(t)??"unreadable"}s(b,"readProcStat");function N(n){const e=n.code;return e==="ENOENT"||e==="ESRCH"?"gone":e==="EACCES"||e==="EPERM"?"denied":"unreadable"}s(N,"classifyProcReadError");function rn(n,e){const t=e.selfUid!==void 0?e.selfUid():process.getuid?.()??null;if(t===null)return"own-or-unknown";try{return q(`${e.root}/${n}`).uid===t?"own-or-unknown":"foreign"}catch(r){return N(r)==="gone"?"gone":"own-or-unknown"}}s(rn,"procOwnership");function C(n,e,t){return n==="gone"?"skip":n==="unreadable"||rn(e,t)==="own-or-unknown"?"fail":"skip"}s(C,"scanDisposition");function z(n,e=a){const t=e.bootId();return t===null?null:`${t}#${n.starttime}`}s(z,"procIdentity");function D(n,e=a){const t=b(n,e);return t==="gone"?"gone":t==="denied"||t==="unreadable"?"unknown":t.state==="Z"||t.state==="X"?"zombie":"live"}s(D,"procProcessState");function G(n,e=a){const t=new Map;for(const r of n){const o=b(r,e);if(o==="gone")continue;if(o==="unreadable"||o==="denied")return null;const i=z(o,e);if(i===null)return null;t.set(r,i)}return t}s(G,"procIdentities");function H(n,e=a){const t=b(n,e);if(t==="gone"||t==="unreadable"||t==="denied")return null;try{const r=B(h(`${e.root}/${n}/cmdline`,"utf8"),t.comm);return r===""?null:r}catch{return null}}s(H,"procCommand");function U(n,e,t,r,o){const i=z(r,n);if(i===null)return!1;e.processes.set(t,{zombie:r.state==="Z"||r.state==="X",pgid:r.pgid,identity:i,command:B(o,r.comm)});const u=e.childrenOf.get(r.ppid);return u===void 0?e.childrenOf.set(r.ppid,[t]):u.push(t),!0}s(U,"addProcfsRow");function j(n){try{return X(n).filter(e=>/^\d+$/.test(e)).map(Number).filter(e=>e>0)}catch{return null}}s(j,"procPids");function E(n=a){const e=j(n.root);if(e===null)return null;const t={processes:new Map,childrenOf:new Map};for(const r of e){const o=b(r,n);if(o==="gone"||o==="denied"||o==="unreadable"){if(C(o==="gone"?"gone":o,r,n)==="skip")continue;return null}let i="";try{i=h(`${n.root}/${r}/cmdline`,"utf8")}catch(u){const c=u.code;if(c==="ENOENT"||c==="ESRCH")continue}if(!U(n,t,r,o,i))return null}return t}s(E,"readProcfsSnapshot");async function F(n=a){const e=j(n.root);if(e===null)return null;const t={processes:new Map,childrenOf:new Map},r=64;for(let o=0;o<e.length;o+=r){const i=e.slice(o,o+r),u=await Promise.all(i.map(async c=>{try{const l=L(await T(`${n.root}/${c}/stat`,"utf8"));if(l===null)return"failed";const f=await T(`${n.root}/${c}/cmdline`,"utf8").catch(d=>{if(d.code==="ENOENT"||d.code==="ESRCH")throw d;return""});return{pid:c,stat:l,cmdline:f}}catch(l){return C(N(l),c,n)==="skip"?"gone":"failed"}}));for(const c of u)if(c!=="gone"&&(c==="failed"||!U(n,t,c.pid,c.stat,c.cmdline)))return null}return t}s(F,"readProcfsSnapshotAsync");function bn(){if(m()){const n=E();if(n===null)return null;const e=new Map;for(const[t,r]of n.childrenOf)for(const o of r)e.set(o,t);return[...n.processes].map(([t,r])=>({pid:t,ppid:e.get(t)??0,command:r.command}))}try{const n=g("ps",["-ax","-o","pid=,ppid=,command="],{encoding:"utf8",timeout:2e3,maxBuffer:8388608});if(n.status!==0||typeof n.stdout!="string")return null;const e=[];for(const t of n.stdout.split(`
3
+ `)){const r=/^\s*(\d+)\s+(\d+)\s+(.*)$/.exec(t);r!==null&&e.push({pid:Number(r[1]),ppid:Number(r[2]),command:r[3].trim()})}return e}catch{return null}}s(bn,"listProcessTable");const hn={procIdentities:G,procProcessState:D,procCommand:H,readProcfsSnapshot:E,readProcfsSnapshotAsync:F};function Z(n){const e=new Map,t=new Map;for(const r of n.split(`
4
+ `)){const o=r.trim();if(o==="")continue;const i=/^(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+(?:\s+\S+){4})\s+(.+)$/.exec(o);if(i===null)continue;const u=Number(i[1]),c=Number(i[2]),l=Number(i[3]),f=i[4],d=i[5],O=i[6].trim();if(!Number.isInteger(u)||u<=0||!Number.isInteger(c)||c<0||O==="")continue;e.set(u,{zombie:f.startsWith("Z"),pgid:l,identity:A(u,d),command:O});const P=t.get(c);P===void 0?t.set(c,[u]):P.push(u)}return{processes:e,childrenOf:t}}s(Z,"parseLivenessTable");function x(n,e){const t=[],r=new Set([n]),o=[...e.get(n)??[]];for(;o.length>0;){const i=o.shift();if(r.has(i))continue;r.add(i),t.push(i);const u=e.get(i);u!==void 0&&o.push(...u)}return t}s(x,"descendantPidsFrom");function on(n){if(!Number.isInteger(n)||n<=0)return[];const e=I();return e===null?[]:x(n,e.childrenOf)}s(on,"descendantPids");function Sn(n){const e=I();return e===null?null:[...e.processes].filter(([,t])=>t.pgid===n&&!t.zombie).map(([t])=>t)}s(Sn,"pidsInProcessGroup");function J(n){const e=n.filter(r=>Number.isInteger(r)&&r>0),t=new Map;if(e.length===0)return t;if(m())return G(e);try{const r=g("ps",["-o","pid=,lstart=","-p",e.join(",")],{encoding:"utf8",timeout:2e3});if(r.error!=null||r.signal!=null||typeof r.stdout!="string"||typeof r.stderr=="string"&&r.stderr.trim()!=="")return null;for(const o of r.stdout.split(`
5
+ `)){const i=o.trim();if(i==="")continue;const u=i.indexOf(" ");if(u===-1)continue;const c=Number(i.slice(0,u)),l=i.slice(u+1).trim();!Number.isInteger(c)||c<=0||l===""||t.set(c,A(c,l))}return t}catch{return null}}s(J,"capturePidIdentities");function yn(n){if(!Number.isInteger(n)||n<=0)return null;if(m())return H(n);try{const e=g("ps",["-o","command=","-p",String(n)],{encoding:"utf8",timeout:2e3});if(e.status!==0||typeof e.stdout!="string")return null;const t=e.stdout.trim();return t===""?null:t}catch{return null}}s(yn,"capturePidCommand");function wn(n,e,t){if(!Number.isInteger(n)||n<=0)return{tree:[],identities:null,reused:!1};const r=I();if(r===null)return{tree:[n],identities:null,reused:!1};const o=r.processes.get(n)?.identity;if(e!=null&&o!==void 0&&!k(o,e))return{tree:[],identities:null,reused:!0};const u=t!==void 0&&Number.isInteger(t)&&t>0?new Set([t,...x(t,r.childrenOf)]):void 0,c=[n,...x(n,r.childrenOf)].filter(f=>!u?.has(f)),l=new Map;for(const f of c){const d=r.processes.get(f)?.identity;d!==void 0&&l.set(f,d)}return{tree:c,identities:l,reused:!1}}s(wn,"captureTeardownSnapshot");function kn(n,e="SIGTERM",t){const r=t!=null?J(n):void 0;for(const o of n){if(t!=null&&r!=null){const i=t.get(o),u=r.get(o);if(i!==void 0&&u!==void 0&&!k(u,i))continue}if(Q(o,e),!(!Number.isInteger(o)||o<=0))try{process.kill(o,e)}catch{}}}s(kn,"killProcessTreePids");function In(n){return S(n)?!0:on(n).some(e=>S(e))}s(In,"isProcessTreeAlive");function $n(n){return n.some(e=>S(e))}s($n,"isAnyPidAlive");export{I as captureLivenessSnapshot,gn as captureLivenessSnapshotAsync,yn as capturePidCommand,J as capturePidIdentities,wn as captureTeardownSnapshot,A as composeIdentity,on as descendantPids,k as identitiesMatch,$n as isAnyPidAlive,mn as isBootScopedIdentity,pn as isLegacyTicksIdentity,S as isPidAlive,dn as isPidPresent,In as isProcessTreeAlive,Q as killProcessGroup,kn as killProcessTreePids,bn as listProcessTable,en as makeBootIdCache,L as parseProcStat,Sn as pidsInProcessGroup,B as procCommandLine,hn as procfsBackendForTest,E as readProcfsSnapshot,F as readProcfsSnapshotAsync,an as recordedPidLiveness};
@@ -1 +1 @@
1
- var j=Object.defineProperty;var o=(n,e)=>j(n,"name",{value:e,configurable:!0});import{resolve as A}from"node:path";import{envNodeId as L}from"../../shared/env.js";import{cliClient as f}from"../../commands/api-client.js";import{apiErrorToCliError as h}from"../io.js";import{ApiError as l}from"../../api/index.js";import{defineBranch as y,defineLeaf as M}from"../command.js";import{qualifiedTopLevelCommandName as _,resolveCommandRegistry as N}from"../command-manifests/registry.js";import{executeExternalLeaf as S}from"./transport/exec-invoke.js";import{invokeHttpLeaf as U}from"./transport/http-invoke.js";import{readPathParam as b}from"./transport/http-plan.js";import{sha256Hex as P,signedHeaderDigest as H}from"./transport/sign.js";import{needsAppToken as R}from"../plugin-objects-manifest.js";async function $(n){const e=await f().request("GET",`/v1/providers/${encodeURIComponent(n)}/tools`);return new Set(e.groups.flatMap(a=>a.leaves.map(t=>t.tool)))}o($,"allowedProviderTools");function g(n,e,a){return n.children.some(t=>t.kind==="leaf"?a.has([...e,t.name].join(" ")):g(t,[...e,t.name],a))}o(g,"hasAllowedLeaf");function I(n,e,a){return n.children.filter(t=>t.tier!=="hidden"&&(t.kind==="leaf"?a.has([...e,t.name].join(" ")):g(t,[...e,t.name],a))).length}o(I,"visibleChildCount");function w(n,e,a){return async()=>{if(!L())return n.help;const t=await a();return{...n.help,listing:n.help.listing?.flatMap(d=>{const i=n.children.find(c=>c.name===d.name);if(i?.kind==="leaf")return t.has([...e,i.name].join(" "))?[d]:[];if(i?.kind!=="branch")return[];const s=[...e,i.name];if(!g(i,s,t))return[];const p=I(i,s,t);return[{...d,...d.subCount!==void 0?{subCount:p}:{}}]})}}}o(w,"providerHelp");const K="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 E(n,e,a,t=!1){return{outputKind:"object",...t?{machineSelector:!0}:{},run:o(async(d,i)=>{const s={...d};for(const p of a.params){if(!("type"in p))continue;const c=p.name.replace(/-([a-z])/g,(u,m)=>m.toUpperCase()),r=s[c];p.type==="file"&&typeof r=="string"?s[c]=A(r):p.type==="path"&&r!==void 0&&(s[c]=b(`provider command \`${n} ${e}\``,p,r))}try{return await f({timeoutMs:0}).request("POST","/v1/provider/call",{provider:n,tool:e,arguments:s,...i?.machine===void 0?{}:{machine:i.machine}},{timeout:0})}catch(p){throw p instanceof l?h(p,void 0,{providerCall:!0}):p}},"run")}}o(E,"providerLeafAdapter");function k(n,e){return/^[A-Za-z0-9_-]{1,64}$/.test(n)&&n!=="crtr"&&!e.has(n)}o(k,"validRootName");function ne(n,e){const a=[],t=[];for(const i of n){const{provider:s,commands:p}=i;if(!Array.isArray(p)||i.status!=="available"&&!k(s,e))continue;if(i.status!=="available"){const r=i.error??"bundle installation pending",u=`Unavailable (${i.status}): ${r}`,m=y({name:s,rootEntry:{concept:i.display_name,desc:u,useWhen:u},help:{name:s,summary:u},children:[]});m.unavailable=u,t.push(m);continue}const c=o(r=>r.kind==="branch"?{...r,children:r.children.map(c)}:{...r,rest:{method:"GET",path:"/",params:{}}},"helpNode");for(const r of p){if(r.kind!=="branch"||!Array.isArray(r.children)||!k(r.name,e))continue;const u=c(r);a.push({contributor:{name:s,scope:"user"},manifestName:r.name,node:i.groups.length===0?{...u,rootEntry:void 0}:u,adaptLeaf:o((m,x)=>E(s,x.slice(1).join(" "),m,i.transport==="machine"),"adaptLeaf")})}}const d=q(N(a,e));for(const i of d){const s=n.find(r=>r.status==="available"&&r.commands.some(u=>u.name===i.name||_({name:r.provider},u.name)===i.name));if(s?.status!=="available")continue;const p=new Set(s.groups.flatMap(r=>r.leaves.map(u=>u.tool))),c=o((r,u)=>{r.effectiveHelp=w(r,u,async()=>p);for(const m of r.children)m.kind==="branch"&&c(m,[...u,m.name])},"attachHelp");c(i,[])}return[...d,...t]}o(ne,"composeSandboxProviderSubtrees");async function O(n){try{const{token:e,socket:a}=await f().plugins.appToken(n);return{CRTR_APP_TOKEN:e,CRTR_DAEMON_SOCKET:a}}catch(e){if(e instanceof l&&(e.status===404||e.status===403))return;throw e instanceof l?h(e):e}}o(O,"appEnvFor");function C(n,e){return(a,t)=>{const d=n.transport,i=e(t);if(n.provider!==void 0)return E(n.provider.id,i.slice(1).join(" "),a,d.kind==="machine");if(d.kind==="exec"){const p=d.executable;if(p===void 0)throw new Error(`exec plugin "${n.plugin.name}" has an executable leaf without transport.executable`);return{outputKind:"object",run:o(async c=>{const r=R(n.plugin.manifest)?await O(n.plugin.name):void 0;return S({plugin:n.plugin,executable:p,commandPath:i,output:a.output,...r!==void 0?{appEnv:r}:{}},c)},"run")}}if(d.kind==="machine")throw new Error(`machine plugin "${n.plugin.name}" is not a capability provider`);const s=a;return{outputKind:s.rest.streaming===!0?"jsonl":"object",run:o((p,c)=>U({registration:{name:n.plugin.name,endpoint:d.endpoint,...d.authEnv!==void 0?{authEnv:d.authEnv}:{}},...n.manifest?.baseUrl!==void 0?{baseUrl:n.manifest.baseUrl}:{},...n.manifest?.timeouts!==void 0?{timeouts:n.manifest.timeouts}:{},params:s.params,rest:s.rest,output:s.output,commandPath:i},p,c,{callHeaders:o(r=>f().plugins.callHeaders(n.plugin.name,{method:r.method,url:r.url.href,body_sha256:P(r.body),...H(r.headers)}).then(u=>u.headers,u=>{throw u instanceof l?h(u):u}),"callHeaders")}),"run")}}}o(C,"adaptLeaf");function ae(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:C(e,a=>[e.manifestName,...a.slice(1)]),...e.provider!==void 0?{provider:e.provider}:{}})),coreMounts:[],issues:[]}}o(ae,"adaptPluginContributions");function re(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:C({...e,manifestName:e.node.name},a=>e.provider===void 0?[...a]:[e.provider.id,...a.slice(e.parent.length)])}))}o(re,"adaptPluginCoreMounts");function q(n){return n.contributions.map(e=>v(e,e.node,[e.node.name]))}o(q,"composeExternalSubtrees");function te(n,e){return n.filter(a=>a.parent.join("\0")===e.join("\0")).map(a=>a.node.kind==="branch"?v(a,a.node,[...e,a.node.name],a.provider===void 0?1:e.length):T(a,a.node,[...e,a.node.name]))}o(te,"composeCoreBranchMounts");function v(n,e,a,t=1){const d=e.rootEntry===void 0?void 0:{concept:e.rootEntry.concept,desc:e.rootEntry.description,useWhen:e.rootEntry.whenToUse},i=y({name:e.name,description:e.description,whenToUse:e.whenToUse,...e.tier!==void 0?{tier:e.tier}:{},...n.plugin!==void 0?{plugin:n.plugin}:{},...d!==void 0?{rootEntry:d}:{},help:{name:a.join(" "),summary:e.summary,...e.model!==void 0?{model:e.model}:{},...a.length===1&&n.extensionIssue!==void 0?{extensionIssue:n.extensionIssue}:{}},...e.passthrough!==void 0?{passthrough:e.passthrough}:{},children:e.children.map(s=>s.kind==="branch"?v(n,s,[...a,s.name],t):T(n,s,[...a,s.name]))});return n.provider!==void 0&&(i.effectiveHelp=w(i,a.slice(t),()=>$(n.provider.id))),i}o(v,"buildBranch");function T(n,e,a){const t=n.adaptLeaf(e,a);return M({name:e.name,description:e.description,whenToUse:e.whenToUse,...e.tier!==void 0?{tier:e.tier}:{},...n.plugin!==void 0?{plugin:n.plugin}:{},...t.machineSelector===!0?{machineSelector:!0}:{},help:{name:a.join(" "),summary:e.summary,...t.machineSelector===!0?{guide:K}:{},params:e.params,output:e.output,outputKind:t.outputKind,effects:[...e.effects]},run:t.run})}o(T,"buildLeaf");export{ae as adaptPluginContributions,re as adaptPluginCoreMounts,te as composeCoreBranchMounts,q as composeExternalSubtrees,ne as composeSandboxProviderSubtrees};
1
+ var b=Object.defineProperty;var o=(n,e)=>b(n,"name",{value:e,configurable:!0});import{resolve as _}from"node:path";import{ExitCode as j}from"../../types.js";import{envNodeId as A}from"../../shared/env.js";import{cliClient as f}from"../../commands/api-client.js";import{apiErrorToCliError as h}from"../io.js";import{ApiError as l}from"../../api/index.js";import{defineBranch as y,defineLeaf as L}from"../command.js";import{qualifiedTopLevelCommandName as M,resolveCommandRegistry as N}from"../command-manifests/registry.js";import{executeExternalLeaf as S}from"./transport/exec-invoke.js";import{invokeHttpLeaf as U}from"./transport/http-invoke.js";import{readPathParam as P}from"./transport/http-plan.js";import{sha256Hex as H,signedHeaderDigest as R}from"./transport/sign.js";import{needsAppToken as K}from"../plugin-objects-manifest.js";async function $(n){const e=await f().request("GET",`/v1/providers/${encodeURIComponent(n)}/tools`);return new Set(e.groups.flatMap(a=>a.leaves.map(t=>t.tool)))}o($,"allowedProviderTools");function g(n,e,a){return n.children.some(t=>t.kind==="leaf"?a.has([...e,t.name].join(" ")):g(t,[...e,t.name],a))}o(g,"hasAllowedLeaf");function I(n,e,a){return n.children.filter(t=>t.tier!=="hidden"&&(t.kind==="leaf"?a.has([...e,t.name].join(" ")):g(t,[...e,t.name],a))).length}o(I,"visibleChildCount");function E(n,e,a){return async()=>{if(!A())return n.help;const t=await a();return{...n.help,listing:n.help.listing?.flatMap(d=>{const i=n.children.find(c=>c.name===d.name);if(i?.kind==="leaf")return t.has([...e,i.name].join(" "))?[d]:[];if(i?.kind!=="branch")return[];const s=[...e,i.name];if(!g(i,s,t))return[];const p=I(i,s,t);return[{...d,...d.subCount!==void 0?{subCount:p}:{}}]})}}}o(E,"providerHelp");const O="Runs on one of your devices. Add `--machine <machine_id>` to pick which; without it the call goes to your only device, and with several the answer is `machine_required` listing each one's machine_id. The `runtime:` prefix on the id is optional.";function w(n,e,a,t=!1){return{outputKind:"object",...t?{machineSelector:!0}:{},run:o(async(d,i)=>{const s={...d};for(const p of a.params){if(!("type"in p))continue;const c=p.name.replace(/-([a-z])/g,(u,m)=>m.toUpperCase()),r=s[c];p.type==="file"&&typeof r=="string"?s[c]=_(r):p.type==="path"&&r!==void 0&&(s[c]=P(`provider command \`${n} ${e}\``,p,r))}try{return await f({timeoutMs:0}).request("POST","/v1/provider/call",{provider:n,tool:e,arguments:s,...i?.machine===void 0?{}:{machine:i.machine}},{timeout:0})}catch(p){throw p instanceof l?h(p,void 0,{providerCall:!0}):p}},"run")}}o(w,"providerLeafAdapter");function k(n,e){return/^[A-Za-z0-9_-]{1,64}$/.test(n)&&n!=="crtr"&&!e.has(n)}o(k,"validRootName");function re(n,e){const a=[],t=[];for(const i of n){const{provider:s,commands:p}=i;if(!Array.isArray(p)||i.status!=="available"&&!k(s,e))continue;if(i.status!=="available"){const r=i.error??"bundle installation pending",u=`Unavailable (${i.status}): ${r}`,m=y({name:s,rootEntry:{concept:i.display_name,desc:u,useWhen:u},help:{name:s,summary:u},children:[],refusal:{code:"provider_unavailable",message:u,exit:j.NETWORK,next:"The daemon retries bundle installation on grant sync; retry after the provider becomes available."}});t.push(m);continue}const c=o(r=>r.kind==="branch"?{...r,children:r.children.map(c)}:{...r,rest:{method:"GET",path:"/",params:{}}},"helpNode");for(const r of p){if(r.kind!=="branch"||!Array.isArray(r.children)||!k(r.name,e))continue;const u=c(r);a.push({contributor:{name:s,scope:"user"},manifestName:r.name,node:i.groups.length===0?{...u,rootEntry:void 0}:u,adaptLeaf:o((m,T)=>w(s,T.slice(1).join(" "),m,i.transport==="machine"),"adaptLeaf")})}}const d=D(N(a,e));for(const i of d){const s=n.find(r=>r.status==="available"&&r.commands.some(u=>u.name===i.name||M({name:r.provider},u.name)===i.name));if(s?.status!=="available")continue;const p=new Set(s.groups.flatMap(r=>r.leaves.map(u=>u.tool))),c=o((r,u)=>{r.effectiveHelp=E(r,u,async()=>p);for(const m of r.children)m.kind==="branch"&&c(m,[...u,m.name])},"attachHelp");c(i,[])}return[...d,...t]}o(re,"composeSandboxProviderSubtrees");async function q(n){try{const{token:e,socket:a}=await f().plugins.appToken(n);return{CRTR_APP_TOKEN:e,CRTR_DAEMON_SOCKET:a}}catch(e){if(e instanceof l&&(e.status===404||e.status===403))return;throw e instanceof l?h(e):e}}o(q,"appEnvFor");function x(n,e){return(a,t)=>{const d=n.transport,i=e(t);if(n.provider!==void 0)return w(n.provider.id,i.slice(1).join(" "),a,d.kind==="machine");if(d.kind==="exec"){const p=d.executable;if(p===void 0)throw new Error(`exec plugin "${n.plugin.name}" has an executable leaf without transport.executable`);return{outputKind:"object",run:o(async c=>{const r=K(n.plugin.manifest)?await q(n.plugin.name):void 0;return S({plugin:n.plugin,executable:p,commandPath:i,output:a.output,...r!==void 0?{appEnv:r}:{}},c)},"run")}}if(d.kind==="machine")throw new Error(`machine plugin "${n.plugin.name}" is not a capability provider`);const s=a;return{outputKind:s.rest.streaming===!0?"jsonl":"object",run:o((p,c)=>U({registration:{name:n.plugin.name,endpoint:d.endpoint,...d.authEnv!==void 0?{authEnv:d.authEnv}:{}},...n.manifest?.baseUrl!==void 0?{baseUrl:n.manifest.baseUrl}:{},...n.manifest?.timeouts!==void 0?{timeouts:n.manifest.timeouts}:{},params:s.params,rest:s.rest,output:s.output,commandPath:i},p,c,{callHeaders:o(r=>f().plugins.callHeaders(n.plugin.name,{method:r.method,url:r.url.href,body_sha256:H(r.body),...R(r.headers)}).then(u=>u.headers,u=>{throw u instanceof l?h(u):u}),"callHeaders")}),"run")}}}o(x,"adaptLeaf");function te(n){return{contributions:n.map(e=>({contributor:{name:e.plugin.name,scope:e.plugin.scope,sourceMarketplace:e.plugin.sourceMarketplace},node:e.node,manifestName:e.manifestName,adaptLeaf:x(e,a=>[e.manifestName,...a.slice(1)]),...e.provider!==void 0?{provider:e.provider}:{}})),coreMounts:[],issues:[]}}o(te,"adaptPluginContributions");function ie(n){return n.map(e=>({parent:e.parent,plugin:e.plugin.name,contributor:{name:e.plugin.name,scope:e.plugin.scope,sourceMarketplace:e.plugin.sourceMarketplace},node:e.node,...e.provider!==void 0?{provider:e.provider}:{},adaptLeaf:x({...e,manifestName:e.node.name},a=>e.provider===void 0?[...a]:[e.provider.id,...a.slice(e.parent.length)])}))}o(ie,"adaptPluginCoreMounts");function D(n){return n.contributions.map(e=>v(e,e.node,[e.node.name]))}o(D,"composeExternalSubtrees");function se(n,e){return n.filter(a=>a.parent.join("\0")===e.join("\0")).map(a=>a.node.kind==="branch"?v(a,a.node,[...e,a.node.name],a.provider===void 0?1:e.length):C(a,a.node,[...e,a.node.name]))}o(se,"composeCoreBranchMounts");function v(n,e,a,t=1){const d=e.rootEntry===void 0?void 0:{concept:e.rootEntry.concept,desc:e.rootEntry.description,useWhen:e.rootEntry.whenToUse},i=y({name:e.name,description:e.description,whenToUse:e.whenToUse,...e.tier!==void 0?{tier:e.tier}:{},...n.plugin!==void 0?{plugin:n.plugin}:{},...d!==void 0?{rootEntry:d}:{},help:{name:a.join(" "),summary:e.summary,...e.model!==void 0?{model:e.model}:{},...a.length===1&&n.extensionIssue!==void 0?{extensionIssue:n.extensionIssue}:{}},...e.passthrough!==void 0?{passthrough:e.passthrough}:{},children:e.children.map(s=>s.kind==="branch"?v(n,s,[...a,s.name],t):C(n,s,[...a,s.name]))});return n.provider!==void 0&&(i.effectiveHelp=E(i,a.slice(t),()=>$(n.provider.id))),i}o(v,"buildBranch");function C(n,e,a){const t=n.adaptLeaf(e,a);return L({name:e.name,description:e.description,whenToUse:e.whenToUse,...e.tier!==void 0?{tier:e.tier}:{},...n.plugin!==void 0?{plugin:n.plugin}:{},...t.machineSelector===!0?{machineSelector:!0}:{},help:{name:a.join(" "),summary:e.summary,...t.machineSelector===!0?{guide:O}:{},params:e.params,output:e.output,outputKind:t.outputKind,effects:[...e.effects]},run:t.run})}o(C,"buildLeaf");export{te as adaptPluginContributions,ie as adaptPluginCoreMounts,se as composeCoreBranchMounts,D as composeExternalSubtrees,re as composeSandboxProviderSubtrees};
@@ -1,5 +1,7 @@
1
1
  import type { RootHelp, RootEntry, BranchHelp, LeafHelp, EffectiveLeafHelp, AsyncHelpState, InputParam, SubTier } from './help.js';
2
2
  import { CrtrError } from './errors.js';
3
+ import type { ApiError } from '../api/errors.js';
4
+ import { type ExitCodeValue } from '../types.js';
3
5
  /** Runtime context passed to a leaf's run function. Includes metadata about
4
6
  * which parameters were explicitly provided vs. defaulted. Used by configured
5
7
  * CLIs to distinguish intentionally-empty stdin from absent stdin. */
@@ -71,6 +73,12 @@ export interface LeafDef {
71
73
  /** Suppress the dispatcher's trailing newline for byte-exact stdout. */
72
74
  stdoutExact?: (result: Record<string, unknown>) => boolean;
73
75
  }
76
+ export interface BranchRefusal {
77
+ code: string;
78
+ message: string;
79
+ next: string;
80
+ exit: ExitCodeValue;
81
+ }
74
82
  export interface BranchDef {
75
83
  kind: 'branch';
76
84
  name: string;
@@ -109,14 +117,18 @@ export interface BranchDef {
109
117
  bin: string;
110
118
  installHint: string;
111
119
  };
112
- /** A granted provider without a usable bundle rejects every leaf path. */
113
- unavailable?: string;
120
+ /** Every invocation of this branch except its own `-h` throws this refusal before any client is built: a granted provider
121
+ * without a usable bundle (`provider_unavailable`), or a Northlight preview before sign-in (`needs_northlight_account`). */
122
+ refusal?: BranchRefusal;
114
123
  children: (LeafDef | BranchDef)[];
115
124
  }
116
125
  export interface RootDef {
117
126
  kind: 'root';
118
127
  help: RootHelp;
119
128
  subtrees: BranchDef[];
129
+ /** Why a moved computer could not read the account's provider commands (`not_signed_in`, `northlight_unreachable`, `move_unfinished`). The tree
130
+ * was built without them, so a top-level name it lacks may be one of those commands: the dispatcher raises this instead of `unknown_path`. */
131
+ providerCatalogFailure?: ApiError | CrtrError;
120
132
  }
121
133
  export declare function defineLeaf(opts: {
122
134
  name: string;
@@ -153,6 +165,7 @@ export declare function defineBranch(opts: {
153
165
  bin: string;
154
166
  installHint: string;
155
167
  };
168
+ refusal?: BranchRefusal;
156
169
  children: (LeafDef | BranchDef)[];
157
170
  }): BranchDef;
158
171
  /** Walk the whole tree and collect every node's SlashSpec (depth-first). Used
@@ -187,12 +200,8 @@ export declare function renderNode(node: AnyNode): string;
187
200
  * the full path to the matched node (not just its local name), so the recovery
188
201
  * hint is a command that actually exists. No fuzzy matching. */
189
202
  export declare function unknownPathError(node: AnyNode, path: string[], bad: string): CrtrError;
190
- /** Northlight's account site: where a person creates a join token. */
191
- export declare const DEFAULT_DIRECTORY_ISSUER = "https://account.northlight.ai";
192
- /** An unknown top-level command on a runtime never joined to a directory
193
- * (no `runtime.issuer`). Every `cloud` leaf and every other provider command
194
- * is installed by grant sync, which runs only on a joined runtime, so the
195
- * likeliest cause of a missing command there is the missing join. */
203
+ /** An unknown top-level command on a computer with no Northlight account (no `runtime/machine.json`, no `runtime.issuer`):
204
+ * a command that comes from Northlight needs the account. */
196
205
  export declare function notJoinedError(bad: string): CrtrError;
197
206
  /** Options for parseArgv. */
198
207
  export interface ParseArgvOptions {