dsh-home-hosted 0.6.2 → 0.6.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/lib/index.js.map CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../src/index.ts", "../src/config.ts", "../src/shared/contracts.ts", "../src/service.ts", "../src/boot/common.ts", "../src/util/fsx.ts", "../src/boot/ladder.ts", "../src/util/exec.ts", "../src/boot/systemd.ts", "../src/boot/escape.ts", "../src/boot/launchd.ts", "../src/boot/xdg.ts", "../src/boot/windows.ts", "../src/boot/fallback.ts", "../src/home-hosted/config-file.ts", "../src/home-hosted/layout.ts", "../src/home-hosted/dsh-entry.ts", "../src/util/paths.ts", "../src/home-hosted/launch.ts", "../src/home-hosted/resolve.ts", "../src/home-hosted/launcher.ts", "../src/home-hosted/instances.ts", "../src/home-hosted/runtime.ts", "../src/home-hosted/entries.ts", "../src/home-hosted/panel-control.ts", "../src/home-hosted/panel.ts", "../src/home-hosted/token.ts", "../src/rpc.ts", "../src/home-hosted/instances-notice.ts", "../src/home-hosted/web-notice.ts", "../src/settings.ts", "../src/tools.ts"],
4
- "sourcesContent": ["/**\n * `dsh-home-hosted` \u2014 boot autostart for the home-hosted panel, and home-hosted\n * server management, from inside DeepSeek Harness.\n *\n * The plugin supplies no server entries of its own: everything it manages is\n * declared by the person using the page, and nothing here knows a blessed id\n * beyond the one it is told to treat as the running harness.\n */\nimport type { Context } from '@deepseek-ai/cordis'\nimport path from 'node:path'\nimport { Config } from './config.js'\nimport type { Config as ConfigShape } from './config.js'\nimport { registerRpc } from './rpc.js'\nimport { HomeHostedService } from './service.js'\nimport { registerInstancesNotice } from './home-hosted/instances-notice.js'\nimport { registerAuthNotice } from './home-hosted/web-notice.js'\nimport { SettingsStore } from './settings.js'\nimport { registerAgentTools } from './tools.js'\nimport { ensureDir } from './util/fsx.js'\nimport { pluginStateDir, resolveHomeHostedHome } from './util/paths.js'\n\nexport const name = 'dsh-home-hosted'\n\nexport { Config }\n\nexport const inject: string[] = []\n\nexport function apply(ctx: Context, config?: ConfigShape): void {\n const resolved: ConfigShape = {\n stateDir: config?.stateDir,\n homeHostedCommand: config?.homeHostedCommand,\n defaultEntryId: config?.defaultEntryId ?? 'dsh',\n instanceRoots: config?.instanceRoots ?? [],\n }\n\n const stateDir = pluginStateDir(resolved.stateDir)\n ensureDir(stateDir, 0o700)\n\n // Per state root, not per machine: a second dsh install must not adopt, edit\n // or autostart the first one's panel. The page reports which root this is.\n const panel = resolveHomeHostedHome(stateDir)\n\n const settings = new SettingsStore(path.join(stateDir, 'settings.json'), resolved.defaultEntryId)\n const service = new HomeHostedService(ctx, {\n home: panel.home,\n panelHomeSource: panel.source,\n stateDir,\n homeHostedCommand: resolved.homeHostedCommand,\n defaultEntryId: resolved.defaultEntryId,\n instanceRoots: resolved.instanceRoots,\n settings,\n })\n\n registerRpc(ctx, service)\n registerAgentTools(ctx, service, settings)\n // Read per assembly, so the setting takes effect without re-registering \u2014\n // and re-measured there, so a panel started while this dsh runs is noticed\n // even when nobody has the settings page open.\n registerInstancesNotice(ctx, {\n instances: () => service.instancesNow(),\n enabled: () => settings.get().instancesNotice,\n })\n // Read per 401, so the setting takes effect without re-installing the wrapper.\n registerAuthNotice(ctx, () => (settings.get().authNotice ? service.panelLogUrl() : null))\n\n // Measure the panel inventory once now: the first model step should already\n // know whether this machine has more than one panel.\n void service.instances().catch(() => {\n // a discovery failure only means an empty inventory\n })\n\n // Re-assert an enabled boot entry shortly after startup: a node or CLI upgrade\n // moves the paths a unit was written with, and the fix is to rewrite it.\n ctx.effect(() => {\n const timer = setTimeout(() => {\n void service.reconcile().catch(() => {\n // the page reads the resulting status; a failed repair is not a startup failure\n })\n }, 5000)\n return () => clearTimeout(timer)\n }, 'dsh-home-hosted: startup reconcile')\n}\n", "/**\n * The Cordis row configuration: operator overrides only. Everything a person\n * changes in the plugin's own page lives in `settings.json` under the plugin\n * state directory, so the two never fight over the same field.\n */\nimport z from '@deepseek-ai/schemastery'\n\nexport interface Config {\n /** Override the plugin state directory (defaults to `$DSH_HOME/dsh-home-hosted`). */\n stateDir?: string\n /** Override the home-hosted executable used in generated boot entries. */\n homeHostedCommand?: string\n /** The entry this plugin manages for the running harness. */\n defaultEntryId: string\n /**\n * Extra home-hosted state roots to report as other panels. Discovery already\n * knows the managed root, `$HHOSTED_HOME` and the `~/.home-hosted*` siblings;\n * a panel run from a project with its own `--home` is declared here.\n */\n instanceRoots: string[]\n}\n\nexport const Config: z<Config> = z.object({\n stateDir: z.string().description('Plugin state directory; defaults to $DSH_HOME/dsh-home-hosted'),\n homeHostedCommand: z.string().description('home-hosted executable to put in a generated autostart entry'),\n defaultEntryId: z.string().default('dsh').description('Server entry id used for the running harness'),\n instanceRoots: z.array(z.string()).default([]).description('Other home-hosted state roots (--home) to report as other panels'),\n})\n", "/**\n * The one wire contract shared by the host half and the browser half.\n *\n * Nothing here may import a Node builtin: the browser bundle includes this\n * module verbatim.\n */\n\n/** Exact Fetch route registered on DeepSeek Harness's authenticated `/api` channel. */\nexport const RPC_PATH = '/home-hosted'\n\n/** Bumped when a payload shape changes; a mismatch is reported, never guessed at. */\nexport const RPC_VERSION = 1\n\nexport interface RpcError {\n code: string\n message: string\n detail?: unknown\n}\n\nexport type Envelope<T> = { ok: true, value: T } | { ok: false, error: RpcError }\n\n/** Endpoints the browser and the agent tools both speak. */\nexport type RpcEndpoint =\n | 'status'\n | 'servers.list'\n | 'workspaces.list'\n | 'panel.migrate'\n | 'servers.get'\n | 'servers.create'\n | 'servers.update'\n | 'servers.delete'\n | 'servers.start'\n | 'servers.stop'\n | 'servers.restart'\n | 'servers.freePort'\n | 'entries.apply'\n | 'entries.remove'\n | 'entries.restore'\n | 'boot.install'\n | 'boot.uninstall'\n | 'boot.verify'\n | 'panel.start'\n | 'panel.stop'\n | 'panel.takeover'\n | 'panel.reclaimToken'\n | 'cli.installGlobal'\n | 'ui.manage'\n | 'settings.update'\n\n/** A settings write sends only the changed subtree; the host merges group by group. */\n/** The workspace every panel has, and the one this plugin manages. */\nexport const DEFAULT_WORKSPACE = 'default'\n\n/** The panel's workspace id shape; the same regex its own schema enforces. */\nexport const WORKSPACE_ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/\n\nexport function isWorkspaceId(value: unknown): value is string {\n return typeof value === 'string' && WORKSPACE_ID_PATTERN.test(value)\n}\n\nexport interface SettingsPatch {\n autostart?: Partial<PluginSettings['autostart']>\n manageDsh?: boolean\n entries?: PluginSettings['entries']\n panel?: Partial<PluginSettings['panel']>\n authNotice?: boolean\n reclaimToken?: boolean\n instancesNotice?: boolean\n uiStyle?: UiStyle\n agentTools?: Partial<PluginSettings['agentTools']>\n cli?: Partial<PluginSettings['cli']>\n}\n\n/**\n * A state root to act on instead of the panel this plugin manages. Only a panel\n * discovery reports is accepted, and only on the endpoints {@link FOREIGN_MECHANISMS}\n * lists.\n */\nexport interface ForeignTarget {\n /** A discovered panel's `--home`; omitted means the panel this plugin manages. */\n home?: string\n /**\n * Which workspace inside that panel to act on; omitted means the plugin's own\n * managed workspace (another panel: its default one). Every workspace the panel\n * serves is reachable, not only the managed one.\n */\n workspace?: string\n /**\n * How to reach it; omitted means the endpoint's first mechanism. Only a value\n * {@link FOREIGN_MECHANISMS} lists for that endpoint is accepted.\n */\n via?: ForeignMechanism\n}\n\n/**\n * How this plugin can act on a panel it does not manage.\n *\n * `file` edits that panel's workspace config file, `cli` runs the home-hosted\n * CLI against its state root, and `api` enrols a token for it and drives its own\n * HTTP API \u2014 the only way to reach some operations, and the only one that gives\n * live status.\n */\nexport type ForeignMechanism = 'file' | 'cli' | 'api'\n\n/**\n * Which ways each endpoint has to reach another panel, least invasive first (the\n * first entry is the default when a caller names no mechanism). A config edit\n * needs no credential, the CLI needs none either, and `api` mints one.\n *\n * Anything absent here is about this machine or about the managed panel itself\n * and is never aimed elsewhere.\n */\nexport const FOREIGN_MECHANISMS: Partial<Record<RpcEndpoint, readonly ForeignMechanism[]>> = {\n 'servers.list': ['file', 'api'],\n 'servers.create': ['file', 'api'],\n 'servers.update': ['file', 'api'],\n 'servers.delete': ['file', 'api'],\n 'servers.start': ['cli', 'api'],\n 'servers.stop': ['cli', 'api'],\n 'servers.restart': ['cli', 'api'],\n 'ui.manage': ['cli'],\n}\n\nexport interface EndpointPayloads {\n /** `refresh` bypasses the short panel-inventory cache; status is live either way. */\n 'status': { refresh?: boolean }\n 'servers.list': ForeignTarget\n 'servers.get': ForeignTarget & { id: string }\n 'servers.create': ForeignTarget & { entry: ServerEntry }\n 'servers.update': ForeignTarget & { id: string, patch: ServerEntryPatch }\n 'servers.delete': ForeignTarget & { id: string }\n 'servers.start': ForeignTarget & { id: string }\n 'servers.stop': ForeignTarget & { id: string }\n 'servers.restart': ForeignTarget & { id: string }\n 'servers.freePort': ForeignTarget & { id: string }\n /** Every workspace a panel serves, with its counts. */\n 'workspaces.list': { home?: string }\n /** Move a pre-0.7 panel root into `.hh` (idempotent; the CLI's own migration). */\n 'panel.migrate': Record<string, never>\n 'entries.apply': { intents: EntryIntent[] }\n /** Stop managing an entry: restore what it was, or remove it when we created it. */\n 'entries.remove': { id: string }\n 'entries.restore': { id: string }\n 'boot.install': { mechanism?: BootMechanism }\n 'boot.uninstall': { mechanism?: BootMechanism }\n 'boot.verify': Record<string, never>\n /** Start the preferred CLI as a detached panel; refused while one already answers. */\n 'panel.start': Record<string, never>\n /**\n * Stop the answering panel through its own CLI. The servers it supervises stop\n * with it, which can include the dsh this plugin is running in.\n */\n 'panel.stop': Record<string, never>\n /** Stop the answering panel and start the preferred copy instead. */\n 'panel.takeover': { force?: boolean }\n /**\n * Mint a fresh panel API token and enrol it, replacing one the panel refuses.\n * Clearing first is what makes it work when home-hosted already holds a hash.\n */\n 'panel.reclaimToken': Record<string, never>\n /** Install the pinned range as a global CLI, so the `global` preference can use it. */\n 'cli.installGlobal': Record<string, never>\n /**\n * Drive the panel's own UI. `file` installs a local zip; `asset` (with optional\n * `repo`/`tag`) installs a release asset through the panel's own `ui-switch`,\n * so its download and version checking are not reimplemented here.\n */\n 'ui.manage': ForeignTarget & {\n action: UiAction\n file?: string\n asset?: string\n repo?: string\n tag?: string\n }\n 'settings.update': { patch: SettingsPatch }\n}\n\nexport interface RpcRequest {\n v: number\n endpoint: RpcEndpoint\n payload?: unknown\n}\n\n// ---------------------------------------------------------------------------\n// Server entries\n// ---------------------------------------------------------------------------\n\nexport type OnPortConflict = 'block' | 'warn' | 'follow' | 'reclaim' | 'kill'\n\n/** Every policy home-hosted's config schema accepts; anything else stops it booting. */\nexport const ON_PORT_CONFLICT_POLICIES: readonly OnPortConflict[] = ['block', 'warn', 'follow', 'reclaim', 'kill']\n\nexport function isOnPortConflict(value: unknown): value is OnPortConflict {\n return typeof value === 'string' && (ON_PORT_CONFLICT_POLICIES as readonly string[]).includes(value)\n}\n\n/** Only the fields this plugin reads or writes are typed; the rest is preserved. */\nexport interface ServerEntry {\n id: string\n label?: string\n enabled?: boolean\n autostart?: boolean\n command?: string\n args?: string[]\n cwd?: string\n env?: Record<string, string>\n dataEnvs?: Record<string, string>\n port?: number | null\n bind?: string\n onPortConflict?: OnPortConflict\n stop?: { killPortHolders?: boolean, [key: string]: unknown }\n health?: Record<string, unknown>\n restart?: Record<string, unknown>\n [key: string]: unknown\n}\n\nexport type ServerEntryPatch = Partial<ServerEntry>\n\n/** What the plugin decides about one entry, and the keys it therefore owns. */\nexport interface EntryIntent {\n id: string\n /**\n * Run under home-hosted's nanny, so stopping or restarting the *panel* leaves\n * this process alive. Needs home-hosted 0.6.3.\n */\n persistent: boolean\n autostart: boolean\n onPortConflict: OnPortConflict\n stopKillPortHolders: boolean\n}\n\n/** Keys an intent owns: a patch touches these and nothing else. */\nexport const OWNED_ENTRY_KEYS = ['autostart', 'onPortConflict', 'persistent', 'stop'] as const\n\nexport interface ServerEntryView {\n id: string\n /** The workspace it lives in: a server id is only unique inside one. */\n workspace: string\n status: string\n pid: number | null\n url: string | null\n config: ServerEntry\n}\n\nexport interface ManagedEntryStatus {\n intent: EntryIntent\n exists: boolean\n /** True once the plugin has adopted the entry and holds a snapshot of what it was. */\n managed: boolean\n /** Owned keys whose live value differs from the intent. */\n drift: string[]\n live: ServerEntryView | null\n snapshot: ServerEntry | null\n}\n\n// ---------------------------------------------------------------------------\n// Panel\n// ---------------------------------------------------------------------------\n\n/**\n * `enrolled` \u2014 the plugin holds a token; `present` \u2014 home-hosted has one this\n * plugin does not hold; `absent` \u2014 none exists yet; `stale` \u2014 the plugin holds\n * one the panel just refused; `unknown` \u2014 the panel was not reachable to ask.\n */\nexport type TokenState = 'enrolled' | 'present' | 'absent' | 'stale' | 'unknown'\n\nexport interface PanelStatus {\n /** `$HHOSTED_HOME` this plugin resolved, whether or not the panel answers. */\n home: string\n reachable: boolean\n /** The port the panel's own config holds (what a restart would use). */\n configPort?: number | null\n url: string | null\n version: string | null\n pid: number | null\n /** How writes are authenticated right now. */\n writeVia: 'api' | 'file' | 'none'\n token: TokenState\n /** True when the panel was asked and accepted the token, so `token` is measured, not assumed. */\n tokenVerified?: boolean\n detail: string\n}\n\n/** Which home-hosted CLI the plugin drives, and where it came from. */\nexport type CliSource = 'config' | 'dependency' | 'path' | 'none'\n\n/** One place a CLI could come from, with the version found there. */\nexport interface CliCandidate {\n source: 'config' | 'dependency' | 'path'\n path: string | null\n version: string | null\n}\n\nexport type UiAction = 'status' | 'update' | 'revert' | 'switch'\n\nexport interface UiResult {\n ok: boolean\n detail: string\n /** The active UI, when the action reported or changed one. */\n ui?: { name: string | null, version: string | null, repo?: string | null, tag?: string | null } | null\n output?: string\n}\n\nexport interface PanelControlResult {\n ok: boolean\n detail: string\n url?: string | null\n version?: string | null\n}\n\nexport interface CliStatus {\n /** `config` (operator override), `dependency` (the pinned copy), `path`, or none. */\n source: CliSource\n path: string | null\n version: string | null\n /** The range the plugin ships in its own dependencies. */\n expectedRange: string\n /** False when the resolved CLI is older than the oldest release this plugin supports. */\n supported: boolean\n /** The stable launcher a boot entry runs instead of a moving node_modules path. */\n launcherPath?: string | null\n /** What the launcher answers right now, as the boot entry would invoke it. */\n launcherVersion?: string | null\n /** Which copy the plugin prefers. */\n prefer?: 'pinned' | 'global'\n /** The plugin's own copy, when it resolves. */\n dependency?: CliCandidate | null\n /** The global install on PATH, when there is one. */\n global?: CliCandidate | null\n detail: string\n}\n\n// ---------------------------------------------------------------------------\n// Panels on this machine\n// ---------------------------------------------------------------------------\n\n/**\n * `home-hosted` is not one panel per machine: a person can install it per\n * project, or run several panels from one install with different `--home`\n * roots. The plugin drives exactly one of them \u2014 the root it resolved.\n */\nexport type InstanceSource = 'managed' | 'env' | 'sibling' | 'configured'\n\n/** One home-hosted panel: its state root, and the facts readable from it. */\nexport interface InstanceView {\n /** The state root itself: what `--home` / `$HHOSTED_HOME` names. */\n home: string\n /**\n * The panel this plugin drives. Every tool call targets it unless the call\n * names another panel, and the plugin never writes to another one.\n */\n managed: boolean\n /** This dsh process runs as one of this panel's entries (`HHOSTED_SERVER_ID`). */\n hosting: boolean\n url: string | null\n port: number | null\n pid: number | null\n version: string | null\n /** A `run.json` whose pid is still alive. */\n running: boolean\n projectDir: string | null\n /** Entries in that panel's default workspace; `null` when it is unreadable. */\n servers: number | null\n /** How discovery found this root. */\n source: InstanceSource\n}\n\n// ---------------------------------------------------------------------------\n// Boot autostart\n// ---------------------------------------------------------------------------\n\nexport type BootMechanism =\n | 'systemd-user'\n | 'systemd-system'\n | 'xdg-autostart'\n | 'launchd-agent'\n | 'launchd-daemon'\n | 'windows-run'\n | 'windows-task'\n | 'container'\n | 'unsupported'\n\nexport type BootState =\n | 'not-installed'\n | 'installed-disabled'\n | 'enabled-running'\n | 'enabled-failing'\n | 'unsupported'\n\nexport interface BootCandidate {\n mechanism: BootMechanism\n available: boolean\n /** Starts before an interactive login. */\n bootCapable: boolean\n /** This process can drive it without an elevation prompt. */\n privileged: boolean\n reason: string\n}\n\nexport interface BootStatus {\n platform: 'linux' | 'darwin' | 'win32' | 'other'\n mechanism: BootMechanism | null\n recommended: BootMechanism | null\n state: BootState\n bootCapable: boolean\n privileged: boolean\n unitPath: string | null\n /** Exact commands a person can run when this process cannot elevate. */\n commands: string[]\n detail: string\n candidates: BootCandidate[]\n}\n\n// ---------------------------------------------------------------------------\n// Plugin settings (UI-managed, persisted outside the Cordis config)\n// ---------------------------------------------------------------------------\n\nexport type AgentToolName =\n | 'status'\n | 'workspaces_list'\n | 'servers_list'\n | 'servers_lifecycle'\n | 'servers_edit'\n | 'autostart_manage'\n | 'ui_manage'\n\nexport const AGENT_TOOL_NAMES: readonly AgentToolName[] = [\n 'status',\n 'workspaces_list',\n 'servers_list',\n 'servers_lifecycle',\n 'servers_edit',\n 'autostart_manage',\n 'ui_manage',\n]\n\n/**\n * Tools that change something. They ask for approval only when the calling\n * session is not already `danger-full-access`.\n */\nexport const MUTATING_AGENT_TOOLS: readonly AgentToolName[] = [\n 'servers_lifecycle',\n 'servers_edit',\n 'autostart_manage',\n 'ui_manage',\n]\n\n/** The shape this release writes; a file without it was written by 0.1.x. */\nexport const SETTINGS_VERSION = 3\n\nexport type UiStyle = 'detailed' | 'compact'\n\nexport interface PluginSettings {\n version: number\n autostart: {\n enabled: boolean\n /** `auto` picks the best available mechanism; an explicit one is honoured only when available. */\n mechanism: 'auto' | BootMechanism\n /** The last install/uninstall attempt, kept so a failure survives a reload. */\n lastAttempt?: BootAttempt\n }\n /** Manage the running harness as a home-hosted entry. */\n manageDsh: boolean\n entries: EntryIntent[]\n agentTools: {\n enabled: boolean\n allow: AgentToolName[]\n }\n /** Port the panel should listen on; `null` keeps whatever the config holds. */\n panel: {\n port: number | null\n }\n /** Point dsh web's sign-in page at the panel log that holds the tokenised URL. */\n authNotice: boolean\n /**\n * When the panel refuses the plugin's API token, re-enrol one on the spot\n * instead of failing the call. On by default: a token that went stale (someone\n * cleared it, or minted their own) should not keep an agent tool from working.\n */\n reclaimToken: boolean\n /**\n * Tell the agent how many home-hosted panels exist on this machine, and ask\n * the user which one to act on when there is more than one. On by default: a\n * plugin that owns one panel must not look like the only one there is.\n */\n instancesNotice: boolean\n /** `detailed` shows cards and open disclosures; `compact` folds both away. */\n uiStyle: UiStyle\n cli: {\n /** `pinned` runs the copy this plugin ships; `global` runs the one on PATH. */\n prefer: 'pinned' | 'global'\n }\n}\n\nexport interface BootAttempt {\n ok: boolean\n /** `install` or `uninstall`. */\n action: 'install' | 'uninstall'\n mechanism: BootMechanism | null\n detail: string\n /** The exact commands a person can run when the attempt needed privilege. */\n commands: string[]\n at: number\n}\n\nexport const DEFAULT_SETTINGS: PluginSettings = {\n version: SETTINGS_VERSION,\n autostart: { enabled: false, mechanism: 'auto' },\n manageDsh: false,\n entries: [],\n // Every tool on by default; the session's own permission mode is what gates them.\n agentTools: { enabled: true, allow: [...AGENT_TOOL_NAMES] },\n panel: { port: null },\n authNotice: true,\n reclaimToken: true,\n instancesNotice: true,\n uiStyle: 'detailed',\n cli: { prefer: 'pinned' },\n}\n\n/** One workspace a panel serves, as the page lists them. */\nexport interface WorkspaceSummary {\n id: string\n label: string\n servers: number\n running: number\n /** `api` counts come from the running panel; `file` counts from its config files. */\n source: 'api' | 'file'\n}\n\nexport interface HomeHostedStatus {\n /** The entry id this plugin manages; the page must not assume \"dsh\". */\n defaultEntryId: string\n /** The workspace this plugin manages. */\n workspace: string\n /** Every workspace the managed panel serves. */\n workspaces: WorkspaceSummary[]\n /**\n * The managed root still uses the pre-0.7 layout. Nothing can be written to it\n * until it is moved, so the page offers the one step that fixes it.\n */\n legacyRoot?: boolean\n panel: PanelStatus\n /** The panel root this plugin instance drives, so a second install is visible. */\n panelRoot?: string\n /** How that root was chosen; `legacy` means an existing `~/.home-hosted` panel was adopted. */\n panelRootSource?: 'env' | 'legacy' | 'instance'\n /** The OS boot artifact name this state root owns. */\n bootUnitName?: string\n /** Every home-hosted panel found on this machine, the managed one first. */\n instances?: InstanceView[]\n boot: BootStatus\n entries: ManagedEntryStatus[]\n servers: ServerEntryView[]\n settings: PluginSettings\n /** The CLI the plugin drives; absent only from a host older than this field. */\n cli?: CliStatus\n /** Set when the panel could not be reached, so the UI can show a degraded page. */\n lastError: string | null\n}\n", "/**\n * The one implementation behind both surfaces: the browser page and the agent\n * tools call the same endpoints, so they cannot drift apart.\n *\n * Writes prefer the panel API (the store ignores its own writes, so nothing is\n * restarted behind the user's back) and fall back to an atomic config-file write\n * only when the panel is not answering.\n */\nimport { Service, type Context } from '@deepseek-ai/cordis'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { fileURLToPath } from 'node:url'\nimport type { BootMechanism, BootStatus, EntryIntent, ForeignMechanism, HomeHostedStatus, InstanceView, ManagedEntryStatus, PanelControlResult, PanelStatus, RpcEndpoint, ServerEntry, ServerEntryPatch, ServerEntryView, UiAction, UiResult, WorkspaceSummary } from './shared/contracts.js'\nimport { FOREIGN_MECHANISMS, isOnPortConflict, isWorkspaceId, ON_PORT_CONFLICT_POLICIES } from './shared/contracts.js'\nimport type { BootActivation, BootSpec } from './boot/types.js'\nimport type { ActivationPlan } from './home-hosted/panel-control.js'\nimport { createBootLadder } from './boot/index.js'\nimport { accountOf } from './boot/index.js'\nimport type { AccountInput, PasswdEntry } from './boot/index.js'\nimport { findEntry, patchEntry, readConfig, readGlobalSettings, removeEntry as removeConfigEntry, setControl, upsertEntry, writeConfig } from './home-hosted/config-file.js'\nimport { DEFAULT_WORKSPACE, defaultWorkspace, isLegacyRoot, readWorkspaces, serversFile } from './home-hosted/layout.js'\nimport type { RawConfig } from './home-hosted/config-file.js'\nimport { buildDshEntry, detectProfile, launcherRepair, needsLauncherRepair, resolveDshLaunch } from './home-hosted/dsh-entry.js'\nimport type { DshLaunch } from './home-hosted/dsh-entry.js'\nimport { canonicalPath, discoverInstances } from './home-hosted/instances.js'\nimport { defaultIntent, isCreatedEntry, ownedDrift, ownedPatch, restorePatch, snapshotOwned } from './home-hosted/entries.js'\nimport { buildHomeHostedBootSpec, homeHostedEnv, LEGACY_BOOT_UNIT_NAME } from './home-hosted/launch.js'\nimport type { CliResolution } from './home-hosted/resolve.js'\nimport { EXPECTED_RANGE, MIN_SUPPORTED_VERSION, resolveCli } from './home-hosted/resolve.js'\nimport { preflightLauncher, writeLauncher } from './home-hosted/launcher.js'\nimport type { PanelControlDeps } from './home-hosted/panel-control.js'\nimport { activationLogPath, installGlobal, spawnActivation, spawnTakeover, startPanel as startPanelProcess, stopPanel as stopPanelProcess } from './home-hosted/panel-control.js'\nimport { PanelClient, PanelError, probeToken, verifyToken } from './home-hosted/panel.js'\nimport { readRuntime, probePanel, pidAlive } from './home-hosted/runtime.js'\nimport { apiTokenEnrolled, ensureToken, readStoredToken, reclaimToken, tokenSlot } from './home-hosted/token.js'\nimport type { SettingsStore } from './settings.js'\nimport { bootUnitName, dshHome } from './util/paths.js'\nimport type { RunResult } from './util/exec.js'\nimport { run } from './util/exec.js'\nimport { readJson, writeJsonAtomic } from './util/fsx.js'\n\nconst ENTRY_ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/\n\n/** A token probe is an auxiliary fact, not a user action: never let it stall a poll. */\nconst TOKEN_PROBE_TIMEOUT_MS = 3000\n\n/** How often a deleted managed entry may be put back from the status read. */\nconst ENTRY_RECOVERY_INTERVAL_MS = 30_000\n\n/** How long a panel inventory stands before the next read re-scans the disk. */\nconst INSTANCES_CACHE_MS = 10_000\n\n/** This plugin's own package root, used as a search hint for the pinned CLI. */\nfunction pluginRoot(): string | null {\n try {\n return path.dirname(path.dirname(fileURLToPath(import.meta.url)))\n }\n catch {\n return null\n }\n}\n\n/** What a boot install/uninstall answers with; mirrors the boot module's shape. */\nexport interface BootInstallResult {\n ok: boolean\n changed: boolean\n detail: string\n commands: string[]\n needsPrivilege: boolean\n /** Present on install; an uninstall names no mechanism. */\n mechanism?: BootMechanism | null\n status: BootStatus\n}\n\n/** The subset of the boot ladder this service uses. */\nexport interface BootLadderLike {\n status: (spec: BootSpec, mechanism?: BootMechanism) => Promise<BootStatus>\n install: (spec: BootSpec, mechanism?: BootMechanism) => Promise<BootInstallResult>\n uninstall: (spec: BootSpec, mechanism?: BootMechanism) => Promise<BootInstallResult>\n activate: (spec: BootSpec, mechanism?: BootMechanism) => Promise<BootActivation | null>\n}\n\nexport class HomeHostedError extends Error {\n constructor(message: string, readonly code: string) {\n super(message)\n this.name = 'HomeHostedError'\n }\n}\n\n/** An entry another panel's config holds: no live status without that panel's API. */\nfunction isRecordValue(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction foreignView(entry: ServerEntry, workspace: string): ServerEntryView {\n return { id: entry.id, workspace, status: 'unknown', pid: null, url: null, config: entry }\n}\n\nexport interface HomeHostedServiceOptions {\n home: string\n /** How that root was chosen: an explicit env var, an adopted legacy panel, or this instance's own. */\n panelHomeSource?: 'env' | 'legacy' | 'instance'\n stateDir: string\n homeHostedCommand?: string\n defaultEntryId: string\n /** Other home-hosted state roots to report as panels; discovery finds the rest. */\n instanceRoots?: readonly string[]\n /** Test seam: this process's `$HHOSTED_HOME`; defaults to the environment. */\n envHome?: string | null\n /** Test seam: where `~/.home-hosted*` siblings are looked for; defaults to the user's home. */\n homeDir?: string\n settings: SettingsStore\n /** Test seam: run the home-hosted CLI without spawning it. */\n execCli?: (args: string[], env: Record<string, string | undefined>) => Promise<RunResult>\n /** Test seam: supply the boot ladder instead of probing the real OS. */\n createLadder?: () => BootLadderLike\n /** Test seam: hand the panel to an autostart entry without spawning a helper. */\n spawnActivation?: (stateDir: string, plan: ActivationPlan) => PanelControlResult\n /** The project the managed `dsh` row belongs to; defaults to this process's cwd. */\n projectDir?: string\n /** Test seam: resolve the running harness instead of reading this process. */\n resolveDsh?: (options: { dshHome: string, stateDir: string, projectDir?: string | null }) => Promise<DshLaunch | null>\n /** Test seam: the environment account resolution reads; defaults to this process's. */\n env?: Readonly<Record<string, string | undefined>>\n /** Test seam: this process's uid; defaults to `process.getuid()`. */\n uid?: number | null\n /** Test seam: the user database to resolve the entry's account against. */\n passwd?: readonly PasswdEntry[]\n}\n\ninterface CachedClient {\n client: PanelClient\n until: number\n}\n\ninterface LiveEntry {\n view: ServerEntryView\n config: ServerEntry\n}\n\nexport class HomeHostedService extends Service {\n private clientCache: CachedClient | null = null\n /** The last token this service proved against a panel, so a poll does not re-probe it. */\n private tokenProof: { url: string, token: string, until: number } | null = null\n private tokenDetail = ''\n /** When a missing managed entry was last put back, so a poll cannot become a write loop. */\n private entryRecoveryAt = 0\n /** When a managed row was last re-pointed at the pinned dsh, for the same reason. */\n private commandRepairAt = 0\n /** The last panel inventory, so a model-step assembly never reads the disk. */\n private instanceCache: { at: number, list: InstanceView[] } | null = null\n private readonly snapshotsFile: string\n\n constructor(ctx: Context, private readonly options: HomeHostedServiceOptions) {\n super(ctx, 'homeHosted')\n this.snapshotsFile = path.join(options.stateDir, 'snapshots.json')\n }\n\n private projectDir(): string {\n return this.options.projectDir ?? process.cwd()\n }\n\n /**\n * The workspace this plugin manages. Fixed to the panel's own default: one\n * workspace is what its intent (reconcile, the harness entry, snapshots) is\n * about, and a setting for it bought nothing but a way to write into the wrong\n * one. Every other workspace is still reachable per call.\n */\n private managedWorkspace(): string {\n return DEFAULT_WORKSPACE\n }\n\n private async resolveDsh(dshHome: string, projectDir: string | null = this.projectDir()): Promise<DshLaunch | null> {\n return await (this.options.resolveDsh ?? (options => resolveDshLaunch(options)))({\n dshHome,\n stateDir: this.options.stateDir,\n projectDir,\n })\n }\n\n // -------------------------------------------------------------------------\n // Panels on this machine\n // -------------------------------------------------------------------------\n\n /**\n * The panel inventory, cached for a short while: the page polls status and\n * the prompt provider reads it at every assembly. `refresh` (or an expired\n * cache) re-reads the disk.\n */\n async instances(refresh = false): Promise<InstanceView[]> {\n if (refresh || this.instanceCache === null || Date.now() - this.instanceCache.at >= INSTANCES_CACHE_MS)\n return this.measureInstances()\n return this.instanceCache.list\n }\n\n /**\n * The inventory for a caller that cannot await \u2014 a prompt provider runs\n * synchronously before a model step. Discovery is synchronous too, so this\n * re-measures a stale cache in place and never blocks the step.\n */\n instancesNow(): InstanceView[] {\n if (this.instanceCache !== null && Date.now() - this.instanceCache.at < INSTANCES_CACHE_MS)\n return this.instanceCache.list\n return this.measureInstances()\n }\n\n private measureInstances(): InstanceView[] {\n const now = Date.now()\n try {\n this.instanceCache = {\n at: now,\n list: discoverInstances({\n managedHome: this.options.home,\n extraRoots: this.options.instanceRoots,\n envHome: this.options.envHome === undefined ? process.env.HHOSTED_HOME ?? null : this.options.envHome,\n homeDir: this.options.homeDir,\n hostingEntryId: this.selfEntryId(),\n }),\n }\n }\n catch {\n // An unreadable machine is an empty inventory, never a failed model step.\n this.instanceCache = { at: now, list: [] }\n }\n return this.instanceCache.list\n }\n\n /** The inventory as last measured, without touching the disk. */\n instancesSnapshot(): InstanceView[] {\n return this.instanceCache?.list ?? []\n }\n\n // -------------------------------------------------------------------------\n // Acting on another panel\n // -------------------------------------------------------------------------\n\n /**\n * The state root a call targets: the managed one unless it names another panel\n * discovery reported. A caller-supplied path is never acted on directly, so a\n * stray string cannot become a way to write anywhere on the disk.\n */\n private async targetHome(requested: unknown): Promise<{ home: string, foreign: boolean }> {\n if (requested === undefined || requested === null || requested === '')\n return { home: this.options.home, foreign: false }\n if (typeof requested !== 'string')\n throw new HomeHostedError('a panel target must be a state root path', 'INSTANCE_INVALID')\n if (requested.trim().length === 0)\n return { home: this.options.home, foreign: false }\n const home = canonicalPath(requested)\n if (home === canonicalPath(this.options.home))\n return { home: this.options.home, foreign: false }\n const instances = await this.instances()\n if (!instances.some(instance => instance.home === home && !instance.managed))\n throw new HomeHostedError(`\"${requested}\" is not a home-hosted panel this machine reports`, 'INSTANCE_UNKNOWN')\n return { home, foreign: true }\n }\n\n /**\n * How a call reaches another panel. `via` names it; without one the endpoint's\n * least invasive mechanism is used, which is the order the shared list keeps.\n */\n private mechanismFor(endpoint: RpcEndpoint, requested: unknown): ForeignMechanism {\n const allowed = FOREIGN_MECHANISMS[endpoint]\n if (allowed === undefined)\n throw new HomeHostedError(`\"${endpoint}\" cannot be aimed at another panel`, 'INSTANCE_UNSUPPORTED')\n if (requested === undefined || requested === null)\n return allowed[0] as ForeignMechanism\n if (typeof requested === 'string' && (allowed as readonly string[]).includes(requested))\n return requested as ForeignMechanism\n throw new HomeHostedError(\n `\"${String(requested)}\" is not a way to reach another panel for ${endpoint}; use one of ${allowed.join(', ')}`,\n 'MECHANISM_UNSUPPORTED',\n )\n }\n\n /** Whether a token this plugin already holds for a panel still works. */\n private async foreignTokenWorks(home: string): Promise<boolean> {\n const runtime = readRuntime(home)\n if (runtime === null || runtime.url.length === 0)\n return false\n const stored = readStoredToken(this.options.stateDir, tokenSlot(home))\n return stored !== null && await verifyToken(runtime.url, stored)\n }\n\n /**\n * A client for another panel: the plugin's own credential for that state root,\n * minted and enrolled the first time it is needed, proved against the panel,\n * and replaced when the panel no longer accepts it.\n *\n * home-hosted keeps only a hash, so an existing token this plugin does not hold\n * can only be replaced \u2014 which is what choosing this mechanism means.\n */\n private async foreignClient(home: string): Promise<PanelClient> {\n const runtime = readRuntime(home)\n if (runtime === null || runtime.url.length === 0)\n throw new HomeHostedError(`no panel is running at ${home}, so its API cannot be used`, 'PANEL_UNAVAILABLE')\n const slot = tokenSlot(home)\n const exec = async (args: string[], env: Record<string, string | undefined>) => await this.cliExec(args, env)\n const stored = readStoredToken(this.options.stateDir, slot)\n if (stored !== null && await verifyToken(runtime.url, stored))\n return new PanelClient({ baseUrl: runtime.url, token: stored, workspace: this.managedWorkspace() })\n\n const enrolled = stored !== null || apiTokenEnrolled(home)\n ? await reclaimToken({ home, stateDir: this.options.stateDir, exec, slot })\n : await ensureToken({ home, stateDir: this.options.stateDir, exec, slot })\n if (enrolled.token === null)\n throw new HomeHostedError(enrolled.detail, 'TOKEN_UNAVAILABLE')\n return new PanelClient({ baseUrl: runtime.url, token: enrolled.token, workspace: this.managedWorkspace() })\n }\n\n /**\n * Another panel's own entries. Its managed workspace is not knowable from here,\n * so its default one is read and written.\n */\n private readForeignConfig(home: string, workspace: string = defaultWorkspace(home)): RawConfig {\n const read = readConfig(home, workspace)\n if (read.error !== null || read.raw === null)\n throw new HomeHostedError(read.error ?? `no servers config in ${home}`, 'CONFIG_UNREADABLE')\n return read.raw\n }\n\n /**\n * Every workspace a panel serves, with counts. The running panel answers with\n * live numbers; otherwise the registry plus each workspace's config file is\n * enough to name them and count their entries.\n */\n private async workspaceSummaries(home: string, foreign: boolean): Promise<WorkspaceSummary[]> {\n const registry = readWorkspaces(home)\n const fromFile: WorkspaceSummary[] = registry.workspaces.map(entry => ({\n id: entry.id,\n label: entry.label,\n servers: (readConfig(home, entry.id).raw?.servers ?? []).length,\n running: 0,\n source: 'file',\n }))\n try {\n const client = foreign\n ? (await this.foreignTokenWorks(home) ? await this.foreignClient(home) : null)\n : await this.tryClient()\n if (client !== null) {\n const live = await client.listWorkspaces()\n return live.map(entry => ({ id: entry.id, label: entry.label, servers: entry.serverCount, running: entry.runningCount, source: 'api' as const }))\n }\n }\n catch {\n // The files remain the best available truth.\n }\n return fromFile\n }\n\n /** Another panel's entries as its config holds them: live status needs its API token. */\n private listForeign(home: string, workspace: string = defaultWorkspace(home)): ServerEntryView[] {\n return (this.readForeignConfig(home, workspace).servers ?? []).map(entry => foreignView(entry, workspace))\n }\n\n /**\n * A workspace's entries read from disk, for when no panel client is available.\n * The page shows them with `status: 'unknown'`, which is the truth: nothing is\n * supervising them from this process's point of view.\n */\n private fileServers(home: string, workspace: string): { servers: ServerEntryView[], error: string | null } {\n const read = readConfig(home, workspace)\n return {\n servers: (read.raw?.servers ?? []).map(entry => foreignView(entry, workspace)),\n error: read.error,\n }\n }\n\n private createForeign(home: string, entry: ServerEntry, workspace: string = defaultWorkspace(home)): ServerEntryView {\n const raw = this.readForeignConfig(home, workspace)\n if (findEntry(raw, entry.id) !== null)\n throw new HomeHostedError(`\"${entry.id}\" already exists in ${serversFile(home, workspace)}`, 'DUPLICATE_SERVER')\n // The same two-phase add the managed path uses: that panel's watcher starts a\n // newly added `autostart` entry at once, while a changed definition is only\n // applied at that entry's next start.\n writeConfig(home, upsertEntry(raw, { ...entry, autostart: false }), this.writtenBy(home), workspace)\n const added = this.readForeignConfig(home, workspace)\n writeConfig(home, patchEntry(added, entry.id, { autostart: entry.autostart === true }), this.writtenBy(home), workspace)\n return this.writtenForeignEntry(home, entry.id, workspace)\n }\n\n private updateForeign(home: string, id: string, patch: ServerEntryPatch, workspace: string = defaultWorkspace(home)): ServerEntryView {\n const raw = this.readForeignConfig(home, workspace)\n if (findEntry(raw, id) === null)\n throw new HomeHostedError(`no server \"${id}\" exists in ${serversFile(home, workspace)}`, 'UNKNOWN_SERVER')\n // A patch never renames: the id is the key it was found by.\n const next = patchEntry(raw, id, { ...patch, id })\n writeConfig(home, next, this.writtenBy(home), workspace)\n return this.writtenForeignEntry(home, id, workspace)\n }\n\n /** What a write actually left on disk, never what it was asked to write. */\n private writtenForeignEntry(home: string, id: string, workspace: string = defaultWorkspace(home)): ServerEntryView {\n const entry = findEntry(this.readForeignConfig(home, workspace), id)\n if (entry === null)\n throw new HomeHostedError(`the write to ${serversFile(home, workspace)} did not take`, 'FOREIGN_WRITE_LOST')\n return foreignView(entry, workspace)\n }\n\n private deleteForeign(home: string, id: string, workspace: string = defaultWorkspace(home)): { id: string, home: string, via: 'file' } {\n const raw = this.readForeignConfig(home, workspace)\n if (findEntry(raw, id) === null)\n throw new HomeHostedError(`no server \"${id}\" exists in ${serversFile(home, workspace)}`, 'UNKNOWN_SERVER')\n writeConfig(home, removeConfigEntry(raw, id), this.writtenBy(home), workspace)\n return { id, home, via: 'file' }\n }\n\n /**\n * Start, stop or restart one entry through that panel's own CLI: this plugin\n * holds no API token for a panel it does not manage, and a config edit cannot\n * start anything. The CLI has no server restart, so a restart is a stop\n * followed by a start \u2014 a stop that is not followed by one is reported.\n */\n private async lifecycleForeign(home: string, action: 'start' | 'stop' | 'restart', id: string, workspace: string = defaultWorkspace(home)): Promise<{ home: string, workspace: string, id: string, action: string, via: 'cli' }> {\n const once = async (command: string): Promise<void> => {\n const result = await this.cliExec([command, id, '--workspace', workspace, '--home', home], { ...process.env, HHOSTED_HOME: home })\n if (result.code !== 0) {\n const output = [result.stderr, result.stdout, result.error].filter(part => typeof part === 'string' && part.trim().length > 0).join('\\n').trim()\n throw new HomeHostedError(output.length > 0 ? output : `home-hosted ${command} ${id} exited ${String(result.code)}`, 'CLI_FAILED')\n }\n }\n\n if (action === 'restart') {\n await once('stop')\n await once('start')\n }\n else {\n await once(action)\n }\n return { home, workspace, id, action, via: 'cli' }\n }\n\n // -------------------------------------------------------------------------\n // Facts\n // -------------------------------------------------------------------------\n\n private runtime() {\n return readRuntime(this.options.home)\n }\n\n /** The entry id this very process was started as, when the panel supervises us. */\n selfEntryId(): string | null {\n const id = process.env.HHOSTED_SERVER_ID\n return id !== undefined && id.length > 0 ? id : null\n }\n\n /** What the config's `meta.writtenBy` names: the panel release when we can read it. */\n private writtenBy(home = this.options.home): string {\n return readRuntime(home)?.version ?? 'dsh-home-hosted'\n }\n\n private webServer(): { port: number | null } {\n const service = this.ctx.get('webServer') as { port?: unknown } | undefined\n const port = typeof service?.port === 'number' ? service.port : null\n return { port }\n }\n\n private cliCache: { prefer: 'pinned' | 'global', resolution: CliResolution, launcher: string | null, launcherVersion: string | null, until: number } | null = null\n\n /**\n * Resolve the preferred CLI, refresh the stable launcher a boot entry runs, and\n * preflight that launcher the same way the entry will invoke it.\n */\n private async cli(): Promise<{ resolution: CliResolution, launcher: string | null, launcherVersion: string | null }> {\n const prefer = this.options.settings.get().cli.prefer\n if (this.cliCache !== null && this.cliCache.until > Date.now() && this.cliCache.prefer === prefer)\n return this.cliCache\n const resolution = await resolveCli({ override: this.options.homeHostedCommand, prefer })\n let launcher: string | null = null\n let launcherVersion: string | null = null\n if (resolution.launch !== null) {\n launcher = writeLauncher({\n stateDir: this.options.stateDir,\n dshHome: dshHome(),\n resolvedEntry: resolution.launch.cliEntry ?? resolution.launch.shimPath ?? null,\n resolvedVersion: resolution.status.version,\n pluginRoot: pluginRoot(),\n minVersion: MIN_SUPPORTED_VERSION,\n }).path\n launcherVersion = await preflightLauncher(this.options.stateDir)\n }\n this.cliCache = { prefer, resolution, launcher, launcherVersion, until: Date.now() + 60_000 }\n return this.cliCache\n }\n\n private async panelControlDeps(): Promise<PanelControlDeps> {\n const { resolution } = await this.cli()\n const launch = resolution.launch\n const projectDir = process.env.HHOSTED_PROJECT ?? this.runtime()?.projectDir ?? null\n return {\n launch,\n home: this.options.home,\n projectDir,\n stateDir: this.options.stateDir,\n env: launch === null ? {} : homeHostedEnv({ home: this.options.home, projectDir }, launch),\n exec: this.options.execCli,\n }\n }\n\n private async cliExec(args: string[], env: Record<string, string | undefined>) {\n if (this.options.execCli !== undefined)\n return await this.options.execCli(args, env)\n const { resolution } = await this.cli()\n const launch = resolution.launch\n if (launch === null)\n throw new HomeHostedError(resolution.status.detail, 'CLI_NOT_FOUND')\n return await run(launch.program, [...launch.args, ...args], { env, timeoutMs: 30_000 })\n }\n\n // -------------------------------------------------------------------------\n // Panel\n // -------------------------------------------------------------------------\n\n /** The panel page showing this session's log, where its tokenised URL is. */\n panelLogUrl(): string | null {\n const url = this.runtime()?.url\n if (url === undefined || url.length === 0)\n return null\n // The workspace is part of the URL; the legacy `/logs` path would land on the\n // panel's default workspace, which is not necessarily the managed one.\n return `${url.replace(/\\/+$/, '')}/w/${encodeURIComponent(this.managedWorkspace())}/logs?server=${encodeURIComponent(this.options.defaultEntryId)}`\n }\n\n /**\n * Relocate a pre-0.7 root into `.hh`. `up` does this on its own, but a person\n * looking at a root this plugin cannot write needs the one step that fixes it;\n * `migrate` is the CLI that does exactly that, non-interactively with `--yes`.\n */\n async migrateRoot(): Promise<PanelControlResult> {\n if (!isLegacyRoot(this.options.home))\n return { ok: true, detail: `${this.options.home} is already on the .hh layout` }\n const result = await this.cliExec(['migrate', '--yes', '--home', this.options.home], { ...process.env, HHOSTED_HOME: this.options.home })\n if (result.code !== 0) {\n const output = [result.stderr, result.stdout, result.error].filter(part => typeof part === 'string' && part.trim().length > 0).join('\\n').trim()\n return { ok: false, detail: output.length > 0 ? output : `home-hosted migrate exited ${String(result.code)}` }\n }\n return { ok: true, detail: `${this.options.home} was moved to the .hh layout` }\n }\n\n /** The port the panel's settings hold, without touching a running panel. */\n private configuredPort(): number | null {\n const control = readGlobalSettings(this.options.home).raw?.control as { port?: unknown } | undefined\n return typeof control?.port === 'number' ? control.port : null\n }\n\n /** Write the chosen panel port into the state the panel boots from. */\n private applyPanelPort(): void {\n const port = this.options.settings.get().panel.port\n if (port === null || port === this.configuredPort())\n return\n setControl(this.options.home, { port }, this.writtenBy())\n }\n\n /** Whether this exact token was already proved against this exact panel. */\n private tokenProven(url: string, token: string): boolean {\n return this.tokenProof !== null && this.tokenProof.until > Date.now()\n && this.tokenProof.url === url && this.tokenProof.token === token\n }\n\n private proveToken(url: string, token: string): void {\n this.tokenProof = { url, token, until: Date.now() + 30_000 }\n }\n\n async panelStatus(): Promise<PanelStatus> {\n const runtime = this.runtime()\n const stored = readStoredToken(this.options.stateDir)\n const enrolledOnDisk = apiTokenEnrolled(this.options.home)\n const reachable = runtime !== null && (pidAlive(runtime.pid) || await probePanel(runtime.url))\n const answered = runtime !== null && await probePanel(runtime.url)\n\n // The token state is measured, not assumed: when the panel answers, the\n // plugin's own token is proved against it. A refusal is `stale`; a panel\n // that would not answer the API call leaves the state `unknown` rather than\n // claiming a measurement that was never taken.\n let token: PanelStatus['token'] = 'unknown'\n let tokenVerified = false\n if (answered) {\n if (stored === null) {\n token = enrolledOnDisk ? 'present' : 'absent'\n }\n else if (this.tokenProven(runtime.url, stored)) {\n token = 'enrolled'\n tokenVerified = true\n }\n else {\n const probe = await probeToken(runtime.url, stored, TOKEN_PROBE_TIMEOUT_MS)\n if (probe === 'ok') {\n token = 'enrolled'\n tokenVerified = true\n this.proveToken(runtime.url, stored)\n }\n else if (probe === 'refused') {\n token = 'stale'\n tokenVerified = true\n }\n }\n }\n\n // A token the panel refuses is not a write path, however present it is on disk.\n const writeVia: PanelStatus['writeVia'] = token === 'enrolled' ? 'api' : 'file'\n\n return {\n home: this.options.home,\n reachable: answered,\n configPort: this.configuredPort(),\n url: runtime?.url ?? null,\n version: runtime?.version ?? null,\n pid: runtime?.pid ?? null,\n writeVia,\n token,\n tokenVerified,\n detail: answered\n ? (token === 'enrolled'\n ? (this.tokenDetail || 'the panel is answering and this plugin holds a token')\n : token === 'stale'\n ? 'the panel refused this plugin\\'s API token, so writes go straight to the workspace config file; regenerate the token'\n : token === 'present'\n ? 'the panel is answering, but home-hosted already holds an API token this plugin does not have'\n : token === 'absent'\n ? 'the panel is answering but no API token is enrolled for it yet; writes go to the workspace config file until one is'\n : 'the panel answered, but the plugin\\'s token could not be checked')\n : (runtime === null\n ? 'no .hh/run.json: the panel is not running, so entries are written straight to the workspace config file'\n : token === 'present' || token === 'stale'\n ? 'the panel process is up but does not answer with this plugin\\'s API token, so this page cannot reach it'\n : 'the panel process is alive but is not answering'),\n }\n }\n\n /**\n * Replace the panel's API token with a fresh one and prove it.\n *\n * home-hosted keeps only a hash, so a token this plugin does not hold cannot\n * be recovered: a new token replaces the old hash in one CLI write, and it is\n * proved against the answering panel before being reported.\n *\n * The panel is not required to be *answering*: a missing or refused token is a\n * common reason it cannot be reached at all, and refusing the repair for that\n * reason would leave no way out from the page. Only a panel that was never\n * started is refused \u2014 there is nothing to enrol a token against yet.\n */\n async reclaimPanelToken(): Promise<HomeHostedStatus> {\n const runtime = this.runtime()\n if (runtime === null) {\n throw new HomeHostedError(\n 'the panel has not been started, so there is nothing to enrol a token against; start it first',\n 'PANEL_UNAVAILABLE',\n )\n }\n const answering = await probePanel(runtime.url)\n\n const result = await reclaimToken({\n home: this.options.home,\n stateDir: this.options.stateDir,\n exec: (args, env) => this.cliExec(args, env),\n })\n // Whatever happened, nothing cached was built from the token in play now.\n this.clientCache = null\n this.tokenProof = null\n this.tokenDetail = result.detail\n if (result.token === null)\n throw new HomeHostedError(result.detail, 'TOKEN_RECLAIM_FAILED')\n\n if (!answering) {\n // Enrolled, but there is nothing to hand it to yet. The page says so\n // instead of claiming a verification it could not take.\n this.tokenDetail = `${result.detail}; the panel is not answering, so it could not be proved yet`\n return await this.status()\n }\n\n const probe = await probeToken(runtime.url, result.token, TOKEN_PROBE_TIMEOUT_MS)\n if (probe !== 'ok') {\n throw new HomeHostedError(\n probe === 'refused'\n ? 'a fresh token was enrolled, but the panel refused it; check the panel log before trying again'\n : 'a fresh token was enrolled, but the panel stopped answering before it could be verified',\n 'TOKEN_RECLAIM_UNVERIFIED',\n )\n }\n this.proveToken(runtime.url, result.token)\n return await this.status()\n }\n\n private async tryClient(): Promise<PanelClient | null> {\n if (this.clientCache !== null && this.clientCache.until > Date.now())\n return this.clientCache.client\n const runtime = this.runtime()\n if (runtime === null || !(await probePanel(runtime.url)))\n return null\n\n const ensured = await ensureToken({\n home: this.options.home,\n stateDir: this.options.stateDir,\n exec: (args, env) => this.cliExec(args, env),\n })\n this.tokenDetail = ensured.detail\n if (ensured.token === null)\n return null\n if (!(await verifyToken(runtime.url, ensured.token)))\n return null\n\n const client = new PanelClient({ baseUrl: runtime.url, token: ensured.token, workspace: this.managedWorkspace() })\n this.clientCache = { client, until: Date.now() + 30_000 }\n this.proveToken(runtime.url, ensured.token)\n return client\n }\n\n private async requireClient(): Promise<PanelClient> {\n const client = await this.tryClient()\n if (client === null) {\n const status = await this.panelStatus()\n throw new HomeHostedError(status.detail, 'PANEL_UNAVAILABLE')\n }\n return client\n }\n\n // -------------------------------------------------------------------------\n // Snapshot bookkeeping\n // -------------------------------------------------------------------------\n\n /**\n * What an adopted entry looked like before this plugin touched it, keyed by id\n * inside the workspace those snapshots belong to. A file from another workspace\n * is ignored: ids repeat across workspaces, and restoring the wrong one would\n * rewrite an entry this plugin never adopted.\n */\n private snapshots(): Record<string, ServerEntry> {\n const raw = readJson<Record<string, unknown>>(this.snapshotsFile)\n if (raw === null)\n return {}\n if (raw.version === 2 && isRecordValue(raw.entries))\n return raw.workspace === this.managedWorkspace() ? raw.entries as Record<string, ServerEntry> : {}\n // A file from before workspaces: there was one workspace, and it was `default`.\n return this.managedWorkspace() === DEFAULT_WORKSPACE ? raw as Record<string, ServerEntry> : {}\n }\n\n private saveSnapshots(snapshots: Record<string, ServerEntry>): void {\n writeJsonAtomic(this.snapshotsFile, { version: 2, workspace: this.managedWorkspace(), entries: snapshots }, 0o600)\n }\n\n // -------------------------------------------------------------------------\n // Entries\n // -------------------------------------------------------------------------\n\n private async liveEntries(): Promise<Map<string, LiveEntry>> {\n const map = new Map<string, LiveEntry>()\n const client = await this.tryClient()\n if (client !== null) {\n try {\n for (const view of await client.listServers())\n map.set(view.id, { view, config: view.config })\n return map\n }\n catch {\n // fall through to the file, which is still the best available truth\n }\n }\n const workspace = this.managedWorkspace()\n const read = readConfig(this.options.home, workspace)\n for (const entry of read.raw?.servers ?? [])\n map.set(entry.id, { view: foreignView(entry, workspace), config: entry })\n return map\n }\n\n private async createEntry(intent: EntryIntent, patch: ServerEntryPatch): Promise<ServerEntry | null> {\n if (intent.id !== this.options.defaultEntryId)\n return null\n const harnessHome = process.env.DSH_HOME ?? dshHome()\n // State dir and harness home are the plugin's own, not the defaults: an\n // override has to point the launcher at the same place everything else is.\n const dsh = await this.resolveDsh(harnessHome)\n const { port } = this.webServer()\n const generated = buildDshEntry({\n id: intent.id,\n port,\n profile: detectProfile(process.argv, process.env),\n dshHome: harnessHome,\n launch: dsh,\n launcherPath: dsh?.launcherPath ?? null,\n projectDir: this.projectDir(),\n })\n return { ...generated, ...patch }\n }\n\n /**\n * An entry written before the launcher existed still runs a clone's build\n * output directly. Point it at the stable launcher, or the fix would only\n * ever apply to freshly created entries \u2014 the plugin never rewrites an\n * existing entry's command.\n */\n private async dshCommandRepair(live: ServerEntry | null): Promise<{ command: string, args: string[] } | null> {\n if (live === null || !needsLauncherRepair(live))\n return null\n const harnessHome = process.env.DSH_HOME ?? dshHome()\n const dsh = await this.resolveDsh(harnessHome)\n // A row this plugin created carries `{ id }` as its snapshot; one it merely\n // adopted keeps the person's own command unless that command is a bare name.\n const owned = isCreatedEntry(this.snapshots()[live.id])\n return launcherRepair(live, dsh, { ownedCommand: owned, projectDir: this.projectDir() })\n }\n\n private async writeOwned(intent: EntryIntent): Promise<void> {\n const live = (await this.liveEntries()).get(intent.id) ?? null\n const snapshots = this.snapshots()\n if (snapshots[intent.id] === undefined) {\n snapshots[intent.id] = live === null ? { id: intent.id } : snapshotOwned(live.config)\n this.saveSnapshots(snapshots)\n }\n\n const patch = ownedPatch(intent, live?.config ?? null)\n const repair = intent.id === this.options.defaultEntryId ? await this.dshCommandRepair(live?.config ?? null) : null\n const client = await this.tryClient()\n if (client !== null) {\n if (live !== null) {\n await client.updateServer(intent.id, { ...patch, ...(repair ?? {}) })\n return\n }\n const entry = await this.createEntry(intent, patch)\n if (entry === null) {\n throw new HomeHostedError(\n `no server \"${intent.id}\" exists; create it first, then this plugin can adopt its autostart and conflict policy`,\n 'ENTRY_MISSING',\n )\n }\n await client.createServer(entry)\n return\n }\n\n const workspace = this.managedWorkspace()\n const read = readConfig(this.options.home, workspace)\n if (read.error !== null)\n throw new HomeHostedError(read.error, 'CONFIG_UNREADABLE')\n let raw = read.raw ?? {}\n if (findEntry(raw, intent.id) !== null) {\n writeConfig(this.options.home, patchEntry(raw, intent.id, { ...patch, ...(repair ?? {}) }), this.writtenBy(), workspace)\n return\n }\n\n const entry = await this.createEntry(intent, patch)\n if (entry === null)\n throw new HomeHostedError(`no server \"${intent.id}\" exists in ${serversFile(this.options.home, workspace)}`, 'ENTRY_MISSING')\n\n // While a panel is running, an external write is a real change and a newly\n // added autostart entry would be started at once. Adding it disabled and\n // flipping autostart in a second write makes that a changed definition\n // instead, which only takes effect at that entry's next start.\n if (await this.panelRunning()) {\n writeConfig(this.options.home, upsertEntry(raw, { ...entry, autostart: false }), this.writtenBy(), workspace)\n raw = readConfig(this.options.home, workspace).raw ?? raw\n writeConfig(this.options.home, patchEntry(raw, intent.id, { autostart: intent.autostart }), this.writtenBy(), workspace)\n return\n }\n\n writeConfig(this.options.home, upsertEntry(raw, entry), this.writtenBy(), workspace)\n }\n\n private async panelRunning(): Promise<boolean> {\n const runtime = this.runtime()\n return runtime !== null && (pidAlive(runtime.pid) || await probePanel(runtime.url))\n }\n\n private async applyIntents(intents: EntryIntent[]): Promise<ManagedEntryStatus[]> {\n for (const raw of intents) {\n if (!ENTRY_ID_PATTERN.test(raw.id))\n throw new HomeHostedError(`\"${raw.id}\" is not a valid server id`, 'INVALID_ID')\n const requested: unknown = raw.onPortConflict\n if (requested !== undefined && requested !== null && !isOnPortConflict(requested))\n throw new HomeHostedError(\n `\"${String(requested)}\" is not a port-conflict policy; use one of ${ON_PORT_CONFLICT_POLICIES.join(', ')}`,\n 'INVALID_POLICY',\n )\n const fallback = defaultIntent(raw.id)\n const intent: EntryIntent = {\n id: raw.id,\n autostart: raw.autostart === true,\n onPortConflict: isOnPortConflict(requested) ? requested : fallback.onPortConflict,\n stopKillPortHolders: raw.stopKillPortHolders ?? fallback.stopKillPortHolders,\n persistent: raw.persistent ?? fallback.persistent,\n }\n await this.writeOwned(intent)\n const current = this.options.settings.get()\n const entries = current.entries.filter(entry => entry.id !== intent.id)\n entries.push(intent)\n this.options.settings.update({\n // The page's toggle reads this flag, so managing the harness has to record\n // itself; restoring or removing that entry clears it again. A paused intent\n // (`autostart: false`) is not management, or the toggle could never go off.\n ...(intent.id === this.options.defaultEntryId ? { manageDsh: intent.autostart } : {}),\n entries,\n })\n }\n return await this.entriesStatus()\n }\n\n /**\n * Stop managing an entry: restore what it was when we only adopted it, or\n * remove it when this plugin created it. Removing an entry the panel\n * supervises stops that process \u2014 which may be this session.\n */\n private async removeManagedEntry(id: string): Promise<ManagedEntryStatus[]> {\n const snapshots = this.snapshots()\n const snapshot = snapshots[id]\n const adopted = snapshot !== undefined && Object.keys(snapshot).some(key => key !== 'id')\n if (adopted)\n return await this.restoreEntry(id)\n\n // Deleting this entry stops the process running this session, and nothing\n // would start it again \u2014 so \"stop managing\" pauses it instead: the entry\n // stays, it is simply no longer started for us.\n if (id === this.selfEntryId())\n return await this.pauseManagedEntry(id)\n\n const client = await this.tryClient()\n if (client !== null) {\n try {\n await client.deleteServer(id)\n }\n catch (error) {\n // Already gone is the outcome we wanted.\n const missing = error instanceof PanelError && (error.status === 404 || error.code === 'UNKNOWN_SERVER')\n if (!missing)\n throw error\n }\n }\n else {\n const read = readConfig(this.options.home, this.managedWorkspace())\n if (read.error !== null || read.raw === null)\n throw new HomeHostedError(read.error ?? 'the config file is unreadable', 'CONFIG_UNREADABLE')\n writeConfig(this.options.home, removeConfigEntry(read.raw, id), this.writtenBy(), this.managedWorkspace())\n }\n delete snapshots[id]\n this.saveSnapshots(snapshots)\n const current = this.options.settings.get()\n this.options.settings.update({\n // Only the harness entry drives the page's manage toggle.\n ...(id === this.options.defaultEntryId ? { manageDsh: false } : {}),\n entries: current.entries.filter(entry => entry.id !== id),\n })\n return await this.entriesStatus()\n }\n\n /** Turn off the autostart intent for one entry, keeping the entry itself. */\n private async pauseManagedEntry(id: string): Promise<ManagedEntryStatus[]> {\n const intent = this.options.settings.intentFor(id)\n await this.applyIntents([{ ...intent, autostart: false }])\n const current = this.options.settings.get()\n this.options.settings.update({\n ...(id === this.options.defaultEntryId ? { manageDsh: false } : {}),\n entries: current.entries,\n })\n return await this.entriesStatus()\n }\n\n /**\n * Drive the panel's own UI through its CLI, and report what is installed.\n *\n * `ui-switch --asset` is preferred over a local zip for an official UI: the\n * panel's own CLI picks the release for its version, matches the asset name\n * and downloads it, so nothing here has to reimplement a GitHub lookup or\n * guess which release belongs to the running panel.\n */\n async uiManage(action: UiAction, options: { file?: string, asset?: string, repo?: string, tag?: string } = {}, target?: string): Promise<UiResult> {\n const home = target ?? this.options.home\n const cli = action === 'status' ? null : await this.cli()\n\n const runUi = async (args: string[]): Promise<{ code: number | null, output: string }> => {\n const launch = cli?.resolution.launch ?? null\n if (launch === null)\n throw new HomeHostedError(cli?.resolution.status.detail ?? 'no home-hosted CLI could be resolved', 'CLI_NOT_FOUND')\n const full = [...launch.args, ...args, '--home', home]\n const env = { ...process.env, HHOSTED_HOME: home }\n const result = this.options.execCli !== undefined\n ? await this.options.execCli(full, env)\n : await run(launch.program, full, { env, timeoutMs: 300_000 })\n // A CLI that never started has no stdout to show, so its spawn error is the\n // only thing that explains the failure.\n const output = [result.error, result.stdout, result.stderr].filter(part => typeof part === 'string' && part.trim().length > 0).join('\\n').trim()\n return { code: result.code, output }\n }\n\n interface ActiveUi { name: string | null, version: string | null, repo: string | null, tag: string | null }\n const active = (): ActiveUi | null => {\n const meta = readJson<{ name?: string, version?: string | null, repo?: string, tag?: string }>(path.join(home, '.ui', 'ui.json'))\n return meta === null ? null : { name: meta.name ?? null, version: meta.version ?? null, repo: meta.repo ?? null, tag: meta.tag ?? null }\n }\n\n const releaseFlags = (): string[] => [\n ...(options.repo === undefined ? [] : ['--repo', options.repo]),\n ...(options.tag === undefined ? [] : ['--tag', options.tag]),\n ]\n\n switch (action) {\n case 'status': {\n const ui = active()\n return { ok: true, ui, detail: ui === null ? 'the panel is using its stock UI' : `${ui.name ?? 'a custom UI'}${ui.version === null ? '' : ` ${ui.version}`}` }\n }\n case 'update': {\n const result = await runUi(['ui-update', '--yes', ...releaseFlags()])\n return { ok: result.code === 0, detail: result.code === 0 ? 'the panel UI is up to date' : 'the update did not run', ui: active(), output: result.output }\n }\n case 'revert': {\n const result = await runUi(['ui-revert'])\n return { ok: result.code === 0, detail: result.code === 0 ? 'back to the stock UI' : 'the revert did not run', ui: active(), output: result.output }\n }\n case 'switch': {\n const file = options.file?.trim()\n const asset = options.asset?.trim()\n if (file === undefined || file.length === 0) {\n if (asset === undefined || asset.length === 0)\n throw new HomeHostedError('switching the UI needs an asset name or a local zip', 'UI_SOURCE_REQUIRED')\n const result = await runUi(['ui-switch', '--asset', asset, '--yes', ...releaseFlags()])\n return { ok: result.code === 0, detail: result.code === 0 ? `installed the ${asset} UI` : 'the switch did not run', ui: active(), output: result.output }\n }\n if (!fs.existsSync(file))\n throw new HomeHostedError(`no file at ${file}`, 'UI_FILE_MISSING')\n const result = await runUi(['ui-switch', '--file', file, '--yes'])\n return { ok: result.code === 0, detail: result.code === 0 ? 'the UI was replaced' : 'the switch did not run', ui: active(), output: result.output }\n }\n default:\n throw new HomeHostedError(`unknown ui action \"${String(action)}\"`, 'UNKNOWN_UI_ACTION')\n }\n }\n\n private async restoreEntry(id: string): Promise<ManagedEntryStatus[]> {\n const live = (await this.liveEntries()).get(id) ?? null\n if (live === null)\n throw new HomeHostedError(`no server \"${id}\" exists`, 'ENTRY_MISSING')\n const snapshots = this.snapshots()\n // A restore without a snapshot would write schema defaults over the person's\n // own autostart and conflict policy, silently and irreversibly.\n if (snapshots[id] === undefined)\n throw new HomeHostedError(`\"${id}\" was never adopted by this plugin, so there is nothing to restore; adopt it first`, 'NOT_ADOPTED')\n const patch = restorePatch(live.config, snapshots[id] ?? null)\n const client = await this.tryClient()\n if (client !== null)\n await client.updateServer(id, patch)\n else {\n const read = readConfig(this.options.home, this.managedWorkspace())\n if (read.error !== null || read.raw === null)\n throw new HomeHostedError(read.error ?? 'the config file is unreadable', 'CONFIG_UNREADABLE')\n writeConfig(this.options.home, patchEntry(read.raw, id, patch), this.writtenBy(), this.managedWorkspace())\n }\n delete snapshots[id]\n this.saveSnapshots(snapshots)\n const current = this.options.settings.get()\n this.options.settings.update({\n ...(id === this.options.defaultEntryId ? { manageDsh: false } : {}),\n entries: current.entries.filter(entry => entry.id !== id),\n })\n return await this.entriesStatus()\n }\n\n async entriesStatus(): Promise<ManagedEntryStatus[]> {\n const settings = this.options.settings.get()\n const live = await this.liveEntries()\n const snapshots = this.snapshots()\n const ids = new Set<string>([this.options.defaultEntryId, ...settings.entries.map(entry => entry.id)])\n return [...ids].map((id) => {\n const intent = this.options.settings.intentFor(id)\n const entry = live.get(id) ?? null\n return {\n intent,\n exists: entry !== null,\n managed: snapshots[id] !== undefined,\n drift: entry === null ? ['missing entry'] : ownedDrift(entry.config, intent),\n live: entry?.view ?? null,\n snapshot: snapshots[id] ?? null,\n }\n })\n }\n\n // -------------------------------------------------------------------------\n // Boot autostart\n // -------------------------------------------------------------------------\n\n /**\n * Everything account resolution reads.\n *\n * The state paths matter: they name the person the panel belongs to, so a root\n * process launched at boot (a system unit that names no `User=`) can still be\n * told whose panel it is running.\n */\n /** This process's environment, from the test seam when one is supplied. */\n private processEnv(): Readonly<Record<string, string | undefined>> {\n return this.options.env ?? process.env\n }\n\n private accountFacts(): Omit<AccountInput, 'warn'> {\n return {\n env: this.processEnv() as Record<string, string | undefined>,\n uid: this.options.uid,\n ownerPaths: [this.options.home, this.options.stateDir],\n passwd: this.options.passwd,\n }\n }\n\n /**\n * The ladder resolves the account per provider, and the spec below resolves it\n * for the entry's environment. Both are handed the *same* facts, so the `User=`\n * line and the `HOME=` line can never describe two different people.\n */\n private ladder(): BootLadderLike {\n if (this.options.createLadder !== undefined)\n return this.options.createLadder()\n return createBootLadder(this.accountFacts())\n }\n\n private async bootSpec(): Promise<BootSpec | null> {\n const { resolution, launcher } = await this.cli()\n const launch = resolution.launch\n if (launch === null)\n return null\n const runtime = this.runtime()\n // The account a system entry hands the panel to, resolved from the login that\n // elevated (or this process's own), never from `$USER`: under `sudo` the\n // environment says *root*, which is how a system unit came to run the panel as\n // root. A root answer travels as `root: true` so providers refuse to write it.\n const account = accountOf(this.accountFacts())\n const spec = buildHomeHostedBootSpec(\n {\n home: this.options.home,\n projectDir: process.env.HHOSTED_PROJECT ?? runtime?.projectDir ?? null,\n version: runtime?.version ?? null,\n },\n launch,\n {\n stateDir: this.options.stateDir,\n unitName: bootUnitName(this.options.stateDir),\n // `undefined` when no account was resolved: the entry then runs as this\n // process (or as root), and this process's own HOME is the right one. A\n // spec that merely repeats this environment stays byte-identical, so\n // reconcile does not rewrite the entry on every start.\n accountHome: account === null ? undefined : account.home,\n env: this.processEnv(),\n },\n )\n\n // The entry runs the stable launcher, not the pinned node_modules path: that\n // path carries a version and a peer hash, and moves on the next install.\n if (launcher !== null) {\n const cliArgs = spec.args.slice(launch.args.length)\n if (process.platform === 'win32') {\n spec.command = process.execPath\n spec.args = [launcher, ...cliArgs]\n }\n else {\n spec.command = launcher\n spec.args = cliArgs\n }\n }\n return spec\n }\n\n async bootStatus(mechanism?: BootMechanism): Promise<BootStatus> {\n const platform: BootStatus['platform'] = process.platform === 'linux'\n ? 'linux'\n : process.platform === 'darwin' ? 'darwin' : process.platform === 'win32' ? 'win32' : 'other'\n const spec = await this.bootSpec()\n if (spec === null) {\n return {\n platform,\n mechanism: null,\n recommended: null,\n state: 'unsupported',\n bootCapable: false,\n privileged: false,\n unitPath: null,\n commands: [],\n detail: 'no home-hosted CLI is available, so no boot entry can be generated; install the plugin with its dependencies',\n candidates: [],\n }\n }\n const settings = this.options.settings.get()\n const requested = mechanism ?? (settings.autostart.mechanism === 'auto' ? undefined : settings.autostart.mechanism)\n return await this.ladder().status(spec, requested)\n }\n\n async installBoot(mechanism?: BootMechanism): Promise<{ result: BootInstallResult, status: BootStatus }> {\n const spec = await this.bootSpec()\n if (spec === null)\n throw new HomeHostedError((await this.cli()).resolution.status.detail, 'CLI_NOT_FOUND')\n\n // Read what the panel is doing *before* the install: this decides whether the\n // entry has to be started, and whether that start must stop anything first.\n const panel = await this.panelStatus()\n const before = await this.bootStatus(mechanism)\n const result = await this.ladder().install(spec, mechanism)\n if (result.ok)\n await this.retireLegacyBootEntry(spec, mechanism)\n const installed = mechanism ?? result.mechanism ?? null\n const handover = result.ok\n ? await this.bootHandoverPlan(spec, installed ?? undefined, panel, before.mechanism)\n : { detail: null, commands: [], stateDir: null, plan: null, display: [] }\n\n const detail = [result.detail, handover.detail].filter((line): line is string => line !== null).join('; ')\n const commands = [...result.commands, ...handover.commands]\n const current = this.options.settings.get().autostart\n this.options.settings.update({\n autostart: {\n // The preference follows the outcome for a real install, and the last\n // attempt is recorded either way so a failure is visible after a reload.\n enabled: result.ok ? true : current.enabled,\n mechanism: result.ok\n ? (mechanism ?? result.mechanism ?? current.mechanism)\n : current.mechanism,\n lastAttempt: {\n ok: result.ok,\n action: 'install',\n mechanism: result.mechanism ?? mechanism ?? null,\n detail,\n commands,\n at: Date.now(),\n },\n },\n })\n\n // Only now, with the intent already on disk: the helper stops the panel, and\n // this process may not survive to write anything after that.\n if (handover.plan !== null && handover.stateDir !== null) {\n const spawned = (this.options.spawnActivation ?? spawnActivation)(handover.stateDir, handover.plan)\n this.clientCache = null\n this.tokenProof = null\n return {\n result: {\n ...result,\n detail: `${detail}; ${spawned.detail}`,\n // A helper that never started leaves the panel up, so a person needs the\n // exact steps to do it by hand.\n commands: spawned.ok ? commands : [...commands, ...handover.display],\n },\n status: await this.bootStatus(mechanism),\n }\n }\n\n return {\n result: { ...result, detail, commands },\n status: await this.bootStatus(mechanism),\n }\n }\n\n /**\n * The hand-over this install needs, if any: the plan for a detached helper, or\n * just the note explaining why the panel is being left alone.\n *\n * An install alone proves nothing: the panel the plugin drove keeps running, so\n * nothing shows whether the entry would ever start one. Starting it is the only\n * way to know. Nothing is spawned here \u2014 the caller persists the intent first,\n * because the helper stops the panel and this process may not outlive it.\n *\n * When the mechanism cannot start anything (a `Run` value, a `.desktop` file the\n * session reads at login), nothing is stopped: leaving a working panel up beats\n * killing it for an entry that would not bring it back.\n */\n private async bootHandoverPlan(\n spec: BootSpec,\n mechanism: BootMechanism | undefined,\n panel: PanelStatus,\n previousMechanism: BootMechanism | null,\n ): Promise<{ detail: string | null, commands: string[], stateDir: string | null, plan: ActivationPlan | null, display: string[] }> {\n const activation = await this.ladder().activate(spec, mechanism)\n if (activation === null) {\n return {\n detail: panel.reachable\n ? `${mechanism ?? 'this mechanism'} cannot start the panel itself, so it was left running rather than stopped with nothing to bring it back`\n : null,\n commands: [],\n stateDir: null,\n plan: null,\n display: [],\n }\n }\n\n if (!panel.reachable) {\n return {\n detail: 'no panel is answering, so the entry was not started now; it starts the panel at boot or login',\n commands: [],\n stateDir: null,\n plan: null,\n display: [],\n }\n }\n // A panel already running under this mechanism is where this is trying to\n // get to; stopping it would only interrupt a working panel.\n if (previousMechanism !== null && previousMechanism === mechanism) {\n return {\n detail: `the panel is already running under ${mechanism}, so it was left alone`,\n commands: [],\n stateDir: null,\n plan: null,\n display: [],\n }\n }\n\n const deps = await this.panelControlDeps()\n if (deps.launch === null)\n throw new HomeHostedError((await this.cli()).resolution.status.detail, 'CLI_NOT_FOUND')\n\n const retire = activation.retired.length > 0 ? `; retiring ${activation.retired.join(', ')}` : ''\n return {\n detail: `handing the panel to ${mechanism ?? 'the entry'} now; this page disconnects and comes back when it answers${retire}`,\n commands: [],\n stateDir: deps.stateDir,\n display: [...activation.display, ...activation.retireDisplay],\n plan: {\n commands: activation.commands,\n requires: activation.requires.commands,\n files: activation.requires.files,\n // The CLI's own `down`, never the mechanism's `restart`: home-hosted\n // refuses to start a second panel while `run.json` names a live pid, so\n // the old one has to be gone first or the entry just fails while the\n // orphan keeps the port.\n stop: [...deps.launch.args, 'down', '--home', deps.home],\n detach: activation.stop,\n oldPid: panel.pid,\n retire: activation.retire,\n logPath: activationLogPath(deps.stateDir),\n },\n }\n }\n\n async uninstallBoot(mechanism?: BootMechanism): Promise<{ result: BootInstallResult, status: BootStatus }> {\n const spec = await this.bootSpec()\n if (spec === null)\n throw new HomeHostedError((await this.cli()).resolution.status.detail, 'CLI_NOT_FOUND')\n const result = await this.ladder().uninstall(spec, mechanism)\n if (result.ok)\n await this.retireLegacyBootEntry(spec, mechanism)\n const current = this.options.settings.get().autostart\n this.options.settings.update({\n autostart: {\n ...current,\n enabled: result.ok ? false : current.enabled,\n lastAttempt: {\n ok: result.ok,\n action: 'uninstall',\n mechanism: result.mechanism ?? mechanism ?? null,\n detail: result.detail,\n commands: result.commands,\n at: Date.now(),\n },\n },\n })\n return { result, status: await this.bootStatus(mechanism) }\n }\n\n /**\n * Remove the pre-per-instance artifact once this state root has its own.\n *\n * An install that used to write `home-hosted` now writes `home-hosted-<hash>`,\n * and a machine left with both would autostart the panel twice \u2014 or keep\n * starting it from an entry the page no longer reports. Only an artifact\n * carrying this plugin's marker is touched, so somebody else's unit survives.\n */\n private async retireLegacyBootEntry(spec: BootSpec, mechanism?: BootMechanism): Promise<void> {\n if (spec.unitName === LEGACY_BOOT_UNIT_NAME)\n return\n const legacy = { ...spec, unitName: LEGACY_BOOT_UNIT_NAME }\n try {\n const status = await this.ladder().status(legacy, mechanism)\n if (status.unitPath === null || status.state === 'not-installed' || status.state === 'unsupported')\n return\n if (!fs.existsSync(status.unitPath))\n return\n await this.ladder().uninstall(legacy, status.mechanism ?? mechanism)\n }\n catch {\n // A legacy entry we cannot retire is reported by the boot status the page\n // reads; failing the install over it would be worse.\n }\n }\n\n // -------------------------------------------------------------------------\n // Panel lifecycle\n // -------------------------------------------------------------------------\n\n /** Out-of-the-box start: run the preferred CLI's `up`, which detaches itself. */\n async startPanelNow(): Promise<PanelControlResult> {\n const { resolution } = await this.cli()\n const deps = await this.panelControlDeps()\n if (deps.launch === null)\n throw new HomeHostedError(resolution.status.detail, 'CLI_NOT_FOUND')\n const runtime = this.runtime()\n if (runtime !== null && await probePanel(runtime.url)) {\n return { ok: true, detail: 'a panel is already answering', url: runtime.url, version: runtime.version }\n }\n this.applyPanelPort()\n const result = await startPanelProcess(deps)\n this.clientCache = null\n return result\n }\n\n /**\n * Stop the panel the plugin drives, and every server it supervises with it.\n *\n * Deliberately not a replacement for the takeover guard: stopping is safe to\n * lose (an `autostart` entry comes back), while replacing is not. If this\n * process is one of those servers the answer may never arrive, so the page\n * treats a dropped call as \"the panel is stopping\" rather than a failure.\n */\n async stopPanelNow(): Promise<PanelControlResult> {\n const { resolution } = await this.cli()\n const runtime = this.runtime()\n if (runtime === null || !await probePanel(runtime.url)) {\n return { ok: true, detail: 'no panel is answering, so there was nothing to stop' }\n }\n const deps = await this.panelControlDeps()\n if (deps.launch === null)\n throw new HomeHostedError(resolution.status.detail, 'CLI_NOT_FOUND')\n const result = await stopPanelProcess(deps)\n this.clientCache = null\n this.tokenProof = null\n return result\n }\n\n /**\n * Replace an answering panel with the preferred copy.\n *\n * That stops the servers the old panel supervises \u2014 this process included \u2014 so\n * the work is handed to a detached helper and the guard demands that this\n * session is an adopted, autostarting entry the new panel will bring back.\n */\n async takeoverPanel(force = false): Promise<PanelControlResult> {\n const { resolution } = await this.cli()\n const deps = await this.panelControlDeps()\n if (deps.launch === null)\n throw new HomeHostedError(resolution.status.detail, 'CLI_NOT_FOUND')\n\n const runtime = this.runtime()\n if (runtime === null || !(await probePanel(runtime.url)))\n return await this.startPanelNow()\n\n if (runtime.version !== null && resolution.status.version !== null && runtime.version === resolution.status.version) {\n return { ok: true, detail: `the answering panel is already ${runtime.version}`, url: runtime.url, version: runtime.version }\n }\n\n if (!force) {\n const id = this.selfEntryId()\n const live = id === null ? null : (await this.liveEntries()).get(id) ?? null\n if (id === null || live === null || live.config.autostart !== true) {\n throw new HomeHostedError(\n 'replacing the panel stops every server it supervises, including this session, and nothing would start it again: '\n + 'adopt this entry with autostart first, or pass force',\n 'TAKEOVER_UNSAFE',\n )\n }\n }\n\n return spawnTakeover(deps, runtime.pid)\n }\n\n /** Install the pinned range globally, so the `global` preference has a copy to run. */\n async installGlobalCli(): Promise<PanelControlResult & { output?: string }> {\n const result = await installGlobal(EXPECTED_RANGE)\n this.cliCache = null\n return { ok: result.ok, detail: result.detail, output: result.output }\n }\n\n /** Re-assert an entry that is already installed: a node or CLI upgrade moves the\n * paths a unit was written with, and the fix is to rewrite it.\n *\n * Never installs one that is not there. Installing is a deliberate act \u2014 it can\n * need root and it makes this machine start something at boot \u2014 so it happens on\n * an explicit click or an approved tool call, not as a side effect of startup.\n */\n async reconcile(): Promise<void> {\n // A managed entry that someone deleted (by mistake, from the panel or by hand)\n // stays \"managed\" in settings with nothing to manage, which is the state the\n // page reports as a missing entry. Recreate it \u2014 the toggle is the intent.\n await this.ensureManagedEntry()\n await this.repairManagedCommand()\n await this.repairBootEntry()\n await this.migrateBootEntryName()\n }\n\n /**\n * An upgrade from a release that named every artifact `home-hosted` leaves\n * that one behind while this state root now writes its own name. Once our own\n * artifact is in place, the old one is retired \u2014 otherwise the machine\n * autostarts the panel twice, or from an entry the page no longer reports.\n */\n private async migrateBootEntryName(): Promise<void> {\n if (!this.options.settings.get().autostart.enabled)\n return\n const spec = await this.bootSpec()\n if (spec === null || spec.unitName === LEGACY_BOOT_UNIT_NAME)\n return\n try {\n const own = await this.bootStatus()\n if (own.state !== 'enabled-running' && own.state !== 'enabled-failing' && own.state !== 'installed-disabled')\n return\n await this.retireLegacyBootEntry(spec, own.mechanism ?? undefined)\n }\n catch {\n // reported by the boot status the page reads\n }\n }\n\n /**\n * Put back a managed entry that no longer exists, while its intent still wants\n * autostart. A paused intent is a deliberate stop and is left alone.\n *\n * Called from startup `reconcile()` as well as the status read, because a person\n * who deletes the entry from the panel is looking at the page right then \u2014 not\n * at the next plugin start. A failed attempt is not repeated for a while, so a\n * config that cannot be written does not turn every poll into a write.\n */\n private async ensureManagedEntry(): Promise<void> {\n const { settings } = this.options\n const id = this.options.defaultEntryId\n if (!settings.get().manageDsh || !settings.intentFor(id).autostart)\n return\n // Claim the attempt before the first await, so two status calls racing each\n // other cannot both decide to create the same entry.\n const now = Date.now()\n if (now - this.entryRecoveryAt < ENTRY_RECOVERY_INTERVAL_MS)\n return\n this.entryRecoveryAt = now\n if ((await this.entriesStatus()).every(entry => entry.intent.id !== id || entry.exists))\n return\n try {\n await this.writeOwned({ ...settings.intentFor(id), autostart: true })\n }\n catch {\n // an unwritable config is reported by the status the page reads\n }\n }\n\n /**\n * Re-point a managed `dsh` row that runs the wrong dsh.\n *\n * A row created before the launcher existed, or by an older release that took\n * whatever PATH answered, keeps starting that copy \u2014 and no click would fix\n * it, because the plugin only writes the row when the toggle is applied. So\n * the drift is repaired where the entry's existence is already checked, under\n * the same throttle, and only for a row this plugin owns.\n */\n private async repairManagedCommand(): Promise<void> {\n const id = this.options.defaultEntryId\n if (!this.options.settings.get().manageDsh || !this.options.settings.intentFor(id).autostart)\n return\n const now = Date.now()\n // Its own throttle: the entry-recovery one is claimed by every status read,\n // so sharing it would mean this repair never gets a turn.\n if (now - this.commandRepairAt < ENTRY_RECOVERY_INTERVAL_MS)\n return\n const live = (await this.liveEntries()).get(id)?.config ?? null\n if (live === null || this.snapshots()[id] === undefined || !needsLauncherRepair(live))\n return\n const repair = await this.dshCommandRepair(live)\n if (repair === null)\n return\n this.commandRepairAt = now\n try {\n await this.writeOwned({ ...this.options.settings.intentFor(id), autostart: true })\n }\n catch {\n // reported by the status the page reads\n }\n }\n\n /** Re-assert the boot entry when the OS still has it but it stopped working. */\n private async repairBootEntry(): Promise<void> {\n if (!this.options.settings.get().autostart.enabled)\n return\n const status = await this.bootStatus()\n if (status.state !== 'enabled-failing' && status.state !== 'installed-disabled')\n return\n // Belt on top of the probe: a file-backed entry is only repaired when the file\n // is actually there, so a reporting slip can never turn startup into an install.\n const fileBacked = status.mechanism === 'systemd-user'\n || status.mechanism === 'systemd-system'\n || status.mechanism === 'xdg-autostart'\n || status.mechanism === 'launchd-agent'\n || status.mechanism === 'launchd-daemon'\n if (fileBacked && (status.unitPath === null || !fs.existsSync(status.unitPath)))\n return\n // `enabled-failing` is exactly what a system unit that runs the panel as root\n // looks like once a server refuses to run as root, so this repair now rewrites\n // it with the account resolved above. A *working* entry is deliberately not\n // touched: re-installing stops the panel and hands it to the entry, which is a\n // person's decision, not a startup side effect \u2014 and the status detail names\n // the wrong account so the page's install button is an informed choice.\n try {\n await this.installBoot()\n }\n catch {\n // a failed repair is reported by the status the page reads\n }\n }\n\n // -------------------------------------------------------------------------\n // Status\n // -------------------------------------------------------------------------\n\n async status(refresh = false): Promise<HomeHostedStatus> {\n // Resolve the client first: enrolling a token changes which write path is\n // available, and the page should see the state after that, not before.\n const client = await this.tryClient()\n const panel = await this.panelStatus()\n const { resolution, launcher, launcherVersion } = await this.cli()\n const cliDetail = resolution.status.detail\n const panelVersion = this.runtime()?.version ?? null\n const cli = {\n ...resolution.status,\n launcherPath: launcher,\n launcherVersion,\n // A panel left running from an older install is the usual reason to see\n // two versions on this page; say so instead of leaving it ambiguous.\n detail: panelVersion !== null && resolution.status.version !== null && panelVersion !== resolution.status.version\n ? `${cliDetail}; the running panel is ${panelVersion}`\n : cliDetail,\n }\n let servers: ServerEntryView[] = []\n let lastError: string | null = null\n if (client !== null) {\n try {\n servers = await client.listServers(this.managedWorkspace())\n }\n catch (error) {\n lastError = error instanceof Error ? error.message : String(error)\n }\n }\n else {\n if (panel.reachable)\n lastError = panel.detail\n // No client: the workspace's own file is still the best available truth,\n // and it is what the `source: 'file'` counts are read from.\n const read = this.fileServers(this.options.home, this.managedWorkspace())\n servers = read.servers\n if (read.error !== null)\n lastError ??= read.error\n }\n\n return {\n defaultEntryId: this.options.defaultEntryId,\n workspace: this.managedWorkspace(),\n workspaces: await this.workspaceSummaries(this.options.home, false).catch(() => []),\n legacyRoot: isLegacyRoot(this.options.home),\n panel,\n panelRoot: this.options.home,\n panelRootSource: this.options.panelHomeSource ?? 'instance',\n bootUnitName: bootUnitName(this.options.stateDir),\n instances: await this.instances(refresh),\n boot: await this.bootStatus(),\n // A deleted managed entry is put back here as well as at startup: whoever\n // deleted it is looking at the page that reports it missing.\n entries: await this.ensureManagedEntry()\n .then(async () => await this.repairManagedCommand())\n .then(async () => await this.entriesStatus()),\n servers,\n settings: this.options.settings.get(),\n cli,\n lastError,\n }\n }\n\n // -------------------------------------------------------------------------\n // Endpoint dispatch\n // -------------------------------------------------------------------------\n\n /**\n * Which workspace an endpoint call acts on. A caller may name any workspace the\n * panel serves; omitted means the one this plugin manages, which for another\n * panel is its default.\n */\n private callWorkspace(input: Record<string, unknown>, home: string, foreign: boolean): string {\n const named = typeof input.workspace === 'string' ? input.workspace.trim() : ''\n if (named.length > 0) {\n if (!isWorkspaceId(named))\n throw new HomeHostedError(`\"${named}\" is not a workspace id (expected ^[a-z0-9][a-z0-9_-]*$)`, 'INVALID_WORKSPACE')\n return named\n }\n return foreign ? defaultWorkspace(home) : this.managedWorkspace()\n }\n\n async call(endpoint: RpcEndpoint, payload: unknown): Promise<unknown> {\n const input = (payload ?? {}) as Record<string, unknown>\n // A target names a panel; an endpoint with no way to reach another one must\n // refuse it rather than quietly act somewhere else.\n if (typeof input.home === 'string' && input.home.trim().length > 0 && FOREIGN_MECHANISMS[endpoint] === undefined) {\n throw new HomeHostedError(\n `\"${endpoint}\" is about the panel this plugin manages and cannot be aimed at another one`,\n 'INSTANCE_UNSUPPORTED',\n )\n }\n switch (endpoint) {\n case 'status':\n return await this.status(input.refresh === true)\n\n case 'settings.update': {\n const patch = (input.patch ?? {}) as Parameters<SettingsStore['update']>[0]\n this.options.settings.update(patch)\n return await this.status()\n }\n\n case 'panel.migrate':\n return await this.migrateRoot()\n\n case 'workspaces.list': {\n const target = await this.targetHome(input.home)\n return await this.workspaceSummaries(target.home, target.foreign)\n }\n\n case 'servers.list': {\n const target = await this.targetHome(input.home)\n const workspace = this.callWorkspace(input, target.home, target.foreign)\n if (!target.foreign) {\n // A panel that is not answering is still a readable config: the page's\n // degraded view must not turn into an empty list.\n const client = await this.tryClient()\n return client === null ? this.fileServers(target.home, workspace).servers : await client.listServers(workspace)\n }\n const via = this.mechanismFor('servers.list', input.via)\n // A read never mints a credential: it uses one this plugin already holds\n // for that panel, and otherwise reads the config file it can always read.\n if (via === 'api' || (input.via === undefined && await this.foreignTokenWorks(target.home)))\n return await (await this.foreignClient(target.home)).listServers(workspace)\n return this.listForeign(target.home, workspace)\n }\n\n case 'servers.get':\n return await (await this.requireClient()).getServer(String(input.id), this.callWorkspace(input, this.options.home, false))\n\n case 'servers.create': {\n const entry = input.entry as ServerEntry\n if (typeof entry?.id !== 'string' || !ENTRY_ID_PATTERN.test(entry.id))\n throw new HomeHostedError('a server entry needs an id matching ^[a-z0-9][a-z0-9_-]*$', 'INVALID_ID')\n const target = await this.targetHome(input.home)\n const workspace = this.callWorkspace(input, target.home, target.foreign)\n if (!target.foreign)\n return await (await this.requireClient()).createServer(entry, workspace)\n return this.mechanismFor('servers.create', input.via) === 'api'\n ? await (await this.foreignClient(target.home)).createServer(entry, workspace)\n : this.createForeign(target.home, entry, workspace)\n }\n\n case 'servers.update': {\n const target = await this.targetHome(input.home)\n const id = String(input.id)\n const patch = (input.patch ?? {}) as ServerEntryPatch\n const workspace = this.callWorkspace(input, target.home, target.foreign)\n if (!target.foreign)\n return await (await this.requireClient()).updateServer(id, patch, workspace)\n return this.mechanismFor('servers.update', input.via) === 'api'\n ? await (await this.foreignClient(target.home)).updateServer(id, patch, workspace)\n : this.updateForeign(target.home, id, patch, workspace)\n }\n\n case 'servers.delete': {\n const id = String(input.id)\n const target = await this.targetHome(input.home)\n const workspace = this.callWorkspace(input, target.home, target.foreign)\n if (target.foreign) {\n return this.mechanismFor('servers.delete', input.via) === 'api'\n ? await (await this.foreignClient(target.home)).deleteServer(id, workspace).then(() => ({ id, workspace, home: target.home, via: 'api' as const }))\n : this.deleteForeign(target.home, id, workspace)\n }\n if (id === this.selfEntryId()) {\n throw new HomeHostedError(\n `\"${id}\" is the entry this very process runs as, and deleting it stops this session; `\n + 'pause it (autostart off) or restore it instead',\n 'SELF_ENTRY',\n )\n }\n await (await this.requireClient()).deleteServer(id, workspace)\n return { id, workspace }\n }\n\n case 'servers.start': {\n const target = await this.targetHome(input.home)\n const id = String(input.id)\n const workspace = this.callWorkspace(input, target.home, target.foreign)\n if (target.foreign) {\n if (this.mechanismFor('servers.start', input.via) === 'api') {\n await (await this.foreignClient(target.home)).startServer(id, workspace)\n return { home: target.home, workspace, id, action: 'start', via: 'api' }\n }\n return await this.lifecycleForeign(target.home, 'start', id, workspace)\n }\n await (await this.requireClient()).startServer(id, workspace)\n return await this.entriesStatus()\n }\n\n case 'servers.stop': {\n const target = await this.targetHome(input.home)\n const id = String(input.id)\n const workspace = this.callWorkspace(input, target.home, target.foreign)\n if (target.foreign) {\n if (this.mechanismFor('servers.stop', input.via) === 'api') {\n await (await this.foreignClient(target.home)).stopServer(id, workspace)\n return { home: target.home, workspace, id, action: 'stop', via: 'api' }\n }\n return await this.lifecycleForeign(target.home, 'stop', id, workspace)\n }\n await (await this.requireClient()).stopServer(id, workspace)\n return await this.entriesStatus()\n }\n\n case 'servers.restart': {\n const target = await this.targetHome(input.home)\n const id = String(input.id)\n const workspace = this.callWorkspace(input, target.home, target.foreign)\n if (target.foreign) {\n if (this.mechanismFor('servers.restart', input.via) === 'api') {\n await (await this.foreignClient(target.home)).restartServer(id, workspace)\n return { home: target.home, workspace, id, action: 'restart', via: 'api' }\n }\n return await this.lifecycleForeign(target.home, 'restart', id, workspace)\n }\n await (await this.requireClient()).restartServer(id, workspace)\n return await this.entriesStatus()\n }\n\n case 'servers.freePort':\n return await (await this.requireClient()).freePort(String(input.id), this.callWorkspace(input, this.options.home, false))\n\n case 'entries.apply': {\n const intents = Array.isArray(input.intents) ? input.intents as EntryIntent[] : []\n return await this.applyIntents(intents)\n }\n\n case 'entries.remove':\n return await this.removeManagedEntry(String(input.id))\n\n case 'entries.restore':\n return await this.restoreEntry(String(input.id))\n\n case 'ui.manage': {\n const target = await this.targetHome(input.home)\n if (target.foreign)\n this.mechanismFor('ui.manage', input.via)\n return await this.uiManage(\n input.action as UiAction,\n {\n file: typeof input.file === 'string' ? input.file : undefined,\n asset: typeof input.asset === 'string' ? input.asset : undefined,\n repo: typeof input.repo === 'string' ? input.repo : undefined,\n tag: typeof input.tag === 'string' ? input.tag : undefined,\n },\n target.foreign ? target.home : undefined,\n )\n }\n\n case 'boot.install':\n return await this.installBoot(input.mechanism as BootMechanism | undefined)\n\n case 'boot.uninstall':\n return await this.uninstallBoot(input.mechanism as BootMechanism | undefined)\n\n case 'boot.verify':\n return await this.bootStatus()\n\n case 'panel.start':\n return await this.startPanelNow()\n\n case 'panel.stop':\n return await this.stopPanelNow()\n\n case 'panel.takeover':\n return await this.takeoverPanel(input.force === true)\n\n case 'panel.reclaimToken':\n return await this.reclaimPanelToken()\n\n case 'cli.installGlobal':\n return await this.installGlobalCli()\n\n default:\n throw new HomeHostedError(`unknown endpoint \"${String(endpoint)}\"`, 'UNKNOWN_ENDPOINT')\n }\n }\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n homeHosted: HomeHostedService\n }\n}\n", "/**\n * Shared pieces of the boot ladder: where each mechanism's artifact lives, how\n * ownership of an existing artifact is proven before it is touched, and how a\n * probe's answers collapse into one `BootState`. No process is started here.\n */\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { readText, writeFileAtomic } from '../util/fsx.js'\nimport type { BootState } from '../shared/contracts.js'\nimport type { BootAccount, BootActionResult, BootProviderContext, PasswdEntry } from './types.js'\n\nexport function errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/** Existence check through the injected seam, so a probe is testable without touching the host. */\nexport function existsOf(ctx: BootProviderContext, file: string): boolean {\n return ctx.exists ? ctx.exists(file) : fs.existsSync(file)\n}\n\nexport function posixJoin(...parts: string[]): string {\n return path.posix.join(...parts)\n}\n\n/** `$XDG_CONFIG_HOME`, else `~/.config`. */\nexport function configHome(ctx: BootProviderContext): string {\n const xdg = ctx.env.XDG_CONFIG_HOME?.trim()\n return xdg ? path.posix.resolve(xdg) : posixJoin(ctx.home, '.config')\n}\n\n/** `%LOCALAPPDATA%`, else `~\\AppData\\Local`. */\nexport function localAppData(ctx: BootProviderContext): string {\n const value = ctx.env.LOCALAPPDATA?.trim()\n return value ? value : path.join(ctx.home, 'AppData', 'Local')\n}\n\n/** A writable scratch directory, used to stage a unit before `sudo install`. */\nexport function tempDir(ctx: BootProviderContext): string {\n const value = ctx.env.TMPDIR?.trim() || ctx.env.TEMP?.trim() || ctx.env.TMP?.trim()\n return value || os.tmpdir()\n}\n\n// ---------------------------------------------------------------------------\n// The invoking account\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a passwd-format file. Exported so a test can read a fixture: the only\n * alternative is asserting against whatever accounts the machine running the\n * suite happens to have, which is exactly the host dependency this module exists\n * to remove from unit generation.\n */\nexport function parsePasswd(text: string): PasswdEntry[] {\n return text\n .split('\\n')\n .map((line) => {\n const fields = line.split(':')\n const uid = Number.parseInt(fields[2] ?? '', 10)\n return fields.length < 6 || !Number.isFinite(uid) || (fields[0] ?? '').length === 0\n ? null\n : { name: fields[0] ?? '', uid, home: fields[5] ?? '' }\n })\n .filter((entry): entry is PasswdEntry => entry !== null)\n}\n\n/**\n * Read the user database: a file read, never a subprocess.\n *\n * An empty result is a normal outcome, not a failure \u2014 macOS keeps local accounts\n * in Open Directory and its `/etc/passwd` holds system accounts only. Nothing here\n * depends on the lookup succeeding: a session name is used as itself, and the\n * database only supplies the home and confirms a uid.\n */\nfunction readPasswd(): PasswdEntry[] {\n try {\n return parsePasswd(fs.readFileSync('/etc/passwd', 'utf8'))\n }\n catch {\n return []\n }\n}\n\n/** A numeric env value, or null when it is absent or not a plain number. */\nfunction numberFrom(value: string | undefined): number | null {\n const text = value?.trim()\n if (text === undefined || text.length === 0)\n return null\n return /^\\d+$/.test(text) ? Number.parseInt(text, 10) : null\n}\n\n/**\n * The home a row gives, falling back to the ambient one only when the row has none.\n *\n * They agree in the normal case. When they do not, the row is the one that is\n * actually that login's home \u2014 and the ambient one is the value that arrives as\n * root's under `sudo`.\n */\nfunction accountFrom(row: PasswdEntry, ambientHome?: string): BootAccount {\n const home = row.home.length > 0 ? row.home : (ambientHome?.trim() || null)\n return { name: row.name, uid: row.uid, home, root: row.uid === 0, verified: true }\n}\n\n/** `SUDO_USER`/`PKEXEC_USER`: what an elevating tool says it elevated. */\nfunction declaredName(env: Record<string, string | undefined>): string | null {\n const value = env.SUDO_USER?.trim() || env.PKEXEC_USER?.trim()\n return value !== undefined && value.length > 0 ? value : null\n}\n\n/** `LOGNAME`, else `USER`: this process's own login, when nothing elevated it. */\nfunction sessionName(env: Record<string, string | undefined>): string | null {\n const value = env.LOGNAME?.trim() || env.USER?.trim() || env.USERNAME?.trim()\n return value !== undefined && value.length > 0 ? value : null\n}\n\n/** The owner of the first of `paths` that exists and is not root, as an account. */\nfunction ownerAccount(paths: readonly string[] | undefined, passwd: readonly PasswdEntry[]): BootAccount | null {\n for (const path of paths ?? []) {\n try {\n const uid = fs.statSync(path).uid\n if (uid === 0)\n continue\n const row = passwd.find(entry => entry.uid === uid)\n if (row !== undefined)\n return accountFrom(row)\n }\n catch {\n // an unreadable path is just not an answer\n }\n }\n return null\n}\n\n/** What resolving an account needs; {@link BootProviderContext} satisfies it. */\nexport interface AccountInput {\n env: Record<string, string | undefined>\n /** This process's own uid; defaults to `process.getuid()`. */\n uid?: number | null\n /** Paths whose owner names the person a root process's panel belongs to. */\n ownerPaths?: readonly string[]\n /** The user database to resolve against; defaults to the real one. */\n passwd?: readonly PasswdEntry[]\n /** Where a refusal or a root-account fallback is said out loud. */\n warn?: (message: string) => void\n}\n\n/**\n * The account a boot entry must run as.\n *\n * `$USER` is right in the common case and wrong in exactly the case this exists\n * for: `sudo` and `pkexec` rewrite `USER`, `LOGNAME` and `HOME` to the *target*\n * account, so a panel started from a root shell or a root unit reads\n * `USER=root` \u2014 and so does `os.userInfo()`, which reports the euid.\n *\n * Two rules keep the answer usable, and both are about what systemd does with it:\n *\n * 1. **Prefer the database row for the uid over the name in the environment.**\n * They agree in the normal case. When they do not, the row is the one that\n * names a real login, and `User=<a name systemd cannot resolve>` is worse than\n * no entry at all: systemd refuses to start it (`status=217/USER`) and\n * `Restart=always` turns that into a crash-loop, which is exactly the\n * unexplained failure this whole change exists to remove.\n * 2. **A uid this process has is the fact; `$UID` is a claim.** `$UID` is a shell\n * variable rather than an exported one, so when it is present at all it was\n * inherited, and it feeds the launchd domain (`gui/$UID`) as well as the owner\n * lookups. It is consulted only when there is no uid to be had from the\n * process itself.\n *\n * The order is therefore:\n *\n * 1. an elevating tool's `SUDO_UID`/`PKEXEC_UID` (an instruction, and the only\n * place the invoking login survives) \u2014 but only while it resolves, so a stale\n * value inherited from an ancestor cannot collapse the entry to root;\n * 2. this session's own login: its database row, else the row for this uid, else\n * the name itself for a login the local database does not hold (macOS keeps\n * accounts in Open Directory and its `/etc/passwd` has no user rows);\n * 3. the row for this uid;\n * 4. the owner of a state path \u2014 who the panel actually belongs to;\n * 5. and only then, honestly, root.\n *\n * A name that cannot be placed is reported with `verified: false`, so the writer\n * can warn instead of quietly installing a unit that will not start.\n */\nexport function accountOf(input: AccountInput): BootAccount | null {\n const passwd = input.passwd ?? readPasswd()\n const byUid = (uid: number | null): PasswdEntry | null => uid === null ? null : passwd.find(entry => entry.uid === uid) ?? null\n const byName = (name: string | null): PasswdEntry | null => name === null ? null : passwd.find(entry => entry.name === name) ?? null\n\n const elevatedUid = numberFrom(input.env.SUDO_UID) ?? numberFrom(input.env.PKEXEC_UID)\n const elevatedName = declaredName(input.env)\n if (elevatedUid !== null || elevatedName !== null) {\n const row = byUid(elevatedUid) ?? byName(elevatedName)\n if (row !== null)\n return accountFrom(row)\n // An elevating tool that named *both* a login and a nonzero uid has made an\n // explicit, consistent statement about an account the local database may not\n // hold \u2014 an LDAP or NIS login is a real account even though `/etc/passwd` has\n // no row for it. `$HOME` has already been rewritten to the target's, so it is\n // not this account's home and carrying `null` stops the entry inheriting it.\n if (elevatedUid !== null && elevatedUid !== 0 && elevatedName !== null)\n return { name: elevatedName, uid: elevatedUid, home: null, root: false, verified: false }\n // Anything less is not a statement about an account. Fall through rather than\n // let a stale `SUDO_UID` override a login this process can still name.\n }\n\n // This process's own uid. `$UID` is a claim and the process is the fact, so it\n // is only consulted when the platform gives no uid at all.\n const euid = input.uid === undefined ? (process.getuid?.() ?? null) : input.uid\n const ownUid = euid ?? numberFrom(input.env.UID)\n\n const session = sessionName(input.env)\n // systemd matches names case-sensitively, so `ROOT` is not root to it \u2014 but it is\n // also not a login, and writing it produces a 217/USER refusal.\n if (session !== null && session.toLowerCase() !== 'root') {\n const sessionRow = byName(session)\n if (sessionRow !== null)\n return accountFrom(sessionRow, input.env.HOME)\n // The name has no row. The uid's row is the better answer: it names a real\n // login, and a typo'd `$USER` would otherwise make `User=<typo>`.\n const uidRow = byUid(ownUid === 0 ? null : ownUid)\n if (uidRow !== null)\n return accountFrom(uidRow, input.env.HOME)\n return { name: session, uid: ownUid, home: input.env.HOME?.trim() || null, root: false, verified: false }\n }\n\n // The root row is deliberately not consulted here: `uid 0` always has one, so\n // accepting it would short-circuit the state-owner fallback below \u2014 which is the\n // only answer a root process has for \"whose panel is this?\". Root is the *last*\n // resort, after the panel's own files have been asked.\n const ownRow = ownUid === 0 ? null : byUid(ownUid)\n if (ownRow !== null)\n return accountFrom(ownRow)\n\n const owner = ownerAccount(input.ownerPaths, passwd)\n if (owner !== null)\n return owner\n\n // Nothing named a person. A root process's panel runs as root whether or not a\n // unit says so, so report that and let every writer refuse to dress it up.\n if (ownUid === 0 || session?.toLowerCase() === 'root')\n return { name: 'root', uid: 0, home: input.env.HOME?.trim() || '/root', root: true, verified: true }\n return null\n}\n\n/**\n * The account a *login-scoped* mechanism belongs to.\n *\n * The systemd user manager and the desktop session are the invoking user's, so\n * the environment that started this process is the right answer here \u2014 unlike a\n * system unit, which outlives that environment entirely. A root account is never\n * offered: it is not a login this plugin can put an entry in.\n */\nexport function currentUser(ctx: AccountInput): string | null {\n const account = accountOf(ctx)\n if (account !== null && !account.root)\n return account.name\n const value = sessionName(ctx.env)\n return value !== null && value.toLowerCase() !== 'root' ? value : null\n}\n\n/**\n * A name a **system** entry may safely carry, or null.\n *\n * `User=root` is never a choice a panel made: root is the silent default of a root\n * process, and a unit that names it looks deliberate while it is the bug. So a\n * root account is refused here, loudly, and the caller omits the directive.\n *\n * A name that cannot be written is refused the same way. `User=`/`UserName` are\n * unquoted, so a name carrying a newline would end the field and let the rest of it\n * become another directive \u2014 and the environment this reads is one a caller can\n * set. No name is better than an injected one.\n */\nexport function bootUserName(ctx: AccountInput): string | null {\n const account = accountOf(ctx)\n if (account === null) {\n // No account is the same runtime outcome as root \u2014 the entry runs as root \u2014\n // so it is refused just as loudly. The candidate reason says it too, but a\n // warning belongs at the point the decision is actually made.\n warnAccount(ctx, 'no account could be resolved for the entry, so a system unit would run the panel as root. Install it from the account that should own the panel, or set User= yourself.')\n return null\n }\n if (account.root) {\n warnAccount(ctx, 'the only account available is root; a system unit would run the panel as root. Install it from the account that should own the panel, or set User= yourself.')\n return null\n }\n if (!isUserName(account.name)) {\n warnAccount(ctx, `the account name ${JSON.stringify(account.name)} is not a usable user name, so no User= line was written`)\n return null\n }\n if (!account.verified) {\n // The name is writable but nothing here can place it: an LDAP or NIS login has\n // no `/etc/passwd` row, and a stale `$USER` names nobody at all. systemd cannot\n // resolve it either, and `User=<a name it cannot resolve>` makes the unit fail\n // with `status=217/USER` \u2014 which `Restart=always` turns into a crash-loop that\n // never mentions the account. Installing that is worse than installing nothing,\n // so the name is refused and the reason is said out loud; the page's install\n // button remains for anyone who knows the name is resolvable on that machine.\n warnAccount(ctx, `the account ${JSON.stringify(account.name)} could not be found in the user database, so no User= line was written: systemd would refuse to start the unit (status=217/USER). Set User= yourself if that account resolves through LDAP or NIS.`)\n return null\n }\n return account.name\n}\n\n/** The one definition of a writable account name; {@link assertUserName} enforces it in a payload. */\nfunction isUserName(name: string): boolean {\n return /^[A-Za-z_][A-Za-z0-9._-]*$/.test(name)\n}\n\nfunction warnAccount(ctx: AccountInput, message: string): void {\n try {\n (ctx.warn ?? ((line: string) => console.warn(`[dsh-home-hosted] ${line}`)))(message)\n }\n catch {\n // a diagnostic is never worth failing a boot entry over\n }\n}\n\n/** The numeric uid this process runs as, as text, for a launchd domain. */\nexport function uidOf(ctx: BootProviderContext): string | null {\n const configured = numberFrom(ctx.env.UID)\n if (configured !== null)\n return String(configured)\n const uid = ctx.uid === undefined ? (process.getuid?.() ?? null) : ctx.uid\n return uid === null ? null : String(uid)\n}\n\n// ---------------------------------------------------------------------------\n// Ownership\n// ---------------------------------------------------------------------------\n\nexport interface FileOwnership {\n exists: boolean\n owned: boolean\n text: string | null\n reason: string | null\n}\n\n/**\n * An artifact belongs to this plugin only when it carries the marker. A file\n * we cannot read is treated as foreign: we must never overwrite or delete a\n * unit somebody else wrote.\n */\nexport function inspectOwned(file: string, marker: string): FileOwnership {\n if (!fs.existsSync(file))\n return { exists: false, owned: false, text: null, reason: null }\n const text = readText(file)\n if (text === null)\n return { exists: true, owned: false, text: null, reason: `${file} exists but is unreadable, so it cannot be proven to be ours` }\n if (!text.includes(marker))\n return { exists: true, owned: false, text, reason: `${file} exists and does not carry this plugin's marker (${marker}); refusing to touch an artifact we did not write` }\n return { exists: true, owned: true, text, reason: null }\n}\n\n/** Write our artifact if the bytes differ; never overwrite a foreign one. */\nexport function writeOwned(file: string, marker: string, content: string, mode = 0o644): { changed: boolean, refusal: string | null } {\n const own = inspectOwned(file, marker)\n if (own.exists && !own.owned)\n return { changed: false, refusal: own.reason }\n if (own.exists && own.text === content)\n return { changed: false, refusal: null }\n writeFileAtomic(file, content, mode)\n return { changed: true, refusal: null }\n}\n\n/** Delete our artifact only; a missing file is already the desired state. */\nexport function removeOwned(file: string, marker: string): { removed: boolean, refusal: string | null } {\n const own = inspectOwned(file, marker)\n if (!own.exists)\n return { removed: false, refusal: null }\n if (!own.owned)\n return { removed: false, refusal: own.reason }\n try {\n fs.rmSync(file, { force: true })\n return { removed: true, refusal: null }\n }\n catch (error) {\n return { removed: false, refusal: `could not delete ${file}: ${errorMessage(error)}` }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Results\n// ---------------------------------------------------------------------------\n\nexport function failed(detail: string, extra: Partial<BootActionResult> = {}): BootActionResult {\n return { ok: false, changed: false, detail, commands: [], needsPrivilege: false, ...extra }\n}\n\n/** Installed + enabled + last run failed is `enabled-failing`; never a guessed success. */\nexport function bootState(installed: boolean, enabled: boolean, failing: boolean): BootState {\n if (!installed)\n return 'not-installed'\n if (!enabled)\n return 'installed-disabled'\n return failing ? 'enabled-failing' : 'enabled-running'\n}\n\nexport function isInstalledState(state: BootState): boolean {\n return state === 'installed-disabled' || state === 'enabled-running' || state === 'enabled-failing'\n}\n", "/**\n * Small filesystem helpers. Every write is atomic (temp file + rename) so a\n * reader never sees half a file, and secret-bearing files are created 0600.\n */\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\n\nexport function fileExists(file: string): boolean {\n try {\n return fs.statSync(file).isFile()\n }\n catch {\n return false\n }\n}\n\nexport function readText(file: string): string | null {\n try {\n return fs.readFileSync(file, 'utf8')\n }\n catch {\n return null\n }\n}\n\nexport function readJson<T>(file: string): T | null {\n const text = readText(file)\n if (text === null)\n return null\n try {\n return JSON.parse(text) as T\n }\n catch {\n return null\n }\n}\n\nexport function ensureDir(dir: string, mode = 0o700): void {\n fs.mkdirSync(dir, { recursive: true, mode })\n}\n\n/** Write a file through a sibling temp file, then rename it into place. */\nexport function writeFileAtomic(file: string, data: string, mode?: number): void {\n ensureDir(path.dirname(file))\n const temp = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${Date.now()}.tmp`)\n const fd = fs.openSync(temp, 'w', mode ?? 0o644)\n try {\n fs.writeFileSync(fd, data, 'utf8')\n fs.fsyncSync(fd)\n }\n finally {\n fs.closeSync(fd)\n }\n if (mode !== undefined)\n fs.chmodSync(temp, mode)\n fs.renameSync(temp, file)\n}\n\nexport function writeJsonAtomic(file: string, value: unknown, mode?: number): void {\n writeFileAtomic(file, `${JSON.stringify(value, null, 2)}\\n`, mode)\n}\n\n/** Best-effort removal; a missing file is not an error. */\nexport function removeFile(file: string): void {\n try {\n fs.rmSync(file, { force: true })\n }\n catch {\n // ignored: the caller only cares that it is gone, and reconciliation reports the rest\n }\n}\n", "/**\n * The boot ladder: which mechanisms exist on this platform, which one is\n * installed, and which one this process should use. Everything platform\n * specific is inside a provider's `detect()`/`status()`/`install()` \u2014 this file\n * only orders them and never runs at import time.\n */\nimport os from 'node:os'\nimport process from 'node:process'\nimport type { BootCandidate, BootMechanism, BootStatus } from '../shared/contracts.js'\nimport { run as execRun, sudoAvailable } from '../util/exec.js'\nimport type {\n BootActionResult,\n BootActivation,\n BootLadder,\n BootLadderOptions,\n BootProvider,\n BootProviderContext,\n BootProviderStatus,\n BootRetirement,\n BootSpec,\n BootStart,\n} from './types.js'\nimport { errorMessage, failed, isInstalledState } from './common.js'\nimport { createSystemdSystemProvider, createSystemdUserProvider } from './systemd.js'\nimport { createLaunchdAgentProvider, createLaunchdDaemonProvider } from './launchd.js'\nimport { createXdgAutostartProvider } from './xdg.js'\nimport { createWindowsRunProvider, createWindowsTaskProvider } from './windows.js'\nimport { createContainerProvider, createUnsupportedProvider } from './fallback.js'\n\nfunction platformKind(platform: NodeJS.Platform): BootStatus['platform'] {\n if (platform === 'linux' || platform === 'darwin' || platform === 'win32')\n return platform\n return 'other'\n}\n\n/** Ordered by preference within a platform; the terminal providers come last. */\nexport function bootProviders(\n ctx: BootProviderContext,\n platform: NodeJS.Platform = ctx.platform,\n): BootProvider[] {\n const container = createContainerProvider(ctx)\n const unsupported = createUnsupportedProvider(platform)\n switch (platform) {\n case 'linux':\n return [createSystemdUserProvider(ctx), createSystemdSystemProvider(ctx), createXdgAutostartProvider(ctx), container, unsupported]\n case 'darwin':\n return [createLaunchdAgentProvider(ctx), createLaunchdDaemonProvider(ctx), unsupported]\n case 'win32':\n return [createWindowsRunProvider(ctx), createWindowsTaskProvider(ctx), unsupported]\n default:\n return [container, unsupported]\n }\n}\n\nexport function createBootLadder(options: BootLadderOptions = {}): BootLadder {\n const platform = options.platform ?? process.platform\n // `uid` is this process's own uid, so pinning it in a test also pins whether the\n // process counts as root \u2014 otherwise the answer depends on who ran the suite.\n const uid = options.uid === undefined ? (process.getuid?.() ?? null) : options.uid\n const isRoot = uid === 0\n const ctx: BootProviderContext = {\n platform,\n home: options.home ?? os.homedir(),\n env: options.env ?? process.env,\n run: options.run ?? execRun,\n sudo: options.sudo ?? (async () => (isRoot ? true : await sudoAvailable())),\n isRoot,\n uid: options.uid,\n ownerPaths: options.ownerPaths,\n passwd: options.passwd,\n warn: options.warn,\n exists: options.exists,\n }\n const providers = bootProviders(ctx, platform)\n const kind = platformKind(platform)\n\n async function detectAll(): Promise<{ platform: BootStatus['platform'], candidates: BootCandidate[] }> {\n const candidates = await Promise.all(providers.map(async (provider) => {\n try {\n return await provider.detect()\n }\n catch (error) {\n return {\n mechanism: provider.mechanism,\n available: false,\n bootCapable: false,\n privileged: false,\n reason: `detection failed: ${errorMessage(error)}`,\n } satisfies BootCandidate\n }\n }))\n return { platform: kind, candidates }\n }\n\n async function safeStatus(provider: BootProvider, spec: BootSpec): Promise<BootProviderStatus> {\n try {\n return await provider.status(spec)\n }\n catch (error) {\n return { state: 'not-installed', unitPath: null, detail: errorMessage(error), commands: [] }\n }\n }\n\n function recommend(candidates: BootCandidate[]): BootMechanism | null {\n const byMechanism = new Map(candidates.map(candidate => [candidate.mechanism, candidate]))\n const available = providers\n .map(provider => provider.mechanism)\n .filter(mechanism => byMechanism.get(mechanism)?.available === true)\n const usable = (mechanism: string): boolean => mechanism !== 'container' && mechanism !== 'unsupported'\n // Inside a container no OS entry starts anything, so the honest advice is the\n // restart policy \u2014 ahead of anything else that merely looks available.\n if (available.includes('container'))\n return 'container'\n // A boot-capable mechanism that needs a password prompt is offered, but it is\n // not the *recommendation*: automatic should mean zero manual steps.\n const readyBoot = available.find(mechanism => usable(mechanism)\n && byMechanism.get(mechanism)?.bootCapable === true\n && byMechanism.get(mechanism)?.privileged === true)\n if (readyBoot)\n return readyBoot\n const ready = available.find(mechanism => usable(mechanism) && byMechanism.get(mechanism)?.privileged === true)\n if (ready)\n return ready\n const bootCapable = available.find(mechanism => usable(mechanism) && byMechanism.get(mechanism)?.bootCapable === true)\n if (bootCapable)\n return bootCapable\n return available.find(mechanism => mechanism !== 'unsupported')\n ?? available[0]\n ?? null\n }\n\n async function buildStatus(spec: BootSpec, mechanism?: BootMechanism): Promise<BootStatus> {\n const { candidates } = await detectAll()\n const byMechanism = new Map(candidates.map(candidate => [candidate.mechanism, candidate]))\n const statuses = new Map<BootMechanism, BootProviderStatus>()\n for (const [provider, status] of await Promise.all(providers.map(async provider => [provider, await safeStatus(provider, spec)] as const)))\n statuses.set(provider.mechanism, status)\n\n const explicit = mechanism ? providers.find(provider => provider.mechanism === mechanism) : undefined\n const installedProvider = providers.find((provider) => {\n const status = statuses.get(provider.mechanism)\n return status !== undefined && isInstalledState(status.state)\n })\n const recommended = recommend(candidates)\n const target = explicit\n ?? installedProvider\n ?? (recommended ? providers.find(provider => provider.mechanism === recommended) : undefined)\n\n const explicitStatus = explicit ? statuses.get(explicit.mechanism) : undefined\n const installedMechanism = explicit && explicitStatus && isInstalledState(explicitStatus.state)\n ? explicit.mechanism\n : installedProvider?.mechanism ?? null\n\n const common = { platform: kind, mechanism: installedMechanism, recommended, candidates }\n if (!target) {\n return {\n ...common,\n state: 'unsupported',\n bootCapable: false,\n privileged: false,\n unitPath: null,\n commands: [],\n detail: `no boot mechanism is available on ${platform}`,\n }\n }\n\n const status = statuses.get(target.mechanism)\n const candidate = byMechanism.get(target.mechanism)\n const alsoInstalled = providers\n .filter(provider => provider.mechanism !== target.mechanism && isInstalledState(statuses.get(provider.mechanism)?.state ?? 'unsupported'))\n .map(provider => provider.mechanism)\n return {\n ...common,\n state: status?.state ?? 'unsupported',\n bootCapable: candidate?.bootCapable ?? false,\n privileged: candidate?.privileged ?? false,\n unitPath: status?.unitPath ?? null,\n commands: status?.commands ?? [],\n detail: `${status?.detail ?? 'no detail'}${alsoInstalled.length ? `; also installed: ${alsoInstalled.join(', ')}` : ''}`,\n }\n }\n\n async function safeInstall(provider: BootProvider, spec: BootSpec): Promise<BootActionResult> {\n try {\n return await provider.install(spec)\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n }\n\n async function safeUninstall(provider: BootProvider, spec: BootSpec): Promise<BootActionResult> {\n try {\n return await provider.uninstall(spec)\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n }\n\n async function safeActivate(provider: BootProvider, spec: BootSpec): Promise<BootStart | null> {\n if (provider.activate === undefined)\n return null\n try {\n return await provider.activate(spec)\n }\n catch {\n return null\n }\n }\n\n async function safeRetirement(provider: BootProvider, spec: BootSpec): Promise<BootRetirement | null> {\n if (provider.retireCommands === undefined)\n return null\n try {\n return await provider.retireCommands(spec)\n }\n catch {\n return null\n }\n }\n\n async function safeStop(provider: BootProvider, spec: BootSpec): Promise<string[][]> {\n if (provider.stopCommands === undefined)\n return []\n try {\n return await provider.stopCommands(spec)\n }\n catch {\n return []\n }\n }\n\n /** Every mechanism the OS currently has an entry for, whatever it is doing. */\n async function installedMechanisms(spec: BootSpec): Promise<BootProvider[]> {\n const probes = await Promise.all(providers.map(async provider => [provider, await safeStatus(provider, spec)] as const))\n return probes.filter(([, status]) => isInstalledState(status.state)).map(([provider]) => provider)\n }\n\n /**\n * Which mechanism a call means. An install may fall back to the\n * recommendation; every other call means one the OS actually has an entry\n * for, because uninstalling or starting a mechanism that was never installed\n * cannot succeed and must not be invented.\n */\n function pick(status: BootStatus, mechanism?: BootMechanism): BootProvider | undefined {\n return pickOf(mechanism ?? status.mechanism)\n }\n\n function pickOf(mechanism: BootMechanism | null | undefined): BootProvider | undefined {\n return mechanism === null || mechanism === undefined\n ? undefined\n : providers.find(provider => provider.mechanism === mechanism)\n }\n\n return {\n providers,\n\n detect: detectAll,\n\n status: buildStatus,\n\n async install(spec: BootSpec, mechanism?: BootMechanism) {\n const before = await buildStatus(spec, mechanism)\n const target = pickOf(mechanism ?? before.mechanism ?? before.recommended)\n if (!target) {\n return {\n ...failed(`no boot mechanism is available on ${platform}`),\n mechanism: null,\n status: before,\n }\n }\n\n // Retiring the mechanism we came from is *not* done here: its entry is the\n // one currently running the panel (and often this plugin), so removing it\n // first would stop everything before the new entry was told to start.\n // `activate()` carries that retirement, for the caller that stops the panel.\n const result = await safeInstall(target, spec)\n const status = await buildStatus(spec, mechanism)\n return {\n ...result,\n mechanism: result.ok ? target.mechanism : status.mechanism,\n status,\n }\n },\n\n async uninstall(spec: BootSpec, mechanism?: BootMechanism) {\n const before = await buildStatus(spec, mechanism)\n const target = pick(before, mechanism)\n if (!target) {\n return {\n ok: true,\n changed: false,\n detail: 'no OS boot entry is installed for this plugin, so there is nothing to uninstall',\n commands: [],\n needsPrivilege: false,\n status: before,\n }\n }\n const result = await safeUninstall(target, spec)\n const status = await buildStatus(spec, mechanism)\n return { ...result, status }\n },\n\n /**\n * The start steps for the installed mechanism, plus the retirement of every\n * other entry on this machine.\n *\n * A switch is the whole reason this exists: the old entry has to go, and it\n * can only go after the panel is down \u2014 retiring it first would stop the\n * panel, and the plugin with it, before the new entry was ever told to\n * start. So both halves are handed to one caller that runs them in order,\n * detached. Nothing is stopped here.\n */\n async activate(spec: BootSpec, mechanism?: BootMechanism): Promise<BootActivation | null> {\n const status = await buildStatus(spec, mechanism)\n const target = pick(status, mechanism)\n if (!target)\n return null\n const start = await safeActivate(target, spec)\n if (start === null)\n return null\n\n const others = (await installedMechanisms(spec))\n .filter(provider => provider.mechanism !== target.mechanism)\n const retirements = await Promise.all(others.map(async provider => [provider, await safeRetirement(provider, spec)] as const))\n const usable = retirements.filter((pair): pair is [BootProvider, BootRetirement] => pair[1] !== null)\n\n // The mechanism we are leaving has to stop the panel itself: its own\n // restart policy would bring the panel back the moment the CLI's `down`\n // returns, racing the entry that is about to start one.\n const supervised = status.mechanism !== null\n ? providers.find(provider => provider.mechanism === status.mechanism)\n : others.find(provider => provider.mechanism !== target.mechanism)\n const stop = supervised === undefined ? [] : await safeStop(supervised, spec)\n\n return {\n ...start,\n stop,\n // A mechanism whose entry cannot be removed without stopping the panel is\n // left alone here: one stale entry is better than a panel nothing starts.\n retire: usable.flatMap(([, retirement]) => retirement.commands),\n retired: usable.map(([provider]) => provider.mechanism),\n retireDisplay: usable.flatMap(([, retirement]) => retirement.display),\n }\n },\n }\n}\n", "/**\n * The only place this plugin starts a process.\n *\n * Every call is an argv array with no shell, so a path or a value can never\n * become a command. Callers pass absolute programs; nothing here resolves\n * through a shell profile.\n */\nimport { spawn } from 'node:child_process'\n\nexport interface RunOptions {\n cwd?: string\n env?: Record<string, string | undefined>\n timeoutMs?: number\n /** Cap on each captured stream; a chatty command is truncated, not buffered forever. */\n maxBytes?: number\n}\n\nexport interface RunResult {\n command: string\n args: string[]\n code: number | null\n signal: NodeJS.Signals | null\n stdout: string\n stderr: string\n timedOut: boolean\n error: string | null\n}\n\n/** Run a program and always resolve; a non-zero code is data, not a throw. */\nexport async function run(command: string, args: string[] = [], options: RunOptions = {}): Promise<RunResult> {\n const maxBytes = options.maxBytes ?? 256 * 1024\n const timeoutMs = options.timeoutMs ?? 20_000\n\n return await new Promise<RunResult>((resolve) => {\n let settled = false\n let stdout = ''\n let stderr = ''\n let timedOut = false\n\n const finish = (result: Partial<RunResult>): void => {\n if (settled)\n return\n settled = true\n resolve({\n command,\n args,\n code: result.code ?? null,\n signal: result.signal ?? null,\n stdout: result.stdout ?? stdout,\n stderr: result.stderr ?? stderr,\n timedOut,\n error: result.error ?? null,\n })\n }\n\n let child\n try {\n child = spawn(command, args, {\n cwd: options.cwd,\n env: { ...process.env, ...options.env },\n shell: false,\n windowsHide: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n }\n catch (error) {\n finish({ error: error instanceof Error ? error.message : String(error) })\n return\n }\n\n const timer = setTimeout(() => {\n timedOut = true\n child.kill('SIGKILL')\n }, timeoutMs)\n\n child.stdout?.on('data', (chunk: Buffer) => {\n if (stdout.length < maxBytes)\n stdout += chunk.toString('utf8').slice(0, maxBytes - stdout.length)\n })\n child.stderr?.on('data', (chunk: Buffer) => {\n if (stderr.length < maxBytes)\n stderr += chunk.toString('utf8').slice(0, maxBytes - stderr.length)\n })\n\n child.on('error', (error) => {\n clearTimeout(timer)\n finish({ error: error.message })\n })\n child.on('close', (code, signal) => {\n clearTimeout(timer)\n finish({ code, signal })\n })\n })\n}\n\n/** Run a program and fail loudly on anything but success. */\nexport async function runOk(command: string, args: string[] = [], options: RunOptions = {}): Promise<RunResult> {\n const result = await run(command, args, options)\n if (result.error !== null)\n throw new Error(`${command} failed: ${result.error}`)\n if (result.code !== 0)\n throw new Error(`${command} exited ${String(result.code)}: ${result.stderr.trim() || result.stdout.trim()}`)\n return result\n}\n\n/** True when the program exists and answers to `--version` or similar. */\nexport async function hasCommand(command: string): Promise<boolean> {\n const probe = process.platform === 'win32' ? 'where.exe' : 'which'\n const result = await run(probe, [command], { timeoutMs: 5000 })\n return result.code === 0\n}\n\n/**\n * Whether this process can run one privileged command with no prompt.\n * `sudo -n` never asks for a password, so a failure is a definitive \"not\n * available\" rather than something to retry interactively.\n */\nexport async function sudoAvailable(): Promise<boolean> {\n if (process.getuid?.() === 0)\n return true\n const result = await run('sudo', ['-n', 'true'], { timeoutMs: 5000 })\n return result.code === 0\n}\n", "/**\n * systemd: a user unit (`~/.config/systemd/user`) and a system unit\n * (`/etc/systemd/system`). The system one needs root, so every privileged call\n * goes through `sudo -n` \u2014 it never prompts \u2014 and the copy-pasteable `sudo \u2026`\n * form is reported when elevation is not available.\n */\nimport fs from 'node:fs'\nimport type { BootCandidate, BootState } from '../shared/contracts.js'\nimport type { BootActionResult, BootProvider, BootProviderContext, BootProviderStatus, BootRetirement, BootSpec, BootStart } from './types.js'\nimport {\n assertAbsolute,\n assertUserName,\n assertArg,\n assertEnvKey,\n assertLabel,\n assertMarker,\n assertUnitName,\n shellCommand,\n systemdEnvLine,\n systemdExecWord,\n systemdPath,\n systemdText,\n} from './escape.js'\nimport type { FileOwnership } from './common.js'\nimport {\n accountOf,\n bootState,\n bootUserName,\n configHome,\n currentUser,\n errorMessage,\n existsOf,\n failed,\n inspectOwned,\n posixJoin,\n removeOwned,\n tempDir,\n writeOwned,\n} from './common.js'\n\nconst ENABLED_UNIT_STATES = new Set(['enabled', 'enabled-runtime', 'alias', 'static', 'indirect', 'generated', 'transient'])\nconst FAILED_RESULTS = new Set(['failed', 'exit-code', 'signal', 'timeout', 'core-dump', 'watchdog', 'start-limit-hit', 'oom-kill'])\n\ntype Scope = 'user' | 'system'\n\nfunction validate(spec: BootSpec): void {\n assertUnitName(spec.unitName)\n assertMarker(spec.marker)\n assertLabel(spec.label)\n assertAbsolute(spec.command, 'spec.command')\n assertAbsolute(spec.cwd, 'spec.cwd')\n assertAbsolute(spec.logDir, 'spec.logDir')\n for (const arg of spec.args)\n assertArg(arg)\n for (const key of Object.keys(spec.env))\n assertEnvKey(key)\n}\n\nfunction envLines(spec: BootSpec): string[] {\n return Object.entries(spec.env).map(([key, value]) => `Environment=${systemdEnvLine(key, value)}`)\n}\n\nconst MANAGED = (spec: BootSpec): string => `# Managed by ${spec.marker}`\n\nfunction codeOf(result: { code: number | null, error?: string | null }): number | null {\n return result.error ? null : result.code\n}\n\n/**\n * What a status read says about the account the installed unit runs as.\n *\n * A unit this plugin wrote that names no `User=` runs the panel as root, and one\n * that names somebody else is running a panel that is not that person's \u2014 either\n * way a server which refuses root crash-loops with no hint that the panel's own\n * user is the cause. So the account is stated where the operator is already\n * looking, and the remedy is the install button already on the page.\n */\nfunction accountNote(own: FileOwnership, ctx: BootProviderContext): string {\n if (!own.owned || own.text === null)\n return ''\n // Only the root outcome is reachable: any other name in a unit this plugin wrote\n // came from this same resolver, so it is the name the resolver would pick now.\n const declared = /^User=(.*)$/m.exec(own.text)?.[1]?.trim() ?? ''\n return declared === '' || declared === 'root'\n ? '; the unit names no other user, so the panel runs as root \u2014 install again from the account that should own it'\n : ''\n}\n\n/** `Type=exec` with an explicit environment; boot capability comes from linger, not from this file. */\nexport function systemdUserUnit(spec: BootSpec): string {\n validate(spec)\n return `${[\n MANAGED(spec),\n '[Unit]',\n `Description=${systemdText(spec.label)}`,\n 'After=network-online.target',\n '',\n '[Service]',\n 'Type=exec',\n `ExecStart=${[spec.command, ...spec.args].map(systemdExecWord).join(' ')}`,\n `WorkingDirectory=${systemdPath(spec.cwd, 'spec.cwd')}`,\n // A persistent entry survives a panel restart only because its nanny is not\n // the panel. The nanny is spawned `detached`, which gives it its own session \u2014\n // but *not* its own cgroup, so the default `KillMode=control-group` kills it\n // with the panel and takes the entry down too, quietly falsifying what\n // `persistent: true` promises. `process` signals the panel alone. (An ordinary\n // entry is spawned detached as well, so this keeps every supervised server, not\n // only the nannies \u2014 the panel re-adopts such a survivor by its port.)\n 'KillMode=process',\n 'Restart=always',\n 'RestartSec=5',\n ...envLines(spec),\n '',\n '[Install]',\n 'WantedBy=default.target',\n '',\n ].join('\\n')}`\n}\n\n/**\n * The system unit. `StartLimit*` belongs in `[Unit]`, not `[Service]`.\n *\n * `User=` is written when the entry has a real account to run as, and *never* as\n * `root`: an entry with no `User=` already runs as root, so naming it changes\n * nothing except to make the silent default look deliberate. A root account is\n * therefore refused loudly (by {@link bootUserName}) and the directive omitted.\n */\nexport function systemdSystemUnit(spec: BootSpec, user: string | null = null): string {\n validate(spec)\n return `${[\n MANAGED(spec),\n '[Unit]',\n `Description=${systemdText(spec.label)}`,\n 'After=network-online.target',\n 'StartLimitIntervalSec=60',\n 'StartLimitBurst=5',\n '',\n '[Service]',\n ...(user === null || user === '' || user === 'root' ? [] : [`User=${assertUserName(user)}`]),\n 'Type=exec',\n `ExecStart=${[spec.command, ...spec.args].map(systemdExecWord).join(' ')}`,\n `WorkingDirectory=${systemdPath(spec.cwd, 'spec.cwd')}`,\n // See the user unit: `control-group` would kill the nannies too.\n 'KillMode=process',\n 'Restart=always',\n 'RestartSec=5',\n ...envLines(spec),\n '',\n '[Install]',\n 'WantedBy=multi-user.target',\n '',\n ].join('\\n')}`\n}\n\nfunction parseKeyValues(stdout: string): Record<string, string> {\n const out: Record<string, string> = {}\n for (const line of stdout.split('\\n')) {\n const eq = line.indexOf('=')\n if (eq > 0)\n out[line.slice(0, eq).trim()] = line.slice(eq + 1).trim()\n }\n return out\n}\n\ninterface UnitProbe {\n unitFileState: string\n activeState: string\n result: string\n nRestarts: string\n enabled: boolean\n installed: boolean\n reachable: boolean\n detail: string\n}\n\nasync function readUnit(ctx: BootProviderContext, unit: string, scope: Scope): Promise<UnitProbe> {\n const scopeArgs = scope === 'user' ? ['--user'] : []\n const show = await ctx.run('systemctl', [...scopeArgs, 'show', '-p', 'UnitFileState', '-p', 'ActiveState', '-p', 'Result', '-p', 'NRestarts', unit])\n const props = parseKeyValues(show.stdout)\n const isEnabled = await ctx.run('systemctl', [...scopeArgs, 'is-enabled', unit])\n const enabledCode = codeOf(isEnabled)\n const unitFileState = props.UnitFileState ?? ''\n /**\n * `is-enabled` exits 1 both for a disabled unit and for a unit it cannot even\n * look up, so only a known `UnitFileState` (or an explicit exit 0) proves the\n * unit exists. A user-bus failure must not read as `installed-disabled`.\n */\n const knownState = unitFileState !== '' && unitFileState !== 'not-found'\n const enabled = enabledCode === 0 || ENABLED_UNIT_STATES.has(unitFileState)\n const installed = enabled || knownState\n const result = props.Result ?? ''\n return {\n unitFileState,\n activeState: props.ActiveState ?? '',\n result,\n nRestarts: props.NRestarts ?? '',\n enabled,\n installed,\n reachable: show.error === undefined || show.error === null,\n detail: `UnitFileState=${unitFileState || '(none)'} ActiveState=${props.ActiveState || '(none)'} Result=${result || '(none)'} NRestarts=${props.NRestarts ?? '0'}`,\n }\n}\n\nasync function userScopeReachable(ctx: BootProviderContext): Promise<{ reachable: boolean, reason: string }> {\n const probe = await ctx.run('systemctl', ['--user', 'is-system-running'])\n if (probe.error)\n return { reachable: false, reason: `systemctl --user is not available here (${probe.error})` }\n if (`${probe.stdout}\\n${probe.stderr}`.includes('Failed to connect to user scope bus'))\n return { reachable: false, reason: 'systemctl --user cannot reach a user manager: Failed to connect to user scope bus' }\n return { reachable: true, reason: '' }\n}\n\nasync function lingerEnabled(ctx: BootProviderContext): Promise<boolean> {\n const user = currentUser(ctx)\n if (!user)\n return false\n const probe = await ctx.run('loginctl', ['show-user', user, '--property=Linger'])\n if (codeOf(probe) !== 0)\n return false\n return /(^|\\n)Linger=yes(\\n|$)/.test(probe.stdout)\n}\n\nfunction problem(run: { code: number | null, error?: string | null, stdout: string, stderr: string }, command: string): string | null {\n const code = codeOf(run)\n if (code === 0)\n return null\n if (code === null)\n return run.error ? `${command}: ${run.error}` : `${command}: no exit code`\n return `${command} exited ${code}: ${(run.stderr || run.stdout).trim() || '(no output)'}`\n}\n\nexport function createSystemdUserProvider(ctx: BootProviderContext): BootProvider {\n const mechanism = 'systemd-user' as const\n const unitOf = (spec: BootSpec): string => `${assertUnitName(spec.unitName)}.service`\n const pathOf = (spec: BootSpec): string => posixJoin(configHome(ctx), 'systemd', 'user', unitOf(spec))\n\n return {\n mechanism,\n\n async detect(): Promise<BootCandidate> {\n if (ctx.platform !== 'linux')\n return { mechanism, available: false, bootCapable: false, privileged: false, reason: 'systemd is Linux-only' }\n const bus = await userScopeReachable(ctx)\n if (!bus.reachable)\n return { mechanism, available: false, bootCapable: false, privileged: false, reason: bus.reason }\n const linger = await lingerEnabled(ctx)\n const privileged = linger || (await ctx.sudo())\n return {\n mechanism,\n available: true,\n bootCapable: linger,\n privileged,\n reason: linger\n ? 'a systemd user manager is reachable and linger is on, so the unit starts at boot'\n : 'a systemd user manager is reachable, but linger is off: the unit starts at login until `loginctl enable-linger` succeeds',\n }\n },\n\n async status(spec: BootSpec): Promise<BootProviderStatus> {\n try {\n const unit = unitOf(spec)\n const file = pathOf(spec)\n const own = inspectOwned(file, assertMarker(spec.marker))\n if (own.exists && !own.owned)\n return { state: 'not-installed', unitPath: file, detail: own.reason ?? 'foreign file', commands: [] }\n const probe = await readUnit(ctx, unit, 'user')\n const installed = own.owned || probe.installed\n const state: BootState = bootState(installed, probe.enabled, FAILED_RESULTS.has(probe.result))\n const user = currentUser(ctx)\n const linger = await lingerEnabled(ctx)\n const commands: string[] = []\n if (!linger && user)\n commands.push(shellCommand('sudo', ['loginctl', 'enable-linger', user]))\n const detail = `${probe.detail}; ${linger ? 'linger is on (boot-capable)' : 'linger is off (login-scoped)'}`\n return { state, unitPath: file, detail, commands }\n }\n catch (error) {\n return { state: 'not-installed', unitPath: null, detail: errorMessage(error), commands: [] }\n }\n },\n\n async install(spec: BootSpec): Promise<BootActionResult> {\n try {\n validate(spec)\n const unit = unitOf(spec)\n const file = pathOf(spec)\n const write = writeOwned(file, spec.marker, systemdUserUnit(spec), 0o644)\n if (write.refusal)\n return failed(write.refusal)\n\n const before = await readUnit(ctx, unit, 'user')\n let changed = write.changed\n if (write.changed || !before.enabled) {\n const reloadCommand = shellCommand('systemctl', ['--user', 'daemon-reload'])\n const reload = await ctx.run('systemctl', ['--user', 'daemon-reload'])\n const reloadProblem = problem(reload, reloadCommand)\n if (reloadProblem)\n return failed(reloadProblem, { changed, commands: [reloadCommand] })\n\n const enableCommand = shellCommand('systemctl', ['--user', 'enable', '--now', unit])\n const enable = await ctx.run('systemctl', ['--user', 'enable', '--now', unit])\n const enableProblem = problem(enable, enableCommand)\n if (enableProblem)\n return failed(enableProblem, { changed: true, commands: [enableCommand] })\n changed = true\n }\n\n const after = await readUnit(ctx, unit, 'user')\n if (!after.enabled)\n return failed(`unit ${unit} is not enabled after install (${after.detail})`, { changed })\n\n let linger = await lingerEnabled(ctx)\n const user = currentUser(ctx)\n const commands: string[] = []\n if (!linger && user) {\n const res = await ctx.run('loginctl', ['enable-linger', user])\n if (codeOf(res) === 0) {\n linger = true\n changed = true\n }\n else {\n commands.push(shellCommand('sudo', ['loginctl', 'enable-linger', user]))\n }\n }\n const detail = linger\n ? `${file} installed and enabled; linger is on, so ${unit} starts at boot`\n : `${file} installed and enabled, but linger is off: ${unit} starts at login only. ${commands.length ? `Run \\`${commands[0]}\\` to make it boot-capable.` : 'No target user could be determined to enable linger for.'}`\n return { ok: true, changed, detail, commands, needsPrivilege: !linger }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n async uninstall(spec: BootSpec): Promise<BootActionResult> {\n try {\n validate(spec)\n const unit = unitOf(spec)\n const file = pathOf(spec)\n const own = inspectOwned(file, spec.marker)\n if (own.exists && !own.owned)\n return failed(own.reason ?? 'foreign file')\n\n const before = await readUnit(ctx, unit, 'user')\n if (!before.reachable && !own.exists) {\n return { ok: true, changed: false, detail: `no unit file at ${file} and systemctl is unavailable; nothing to remove`, commands: [], needsPrivilege: false }\n }\n // A unit of the same name can come from anywhere on the search path; with\n // no file of ours, disabling by name would stop somebody else's unit.\n if (!own.owned && !own.exists && before.installed)\n return failed(`a unit named ${unit} exists but no unit file of ours is at ${file}; refusing to disable a unit this plugin did not install`)\n if (own.owned && before.reachable) {\n const disableCommand = shellCommand('systemctl', ['--user', 'disable', '--now', unit])\n const disable = await ctx.run('systemctl', ['--user', 'disable', '--now', unit])\n const disableCode = codeOf(disable)\n if (disableCode !== 0 && disableCode !== 1 && disableCode !== 4)\n return failed(problem(disable, disableCommand) ?? 'disable failed', { commands: [disableCommand] })\n }\n\n const remove = removeOwned(file, spec.marker)\n if (remove.refusal)\n return failed(remove.refusal)\n\n let changed = remove.removed\n if (before.installed || remove.removed) {\n await ctx.run('systemctl', ['--user', 'daemon-reload'])\n changed = true\n }\n\n const after = await readUnit(ctx, unit, 'user')\n if (after.installed)\n return failed(`unit ${unit} is still known to systemd after removal (${after.detail})`, { changed })\n\n return {\n ok: true,\n changed,\n detail: remove.removed ? `disabled and removed ${file}` : `${file} was already gone`,\n commands: [],\n needsPrivilege: false,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n /**\n * `restart` stops the panel and starts it again under the unit, so nothing\n * else may stop it first: stopping it from outside would race this call.\n * `is-active` is the proof the unit is really there, before anything is\n * killed.\n */\n async activate(spec: BootSpec): Promise<BootStart> {\n const unit = unitOf(spec)\n return {\n commands: [['systemctl', '--user', 'restart', unit]],\n requires: { commands: [['systemctl', '--user', 'is-active', unit]], files: [] },\n display: [shellCommand('systemctl', ['--user', 'restart', unit])],\n }\n },\n\n /** Stops the panel the unit supervises, keeping the unit enabled. */\n async stopCommands(spec: BootSpec): Promise<string[][]> {\n return [['systemctl', '--user', 'stop', unitOf(spec)]]\n },\n\n /** No `--now`: the panel is already down when a switch runs this. */\n async retireCommands(spec: BootSpec): Promise<BootRetirement> {\n const unit = unitOf(spec)\n const file = pathOf(spec)\n return {\n commands: [\n ['systemctl', '--user', 'disable', unit],\n ['rm', '-f', file],\n ['systemctl', '--user', 'daemon-reload'],\n ],\n display: [\n shellCommand('systemctl', ['--user', 'disable', unit]),\n shellCommand('rm', ['-f', file]),\n shellCommand('systemctl', ['--user', 'daemon-reload']),\n ],\n }\n },\n }\n}\n\nfunction stageUnit(ctx: BootProviderContext, unit: string, content: string): string {\n const staged = posixJoin(tempDir(ctx), `home-hosted-${unit}`)\n fs.writeFileSync(staged, content, { mode: 0o644 })\n return staged\n}\n\nexport function createSystemdSystemProvider(ctx: BootProviderContext): BootProvider {\n const mechanism = 'systemd-system' as const\n const unitOf = (spec: BootSpec): string => `${assertUnitName(spec.unitName)}.service`\n const pathOf = (spec: BootSpec): string => posixJoin('/etc', 'systemd', 'system', unitOf(spec))\n /**\n * The account the entry runs as \u2014 never this process's `$USER`.\n *\n * A system unit outlives the shell that wrote it and is restarted by root, so\n * the only honest answer is the login that asked for root (`SUDO_UID`) or, when\n * the panel itself is not root, the account it belongs to. A root answer is not\n * written as `User=root`: omitting the directive *is* running as root, so\n * naming it only disguises the default this exists to surface.\n */\n const contentOf = (spec: BootSpec): string => systemdSystemUnit(spec, bootUserName(ctx))\n\n const installCommands = (staged: string, file: string, unit: string): string[] => [\n shellCommand('sudo', ['install', '-m', '0644', staged, file]),\n shellCommand('sudo', ['systemctl', 'daemon-reload']),\n shellCommand('sudo', ['systemctl', 'enable', '--now', unit]),\n ]\n\n return {\n mechanism,\n\n async detect(): Promise<BootCandidate> {\n if (ctx.platform !== 'linux')\n return { mechanism, available: false, bootCapable: false, privileged: false, reason: 'systemd is Linux-only' }\n if (!existsOf(ctx, '/run/systemd/system'))\n return { mechanism, available: false, bootCapable: false, privileged: false, reason: 'systemd is not the init system here (no /run/systemd/system)' }\n const privileged = ctx.isRoot || (await ctx.sudo())\n // The account the entry would run as is a property of the candidate, not a\n // detail: a system unit with no account runs the panel as root, and that is\n // the whole reason a server that refuses root crash-loops.\n const who = accountOf(ctx)\n const scope = who === null || who.root\n ? 'no account was resolved for the entry, so it would run the panel as root'\n : `the entry runs the panel as ${who.name}`\n return {\n mechanism,\n available: privileged,\n bootCapable: privileged,\n privileged,\n reason: privileged\n ? `systemd is PID 1 and this process can use root, so a system unit starts at boot; ${scope}`\n : 'systemd is PID 1, but installing a system unit needs root or passwordless sudo',\n }\n },\n\n async status(spec: BootSpec): Promise<BootProviderStatus> {\n try {\n const unit = unitOf(spec)\n const file = pathOf(spec)\n const own = inspectOwned(file, assertMarker(spec.marker))\n if (own.exists && !own.owned)\n return { state: 'not-installed', unitPath: file, detail: own.reason ?? 'foreign file', commands: [] }\n const probe = await readUnit(ctx, unit, 'system')\n const installed = own.owned || probe.installed\n const state: BootState = bootState(installed, probe.enabled, FAILED_RESULTS.has(probe.result))\n const privileged = ctx.isRoot || (await ctx.sudo())\n const commands: string[] = []\n if (installed && !probe.enabled && !privileged)\n commands.push(shellCommand('sudo', ['systemctl', 'enable', '--now', unit]))\n return {\n state,\n unitPath: file,\n detail: `${probe.detail}${privileged ? '' : '; needs root to change'}${accountNote(own, ctx)}`,\n commands,\n }\n }\n catch (error) {\n return { state: 'not-installed', unitPath: null, detail: errorMessage(error), commands: [] }\n }\n },\n\n async install(spec: BootSpec): Promise<BootActionResult> {\n try {\n validate(spec)\n const unit = unitOf(spec)\n const file = pathOf(spec)\n const content = contentOf(spec)\n const own = inspectOwned(file, spec.marker)\n if (own.exists && !own.owned)\n return failed(own.reason ?? 'foreign file')\n\n const before = await readUnit(ctx, unit, 'system')\n const changed = !own.exists || own.text !== content\n\n if (!ctx.isRoot && !(await ctx.sudo())) {\n const staged = stageUnit(ctx, unit, content)\n return failed(`installing a system unit needs root; the unit is staged at ${staged}`, {\n changed: false,\n needsPrivilege: true,\n commands: installCommands(staged, file, unit),\n })\n }\n\n if (ctx.isRoot) {\n const write = writeOwned(file, spec.marker, content, 0o644)\n if (write.refusal)\n return failed(write.refusal)\n }\n else {\n const staged = stageUnit(ctx, unit, content)\n const installCommand = shellCommand('sudo', ['install', '-m', '0644', staged, file])\n const install = await ctx.run('sudo', ['-n', 'install', '-m', '0644', staged, file])\n const installProblem = problem(install, installCommand)\n if (installProblem)\n return failed(installProblem, { needsPrivilege: true, commands: installCommands(staged, file, unit) })\n fs.rmSync(staged, { force: true })\n }\n\n if (changed || !before.enabled) {\n const sudoPrefix = ctx.isRoot ? [] : ['-n']\n const reloadCommand = shellCommand('sudo', ['systemctl', 'daemon-reload'])\n const reload = await ctx.run('sudo', [...sudoPrefix, 'systemctl', 'daemon-reload'])\n const reloadProblem = problem(reload, reloadCommand)\n if (reloadProblem)\n return failed(reloadProblem, { changed, needsPrivilege: !ctx.isRoot, commands: [reloadCommand] })\n\n const enableCommand = shellCommand('sudo', ['systemctl', 'enable', '--now', unit])\n const enable = await ctx.run('sudo', [...sudoPrefix, 'systemctl', 'enable', '--now', unit])\n const enableProblem = problem(enable, enableCommand)\n if (enableProblem)\n return failed(enableProblem, { changed: true, needsPrivilege: !ctx.isRoot, commands: [enableCommand] })\n }\n\n const after = await readUnit(ctx, unit, 'system')\n if (!after.enabled)\n return failed(`unit ${unit} is not enabled after install (${after.detail})`, { changed })\n\n return {\n ok: true,\n changed: changed || !before.enabled,\n detail: `${file} installed and enabled as a system unit (${after.detail})`,\n commands: [],\n needsPrivilege: !ctx.isRoot,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n async uninstall(spec: BootSpec): Promise<BootActionResult> {\n try {\n const unit = unitOf(spec)\n const file = pathOf(spec)\n const own = inspectOwned(file, spec.marker)\n if (own.exists && !own.owned)\n return failed(own.reason ?? 'foreign file')\n\n const privileged = ctx.isRoot || (await ctx.sudo())\n if (!privileged) {\n if (!own.exists)\n return { ok: true, changed: false, detail: `no unit file at ${file} and no privilege to change systemd; nothing to remove`, commands: [], needsPrivilege: false }\n return failed('removing a system unit needs root', {\n needsPrivilege: true,\n commands: [\n shellCommand('sudo', ['systemctl', 'disable', '--now', unit]),\n shellCommand('sudo', ['rm', '-f', file]),\n shellCommand('sudo', ['systemctl', 'daemon-reload']),\n ],\n })\n }\n\n const prefix = ctx.isRoot ? [] : ['-n']\n // With no file of ours at the path we write, a same-named unit belongs to\n // somebody else (a vendor unit in /usr/lib/systemd/system): leave it alone.\n if (!own.exists && !own.owned)\n return failed(`no unit file of ours at ${file}; refusing to disable a unit this plugin did not install`)\n const disableCommand = shellCommand('sudo', ['systemctl', 'disable', '--now', unit])\n const disable = await ctx.run('sudo', [...prefix, 'systemctl', 'disable', '--now', unit])\n const disableCode = codeOf(disable)\n if (disableCode !== 0 && disableCode !== 1 && disableCode !== 4)\n return failed(problem(disable, disableCommand) ?? 'disable failed', { needsPrivilege: !ctx.isRoot, commands: [disableCommand] })\n\n if (own.exists) {\n if (ctx.isRoot) {\n const remove = removeOwned(file, spec.marker)\n if (remove.refusal)\n return failed(remove.refusal)\n }\n else {\n // The check above ran as the invoking user, which cannot read a\n // root-only file; re-prove ownership before the privileged delete.\n if (!own.owned)\n return failed(own.reason ?? 'foreign file')\n const rmCommand = shellCommand('sudo', ['rm', '-f', file])\n const rm = await ctx.run('sudo', ['-n', 'rm', '-f', file])\n const rmProblem = problem(rm, rmCommand)\n if (rmProblem)\n return failed(rmProblem, { needsPrivilege: true, commands: [rmCommand] })\n }\n }\n\n await ctx.run('sudo', [...prefix, 'systemctl', 'daemon-reload'])\n const after = await readUnit(ctx, unit, 'system')\n if (after.installed)\n return failed(`unit ${unit} is still known to systemd after removal (${after.detail})`, { changed: true, needsPrivilege: !ctx.isRoot })\n\n return {\n ok: true,\n changed: own.exists || disableCode === 0,\n detail: own.exists ? `disabled and removed ${file}` : `${file} was already gone`,\n commands: [],\n needsPrivilege: !ctx.isRoot,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n /**\n * `restart` stops the panel and starts it again under the unit, so nothing\n * else may stop it first. A system unit needs root; `sudo -n` never prompts,\n * which is the only elevation a detached helper can get.\n */\n async activate(spec: BootSpec): Promise<BootStart> {\n const unit = unitOf(spec)\n const elevate = (args: string[]): string[] => ctx.isRoot ? args : ['sudo', '-n', ...args]\n return {\n commands: [elevate(['systemctl', 'restart', unit])],\n requires: { commands: [elevate(['systemctl', 'is-active', unit])], files: [] },\n display: [shellCommand('sudo', ['systemctl', 'restart', unit])],\n }\n },\n\n /**\n * Stops the panel the unit supervises. Without this a switch would lose the\n * race: `Restart=always` brings the panel back as soon as the CLI's `down`\n * ends, before the new entry starts one.\n */\n async stopCommands(spec: BootSpec): Promise<string[][]> {\n const elevate = (args: string[]): string[] => ctx.isRoot ? args : ['sudo', '-n', ...args]\n return [elevate(['systemctl', 'stop', unitOf(spec)])]\n },\n\n /**\n * The retirement for a switch. Deliberately no `--now`: this runs in the\n * helper *after* the panel is already down, and `--now` here would race the\n * start command that follows.\n */\n async retireCommands(spec: BootSpec): Promise<BootRetirement> {\n const unit = unitOf(spec)\n const file = pathOf(spec)\n const elevate = (args: string[]): string[] => ctx.isRoot ? args : ['sudo', '-n', ...args]\n return {\n commands: [\n elevate(['systemctl', 'disable', unit]),\n elevate(['rm', '-f', file]),\n elevate(['systemctl', 'daemon-reload']),\n ],\n display: [\n shellCommand('sudo', ['systemctl', 'disable', unit]),\n shellCommand('sudo', ['rm', '-f', file]),\n shellCommand('sudo', ['systemctl', 'daemon-reload']),\n ],\n }\n },\n }\n}\n", "/**\n * Escaping for every format this plugin writes.\n *\n * None of this builds a shell command: a unit file, a plist, a `.desktop`\n * entry, a task XML and a registry value each have their own parser, and each\n * value is quoted for exactly that parser. Values that carry control\n * characters (newlines included) are rejected outright, because a newline is\n * how a value escapes its field in every one of these formats.\n */\n\nconst CONTROL = /[\\u0000-\\u001F\\u007F]/\n\nexport function assertNoControl(value: string, what: string): string {\n if (CONTROL.test(value))\n throw new Error(`${what} must not contain control characters or newlines`)\n return value\n}\n\nexport function assertUnitName(name: string): string {\n if (!/^[A-Za-z0-9._@-]+$/.test(name))\n throw new Error(`unit name ${JSON.stringify(name)} must match ^[A-Za-z0-9._@-]+$`)\n if (name.startsWith('-') || name.startsWith('.') || name.includes('..'))\n throw new Error(`unit name ${JSON.stringify(name)} must not be option-like or traverse paths`)\n return name\n}\n\nexport function assertMarker(marker: string): string {\n if (marker.trim() === '')\n throw new Error('marker must not be empty')\n return assertNoControl(marker, 'marker')\n}\n\n/**\n * A user name a unit or a plist may name.\n *\n * It is *not* quoted anywhere: systemd's `User=` and launchd's `UserName` take a\n * bare name, and a value carrying a newline would therefore end the line and let\n * the rest of it become another directive. So this is the one place that decides\n * what an account name may be, and every writer goes through it.\n */\nexport function assertUserName(name: string): string {\n if (!/^[A-Za-z_][A-Za-z0-9._-]*$/.test(name))\n throw new Error(`user name ${JSON.stringify(name)} must match ^[A-Za-z_][A-Za-z0-9._-]*$`)\n return name\n}\n\nexport function assertEnvKey(key: string): string {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))\n throw new Error(`environment key ${JSON.stringify(key)} is not a valid name`)\n return key\n}\n\n/** POSIX-absolute, Windows-drive-absolute or UNC; checked without consulting the host OS. */\nexport function isAbsolutePath(value: string): boolean {\n return value.startsWith('/') || /^[A-Za-z]:[\\\\/]/.test(value) || value.startsWith('\\\\\\\\')\n}\n\nexport function assertAbsolute(value: string, what: string): string {\n if (!isAbsolutePath(value))\n throw new Error(`${what} must be absolute, got ${JSON.stringify(value)}`)\n return assertNoControl(value, what)\n}\n\nexport function assertLabel(value: string): string {\n if (value.trim() === '')\n throw new Error('label must not be empty')\n return assertNoControl(value, 'label')\n}\n\nexport function assertArg(value: string, what = 'argument'): string {\n return assertNoControl(value, what)\n}\n\nfunction replaceAll(value: string, pairs: Array<[string, string]>): string {\n let out = value\n for (const [from, to] of pairs)\n out = out.split(from).join(to)\n return out\n}\n\n/** Single-quote a value the way a POSIX shell would, for `commands[]`. */\nexport function shellQuote(value: string): string {\n if (value !== '' && /^[A-Za-z0-9._/:=@%+,-]+$/.test(value))\n return value\n return `'${value.split('\\'').join('\\'\\\\\\'\\'')}'`\n}\n\n/** The exact line a person can paste into a shell. */\nexport function shellCommand(program: string, args: string[]): string {\n return [program, ...args].map(shellQuote).join(' ')\n}\n\n// ---------------------------------------------------------------------------\n// systemd\n// ---------------------------------------------------------------------------\n\n/**\n * A word in `ExecStart=`/`ExecStop=`. systemd expands `%` specifiers and `$`\n * variables there before it splits the line, so both are pinned, and `;` \u2014 the\n * multi-command separator \u2014 is quoted rather than left bare.\n */\nexport function systemdExecWord(value: string): string {\n assertNoControl(value, 'ExecStart word')\n const escaped = replaceAll(value, [['\\\\', '\\\\\\\\'], ['\"', '\\\\\"'], ['$', '$$'], ['%', '%%']])\n return escaped !== value || /[\\s\"';]/.test(value) ? `\"${escaped}\"` : value\n}\n\n/**\n * A single-line free-text setting such as `Description=` (specifiers only).\n */\nexport function systemdText(value: string): string {\n assertNoControl(value, 'unit setting')\n // A trailing backslash would continue the directive onto the next line.\n return replaceAll(value, [['\\\\', '\\\\\\\\'], ['%', '%%']])\n}\n\n/**\n * A path setting (`WorkingDirectory=`). systemd does not unquote these, but it\n * does expand `%` specifiers (an unknown one is fatal, verified with\n * `systemd-analyze verify`), and a trailing `\\` continues the line into the next\n * directive. So both are escaped rather than passed raw.\n */\nexport function systemdPath(value: string, what = 'path'): string {\n assertAbsolute(value, what)\n assertNoControl(value, what)\n if (value.includes('\"'))\n throw new Error(`${what} must not contain a double quote: ${JSON.stringify(value)}`)\n return replaceAll(value, [['\\\\', '\\\\\\\\'], ['%', '%%']])\n}\n\n/** `Environment=KEY=value`, quoted as one assignment when the value needs it. */\nexport function systemdEnvLine(key: string, value: string): string {\n assertEnvKey(key)\n assertNoControl(value, `environment value for ${key}`)\n const escaped = replaceAll(value, [['\\\\', '\\\\\\\\'], ['\"', '\\\\\"'], ['%', '%%']])\n return escaped !== value || /[\\s\"']/.test(value) ? `\"${key}=${escaped}\"` : `${key}=${value}`\n}\n\n// ---------------------------------------------------------------------------\n// XML (launchd plist, scheduled-task XML)\n// ---------------------------------------------------------------------------\n\nexport function xmlEscape(value: string): string {\n assertNoControl(value, 'xml value')\n return replaceAll(value, [['&', '&amp;'], ['<', '&lt;'], ['>', '&gt;'], ['\"', '&quot;'], ['\\'', '&apos;']])\n}\n\nexport function xmlUnescape(value: string): string {\n return replaceAll(value, [['&lt;', '<'], ['&gt;', '>'], ['&quot;', '\"'], ['&apos;', '\\''], ['&amp;', '&']])\n}\n\n/** A marker inside an XML comment may not contain `--`. */\nexport function assertXmlCommentSafe(value: string): string {\n if (value.includes('--'))\n throw new Error('marker must not contain `--` when it is written inside an XML comment')\n return value\n}\n\n// ---------------------------------------------------------------------------\n// freedesktop .desktop\n// ---------------------------------------------------------------------------\n\nconst DESKTOP_RESERVED = /[\\s\"'\\\\><~|&;$*?#()`]/\n\nexport function desktopWord(value: string): string {\n assertNoControl(value, 'desktop Exec word')\n const escaped = replaceAll(value, [['\\\\', '\\\\\\\\'], ['\"', '\\\\\"'], ['`', '\\\\`'], ['$', '\\\\$'], ['%', '%%']])\n return DESKTOP_RESERVED.test(value) || escaped !== value ? `\"${escaped}\"` : value\n}\n\nexport function desktopExec(program: string, args: string[]): string {\n return [program, ...args].map(desktopWord).join(' ')\n}\n\n/** A `Key=value` line in a `.desktop` file; the marker is written verbatim. */\nexport function desktopValue(key: string, value: string): string {\n assertNoControl(value, `desktop ${key}`)\n if (key.includes('=') || key.includes('\\n'))\n throw new Error(`desktop key ${JSON.stringify(key)} is not valid`)\n return `${key}=${value}`\n}\n\n// ---------------------------------------------------------------------------\n// Windows\n// ---------------------------------------------------------------------------\n\n/** Quote one argv word the way `CreateProcess`/`cmd` parse a command line. */\nexport function windowsArg(value: string): string {\n assertNoControl(value, 'windows argument')\n if (value !== '' && !/[\\s\"]/.test(value))\n return value\n\n let out = '\"'\n let backslashes = 0\n for (const char of value) {\n if (char === '\\\\') {\n backslashes += 1\n continue\n }\n if (char === '\"') {\n out += `${'\\\\'.repeat(backslashes * 2 + 1)}\"`\n backslashes = 0\n continue\n }\n out += `${'\\\\'.repeat(backslashes)}${char}`\n backslashes = 0\n }\n out += `${'\\\\'.repeat(backslashes * 2)}\"`\n return out\n}\n\nexport function windowsCommandLine(program: string, args: string[]): string {\n return [program, ...args].map(windowsArg).join(' ')\n}\n\n/** A line for a `.cmd` wrapper: `%` is a batch metacharacter and is doubled. */\nexport function batchCommandLine(program: string, args: string[]): string {\n return batchEscape(windowsCommandLine(program, args))\n}\n\n/**\n * Make one already-quoted command line safe inside a batch file. `%` is doubled\n * last: the `^` inserted for a metacharacter must not itself gain a caret.\n */\nfunction batchEscape(line: string): string {\n assertNoControl(line, 'batch command line')\n return replaceAll(line, [\n ['^', '^^'],\n ['&', '^&'],\n ['|', '^|'],\n ['<', '^<'],\n ['>', '^>'],\n ['(', '^('],\n [')', '^)'],\n ['%', '%%'],\n ])\n}\n\n/** A single-quoted PowerShell literal. */\nexport function powershellLiteral(value: string): string {\n assertNoControl(value, 'powershell value')\n return `'${value.split('\\'').join('\\'\\'')}'`\n}\n\n/** Quote one word the way `cmd.exe` takes it, for a copy-pasteable `commands[]` entry. */\nexport function cmdQuote(value: string): string {\n if (value !== '' && !/[\\s\"&|<>^]/.test(value))\n return value\n return `\"${value.split('\"').join('\"\"')}\"`\n}\n\nexport function windowsDisplayCommand(program: string, args: string[]): string {\n return [program, ...args].map(cmdQuote).join(' ')\n}\n\n// ---------------------------------------------------------------------------\n// launchd / registry value names\n// ---------------------------------------------------------------------------\n\nexport function assertRegistryValueName(name: string): string {\n if (name.trim() === '')\n throw new Error('registry value name must not be empty')\n return assertNoControl(name, 'registry value name')\n}\n", "/**\n * launchd: a per-user agent (`~/Library/LaunchAgents`) and a system daemon\n * (`/Library/LaunchDaemons`). `launchctl print` is read for its exit code only\n * \u2014 its output is explicitly not API. Loading is `bootstrap`, unloading is\n * `bootout`, and exit 5 from bootstrap means the job is already loaded.\n */\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport type { BootCandidate, BootState } from '../shared/contracts.js'\nimport type { BootActionResult, BootProvider, BootProviderContext, BootProviderStatus, BootRetirement, BootSpec, BootStart } from './types.js'\nimport {\n assertAbsolute,\n assertUserName,\n assertArg,\n assertEnvKey,\n assertLabel,\n assertMarker,\n assertUnitName,\n assertXmlCommentSafe,\n shellCommand,\n xmlEscape,\n} from './escape.js'\nimport {\n bootState,\n bootUserName,\n errorMessage,\n failed,\n inspectOwned,\n posixJoin,\n removeOwned,\n tempDir,\n uidOf,\n writeOwned,\n} from './common.js'\n\n/** Both scopes label with the same reverse-DNS prefix. */\nexport const LAUNCHD_LABEL_PREFIX = 'dev.home-hosted.'\n/** bootstrap on an already-loaded job reports EIO. */\nconst LAUNCHCTL_ALREADY_LOADED = 5\n/** `launchctl print` for a label that is not loaded. */\nconst LAUNCHCTL_NOT_LOADED = 113\n/** `launchctl bootout` for a job that is not there. */\nconst LAUNCHCTL_NO_SUCH_PROCESS = 3\n\nfunction validate(spec: BootSpec): void {\n assertUnitName(spec.unitName)\n assertMarker(spec.marker)\n assertLabel(spec.label)\n assertAbsolute(spec.command, 'spec.command')\n assertAbsolute(spec.cwd, 'spec.cwd')\n assertAbsolute(spec.logDir, 'spec.logDir')\n for (const arg of spec.args)\n assertArg(arg)\n for (const key of Object.keys(spec.env))\n assertEnvKey(key)\n}\n\nexport function launchdLabel(spec: BootSpec): string {\n return `${LAUNCHD_LABEL_PREFIX}${assertUnitName(spec.unitName)}`\n}\n\n/** The full property list. The marker rides in an XML comment, which launchd ignores. */\nexport interface LaunchdPlistOptions {\n /** Set for a LaunchDaemon: launchd would otherwise run the panel as root. */\n userName?: string | null\n}\n\nexport function launchdPlist(spec: BootSpec, options: LaunchdPlistOptions = {}): string {\n validate(spec)\n const label = launchdLabel(spec)\n // XML escaping would keep a hostile name from breaking the plist, but `UserName`\n // also has to be a name launchd can resolve \u2014 so it is validated, not escaped.\n const named = options.userName?.trim()\n const userName = named === undefined || named.length === 0 ? undefined : assertUserName(named)\n const out = path.posix.join(spec.logDir, `${label}.out.log`)\n const err = path.posix.join(spec.logDir, `${label}.err.log`)\n const envKeys = Object.keys(spec.env)\n const lines = [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n '<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">',\n '<plist version=\"1.0\">',\n '<dict>',\n '\\t<key>Label</key>',\n `\\t<string>${xmlEscape(label)}</string>`,\n ...(userName === undefined || userName.length === 0\n ? []\n : ['\\t<key>UserName</key>', `\\t<string>${xmlEscape(userName)}</string>`]),\n '\\t<key>ProgramArguments</key>',\n '\\t<array>',\n ...[spec.command, ...spec.args].map(arg => `\\t\\t<string>${xmlEscape(arg)}</string>`),\n '\\t</array>',\n '\\t<key>RunAtLoad</key>',\n '\\t<true/>',\n '\\t<key>KeepAlive</key>',\n '\\t<dict>',\n '\\t\\t<key>SuccessfulExit</key>',\n '\\t\\t<false/>',\n '\\t</dict>',\n '\\t<key>ThrottleInterval</key>',\n '\\t<integer>10</integer>',\n '\\t<key>WorkingDirectory</key>',\n `\\t<string>${xmlEscape(spec.cwd)}</string>`,\n ...(envKeys.length\n ? [\n '\\t<key>EnvironmentVariables</key>',\n '\\t<dict>',\n ...envKeys.flatMap(key => [`\\t\\t<key>${xmlEscape(key)}</key>`, `\\t\\t<string>${xmlEscape(spec.env[key] ?? '')}</string>`]),\n '\\t</dict>',\n ]\n : []),\n '\\t<key>StandardOutPath</key>',\n `\\t<string>${xmlEscape(out)}</string>`,\n '\\t<key>StandardErrorPath</key>',\n `\\t<string>${xmlEscape(err)}</string>`,\n `\\t<!-- Managed by ${assertXmlCommentSafe(spec.marker)} -->`,\n '</dict>',\n '</plist>',\n '',\n ]\n return lines.join('\\n')\n}\n\nfunction codeOf(result: { code: number | null, error?: string | null }): number | null {\n return result.error ? null : result.code\n}\n\nfunction problem(run: { code: number | null, error?: string | null, stdout: string, stderr: string }, command: string): string | null {\n const code = codeOf(run)\n if (code === 0)\n return null\n if (code === null)\n return run.error ? `${command}: ${run.error}` : `${command}: no exit code`\n return `${command} exited ${code}: ${(run.stderr || run.stdout).trim() || '(no output)'}`\n}\n\ntype LaunchdMode = 'agent' | 'daemon'\n\n/** Best effort: `/Library/LaunchDaemons` exists on macOS, and only the privileged write can create it. */\nfunction ensureDirQuiet(dir: string): void {\n try {\n fs.mkdirSync(dir, { recursive: true })\n }\n catch {\n // the privileged install reports the real failure\n }\n}\n\nfunction createLaunchdProvider(ctx: BootProviderContext, mode: LaunchdMode): BootProvider {\n const mechanism = mode === 'agent' ? 'launchd-agent' as const : 'launchd-daemon' as const\n const pathOf = (spec: BootSpec): string => mode === 'agent'\n ? posixJoin(ctx.home, 'Library', 'LaunchAgents', `${launchdLabel(spec)}.plist`)\n : posixJoin('/Library', 'LaunchDaemons', `${launchdLabel(spec)}.plist`)\n\n /** `system` for the daemon; `gui/$UID`, else `user/$UID` for the agent. */\n const domainOf = async (): Promise<string | null> => {\n if (mode === 'daemon')\n return 'system'\n const uid = uidOf(ctx)\n if (!uid)\n return null\n const gui = `gui/${uid}`\n if (codeOf(await ctx.run('launchctl', ['print', gui])) === 0)\n return gui\n const user = `user/${uid}`\n if (codeOf(await ctx.run('launchctl', ['print', user])) === 0)\n return user\n return null\n }\n\n const privileged = async (): Promise<boolean> =>\n mode === 'agent' ? true : ctx.isRoot || (await ctx.sudo())\n\n /** Root runs launchctl directly; everything else goes through `sudo -n`. */\n const runPrivileged = async (program: string, args: string[]) =>\n mode === 'agent' || ctx.isRoot ? await ctx.run(program, args) : await ctx.run('sudo', ['-n', program, ...args])\n\n /** The sudo form of what is left to do when this process cannot write the file itself. */\n const commands = (file: string, label: string): string[] => mode === 'agent'\n ? []\n : [\n shellCommand('sudo', ['launchctl', 'bootstrap', 'system', file]),\n shellCommand('sudo', ['launchctl', 'enable', `system/${label}`]),\n ]\n\n return {\n mechanism,\n\n async detect(): Promise<BootCandidate> {\n if (ctx.platform !== 'darwin')\n return { mechanism, available: false, bootCapable: false, privileged: false, reason: 'launchd is macOS-only' }\n if (mode === 'agent') {\n const domain = await domainOf()\n return {\n mechanism,\n // A plist in ~/Library/LaunchAgents is loaded at login whether or not\n // this process can reach launchd right now, so an unreachable domain\n // is a diagnostic, not a reason to refuse.\n available: true,\n bootCapable: false,\n privileged: true,\n reason: domain === null\n ? `a LaunchAgent loads at login; launchd's domain is not reachable from this process for uid ${uidOf(ctx) ?? '?'} right now, so it is loaded at the next login`\n : `launchd ${domain} domain is reachable; a LaunchAgent loads at login, not at boot`,\n }\n }\n const canElevate = ctx.isRoot || (await ctx.sudo())\n return {\n mechanism,\n // Always selectable: without root the plugin stages the plist and hands\n // over the three sudo commands, which is the only way to start before\n // login on a Mac that has no passwordless sudo.\n available: true,\n bootCapable: true,\n privileged: canElevate,\n reason: canElevate\n ? 'this process can write /Library/LaunchDaemons, so a LaunchDaemon starts at boot'\n : 'a LaunchDaemon starts at boot but needs root here, so the plugin stages the plist and shows the sudo commands',\n }\n },\n\n async status(spec: BootSpec): Promise<BootProviderStatus> {\n try {\n const file = pathOf(spec)\n const label = launchdLabel(spec)\n const own = inspectOwned(file, assertMarker(spec.marker))\n if (own.exists && !own.owned)\n return { state: 'not-installed', unitPath: file, detail: own.reason ?? 'foreign file', commands: [] }\n const domain = await domainOf()\n if (!own.owned)\n return { state: 'not-installed', unitPath: file, detail: `no plist at ${file}`, commands: [] }\n if (domain === null)\n return { state: 'enabled-running', unitPath: file, detail: `${file} is present; launchd has no reachable domain for uid ${uidOf(ctx) ?? '?'}, so it loads at the next login`, commands: [] }\n const probe = await ctx.run('launchctl', ['print', `${domain}/${label}`])\n const loaded = codeOf(probe) === 0\n const state: BootState = bootState(true, true, false)\n return {\n state,\n unitPath: file,\n detail: `${file} is present; ${loaded ? `loaded in ${domain}` : `not loaded right now (RunAtLoad loads it at the next login)`}`,\n commands: [],\n }\n }\n catch (error) {\n return { state: 'not-installed', unitPath: null, detail: errorMessage(error), commands: [] }\n }\n },\n\n async install(spec: BootSpec): Promise<BootActionResult> {\n try {\n validate(spec)\n const file = pathOf(spec)\n const label = launchdLabel(spec)\n // A LaunchDaemon is the same hazard as a system unit: `UserName` missing\n // means launchd runs it as root, and `$USER` is root whenever the panel\n // was started from a root shell. So the account comes from the login that\n // elevated (or this process's own euid), never from the environment, and\n // root is refused rather than written as if it were a choice.\n const content = launchdPlist(spec, mode === 'daemon' ? { userName: bootUserName(ctx) } : {})\n const own = inspectOwned(file, spec.marker)\n if (own.exists && !own.owned)\n return failed(own.reason ?? 'foreign file')\n const staged = posixJoin(tempDir(ctx), `${label}.plist`)\n if (!(await privileged())) {\n // The command below installs this exact file, so it has to exist.\n ensureDirQuiet(path.posix.dirname(staged))\n fs.writeFileSync(staged, content, { mode: 0o644 })\n return failed('writing /Library/LaunchDaemons needs root; the plist is staged for you to install', {\n needsPrivilege: true,\n commands: [\n shellCommand('sudo', ['install', '-m', '0644', staged, file]),\n shellCommand('sudo', ['launchctl', 'bootstrap', 'system', file]),\n shellCommand('sudo', ['launchctl', 'enable', `system/${label}`]),\n ],\n })\n }\n\n const domain = await domainOf()\n\n ensureDirQuiet(path.posix.dirname(file))\n ensureDirQuiet(spec.logDir)\n\n const loaded = domain !== null && codeOf(await ctx.run('launchctl', ['print', `${domain}/${label}`])) === 0\n const before = { text: own.text, exists: own.exists }\n\n let changed = false\n if (mode === 'agent' || ctx.isRoot) {\n const write = writeOwned(file, spec.marker, content, 0o644)\n if (write.refusal)\n return failed(write.refusal)\n changed = write.changed\n }\n else {\n ensureDirQuiet(path.dirname(staged))\n fs.writeFileSync(staged, content, { mode: 0o644 })\n changed = !before.exists || before.text !== content\n if (!changed) {\n fs.rmSync(staged, { force: true })\n }\n else {\n const installCommand = shellCommand('sudo', ['install', '-m', '0644', staged, file])\n const install = await runPrivileged('install', ['-m', '0644', staged, file])\n if (codeOf(install) !== 0)\n return failed(problem(install, installCommand) ?? 'launchctl failed', { needsPrivilege: true, commands: [installCommand] })\n fs.rmSync(staged, { force: true })\n }\n }\n\n const lint = await runPrivileged('plutil', ['-lint', file])\n if (codeOf(lint) !== 0)\n return failed(`plutil -lint rejected the generated plist: ${(lint.stderr || lint.stdout).trim() || 'no output'}`, { changed, commands: commands(file, label) })\n\n if (domain === null) {\n return {\n ok: true,\n changed,\n detail: `${file} is in place; launchd's domain is not reachable from this process, so it loads at the next login`,\n commands: [],\n needsPrivilege: false,\n }\n }\n\n if (loaded && changed) {\n const bootout = await runPrivileged('launchctl', ['bootout', `${domain}/${label}`])\n const bootoutCode = codeOf(bootout)\n if (bootoutCode !== 0 && bootoutCode !== LAUNCHCTL_NO_SUCH_PROCESS && bootoutCode !== LAUNCHCTL_NOT_LOADED)\n return failed(problem(bootout, shellCommand('launchctl', ['bootout', `${domain}/${label}`])) ?? 'launchctl failed', { changed, commands: commands(file, label) })\n }\n\n if (changed || !loaded) {\n const boot = await runPrivileged('launchctl', ['bootstrap', domain, file])\n const bootCode = codeOf(boot)\n if (bootCode !== 0 && bootCode !== LAUNCHCTL_ALREADY_LOADED)\n return failed(problem(boot, shellCommand('launchctl', ['bootstrap', domain, file])) ?? 'launchctl failed', { changed, commands: commands(file, label) })\n if (bootCode === 0)\n changed = true\n const enable = await runPrivileged('launchctl', ['enable', `${domain}/${label}`])\n if (codeOf(enable) !== 0)\n return failed(problem(enable, shellCommand('launchctl', ['enable', `${domain}/${label}`])) ?? 'launchctl failed', { changed, commands: commands(file, label) })\n }\n\n const verify = await runPrivileged('launchctl', ['print', `${domain}/${label}`])\n if (codeOf(verify) !== 0)\n return failed(`launchctl print ${domain}/${label} did not confirm the job (exit ${String(codeOf(verify))})`, { changed, commands: commands(file, label) })\n\n return {\n ok: true,\n changed,\n detail: `${file} installed and loaded in ${domain}`,\n commands: [],\n needsPrivilege: mode === 'daemon' && !ctx.isRoot,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n async uninstall(spec: BootSpec): Promise<BootActionResult> {\n try {\n const file = pathOf(spec)\n const label = launchdLabel(spec)\n const own = inspectOwned(file, spec.marker)\n if (own.exists && !own.owned)\n return failed(own.reason ?? 'foreign file')\n if (mode === 'daemon' && !ctx.isRoot && !(await ctx.sudo())) {\n if (!own.exists)\n return { ok: true, changed: false, detail: `no plist at ${file} and no privilege to change launchd; nothing to remove`, commands: [], needsPrivilege: false }\n return failed('removing a LaunchDaemon needs root', {\n needsPrivilege: true,\n commands: [\n shellCommand('sudo', ['launchctl', 'bootout', `system/${label}`]),\n shellCommand('sudo', ['rm', '-f', file]),\n ],\n })\n }\n\n const domain = await domainOf()\n // A job under this label may belong to a plist we did not write; never\n // boot out a job whose own file is not ours.\n if (!own.exists && !own.owned)\n return failed(`no plist of ours at ${file}; refusing to unload a job this plugin did not install`)\n let changed = false\n if (domain !== null) {\n const bootout = await runPrivileged('launchctl', ['bootout', `${domain}/${label}`])\n const bootoutCode = codeOf(bootout)\n if (bootoutCode === 0)\n changed = true\n else if (bootoutCode !== LAUNCHCTL_NO_SUCH_PROCESS && bootoutCode !== LAUNCHCTL_NOT_LOADED)\n return failed(problem(bootout, shellCommand('launchctl', ['bootout', `${domain}/${label}`])) ?? 'launchctl failed', { commands: commands(file, label) })\n }\n\n if (own.exists) {\n if (mode === 'agent' || ctx.isRoot) {\n const remove = removeOwned(file, spec.marker)\n if (remove.refusal)\n return failed(remove.refusal)\n if (remove.removed)\n changed = true\n }\n else {\n // Re-prove ownership before the privileged delete: the check above\n // ran as the invoking user, which may not be able to read the file.\n if (!own.owned)\n return failed(own.reason ?? 'foreign file')\n const rmCommand = shellCommand('sudo', ['rm', '-f', file])\n const rm = await runPrivileged('rm', ['-f', file])\n if (codeOf(rm) !== 0)\n return failed(problem(rm, rmCommand) ?? 'launchctl failed', { needsPrivilege: true, commands: [rmCommand] })\n changed = true\n }\n }\n\n if (domain !== null) {\n const verify = await runPrivileged('launchctl', ['print', `${domain}/${label}`])\n if (codeOf(verify) === 0)\n return failed(`${label} is still loaded in ${domain} after bootout`, { changed })\n }\n\n return {\n ok: true,\n changed,\n detail: own.exists ? `unloaded and removed ${file}` : `no plist at ${file} (already gone)`,\n commands: [],\n needsPrivilege: mode === 'daemon' && !ctx.isRoot,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n /**\n * A LaunchAgent that was written but never booted is exactly the state an\n * install leaves behind, and nothing else can start it: `kickstart -k` needs\n * the label loaded first, so loading it is part of the start.\n */\n async activate(spec: BootSpec): Promise<BootStart | null> {\n const file = pathOf(spec)\n const label = launchdLabel(spec)\n const domain = await domainOf()\n if (domain === null)\n return null\n const job = `${domain}/${label}`\n if (mode === 'daemon') {\n return {\n commands: [\n ['sudo', '-n', 'launchctl', 'bootstrap', 'system', file],\n ['sudo', '-n', 'launchctl', 'kickstart', '-k', job],\n ],\n requires: { commands: [], files: [file] },\n display: [\n shellCommand('sudo', ['launchctl', 'bootstrap', 'system', file]),\n shellCommand('sudo', ['launchctl', 'kickstart', '-k', job]),\n ],\n }\n }\n const loaded = codeOf(await ctx.run('launchctl', ['print', job])) === 0\n return {\n commands: [\n ...(loaded ? [] : [['launchctl', 'bootstrap', domain, file]]),\n ['launchctl', 'kickstart', '-k', job],\n ],\n requires: { commands: [], files: [file] },\n display: [\n ...(loaded ? [] : [shellCommand('launchctl', ['bootstrap', domain, file])]),\n shellCommand('launchctl', ['kickstart', '-k', job]),\n ],\n }\n },\n\n /** `KeepAlive` would resurrect the panel, so the job itself has to stop it. */\n async stopCommands(spec: BootSpec): Promise<string[][]> {\n const label = launchdLabel(spec)\n const domain = await domainOf()\n if (domain === null)\n return []\n const job = `${domain}/${label}`\n return mode === 'daemon'\n ? [['sudo', '-n', 'launchctl', 'kill', 'SIGTERM', job]]\n : [['launchctl', 'kill', 'SIGTERM', job]]\n },\n\n /**\n * launchd has no \"unload but leave running\": `bootout` always stops the job.\n * That is why retirement is handed to the caller instead of run during the\n * install \u2014 here it runs once the panel is already down, and the entry being\n * started is a different mechanism's, so stopping this job is harmless.\n */\n async retireCommands(spec: BootSpec): Promise<BootRetirement> {\n const file = pathOf(spec)\n const label = launchdLabel(spec)\n const domain = await domainOf()\n const bootout = mode === 'daemon'\n ? ['sudo', '-n', 'launchctl', 'bootout', `system/${label}`]\n : domain === null ? null : ['launchctl', 'bootout', `${domain}/${label}`]\n const remove = mode === 'daemon' ? ['sudo', '-n', 'rm', '-f', file] : ['rm', '-f', file]\n return {\n commands: [...(bootout === null ? [] : [bootout]), remove],\n display: [\n ...(bootout === null ? [] : [shellCommand('sudo', ['launchctl', 'bootout', `system/${label}`])]),\n shellCommand('sudo', ['rm', '-f', file]),\n ],\n }\n },\n }\n}\n\nexport function createLaunchdAgentProvider(ctx: BootProviderContext): BootProvider {\n return createLaunchdProvider(ctx, 'agent')\n}\n\nexport function createLaunchdDaemonProvider(ctx: BootProviderContext): BootProvider {\n return createLaunchdProvider(ctx, 'daemon')\n}\n", "/**\n * freedesktop XDG autostart: `~/.config/autostart/<unitName>.desktop`.\n * This runs at login, never at boot, and needs no privilege. `Exec=` is not a\n * shell, so every word is quoted for the desktop entry parser and `$`/`%` are\n * escaped rather than expanded.\n */\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport type { BootCandidate, BootState } from '../shared/contracts.js'\nimport type { BootActionResult, BootProvider, BootProviderContext, BootProviderStatus, BootRetirement, BootSpec } from './types.js'\nimport { assertAbsolute, assertArg, assertEnvKey, assertLabel, assertMarker, assertUnitName, desktopExec, shellCommand } from './escape.js'\nimport { bootState, configHome, errorMessage, failed, inspectOwned, posixJoin, removeOwned, writeOwned } from './common.js'\n\nfunction validate(spec: BootSpec): void {\n assertUnitName(spec.unitName)\n assertMarker(spec.marker)\n assertLabel(spec.label)\n assertAbsolute(spec.command, 'spec.command')\n assertAbsolute(spec.cwd, 'spec.cwd')\n for (const arg of spec.args)\n assertArg(arg)\n for (const key of Object.keys(spec.env))\n assertEnvKey(key)\n}\n\nexport function xdgDesktopEntry(spec: BootSpec): string {\n validate(spec)\n return `${[\n '[Desktop Entry]',\n 'Type=Application',\n `Name=${spec.label}`,\n `Comment=home-hosted daemon, managed by ${spec.marker}`,\n `Exec=${desktopExec(spec.command, spec.args)}`,\n `Path=${spec.cwd}`,\n 'Terminal=false',\n 'X-GNOME-Autostart-enabled=true',\n `X-HomeHosted-Marker=${spec.marker}`,\n '',\n ].join('\\n')}`\n}\n\nfunction desktopFlag(text: string, key: string): string | null {\n for (const line of text.split('\\n')) {\n const trimmed = line.trim()\n if (trimmed.startsWith(`${key}=`))\n return trimmed.slice(key.length + 1).trim()\n }\n return null\n}\n\nexport function createXdgAutostartProvider(ctx: BootProviderContext): BootProvider {\n const mechanism = 'xdg-autostart' as const\n const unitOf = (spec: BootSpec): string => `${assertUnitName(spec.unitName)}.desktop`\n const pathOf = (spec: BootSpec): string => posixJoin(configHome(ctx), 'autostart', unitOf(spec))\n\n return {\n mechanism,\n\n async detect(): Promise<BootCandidate> {\n if (ctx.platform !== 'linux')\n return { mechanism, available: false, bootCapable: false, privileged: false, reason: 'XDG autostart is Linux-only' }\n return {\n mechanism,\n available: true,\n bootCapable: false,\n privileged: true,\n reason: `a .desktop entry under ${posixJoin(configHome(ctx), 'autostart')} needs no privilege, but it starts at login rather than at boot`,\n }\n },\n\n async status(spec: BootSpec): Promise<BootProviderStatus> {\n try {\n const file = pathOf(spec)\n const own = inspectOwned(file, assertMarker(spec.marker))\n if (own.exists && !own.owned)\n return { state: 'not-installed', unitPath: file, detail: own.reason ?? 'foreign file', commands: [] }\n if (!own.owned || own.text === null)\n return { state: 'not-installed', unitPath: file, detail: `no autostart entry at ${file}`, commands: [] }\n const enabled = desktopFlag(own.text, 'X-GNOME-Autostart-enabled') !== 'false'\n && desktopFlag(own.text, 'Hidden') !== 'true'\n const state: BootState = bootState(true, enabled, false)\n return {\n state,\n unitPath: file,\n detail: `${file} is present; ${enabled ? 'it starts at login' : 'it is disabled'} (login scope, not boot)`,\n commands: [],\n }\n }\n catch (error) {\n return { state: 'not-installed', unitPath: null, detail: errorMessage(error), commands: [] }\n }\n },\n\n async install(spec: BootSpec): Promise<BootActionResult> {\n try {\n validate(spec)\n const file = pathOf(spec)\n fs.mkdirSync(path.dirname(file), { recursive: true })\n const write = writeOwned(file, spec.marker, xdgDesktopEntry(spec), 0o644)\n if (write.refusal)\n return failed(write.refusal)\n\n const text = inspectOwned(file, spec.marker).text\n const enabled = text !== null\n && desktopFlag(text, 'X-GNOME-Autostart-enabled') !== 'false'\n && desktopFlag(text, 'Hidden') !== 'true'\n if (!enabled)\n return failed(`${file} was written but does not read back as enabled`, { changed: write.changed })\n\n return {\n ok: true,\n changed: write.changed,\n detail: write.changed\n ? `${file} installed; it starts at login (not at boot)`\n : `${file} was already installed and enabled`,\n commands: [],\n needsPrivilege: false,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n async uninstall(spec: BootSpec): Promise<BootActionResult> {\n try {\n const file = pathOf(spec)\n const own = inspectOwned(file, spec.marker)\n if (own.exists && !own.owned)\n return failed(own.reason ?? 'foreign file')\n const remove = removeOwned(file, spec.marker)\n if (remove.refusal)\n return failed(remove.refusal)\n return {\n ok: true,\n changed: remove.removed,\n detail: remove.removed ? `removed ${file}` : `${file} was already gone`,\n commands: [],\n needsPrivilege: false,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n /**\n * A `.desktop` entry has no launcher of its own: the desktop session reads\n * the file at login. Nothing here can start the panel, and claiming a start\n * command would be a fiction, so the caller falls back to the CLI.\n */\n async activate(): Promise<null> {\n return null\n },\n\n /** A file the session reads at login: deleting it stops nothing. */\n async retireCommands(spec: BootSpec): Promise<BootRetirement> {\n const file = pathOf(spec)\n return { commands: [['rm', '-f', file]], display: [shellCommand('rm', ['-f', file])] }\n },\n }\n}\n", "/**\n * Windows: the per-user `Run` key (login scope, no admin) and a scheduled task\n * (admin). `sc.exe` is never used \u2014 a plain Node process has no service\n * dispatcher and would fail with error 1053.\n *\n * The `Run` key starts *every* value it holds, so the ownership marker cannot\n * live beside the command there; it lives under this plugin's own key\n * (`HKCU\\Software\\home-hosted`), and a `Run` value is only touched when that\n * marker proves we wrote it.\n */\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport type { BootCandidate, BootState } from '../shared/contracts.js'\nimport type { BootActionResult, BootProvider, BootProviderContext, BootProviderStatus, BootRetirement, BootSpec, BootStart } from './types.js'\nimport {\n assertAbsolute,\n assertArg,\n assertEnvKey,\n assertLabel,\n assertMarker,\n assertRegistryValueName,\n assertUnitName,\n batchCommandLine,\n powershellLiteral,\n windowsArg,\n windowsCommandLine,\n windowsDisplayCommand,\n xmlEscape,\n xmlUnescape,\n} from './escape.js'\nimport { bootState, errorMessage, failed, inspectOwned, localAppData, removeOwned, tempDir, writeOwned } from './common.js'\n\nexport const RUN_KEY = 'HKCU\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run'\nexport const MARKER_KEY = 'HKCU\\\\Software\\\\home-hosted'\nconst RUN_VALUE_LIMIT = 260\n\nfunction validate(spec: BootSpec): void {\n assertUnitName(spec.unitName)\n assertMarker(spec.marker)\n assertLabel(spec.label)\n assertAbsolute(spec.command, 'spec.command')\n assertAbsolute(spec.cwd, 'spec.cwd')\n for (const arg of spec.args)\n assertArg(arg)\n for (const key of Object.keys(spec.env))\n assertEnvKey(key)\n}\n\nfunction codeOf(result: { code: number | null, error?: string | null }): number | null {\n return result.error ? null : result.code\n}\n\nfunction problem(run: { code: number | null, error?: string | null, stdout: string, stderr: string }, command: string): string | null {\n const code = codeOf(run)\n if (code === 0)\n return null\n if (code === null)\n return run.error ? `${command}: ${run.error}` : `${command}: no exit code`\n return `${command} exited ${code}: ${(run.stderr || run.stdout).trim() || '(no output)'}`\n}\n\ninterface RegValue {\n exists: boolean\n data: string | null\n}\n\nasync function queryReg(ctx: BootProviderContext, key: string, name: string): Promise<RegValue> {\n const res = await ctx.run('reg.exe', ['query', key, '/v', name])\n if (res.error || res.code !== 0)\n return { exists: false, data: null }\n for (const line of res.stdout.split(/\\r?\\n/)) {\n const match = /^\\s{2,}(.+?)\\s{2,}(REG_[A-Z_]+)\\s{2,}(.*)$/.exec(line)\n if (match && match[1]?.trim() === name)\n return { exists: true, data: (match[3] ?? '').trim() }\n }\n return { exists: false, data: null }\n}\n\nexport interface WindowsRunPayload {\n /** The exact `Run` value data. */\n data: string\n /** Set when the command line exceeded the `Run` value limit and a `.cmd` wrapper is needed. */\n wrapperPath: string | null\n wrapperContent: string | null\n}\n\nexport function windowsRunPayload(ctx: BootProviderContext, spec: BootSpec): WindowsRunPayload {\n const direct = windowsCommandLine(spec.command, spec.args)\n if (direct.length <= RUN_VALUE_LIMIT)\n return { data: direct, wrapperPath: null, wrapperContent: null }\n const wrapperPath = path.join(localAppData(ctx), 'home-hosted', `${spec.unitName}.cmd`)\n return {\n data: `cmd.exe /c ${windowsArg(wrapperPath)}`,\n wrapperPath,\n wrapperContent: `@echo off\\r\\nrem ${spec.marker}\\r\\n${batchCommandLine(spec.command, spec.args)}\\r\\n`,\n }\n}\n\nfunction createWindowsRunProviderImpl(ctx: BootProviderContext): BootProvider {\n const mechanism = 'windows-run' as const\n const nameOf = (spec: BootSpec): string => assertRegistryValueName(assertUnitName(spec.unitName))\n\n const addValue = (key: string, name: string, data: string) =>\n ctx.run('reg.exe', ['add', key, '/v', name, '/t', 'REG_SZ', '/d', data, '/f'])\n\n return {\n mechanism,\n\n async detect(): Promise<BootCandidate> {\n if (ctx.platform !== 'win32')\n return { mechanism, available: false, bootCapable: false, privileged: false, reason: 'the Run key is Windows-only' }\n return {\n mechanism,\n available: true,\n bootCapable: false,\n privileged: true,\n reason: `HKCU\\\\\u2026\\\\Run needs no admin, but it starts at login rather than at boot`,\n }\n },\n\n async status(spec: BootSpec): Promise<BootProviderStatus> {\n try {\n const name = nameOf(spec)\n const marker = assertMarker(spec.marker)\n const value = await queryReg(ctx, RUN_KEY, name)\n if (!value.exists)\n return { state: 'not-installed', unitPath: RUN_KEY, detail: `no ${RUN_KEY}\\\\${name} value`, commands: [] }\n const markerValue = await queryReg(ctx, MARKER_KEY, name)\n const owned = markerValue.data?.includes(marker) === true\n if (!owned)\n return { state: 'not-installed', unitPath: RUN_KEY, detail: `${name} exists under ${RUN_KEY} but is not marked as ours; refusing to treat it as this plugin's entry`, commands: [] }\n const state: BootState = bootState(true, true, false)\n return { state, unitPath: RUN_KEY, detail: `${RUN_KEY}\\\\${name} starts at login (not at boot)`, commands: [] }\n }\n catch (error) {\n return { state: 'not-installed', unitPath: RUN_KEY, detail: errorMessage(error), commands: [] }\n }\n },\n\n async install(spec: BootSpec): Promise<BootActionResult> {\n try {\n validate(spec)\n const name = nameOf(spec)\n const marker = assertMarker(spec.marker)\n const payload = windowsRunPayload(ctx, spec)\n const wrapperExists = payload.wrapperPath !== null && fs.existsSync(payload.wrapperPath)\n const wrapperOwn = payload.wrapperPath ? inspectOwned(payload.wrapperPath, marker) : null\n if (wrapperOwn?.exists && !wrapperOwn.owned)\n return failed(wrapperOwn.reason ?? 'foreign wrapper')\n\n const markerValue = await queryReg(ctx, MARKER_KEY, name)\n const owned = markerValue.data?.includes(marker) === true\n const current = await queryReg(ctx, RUN_KEY, name)\n // A value the marker does not prove is ours is never touched and never\n // claimed, even when it already equals what we would write.\n if (current.exists && !owned)\n return failed(`${RUN_KEY}\\\\${name} already exists and was not written by this plugin; refusing to overwrite it`)\n\n const commands: string[] = [\n windowsDisplayCommand('reg.exe', ['add', RUN_KEY, '/v', name, '/t', 'REG_SZ', '/d', payload.data, '/f']),\n ]\n\n let changed = false\n if (payload.wrapperPath && payload.wrapperContent) {\n const write = writeOwned(payload.wrapperPath, marker, payload.wrapperContent, 0o644)\n if (write.refusal)\n return failed(write.refusal)\n changed = write.changed || changed\n }\n\n if (!current.exists || current.data !== payload.data) {\n const add = await addValue(RUN_KEY, name, payload.data)\n const addProblem = problem(add, commands[0] ?? 'reg.exe add')\n if (addProblem)\n return failed(addProblem, { changed, commands })\n changed = true\n }\n if (!owned) {\n const mark = await addValue(MARKER_KEY, name, marker)\n const markProblem = problem(mark, windowsDisplayCommand('reg.exe', ['add', MARKER_KEY, '/v', name, '/t', 'REG_SZ', '/d', marker, '/f']))\n if (markProblem)\n return failed(markProblem, { changed, commands })\n changed = true\n }\n\n const after = await queryReg(ctx, RUN_KEY, name)\n if (after.data !== payload.data)\n return failed(`${RUN_KEY}\\\\${name} does not read back as the value that was written`, { changed, commands })\n\n // A wrapper we no longer need is cleaned up so the Run value converges on the direct form.\n if (!payload.wrapperPath && wrapperExists && wrapperOwn?.owned) {\n removeOwned(path.join(localAppData(ctx), 'home-hosted', `${spec.unitName}.cmd`), marker)\n }\n\n return {\n ok: true,\n changed,\n detail: changed ? `${RUN_KEY}\\\\${name} installed; it starts at login (not at boot)` : `${RUN_KEY}\\\\${name} was already installed`,\n commands: [],\n needsPrivilege: false,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n async uninstall(spec: BootSpec): Promise<BootActionResult> {\n try {\n const name = nameOf(spec)\n const marker = assertMarker(spec.marker)\n const current = await queryReg(ctx, RUN_KEY, name)\n const markerValue = await queryReg(ctx, MARKER_KEY, name)\n const owned = markerValue.data?.includes(marker) === true\n if (current.exists && !owned) {\n const payload = windowsRunPayload(ctx, spec)\n const wrapperOwned = payload.wrapperPath !== null ? inspectOwned(payload.wrapperPath, marker).owned : false\n if (current.data !== payload.data && !wrapperOwned)\n return failed(`${RUN_KEY}\\\\${name} is not marked as ours; refusing to delete a foreign Run value`)\n }\n\n let changed = false\n if (current.exists) {\n const del = await ctx.run('reg.exe', ['delete', RUN_KEY, '/v', name, '/f'])\n const delCode = codeOf(del)\n if (delCode !== 0 && delCode !== 1)\n return failed(problem(del, windowsDisplayCommand('reg.exe', ['delete', RUN_KEY, '/v', name, '/f'])) ?? 'reg.exe delete failed')\n changed = delCode === 0\n }\n if (owned) {\n await ctx.run('reg.exe', ['delete', MARKER_KEY, '/v', name, '/f'])\n changed = true\n }\n\n const payload = windowsRunPayload(ctx, spec)\n if (payload.wrapperPath) {\n const remove = removeOwned(payload.wrapperPath, marker)\n if (remove.refusal)\n return failed(remove.refusal)\n changed = remove.removed || changed\n }\n const stale = path.join(localAppData(ctx), 'home-hosted', `${spec.unitName}.cmd`)\n const staleRemove = removeOwned(stale, marker)\n if (staleRemove.refusal)\n return failed(staleRemove.refusal)\n changed = staleRemove.removed || changed\n\n const after = await queryReg(ctx, RUN_KEY, name)\n if (after.exists)\n return failed(`${RUN_KEY}\\\\${name} still exists after removal`, { changed })\n\n return {\n ok: true,\n changed,\n detail: changed ? `removed ${RUN_KEY}\\\\${name}` : `${RUN_KEY}\\\\${name} was already gone`,\n commands: [],\n needsPrivilege: false,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n /**\n * The `Run` key is read by the shell at logon; there is no way to make it\n * start something now. Reporting none is what sends the caller back to the\n * CLI, which is the only honest way to bring the panel up here.\n */\n async activate(): Promise<null> {\n return null\n },\n\n /** Deleting the value and its marker stops nothing that is already running. */\n async retireCommands(spec: BootSpec): Promise<BootRetirement> {\n const name = nameOf(spec)\n return {\n commands: [\n ['reg.exe', 'delete', RUN_KEY, '/v', name, '/f'],\n ['reg.exe', 'delete', MARKER_KEY, '/v', name, '/f'],\n ],\n display: [\n windowsDisplayCommand('reg.exe', ['delete', RUN_KEY, '/v', name, '/f']),\n windowsDisplayCommand('reg.exe', ['delete', MARKER_KEY, '/v', name, '/f']),\n ],\n }\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Scheduled task\n// ---------------------------------------------------------------------------\n\nexport function scheduledTaskXml(spec: BootSpec): string {\n validate(spec)\n const args = spec.args.map(windowsArg).join(' ')\n return `${[\n '<?xml version=\"1.0\" encoding=\"UTF-16\"?>',\n '<Task version=\"1.2\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">',\n '<RegistrationInfo>',\n `<Description>${xmlEscape(spec.marker)}</Description>`,\n '</RegistrationInfo>',\n '<Triggers>',\n '<LogonTrigger>',\n '<Enabled>true</Enabled>',\n '</LogonTrigger>',\n '</Triggers>',\n '<Principals>',\n '<Principal id=\"Author\">',\n '<LogonType>InteractiveToken</LogonType>',\n '<RunLevel>LeastPrivilege</RunLevel>',\n '</Principal>',\n '</Principals>',\n '<Settings>',\n '<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>',\n '<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>',\n '<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>',\n '<StartWhenAvailable>true</StartWhenAvailable>',\n '<RestartOnFailure>',\n '<Interval>PT1M</Interval>',\n '<Count>3</Count>',\n '</RestartOnFailure>',\n '<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>',\n '</Settings>',\n '<Actions Context=\"Author\">',\n '<Exec>',\n `<Command>${xmlEscape(spec.command)}</Command>`,\n `<Arguments>${xmlEscape(args)}</Arguments>`,\n `<WorkingDirectory>${xmlEscape(spec.cwd)}</WorkingDirectory>`,\n '</Exec>',\n '</Actions>',\n '</Task>',\n '',\n ].join('\\r\\n')}`\n}\n\nexport function registerTaskScript(spec: BootSpec): string {\n validate(spec)\n const args = spec.args.map(windowsArg).join(' ')\n return [\n `$action = New-ScheduledTaskAction -Execute ${powershellLiteral(spec.command)} -Argument ${powershellLiteral(args)} -WorkingDirectory ${powershellLiteral(spec.cwd)}`,\n '$trigger = New-ScheduledTaskTrigger -AtLogOn',\n '$settings = New-ScheduledTaskSettingsSet -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries',\n `Register-ScheduledTask -TaskName ${powershellLiteral(spec.unitName)} -Action $action -Trigger $trigger -Settings $settings -Description ${powershellLiteral(spec.marker)} -Force | Out-Null`,\n ].join('; ')\n}\n\nexport function unregisterTaskScript(name: string): string {\n return `Unregister-ScheduledTask -TaskName ${powershellLiteral(assertUnitName(name))} -Confirm:$false`\n}\n\nfunction xmlTag(xml: string, tag: string): string | null {\n const match = new RegExp(`<${tag}>([\\\\s\\\\S]*?)</${tag}>`).exec(xml)\n return match?.[1] === undefined ? null : xmlUnescape(match[1].trim())\n}\n\ninterface TaskInfo {\n exists: boolean\n xml: string | null\n marked: boolean\n matches: boolean\n}\n\nexport function createWindowsTaskProvider(ctx: BootProviderContext): BootProvider {\n const mechanism = 'windows-task' as const\n const nameOf = (spec: BootSpec): string => assertUnitName(spec.unitName)\n\n const query = async (name: string): Promise<{ exists: boolean, xml: string | null }> => {\n const res = await ctx.run('schtasks.exe', ['/Query', '/TN', name, '/XML'])\n if (res.error || res.code !== 0)\n return { exists: false, xml: null }\n return { exists: true, xml: res.stdout }\n }\n\n const inspect = async (spec: BootSpec): Promise<TaskInfo> => {\n const found = await query(nameOf(spec))\n if (!found.exists || found.xml === null)\n return { exists: false, xml: null, marked: false, matches: false }\n const marked = xmlUnescape(found.xml).includes(spec.marker)\n const args = spec.args.map(windowsArg).join(' ')\n const matches = marked\n && xmlTag(found.xml, 'Command') === spec.command\n && xmlTag(found.xml, 'Arguments') === args\n && (xmlTag(found.xml, 'WorkingDirectory') ?? spec.cwd) === spec.cwd\n return { exists: true, xml: found.xml, marked, matches }\n }\n\n const hasRegisterCmdlet = async (): Promise<boolean> => {\n const probe = await ctx.run('powershell.exe', ['-NoProfile', '-Command', 'Get-Command Register-ScheduledTask'])\n return probe.error === undefined || probe.error === null\n ? /Register-ScheduledTask/.test(probe.stdout)\n : false\n }\n\n const isElevated = async (): Promise<boolean> => {\n const script = '([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)'\n const probe = await ctx.run('powershell.exe', ['-NoProfile', '-Command', script])\n return probe.error === undefined || probe.error === null ? /^true$/im.test(probe.stdout.trim()) : false\n }\n\n return {\n mechanism,\n\n async detect(): Promise<BootCandidate> {\n if (ctx.platform !== 'win32')\n return { mechanism, available: false, bootCapable: false, privileged: false, reason: 'scheduled tasks are Windows-only' }\n const elevated = await isElevated()\n return {\n mechanism,\n available: elevated,\n bootCapable: false,\n privileged: elevated,\n reason: elevated\n ? 'administrator rights are available; the task is registered for logon (not for boot)'\n : 'registering a scheduled task needs an elevated process',\n }\n },\n\n async status(spec: BootSpec): Promise<BootProviderStatus> {\n try {\n const name = nameOf(spec)\n const info = await inspect(spec)\n if (!info.exists)\n return { state: 'not-installed', unitPath: null, detail: `no scheduled task named ${name}`, commands: [] }\n if (!info.marked)\n return { state: 'not-installed', unitPath: null, detail: `task ${name} exists but does not carry this plugin's marker; refusing to treat it as this plugin's entry`, commands: [] }\n const state: BootState = bootState(true, true, false)\n return { state, unitPath: null, detail: `scheduled task ${name} is registered (runs at logon, not at boot)`, commands: [] }\n }\n catch (error) {\n return { state: 'not-installed', unitPath: null, detail: errorMessage(error), commands: [] }\n }\n },\n\n async install(spec: BootSpec): Promise<BootActionResult> {\n try {\n validate(spec)\n const name = nameOf(spec)\n const info = await inspect(spec)\n if (info.exists && !info.marked)\n return failed(`scheduled task ${name} already exists and was not written by this plugin; refusing to overwrite it`)\n if (!(await isElevated()))\n return failed('registering a scheduled task needs an elevated process', { needsPrivilege: true })\n\n const elevatedCommands = [\n windowsDisplayCommand('powershell.exe', ['-NoProfile', '-Command', unregisterTaskScript(name)]),\n windowsDisplayCommand('schtasks.exe', ['/Query', '/TN', name, '/XML']),\n ]\n\n if (info.exists && info.matches) {\n const verify = await query(name)\n if (verify.exists)\n return { ok: true, changed: false, detail: `scheduled task ${name} is already registered`, commands: [], needsPrivilege: true }\n }\n\n const cmdlet = await hasRegisterCmdlet()\n if (cmdlet) {\n const script = registerTaskScript(spec)\n const res = await ctx.run('powershell.exe', ['-NoProfile', '-Command', script])\n if (codeOf(res) !== 0)\n return failed(problem(res, windowsDisplayCommand('powershell.exe', ['-NoProfile', '-Command', script])) ?? 'Register-ScheduledTask failed', { needsPrivilege: true, commands: elevatedCommands })\n }\n else {\n const xmlFile = path.join(tempDir(ctx), `${name}.xml`)\n fs.mkdirSync(path.dirname(xmlFile), { recursive: true })\n fs.writeFileSync(xmlFile, Buffer.from(`\\uFEFF${scheduledTaskXml(spec)}`, 'utf16le'))\n const createCommand = windowsDisplayCommand('schtasks.exe', ['/Create', '/TN', name, '/XML', xmlFile, '/F'])\n const create = await ctx.run('schtasks.exe', ['/Create', '/TN', name, '/XML', xmlFile, '/F'])\n if (codeOf(create) !== 0)\n return failed(problem(create, createCommand) ?? 'schtasks /Create failed', { needsPrivilege: true, commands: [createCommand] })\n fs.rmSync(xmlFile, { force: true })\n }\n\n const after = await query(name)\n if (!after.exists)\n return failed(`scheduled task ${name} was not found after registration`, { changed: true, needsPrivilege: true, commands: elevatedCommands })\n\n return {\n ok: true,\n changed: true,\n detail: `scheduled task ${name} registered (runs at logon, not at boot)`,\n commands: [],\n needsPrivilege: true,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n async uninstall(spec: BootSpec): Promise<BootActionResult> {\n try {\n const name = nameOf(spec)\n const info = await inspect(spec)\n if (!info.exists)\n return { ok: true, changed: false, detail: `no scheduled task named ${name}; already gone`, commands: [], needsPrivilege: false }\n if (!info.marked)\n return failed(`scheduled task ${name} is not marked as ours; refusing to delete a foreign task`)\n if (!(await isElevated()))\n return failed('deleting a scheduled task needs an elevated process', { needsPrivilege: true })\n\n const cmdlet = await hasRegisterCmdlet()\n const res = cmdlet\n ? await ctx.run('powershell.exe', ['-NoProfile', '-Command', unregisterTaskScript(name)])\n : await ctx.run('schtasks.exe', ['/Delete', '/TN', name, '/F'])\n const code = codeOf(res)\n if (code !== 0 && code !== 1)\n return failed(problem(res, cmdlet ? 'powershell.exe Unregister-ScheduledTask' : windowsDisplayCommand('schtasks.exe', ['/Delete', '/TN', name, '/F'])) ?? 'unregistering the task failed', { needsPrivilege: true })\n\n const after = await query(name)\n if (after.exists)\n return failed(`scheduled task ${name} still exists after removal`, { changed: true, needsPrivilege: true })\n\n return {\n ok: true,\n changed: true,\n detail: `unregistered scheduled task ${name}`,\n commands: [],\n needsPrivilege: true,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n /**\n * `/Run` starts the registered task now, and the query proves the task is\n * really registered before the panel is touched. A task does not restart a\n * panel it does not own, so the caller stops the one it drove first.\n */\n async activate(spec: BootSpec): Promise<BootStart> {\n const name = nameOf(spec)\n return {\n commands: [['schtasks.exe', '/Run', '/TN', name]],\n requires: { commands: [['schtasks.exe', '/Query', '/TN', name]], files: [] },\n display: [windowsDisplayCommand('schtasks.exe', ['/Run', '/TN', name])],\n }\n },\n\n /** Unregistering a task stops nothing that is already running. */\n async retireCommands(spec: BootSpec): Promise<BootRetirement> {\n const name = nameOf(spec)\n const cmdlet = await hasRegisterCmdlet()\n return {\n commands: [cmdlet\n ? ['powershell.exe', '-NoProfile', '-Command', unregisterTaskScript(name)]\n : ['schtasks.exe', '/Delete', '/TN', name, '/F']],\n display: [windowsDisplayCommand('schtasks.exe', ['/Delete', '/TN', name, '/F'])],\n }\n },\n }\n}\n\nexport function createWindowsRunProvider(ctx: BootProviderContext): BootProvider {\n return createWindowsRunProviderImpl(ctx)\n}\n", "/**\n * The two terminals of the ladder: a container (an OS boot entry would land\n * outside it, so the right answer is the container's own restart policy) and a\n * platform this plugin has no mechanism for.\n */\n\nimport type { BootCandidate } from '../shared/contracts.js'\nimport type { BootActionResult, BootProvider, BootProviderContext, BootProviderStatus, BootSpec } from './types.js'\nimport { readText } from '../util/fsx.js'\nimport { failed, existsOf } from './common.js'\n\nconst RESTART_POLICY = 'use the container restart policy instead: `docker run --restart unless-stopped`, or `restart: unless-stopped` in a compose file'\n\n/** A human-readable description of the container we are inside, or null. */\nexport function detectContainer(ctx: BootProviderContext): string | null {\n if (existsOf(ctx, '/.dockerenv'))\n return 'Docker (/.dockerenv)'\n if (existsOf(ctx, '/run/.containerenv'))\n return 'Podman (/run/.containerenv)'\n if (ctx.env.container?.trim())\n return `container environment (container=${ctx.env.container.trim()})`\n const cgroup = existsOf(ctx, '/proc/1/cgroup') ? readText('/proc/1/cgroup') : null\n if (cgroup) {\n if (cgroup.includes('kubepods'))\n return 'Kubernetes (cgroup kubepods)'\n if (cgroup.includes('docker'))\n return 'Docker (cgroup docker)'\n if (cgroup.includes('lxc'))\n return 'LXC (cgroup lxc)'\n if (cgroup.includes('containerd'))\n return 'containerd (cgroup)'\n }\n return null\n}\n\nexport function createContainerProvider(ctx: BootProviderContext): BootProvider {\n const mechanism = 'container' as const\n return {\n mechanism,\n\n async detect(): Promise<BootCandidate> {\n const container = detectContainer(ctx)\n return {\n mechanism,\n available: container !== null,\n bootCapable: false,\n privileged: false,\n reason: container === null\n ? 'this process does not look like it is inside a container'\n : `this process runs inside ${container}; an OS boot entry would live on the host, not here`,\n }\n },\n\n async status(): Promise<BootProviderStatus> {\n const container = detectContainer(ctx)\n return {\n state: 'unsupported',\n unitPath: null,\n detail: container === null\n ? 'no container detected'\n : `running inside ${container}; ${RESTART_POLICY}`,\n commands: [],\n }\n },\n\n async install(): Promise<BootActionResult> {\n const container = detectContainer(ctx)\n return failed(\n container === null\n ? `no container detected; ${RESTART_POLICY}`\n : `this process runs inside ${container}; an OS boot entry would be installed outside the container \u2014 ${RESTART_POLICY}`,\n )\n },\n\n async uninstall(): Promise<BootActionResult> {\n return failed('nothing to uninstall: this plugin installed no OS boot entry for a container; the container restart policy is what starts it')\n },\n\n async activate(): Promise<null> {\n return null\n },\n }\n}\n\nexport function createUnsupportedProvider(platform: NodeJS.Platform): BootProvider {\n const mechanism = 'unsupported' as const\n const reason = `no boot mechanism is known for ${platform}`\n return {\n mechanism,\n\n async detect(): Promise<BootCandidate> {\n return { mechanism, available: true, bootCapable: false, privileged: false, reason }\n },\n\n async status(_spec: BootSpec): Promise<BootProviderStatus> {\n return { state: 'unsupported', unitPath: null, detail: reason, commands: [] }\n },\n\n async install(_spec: BootSpec): Promise<BootActionResult> {\n return failed(`this plugin has no boot mechanism for ${platform}; start home-hosted from your platform's own service manager`)\n },\n\n async uninstall(_spec: BootSpec): Promise<BootActionResult> {\n return failed(`this plugin has no boot mechanism for ${platform}, so it installed nothing to remove`)\n },\n\n async activate(): Promise<null> {\n return null\n },\n }\n}\n", "/**\n * Writing a panel's files directly, for the case where its API cannot be used\n * (nobody is running, or its token was refused).\n *\n * On home-hosted 0.7 the state is two files, not one:\n *\n * - `.hh/<workspace>/servers.config.json` \u2014 the entries (`$schema`, `meta`, `servers`);\n * - `.hh/settings.json` \u2014 the panel's own `control` block (its port, its bind).\n *\n * When the panel *is* running this path is a fallback: its file watcher treats an\n * external edit as a real change, and a newly added `autostart` entry is started\n * at once. The managed add therefore writes the entry disabled first and only\n * then flips `autostart`, which the watcher sees as a changed definition \u2014\n * applied on that entry's next start, never started now.\n *\n * A pre-0.7 root is refused: starting the panel once relocates it.\n */\nimport fs from 'node:fs'\nimport type { ServerEntry, ServerEntryPatch } from '../shared/contracts.js'\nimport { readJson, writeFileAtomic } from '../util/fsx.js'\nimport { DEFAULT_WORKSPACE, globalSettingsFile, migrationRefusal, serversFile } from './layout.js'\n\nexport interface RawConfig {\n meta?: Record<string, unknown>\n servers?: ServerEntry[]\n [key: string]: unknown\n}\n\nexport interface GlobalSettings {\n meta?: Record<string, unknown>\n control?: Record<string, unknown>\n [key: string]: unknown\n}\n\nexport interface ConfigReadResult {\n raw: RawConfig | null\n exists: boolean\n error: string | null\n}\n\nexport interface SettingsReadResult {\n raw: GlobalSettings | null\n exists: boolean\n error: string | null\n}\n\n/** A write that cannot land where it was asked to: a root still on the old layout. */\nexport class ConfigLayoutError extends Error {\n override name = 'ConfigLayoutError'\n}\n\n/** One workspace's entries. A not-yet-migrated root is reported, never parsed. */\nexport function readConfig(home: string, workspace: string = DEFAULT_WORKSPACE): ConfigReadResult {\n const refusal = migrationRefusal(home)\n if (refusal !== null)\n return { raw: null, exists: false, error: refusal }\n const file = serversFile(home, workspace)\n const raw = readJson<RawConfig>(file)\n if (raw === null) {\n // Distinguish \"no file yet\" from \"unreadable\", because only the first is safe to create.\n const exists = fs.existsSync(file)\n return { raw: null, exists, error: exists ? 'the workspace config file could not be parsed' : null }\n }\n if (!Array.isArray(raw.servers) && raw.servers !== undefined)\n return { raw: null, exists: true, error: 'the workspace config file has a servers field that is not an array' }\n return { raw, exists: true, error: null }\n}\n\n/**\n * Write one workspace's entries back with home-hosted's own `meta` contract,\n * which records the release that wrote the file and the shape it wrote.\n *\n * `schema` is never invented here: a file that declared one keeps it, and one\n * that did not stays without it \u2014 home-hosted reads an absent schema as \"the\n * current one\", so stamping our own idea of the schema would one day fail its\n * own guard (\"this release understands schema N\") and stop the panel booting\n * from a file this plugin only patched.\n */\nexport function writeConfig(home: string, raw: RawConfig, writtenBy = 'dsh-home-hosted', workspace: string = DEFAULT_WORKSPACE): void {\n refuseUnmigrated(home)\n const meta = { ...(raw.meta ?? {}), writtenBy }\n const next: RawConfig = { ...raw, meta }\n writeFileAtomic(serversFile(home, workspace), `${JSON.stringify(next, null, 2)}\\n`)\n}\n\n/** The panel's own settings file, where 0.7 keeps `control` (listener, auth, TLS). */\nexport function readGlobalSettings(home: string): SettingsReadResult {\n const refusal = migrationRefusal(home)\n if (refusal !== null)\n return { raw: null, exists: false, error: refusal }\n const file = globalSettingsFile(home)\n const raw = readJson<GlobalSettings>(file)\n if (raw === null) {\n const exists = fs.existsSync(file)\n return { raw: null, exists, error: exists ? 'the panel settings file could not be parsed' : null }\n }\n if (raw.control !== undefined && (typeof raw.control !== 'object' || raw.control === null || Array.isArray(raw.control)))\n return { raw: null, exists: true, error: 'the panel settings file has a control field that is not an object' }\n return { raw, exists: true, error: null }\n}\n\nexport function writeGlobalSettings(home: string, raw: GlobalSettings, writtenBy = 'dsh-home-hosted'): void {\n refuseUnmigrated(home)\n const meta = { ...(raw.meta ?? {}), writtenBy }\n writeFileAtomic(globalSettingsFile(home), `${JSON.stringify({ ...raw, meta }, null, 2)}\\n`)\n}\n\n/**\n * Patch the panel's `control` block (its port, its bind), preserving every other\n * global key \u2014 `auth`, `tls`, `host` and `backups` live in the same file.\n */\nexport function setControl(home: string, patch: Record<string, unknown>, writtenBy = 'dsh-home-hosted'): void {\n const read = readGlobalSettings(home)\n if (read.error !== null)\n throw new ConfigLayoutError(read.error)\n writeGlobalSettings(home, { ...(read.raw ?? {}), control: { ...(read.raw?.control ?? {}), ...patch } }, writtenBy)\n}\n\nfunction refuseUnmigrated(home: string): void {\n const refusal = migrationRefusal(home)\n if (refusal !== null)\n throw new ConfigLayoutError(refusal)\n}\n\nexport function findEntry(raw: RawConfig, id: string): ServerEntry | null {\n return (raw.servers ?? []).find(entry => entry.id === id) ?? null\n}\n\n/** Insert or replace one entry, preserving every other key in the file. */\nexport function upsertEntry(raw: RawConfig, entry: ServerEntry): RawConfig {\n const servers = [...(raw.servers ?? [])]\n const index = servers.findIndex(candidate => candidate.id === entry.id)\n if (index >= 0)\n servers[index] = entry\n else\n servers.push(entry)\n return { ...raw, servers }\n}\n\nexport function patchEntry(raw: RawConfig, id: string, patch: ServerEntryPatch): RawConfig {\n const servers = (raw.servers ?? []).map((entry) => {\n if (entry.id !== id)\n return entry\n const merged: ServerEntry = { ...entry, ...patch }\n if (patch.stop !== undefined && typeof entry.stop === 'object' && entry.stop !== null)\n merged.stop = { ...entry.stop, ...patch.stop }\n return merged\n })\n return { ...raw, servers }\n}\n\n/** Patch a settings file's `control` block, as a pure value. */\nexport function patchControl(raw: GlobalSettings, patch: Record<string, unknown>): GlobalSettings {\n return { ...raw, control: { ...(raw.control ?? {}), ...patch } }\n}\n\nexport function removeEntry(raw: RawConfig, id: string): RawConfig {\n return { ...raw, servers: (raw.servers ?? []).filter(entry => entry.id !== id) }\n}\n", "/**\n * Where a panel keeps its state on home-hosted 0.7+.\n *\n * Everything lives under `<root>/.hh`: the global files at the top, one directory\n * per workspace. The plugin targets 0.7 and never reads a pre-0.7 root \u2014 but it\n * still *recognises* one (`isLegacyRoot`) so an install is not lost before its\n * first 0.7 start, and so a write can say what to do instead of corrupting it.\n */\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { DEFAULT_WORKSPACE } from '../shared/contracts.js'\n\nexport { DEFAULT_WORKSPACE }\n\nexport const HH_DIR = '.hh'\n\nexport function hhDir(home: string): string {\n return path.join(home, HH_DIR)\n}\n\nexport function globalSettingsFile(home: string): string {\n return path.join(hhDir(home), 'settings.json')\n}\n\nexport function workspacesFile(home: string): string {\n return path.join(hhDir(home), 'workspaces.json')\n}\n\n/** The panel-wide secrets (password hash, API token hash), 0600. */\nexport function secretsFile(home: string): string {\n return path.join(hhDir(home), '.control-secrets.json')\n}\n\n/** The live daemon's identity: pid, url, token. */\nexport function runFile(home: string): string {\n return path.join(hhDir(home), 'run.json')\n}\n\nexport function workspaceDir(home: string, workspace: string): string {\n return path.join(hhDir(home), workspace)\n}\n\nexport function serversFile(home: string, workspace: string): string {\n return path.join(workspaceDir(home, workspace), 'servers.config.json')\n}\n\nexport function workspaceSettingsFile(home: string, workspace: string): string {\n return path.join(workspaceDir(home, workspace), 'settings.json')\n}\n\nexport function workspaceSecretsFile(home: string, workspace: string): string {\n return path.join(workspaceDir(home, workspace), '.secrets.json')\n}\n\nexport function workspaceLogsDir(home: string, workspace: string): string {\n return path.join(workspaceDir(home, workspace), '.logs')\n}\n\nexport function workspaceStateDir(home: string, workspace: string): string {\n return path.join(workspaceDir(home, workspace), '.state')\n}\n\nexport interface WorkspaceEntry {\n id: string\n label: string\n}\n\nexport interface WorkspacesRead {\n workspaces: WorkspaceEntry[]\n error: string | null\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** The registry `.hh/workspaces.json` holds; an empty list is \"not started yet\". */\nexport function readWorkspaces(home: string): WorkspacesRead {\n const file = workspacesFile(home)\n if (!fs.existsSync(file))\n return { workspaces: [], error: null }\n let parsed: unknown\n try {\n parsed = JSON.parse(fs.readFileSync(file, 'utf8'))\n }\n catch {\n return { workspaces: [], error: `${path.relative(home, file)} could not be parsed` }\n }\n const list = isRecord(parsed) && Array.isArray(parsed.workspaces) ? parsed.workspaces : null\n if (list === null)\n return { workspaces: [], error: `${path.relative(home, file)} has no workspaces list` }\n const workspaces: WorkspaceEntry[] = []\n for (const entry of list) {\n if (!isRecord(entry) || typeof entry.id !== 'string' || entry.id.length === 0)\n return { workspaces: [], error: `${path.relative(home, file)} has an entry without an id` }\n workspaces.push({ id: entry.id, label: typeof entry.label === 'string' && entry.label.length > 0 ? entry.label : entry.id })\n }\n return { workspaces, error: null }\n}\n\n/** The workspace to act on when the caller names none: `default` when it exists. */\nexport function defaultWorkspace(home: string): string {\n const { workspaces } = readWorkspaces(home)\n return workspaces.find(entry => entry.id === DEFAULT_WORKSPACE)?.id ?? workspaces[0]?.id ?? DEFAULT_WORKSPACE\n}\n\n/** Whether the root has been started on 0.7: it has a registry or panel settings. */\nexport function isHhRoot(home: string): boolean {\n return fs.existsSync(workspacesFile(home)) || fs.existsSync(globalSettingsFile(home))\n}\n\n/** A pre-0.7 root: state at the top level and no `.hh` yet. Detection only. */\nexport function isLegacyRoot(home: string): boolean {\n if (isHhRoot(home))\n return false\n return ['servers.config.json', '.control-secrets.json', 'run.json', '.state']\n .some(name => fs.existsSync(path.join(home, name)))\n}\n\n/**\n * The refusal a write to a not-yet-migrated root gets, or null when it is safe.\n * Starting the panel once on 0.7 relocates the root; `home-hosted migrate` does\n * the same with a report and needs no terminal.\n */\nexport function migrationRefusal(home: string): string | null {\n if (!isLegacyRoot(home))\n return null\n return `${home} still uses the pre-0.7 layout (state at the top level). Start the panel once on home-hosted 0.7 \u2014 it moves everything into .hh \u2014 or run \\`home-hosted migrate --yes --home ${home}\\`.`\n}\n", "/**\n * The server entry that represents the running harness.\n *\n * An existing entry is never rewritten: this plugin only owns `autostart`,\n * `onPortConflict` and `stop.killPortHolders`. A generated entry (only used when\n * there is none) mirrors how this process was actually launched where that is\n * knowable, and otherwise falls back to the `dsh web` app arguments.\n *\n * The generated entry binds loopback only: `dsh` refuses `--host 0.0.0.0`, so a\n * running harness configured to bind the network cannot boot through an entry\n * that repeats its host.\n */\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport type { ServerEntry } from '../shared/contracts.js'\nimport { readJson } from '../util/fsx.js'\nimport { dshHome as resolveDshHome, pluginStateDir } from '../util/paths.js'\nimport type { CliLaunch } from './launch.js'\nimport { resolveShimmedCli, which } from './launch.js'\nimport { compareVersions } from './resolve.js'\nimport { dshCandidatesUnder, dshLauncherPath, dshSearchRoot, readDshLauncherRecord, writeDshLauncher } from './launcher.js'\n\n/**\n * Which profile this process is running.\n *\n * `DSH_PROFILE` alone is not trustworthy: it can be inherited from a parent\n * shell that ran a different profile (that is how an entry once ended up booting\n * `web` under another profile's home). The process's own argv is authoritative,\n * then the profile directory the launcher pointed at, then the env var.\n */\nexport function detectProfile(argv: readonly string[], env: Record<string, string | undefined> = {}): string {\n for (let index = 0; index < argv.length; index += 1) {\n const arg = argv[index]\n if (arg === '--profile' && typeof argv[index + 1] === 'string' && !argv[index + 1]!.startsWith('-'))\n return argv[index + 1]!\n if (arg?.startsWith('--profile='))\n return arg.slice('--profile='.length)\n }\n\n const dir = env.DSH_PROFILE_DIR\n if (typeof dir === 'string' && dir.length > 0) {\n const base = path.basename(dir)\n if (base.length > 0 && base !== '.' && base !== path.sep)\n return base\n }\n\n const app = argv.slice(2).find(candidate => candidate !== undefined && !candidate.startsWith('-'))\n if (app !== undefined && app.length > 0)\n return app\n\n const fromEnv = env.DSH_PROFILE\n return typeof fromEnv === 'string' && fromEnv.length > 0 ? fromEnv : 'web'\n}\n\nexport interface DshFacts {\n id: string\n port: number | null\n profile: string | null\n dshHome: string\n /** The project this row belongs to; defaults to this process's cwd. */\n projectDir?: string | null\n launch: CliLaunch | null\n /**\n * The stable launcher a local/clone entry must boot through, when one exists.\n * Null keeps the older shape: the resolved program and its own argv.\n */\n launcherPath?: string | null\n}\n\nexport interface DshLaunch extends CliLaunch {\n /** The stable launcher written for this launch, or null for `dsh` on PATH. */\n launcherPath: string | null\n}\n\nexport interface ResolveDshLaunchOptions {\n /** The plugin state dir the launcher is written into; defaults to `$DSH_HOME/dsh-home-hosted`. */\n stateDir?: string\n /** The harness home the resolver records as the launcher's search root. */\n dshHome?: string\n /** Test seam: what this process was launched as; defaults to `process.argv[1]`. */\n argv1?: string | null\n /** Test seam: locate a command on PATH. */\n findOnPath?: (command: string) => Promise<string | null>\n /** The project whose own dsh dependency should win; defaults to this process's cwd. */\n projectDir?: string | null\n}\n\n/** The nearest package.json's version, for ordering clone candidates. */\nfunction versionOf(entry: string): string | null {\n let dir = path.dirname(entry)\n for (let hop = 0; hop < 4; hop += 1) {\n const manifest = readJson<{ version?: string }>(path.join(dir, 'package.json'))\n if (typeof manifest?.version === 'string')\n return manifest.version\n dir = path.dirname(dir)\n }\n return null\n}\n\nfunction isEntry(file: string): boolean {\n return /\\.[cm]?js$/.test(file) && fs.existsSync(file) && !fs.statSync(file).isDirectory()\n}\n\n/** The resolved target behind a shim or symlink; the path itself when that fails. */\nfunction realPath(file: string): string {\n try {\n return fs.realpathSync(file)\n }\n catch {\n return file\n }\n}\n\n/**\n * A local clone's built entry, reconstructed from where a clone keeps its build.\n *\n * A clone installs dsh in its own tree, so `argv[1]` names a *build output*\n * (`lib/bin.js` inside `node_modules`, and after a rebuild a different one): the\n * install root is derived from it and every known build location is checked, so a\n * rebuild does not require a new boot entry.\n */\nexport function cloneEntryFrom(argv1: string): string | null {\n if (!isEntry(argv1))\n return null\n const root = path.dirname(path.dirname(argv1))\n for (const candidate of [\n path.join(root, 'lib', 'bin.js'),\n path.join(root, 'lib', 'bin.mjs'),\n path.join(root, 'lib', 'bin.cjs'),\n path.join(root, 'dist', 'bin.js'),\n path.join(root, 'bin', 'dsh.js'),\n path.join(root, 'bin', 'dsh.mjs'),\n ]) {\n if (isEntry(candidate))\n return candidate\n }\n return argv1\n}\n\n/**\n * The entry this process runs, however it was launched.\n *\n * `argv[1]` is directly the built entry for a clone or an installed copy. When\n * it is instead a package-manager shim (`.bin/dsh`), the shim's own recorded\n * target names the real entry, which is the path a boot entry must carry.\n */\nfunction launchedEntry(argv1: string): string | null {\n const real = realPath(argv1)\n const direct = cloneEntryFrom(real)\n if (direct !== null)\n return direct\n const shimmed = resolveShimmedCli(real, process.execPath)\n return shimmed?.cliEntry == null ? null : cloneEntryFrom(shimmed.cliEntry)\n}\n\n/** The best dsh entry installed under a harness home, flat or pnpm. */\nexport function findDshEntry(dshHome: string): string | null {\n const found: string[] = []\n found.push(...dshCandidatesUnder(dshHome))\n for (const name of readdirNames(path.join(dshHome, 'profiles')))\n found.push(...dshCandidatesUnder(path.join(dshHome, 'profiles', name)))\n const withVersion = found.map(entry => ({ entry, version: versionOf(entry) }))\n withVersion.sort((a, b) => compareVersions(b.version ?? '0.0.0', a.version ?? '0.0.0'))\n return withVersion[0]?.entry ?? null\n}\n\nfunction readdirNames(dir: string): string[] {\n try {\n return fs.readdirSync(dir)\n }\n catch {\n return []\n }\n}\n\n/**\n * The dsh a project declares as its own dependency.\n *\n * A project that depends on dsh has already decided which dsh belongs to it, and\n * running the global one there is how a build shares a data directory with a\n * version it was not made for. Resolution stays inside the project: the\n * package-manager shim it wrote, then the package layouts every manager\n * produces, then the pnpm store.\n *\n * Nothing here shells out to `npx`/`pnpm`: a boot entry must not need one of\n * them on PATH, must never fall back to downloading from the registry, and\n * `node_modules` is the contract npm, pnpm, yarn (node-modules linker) and bun\n * all keep.\n */\nexport function projectDshEntry(dir: string, depth = 4): string | null {\n let current = resolveDir(dir)\n for (let hop = 0; hop <= depth; hop += 1) {\n const shim = path.join(current, 'node_modules', '.bin', 'dsh')\n const shimmed = resolveShimmedCli(shim, process.execPath)\n if (shimmed?.cliEntry != null && isEntry(shimmed.cliEntry))\n return shimmed.cliEntry\n const found = dshCandidatesUnder(current)\n .map(entry => ({ entry, version: versionOf(entry) }))\n .sort((a, b) => compareVersions(b.version ?? '0.0.0', a.version ?? '0.0.0'))\n if (found[0] !== undefined)\n return found[0].entry\n const parent = path.dirname(current)\n if (parent === current)\n break\n current = parent\n }\n return null\n}\n\nfunction resolveDir(dir: string): string {\n try {\n return fs.realpathSync(dir)\n }\n catch {\n return path.resolve(dir)\n }\n}\n\n/**\n * Where the running harness's own CLI entry is.\n *\n * A local clone's build path is not stable enough to bake into a boot entry, so\n * the clone case records the entry in a generated launcher and returns that\n * launcher's path; the launcher re-finds dsh at boot. A plain `dsh` on PATH is\n * already resolved to a stable entry and keeps its current shape.\n */\nexport async function resolveDshLaunch(options: ResolveDshLaunchOptions = {}): Promise<DshLaunch | null> {\n const argv1 = options.argv1 === undefined ? process.argv[1] : options.argv1\n const dshHome = options.dshHome ?? resolveDshHome()\n const stateDir = options.stateDir ?? pluginStateDir()\n\n // A project that declares its own dsh keeps it: whoever started this process,\n // the panel must run the dsh that project installed, not one from PATH.\n const projectDir = options.projectDir === undefined ? process.cwd() : options.projectDir\n const declared = projectDir === null || projectDir.trim().length === 0 ? null : projectDshEntry(projectDir)\n if (declared !== null && realPath(declared) !== realPath(launcherFor(stateDir, declared))) {\n const launcher = writeDshLauncher({\n stateDir,\n dshHome,\n resolvedEntry: declared,\n pinnedEntry: declared,\n repin: true,\n entryExtension: path.extname(declared),\n searchRoots: [dshSearchRoot(declared), projectDir].filter((root): root is string => typeof root === 'string' && root.length > 0),\n })\n return { program: process.execPath, args: [declared], cliEntry: declared, shimPath: null, source: 'entry', launcherPath: launcher.path }\n }\n\n const local = typeof argv1 === 'string' && argv1.length > 0 ? launchedEntry(argv1) : null\n // A process already booted through the launcher is not a clone to snapshot:\n // one more layer would just reimplement the launcher. Both sides are\n // canonicalised first \u2014 on macOS a temp directory is reached as `/var/...`\n // and resolved as `/private/var/...`, and comparing those would stack a\n // second launcher on every boot.\n if (local !== null && realPath(local) !== realPath(dshLauncherPath(stateDir, path.extname(local)))) {\n const launcher = writeDshLauncher({\n stateDir,\n dshHome,\n resolvedEntry: local,\n // The running image is the one a boot entry must keep running: a global\n // copy that reports a higher version is not an upgrade of this install.\n pinnedEntry: local,\n repin: true,\n entryExtension: path.extname(local),\n // Where this install sits, so a rebuilt/moved copy is found next boot.\n searchRoots: [dshSearchRoot(local)].filter((root): root is string => root !== null),\n })\n return { program: process.execPath, args: [local], cliEntry: local, shimPath: null, source: 'entry', launcherPath: launcher.path }\n }\n\n // This process *is* the launcher's child. The launcher already decided which\n // dsh to run and recorded it; resolving again could only produce a different\n // answer (whatever `$DSH_HOME` or PATH happens to hold) and would then be\n // baked into the row \u2014 the exact drift this launcher exists to prevent.\n if (argv1 !== undefined && argv1 !== null && realPath(argv1) === realPath(dshLauncherPath(stateDir, path.extname(local ?? argv1)))) {\n const pinned = readDshLauncherRecord(stateDir)?.pinned ?? null\n if (pinned !== null) {\n const launcher = dshLauncherPath(stateDir, path.extname(pinned))\n const launch = resolveShimmedCli(pinned, process.execPath)\n const cliEntry = launch?.cliEntry ?? pinned\n // Re-assert the record for a launcher that was written before a pin\n // existed; a carried pin is not re-chosen.\n if (readDshLauncherRecord(stateDir)?.entry !== pinned)\n writeDshLauncher({ stateDir, dshHome, resolvedEntry: cliEntry, pinnedEntry: pinned, entryExtension: path.extname(pinned) })\n return { program: process.execPath, args: [launcher], cliEntry, shimPath: null, source: 'entry', launcherPath: launcher }\n }\n }\n\n // Not launched as an entry: a clone that is not on PATH still installs into\n // the harness home, and that is a far better answer than \"not found\". An\n // existing pin is the running image and outranks anything found here.\n const installed = findDshEntry(dshHome)\n if (installed !== null) {\n const launcher = writeDshLauncher({ stateDir, dshHome, resolvedEntry: installed, entryExtension: path.extname(installed) })\n return { program: process.execPath, args: [installed], cliEntry: installed, shimPath: null, source: 'entry', launcherPath: launcher.path }\n }\n\n const found = await (options.findOnPath ?? which)('dsh')\n if (found === null)\n return null\n // A launcher with no pin left is a decision for the user, not for this\n // process: returning the PATH shape would rewrite the row to that absolute\n // entry, and nothing would repair it once PATH changes again.\n if (readDshLauncherRecord(stateDir)?.pinned != null)\n return null\n const launch = resolveShimmedCli(found, process.execPath)\n return launch === null ? null : { ...launch, launcherPath: null }\n}\n\n/** The launcher a given entry would boot through, whatever its extension. */\nfunction launcherFor(stateDir: string, entry: string): string {\n return dshLauncherPath(stateDir, path.extname(entry))\n}\n\nfunction buildCommand(launch: CliLaunch, launcherPath: string | null): { command: string, entryArgs: string[] } {\n if (launcherPath !== null && launcherPath.length > 0)\n return { command: process.execPath, entryArgs: [launcherPath] }\n return { command: launch.program, entryArgs: launch.args }\n}\n\n/** The script a stored entry actually runs, out of its command and leading args. */\nfunction entryScript(config: { command?: string, args?: string[] }): string | null {\n const program = config.command\n if (typeof program !== 'string' || program.length === 0)\n return null\n // A bare command (`dsh`, `dsh.cmd`) is itself the executable. An absolute one\n // is `process.execPath` here, and the script is the argument that follows.\n if (!path.isAbsolute(program))\n return program\n const rest = config.args ?? []\n return rest.find(arg => path.isAbsolute(arg)) ?? program\n}\n\n/**\n * Re-point an entry at the stable launcher for the pinned image.\n *\n * Qualifying shapes, and only these: the entry runs the script this pattern\n * pins (a local build recorded in an earlier release), reaches that script\n * through a package-manager shim, or names a bare `dsh`/`dsh.cmd` that a later\n * PATH lookup would answer with a different copy. An entry that runs a\n * *different* dsh is somebody's deliberate choice and is never touched.\n *\n * An entry this process was booted through is also left alone: repairing it\n * would point it at itself, since that is already the launcher.\n */\n/**\n * Whether a bare `dsh` in this project reaches the image we mean.\n *\n * home-hosted resolves a bare command through `<cwd>/node_modules/.bin` first\n * and only then PATH, so a project that installed dsh as a dependency gets its\n * own copy by name \u2014 no absolute path to bake in, and it follows the package\n * manager the project actually uses.\n */\nexport function localDshCommand(projectDir: string | null | undefined, entry: string | null): string | null {\n if (typeof projectDir !== 'string' || projectDir.trim().length === 0 || entry === null)\n return null\n const shim = resolveShimmedCli(path.join(projectDir, 'node_modules', '.bin', 'dsh'))?.cliEntry ?? null\n if (shim === null)\n return null\n return realPath(shim) === realPath(entry) ? 'dsh' : null\n}\n\n/**\n * The script a boot row should run: `dsh` when this project's own shim reaches\n * the pinned image, else the stable launcher for it.\n */\nexport function pinnedScriptFor(launch: DshLaunch | null, projectDir?: string | null): string | null {\n if (launch === null)\n return null\n const entry = launch.cliEntry ?? launch.args.find(arg => path.isAbsolute(arg)) ?? null\n if (localDshCommand(projectDir, entry) === 'dsh')\n return 'dsh'\n const launcher = launch.launcherPath\n if (launcher !== null && launcher.length > 0)\n return launcher\n return entry\n}\n\n/** Whether a stored command is a bare name PATH decides, not a path we know. */\nfunction isBareCommand(stored: string): boolean {\n return !/[/\\\\]/.test(stored) && (stored === 'dsh' || /^dsh\\.(?:cmd|exe)$/i.test(stored))\n}\n\n/**\n * Whether an entry *could* need that repair, without resolving anything.\n *\n * Resolving a local dsh walks the harness home, so the write path asks this\n * first. Almost every shape can need one \u2014 an absolute command, an absolute\n * first argument, a bare `dsh` \u2014 and the cheap answer is therefore \"yes\" here\n * and a full comparison in `launcherRepair`, which is what decides whether\n * anything is actually rewritten.\n */\nexport function needsLauncherRepair(config: { command?: string, args?: string[] }): boolean {\n const program = config.command\n if (typeof program !== 'string' || program.length === 0)\n return false\n if (path.isAbsolute(program))\n return true\n if (isBareCommand(program))\n return true\n return (config.args ?? []).some(arg => path.isAbsolute(arg))\n}\n\nexport interface LauncherRepairOptions {\n /**\n * True for a row this plugin created: its command is the plugin's to set, so a\n * stored script that is neither the pinned one nor a shim to it is replaced\n * rather than respected as somebody's choice.\n */\n ownedCommand?: boolean\n}\n\nexport function launcherRepair(\n config: { command?: string, args?: string[] },\n launch: DshLaunch | null,\n options: LauncherRepairOptions & { projectDir?: string | null } = {},\n): { command: string, args: string[], cwd?: string } | null {\n const pinned = pinnedScriptFor(launch, options.projectDir)\n if (pinned === null || launch === null)\n return null\n // Two different questions: what the row should run (`targetBare`), and where\n // the old command kept its script (only a non-bare command inlined one).\n const targetBare = pinned === 'dsh'\n const storedBare = isBareCommand(entryScript(config) ?? '')\n const stored = entryScript(config)\n if (stored === null || stored === pinned)\n return null\n if (!storedBare && options.ownedCommand !== true) {\n // An entry this plugin did not create may be somebody's deliberate choice of\n // dsh, so only the script this pattern pins or a shim to it qualifies.\n const target = resolveShimmedCli(stored, process.execPath)?.cliEntry ?? stored\n if (target !== launch.cliEntry && target !== pinned)\n return null\n }\n // A bare stored command carries no script argument, so nothing is dropped: an\n // absolute app argument (`--home /Users/x/.dsh`) has to survive. Any other\n // shape loses its inlined script, wherever in the argv it sits.\n const args = config.args ?? []\n const script = storedBare ? -1 : args.findIndex(arg => path.isAbsolute(arg) || arg === pinned)\n const rest = script === -1 ? args : args.filter((_, index) => index !== script)\n // A bare command is only local-first with the right cwd, so the row carries\n // the project it was resolved from.\n return targetBare\n ? { command: 'dsh', args: rest, ...(typeof options.projectDir === 'string' && options.projectDir.length > 0 ? { cwd: options.projectDir } : {}) }\n : { command: process.execPath, args: [pinned, ...rest] }\n}\n\nexport function buildDshEntry(facts: DshFacts): ServerEntry {\n const launch = facts.launch\n const profile = facts.profile === null || facts.profile === undefined ? 'web' : facts.profile\n\n const appArgs: string[] = []\n if (facts.port !== null) {\n appArgs.push(\n '--port', '{port}',\n '--host', '127.0.0.1',\n '--no-open',\n '--trusted-host', `localhost:{port}`,\n )\n }\n\n const launcherArgs = profile === 'web' ? ['web'] : ['--profile', profile]\n // A project that declares dsh gets its own copy by name: home-hosted resolves\n // a bare command through the row's cwd and its own project dir before PATH, so\n // there is no absolute path to bake in and nothing to move when the project does.\n const projectDir = facts.projectDir === undefined || facts.projectDir === null ? process.cwd() : facts.projectDir\n const local = launch === null ? null : localDshCommand(projectDir, launch.cliEntry)\n const generated = launch === null || local === 'dsh'\n ? { command: 'dsh', entryArgs: [] as string[] }\n : buildCommand(launch, facts.launcherPath ?? null)\n\n return {\n id: facts.id,\n label: 'DSH web',\n enabled: true,\n autostart: true,\n command: generated.command,\n args: [...generated.entryArgs, ...launcherArgs, ...appArgs],\n bind: 'local',\n port: facts.port,\n cwd: projectDir,\n env: {},\n dataEnvs: { DSH_HOME: facts.dshHome },\n onPortConflict: 'kill',\n stop: { killPortHolders: true },\n health: {\n enabled: true,\n mode: 'http',\n // 401 is normal here: the panel answers the login route when auth is on.\n http: { path: '/', method: 'GET', expectStatusBelow: 500, expectBody: '' },\n },\n }\n}\n", "/**\n * Where things live. The harness home comes from `$DSH_HOME`; home-hosted's\n * state comes from `$HHOSTED_HOME`. Neither is ever hard-coded into a file this\n * plugin writes \u2014 the resolved value is written into generated units instead.\n */\nimport crypto from 'node:crypto'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { isHhRoot, runFile } from '../home-hosted/layout.js'\n\nexport function expandHome(value: string): string {\n if (value === '~')\n return os.homedir()\n if (value.startsWith('~/') || value.startsWith('~\\\\'))\n return path.join(os.homedir(), value.slice(2))\n return value\n}\n\n/** A configured path, resolved against one home \u2014 never against the cwd. */\nfunction resolveUnder(value: string, homeDir: string): string {\n if (value === '~')\n return path.resolve(homeDir)\n if (value.startsWith('~/') || value.startsWith('~\\\\'))\n return path.resolve(path.join(homeDir, value.slice(2)))\n return path.isAbsolute(value) ? path.resolve(value) : path.resolve(homeDir, value)\n}\n\n/** DeepSeek Harness home: `$DSH_HOME`, else `~/.dsh`. */\nexport function dshHome(): string {\n const configured = process.env.DSH_HOME?.trim()\n return configured ? path.resolve(expandHome(configured)) : path.join(os.homedir(), '.dsh')\n}\n\n/**\n * The panel root one plugin instance drives.\n *\n * Two dsh installs share `~/.dsh` far more often than they should, and a shared\n * panel root means two plugins editing one workspace config and one boot\n * entry. So each instance normally gets its own root beside its own state.\n *\n * An explicit `$HHOSTED_HOME` is an instruction and always wins. So is a panel\n * already living at the machine-wide default: an upgrade must never point a\n * running panel somewhere else, or it would look like every server vanished.\n */\nexport function resolveHomeHostedHome(\n stateDir: string,\n env: NodeJS.ProcessEnv = process.env,\n homeDir: string = os.homedir(),\n): { home: string, source: 'env' | 'legacy' | 'instance' } {\n const configured = env.HHOSTED_HOME?.trim()\n if (configured)\n return { home: resolveUnder(configured, homeDir), source: 'env' }\n const state = resolveUnder(stateDir, homeDir)\n const own = path.join(state, 'panel')\n // Once this instance has a root of its own, it stays there: a root it has\n // already used is never traded for the machine-wide one.\n if (fs.existsSync(own))\n return { home: own, source: 'instance' }\n // The machine-wide panel is only this instance's to adopt when this instance\n // is the machine's own harness home, or when it has run before (its settings\n // are there). A home created fresh \u2014 a scratch `DSH_HOME`, a test project \u2014\n // never reaches over and drives the panel some other install owns.\n const legacy = path.join(homeDir, '.home-hosted')\n if (isEstablishedInstance(state, homeDir) && hasLivePanel(legacy))\n return { home: legacy, source: 'legacy' }\n return { home: own, source: 'instance' }\n}\n\n/** Whether this state dir is the machine's own install, or one that has run before. */\nfunction isEstablishedInstance(stateDir: string, homeDir: string): boolean {\n if (stateDir === path.join(homeDir, '.dsh', 'dsh-home-hosted'))\n return true\n try {\n return fs.statSync(path.join(stateDir, 'settings.json')).isFile()\n }\n catch {\n return false\n }\n}\n\n/**\n * Whether a root holds a panel this instance may adopt: a 0.7 `.hh`, a pre-0.7\n * root with an actual servers config, or a `run.json` whose pid is still alive.\n *\n * A `run.json` alone is not proof \u2014 a killed panel leaves one behind \u2014 so it only\n * counts while its process answers. The same goes for the other top-level\n * leftovers (`.control-secrets.json`, `.state`): they are not a panel.\n */\nfunction hasLivePanel(home: string): boolean {\n if (isHhRoot(home) || fs.existsSync(path.join(home, 'servers.config.json')))\n return true\n for (const file of [runFile(home), path.join(home, 'run.json')]) {\n try {\n const raw = JSON.parse(fs.readFileSync(file, 'utf8')) as { pid?: unknown }\n if (typeof raw.pid !== 'number')\n continue\n process.kill(raw.pid, 0)\n return true\n }\n catch {\n // no runtime here; try the next one\n }\n }\n return false\n}\n\n/**\n * The boot artifact one state root owns. Only the machine-wide default keeps the\n * historical name: keying on the running process's own `$DSH_HOME` would give\n * two differently-homed installs the same name, which is the collision this\n * naming exists to prevent.\n */\nexport function bootUnitName(stateDir: string): string {\n if (isMachineDefaultStateDir(stateDir))\n return 'home-hosted'\n const key = path.resolve(expandHome(stateDir?.trim() ?? ''))\n return `home-hosted-${crypto.createHash('sha256').update(key).digest('hex').slice(0, 8)}`\n}\n\nfunction isMachineDefaultStateDir(stateDir: string): boolean {\n const bare = stateDir?.trim()\n if (bare === undefined || bare.length === 0)\n return true\n return path.resolve(expandHome(bare)) === path.join(os.homedir(), '.dsh', 'dsh-home-hosted')\n}\n\n/** Where this plugin keeps its own durable state (settings, snapshots, token). */\nexport function pluginStateDir(override?: string): string {\n const configured = override?.trim()\n if (configured)\n return path.resolve(expandHome(configured))\n return path.join(dshHome(), 'dsh-home-hosted')\n}\n\n", "/**\n * How the home-hosted daemon is launched, and what a boot entry for it contains.\n *\n * The executable is resolved to an absolute program plus argv: a generated boot\n * entry must not depend on a login shell's PATH. When the resolved command is a\n * package-manager shim, its `cmd-shim-target` comment names the real entry, and\n * node runs that directly \u2014 the same guarantee the shim itself gives.\n */\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport type { BootSpec } from '../boot/types.js'\nimport { run } from '../util/exec.js'\nimport { readText } from '../util/fsx.js'\nimport { bootUnitName } from '../util/paths.js'\n\n/** The artifact name every install wrote before it was per state root. */\nexport const LEGACY_BOOT_UNIT_NAME = 'home-hosted'\n\nexport interface CliLaunch {\n program: string\n /** Args that must precede the command, e.g. the CLI entry path. */\n args: string[]\n cliEntry: string | null\n shimPath: string | null\n source: 'override' | 'entry' | 'shim-target' | 'shim'\n}\n\nexport async function which(command: string): Promise<string | null> {\n const probe = process.platform === 'win32' ? 'where.exe' : 'which'\n const result = await run(probe, [command], { timeoutMs: 5000 })\n if (result.code !== 0)\n return null\n const first = result.stdout.split(/\\r?\\n/).map(line => line.trim()).filter(Boolean)[0]\n return first ?? null\n}\n\n/** Turn a resolved CLI path into an absolute program + argv. */\nexport function resolveShimmedCli(absolute: string, nodePath = process.execPath): CliLaunch | null {\n if (!fs.existsSync(absolute))\n return null\n if (/\\.(?:mjs|cjs|js)$/.test(absolute))\n return { program: nodePath, args: [absolute], cliEntry: absolute, shimPath: null, source: 'entry' }\n\n const shimTarget = /^#\\s*cmd-shim-target=(.+)$/m.exec(readText(absolute) ?? '')?.[1]?.trim()\n if (shimTarget !== undefined && fs.existsSync(shimTarget))\n return { program: nodePath, args: [shimTarget], cliEntry: shimTarget, shimPath: absolute, source: 'shim-target' }\n\n // A shim with no readable target still works when node is on the entry's PATH.\n return { program: absolute, args: [], cliEntry: null, shimPath: absolute, source: 'shim' }\n}\n\nexport async function resolveHomeHostedLaunch(override?: string | null): Promise<CliLaunch | null> {\n const configured = override?.trim()\n const found = configured && configured.length > 0 ? configured : (await which('home-hosted')) ?? (await which('hh'))\n if (found === null || found === undefined)\n return null\n\n const launch = resolveShimmedCli(path.resolve(found))\n if (launch === null)\n return null\n return configured ? { ...launch, source: 'override' } : launch\n}\n\nexport interface HomeHostedFacts {\n home: string\n projectDir?: string | null\n version?: string | null\n /**\n * The home of the account the entry runs as.\n *\n * `HOME` is written into every entry's environment, and that is the problem:\n * installed through `sudo`, it is root's home, so the panel the entry starts\n * resolves `~` (and anything else HOME-relative) into `/root` and dies with a\n * permission error that never mentions its own user.\n *\n * `undefined` means account resolution was not involved, so this process's own\n * `HOME` is right. A string is that account's home. `null` means an account *was*\n * resolved but its home is not knowable here \u2014 an LDAP or NIS login has no\n * `/etc/passwd` row \u2014 and then the line is *omitted* rather than left at the\n * installer's, because an explicit `Environment=HOME=` overrides `User=` and\n * systemd fills in the right one from NSS itself.\n */\n accountHome?: string | null\n /**\n * The environment this process's own values (`HOME`, `PATH`) are read from.\n *\n * Injected so a caller that decides the entry's account from a given environment\n * also builds the entry's own environment from it: reading `process.env` here\n * while resolving the account elsewhere is how the two came to describe\n * different people.\n */\n env?: Readonly<Record<string, string | undefined>>\n}\n\n/** A deterministic environment for a boot-time entry, with absolute values. */\nexport function homeHostedEnv(facts: HomeHostedFacts, launch: CliLaunch, extra: Record<string, string> = {}): Record<string, string> {\n const dirs = [\n launch.shimPath === null ? null : path.dirname(launch.shimPath),\n path.dirname(process.execPath),\n ...(process.env.PATH ?? '').split(path.delimiter),\n '/usr/local/bin',\n '/usr/bin',\n '/bin',\n ].filter((dir): dir is string => typeof dir === 'string' && dir.length > 0)\n const seen = new Set<string>()\n const pathValue = dirs.filter((dir) => {\n if (seen.has(dir))\n return false\n seen.add(dir)\n return true\n }).join(path.delimiter)\n\n const env: Record<string, string> = {\n PATH: pathValue,\n HOME: process.env.HOME ?? process.env.USERPROFILE ?? '',\n HHOSTED_HOME: facts.home,\n ...extra,\n }\n // Only when it is genuinely a *different* home: a spec that merely repeats this\n // process's own environment stays byte-identical, so an existing entry is not\n // rewritten on every plugin start.\n const accountHome = facts.accountHome?.trim()\n if (accountHome !== undefined && accountHome.length > 0) {\n if (accountHome !== env.HOME)\n env.HOME = accountHome\n }\n else if (facts.accountHome === null) {\n // An account was resolved but its home is not knowable here. Dropping the line\n // is the only safe answer: leaving it would hand the panel the installer's\n // home (root's), and `User=` cannot correct an explicit `Environment=HOME=`.\n delete env.HOME\n }\n if (facts.projectDir)\n env.HHOSTED_PROJECT = facts.projectDir\n return env\n}\n\nexport interface BootSpecOptions {\n stateDir: string\n /** The artifact name to write; defaults to the one this state root owns. */\n unitName?: string\n marker?: string\n /** The account's home, so an entry launched by root does not inherit `/root`. */\n accountHome?: string | null\n /** The environment this process's own values are read from. */\n env?: Readonly<Record<string, string | undefined>>\n}\n\nexport function buildHomeHostedBootSpec(\n facts: HomeHostedFacts,\n launch: CliLaunch,\n options: BootSpecOptions,\n): BootSpec {\n const logDir = path.join(facts.home, '.logs')\n fs.mkdirSync(logDir, { recursive: true })\n const args = [...launch.args, 'up', '--foreground', '--home', facts.home]\n if (facts.projectDir)\n args.push('--project', facts.projectDir)\n\n return {\n command: launch.program,\n args,\n cwd: facts.projectDir ?? facts.home,\n env: homeHostedEnv(\n {\n ...facts,\n // `undefined` means the option was not given; `null` is a decision (an\n // account whose home is unknowable), and `??` would erase it.\n accountHome: options.accountHome === undefined ? facts.accountHome : options.accountHome,\n env: options.env ?? facts.env,\n },\n launch,\n ),\n marker: options.marker ?? 'managed by dsh-home-hosted',\n // One artifact per state root, so two plugin instances cannot overwrite or\n // delete each other's entry. The machine-default install keeps the old name.\n unitName: options.unitName ?? bootUnitName(options.stateDir),\n label: 'home-hosted control panel',\n logDir,\n }\n}\n", "/**\n * Which home-hosted CLI this plugin drives.\n *\n * The plugin ships its own pinned copy as a dependency and prefers it, so the\n * version that runs the panel at boot is the version this plugin was built\n * against \u2014 not whatever `home-hosted` happens to be on PATH. Preferring a\n * global install is a supported setting, not an accident.\n *\n * Resolution order: the operator's `homeHostedCommand` override always wins,\n * then the preferred copy, then the other one as a fallback.\n */\nimport { createRequire } from 'node:module'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport type { CliCandidate, CliSource, CliStatus } from '../shared/contracts.js'\nimport { run } from '../util/exec.js'\nimport type { CliLaunch } from './launch.js'\nimport { resolveShimmedCli, which } from './launch.js'\n\n/** The range the plugin ships and is tested against. */\nexport const EXPECTED_RANGE = '^0.7.1'\n\n/** Oldest release whose API and config schema this plugin relies on. */\nexport const MIN_SUPPORTED_VERSION = '0.7.0'\n\nexport type CliPreference = 'pinned' | 'global'\n\nexport interface CliResolution {\n launch: CliLaunch | null\n status: CliStatus\n}\n\nexport interface ResolveCliOptions {\n override?: string | null\n /** Which copy to run when both exist. */\n prefer?: CliPreference\n /** Test seam: the pinned package's manifest path, or null when it is not installed. */\n packageManifest?: () => string | null\n /** Test seam: ask a CLI for its own version. */\n readVersion?: (launch: CliLaunch) => Promise<string | null>\n /** Test seam: locate a command on PATH. */\n findOnPath?: (command: string) => Promise<string | null>\n timeoutMs?: number\n}\n\ninterface Candidate {\n candidate: CliCandidate\n launch: CliLaunch\n}\n\n/** Where the pinned `home-hosted` manifest is, resolved from this module. */\nexport function pinnedManifestPath(): string | null {\n try {\n return createRequire(import.meta.url).resolve('home-hosted/package.json')\n }\n catch {\n return null\n }\n}\n\n/** The `home-hosted` bin entry a package manifest declares. */\nexport function binEntryFromManifest(manifestPath: string): string | null {\n try {\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as { bin?: string | Record<string, string> }\n const declared = typeof manifest.bin === 'string'\n ? manifest.bin\n : manifest.bin?.['home-hosted'] ?? Object.values(manifest.bin ?? {})[0]\n return typeof declared === 'string' && declared.length > 0\n ? path.resolve(path.dirname(manifestPath), declared)\n : null\n }\n catch {\n return null\n }\n}\n\n/** The first `x.y.z[-pre]` in a CLI's output. */\nexport function parseVersion(output: string | null): string | null {\n const match = /(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?/.exec(output ?? '')\n return match === null ? null : `${match[1]}.${match[2]}.${match[3]}${match[4] === undefined ? '' : `-${match[4]}`}`\n}\n\n/** Numeric comparison; a prerelease sorts below its own release. */\nexport function compareVersions(a: string, b: string): number {\n const split = (value: string): { parts: number[], pre: string | null } => {\n const [core, pre = null] = value.split('-', 2)\n return { parts: (core ?? '').split('.').map(part => Number.parseInt(part, 10) || 0), pre }\n }\n const left = split(a)\n const right = split(b)\n for (let index = 0; index < 3; index += 1) {\n const diff = (left.parts[index] ?? 0) - (right.parts[index] ?? 0)\n if (diff !== 0)\n return diff < 0 ? -1 : 1\n }\n if (left.pre === right.pre)\n return 0\n if (left.pre === null)\n return 1\n if (right.pre === null)\n return -1\n return left.pre < right.pre ? -1 : 1\n}\n\nfunction detailFor(source: CliSource, version: string | null, supported: boolean, prefer: CliPreference, unusableOverride: string | null = null): string {\n const shown = version ?? 'unknown version'\n switch (source) {\n case 'config':\n return unusableOverride === null\n ? `from the configured command (${shown})`\n : `the configured command could not be used: ${unusableOverride} does not exist (or is not a file this plugin can run); fix homeHostedCommand, or clear it to let the plugin choose`\n case 'dependency':\n return supported\n ? `the pinned dependency (${shown})`\n : `the pinned dependency is below the oldest supported release (${MIN_SUPPORTED_VERSION})`\n case 'path':\n if (!supported)\n return `the global install on PATH (${shown}) is below the oldest supported release (${MIN_SUPPORTED_VERSION})`\n return prefer === 'global'\n ? `the global install on PATH (${shown})`\n : `a global install on PATH (${shown}), not the pinned dependency ${EXPECTED_RANGE}`\n default:\n return 'no home-hosted CLI found: install the plugin with its dependencies, or put home-hosted on PATH'\n }\n}\n\nasync function defaultReadVersion(launch: CliLaunch, timeoutMs: number): Promise<string | null> {\n const result = await run(launch.program, [...launch.args, '--version'], { timeoutMs })\n return parseVersion(result.stdout) ?? parseVersion(result.stderr)\n}\n\nexport async function resolveCli(options: ResolveCliOptions = {}): Promise<CliResolution> {\n const timeoutMs = options.timeoutMs ?? 5000\n const prefer = options.prefer ?? 'pinned'\n const readVersion = options.readVersion ?? (launch => defaultReadVersion(launch, timeoutMs))\n const locate = options.findOnPath ?? which\n\n // 1. An explicit override is not a preference: it is an instruction. An\n // instruction that cannot be carried out is reported, never silently\n // replaced by a copy the operator did not ask for.\n const override = options.override?.trim()\n let unusableOverride: string | null = null\n if (override !== undefined && override.length > 0) {\n const launch = resolveShimmedCli(path.resolve(override))\n if (launch === null) {\n unusableOverride = override\n }\n else {\n const version = await readVersion(launch)\n const supported = version === null || compareVersions(version, MIN_SUPPORTED_VERSION) >= 0\n return {\n launch,\n status: {\n source: 'config',\n path: launch.cliEntry ?? launch.shimPath ?? launch.program,\n version,\n expectedRange: EXPECTED_RANGE,\n supported,\n prefer,\n dependency: null,\n global: null,\n detail: detailFor('config', version, supported, prefer),\n },\n }\n }\n }\n\n // 2. The two copies this plugin knows about.\n const manifest = (options.packageManifest ?? pinnedManifestPath)()\n const dependencyEntry = manifest === null ? null : binEntryFromManifest(manifest)\n let dependency: Candidate | null = null\n if (dependencyEntry !== null && fs.existsSync(dependencyEntry)) {\n let version: string | null = null\n try {\n version = (JSON.parse(fs.readFileSync(manifest as string, 'utf8')) as { version?: string }).version ?? null\n }\n catch {\n version = null\n }\n dependency = {\n candidate: { source: 'dependency', path: dependencyEntry, version },\n launch: { program: process.execPath, args: [dependencyEntry], cliEntry: dependencyEntry, shimPath: null, source: 'entry' },\n }\n }\n\n let global: Candidate | null = null\n const found = (await locate('home-hosted')) ?? (await locate('hh'))\n if (found !== null) {\n const launch = resolveShimmedCli(path.resolve(found))\n if (launch !== null) {\n global = {\n candidate: { source: 'path', path: launch.cliEntry ?? launch.shimPath ?? launch.program, version: await readVersion(launch) },\n launch,\n }\n }\n }\n\n const order = prefer === 'global' ? [global, dependency] : [dependency, global]\n const chosen = order.find(item => item !== null) ?? null\n const dependencySummary = dependency?.candidate ?? null\n const globalSummary = global?.candidate ?? null\n\n if (unusableOverride !== null) {\n return {\n launch: null,\n status: {\n source: 'config',\n path: unusableOverride,\n version: null,\n expectedRange: EXPECTED_RANGE,\n supported: false,\n prefer,\n dependency: dependencySummary,\n global: globalSummary,\n detail: detailFor('config', null, false, prefer, unusableOverride),\n },\n }\n }\n\n if (chosen === null) {\n return {\n launch: null,\n status: {\n source: 'none',\n path: null,\n version: null,\n expectedRange: EXPECTED_RANGE,\n supported: false,\n prefer,\n dependency: dependencySummary,\n global: globalSummary,\n detail: detailFor('none', null, false, prefer),\n },\n }\n }\n\n const version = chosen.candidate.version\n const supported = version === null || compareVersions(version, MIN_SUPPORTED_VERSION) >= 0\n return {\n launch: chosen.launch,\n status: {\n source: chosen.candidate.source,\n path: chosen.candidate.path,\n version,\n expectedRange: EXPECTED_RANGE,\n supported,\n prefer,\n dependency: dependencySummary,\n global: globalSummary,\n detail: detailFor(chosen.candidate.source, version, supported, prefer),\n },\n }\n}\n", "/**\n * A stable launcher for the pinned CLI.\n *\n * A dependency resolves to a pnpm path that carries the version and the peer\n * hash (`\u2026/.pnpm/home-hosted@0.6.1_zod@4.6.5/node_modules/home-hosted/\u2026`), which\n * moves on the next install and disappears when the profile is rebuilt. A boot\n * entry that baked that path in would fail at exactly the moment it exists for.\n *\n * So the plugin generates one small script in its own state directory and points\n * the boot entry at *that*. At boot the launcher finds the pinned copy again\n * (recorded path \u2192 profile node_modules, flat or pnpm) and falls back to PATH,\n * which is why the entry survives plugin upgrades and profile reinstalls.\n */\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { run } from '../util/exec.js'\nimport { readJson, writeFileAtomic, writeJsonAtomic } from '../util/fsx.js'\nimport { compareVersions, parseVersion } from './resolve.js'\n\n/** A package-manager shim's real target, as the shim itself records it. */\nconst SHIM_TARGET = /^#\\s*cmd-shim-target=(.+)$/m\n\nexport interface LauncherOptions {\n stateDir: string\n dshHome: string\n /** Where the pinned copy is right now; recorded as the fast path. */\n resolvedEntry: string | null\n /** The pinned copy's version, recorded for diagnostics. */\n resolvedVersion?: string | null\n /** The plugin's own package root, as a search hint. */\n pluginRoot?: string | null\n minVersion: string\n marker?: string\n}\n\nexport interface LauncherWrite {\n path: string\n changed: boolean\n}\n\ninterface LauncherRecord {\n entry: string | null\n version: string | null\n pluginRoot: string | null\n writtenAt: number\n}\n\nexport function launcherDir(stateDir: string): string {\n return path.join(stateDir, 'bin')\n}\n\nexport function launcherPath(stateDir: string): string {\n return path.join(launcherDir(stateDir), 'home-hosted.mjs')\n}\n\nexport function launcherRecordPath(stateDir: string): string {\n return path.join(launcherDir(stateDir), 'resolved.json')\n}\n\nexport function readLauncherRecord(stateDir: string): LauncherRecord | null {\n return readJson<LauncherRecord>(launcherRecordPath(stateDir))\n}\n\n/** The generated script's source; exported so a test can inspect it. */\nexport function buildLauncherSource(options: LauncherOptions): string {\n const marker = options.marker ?? 'managed by dsh-home-hosted'\n const record = launcherRecordPath(options.stateDir)\n return `#!/usr/bin/env node\n// ${marker} \u2014 rewritten on every plugin start; do not edit.\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { spawnSync } from 'node:child_process'\n\nconst DSH_HOME = ${JSON.stringify(options.dshHome)}\nconst RECORD = ${JSON.stringify(record)}\nconst MIN_VERSION = ${JSON.stringify(options.minVersion)}\nconst PLUGIN_ROOT = ${JSON.stringify(options.pluginRoot ?? null)}\n\n/** A package-manager shim's real target, as the shim itself records it. */\nconst SHIM_TARGET = /^#\\\\s*cmd-shim-target=(.+)$/m\n\nfunction readRecord() {\n try { return JSON.parse(fs.readFileSync(RECORD, 'utf8')) } catch { return null }\n}\n\n/**\n * The version of the package a CLI entry belongs to, following a package-manager\n * shim to the file it actually runs.\n *\n * A pnpm/npm shim is a shell script whose 'cmd-shim-target' comment names the real\n * entry \u2014 and it lives *inside* the plugin's own package, so walking up from the\n * shim reads this plugin's package.json and reports the plugin's version for the\n * CLI. That is not cosmetic: 'choose' sorts by version, so the plugin's own number\n * let a PATH fallback outrank the recorded pinned copy and boot a different\n * home-hosted than this plugin shipped.\n */\nfunction versionOf(entry) {\n let target = entry\n try {\n if (fs.statSync(entry).isFile()) {\n const shimTarget = SHIM_TARGET.exec(fs.readFileSync(entry, 'utf8'))?.[1]?.trim()\n if (shimTarget !== undefined && shimTarget.length > 0) target = shimTarget\n }\n } catch {}\n let dir = path.dirname(target)\n for (let hop = 0; hop < 4; hop += 1) {\n try {\n const manifest = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'))\n if (typeof manifest.version === 'string') return manifest.version\n } catch {}\n dir = path.dirname(dir)\n }\n return null\n}\n\nfunction compare(a, b) {\n const split = value => {\n const [core, pre = null] = String(value).split('-', 2)\n return { parts: core.split('.').map(part => Number.parseInt(part, 10) || 0), pre }\n }\n const left = split(a); const right = split(b)\n for (let i = 0; i < 3; i += 1) {\n const diff = (left.parts[i] ?? 0) - (right.parts[i] ?? 0)\n if (diff !== 0) return diff < 0 ? -1 : 1\n }\n if (left.pre === right.pre) return 0\n if (left.pre === null) return 1\n if (right.pre === null) return -1\n return left.pre < right.pre ? -1 : 1\n}\n\nfunction collect(out, entry, tier) {\n if (typeof entry === 'string' && entry.length > 0 && fs.existsSync(entry)) out.push({ entry, tier })\n}\n\nfunction collectRoot(root, out, tier) {\n collect(out, path.join(root, 'node_modules', 'home-hosted', 'bin', 'home-hosted.mjs'), tier)\n const store = path.join(root, 'node_modules', '.pnpm')\n let names = []\n try { names = fs.readdirSync(store) } catch { return }\n for (const name of names) {\n if (name.startsWith('home-hosted@') || name.startsWith('dsh-home-hosted@'))\n collect(out, path.join(store, name, 'node_modules', 'home-hosted', 'bin', 'home-hosted.mjs'), tier)\n }\n}\n\n/**\n * Where a copy can come from, best first. A plain version sort let a copy of the\n * plugin's own version \u2014 read from the package-manager shim, or an unrelated\n * global install \u2014 outrank the copy the plugin actually pinned. A recorded pin is\n * an instruction; PATH is a hope.\n */\nconst TIER_RECORD = 0\nconst TIER_LOCAL = 1\nconst TIER_PATH = 2\n\nfunction candidates() {\n const out = []\n const record = readRecord()\n if (record && typeof record.entry === 'string') collect(out, record.entry, TIER_RECORD)\n if (PLUGIN_ROOT !== null) collectRoot(PLUGIN_ROOT, out, TIER_LOCAL)\n try {\n for (const name of fs.readdirSync(path.join(DSH_HOME, 'profiles')))\n collectRoot(path.join(DSH_HOME, 'profiles', name), out, TIER_LOCAL)\n } catch {}\n collectRoot(path.join(DSH_HOME), out, TIER_LOCAL)\n // Last resort: a global install on the entry's own PATH.\n for (const dir of (process.env.PATH ?? '').split(path.delimiter)) {\n if (dir.length === 0) continue\n collect(out, path.join(dir, 'home-hosted'), TIER_PATH)\n collect(out, path.join(dir, 'home-hosted.cmd'), TIER_PATH)\n }\n return out\n}\n\n/** The best copy within a tier: the highest version, first-found when equal. */\nfunction bestOfTier(items) {\n const known = items.filter(item => item.version !== null)\n const pool = known.length === 0 ? items : known\n return [...pool].sort((a, b) => compare(b.version ?? '0.0.0', a.version ?? '0.0.0'))[0] ?? null\n}\n\nfunction choose() {\n const found = candidates().map(item => ({ ...item, version: versionOf(item.entry) }))\n const supported = found.filter(item => item.version !== null && compare(item.version, MIN_VERSION) >= 0)\n for (const tier of [TIER_RECORD, TIER_LOCAL, TIER_PATH]) {\n const best = bestOfTier(supported.filter(item => item.tier === tier))\n if (best !== null) return best.entry\n }\n // Nothing supported: a too-old copy still beats no panel at all.\n for (const tier of [TIER_RECORD, TIER_LOCAL, TIER_PATH]) {\n const best = bestOfTier(found.filter(item => item.tier === tier))\n if (best !== null) return best.entry\n }\n return null\n}\n\nconst entry = choose()\nif (entry === null) {\n console.error('[dsh-home-hosted] no home-hosted CLI found (looked in ' + DSH_HOME + ' and PATH). Reinstall the plugin with its dependencies, or set homeHostedCommand in the plugin row.')\n process.exit(1)\n}\n\nconst args = process.argv.slice(2)\nconst isScript = /\\\\.(mjs|cjs|js)$/.test(entry)\nconst result = isScript\n ? spawnSync(process.execPath, [entry, ...args], { stdio: 'inherit' })\n : spawnSync(entry, args, { stdio: 'inherit', shell: process.platform === 'win32' && /\\\\.(cmd|bat)$/i.test(entry) })\nprocess.exit(typeof result.status === 'number' ? result.status : 1)\n`\n}\n\nexport function writeLauncher(options: LauncherOptions): LauncherWrite {\n const file = launcherPath(options.stateDir)\n const source = buildLauncherSource(options)\n const previous = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : null\n const changed = previous !== source\n if (changed) {\n writeFileAtomic(file, source, 0o755)\n fs.chmodSync(file, 0o755)\n }\n writeJsonAtomic(launcherRecordPath(options.stateDir), {\n entry: options.resolvedEntry,\n version: options.resolvedVersion ?? null,\n pluginRoot: options.pluginRoot ?? null,\n writtenAt: Date.now(),\n } satisfies LauncherRecord, 0o600)\n return { path: file, changed }\n}\n\n/**\n * Run the launcher the way the boot entry will, and read the version it answers.\n *\n * `env` is only for tests that have to describe the PATH the entry would see; a\n * boot entry inherits a login PATH, never the one this process happens to have.\n */\nexport async function preflightLauncher(stateDir: string, timeoutMs = 10_000, env?: Record<string, string | undefined>): Promise<string | null> {\n const file = launcherPath(stateDir)\n if (!fs.existsSync(file))\n return null\n const result = process.platform === 'win32'\n ? await run(process.execPath, [file, '--version'], { timeoutMs, ...(env === undefined ? {} : { env }) })\n : await run(file, ['--version'], { timeoutMs, ...(env === undefined ? {} : { env }) })\n return parseVersion(result.stdout) ?? parseVersion(result.stderr)\n}\n\n/**\n * The file a package-manager shim actually runs, or null when `entry` is a real\n * entry rather than a shim. Read for the same reason the generated launcher reads\n * it: the shim sits inside the plugin's own package.\n */\nfunction shimTargetOf(entry: string): string | null {\n try {\n if (!fs.statSync(entry).isFile())\n return null\n const target = SHIM_TARGET.exec(fs.readFileSync(entry, 'utf8'))?.[1]?.trim()\n return target !== undefined && target.length > 0 ? target : null\n }\n catch {\n return null\n }\n}\n\n/** The version a CLI entry belongs to, following a shim to what it really runs. */\nexport function versionOfEntry(entry: string): string | null {\n let target = entry\n const shimTarget = shimTargetOf(entry)\n if (shimTarget !== null) target = shimTarget\n let dir = path.dirname(target)\n for (let hop = 0; hop < 4; hop += 1) {\n const manifest = readJson<{ version?: string }>(path.join(dir, 'package.json'))\n if (typeof manifest?.version === 'string')\n return manifest.version\n dir = path.dirname(dir)\n }\n return null\n}\n\n/** Candidate entries under one root: a flat install, or a pnpm store. */\nexport function candidatesUnder(root: string): string[] {\n const out: string[] = []\n const direct = path.join(root, 'node_modules', 'home-hosted', 'bin', 'home-hosted.mjs')\n if (fs.existsSync(direct))\n out.push(direct)\n const store = path.join(root, 'node_modules', '.pnpm')\n if (fs.existsSync(store)) {\n for (const name of fs.readdirSync(store)) {\n if (!name.startsWith('home-hosted@') && !name.startsWith('dsh-home-hosted@'))\n continue\n const entry = path.join(store, name, 'node_modules', 'home-hosted', 'bin', 'home-hosted.mjs')\n if (fs.existsSync(entry))\n out.push(entry)\n }\n }\n return out\n}\n\n/** Highest satisfying candidate among explicit roots; exported for tests. */\nexport function findCandidate(roots: string[], minVersion: string): string | null {\n const found = roots.flatMap(root => candidatesUnder(root).map(entry => ({ entry, version: versionOfEntry(entry) })))\n const supported = found.filter(item => item.version !== null && compareVersions(item.version, minVersion) >= 0)\n supported.sort((a, b) => compareVersions(b.version ?? '0.0.0', a.version ?? '0.0.0'))\n return (supported[0] ?? found[0] ?? null)?.entry ?? null\n}\n\n// ---------------------------------------------------------------------------\n// The running harness's own launcher\n// ---------------------------------------------------------------------------\n\nexport interface DshLauncherOptions {\n stateDir: string\n dshHome: string\n /** Where the dsh entry is right now; recorded as the fast path. */\n resolvedEntry: string | null\n /**\n * The image this plugin was installed on: the dsh a boot entry must keep\n * running. Recorded and checked before anything else, so a copy that merely\n * reports a higher version never wins the boot.\n */\n pinnedEntry?: string | null\n /**\n * Adopt `pinnedEntry` even when the record already pins something else. Set\n * only where the caller *is* the running image; a process booted through the\n * launcher must carry the pin it started under instead of replacing it.\n */\n repin?: boolean\n /** The extension the launcher must match: a `.cjs` entry gets a `.cjs` launcher. */\n entryExtension?: string | null\n /**\n * Install roots that are neither the harness home nor PATH \u2014 a clone, or a\n * project whose own `node_modules` carries dsh. Recorded and searched, so a\n * moved build is re-found on the next boot.\n */\n searchRoots?: string[] | null\n}\n\nexport interface DshLauncherWrite {\n path: string\n changed: boolean\n}\n\nexport interface DshLauncherRecord {\n entry: string | null\n /**\n * The running image, while it still exists. A path here is an instruction:\n * `choose()` runs this one and never sorts it against another copy.\n */\n pinned?: string | null\n /** When that image was last confirmed. */\n pinnedAt?: number | null\n /** Where that entry's install sat, kept across rewrites while it stays valid. */\n roots: string[]\n writtenAt: number\n}\n\n/** The recorded entry a boot entry baked in, for the launcher's fast path. */\nexport function dshLauncherRecordPath(stateDir: string): string {\n return path.join(launcherDir(stateDir), 'dsh-resolved.json')\n}\n\n/** `bin/dsh.mjs`, or `bin/dsh.cjs` when the recorded entry is CommonJS. */\nexport function dshLauncherPath(stateDir: string, entryExtension?: string | null): string {\n const extension = (entryExtension ?? '').toLowerCase() === '.cjs' ? '.cjs' : '.mjs'\n return path.join(launcherDir(stateDir), `dsh${extension}`)\n}\n\nexport function readDshLauncherRecord(stateDir: string): DshLauncherRecord | null {\n return readJson<DshLauncherRecord>(dshLauncherRecordPath(stateDir))\n}\n\n/**\n * The dsh launcher's source.\n *\n * Like the home-hosted launcher, this exists because a clone's build output path\n * (or a versioned pnpm path) moves on the next install/rebuild: the boot entry\n * points at this stable script and the script re-finds dsh at every boot. A\n * `.cjs` entry gets a CommonJS launcher, so node loads it as one.\n */\nexport function buildDshLauncherSource(options: DshLauncherOptions): string {\n const esm = (options.entryExtension ?? '').toLowerCase() !== '.cjs'\n const prelude = esm\n ? `import fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { spawnSync } from 'node:child_process'`\n : `const fs = require('node:fs')\nconst path = require('node:path')\nconst process = require('node:process')\nconst { spawnSync } = require('node:child_process')`\n // A clone or a project install is re-found from the install roots that were\n // recorded with it, so a moved build needs no new boot entry.\n const searchRoots = [\n options.resolvedEntry === null ? null : dshSearchRoot(options.resolvedEntry),\n options.pinnedEntry === null || options.pinnedEntry === undefined ? null : dshSearchRoot(options.pinnedEntry),\n ...(options.searchRoots ?? []),\n ].filter((root): root is string => typeof root === 'string' && root.length > 0)\n\n return `#!/usr/bin/env node\n// managed by dsh-home-hosted \u2014 rewritten on every plugin start; do not edit.\n${prelude}\n\nconst DSH_HOME = ${JSON.stringify(options.dshHome)}\nconst RECORD = ${JSON.stringify(dshLauncherRecordPath(options.stateDir))}\nconst SEARCH_ROOTS = ${JSON.stringify([...new Set(searchRoots)])}\n// The image this plugin was installed on, when the record had one to carry.\nconst PINNED = ${JSON.stringify(options.pinnedEntry ?? null)}\n\nfunction readRecord() {\n try { return JSON.parse(fs.readFileSync(RECORD, 'utf8')) } catch { return null }\n}\n\nfunction isFile(file) {\n let stat = null\n try { stat = fs.statSync(file) } catch { return false }\n return stat.isFile()\n}\n\n// One identity per file: a recorded path and a candidate reached through a\n// symlink must compare equal, or a macOS clone would look like a different copy.\nfunction canon(file) {\n try { return fs.realpathSync(file) } catch { return file }\n}\n\n// Keep what can actually boot: a JavaScript entry, or a shim whose recorded\n// target is one. A bare shim found on PATH is not a script, so requiring the\n// extension would silently drop the last resort.\n//\n// Each candidate carries how it was found, which is what ordering uses:\n// TIER_REAL a dsh by construction (a manifest, or node_modules/dsh)\n// TIER_PROBE a root that merely offers a build file \u2014 any package may\n// TIER_PATH a dsh executable found on PATH\nconst TIER_REAL = 2\nconst TIER_PROBE = 1\nconst TIER_PATH = 0\n\nfunction collect(out, entry, source, tier) {\n const argument = typeof source === 'string' && source.length > 0 ? source : entry\n const file = typeof entry === 'string' && isFile(entry) ? canon(entry) : null\n if (file !== null && /\\\\.(?:mjs|cjs|js)$/.test(file)) {\n if (!out.has(file) || (out.get(file) ?? 0) < tier) out.set(file, tier)\n return\n }\n // Anything else is a shim: only its recorded target can boot, and a shim\n // without one is not a candidate at all (never a second pass over itself).\n if (typeof argument !== 'string' || !isFile(argument)) return\n let text = ''\n try { text = fs.readFileSync(argument, 'utf8') } catch { return }\n const marker = 'cmd-shim-target='\n const at = text.indexOf(marker)\n if (at === -1) return\n const target = text.slice(at + marker.length).split('\\\\n')[0].trim()\n if (target.length === 0) return\n const real = canon(target)\n if (!/\\\\.(?:mjs|cjs|js)$/.test(real)) return\n if (!out.has(real) || (out.get(real) ?? 0) < tier) out.set(real, tier)\n}\n\nfunction collectStore(root, out) {\n const store = path.join(root, 'node_modules', '.pnpm')\n let names = []\n try { names = fs.readdirSync(store) } catch { return }\n for (const name of names) {\n if (!name.startsWith('dsh@') && !name.startsWith('@deepseek-ai+dsh@')) continue\n collect(out, path.join(store, name, 'node_modules', '@deepseek-ai', 'dsh', 'lib', 'bin.js'), null, TIER_REAL)\n collect(out, path.join(store, name, 'node_modules', 'dsh', 'bin', 'dsh.js'), null, TIER_REAL)\n }\n}\n\nfunction collectRoot(root, out) {\n collect(out, path.join(root, 'node_modules', '@deepseek-ai', 'dsh', 'lib', 'bin.js'), null, TIER_REAL)\n collect(out, path.join(root, 'node_modules', 'dsh', 'bin', 'dsh.js'), null, TIER_REAL)\n collectStore(root, out)\n // A clone keeps its build output at its own root, not under node_modules.\n for (const entry of ['lib/bin.js', 'lib/bin.mjs', 'lib/bin.cjs', 'dist/bin.js'])\n collect(out, path.join(root, entry), null, TIER_PROBE)\n}\n\nfunction candidates(skipPath) {\n const out = new Map()\n const record = readRecord()\n if (record && typeof record.entry === 'string') collect(out, record.entry, record.entry, TIER_REAL)\n for (const root of [...(Array.isArray(record?.roots) ? record.roots : []), ...SEARCH_ROOTS]) collectRoot(root, out)\n collectRoot(DSH_HOME, out)\n let profiles = []\n try { profiles = fs.readdirSync(path.join(DSH_HOME, 'profiles')) } catch {}\n for (const name of profiles) collectRoot(path.join(DSH_HOME, 'profiles', name), out)\n // Last resort: a dsh on the entry's own PATH. It is no longer an equal\n // candidate: a copy found there is only booted when nothing else answers.\n if (skipPath) return out\n for (const dir of (process.env.PATH ?? '').split(path.delimiter)) {\n const shim = path.join(dir, 'dsh')\n collect(out, shim, shim, TIER_PATH)\n collect(out, path.join(dir, 'dsh.cmd'), path.join(dir, 'dsh.cmd'), TIER_PATH)\n }\n return out\n}\n\nfunction versionOf(entry) {\n let dir = path.dirname(entry)\n for (let hop = 0; hop < 4; hop += 1) {\n try {\n const manifest = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'))\n if (typeof manifest.version === 'string') return manifest.version\n } catch {}\n dir = path.dirname(dir)\n }\n return null\n}\n\nfunction compare(a, b) {\n const split = value => {\n const [core, pre = null] = String(value).split('-', 2)\n return { parts: core.split('.').map(part => Number.parseInt(part, 10) || 0), pre }\n }\n const left = split(a); const right = split(b)\n for (let i = 0; i < 3; i += 1) {\n const diff = (left.parts[i] ?? 0) - (right.parts[i] ?? 0)\n if (diff !== 0) return diff < 0 ? -1 : 1\n }\n if (left.pre === right.pre) return 0\n if (left.pre === null) return 1\n if (right.pre === null) return -1\n return left.pre < right.pre ? -1 : 1\n}\n\nfunction findPin(pin, found) {\n if (typeof pin !== 'string' || pin.length === 0) return null\n const real = canon(pin)\n return found.find(item => item.entry === real)?.entry ?? null\n}\n\n// A root that only offers a build file (lib/bin.js) is a guess: any package may\n// have one. Those stay usable but never outrank a copy that is dsh by\n// construction, or a dsh executable from PATH below them.\nfunction bestOfTier(found, tier) {\n const at = found.filter(item => item.tier >= tier)\n const known = at.filter(item => item.version !== null)\n if (known.length > 0) {\n known.sort((a, b) => compare(b.version, a.version))\n return known[0].entry\n }\n return at[0]?.entry ?? null\n}\n\n// Choose the image this plugin was installed on, and only then fall back to a\n// version sort: a higher version elsewhere is not an invitation to boot it.\nfunction choose() {\n const record = readRecord()\n const pin = typeof record?.pinned === 'string' && record.pinned.length > 0 ? record.pinned : PINNED\n const found = [...candidates(true)].map(([entry, tier]) => ({ entry, tier, version: versionOf(entry) }))\n const pinned = findPin(pin, found)\n if (pinned !== null) return pinned\n // Real dsh installs first; a bare build-file guess only when nothing else\n // exists; PATH only when nothing local answered at all.\n return bestOfTier(found, TIER_REAL)\n ?? bestOfTier(found, TIER_PROBE)\n ?? bestOfTier([...candidates(false)].map(([entry, tier]) => ({ entry, tier, version: versionOf(entry) })), TIER_PATH)\n}\n\nconst entry = choose()\nif (entry === null) {\n console.error('[dsh-home-hosted] no dsh entry found for ' + DSH_HOME + ' (pinned path, recorded path, $DSH_HOME node_modules, profiles/*, PATH). Reinstall dsh, or restart: dsh web')\n process.exit(1)\n}\n\nconst args = process.argv.slice(2)\nconst result = /\\\\.(mjs|cjs|js)$/.test(entry)\n ? spawnSync(process.execPath, [entry, ...args], { stdio: 'inherit' })\n : spawnSync(entry, args, { stdio: 'inherit', shell: process.platform === 'win32' && /\\\\.(cmd|bat)$/i.test(entry) })\nprocess.exit(typeof result.status === 'number' ? result.status : 1)\n`\n}\n\n/** Write the launcher and record what `resolveDshLaunch` found. */\nexport function writeDshLauncher(options: DshLauncherOptions): DshLauncherWrite {\n const file = dshLauncherPath(options.stateDir, options.entryExtension)\n const previousRecord = readDshLauncherRecord(options.stateDir)\n // A pin is carried, never guessed. A caller that *is* the running image may\n // adopt a new one; everyone else keeps the existing pin while its file is\n // still a dsh entry. That is what stops a process booted through this\n // launcher from re-recording a weaker answer (its own `argv[1]` is the\n // launcher, so it resolves nothing local).\n const recordedPin = existingEntry(previousRecord?.pinned)\n const offered = existingEntry(options.pinnedEntry)\n const pinned = options.repin === true ? offered ?? recordedPin : recordedPin ?? offered\n const source = buildDshLauncherSource({\n ...options,\n pinnedEntry: pinned === null ? options.pinnedEntry ?? null : pinned,\n })\n const changed = (fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : null) !== source\n if (changed) {\n writeFileAtomic(file, source, 0o755)\n fs.chmodSync(file, 0o755)\n }\n // Roots are kept while they still look like a dsh install: they are what lets\n // a later boot re-find dsh after this install has moved. The pinned image's\n // own root is kept too, or a rewrite (a process booted through this launcher\n // resolves nothing local) would drop the pin's only candidate.\n const raw = [dshSearchRoot(options.resolvedEntry), dshSearchRoot(pinned), ...(options.searchRoots ?? []), ...(previousRecord?.roots ?? [])]\n const known = raw.filter((root): root is string => typeof root === 'string' && root.length > 0 && dshRootUsable(root))\n const roots = [...new Set(known)]\n writeJsonAtomic(dshLauncherRecordPath(options.stateDir), {\n entry: options.resolvedEntry,\n pinned,\n pinnedAt: pinned === null\n ? null\n : pinned === recordedPin ? previousRecord?.pinnedAt ?? Date.now() : Date.now(),\n roots,\n writtenAt: Date.now(),\n } satisfies DshLauncherRecord, 0o600)\n return { path: file, changed }\n}\n\n/** The canonical path of a file that is still there, or null. */\nfunction existingEntry(file: string | null | undefined): string | null {\n if (typeof file !== 'string' || file.length === 0)\n return null\n if (!fs.existsSync(file))\n return null\n try {\n return fs.realpathSync(file)\n }\n catch {\n return null\n }\n}\n\n/** A dsh entry under one root: a flat install, or either pnpm store layout. */\nexport function dshCandidatesUnder(root: string): string[] {\n const out: string[] = []\n for (const entry of [\n path.join(root, 'node_modules', '@deepseek-ai', 'dsh', 'lib', 'bin.js'),\n path.join(root, 'node_modules', 'dsh', 'bin', 'dsh.js'),\n ]) {\n if (fs.existsSync(entry))\n out.push(entry)\n }\n const store = path.join(root, 'node_modules', '.pnpm')\n if (fs.existsSync(store)) {\n for (const name of fs.readdirSync(store)) {\n if (!name.startsWith('dsh@') && !name.startsWith('@deepseek-ai+dsh@'))\n continue\n for (const entry of [\n path.join(store, name, 'node_modules', '@deepseek-ai', 'dsh', 'lib', 'bin.js'),\n path.join(store, name, 'node_modules', 'dsh', 'bin', 'dsh.js'),\n ]) {\n if (fs.existsSync(entry))\n out.push(entry)\n }\n }\n }\n return out\n}\n\n/**\n * The install root a dsh entry belongs to: the directory whose `node_modules`\n * carries it \u2014 a clone root, or the project that installed dsh. That root is\n * what a rebuilt or upgraded install keeps.\n */\nexport function dshSearchRoot(entry: string | null | undefined): string | null {\n if (typeof entry !== 'string' || entry.length === 0)\n return null\n const parts = entry.split(path.sep)\n // A pnpm store path nests a second node_modules: the real root is the one\n // above `node_modules/.pnpm`, never the store's own package directory.\n const store = parts.lastIndexOf('.pnpm')\n const modules = parts.lastIndexOf('node_modules')\n const root = store > 0 ? parts.lastIndexOf('node_modules', store) : modules\n if (root > 0)\n return parts.slice(0, root).join(path.sep)\n // No node_modules anywhere: a source checkout whose build sits at its own\n // root. Derive it the way the runtime does \u2014 the manifest that owns the\n // entry, else the parent of the build directory \u2014 or the two would disagree\n // about which install a remembered path belongs to.\n return ownerRoot(entry) ?? (parts.length > 2 ? parts.slice(0, -2).join(path.sep) : null)\n}\n\n/** The nearest ancestor whose manifest declares this entry as its bin. */\nfunction ownerRoot(entry: string): string | null {\n let dir = path.dirname(entry)\n for (let hop = 0; hop < 4; hop += 1) {\n const manifestPath = path.join(dir, 'package.json')\n const manifest = readJson<{ bin?: string | Record<string, string> }>(manifestPath)\n if (manifest !== null) {\n const declared = typeof manifest.bin === 'string'\n ? manifest.bin\n : manifest.bin?.['dsh'] ?? Object.values(manifest.bin ?? {})[0]\n if (typeof declared === 'string' && path.resolve(path.dirname(manifestPath), declared) === entry)\n return dir\n }\n const parent = path.dirname(dir)\n if (parent === dir)\n break\n dir = parent\n }\n return null\n}\n\n/** Whether a root still holds a dsh install, or the store one could land in. */\nfunction dshRootUsable(root: string): boolean {\n return dshCandidatesUnder(root).length > 0\n || fs.existsSync(path.join(root, 'node_modules', '.pnpm'))\n // A clone's build output sits at its own root, not under node_modules.\n || ['lib/bin.js', 'lib/bin.mjs', 'lib/bin.cjs', 'dist/bin.js'].some(entry => fs.existsSync(path.join(root, entry)))\n}\n\n", "/**\n * Which home-hosted panels exist on this machine.\n *\n * `home-hosted` is not one panel per machine: a person can install it per\n * project, or run several panels from one install with different `--home`\n * roots. This plugin drives exactly one of them \u2014 the root it resolved \u2014 so\n * discovery exists to name the others, to keep every action on the panel this\n * plugin owns, and to tell the agent that the machine has more than one.\n *\n * Discovery is bounded on purpose: the roots the plugin already knows (the\n * managed one, `$HHOSTED_HOME`, the operator's declared ones) and the\n * `~/.home-hosted*` siblings. A `--home` somewhere else on the disk is not\n * found by scanning for it; it is declared through `instanceRoots`.\n */\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\nimport type { InstanceSource, InstanceView } from '../shared/contracts.js'\nimport { expandHome } from '../util/paths.js'\nimport { readConfig } from './config-file.js'\nimport { defaultWorkspace, isHhRoot, isLegacyRoot, runFile } from './layout.js'\nimport { pidAlive, readRuntime } from './runtime.js'\n\nexport interface InstanceDiscoveryOptions {\n /** The state root this plugin drives. */\n managedHome: string\n /** Extra roots an operator declared in the plugin row. */\n extraRoots?: readonly string[]\n /** Test seam: this process's `$HHOSTED_HOME`. */\n envHome?: string | null\n /** Test seam: `HHOSTED_SERVER_ID`, or null when the panel does not supervise us. */\n hostingEntryId?: string | null\n /** Test seam: the user's home directory. */\n homeDir?: string\n /** Test seam: directory listing. */\n listDir?: (dir: string) => string[] | null\n /** Test seam: whether a pid is alive. */\n alive?: (pid: number) => boolean\n}\n\n/** A sibling state root of the default one, e.g. `~/.home-hosted-staging`. */\nconst SIBLING_PATTERN = /^\\.home-hosted(?:[._-].*)?$/\n\n/** A path as one identity: `~` expanded, symlinks resolved (macOS `/var`). */\nexport function canonicalPath(value: string): string {\n const resolved = path.resolve(expandHome(value))\n try {\n return fs.realpathSync.native(resolved)\n }\n catch {\n return resolved\n }\n}\n\nfunction defaultListDir(dir: string): string[] | null {\n try {\n return fs.readdirSync(dir)\n }\n catch {\n return null\n }\n}\n\n/** A directory that holds a panel's state: a 0.7 `.hh`, a pre-0.7 root, or a runtime it wrote. */\nfunction isStateRoot(home: string): boolean {\n try {\n if (!fs.statSync(home).isDirectory())\n return false\n }\n catch {\n return false\n }\n if (isHhRoot(home) || isLegacyRoot(home))\n return true\n return fs.existsSync(runFile(home))\n}\n\nfunction viewOf(candidate: { home: string, source: InstanceSource }, managed: string, envHome: string | null, hosting: boolean, alive: (pid: number) => boolean): InstanceView {\n const runtime = readRuntime(candidate.home)\n // Another panel's entries are counted in the workspace it would default to;\n // its own managed workspace is not knowable from here.\n const read = readConfig(candidate.home, defaultWorkspace(candidate.home))\n return {\n home: candidate.home,\n managed: candidate.home === managed,\n hosting: hosting && candidate.home === envHome,\n url: runtime?.url ?? null,\n port: runtime?.port ?? null,\n pid: runtime?.pid ?? null,\n version: runtime?.version ?? null,\n // A `run.json` names the process that wrote it; a recycled pid is not worth\n // chasing here, because a stale one only mislabels a panel nobody acts on.\n running: runtime !== null && alive(runtime.pid),\n projectDir: runtime?.projectDir ?? null,\n servers: read.error === null ? (read.raw?.servers ?? []).length : null,\n source: candidate.source,\n }\n}\n\n/** Every panel found, the managed one first and the rest by state root. */\nexport function discoverInstances(options: InstanceDiscoveryOptions): InstanceView[] {\n const managed = canonicalPath(options.managedHome)\n const listDir = options.listDir ?? defaultListDir\n const alive = options.alive ?? pidAlive\n const envRaw = options.envHome?.trim()\n const envHome = envRaw !== undefined && envRaw.length > 0 ? canonicalPath(envRaw) : null\n const hostingEntryId = options.hostingEntryId?.trim() ?? ''\n\n const candidates: Array<{ home: string, source: InstanceSource }> = [{ home: managed, source: 'managed' }]\n if (envHome !== null)\n candidates.push({ home: envHome, source: 'env' })\n for (const root of options.extraRoots ?? []) {\n if (root.trim().length > 0)\n candidates.push({ home: canonicalPath(root), source: 'configured' })\n }\n const homeDir = options.homeDir ?? os.homedir()\n for (const name of listDir(homeDir) ?? []) {\n if (SIBLING_PATTERN.test(name))\n candidates.push({ home: canonicalPath(path.join(homeDir, name)), source: 'sibling' })\n }\n\n const seen = new Set<string>()\n const views: InstanceView[] = []\n for (const candidate of candidates) {\n if (seen.has(candidate.home))\n continue\n seen.add(candidate.home)\n // The managed root is listed even before its first start \u2014 it is the panel\n // the plugin drives. Any other candidate must hold a panel's state to count.\n if (candidate.source !== 'managed' && !isStateRoot(candidate.home))\n continue\n views.push(viewOf(candidate, managed, envHome, hostingEntryId.length > 0, alive))\n }\n views.sort((left, right) => (left.managed === right.managed ? left.home.localeCompare(right.home) : left.managed ? -1 : 1))\n return views\n}\n\n/**\n * One line naming a panel, for a prompt note or a refusal. A root whose\n * `run.json` is gone has no runtime to report \u2014 home-hosted drops a stale one \u2014\n * so it is never called \"stopped\".\n */\nexport function describeInstance(instance: InstanceView): string {\n if (instance.url === null)\n return `${instance.home} (no runtime)`\n return `${instance.home} (${instance.url}${instance.running ? '' : ', stopped'})`\n}\n\n/**\n * The note the agent is given when the machine holds more than one panel.\n *\n * `null` for a single panel: the plugin's preference is only worth stating when\n * there is something to prefer it over.\n */\nexport function instancesNoticeText(instances: readonly InstanceView[]): string | null {\n const managed = instances.find(instance => instance.managed)\n const others = instances.filter(instance => !instance.managed)\n if (managed === undefined || others.length === 0)\n return null\n return [\n `home-hosted: ${instances.length} panels were found on this machine, and this plugin manages only ${describeInstance(managed)}.`,\n `Other panels: ${others.map(describeInstance).join('; ')}.`,\n 'Act on the managed panel unless the user names another; a home_hosted_* call takes \"instance\" to name one.',\n 'Naming another panel is allowed for methods that can reach it: the plugin asks the user how, then edits that panel\\'s config file, runs the CLI against its state root, or \u2014 only if they choose it \u2014 mints a token for that panel and uses its API.',\n ].join(' ')\n}\n", "/**\n * Facts about the running home-hosted panel, read from the `run.json` it writes\n * at startup. Its token field is deliberately never read: the plugin\n * authenticates with its own enrolled API token instead.\n */\nimport http from 'node:http'\nimport https from 'node:https'\nimport process from 'node:process'\nimport { readJson } from '../util/fsx.js'\nimport { runFile } from './layout.js'\n\nexport interface PanelRuntime {\n version: string\n pid: number\n url: string\n probeUrl?: string\n protocol?: string\n port: number\n bindHost?: string\n projectDir?: string\n dataRoot?: string\n configPath?: string\n logFile?: string\n}\n\nexport function readRuntime(home: string): PanelRuntime | null {\n const raw = readJson<Record<string, unknown>>(runFile(home))\n if (raw === null)\n return null\n const url = typeof raw.url === 'string' ? raw.url : null\n const pid = typeof raw.pid === 'number' ? raw.pid : null\n if (url === null || pid === null)\n return null\n return {\n version: typeof raw.version === 'string' ? raw.version : 'unknown',\n pid,\n url: url.replace(/\\/+$/, ''),\n probeUrl: typeof raw.probeUrl === 'string' ? raw.probeUrl : undefined,\n protocol: typeof raw.protocol === 'string' ? raw.protocol : undefined,\n port: typeof raw.port === 'number' ? raw.port : 0,\n bindHost: typeof raw.bindHost === 'string' ? raw.bindHost : undefined,\n projectDir: typeof raw.projectDir === 'string' ? raw.projectDir : undefined,\n dataRoot: typeof raw.dataRoot === 'string' ? raw.dataRoot : undefined,\n configPath: typeof raw.configPath === 'string' ? raw.configPath : undefined,\n logFile: typeof raw.logFile === 'string' ? raw.logFile : undefined,\n }\n}\n\nexport function pidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n }\n catch {\n return false\n }\n}\n\n/**\n * Ask the panel's public health route whether it is answering. Uses the raw\n * http/https modules so a self-signed TLS pair answers too.\n */\nexport async function probePanel(url: string, timeoutMs = 2000): Promise<boolean> {\n const target = `${url.replace(/\\/+$/, '')}/healthz`\n return await new Promise<boolean>((resolve) => {\n let settled = false\n const done = (value: boolean): void => {\n if (settled)\n return\n settled = true\n resolve(value)\n }\n\n let parsed: URL\n try {\n parsed = new URL(target)\n }\n catch {\n done(false)\n return\n }\n\n const transport = parsed.protocol === 'https:' ? https : http\n const request = transport.get(\n {\n hostname: parsed.hostname,\n port: parsed.port,\n path: `${parsed.pathname}${parsed.search}`,\n timeout: timeoutMs,\n rejectUnauthorized: false,\n },\n (response) => {\n response.resume()\n done(typeof response.statusCode === 'number')\n },\n )\n request.on('timeout', () => {\n request.destroy()\n done(false)\n })\n request.on('error', () => done(false))\n })\n}\n", "/**\n * What this plugin decides about a server entry, and how that intent maps onto\n * the entry: only the keys in `OWNED_ENTRY_KEYS` are ever written, so a person's\n * own command, args, env, health block and everything else survive untouched.\n */\nimport type { EntryIntent, OnPortConflict, ServerEntry, ServerEntryPatch } from '../shared/contracts.js'\nimport { isOnPortConflict } from '../shared/contracts.js'\n\n/**\n * A detached restart of dsh is recognisable by its own marker/argv on POSIX, so\n * `follow` adopts it without killing anything. Windows cannot read another\n * process's environment and a `.cmd` shim hides the argv, so only `kill`\n * reliably reclaims the port there.\n */\nexport function defaultOnPortConflict(platform: NodeJS.Platform = process.platform): OnPortConflict {\n return platform === 'win32' ? 'kill' : 'follow'\n}\n\nexport function defaultIntent(id: string, platform: NodeJS.Platform = process.platform): EntryIntent {\n return {\n id,\n autostart: true,\n onPortConflict: defaultOnPortConflict(platform),\n stopKillPortHolders: true,\n persistent: true,\n }\n}\n\n/** The owned keys as a patch, preserving any sibling keys inside `stop`. */\nexport function ownedPatch(intent: EntryIntent, live?: ServerEntry | null): ServerEntryPatch {\n const stop = (live?.stop ?? {}) as Record<string, unknown>\n return {\n autostart: intent.autostart,\n onPortConflict: intent.onPortConflict,\n persistent: intent.persistent,\n stop: { ...stop, killPortHolders: intent.stopKillPortHolders },\n }\n}\n\n/** Owned keys whose live value differs from the intent. */\nexport function ownedDrift(live: ServerEntry | null, intent: EntryIntent): string[] {\n if (live === null)\n return ['missing entry']\n const drift: string[] = []\n if (live.autostart !== intent.autostart)\n drift.push('autostart')\n if (live.onPortConflict !== intent.onPortConflict)\n drift.push('onPortConflict')\n if ((live.persistent ?? false) !== intent.persistent)\n drift.push('persistent')\n const killPortHolders = (live.stop as Record<string, unknown> | undefined)?.killPortHolders\n if (killPortHolders !== intent.stopKillPortHolders)\n drift.push('stop.killPortHolders')\n return drift\n}\n\n/**\n * Only the owned keys, as they were before this plugin touched the entry.\n *\n * A key the entry left out is recorded as the panel's schema default rather than\n * omitted: `{ id }` alone is what marks an entry this plugin created, so an\n * adopted entry with nothing explicit has to be distinguishable from it \u2014 or\n * \"stop managing\" deletes a server the person wrote.\n */\n/**\n * Whether a snapshot marks an entry this plugin created rather than adopted.\n *\n * `{ id }` alone is that marker: a created entry carried nothing else when the\n * plugin first saw it, so its command is the plugin's to set.\n */\nexport function isCreatedEntry(snapshot: ServerEntry | undefined): boolean {\n if (snapshot === undefined)\n return false\n return Object.keys(snapshot).length === 1 && typeof snapshot.id === 'string'\n}\n\nexport function snapshotOwned(live: ServerEntry): ServerEntry {\n const killPortHolders = (live.stop as Record<string, unknown> | undefined)?.killPortHolders\n return {\n id: live.id,\n autostart: typeof live.autostart === 'boolean' ? live.autostart : false,\n onPortConflict: isOnPortConflict(live.onPortConflict) ? live.onPortConflict : 'block',\n persistent: live.persistent === true,\n stop: { killPortHolders: typeof killPortHolders === 'boolean' ? killPortHolders : false },\n }\n}\n\n/**\n * Restore an entry to what it was before adoption. A key the snapshot does not\n * carry was inherited from the panel's defaults, and a PATCH cannot delete a\n * key, so it is restored to that schema default instead.\n */\nexport function restorePatch(live: ServerEntry, snapshot: ServerEntry | null): ServerEntryPatch {\n const stop = (live.stop ?? {}) as Record<string, unknown>\n const previous = (snapshot?.stop ?? {}) as Record<string, unknown>\n return {\n autostart: snapshot?.autostart ?? false,\n onPortConflict: snapshot?.onPortConflict ?? 'block',\n persistent: snapshot?.persistent === true,\n stop: {\n ...stop,\n killPortHolders: typeof previous.killPortHolders === 'boolean' ? previous.killPortHolders : false,\n },\n }\n}\n", "/**\n * Starting, replacing and globally installing the panel.\n *\n * Starting it is the same thing a person does by hand: run the CLI's `up`, which\n * re-spawns itself detached, writes `run.json` and returns once the panel\n * answers. Replacing a panel *is* different \u2014 stopping the one that is answering\n * also stops the servers it supervises, including the dsh this plugin runs in \u2014\n * so the replacement is done by a detached helper that outlives this process.\n */\nimport { spawn } from 'node:child_process'\nimport type { ChildProcess } from 'node:child_process'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport type { PanelControlResult } from '../shared/contracts.js'\nimport { run } from '../util/exec.js'\nimport type { RunResult } from '../util/exec.js'\nimport { writeFileAtomic } from '../util/fsx.js'\nimport type { CliLaunch } from './launch.js'\nimport { launcherDir } from './launcher.js'\n\n/** How the panel's CLI is run: the resolved argv plus an environment. */\nexport type PanelCliExec = (args: string[], env: Record<string, string | undefined>) => Promise<RunResult>\n\nexport interface PanelControlDeps {\n /** The CLI to run, already resolved. */\n launch: CliLaunch | null\n home: string\n projectDir?: string | null\n stateDir: string\n /** Environment for the spawned CLI (absolute PATH, HOME, HHOSTED_HOME). */\n env: Record<string, string>\n timeoutMs?: number\n /** Test seam: run the resolved CLI without spawning it. */\n exec?: PanelCliExec\n}\n\nfunction cliArgs(deps: PanelControlDeps, command: 'up' | 'down' | 'restart'): string[] {\n const args = [...(deps.launch?.args ?? []), command, '--home', deps.home]\n if (deps.projectDir)\n args.push('--project', deps.projectDir)\n return args\n}\n\nasync function runCli(deps: PanelControlDeps, command: 'up' | 'down' | 'restart'): Promise<{ code: number | null, stdout: string, stderr: string, error: string | null }> {\n const args = cliArgs(deps, command)\n if (deps.exec !== undefined)\n return await deps.exec(args, deps.env)\n const launch = deps.launch\n if (launch === null)\n return { code: null, stdout: '', stderr: 'no home-hosted CLI is available', error: null }\n return await run(launch.program, args, {\n env: deps.env,\n timeoutMs: deps.timeoutMs ?? 90_000,\n })\n}\n\n/** Start the preferred CLI as a detached panel; the CLI itself does the detaching. */\nexport async function startPanel(deps: PanelControlDeps): Promise<PanelControlResult> {\n const result = await runCli(deps, 'up')\n if (result.code !== 0) {\n const detail = result.stderr.trim() || result.stdout.trim() || `the CLI exited ${String(result.code)}`\n return { ok: false, detail }\n }\n const match = /https?:\\/\\/[^\\s]+/.exec(result.stdout)\n return { ok: true, detail: 'the panel is answering', url: match?.[0] ?? null }\n}\n\n/**\n * Stop the panel through its own CLI.\n *\n * The panel is the parent of every server it supervises, so this stops those\n * too \u2014 including the dsh this plugin may be running in. That is the point of\n * the button, not a hazard to hide: the caller's UI says so. An entry with\n * `autostart` is started again on the next panel start; it is not restarted here.\n */\nexport async function stopPanel(deps: PanelControlDeps): Promise<PanelControlResult> {\n const result = await runCli(deps, 'down')\n if (result.code !== 0) {\n const detail = result.error ?? (result.stderr.trim() || result.stdout.trim() || `the CLI exited ${String(result.code)}`)\n return { ok: false, detail: `the panel did not stop: ${detail}` }\n }\n return { ok: true, detail: 'the panel stopped; the servers it supervised stopped with it' }\n}\n\nexport function takeoverHelperPath(stateDir: string): string {\n return path.join(launcherDir(stateDir), 'panel-takeover.mjs')\n}\n\nexport function takeoverLogPath(stateDir: string): string {\n return path.join(launcherDir(stateDir), 'panel-takeover.log')\n}\n\nexport function activationHelperPath(stateDir: string): string {\n return path.join(launcherDir(stateDir), 'panel-activate.mjs')\n}\n\nexport function activationLogPath(stateDir: string): string {\n return path.join(launcherDir(stateDir), 'panel-activate.log')\n}\n\n/**\n * The generated helper that hands the panel to an autostart mechanism.\n *\n * It has to outlive this process for the same reason the takeover helper does:\n * stopping the panel also stops the servers it supervises, and the plugin is\n * usually one of them, so nothing here may wait on the answer.\n *\n * The order is the whole design. `files` and `requires` are proven first, while\n * the panel is still up: a start that could never work must never be attempted\n * after the old panel is already gone. Then the panel is stopped \u2014 the CLI's\n * `down`, which is the only stop that also reports whether the pid went \u2014 because\n * a start over a *running* panel is refused by home-hosted itself (`run.json`\n * names a live pid). Only then does the start command run, and only after it\n * succeeded are the other mechanisms' entries removed: retiring them any earlier\n * would delete the entry that is currently keeping the panel alive.\n */\nexport interface ActivationPlan {\n /** Steps that start the panel under the mechanism, in order. */\n commands: string[][]\n /** Proof commands that must exit 0 before the panel is touched. */\n requires: string[][]\n /** Files that must exist before the panel is touched. */\n files: string[]\n /** The CLI's own stop, which also reports whether the panel went. */\n stop: string[]\n /**\n * Steps that stop the panel the *previous* mechanism supervises, run before\n * the CLI's `down`. Without them a switch loses the race: the old entry's own\n * restart policy brings the panel back as soon as `down` returns, before the\n * new entry starts one.\n */\n detach: string[][]\n /** The pid of the panel being replaced; escalation waits on it. */\n oldPid: number | null\n /** Steps that remove the other mechanisms' entries, run after the start. */\n retire: string[][]\n /** The log a person reads when the panel does not come back. */\n logPath: string\n}\n\n/** The generated helper's source; exported so a test can inspect it. */\nexport function buildActivationSource(plan: ActivationPlan, marker = 'managed by dsh-home-hosted'): string {\n return `#!/usr/bin/env node\n// ${marker} \u2014 starts the panel through its autostart entry; do not edit.\nimport fs from 'node:fs'\nimport { spawnSync } from 'node:child_process'\n\nconst STEPS = ${JSON.stringify(plan.commands)}\nconst REQUIRES = ${JSON.stringify(plan.requires)}\nconst FILES = ${JSON.stringify(plan.files)}\nconst STOP = ${JSON.stringify(plan.stop)}\nconst DETACH = ${JSON.stringify(plan.detach)}\nconst OLD_PID = ${JSON.stringify(plan.oldPid)}\nconst RETIRE = ${JSON.stringify(plan.retire)}\nconst LOG = ${JSON.stringify(plan.logPath)}\n\nfunction log(line) {\n try { fs.appendFileSync(LOG, new Date().toISOString() + ' ' + line + '\\\\n') } catch {}\n}\n\nfunction fail(line) {\n log(line)\n process.exit(1)\n}\n\nfunction alive(pid) {\n if (typeof pid !== 'number') return false\n try { process.kill(pid, 0); return true } catch { return false }\n}\n\nconst sleep = ms => new Promise(resolve => setTimeout(resolve, ms))\n\n// Everything is proven while the panel is still answering: a start that could\n// never work must not be attempted after the old panel is already gone.\nfor (const file of FILES) {\n if (!fs.existsSync(file))\n fail('refusing to stop the panel: ' + file + ' is not there')\n}\nfor (const probe of REQUIRES) {\n const result = spawnSync(probe[0], probe.slice(1), { stdio: 'ignore' })\n if (result.error || result.status !== 0)\n fail('refusing to stop the panel: ' + probe.join(' ') + ' did not confirm (' + String(result.status ?? result.error) + ')')\n}\n\n// A panel the previous mechanism supervises must be stopped through that\n// mechanism first: its restart policy would revive it the moment 'down' ends.\nfor (const step of DETACH) {\n log('detaching from the previous autostart entry: ' + step.join(' '))\n const result = spawnSync(step[0], step.slice(1), { stdio: 'ignore' })\n log('exited ' + String(result.status ?? result.error))\n}\n\n// The panel has to be gone before the entry runs: home-hosted refuses to start a\n// second panel while run.json names a live pid, so starting over a running one\n// leaves the entry failed while the old panel keeps the port.\nlog('stopping the answering panel: ' + STOP.join(' '))\nconst stopped = spawnSync(STOP[0], STOP.slice(1), { stdio: 'ignore', timeout: 90000 })\nlog('down exited ' + String(stopped.status ?? stopped.error))\n\nif (alive(OLD_PID)) {\n log('the panel is still alive; sending SIGTERM')\n try { process.kill(OLD_PID, 'SIGTERM') } catch {}\n for (let i = 0; i < 40 && alive(OLD_PID); i += 1)\n await sleep(250)\n}\n\nif (alive(OLD_PID)) {\n log('still alive; sending SIGKILL')\n try { process.kill(OLD_PID, 'SIGKILL') } catch {}\n for (let i = 0; i < 20 && alive(OLD_PID); i += 1)\n await sleep(250)\n}\n\nfor (const step of STEPS) {\n log('running ' + step.join(' '))\n const result = spawnSync(step[0], step.slice(1), { stdio: 'ignore' })\n log('exited ' + String(result.status ?? result.error))\n if (result.error || result.status !== 0)\n fail(step.join(' ') + ' did not succeed, so the panel is down')\n}\n\n// Only now: the entry being removed may be the one that just started the panel,\n// and a failure to remove it is logged rather than fatal \u2014 the panel is up.\nfor (const step of RETIRE) {\n log('retiring ' + step.join(' '))\n const result = spawnSync(step[0], step.slice(1), { stdio: 'ignore' })\n log('exited ' + String(result.status ?? result.error))\n}\n\nlog('the autostart entry was told to start the panel')\n`\n}\n\n/** The generated helper's source; exported so a test can inspect it. */\nexport function buildTakeoverSource(deps: PanelControlDeps, oldPid: number | null, marker = 'managed by dsh-home-hosted'): string {\n const program = deps.launch?.program ?? process.execPath\n const args = deps.launch === null\n ? []\n : [...deps.launch.args, 'up', '--home', deps.home, ...(deps.projectDir ? ['--project', deps.projectDir] : [])]\n const downArgs = [...(deps.launch?.args ?? []), 'down', '--home', deps.home]\n return `#!/usr/bin/env node\n// ${marker} \u2014 replaces the panel; do not edit.\nimport fs from 'node:fs'\nimport { spawnSync } from 'node:child_process'\n\nconst OLD_PID = ${JSON.stringify(oldPid)}\nconst DOWN_ARGS = ${JSON.stringify(downArgs)}\nconst PROGRAM = ${JSON.stringify(program)}\nconst ARGS = ${JSON.stringify(args)}\nconst ENV = ${JSON.stringify(deps.env)}\nconst LOG = ${JSON.stringify(takeoverLogPath(deps.stateDir))}\n\nfunction log(line) {\n try { fs.appendFileSync(LOG, new Date().toISOString() + ' ' + line + '\\\\n') } catch {}\n}\n\nfunction alive(pid) {\n if (typeof pid !== 'number') return false\n try { process.kill(pid, 0); return true } catch { return false }\n}\n\nconst sleep = ms => new Promise(resolve => setTimeout(resolve, ms))\n\nlog('stopping the answering panel: ' + PROGRAM + ' ' + DOWN_ARGS.join(' '))\nconst down = spawnSync(PROGRAM, DOWN_ARGS, { env: { ...process.env, ...ENV }, stdio: 'ignore' })\nlog('down exited ' + String(down.status))\n\nfor (let i = 0; i < 40 && alive(OLD_PID); i += 1)\n await sleep(250)\n\nif (alive(OLD_PID)) {\n log('still alive; sending SIGTERM')\n try { process.kill(OLD_PID, 'SIGTERM') } catch {}\n for (let i = 0; i < 40 && alive(OLD_PID); i += 1)\n await sleep(250)\n}\n\nif (alive(OLD_PID)) {\n log('still alive; sending SIGKILL')\n try { process.kill(OLD_PID, 'SIGKILL') } catch {}\n for (let i = 0; i < 20 && alive(OLD_PID); i += 1)\n await sleep(250)\n}\n\nlog('starting ' + PROGRAM + ' ' + ARGS.join(' '))\nconst result = spawnSync(PROGRAM, ARGS, { env: { ...process.env, ...ENV }, stdio: 'ignore' })\nlog('started with exit ' + String(result.status))\n`\n}\n\nexport function writeTakeoverHelper(deps: PanelControlDeps, oldPid: number | null): string {\n const file = takeoverHelperPath(deps.stateDir)\n writeFileAtomic(file, buildTakeoverSource(deps, oldPid), 0o755)\n fs.chmodSync(file, 0o755)\n return file\n}\n\nexport function writeActivationHelper(stateDir: string, plan: ActivationPlan): string {\n const file = activationHelperPath(stateDir)\n writeFileAtomic(file, buildActivationSource(plan), 0o755)\n fs.chmodSync(file, 0o755)\n return file\n}\n\n/**\n * Spawn the activation helper detached and return immediately: the panel's\n * shutdown is about to stop this very process when the plugin runs under it, so\n * waiting for the result would lose the answer.\n *\n * The helper is left to it: whatever it does to the panel, the entry it starts\n * is the one that brings the plugin back.\n */\nexport function spawnActivation(stateDir: string, plan: ActivationPlan, spawnChild: SpawnDetached = spawnDetached): PanelControlResult {\n const helper = writeActivationHelper(stateDir, plan)\n let child: ChildProcess\n try {\n child = spawnChild(process.execPath, [helper])\n }\n catch (error) {\n return { ok: false, detail: `could not start the autostart helper: ${error instanceof Error ? error.message : String(error)}` }\n }\n child.on('error', () => {})\n child.unref()\n return {\n ok: true,\n detail: `starting the panel through its autostart entry now; this page disconnects and comes back when it answers`,\n }\n}\n\n/** Spawn a helper process; injected so a test can drive the failure paths. */\nexport type SpawnDetached = (program: string, args: string[]) => ChildProcess\n\nfunction spawnDetached(program: string, args: string[]): ChildProcess {\n return spawn(program, args, { detached: true, stdio: 'ignore' })\n}\n\n/**\n * Spawn the helper detached and return immediately: the old panel's shutdown is\n * about to stop this very process, so waiting for the result would lose it.\n */\nexport function spawnTakeover(deps: PanelControlDeps, oldPid: number | null, spawnChild: SpawnDetached = spawnDetached): PanelControlResult {\n const helper = writeTakeoverHelper(deps, oldPid)\n let child: ChildProcess\n try {\n child = spawnChild(process.execPath, [helper])\n }\n catch (error) {\n return { ok: false, detail: `could not start the panel-replacement helper: ${error instanceof Error ? error.message : String(error)}` }\n }\n // A detached child reports a failed start asynchronously; an unhandled 'error'\n // event is thrown, and that would end this process instead of the helper's.\n child.on('error', () => {})\n child.unref()\n return {\n ok: true,\n detail: 'replacing the panel now; this page will disconnect and come back under the preferred copy',\n }\n}\n\n/** Install the pinned range globally, so the `global` preference has something to use. */\nexport async function installGlobal(\n range: string,\n options: { pnpm?: boolean, run?: typeof run } = {},\n): Promise<{ ok: boolean, detail: string, output: string }> {\n const execute = options.run ?? run\n const usePnpm = options.pnpm ?? true\n const result = usePnpm\n ? await execute('pnpm', ['add', '-g', `home-hosted@${range}`], { timeoutMs: 300_000 })\n : await execute('npm', ['install', '-g', `home-hosted@${range}`], { timeoutMs: 300_000 })\n const output = `${result.stdout}\\n${result.stderr}`.trim()\n if (result.code !== 0)\n return { ok: false, detail: result.error ?? `the installer exited ${String(result.code)}`, output }\n return { ok: true, detail: `installed home-hosted@${range} globally`, output }\n}\n", "/**\n * The home-hosted HTTP API client.\n *\n * Every call carries the plugin's own API token; the panel verifies it from its\n * secrets file on each request. A write through this client is the safe path:\n * the config store records the bytes it wrote, so the panel's own file watcher\n * never treats the change as an external edit.\n */\nimport type { ServerEntry, ServerEntryPatch, ServerEntryView } from '../shared/contracts.js'\n\n/** What the panel's `/api/servers*` actually answers with: it names the workspace `workspaceId`. */\ninterface ApiServerView extends Omit<ServerEntryView, 'workspace'> {\n workspaceId?: string\n}\n\nexport class PanelError extends Error {\n constructor(\n message: string,\n readonly code: string,\n readonly status: number | null = null,\n /** The panel's own diagnostic, when it sent one. */\n readonly detail: unknown = undefined,\n ) {\n super(message)\n this.name = 'PanelError'\n }\n}\n\nexport interface PanelClientOptions {\n baseUrl: string\n token: string\n timeoutMs?: number\n /** The workspace every call acts on unless it names another one. */\n workspace?: string\n}\n\nexport class PanelClient {\n private readonly baseUrl: string\n private readonly token: string\n private readonly timeoutMs: number\n private readonly workspace: string | null\n\n constructor(options: PanelClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/+$/, '')\n this.token = options.token\n this.timeoutMs = options.timeoutMs ?? 10_000\n this.workspace = options.workspace ?? null\n }\n\n /**\n * A server path for one workspace. Server ids are only unique inside a\n * workspace, so `?workspace=` is what selects which one is meant; omitting it\n * on a panel that predates workspaces changes nothing.\n */\n /**\n * The panel names a server's workspace `workspaceId`; the plugin's view calls it\n * `workspace`. One mapping, so a caller never has to know both spellings.\n */\n private view(raw: ApiServerView, workspace?: string | null): ServerEntryView {\n const { workspaceId, ...rest } = raw\n return { ...rest, workspace: workspaceId ?? workspace ?? this.workspace ?? '' }\n }\n\n private ws(path: string, workspace?: string | null): string {\n const id = workspace ?? this.workspace\n if (id === null || id === undefined || id.length === 0)\n return path\n return `${path}${path.includes('?') ? '&' : '?'}workspace=${encodeURIComponent(id)}`\n }\n\n private async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n let response: Response\n try {\n response = await fetch(`${this.baseUrl}${path}`, {\n method,\n headers: {\n authorization: `Bearer ${this.token}`,\n ...(body === undefined ? {} : { 'content-type': 'application/json' }),\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: AbortSignal.timeout(this.timeoutMs),\n })\n }\n catch (error) {\n throw new PanelError(\n error instanceof Error ? error.message : String(error),\n 'PANEL_UNREACHABLE',\n )\n }\n\n const text = await response.text()\n let parsed: unknown = null\n try {\n parsed = text.length > 0 ? JSON.parse(text) : null\n }\n catch {\n throw new PanelError(`the panel answered ${response.status} with a non-JSON body`, 'PANEL_BAD_RESPONSE', response.status)\n }\n\n if (!response.ok) {\n const record = parsed as { message?: unknown, code?: unknown, detail?: unknown } | null\n const message = typeof record?.message === 'string' ? record.message : `the panel answered ${response.status}`\n const code = typeof record?.code === 'string' ? record.code : 'PANEL_ERROR'\n throw new PanelError(message, code, response.status, record?.detail)\n }\n\n return parsed as T\n }\n\n async listServers(workspace?: string): Promise<ServerEntryView[]> {\n const answer = await this.request<{ servers: ApiServerView[] }>('GET', this.ws('/api/servers', workspace))\n return answer.servers.map(server => this.view(server, workspace))\n }\n\n async getServer(id: string, workspace?: string): Promise<ServerEntryView> {\n const answer = await this.request<{ server: ApiServerView }>('GET', this.ws(`/api/servers/${encodeURIComponent(id)}`, workspace))\n return this.view(answer.server, workspace)\n }\n\n async createServer(entry: ServerEntry, workspace?: string): Promise<ServerEntryView> {\n const answer = await this.request<{ server: ApiServerView }>('POST', this.ws('/api/servers', workspace), entry)\n return this.view(answer.server, workspace)\n }\n\n async updateServer(id: string, patch: ServerEntryPatch, workspace?: string): Promise<ServerEntryView> {\n const answer = await this.request<{ server: ApiServerView }>('PATCH', this.ws(`/api/servers/${encodeURIComponent(id)}`, workspace), patch)\n return this.view(answer.server, workspace)\n }\n\n async deleteServer(id: string, workspace?: string): Promise<void> {\n await this.request('DELETE', this.ws(`/api/servers/${encodeURIComponent(id)}`, workspace))\n }\n\n async startServer(id: string, workspace?: string): Promise<void> {\n await this.request('POST', this.ws(`/api/servers/${encodeURIComponent(id)}/start`, workspace))\n }\n\n async stopServer(id: string, workspace?: string): Promise<void> {\n await this.request('POST', this.ws(`/api/servers/${encodeURIComponent(id)}/stop`, workspace))\n }\n\n async restartServer(id: string, workspace?: string): Promise<void> {\n await this.request('POST', this.ws(`/api/servers/${encodeURIComponent(id)}/restart`, workspace))\n }\n\n /** The workspaces this panel serves, as its registry reports them. */\n async listWorkspaces(): Promise<{ id: string, label: string, serverCount: number, runningCount: number, crashedCount: number }[]> {\n const answer = await this.request<{ workspaces: Array<{ id: string, label: string, serverCount: number, runningCount: number, crashedCount: number }> }>('GET', '/api/workspaces')\n return answer.workspaces\n }\n\n /** The panel's own settings: `control` is the listener block a port change edits. */\n async settings(): Promise<{ control?: Record<string, unknown> }> {\n return await this.request<{ control?: Record<string, unknown> }>('GET', '/api/settings')\n }\n\n /** Re-list the port's listeners and stop what is not the panel's own tree. */\n async freePort(id: string, workspace?: string): Promise<{ stopped?: number[] }> {\n return await this.request<{ stopped?: number[] }>('POST', this.ws(`/api/servers/${encodeURIComponent(id)}/free-port`, workspace))\n }\n}\n\n/**\n * What a token probe measured: `ok` \u2014 the panel accepted it; `refused` \u2014 the\n * panel answered and rejected it; `unreachable` \u2014 the panel was not there to\n * ask, which is not the same as a refusal.\n */\nexport type TokenProbe = 'ok' | 'refused' | 'unreachable'\n\n/**\n * Prove a token with a non-mutating `listServers()`, and say whether the panel\n * refused it or merely was not answering. A timeout or connection error must\n * never be reported as a stale token.\n */\nexport async function probeToken(baseUrl: string, token: string, timeoutMs = 5000): Promise<TokenProbe> {\n try {\n await new PanelClient({ baseUrl, token, timeoutMs }).listServers()\n return 'ok'\n }\n catch (error) {\n if (error instanceof PanelError && (error.code === 'AUTH_REQUIRED' || error.status === 401 || error.status === 403))\n return 'refused'\n return 'unreachable'\n }\n}\n\n/** Prove a token works, without needing any write permission. */\nexport async function verifyToken(baseUrl: string, token: string, timeoutMs = 5000): Promise<boolean> {\n return await probeToken(baseUrl, token, timeoutMs) === 'ok'\n}\n", "/**\n * The plugin's own credential for the home-hosted panel.\n *\n * home-hosted keeps only the token's hash, so a token this plugin did not\n * create can never be recovered from disk. When none is enrolled, the plugin\n * mints one and hands it to `home-hosted set-token` through `HHOSTED_TOKEN`,\n * which stores the hash and prints nothing; when the panel refuses the stored\n * one, `reclaimToken` replaces it with a fresh token the same way.\n * The plaintext lives 0600 under the plugin state directory and is never logged,\n * rendered, or sent to the browser.\n */\nimport { createHash, randomBytes } from 'node:crypto'\nimport path from 'node:path'\nimport type { RunResult } from '../util/exec.js'\nimport { readJson, readText, writeFileAtomic } from '../util/fsx.js'\nimport { secretsFile } from './layout.js'\n\nexport type CliExecutor = (args: string[], env: Record<string, string | undefined>) => Promise<RunResult>\n\nexport interface EnsureTokenOptions {\n home: string\n stateDir: string\n exec: CliExecutor\n /** Which panel this credential is for; omitted is the panel the plugin manages. */\n slot?: string\n}\n\nexport interface EnsureTokenResult {\n token: string | null\n /** True when this call created the token. */\n enrolled: boolean\n detail: string\n}\n\nexport interface ReclaimTokenOptions {\n home: string\n stateDir: string\n exec: CliExecutor\n slot?: string\n}\n\nexport interface ReclaimTokenResult {\n token: string | null\n detail: string\n}\n\n/** A stable, unguessable file name for one state root's credential. */\nexport function tokenSlot(home: string): string {\n return createHash('sha256').update(path.resolve(home)).digest('hex').slice(0, 16)\n}\n\nexport function storedTokenPath(stateDir: string, slot?: string): string {\n return slot === undefined\n ? path.join(stateDir, 'panel-token')\n : path.join(stateDir, 'panel-tokens', `${slot}.token`)\n}\n\nexport function readStoredToken(stateDir: string, slot?: string): string | null {\n const text = readText(storedTokenPath(stateDir, slot))?.trim()\n return text !== undefined && text.length > 0 ? text : null\n}\n\nexport function storeToken(stateDir: string, token: string, slot?: string): void {\n writeFileAtomic(storedTokenPath(stateDir, slot), `${token}\\n`, 0o600)\n}\n\nexport function generateToken(): string {\n return randomBytes(32).toString('base64url')\n}\n\n/** Whether home-hosted already holds an API token (only its hash is on disk). */\nexport function apiTokenEnrolled(home: string): boolean {\n const secrets = readJson<{ apiToken?: unknown }>(secretsFile(home))\n return secrets !== null && secrets.apiToken !== null && secrets.apiToken !== undefined\n}\n\n/**\n * One in-flight token operation per state directory, so two callers cannot race.\n *\n * Two concurrent callers used to mint two tokens: both `set-token` calls land, and\n * the loser's token is the one stored on disk while the panel keeps the winner's\n * hash \u2014 after which the plugin can never authenticate, and never re-enrols,\n * because home-hosted already holds an API token. Every operation on one state\n * directory therefore runs after the previous one has settled.\n */\nconst queue = new Map<string, Promise<unknown>>()\n\nfunction serialised<T>(stateDir: string, slot: string | undefined, work: () => Promise<T>): Promise<T> {\n const key = `${path.resolve(stateDir)}\\u0000${slot ?? ''}`\n const previous = queue.get(key) ?? Promise.resolve()\n // Run even when the previous operation rejected: that failure is its caller's.\n const next = previous.then(work, work)\n const settled = next.then(() => undefined, () => undefined)\n queue.set(key, settled)\n void settled.then(() => {\n if (queue.get(key) === settled) queue.delete(key)\n })\n return next\n}\n\nexport async function ensureToken(options: EnsureTokenOptions): Promise<EnsureTokenResult> {\n return await serialised(options.stateDir, options.slot, async () => await enrollToken(options))\n}\n\n/**\n * Replace the panel's API token with a fresh one.\n *\n * home-hosted keeps only the token's hash, so a token this plugin does not hold\n * cannot be recovered: a fresh token is minted, enrolled through the same\n * `HHOSTED_TOKEN` path `ensureToken` uses \u2014 one write, since `set-token` replaces\n * whatever hash is there \u2014 and only then is the plaintext stored 0600. The\n * attempted write carries no risk to the stored token: a failure replaced\n * nothing, so the previous plaintext is kept.\n */\nexport async function reclaimToken(options: ReclaimTokenOptions): Promise<ReclaimTokenResult> {\n return await serialised(options.stateDir, options.slot, async () => await enrollFreshToken(options))\n}\n\ninterface CliAttempt {\n result?: RunResult\n failure?: string\n}\n\n/** Run one CLI step, turning both a non-zero exit and a spawn throw into text. */\nasync function runCli(exec: CliExecutor, args: string[], env: Record<string, string | undefined>): Promise<CliAttempt> {\n try {\n const result = await exec(args, env)\n if (result.code !== 0) {\n return {\n failure: result.error?.trim() || result.stderr.trim() || result.stdout.trim() || `exit ${String(result.code)}`,\n }\n }\n return { result }\n }\n catch (error) {\n return { failure: error instanceof Error ? error.message : String(error) }\n }\n}\n\n/** Mint a token when none is enrolled. */\nasync function enrollToken(options: EnsureTokenOptions): Promise<EnsureTokenResult> {\n const stored = readStoredToken(options.stateDir, options.slot)\n if (stored !== null)\n return { token: stored, enrolled: false, detail: 'using the stored panel token' }\n\n if (apiTokenEnrolled(options.home)) {\n return {\n token: null,\n enrolled: false,\n detail: 'home-hosted already has an API token, and this plugin does not have it; '\n + 'run `home-hosted set-token --generate` and store that token, or clear it and re-enable',\n }\n }\n\n const token = generateToken()\n const enrolled = await runCli(options.exec, ['--home', options.home, 'set-token'], { HHOSTED_TOKEN: token })\n if (enrolled.result === undefined) {\n return {\n token: null,\n enrolled: false,\n detail: `could not enrol an API token: ${enrolled.failure ?? 'the CLI did not answer'}`,\n }\n }\n\n storeToken(options.stateDir, token, options.slot)\n return { token, enrolled: true, detail: 'enrolled a new panel API token' }\n}\n\nasync function enrollFreshToken(options: ReclaimTokenOptions): Promise<ReclaimTokenResult> {\n // One write, not a clear-then-set pair: `set-token` replaces whatever hash is\n // there (proved against the real CLI), and clearing first would leave a window\n // in which the panel has no token at all.\n const token = generateToken()\n const enrolled = await runCli(options.exec, ['--home', options.home, 'set-token'], { HHOSTED_TOKEN: token })\n if (enrolled.result === undefined) {\n // Nothing was replaced, so the stored token is still the panel's and is left\n // alone: dropping it would throw away a credential that may still work.\n return {\n token: null,\n detail: `could not enrol a fresh API token: ${enrolled.failure ?? 'the CLI did not answer'}; `\n + 'the previously stored token was kept',\n }\n }\n\n storeToken(options.stateDir, token, options.slot)\n return { token, detail: 'enrolled a fresh panel API token' }\n}\n", "/**\n * The browser half's one entry point.\n *\n * The route is registered on DeepSeek Harness's shared `/api` channel, so the\n * physical carrier has already applied the Host/Origin fence and browser\n * authentication before this handler runs: only the local operator reaches it.\n * Domain failures travel inside the envelope, protocol failures as HTTP status.\n */\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { RpcEndpoint, RpcRequest } from './shared/contracts.js'\nimport { RPC_PATH, RPC_VERSION } from './shared/contracts.js'\nimport type { HomeHostedService } from './service.js'\nimport { HomeHostedError } from './service.js'\n\ninterface FetchRoute {\n path: string\n methods: readonly string[]\n requestBody: 'buffered' | 'streaming'\n fetch: (request: Request) => Promise<Response>\n}\n\ninterface ConnectionLike {\n fetch: { register: (route: FetchRoute) => () => unknown }\n}\n\ninterface ConnectionHost {\n connection: ConnectionLike\n}\n\ninterface LoggerLike {\n warn?: (message: string) => void\n}\n\nfunction answer(endpoint: RpcEndpoint | string, result: unknown): Response {\n return Response.json({ v: RPC_VERSION, endpoint, result })\n}\n\nexport function registerRpc(ctx: Context, service: HomeHostedService): void {\n ctx.inject(['connection'], (scoped) => {\n const connection = (scoped as unknown as ConnectionHost).connection\n if (connection?.fetch?.register === undefined)\n return\n\n scoped.effect(() => {\n let dispose: () => unknown\n try {\n dispose = connection.fetch.register({\n // The exact-route registry is keyed by the full request path, so the\n // `/api` prefix belongs here; the page composes the same URL from\n // API_BASE + RPC_PATH.\n path: `/api${RPC_PATH}`,\n methods: ['POST'],\n requestBody: 'buffered',\n fetch: async (request: Request) => {\n let body: RpcRequest\n try {\n body = await request.json() as RpcRequest\n }\n catch {\n return Response.json({ error: 'the request body is not JSON' }, { status: 400 })\n }\n\n if (body === null || typeof body !== 'object' || body.v !== RPC_VERSION) {\n return Response.json(\n { error: `this page and the host disagree on the protocol version (expected ${String(RPC_VERSION)})` },\n { status: 409 },\n )\n }\n\n try {\n const value = await service.call(body.endpoint, body.payload)\n return answer(body.endpoint, { ok: true, value })\n }\n catch (error) {\n const code = error instanceof HomeHostedError ? error.code : 'INTERNAL'\n const message = error instanceof Error ? error.message : String(error)\n const detail = (error as { detail?: unknown } | null)?.detail\n return answer(body.endpoint, { ok: false, error: { code, message, ...(detail === undefined ? {} : { detail }) } })\n }\n },\n })\n }\n catch (error) {\n // A registration the carrier refuses would otherwise look like a page\n // that silently 404s; say so where an operator can see it.\n const logger = (scoped as unknown as { logger?: LoggerLike }).logger\n logger?.warn?.(`[dsh-home-hosted] could not register ${RPC_PATH}: ${error instanceof Error ? error.message : String(error)}`)\n return () => {}\n }\n\n return () => {\n // A disposer that rejects would otherwise surface as an unhandled\n // rejection, which ends the process on Node 24.\n void Promise.resolve(dispose()).catch(() => {})\n }\n }, 'dsh-home-hosted: rpc route')\n })\n}\n", "/**\n * Telling the agent which home-hosted panels exist.\n *\n * A plugin that owns one panel must not look like the only one on the machine.\n * When discovery finds more than one, the inventory is contributed as dynamic\n * runtime context (`ctx.systemPrompt.context`), so every model step knows the\n * managed panel, the others, and that this plugin cannot write to them.\n *\n * The contribution reads the service's cached inventory: assembly happens\n * before a model step and must never block on the disk.\n */\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { InstanceView } from '../shared/contracts.js'\nimport { instancesNoticeText } from './instances.js'\n\n/** The subset of `ctx.systemPrompt` this uses; absent on a host without it. */\nexport interface SystemPromptLike {\n context: (contribution: {\n name: string\n order: number\n text: string | ((context: unknown) => string)\n }) => () => void\n}\n\nexport interface InstancesNoticeDeps {\n /** The inventory the plugin already measured; never a disk read at assembly. */\n instances: () => readonly InstanceView[]\n /** The `instancesNotice` setting, read per assembly. */\n enabled: () => boolean\n}\n\n/**\n * After the first-party runtime contexts (sandbox 110, approval 115,\n * subagent delegation 120) and before anything a repository owns.\n */\nexport const INSTANCES_CONTEXT_ORDER = 130\n\n/** The exact text one assembly contributes, or `''` for nothing. */\nexport function instancesContextText(instances: readonly InstanceView[], enabled: boolean): string {\n return enabled ? instancesNoticeText(instances) ?? '' : ''\n}\n\nexport function registerInstancesNotice(ctx: Context, deps: InstancesNoticeDeps): void {\n ctx.inject(['systemPrompt'], (scoped) => {\n const prompt = (scoped as unknown as { systemPrompt?: SystemPromptLike }).systemPrompt\n if (typeof prompt?.context !== 'function')\n return\n scoped.effect(() => prompt.context({\n name: 'dsh-home-hosted:instances',\n order: INSTANCES_CONTEXT_ORDER,\n text: () => instancesContextText(deps.instances(), deps.enabled()),\n }), 'dsh-home-hosted: instance notice')\n })\n}\n", "import type { Context } from '@deepseek-ai/cordis'\n\n/**\n * dsh web answers an unauthenticated browser with a plain-text 401 line written\n * by the in-box connection plugin. That text is not configurable and no event\n * covers it, so a home-hosted deployment \u2014 which is where this session's log,\n * and with it the tokenised URL, actually lives \u2014 appends where to look.\n *\n * It reaches one internal field, `connection.browserAuth.writeUnauthorized`, and\n * fails soft: if that shape is ever gone, dsh's own text is left untouched.\n */\nexport const STOCK_UNAUTHORIZED = 'dsh web authentication required; reopen the URL printed by dsh web.'\n\n/** The 401 body: dsh's own line, plus where this deployment's log page is. */\nexport function authNoticeBody(logUrl: string | null): string {\n if (logUrl === null)\n return `${STOCK_UNAUTHORIZED}\\n`\n return `${STOCK_UNAUTHORIZED}\\nhome-hosted: the URL with its token is in the dsh log at ${logUrl}. An authentication plugin can sign you in seamlessly instead.\\n`\n}\n\ninterface AuthLike {\n writeUnauthorized?: unknown\n}\n\n/**\n * Wrap the auth object's 401 writer so the body carries the notice.\n * @returns the disposer that puts the original back.\n */\nfunction wrap(auth: { writeUnauthorized: (req: unknown, res: unknown) => void }, logUrlOf: () => string | null): () => void {\n const original = auth.writeUnauthorized.bind(auth)\n auth.writeUnauthorized = (req, res) => {\n const response = res as {\n end?: (...args: unknown[]) => unknown\n once?: (event: string, listener: () => void) => unknown\n writableEnded?: boolean\n }\n const end = response.end\n if (typeof end !== 'function' || response.writableEnded === true) {\n original(req, res)\n return\n }\n\n let restored = false\n const restore = (): void => {\n if (restored) return\n restored = true\n try {\n response.end = end\n }\n catch {\n // a response that refuses the assignment keeps the original writer\n }\n }\n\n try {\n response.end = (...args: unknown[]) => {\n restore()\n const [chunk, ...rest] = args\n let body = chunk\n // Only the untouched stock line is ours to rewrite; an authentication\n // plugin's own page (or the same sentence inside longer text) is left alone.\n if (typeof chunk === 'string' && chunk.trim() === STOCK_UNAUTHORIZED) {\n try {\n body = authNoticeBody(logUrlOf())\n }\n catch {\n body = chunk\n }\n }\n return end.call(response, body, ...rest)\n }\n }\n catch {\n original(req, res)\n return\n }\n\n // A writer that ends later still gets the notice; one that never ends must\n // not leave our wrapper on the response.\n try {\n response.once?.('finish', restore)\n response.once?.('close', restore)\n }\n catch {\n // events are best-effort tidiness, never a reason to lose the 401\n }\n original(req, res)\n }\n return () => {\n auth.writeUnauthorized = original\n }\n}\n\n/**\n * Install the notice once the connection service is up. `logUrlOf` is read per\n * 401, so turning the setting off takes effect without re-installing anything.\n */\nexport function registerAuthNotice(ctx: Context, logUrlOf: () => string | null): void {\n ctx.inject(['connection'], (scoped) => {\n scoped.effect(() => {\n const connection = scoped.get('connection') as { browserAuth?: AuthLike } | undefined\n const auth = connection?.browserAuth\n if (typeof auth?.writeUnauthorized !== 'function')\n return () => {}\n return wrap(auth as { writeUnauthorized: (req: unknown, res: unknown) => void }, logUrlOf)\n }, 'dsh-home-hosted: sign-in notice')\n })\n}\n", "/**\n * The plugin's own settings, kept in a 0600 JSON file under its state directory.\n *\n * These are the things a person toggles; the Cordis row config stays for\n * operator overrides only, so neither surface can silently overwrite the other.\n */\nimport { AGENT_TOOL_NAMES, DEFAULT_SETTINGS, isOnPortConflict } from './shared/contracts.js'\nimport type { AgentToolName, EntryIntent, PluginSettings, SettingsPatch } from './shared/contracts.js'\nimport { SETTINGS_VERSION } from './shared/contracts.js'\nimport { readJson, writeJsonAtomic } from './util/fsx.js'\nimport { defaultIntent } from './home-hosted/entries.js'\n\nfunction knownTool(value: unknown): value is AgentToolName {\n return typeof value === 'string' && (AGENT_TOOL_NAMES as readonly string[]).includes(value)\n}\n\nfunction normalizeIntent(value: Partial<EntryIntent> | null | undefined, fallbackId: string): EntryIntent {\n const base = defaultIntent(typeof value?.id === 'string' && value.id.length > 0 ? value.id : fallbackId)\n return {\n id: base.id,\n autostart: typeof value?.autostart === 'boolean' ? value.autostart : base.autostart,\n // Absent means a stored intent from before persistence existed: it wants it.\n persistent: typeof value?.persistent === 'boolean' ? value.persistent : base.persistent,\n // Only a policy the panel's own schema parses may reach the config it boots from.\n onPortConflict: isOnPortConflict(value?.onPortConflict) ? value.onPortConflict : base.onPortConflict,\n stopKillPortHolders: typeof value?.stopKillPortHolders === 'boolean' ? value.stopKillPortHolders : base.stopKillPortHolders,\n }\n}\n\n/**\n * Tools that were split apart before 0.2.0 and merged since. An allowlist\n * written by an older release lists the old names, and an unknown name is\n * dropped \u2014 which would silently turn every tool off on upgrade.\n */\nconst LEGACY_TOOL_NAMES: Record<string, string> = {\n servers_start: 'servers_lifecycle',\n servers_stop: 'servers_lifecycle',\n servers_restart: 'servers_lifecycle',\n servers_create: 'servers_edit',\n servers_update: 'servers_edit',\n servers_delete: 'servers_edit',\n autostart_install: 'autostart_manage',\n autostart_uninstall: 'autostart_manage',\n}\n\n/** The pair an older release wrote when nobody had chosen anything. */\nfunction isLegacyDefaultAllowlist(names: readonly string[]): boolean {\n return names.length === 2 && names.includes('status') && names.includes('servers_list')\n}\n\n/**\n * The release that merged the split tools and introduced `manageDsh`. The\n * migration bound is that release, never the current `SETTINGS_VERSION`: bumping\n * the stamp for an unrelated field must not re-run a \"nobody chose\" reading over\n * a file that already speaks the current shape.\n */\nconst PRE_MERGE_SETTINGS_VERSION = 2\n\nconst BOOT_MECHANISMS: readonly string[] = ['auto', 'systemd-user', 'systemd-system', 'launchd-agent', 'launchd-daemon', 'xdg-autostart', 'windows-run', 'windows-task', 'container', 'unsupported']\n\nfunction isBootMechanism(value: unknown): value is PluginSettings['autostart']['mechanism'] {\n return typeof value === 'string' && BOOT_MECHANISMS.includes(value)\n}\n\nfunction normalize(raw: Partial<PluginSettings> | null, fallbackEntryId: string): PluginSettings {\n const entries = Array.isArray(raw?.entries)\n ? raw.entries.map(intent => normalizeIntent(intent, fallbackEntryId))\n : []\n const declared = Array.isArray(raw?.agentTools?.allow) ? raw.agentTools.allow : null\n // Only a file written before the version stamp needs translating: the release\n // that wrote it named tools this one merged, and dropping those names silently\n // would turn every tool off on upgrade.\n const legacyFile = (typeof raw?.version === 'number' ? raw.version : 1) < PRE_MERGE_SETTINGS_VERSION\n const migrated = declared === null\n ? null\n : [...new Set(declared.map(name => (legacyFile ? LEGACY_TOOL_NAMES[name] ?? name : name)).filter(knownTool))] as AgentToolName[]\n // In a legacy file the old default pair is not a choice, it is what that\n // release wrote for \"unset\".\n const choseNothing = legacyFile && declared !== null && migrated !== null && isLegacyDefaultAllowlist(declared)\n const allow = migrated === null || choseNothing ? [...DEFAULT_SETTINGS.agentTools.allow] : migrated\n const attempt = raw?.autostart?.lastAttempt\n return {\n version: SETTINGS_VERSION,\n autostart: {\n enabled: raw?.autostart?.enabled === true,\n mechanism: isBootMechanism(raw?.autostart?.mechanism) ? raw.autostart.mechanism : 'auto',\n ...(attempt === undefined ? {} : { lastAttempt: attempt }),\n },\n // A pre-0.2.0 file expressed management only by holding an intent for the\n // harness entry; the page's toggle needs the flag it never had.\n manageDsh: raw?.manageDsh === true\n || (legacyFile && entries.some(entry => entry.id === fallbackEntryId)),\n entries,\n panel: {\n // An integer in range, or nothing: the panel's schema rejects anything else,\n // and a value it cannot parse stops it booting at all.\n port: typeof raw?.panel?.port === 'number' && Number.isInteger(raw.panel.port) && raw.panel.port >= 1 && raw.panel.port <= 65535\n ? raw.panel.port\n : null,\n },\n authNotice: raw?.authNotice === undefined ? DEFAULT_SETTINGS.authNotice : raw.authNotice === true,\n // Absent means on: a stale token is a fault to repair, not a setting to opt into.\n reclaimToken: raw?.reclaimToken === undefined ? DEFAULT_SETTINGS.reclaimToken : raw.reclaimToken === true,\n // Absent means on: a plugin that owns one panel should say so by default.\n instancesNotice: raw?.instancesNotice === undefined ? DEFAULT_SETTINGS.instancesNotice : raw.instancesNotice === true,\n uiStyle: raw?.uiStyle === 'compact' ? 'compact' : 'detailed',\n agentTools: {\n // Absent means the default (on), and so does the pair the previous\n // release wrote for it; any other explicit false stays false.\n enabled: choseNothing || raw?.agentTools?.enabled === undefined\n ? DEFAULT_SETTINGS.agentTools.enabled\n : raw.agentTools.enabled === true,\n // Absent means every tool; an explicit empty list means none of them.\n allow,\n },\n cli: {\n prefer: raw?.cli?.prefer === 'global' ? 'global' : 'pinned',\n },\n }\n}\n\nexport class SettingsStore {\n private current: PluginSettings\n private readonly listeners = new Set<(settings: PluginSettings) => void>()\n\n constructor(\n readonly file: string,\n private readonly fallbackEntryId: string,\n ) {\n this.current = normalize(readJson<Partial<PluginSettings>>(file), fallbackEntryId)\n }\n\n get(): PluginSettings {\n return this.current\n }\n\n /** The intent for an entry, materialising the default when it has none yet. */\n intentFor(id: string): EntryIntent {\n return this.current.entries.find(entry => entry.id === id) ?? defaultIntent(id)\n }\n\n update(patch: SettingsPatch): PluginSettings {\n const next = normalize({\n // A patch is not a legacy file: the migration belongs to reading one.\n version: SETTINGS_VERSION,\n autostart: { ...this.current.autostart, ...(patch.autostart ?? {}) },\n manageDsh: patch.manageDsh ?? this.current.manageDsh,\n entries: patch.entries ?? this.current.entries,\n panel: { ...this.current.panel, ...(patch.panel ?? {}) },\n authNotice: patch.authNotice ?? this.current.authNotice,\n reclaimToken: patch.reclaimToken ?? this.current.reclaimToken,\n instancesNotice: patch.instancesNotice ?? this.current.instancesNotice,\n uiStyle: patch.uiStyle ?? this.current.uiStyle,\n agentTools: { ...this.current.agentTools, ...(patch.agentTools ?? {}) },\n cli: { ...this.current.cli, ...(patch.cli ?? {}) },\n }, this.fallbackEntryId)\n this.current = next\n writeJsonAtomic(this.file, next, 0o600)\n for (const listener of this.listeners)\n listener(next)\n return next\n }\n\n onChange(listener: (settings: PluginSettings) => void): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n}\n", "/**\n * Agent tools, on by default and allowlisted by name.\n *\n * The list is read at registration time and re-read whenever the settings file\n * changes, so toggling a tool takes effect without a restart. A tool that\n * changes something asks the approval service only when the calling session is\n * not already `danger-full-access` \u2014 the session has then granted exactly what\n * the tool would be asking about, and asking anyway is what makes a\n * Full-access run fail against an auto-rejecting approval channel.\n */\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { ParameterPropertySpec, ParameterSchemaSpec } from '@deepseek-ai/dsh-tools'\nimport { defineTool } from '@deepseek-ai/dsh-tools'\nimport type { AgentToolName, ForeignMechanism, HomeHostedStatus, InstanceView, RpcEndpoint, UiAction } from './shared/contracts.js'\nimport { FOREIGN_MECHANISMS, MUTATING_AGENT_TOOLS, ON_PORT_CONFLICT_POLICIES } from './shared/contracts.js'\nimport { canonicalPath, describeInstance } from './home-hosted/instances.js'\nimport type { HomeHostedService } from './service.js'\nimport type { SettingsStore } from './settings.js'\n\ninterface ApprovalLike {\n request: (request: { agent: unknown, toolName: string, reason?: string }) => Promise<string>\n}\n\ninterface SandboxPolicyLike {\n resolve?: (input: { session?: unknown }) => { mode?: string } | undefined\n}\n\n/** The subset of `ctx.userQuestions` this uses; absent on a host without a UI. */\ninterface UserQuestionsLike {\n ask: (request: {\n questions: Array<{\n id: string\n header?: string\n question: string\n detail?: string\n options?: Array<{ label: string, description?: string }>\n }>\n agent?: unknown\n signal?: unknown\n }) => Promise<{ answers: Array<{ id: string, selected: string[], custom?: string }> }>\n}\n\n/** The calling session's file-sandbox mode, or null when nothing can report it. */\nfunction sandboxMode(ctx: Context, exec: unknown): string | null {\n const policy = ctx.get('sandboxPolicy') as SandboxPolicyLike | undefined\n if (policy?.resolve === undefined)\n return null\n const session = (exec as { agent?: { session?: unknown } } | undefined)?.agent?.session\n try {\n return policy.resolve({ ...(session === undefined ? {} : { session }) })?.mode ?? null\n }\n catch {\n return null\n }\n}\n\ntype Input = Record<string, unknown>\n\n/** A payload carrying the panel a call targets and how to reach it. */\nfunction withTarget(payload: unknown, home: string, via: ForeignMechanism | null): unknown {\n const target = via === null ? { home } : { home, via }\n return typeof payload === 'object' && payload !== null ? { ...payload, ...target } : target\n}\n\nfunction stringArg(input: Input, key: string): string | null {\n const value = input[key]\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null\n}\n\n/**\n * The workspace a server call acts on, which every such call names explicitly \u2014\n * a server id is only unique inside one workspace, so a default would make the\n * same call mean different things on different machines.\n */\nfunction requiredWorkspace(input: Input): string {\n const workspace = stringArg(input, 'workspace')\n if (workspace === null)\n throw new Error('workspace is required; a server id is only unique inside one')\n return workspace\n}\n\n/**\n * A structured argument. `entry`/`patch` are declared as objects, so the runtime\n * validates them before this runs; what remains is the absent case, which the\n * caller reports as such rather than as an unusable value.\n */\nfunction jsonArg(input: Input, key: string): Record<string, unknown> | null {\n const value = input[key]\n return typeof value === 'object' && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : null\n}\n\n/** Why a structured argument was unusable, naming the type that arrived. */\nfunction jsonArgError(input: Input, key: string, purpose: string): Error {\n const value = input[key]\n const received = value === undefined ? 'missing' : Array.isArray(value) ? 'array' : typeof value\n return new Error(`${key} must be a JSON object ${purpose} (received: ${received})`)\n}\n\ninterface ToolSpec {\n description: string\n parameters: ParameterSchemaSpec\n /** The endpoint call this tool performs, after its own argument handling. */\n run: (input: Input) => { endpoint: RpcEndpoint, payload: unknown }\n}\n\n/**\n * Every tool takes the same optional panel selector. Omitting it means the panel\n * this plugin manages; naming another panel is neither ignored nor silently\n * retargeted \u2014 this plugin asks the user first, then reaches that panel the only\n * way it can: its config file, or its own CLI.\n */\nconst INSTANCE_PARAM = {\n type: 'string',\n description: 'Panel state root or URL; omit for the managed panel',\n} as const satisfies ParameterPropertySpec\n\n/** Which workspace a call acts on; a server id is only unique inside one. */\nconst WORKSPACE_PARAM = {\n type: 'string',\n required: true,\n description: 'Workspace id (^[a-z0-9][a-z0-9_-]*$)',\n} as const satisfies ParameterPropertySpec\n\n/**\n * A home-hosted server entry, as one object parameter.\n *\n * Declared field by field rather than as `type: 'json'`: an author-only `json`\n * node projects to a schema with no `type`, and the model then has nothing to\n * aim at \u2014 the same mistake that made `create`/`update` unusable. Only the keys\n * this plugin understands are named; the panel accepts more, so unknown keys\n * stay allowed.\n */\nconst ENTRY_FIELDS: ParameterSchemaSpec = {\n id: { type: 'string', required: true, description: 'Entry id, ^[a-z0-9][a-z0-9_-]*$' },\n command: { type: 'string', description: 'Executable to run' },\n args: { type: 'array', items: { type: 'string' }, description: 'Arguments' },\n cwd: { type: 'string', description: 'Working directory' },\n label: { type: 'string', description: 'Human label' },\n enabled: { type: 'boolean', description: 'Whether the entry may run' },\n autostart: { type: 'boolean', description: 'Start it when the panel starts' },\n // The panel's schema is `1..65535 | null`, and a patch that clears a port sends it.\n port: { oneOf: [{ type: 'number' }, { type: 'null' }], description: 'Port the panel tracks for this entry, or null' },\n bind: { type: 'string', description: 'Bind host or local/lan' },\n onPortConflict: { type: 'string', enum: [...ON_PORT_CONFLICT_POLICIES], description: 'Policy when the port is taken' },\n stop: { type: 'object', additionalProperties: true, description: 'Stop policy, e.g. killPortHolders' },\n health: { type: 'object', additionalProperties: true, description: 'Health check block' },\n restart: { type: 'object', additionalProperties: true, description: 'Restart policy block' },\n env: { type: 'object', additionalProperties: true, description: 'Environment variables' },\n dataEnvs: { type: 'object', additionalProperties: true, description: 'Environment entries the panel fills in' },\n}\n\nconst ENTRY_PARAM = {\n type: 'object',\n additionalProperties: true,\n description: 'A home-hosted server entry: at least id and command',\n properties: ENTRY_FIELDS,\n} as const satisfies ParameterPropertySpec\n\nconst PATCH_PARAM = {\n type: 'object',\n additionalProperties: true,\n description: 'Fields to change on an existing entry; only the given keys are touched',\n // The same fields, none required and without `id`: the entry patched is the one\n // the `id` argument names, and the panel rejects a patch that carries an id.\n properties: Object.fromEntries(Object.entries(ENTRY_FIELDS).filter(([key]) => key !== 'id')),\n} as const satisfies ParameterPropertySpec\n\nconst TOOL_SPECS: Record<AgentToolName, ToolSpec> = {\n status: {\n description: 'Report the panel state, the panels found on this machine, its autostart entry, and the entries this plugin manages.',\n parameters: { instance: INSTANCE_PARAM },\n run: () => ({ endpoint: 'status', payload: {} }),\n },\n workspaces_list: {\n description: 'List every workspace the panel serves, with its label, entry count and running count. Read-only.',\n parameters: { instance: INSTANCE_PARAM },\n run: () => ({ endpoint: 'workspaces.list', payload: {} }),\n },\n servers_list: {\n description: 'List the servers home-hosted supervises in one workspace, with status, pid and url.',\n parameters: { instance: INSTANCE_PARAM, workspace: WORKSPACE_PARAM },\n run: (input) => {\n const workspace = requiredWorkspace(input)\n return { endpoint: 'servers.list', payload: { workspace } }\n },\n },\n servers_lifecycle: {\n description: 'Start, stop or restart a server supervised by home-hosted. Restarting the entry this session runs as ends the session.',\n parameters: {\n instance: INSTANCE_PARAM,\n workspace: WORKSPACE_PARAM,\n action: { type: 'string', required: true, description: 'start, stop or restart' },\n id: { type: 'string', required: true, description: 'Server entry id' },\n },\n run: (input) => {\n const action = stringArg(input, 'action')\n if (action !== 'start' && action !== 'stop' && action !== 'restart')\n throw new Error('action must be start, stop or restart')\n const id = stringArg(input, 'id')\n if (id === null)\n throw new Error('id is required')\n return {\n endpoint: `servers.${action}` as RpcEndpoint,\n payload: { id, workspace: requiredWorkspace(input) },\n }\n },\n },\n servers_edit: {\n description: 'Create, update or delete a home-hosted server entry. Delete is refused for the entry this session runs as.',\n parameters: {\n instance: INSTANCE_PARAM,\n workspace: WORKSPACE_PARAM,\n action: { type: 'string', required: true, description: 'create, update or delete' },\n id: { type: 'string', description: 'Server entry id (update, delete)' },\n entry: ENTRY_PARAM,\n patch: PATCH_PARAM,\n },\n run: (input) => {\n const action = stringArg(input, 'action')\n const workspace = requiredWorkspace(input)\n if (action === 'create') {\n const entry = jsonArg(input, 'entry')\n if (entry === null)\n throw jsonArgError(input, 'entry', 'to create a server')\n return { endpoint: 'servers.create', payload: { entry, workspace } }\n }\n if (action === 'update') {\n const id = stringArg(input, 'id')\n const patch = jsonArg(input, 'patch')\n if (id === null)\n throw new Error('id is required to update a server')\n if (patch === null)\n throw jsonArgError(input, 'patch', 'to update a server')\n // A caller may echo the id back; it is the key, not a field to change.\n const { id: _key, ...fields } = patch\n return { endpoint: 'servers.update', payload: { id, patch: fields, workspace } }\n }\n if (action === 'delete') {\n const id = stringArg(input, 'id')\n if (id === null)\n throw new Error('id is required to delete a server')\n return { endpoint: 'servers.delete', payload: { id, workspace } }\n }\n throw new Error('action must be create, update or delete')\n },\n },\n autostart_manage: {\n description: 'Install or remove the OS entry that starts home-hosted at boot or login. Installing stops the panel (and every server it supervises, which can include this session) and starts it again through that entry.',\n parameters: {\n instance: INSTANCE_PARAM,\n action: { type: 'string', required: true, description: 'install or uninstall' },\n mechanism: { type: 'string', description: 'Mechanism, e.g. systemd-system; omit for the plugin setting' },\n },\n run: (input) => {\n const action = stringArg(input, 'action')\n if (action !== 'install' && action !== 'uninstall')\n throw new Error('action must be install or uninstall')\n const mechanism = stringArg(input, 'mechanism')\n return { endpoint: `boot.${action}` as RpcEndpoint, payload: mechanism === null ? {} : { mechanism } }\n },\n },\n ui_manage: {\n description: 'Inspect or change the panel\\'s own web UI: status, update, revert, or install a UI by release asset or local zip.',\n parameters: {\n instance: INSTANCE_PARAM,\n action: { type: 'string', required: true, description: 'status, update, revert or switch' },\n asset: { type: 'string', description: 'Release asset name to install (switch), e.g. noc-console' },\n repo: { type: 'string', description: 'owner/name the asset lives in (switch/update)' },\n tag: { type: 'string', description: 'Release tag (switch/update)' },\n file: { type: 'string', description: 'Absolute path to a local UI zip (switch)' },\n },\n run: (input) => {\n const action = stringArg(input, 'action')\n if (action !== 'status' && action !== 'update' && action !== 'revert' && action !== 'switch')\n throw new Error('action must be status, update, revert or switch')\n const optional = (key: string): Record<string, string> => {\n const value = stringArg(input, key)\n return value === null ? {} : { [key]: value }\n }\n return {\n endpoint: 'ui.manage',\n payload: {\n action: action as UiAction,\n ...optional('file'),\n ...optional('asset'),\n ...optional('repo'),\n ...optional('tag'),\n },\n }\n },\n },\n}\n\nexport function toolNameFor(name: AgentToolName): string {\n return `home_hosted_${name}`\n}\n\n/**\n * Actions of an otherwise mutating tool that only read. `ui_manage` carries both,\n * and a status call must not be blocked (or prompted for) as if it changed the UI.\n */\nconst READ_ONLY_ACTIONS: Partial<Record<AgentToolName, (input: Input) => boolean>> = {\n ui_manage: input => stringArg(input, 'action') === 'status',\n}\n\n/**\n * Which panel a call lands on, and whether the user still has to agree to it.\n *\n * The plugin drives one state root; a person may run several. A call that names\n * another panel is neither silently retargeted nor refused outright: the plugin\n * asks the user, names the way it would reach that panel (its config file, or\n * its own CLI), and only then changes it. A call that names none is the managed\n * panel by definition, unless several exist and the `instancesNotice` setting is\n * on, in which case the user is asked which panel was meant.\n */\ntype TargetPlan =\n | { ok: false, text: string }\n /** `home: null` is the managed panel; a null `ask` means nothing to confirm. */\n | { ok: true, home: string | null, ask: null }\n | { ok: true, home: null, ask: 'which', mechanisms: readonly ForeignMechanism[] }\n | { ok: true, home: string, ask: 'foreign', mechanisms: readonly ForeignMechanism[] }\n\n/** The plan a question settled on: the panel and how to reach it, or why not. */\ntype TargetDecision = { ok: true, home: string | null, via: ForeignMechanism | null } | { ok: false, text: string }\n\nconst CANCEL_LABEL = 'Cancel'\n\n/**\n * `userQuestions.ask` failures that mean no human could be reached: a host with\n * no answerer, and a delegated child (whose answers never reach a person).\n */\nconst NO_ANSWERER_CODES = new Set(['NO_PROVIDER', 'DELEGATED_CALLER', 'CALLER_NOT_LIVE'])\n\n/** How each mechanism is offered to a person, and how the result names it. */\nconst MECHANISM_WORDING: Record<ForeignMechanism, { option: string, phrase: string }> = {\n file: { option: 'Edit its config file', phrase: 'by editing its servers.config.json' },\n cli: { option: 'Run the home-hosted CLI', phrase: 'by running the home-hosted CLI against its state root' },\n api: { option: 'Generate a token and use its API', phrase: 'by enrolling a token for it and using its API' },\n}\n\n/** Why an endpoint cannot be aimed at another panel, when it cannot. */\nconst NO_FOREIGN_REASON: Partial<Record<RpcEndpoint, string>> = {\n 'status': 'home_hosted_status describes the panel this plugin manages, and it already lists every panel found on the machine.',\n 'boot.install': 'the OS boot entry starts the panel this plugin manages; it is one machine-wide entry and cannot be aimed at another panel.',\n 'boot.uninstall': 'the OS boot entry is one machine-wide entry and belongs to the panel this plugin manages.',\n}\n\n/** The panel inventory, when the host can produce one. */\nasync function inventoryOf(service: HomeHostedService): Promise<InstanceView[]> {\n const reader = (service as unknown as { instances?: () => Promise<InstanceView[]> }).instances\n if (typeof reader !== 'function')\n return []\n try {\n return await reader.call(service)\n }\n catch {\n return []\n }\n}\n\n/** The instance a caller named by state root, URL or project dir. */\nfunction matchInstance(instances: readonly InstanceView[], wanted: string): InstanceView | null {\n const target = wanted.trim()\n if (target.length === 0)\n return null\n const asPath = canonicalPath(target)\n return instances.find(instance =>\n instance.home === asPath\n || instance.url === target\n || (instance.projectDir !== null && instance.projectDir === target),\n ) ?? null\n}\n\nfunction inventoryText(instances: readonly InstanceView[]): string {\n return instances.map(instance => `${instance.managed ? 'managed: ' : ''}${describeInstance(instance)}`).join('; ')\n}\n\nfunction refusalForUnknownPanel(instances: readonly InstanceView[], wanted: string): string {\n return `refused: no home-hosted panel at \"${wanted}\" was found on this machine. `\n + `Panels found: ${inventoryText(instances)}. `\n + 'Ask the user which panel they mean, or read home_hosted_status for the inventory.'\n}\n\nfunction refusalForUnreachablePanel(instances: readonly InstanceView[], target: InstanceView, endpoint: RpcEndpoint): string {\n const reason = NO_FOREIGN_REASON[endpoint] ?? `this plugin has no way to change another panel's state for ${endpoint}`\n return `refused: ${describeInstance(target)} is another home-hosted panel, and ${reason} `\n + `Panels found: ${inventoryText(instances)}.`\n}\n\n/** Read an answer's option labels; a UI may also return free text. */\nfunction answerOf(answer: { answers?: Array<{ id: string, selected: string[], custom?: string }> }, id: string): { selected: string[], custom: string | null } {\n const item = (Array.isArray(answer?.answers) ? answer.answers : []).find(entry => entry.id === id)\n return {\n selected: Array.isArray(item?.selected) ? item.selected : [],\n custom: typeof item?.custom === 'string' && item.custom.trim().length > 0 ? item.custom.trim() : null,\n }\n}\n\n/**\n * Which panel the call lands on, validated before anything is asked. The\n * questions themselves run after the approval gate, so a call the session may\n * not make never puts a question to the user.\n */\nasync function planTarget(\n service: HomeHostedService,\n settings: SettingsStore,\n endpoint: RpcEndpoint,\n requested: string | null,\n mutating: boolean,\n): Promise<TargetPlan> {\n const instances = await inventoryOf(service)\n if (instances.length === 0) {\n // An unverifiable target must not become the managed panel by default: that\n // is the silent retarget this whole check exists to prevent.\n return requested === null\n ? { ok: true, home: null, ask: null }\n : { ok: false, text: `refused: this host could not list the home-hosted panels on this machine, so \"${requested}\" cannot be checked; ask the user, or call without instance to act on the panel this plugin manages.` }\n }\n const managed = instances.find(instance => instance.managed)\n const mechanisms = FOREIGN_MECHANISMS[endpoint]\n\n if (requested !== null) {\n const target = matchInstance(instances, requested)\n if (target === null)\n return { ok: false, text: refusalForUnknownPanel(instances, requested) }\n if (target.managed)\n return { ok: true, home: null, ask: null }\n if (mechanisms === undefined)\n return { ok: false, text: refusalForUnreachablePanel(instances, target, endpoint) }\n return mutating ? { ok: true, home: target.home, ask: 'foreign', mechanisms } : { ok: true, home: target.home, ask: null }\n }\n\n // Asking which panel is only worth it when this call could reach more than one.\n if (!mutating || !settings.get().instancesNotice || managed === undefined || mechanisms === undefined || instances.length < 2)\n return { ok: true, home: null, ask: null }\n return { ok: true, home: null, ask: 'which', mechanisms }\n}\n\n/** One ask, returning the answered option labels or how it failed. */\nasync function ask(\n ctx: Context,\n exec: unknown,\n questions: UserQuestionsLike,\n item: { id: string, question: string, detail?: string, options: Array<{ label: string, description: string }> },\n): Promise<{ ok: true, selected: string[], custom: string | null } | { ok: false, code: string | null, error: unknown }> {\n try {\n const answer = await questions.ask({\n questions: [{ id: item.id, header: 'home-hosted', question: item.question, detail: item.detail, options: item.options }],\n agent: (exec as { agent?: unknown } | undefined)?.agent,\n signal: (exec as { signal?: unknown } | undefined)?.signal,\n })\n const { selected, custom } = answerOf(answer, item.id)\n return { ok: true, selected, custom }\n }\n catch (error) {\n return { ok: false, code: errorCode(error), error }\n }\n}\n\n/**\n * Confirm a change to a panel the plugin does not manage, and how to reach it.\n *\n * Every mechanism that endpoint has is offered, least invasive first, so the\n * person chooses whether the plugin edits that panel's config, runs the\n * home-hosted CLI against it, or mints a token and uses its API. A host that\n * cannot reach a person proceeds on the mechanism the caller would have used\n * anyway; the tool result then says which one it was.\n */\nasync function askForeignPanel(\n ctx: Context,\n exec: unknown,\n instances: readonly InstanceView[],\n target: InstanceView,\n mechanisms: readonly ForeignMechanism[],\n): Promise<TargetDecision> {\n const questions = ctx.get('userQuestions') as UserQuestionsLike | undefined\n const first = mechanisms[0] as ForeignMechanism\n if (typeof questions?.ask !== 'function')\n return { ok: true, home: target.home, via: first }\n\n const answer = await ask(ctx, exec, questions, {\n id: 'home-hosted-foreign-panel',\n question: `${target.home} is not a panel this plugin manages. How should it be changed?`,\n detail: inventoryText(instances),\n options: [\n ...mechanisms.map(mechanism => ({\n label: MECHANISM_WORDING[mechanism].option,\n description: `${MECHANISM_WORDING[mechanism].phrase}${\n mechanism === 'api' ? '; that mints a token for it, replacing any it had' : '; this plugin holds no API token for that panel'}`,\n })),\n { label: CANCEL_LABEL, description: 'Change nothing; say which panel you meant instead.' },\n ],\n })\n\n if (!answer.ok) {\n if (answer.code === 'ASK_ABORTED')\n return { ok: false, text: 'cancelled: the user dismissed the panel question to speak instead; stop and wait for their message.' }\n if (answer.code !== null && NO_ANSWERER_CODES.has(answer.code))\n return { ok: true, home: target.home, via: first }\n return { ok: false, text: `refused: the panel question could not be asked (${messageOf(answer.error)}); ask the user before changing another panel.` }\n }\n\n // Cancel settles it: an answer that cancels, or cancels and types something,\n // must never be read as consent.\n if (answer.selected.includes(CANCEL_LABEL))\n return { ok: false, text: `cancelled: the user did not confirm changing ${target.home}; ask which panel they meant.` }\n const chosen = answer.selected.find(label => label !== CANCEL_LABEL)\n if (chosen !== undefined) {\n const mechanism = mechanisms.find(candidate => MECHANISM_WORDING[candidate].option === chosen)\n if (mechanism !== undefined)\n return { ok: true, home: target.home, via: mechanism }\n }\n if (chosen === undefined && answer.custom !== null) {\n const mechanism = mechanisms.find(candidate => matchesMechanism(answer.custom as string, candidate))\n if (mechanism !== undefined)\n return { ok: true, home: target.home, via: mechanism }\n }\n return { ok: false, text: `cancelled: the user did not confirm changing ${target.home}; ask which panel they meant.` }\n}\n\n/** Whether free text names a mechanism, so a typed answer is honoured. */\nfunction matchesMechanism(text: string, mechanism: ForeignMechanism): boolean {\n const value = text.toLowerCase()\n if (mechanism === 'file')\n return /file|config/.test(value)\n if (mechanism === 'cli')\n return /cli|command|terminal/.test(value)\n return /api|token/.test(value)\n}\n\n/**\n * Ask which panel a call that named none should hit, when more than one could\n * take it. The managed panel is the default, so a host with no answerer proceeds\n * there; the others are only offered when this call knows how to reach them.\n */\nasync function askWhichPanel(\n ctx: Context,\n exec: unknown,\n instances: readonly InstanceView[],\n managed: InstanceView,\n others: readonly InstanceView[],\n mechanisms: readonly ForeignMechanism[],\n): Promise<TargetDecision> {\n const questions = ctx.get('userQuestions') as UserQuestionsLike | undefined\n if (typeof questions?.ask !== 'function')\n return { ok: true, home: null, via: null }\n\n const answer = await ask(ctx, exec, questions, {\n id: 'home-hosted-instance',\n question: `${instances.length} home-hosted panels were found on this machine. Act on the panel this plugin manages?`,\n detail: inventoryText(instances),\n options: [\n { label: managed.home, description: `managed by this plugin${managed.url === null ? '' : ` \u00B7 ${managed.url}`}` },\n ...others.map(instance => ({\n label: instance.home,\n description: `not managed by this plugin${instance.url === null ? '' : ` \u00B7 ${instance.url}`}`\n + ` \u00B7 ${mechanisms.map(candidate => MECHANISM_WORDING[candidate].phrase).join(', or ')}`,\n })),\n { label: CANCEL_LABEL, description: 'Do nothing; say which panel you meant instead.' },\n ],\n })\n\n if (!answer.ok) {\n if (answer.code === 'ASK_ABORTED')\n return { ok: false, text: 'cancelled: the user dismissed the panel question to speak instead; stop and wait for their message.' }\n if (answer.code !== null && NO_ANSWERER_CODES.has(answer.code))\n return { ok: true, home: null, via: null }\n return { ok: false, text: `refused: the panel question could not be asked (${messageOf(answer.error)}); ask the user which panel they mean before acting.` }\n }\n\n if (answer.selected.includes(CANCEL_LABEL))\n return { ok: false, text: 'cancelled: the user chose not to pick a panel; ask them which one they mean.' }\n const chosen = answer.selected.find(label => label !== CANCEL_LABEL)\n if (chosen !== undefined) {\n const instance = matchInstance(instances, chosen)\n if (instance === null)\n return { ok: false, text: `cancelled: the answer \"${chosen}\" names no panel; ask the user which panel they mean.` }\n return instance.managed ? { ok: true, home: null, via: null } : { ok: true, home: instance.home, via: null }\n }\n if (answer.custom !== null) {\n const instance = matchInstance(instances, answer.custom)\n if (instance?.managed === true)\n return { ok: true, home: null, via: null }\n if (instance !== null)\n return { ok: true, home: instance.home, via: null }\n return { ok: false, text: `cancelled: the user answered \"${answer.custom}\", which names no panel; ask them which panel they mean.` }\n }\n return { ok: false, text: 'cancelled: the panel question was not answered; ask the user which panel they mean before acting.' }\n}\n\n/**\n * Run the questions a plan called for, and settle on the panel and mechanism.\n *\n * A call that named another panel is asked about the mechanism straight away; a\n * call that named none is asked which panel first, and when that answer is\n * another panel it is asked how to reach it \u2014 one decision per question.\n */\nasync function confirmPlan(\n ctx: Context,\n exec: unknown,\n service: HomeHostedService,\n plan: Extract<TargetPlan, { ok: true }>,\n): Promise<TargetDecision> {\n if (plan.ask === null)\n return { ok: true, home: plan.home, via: null }\n const instances = await inventoryOf(service)\n const managed = instances.find(instance => instance.managed)\n if (plan.ask === 'foreign') {\n const target = instances.find(instance => instance.home === plan.home)\n // A panel that vanished between validation and here is not worth a question.\n return target === undefined\n ? { ok: false, text: refusalForUnknownPanel(instances, plan.home) }\n : await askForeignPanel(ctx, exec, instances, target, plan.mechanisms)\n }\n if (managed === undefined)\n return { ok: true, home: null, via: null }\n const others = instances.filter(instance => !instance.managed)\n const picked = await askWhichPanel(ctx, exec, instances, managed, others, plan.mechanisms)\n if (!picked.ok || picked.home === null)\n return picked\n const chosen = instances.find(instance => instance.home === picked.home)\n if (chosen === undefined || chosen.managed)\n return picked\n return await askForeignPanel(ctx, exec, instances, chosen, plan.mechanisms)\n}\n\n/** Error codes that mean the panel refused the plugin's credential. */\nconst TOKEN_REFUSAL_CODES = new Set([\n 'AUTH_REQUIRED',\n 'AUTH_UNARMED',\n 'UNAUTHORIZED',\n 'FORBIDDEN',\n 'INVALID_TOKEN',\n 'TOKEN_STALE',\n 'TOKEN_REFUSED',\n])\n\nfunction errorCode(error: unknown): string | null {\n const code = (error as { code?: unknown } | null | undefined)?.code\n return typeof code === 'string' ? code : null\n}\n\nfunction errorStatus(error: unknown): number | null {\n const status = (error as { status?: unknown } | null | undefined)?.status\n return typeof status === 'number' ? status : null\n}\n\n/** A panel answer that refused the token, as opposed to a bad request or a 404. */\nfunction refusedByPanel(error: unknown): boolean {\n const status = errorStatus(error)\n if (status === 401 || status === 403)\n return true\n const code = errorCode(error)\n return code !== null && TOKEN_REFUSAL_CODES.has(code)\n}\n\n/**\n * Whether a failed call failed because the panel refused this plugin's token.\n *\n * A 401/403 or an auth code is that refusal directly. A client that could not be\n * established at all is ambiguous \u2014 the panel may simply be down \u2014 so the\n * panel's measured token state is asked instead; only `stale`, which the panel\n * sets from an actual refusal, justifies a repair.\n */\nasync function tokenRefused(service: Pick<HomeHostedService, 'call'>, error: unknown): Promise<boolean> {\n if (refusedByPanel(error))\n return true\n if (errorCode(error) !== 'PANEL_UNAVAILABLE')\n return false\n try {\n const status = await service.call('status', {}) as HomeHostedStatus | null\n return status?.panel.token === 'stale'\n }\n catch {\n return false\n }\n}\n\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nfunction failureText(error: unknown): string {\n return `failed: ${messageOf(error)}`\n}\n\nfunction registerOne(ctx: Context, service: HomeHostedService, settings: SettingsStore, name: AgentToolName): () => void {\n const spec = TOOL_SPECS[name]\n const toolName = toolNameFor(name)\n const mutatingTool = MUTATING_AGENT_TOOLS.includes(name)\n\n return ctx.tools.register(defineTool({\n name: toolName,\n description: spec.description,\n parameters: spec.parameters,\n output: {\n schema: { type: 'string' },\n render: (_args, value) => [{ type: 'text', text: value }],\n },\n async execute(args, exec) {\n const input = (args ?? {}) as Input\n // Argument handling is not a panel call: a bad input is reported as-is,\n // never \"repaired\" by minting a token.\n let request: { endpoint: RpcEndpoint, payload: unknown }\n try {\n request = spec.run(input)\n }\n catch (error) {\n return failureText(error)\n }\n\n const mode = sandboxMode(ctx, exec)\n // Fail closed: only a positively identified read-only action skips the\n // gate; an unknown action is rejected by the argument check above.\n const mutating = mutatingTool && !(READ_ONLY_ACTIONS[name]?.(input) ?? false)\n // The panel is settled before permission is asked: a refusal here changes\n // nothing, and no question is put to the user for a call that will not run.\n const plan = await planTarget(service, settings, request.endpoint, stringArg(input, 'instance'), mutating)\n if (!plan.ok)\n return plan.text\n\n if (mutating && mode !== 'danger-full-access') {\n const remedy = 'Set the session to Full access (danger-full-access), or use a session where approvals can be answered.'\n const approval = ctx.get('approval') as ApprovalLike | undefined\n if (approval?.request === undefined)\n return `refused: this session runs in ${mode ?? 'an unknown'} sandbox and the deployment has no approval service. ${remedy}`\n const outcome = await approval.request({\n agent: (exec as { agent?: unknown } | undefined)?.agent,\n toolName,\n reason: `${spec.description} (${JSON.stringify(input)})`,\n })\n if (outcome !== 'allowed-once')\n return `refused: approval answered \"${outcome}\" (session sandbox: ${mode ?? 'unknown'}). ${remedy}`\n }\n\n const target = await confirmPlan(ctx, exec, service, plan)\n if (!target.ok)\n return target.text\n const payload = target.home === null\n ? request.payload\n : withTarget(request.payload, target.home, target.via ?? FOREIGN_MECHANISMS[request.endpoint]?.[0] ?? null)\n // What another panel's result must carry: the model may not have been the\n // one that chose it, and a config edit has no page to show.\n const landing = target.home === null\n ? ''\n : `\\n\\nhome-hosted: ${plan.ask === null ? 'read from' : 'acted on'} ${target.home}, not the panel this plugin manages`\n + `${target.via === null ? '' : `, ${MECHANISM_WORDING[target.via].phrase}`}.`\n\n try {\n return `${JSON.stringify(await service.call(request.endpoint, payload), null, 2)}${landing}`\n }\n catch (error) {\n // A foreign call carries its own credential; repairing this plugin's\n // managed token would churn it and still fail the retry.\n if (target.home !== null || !settings.get().reclaimToken || !(await tokenRefused(service, error)))\n return failureText(error)\n\n // One repair, one retry. The reclaim is a write, but it repairs this\n // plugin's own credential, not the panel's state, so it runs behind the\n // call's existing approval gate rather than asking for a second one.\n try {\n await service.call('panel.reclaimToken', {})\n }\n catch (reclaimError) {\n return `${failureText(error)} (token reclaim failed: ${messageOf(reclaimError)})`\n }\n try {\n return `${JSON.stringify(await service.call(request.endpoint, payload), null, 2)}${landing}`\n }\n catch (retryError) {\n return failureText(retryError)\n }\n }\n },\n }))\n}\n\nexport function registerAgentTools(ctx: Context, service: HomeHostedService, settings: SettingsStore): void {\n ctx.inject(['tools'], (scoped) => {\n const disposers: Array<() => void> = []\n\n const sync = (): void => {\n while (disposers.length > 0)\n disposers.pop()?.()\n const current = settings.get()\n if (!current.agentTools.enabled)\n return\n for (const name of current.agentTools.allow) {\n try {\n disposers.push(registerOne(scoped, service, settings, name))\n }\n catch {\n // one unruly tool must not keep the rest of the allowlist off the model\n }\n }\n }\n\n scoped.effect(() => {\n sync()\n const off = settings.onChange(sync)\n return () => {\n off()\n while (disposers.length > 0)\n disposers.pop()?.()\n }\n }, 'dsh-home-hosted: agent tools')\n })\n}\n"],
4
+ "sourcesContent": ["/**\n * `dsh-home-hosted` \u2014 boot autostart for the home-hosted panel, and home-hosted\n * server management, from inside DeepSeek Harness.\n *\n * The plugin supplies no server entries of its own: everything it manages is\n * declared by the person using the page, and nothing here knows a blessed id\n * beyond the one it is told to treat as the running harness.\n */\nimport type { Context } from '@deepseek-ai/cordis'\nimport path from 'node:path'\nimport { Config } from './config.js'\nimport type { Config as ConfigShape } from './config.js'\nimport { registerRpc } from './rpc.js'\nimport { HomeHostedService } from './service.js'\nimport { registerInstancesNotice } from './home-hosted/instances-notice.js'\nimport { registerAuthNotice } from './home-hosted/web-notice.js'\nimport { SettingsStore } from './settings.js'\nimport { registerAgentTools } from './tools.js'\nimport { ensureDir } from './util/fsx.js'\nimport { pluginStateDir, resolveHomeHostedHome } from './util/paths.js'\n\nexport const name = 'dsh-home-hosted'\n\nexport { Config }\n\nexport const inject: string[] = []\n\nexport function apply(ctx: Context, config?: ConfigShape): void {\n const resolved: ConfigShape = {\n stateDir: config?.stateDir,\n homeHostedCommand: config?.homeHostedCommand,\n defaultEntryId: config?.defaultEntryId ?? 'dsh',\n instanceRoots: config?.instanceRoots ?? [],\n }\n\n const stateDir = pluginStateDir(resolved.stateDir)\n ensureDir(stateDir, 0o700)\n\n // Per state root, not per machine: a second dsh install must not adopt, edit\n // or autostart the first one's panel. The page reports which root this is.\n const panel = resolveHomeHostedHome(stateDir)\n\n const settings = new SettingsStore(path.join(stateDir, 'settings.json'), resolved.defaultEntryId)\n const service = new HomeHostedService(ctx, {\n home: panel.home,\n panelHomeSource: panel.source,\n stateDir,\n homeHostedCommand: resolved.homeHostedCommand,\n defaultEntryId: resolved.defaultEntryId,\n instanceRoots: resolved.instanceRoots,\n settings,\n })\n\n registerRpc(ctx, service)\n registerAgentTools(ctx, service, settings)\n // Read per assembly, so the setting takes effect without re-registering \u2014\n // and re-measured there, so a panel started while this dsh runs is noticed\n // even when nobody has the settings page open.\n registerInstancesNotice(ctx, {\n instances: () => service.instancesNow(),\n enabled: () => settings.get().instancesNotice,\n })\n // Read per 401, so the setting takes effect without re-installing the wrapper.\n registerAuthNotice(ctx, () => (settings.get().authNotice ? service.panelLogUrl() : null))\n\n // Measure the panel inventory once now: the first model step should already\n // know whether this machine has more than one panel.\n void service.instances().catch(() => {\n // a discovery failure only means an empty inventory\n })\n\n // Re-assert an enabled boot entry shortly after startup: a node or CLI upgrade\n // moves the paths a unit was written with, and the fix is to rewrite it.\n ctx.effect(() => {\n const timer = setTimeout(() => {\n void service.reconcile().catch(() => {\n // the page reads the resulting status; a failed repair is not a startup failure\n })\n }, 5000)\n return () => clearTimeout(timer)\n }, 'dsh-home-hosted: startup reconcile')\n}\n", "/**\n * The Cordis row configuration: operator overrides only. Everything a person\n * changes in the plugin's own page lives in `settings.json` under the plugin\n * state directory, so the two never fight over the same field.\n */\nimport z from '@deepseek-ai/schemastery'\n\nexport interface Config {\n /** Override the plugin state directory (defaults to `$DSH_HOME/dsh-home-hosted`). */\n stateDir?: string\n /** Override the home-hosted executable used in generated boot entries. */\n homeHostedCommand?: string\n /** The entry this plugin manages for the running harness. */\n defaultEntryId: string\n /**\n * Extra home-hosted state roots to report as other panels. Discovery already\n * knows the managed root, `$HHOSTED_HOME` and the `~/.home-hosted*` siblings;\n * a panel run from a project with its own `--home` is declared here.\n */\n instanceRoots: string[]\n}\n\nexport const Config: z<Config> = z.object({\n stateDir: z.string().description('Plugin state directory; defaults to $DSH_HOME/dsh-home-hosted'),\n homeHostedCommand: z.string().description('home-hosted executable to put in a generated autostart entry'),\n defaultEntryId: z.string().default('dsh').description('Server entry id used for the running harness'),\n instanceRoots: z.array(z.string()).default([]).description('Other home-hosted state roots (--home) to report as other panels'),\n})\n", "/**\n * The one wire contract shared by the host half and the browser half.\n *\n * Nothing here may import a Node builtin: the browser bundle includes this\n * module verbatim.\n */\n\n/** Exact Fetch route registered on DeepSeek Harness's authenticated `/api` channel. */\nexport const RPC_PATH = '/home-hosted'\n\n/** Bumped when a payload shape changes; a mismatch is reported, never guessed at. */\nexport const RPC_VERSION = 1\n\nexport interface RpcError {\n code: string\n message: string\n detail?: unknown\n}\n\nexport type Envelope<T> = { ok: true, value: T } | { ok: false, error: RpcError }\n\n/** Endpoints the browser and the agent tools both speak. */\nexport type RpcEndpoint =\n | 'status'\n | 'servers.list'\n | 'workspaces.list'\n | 'panel.migrate'\n | 'servers.get'\n | 'servers.create'\n | 'servers.update'\n | 'servers.delete'\n | 'servers.start'\n | 'servers.stop'\n | 'servers.restart'\n | 'servers.freePort'\n | 'entries.apply'\n | 'entries.remove'\n | 'entries.restore'\n | 'boot.install'\n | 'boot.uninstall'\n | 'boot.verify'\n | 'panel.start'\n | 'panel.stop'\n | 'panel.takeover'\n | 'panel.reclaimToken'\n | 'cli.installGlobal'\n | 'ui.manage'\n | 'settings.update'\n\n/** A settings write sends only the changed subtree; the host merges group by group. */\n/** The workspace every panel has, and the one this plugin manages. */\nexport const DEFAULT_WORKSPACE = 'default'\n\n/** The panel's workspace id shape; the same regex its own schema enforces. */\nexport const WORKSPACE_ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/\n\nexport function isWorkspaceId(value: unknown): value is string {\n return typeof value === 'string' && WORKSPACE_ID_PATTERN.test(value)\n}\n\nexport interface SettingsPatch {\n autostart?: Partial<PluginSettings['autostart']>\n manageDsh?: boolean\n entries?: PluginSettings['entries']\n panel?: Partial<PluginSettings['panel']>\n authNotice?: boolean\n reclaimToken?: boolean\n instancesNotice?: boolean\n uiStyle?: UiStyle\n agentTools?: Partial<PluginSettings['agentTools']>\n cli?: Partial<PluginSettings['cli']>\n}\n\n/**\n * A state root to act on instead of the panel this plugin manages. Only a panel\n * discovery reports is accepted, and only on the endpoints {@link FOREIGN_MECHANISMS}\n * lists.\n */\nexport interface ForeignTarget {\n /** A discovered panel's `--home`; omitted means the panel this plugin manages. */\n home?: string\n /**\n * Which workspace inside that panel to act on; omitted means the plugin's own\n * managed workspace (another panel: its default one). Every workspace the panel\n * serves is reachable, not only the managed one.\n */\n workspace?: string\n /**\n * How to reach it; omitted means the endpoint's first mechanism. Only a value\n * {@link FOREIGN_MECHANISMS} lists for that endpoint is accepted.\n */\n via?: ForeignMechanism\n}\n\n/**\n * How this plugin can act on a panel it does not manage.\n *\n * `file` edits that panel's workspace config file, `cli` runs the home-hosted\n * CLI against its state root, and `api` enrols a token for it and drives its own\n * HTTP API \u2014 the only way to reach some operations, and the only one that gives\n * live status.\n */\nexport type ForeignMechanism = 'file' | 'cli' | 'api'\n\n/**\n * Which ways each endpoint has to reach another panel, least invasive first (the\n * first entry is the default when a caller names no mechanism). A config edit\n * needs no credential, the CLI needs none either, and `api` mints one.\n *\n * Anything absent here is about this machine or about the managed panel itself\n * and is never aimed elsewhere.\n */\nexport const FOREIGN_MECHANISMS: Partial<Record<RpcEndpoint, readonly ForeignMechanism[]>> = {\n 'servers.list': ['file', 'api'],\n 'servers.create': ['file', 'api'],\n 'servers.update': ['file', 'api'],\n 'servers.delete': ['file', 'api'],\n 'servers.start': ['cli', 'api'],\n 'servers.stop': ['cli', 'api'],\n 'servers.restart': ['cli', 'api'],\n 'ui.manage': ['cli'],\n}\n\nexport interface EndpointPayloads {\n /** `refresh` bypasses the short panel-inventory cache; status is live either way. */\n 'status': { refresh?: boolean }\n 'servers.list': ForeignTarget\n 'servers.get': ForeignTarget & { id: string }\n 'servers.create': ForeignTarget & { entry: ServerEntry }\n 'servers.update': ForeignTarget & { id: string, patch: ServerEntryPatch }\n 'servers.delete': ForeignTarget & { id: string }\n 'servers.start': ForeignTarget & { id: string }\n 'servers.stop': ForeignTarget & { id: string }\n 'servers.restart': ForeignTarget & { id: string }\n 'servers.freePort': ForeignTarget & { id: string }\n /** Every workspace a panel serves, with its counts. */\n 'workspaces.list': { home?: string }\n /** Move a pre-0.7 panel root into `.hh` (idempotent; the CLI's own migration). */\n 'panel.migrate': Record<string, never>\n 'entries.apply': { intents: EntryIntent[] }\n /** Stop managing an entry: restore what it was, or remove it when we created it. */\n 'entries.remove': { id: string }\n 'entries.restore': { id: string }\n 'boot.install': { mechanism?: BootMechanism }\n 'boot.uninstall': { mechanism?: BootMechanism }\n 'boot.verify': Record<string, never>\n /** Start the preferred CLI as a detached panel; refused while one already answers. */\n 'panel.start': Record<string, never>\n /**\n * Stop the answering panel through its own CLI. The servers it supervises stop\n * with it, which can include the dsh this plugin is running in.\n */\n 'panel.stop': Record<string, never>\n /** Stop the answering panel and start the preferred copy instead. */\n 'panel.takeover': { force?: boolean }\n /**\n * Mint a fresh panel API token and enrol it, replacing one the panel refuses.\n * Clearing first is what makes it work when home-hosted already holds a hash.\n */\n 'panel.reclaimToken': Record<string, never>\n /** Install the pinned range as a global CLI, so the `global` preference can use it. */\n 'cli.installGlobal': Record<string, never>\n /**\n * Drive the panel's own UI. `file` installs a local zip; `asset` (with optional\n * `repo`/`tag`) installs a release asset through the panel's own `ui-switch`,\n * so its download and version checking are not reimplemented here.\n */\n 'ui.manage': ForeignTarget & {\n action: UiAction\n file?: string\n asset?: string\n repo?: string\n tag?: string\n }\n 'settings.update': { patch: SettingsPatch }\n}\n\nexport interface RpcRequest {\n v: number\n endpoint: RpcEndpoint\n payload?: unknown\n}\n\n// ---------------------------------------------------------------------------\n// Server entries\n// ---------------------------------------------------------------------------\n\nexport type OnPortConflict = 'block' | 'warn' | 'follow' | 'reclaim' | 'kill'\n\n/** Every policy home-hosted's config schema accepts; anything else stops it booting. */\nexport const ON_PORT_CONFLICT_POLICIES: readonly OnPortConflict[] = ['block', 'warn', 'follow', 'reclaim', 'kill']\n\nexport function isOnPortConflict(value: unknown): value is OnPortConflict {\n return typeof value === 'string' && (ON_PORT_CONFLICT_POLICIES as readonly string[]).includes(value)\n}\n\n/** Only the fields this plugin reads or writes are typed; the rest is preserved. */\nexport interface ServerEntry {\n id: string\n label?: string\n enabled?: boolean\n autostart?: boolean\n command?: string\n args?: string[]\n cwd?: string\n env?: Record<string, string>\n dataEnvs?: Record<string, string>\n port?: number | null\n bind?: string\n onPortConflict?: OnPortConflict\n stop?: { killPortHolders?: boolean, [key: string]: unknown }\n health?: Record<string, unknown>\n restart?: Record<string, unknown>\n [key: string]: unknown\n}\n\nexport type ServerEntryPatch = Partial<ServerEntry>\n\n/** What the plugin decides about one entry, and the keys it therefore owns. */\nexport interface EntryIntent {\n id: string\n /**\n * Run under home-hosted's nanny, so stopping or restarting the *panel* leaves\n * this process alive. Needs home-hosted 0.6.3.\n */\n persistent: boolean\n autostart: boolean\n onPortConflict: OnPortConflict\n stopKillPortHolders: boolean\n}\n\n/** Keys an intent owns: a patch touches these and nothing else. */\nexport const OWNED_ENTRY_KEYS = ['autostart', 'onPortConflict', 'persistent', 'stop'] as const\n\nexport interface ServerEntryView {\n id: string\n /** The workspace it lives in: a server id is only unique inside one. */\n workspace: string\n status: string\n pid: number | null\n url: string | null\n config: ServerEntry\n}\n\nexport interface ManagedEntryStatus {\n intent: EntryIntent\n exists: boolean\n /** True once the plugin has adopted the entry and holds a snapshot of what it was. */\n managed: boolean\n /** Owned keys whose live value differs from the intent. */\n drift: string[]\n live: ServerEntryView | null\n snapshot: ServerEntry | null\n}\n\n// ---------------------------------------------------------------------------\n// Panel\n// ---------------------------------------------------------------------------\n\n/**\n * `enrolled` \u2014 the plugin holds a token; `present` \u2014 home-hosted has one this\n * plugin does not hold; `absent` \u2014 none exists yet; `stale` \u2014 the plugin holds\n * one the panel just refused; `unknown` \u2014 the panel was not reachable to ask.\n */\nexport type TokenState = 'enrolled' | 'present' | 'absent' | 'stale' | 'unknown'\n\nexport interface PanelStatus {\n /** `$HHOSTED_HOME` this plugin resolved, whether or not the panel answers. */\n home: string\n reachable: boolean\n /** The port the panel's own config holds (what a restart would use). */\n configPort?: number | null\n url: string | null\n version: string | null\n pid: number | null\n /** How writes are authenticated right now. */\n writeVia: 'api' | 'file' | 'none'\n token: TokenState\n /** True when the panel was asked and accepted the token, so `token` is measured, not assumed. */\n tokenVerified?: boolean\n detail: string\n}\n\n/** Which home-hosted CLI the plugin drives, and where it came from. */\nexport type CliSource = 'config' | 'dependency' | 'path' | 'none'\n\n/** One place a CLI could come from, with the version found there. */\nexport interface CliCandidate {\n source: 'config' | 'dependency' | 'path'\n path: string | null\n version: string | null\n}\n\nexport type UiAction = 'status' | 'update' | 'revert' | 'switch'\n\nexport interface UiResult {\n ok: boolean\n detail: string\n /** The active UI, when the action reported or changed one. */\n ui?: { name: string | null, version: string | null, repo?: string | null, tag?: string | null } | null\n output?: string\n}\n\nexport interface PanelControlResult {\n ok: boolean\n detail: string\n url?: string | null\n version?: string | null\n}\n\nexport interface CliStatus {\n /** `config` (operator override), `dependency` (the pinned copy), `path`, or none. */\n source: CliSource\n path: string | null\n version: string | null\n /** The range the plugin ships in its own dependencies. */\n expectedRange: string\n /** False when the resolved CLI is older than the oldest release this plugin supports. */\n supported: boolean\n /** The stable launcher a boot entry runs instead of a moving node_modules path. */\n launcherPath?: string | null\n /** What the launcher answers right now, as the boot entry would invoke it. */\n launcherVersion?: string | null\n /** Which copy the plugin prefers. */\n prefer?: 'pinned' | 'global'\n /** The plugin's own copy, when it resolves. */\n dependency?: CliCandidate | null\n /** The global install on PATH, when there is one. */\n global?: CliCandidate | null\n detail: string\n}\n\n// ---------------------------------------------------------------------------\n// Panels on this machine\n// ---------------------------------------------------------------------------\n\n/**\n * `home-hosted` is not one panel per machine: a person can install it per\n * project, or run several panels from one install with different `--home`\n * roots. The plugin drives exactly one of them \u2014 the root it resolved.\n */\nexport type InstanceSource = 'managed' | 'env' | 'sibling' | 'configured'\n\n/** One home-hosted panel: its state root, and the facts readable from it. */\nexport interface InstanceView {\n /** The state root itself: what `--home` / `$HHOSTED_HOME` names. */\n home: string\n /**\n * The panel this plugin drives. Every tool call targets it unless the call\n * names another panel, and the plugin never writes to another one.\n */\n managed: boolean\n /** This dsh process runs as one of this panel's entries (`HHOSTED_SERVER_ID`). */\n hosting: boolean\n url: string | null\n port: number | null\n pid: number | null\n version: string | null\n /** A `run.json` whose pid is still alive. */\n running: boolean\n projectDir: string | null\n /** Entries in that panel's default workspace; `null` when it is unreadable. */\n servers: number | null\n /** How discovery found this root. */\n source: InstanceSource\n}\n\n// ---------------------------------------------------------------------------\n// Boot autostart\n// ---------------------------------------------------------------------------\n\nexport type BootMechanism =\n | 'systemd-user'\n | 'systemd-system'\n | 'xdg-autostart'\n | 'launchd-agent'\n | 'launchd-daemon'\n | 'windows-run'\n | 'windows-task'\n | 'container'\n | 'unsupported'\n\nexport type BootState =\n | 'not-installed'\n | 'installed-disabled'\n | 'enabled-running'\n | 'enabled-failing'\n | 'unsupported'\n\nexport interface BootCandidate {\n mechanism: BootMechanism\n available: boolean\n /** Starts before an interactive login. */\n bootCapable: boolean\n /** This process can drive it without an elevation prompt. */\n privileged: boolean\n reason: string\n}\n\nexport interface BootStatus {\n platform: 'linux' | 'darwin' | 'win32' | 'other'\n mechanism: BootMechanism | null\n recommended: BootMechanism | null\n state: BootState\n bootCapable: boolean\n privileged: boolean\n unitPath: string | null\n /** Exact commands a person can run when this process cannot elevate. */\n commands: string[]\n detail: string\n candidates: BootCandidate[]\n}\n\n// ---------------------------------------------------------------------------\n// Plugin settings (UI-managed, persisted outside the Cordis config)\n// ---------------------------------------------------------------------------\n\nexport type AgentToolName =\n | 'status'\n | 'workspaces_list'\n | 'servers_list'\n | 'servers_lifecycle'\n | 'servers_edit'\n | 'autostart_manage'\n | 'ui_manage'\n\nexport const AGENT_TOOL_NAMES: readonly AgentToolName[] = [\n 'status',\n 'workspaces_list',\n 'servers_list',\n 'servers_lifecycle',\n 'servers_edit',\n 'autostart_manage',\n 'ui_manage',\n]\n\n/**\n * Tools that change something. They ask for approval only when the calling\n * session is not already `danger-full-access`.\n */\nexport const MUTATING_AGENT_TOOLS: readonly AgentToolName[] = [\n 'servers_lifecycle',\n 'servers_edit',\n 'autostart_manage',\n 'ui_manage',\n]\n\n/** The shape this release writes; a file without it was written by 0.1.x. */\nexport const SETTINGS_VERSION = 3\n\nexport type UiStyle = 'detailed' | 'compact'\n\nexport interface PluginSettings {\n version: number\n autostart: {\n enabled: boolean\n /** `auto` picks the best available mechanism; an explicit one is honoured only when available. */\n mechanism: 'auto' | BootMechanism\n /** The last install/uninstall attempt, kept so a failure survives a reload. */\n lastAttempt?: BootAttempt\n }\n /** Manage the running harness as a home-hosted entry. */\n manageDsh: boolean\n entries: EntryIntent[]\n agentTools: {\n enabled: boolean\n allow: AgentToolName[]\n }\n /** Port the panel should listen on; `null` keeps whatever the config holds. */\n panel: {\n port: number | null\n }\n /** Point dsh web's sign-in page at the panel log that holds the tokenised URL. */\n authNotice: boolean\n /**\n * When the panel refuses the plugin's API token, re-enrol one on the spot\n * instead of failing the call. On by default: a token that went stale (someone\n * cleared it, or minted their own) should not keep an agent tool from working.\n */\n reclaimToken: boolean\n /**\n * Tell the agent how many home-hosted panels exist on this machine, and ask\n * the user which one to act on when there is more than one. On by default: a\n * plugin that owns one panel must not look like the only one there is.\n */\n instancesNotice: boolean\n /** `detailed` shows cards and open disclosures; `compact` folds both away. */\n uiStyle: UiStyle\n cli: {\n /** `pinned` runs the copy this plugin ships; `global` runs the one on PATH. */\n prefer: 'pinned' | 'global'\n }\n}\n\nexport interface BootAttempt {\n ok: boolean\n /** `install` or `uninstall`. */\n action: 'install' | 'uninstall'\n mechanism: BootMechanism | null\n detail: string\n /** The exact commands a person can run when the attempt needed privilege. */\n commands: string[]\n at: number\n}\n\nexport const DEFAULT_SETTINGS: PluginSettings = {\n version: SETTINGS_VERSION,\n autostart: { enabled: false, mechanism: 'auto' },\n manageDsh: false,\n entries: [],\n // Every tool on by default; the session's own permission mode is what gates them.\n agentTools: { enabled: true, allow: [...AGENT_TOOL_NAMES] },\n panel: { port: null },\n authNotice: true,\n reclaimToken: true,\n instancesNotice: true,\n uiStyle: 'detailed',\n cli: { prefer: 'pinned' },\n}\n\n/** One workspace a panel serves, as the page lists them. */\nexport interface WorkspaceSummary {\n id: string\n label: string\n servers: number\n running: number\n /** `api` counts come from the running panel; `file` counts from its config files. */\n source: 'api' | 'file'\n}\n\nexport interface HomeHostedStatus {\n /** The entry id this plugin manages; the page must not assume \"dsh\". */\n defaultEntryId: string\n /** The workspace this plugin manages. */\n workspace: string\n /** Every workspace the managed panel serves. */\n workspaces: WorkspaceSummary[]\n /**\n * The managed root still uses the pre-0.7 layout. Nothing can be written to it\n * until it is moved, so the page offers the one step that fixes it.\n */\n legacyRoot?: boolean\n panel: PanelStatus\n /** The panel root this plugin instance drives, so a second install is visible. */\n panelRoot?: string\n /** How that root was chosen; `legacy` means an existing `~/.home-hosted` panel was adopted. */\n panelRootSource?: 'env' | 'legacy' | 'instance'\n /** The OS boot artifact name this state root owns. */\n bootUnitName?: string\n /** Every home-hosted panel found on this machine, the managed one first. */\n instances?: InstanceView[]\n boot: BootStatus\n entries: ManagedEntryStatus[]\n servers: ServerEntryView[]\n settings: PluginSettings\n /** The CLI the plugin drives; absent only from a host older than this field. */\n cli?: CliStatus\n /** Set when the panel could not be reached, so the UI can show a degraded page. */\n lastError: string | null\n}\n", "/**\n * The one implementation behind both surfaces: the browser page and the agent\n * tools call the same endpoints, so they cannot drift apart.\n *\n * Writes prefer the panel API (the store ignores its own writes, so nothing is\n * restarted behind the user's back) and fall back to an atomic config-file write\n * only when the panel is not answering.\n */\nimport { Service, type Context } from '@deepseek-ai/cordis'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { fileURLToPath } from 'node:url'\nimport type { BootMechanism, BootStatus, EntryIntent, ForeignMechanism, HomeHostedStatus, InstanceView, ManagedEntryStatus, PanelControlResult, PanelStatus, RpcEndpoint, ServerEntry, ServerEntryPatch, ServerEntryView, UiAction, UiResult, WorkspaceSummary } from './shared/contracts.js'\nimport { FOREIGN_MECHANISMS, isOnPortConflict, isWorkspaceId, ON_PORT_CONFLICT_POLICIES } from './shared/contracts.js'\nimport type { BootActivation, BootSpec } from './boot/types.js'\nimport type { ActivationPlan } from './home-hosted/panel-control.js'\nimport { createBootLadder } from './boot/index.js'\nimport { accountOf } from './boot/index.js'\nimport type { AccountInput, PasswdEntry } from './boot/index.js'\nimport { findEntry, patchEntry, readConfig, readGlobalSettings, removeEntry as removeConfigEntry, setControl, upsertEntry, writeConfig } from './home-hosted/config-file.js'\nimport { DEFAULT_WORKSPACE, defaultWorkspace, isLegacyRoot, readWorkspaces, serversFile } from './home-hosted/layout.js'\nimport type { RawConfig } from './home-hosted/config-file.js'\nimport { buildDshEntry, detectProfile, launcherRepair, needsLauncherRepair, resolveDshLaunch } from './home-hosted/dsh-entry.js'\nimport type { DshLaunch } from './home-hosted/dsh-entry.js'\nimport { canonicalPath, discoverInstances } from './home-hosted/instances.js'\nimport { defaultIntent, isCreatedEntry, ownedDrift, ownedPatch, restorePatch, snapshotOwned } from './home-hosted/entries.js'\nimport { buildHomeHostedBootSpec, homeHostedEnv, LEGACY_BOOT_UNIT_NAME } from './home-hosted/launch.js'\nimport type { CliResolution } from './home-hosted/resolve.js'\nimport { EXPECTED_RANGE, MIN_SUPPORTED_VERSION, resolveCli } from './home-hosted/resolve.js'\nimport { preflightLauncher, writeLauncher } from './home-hosted/launcher.js'\nimport type { PanelControlDeps } from './home-hosted/panel-control.js'\nimport { activationLogPath, installGlobal, spawnActivation, spawnTakeover, startPanel as startPanelProcess, stopPanel as stopPanelProcess } from './home-hosted/panel-control.js'\nimport { PanelClient, PanelError, probeToken, verifyToken } from './home-hosted/panel.js'\nimport { readRuntime, probePanel, pidAlive } from './home-hosted/runtime.js'\nimport { apiTokenEnrolled, ensureToken, readStoredToken, reclaimToken, tokenSlot } from './home-hosted/token.js'\nimport type { SettingsStore } from './settings.js'\nimport { bootUnitName, dshHome } from './util/paths.js'\nimport type { RunResult } from './util/exec.js'\nimport { run } from './util/exec.js'\nimport { readJson, writeJsonAtomic } from './util/fsx.js'\n\nconst ENTRY_ID_PATTERN = /^[a-z0-9][a-z0-9_-]*$/\n\n/** A token probe is an auxiliary fact, not a user action: never let it stall a poll. */\nconst TOKEN_PROBE_TIMEOUT_MS = 3000\n\n/** How often a deleted managed entry may be put back from the status read. */\nconst ENTRY_RECOVERY_INTERVAL_MS = 30_000\n\n/** How long a panel inventory stands before the next read re-scans the disk. */\nconst INSTANCES_CACHE_MS = 10_000\n\n/** This plugin's own package root, used as a search hint for the pinned CLI. */\nfunction pluginRoot(): string | null {\n try {\n return path.dirname(path.dirname(fileURLToPath(import.meta.url)))\n }\n catch {\n return null\n }\n}\n\n/** What a boot install/uninstall answers with; mirrors the boot module's shape. */\nexport interface BootInstallResult {\n ok: boolean\n changed: boolean\n detail: string\n commands: string[]\n needsPrivilege: boolean\n /** Present on install; an uninstall names no mechanism. */\n mechanism?: BootMechanism | null\n status: BootStatus\n}\n\n/** The subset of the boot ladder this service uses. */\nexport interface BootLadderLike {\n status: (spec: BootSpec, mechanism?: BootMechanism) => Promise<BootStatus>\n install: (spec: BootSpec, mechanism?: BootMechanism) => Promise<BootInstallResult>\n uninstall: (spec: BootSpec, mechanism?: BootMechanism) => Promise<BootInstallResult>\n activate: (spec: BootSpec, mechanism?: BootMechanism) => Promise<BootActivation | null>\n}\n\nexport class HomeHostedError extends Error {\n constructor(message: string, readonly code: string) {\n super(message)\n this.name = 'HomeHostedError'\n }\n}\n\n/** An entry another panel's config holds: no live status without that panel's API. */\nfunction isRecordValue(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\nfunction foreignView(entry: ServerEntry, workspace: string): ServerEntryView {\n return { id: entry.id, workspace, status: 'unknown', pid: null, url: null, config: entry }\n}\n\nexport interface HomeHostedServiceOptions {\n home: string\n /** How that root was chosen: an explicit env var, an adopted legacy panel, or this instance's own. */\n panelHomeSource?: 'env' | 'legacy' | 'instance'\n stateDir: string\n homeHostedCommand?: string\n defaultEntryId: string\n /** Other home-hosted state roots to report as panels; discovery finds the rest. */\n instanceRoots?: readonly string[]\n /** Test seam: this process's `$HHOSTED_HOME`; defaults to the environment. */\n envHome?: string | null\n /** Test seam: where `~/.home-hosted*` siblings are looked for; defaults to the user's home. */\n homeDir?: string\n settings: SettingsStore\n /** Test seam: run the home-hosted CLI without spawning it. */\n execCli?: (args: string[], env: Record<string, string | undefined>) => Promise<RunResult>\n /** Test seam: supply the boot ladder instead of probing the real OS. */\n createLadder?: () => BootLadderLike\n /** Test seam: hand the panel to an autostart entry without spawning a helper. */\n spawnActivation?: (stateDir: string, plan: ActivationPlan) => PanelControlResult\n /** The project the managed `dsh` row belongs to; defaults to this process's cwd. */\n projectDir?: string\n /** Test seam: resolve the running harness instead of reading this process. */\n resolveDsh?: (options: { dshHome: string, stateDir: string, projectDir?: string | null }) => Promise<DshLaunch | null>\n /** Test seam: the environment account resolution reads; defaults to this process's. */\n env?: Readonly<Record<string, string | undefined>>\n /** Test seam: this process's uid; defaults to `process.getuid()`. */\n uid?: number | null\n /** Test seam: the user database to resolve the entry's account against. */\n passwd?: readonly PasswdEntry[]\n}\n\ninterface CachedClient {\n client: PanelClient\n until: number\n}\n\ninterface LiveEntry {\n view: ServerEntryView\n config: ServerEntry\n}\n\nexport class HomeHostedService extends Service {\n private clientCache: CachedClient | null = null\n /** The last token this service proved against a panel, so a poll does not re-probe it. */\n private tokenProof: { url: string, token: string, until: number } | null = null\n private tokenDetail = ''\n /** When a missing managed entry was last put back, so a poll cannot become a write loop. */\n private entryRecoveryAt = 0\n /** When a managed row was last re-pointed at the pinned dsh, for the same reason. */\n private commandRepairAt = 0\n /** The last panel inventory, so a model-step assembly never reads the disk. */\n private instanceCache: { at: number, list: InstanceView[] } | null = null\n private readonly snapshotsFile: string\n\n constructor(ctx: Context, private readonly options: HomeHostedServiceOptions) {\n super(ctx, 'homeHosted')\n this.snapshotsFile = path.join(options.stateDir, 'snapshots.json')\n }\n\n private projectDir(): string {\n return this.options.projectDir ?? process.cwd()\n }\n\n /**\n * The workspace this plugin manages. Fixed to the panel's own default: one\n * workspace is what its intent (reconcile, the harness entry, snapshots) is\n * about, and a setting for it bought nothing but a way to write into the wrong\n * one. Every other workspace is still reachable per call.\n */\n private managedWorkspace(): string {\n return DEFAULT_WORKSPACE\n }\n\n private async resolveDsh(dshHome: string, projectDir: string | null = this.projectDir()): Promise<DshLaunch | null> {\n return await (this.options.resolveDsh ?? (options => resolveDshLaunch(options)))({\n dshHome,\n stateDir: this.options.stateDir,\n projectDir,\n })\n }\n\n // -------------------------------------------------------------------------\n // Panels on this machine\n // -------------------------------------------------------------------------\n\n /**\n * The panel inventory, cached for a short while: the page polls status and\n * the prompt provider reads it at every assembly. `refresh` (or an expired\n * cache) re-reads the disk.\n */\n async instances(refresh = false): Promise<InstanceView[]> {\n if (refresh || this.instanceCache === null || Date.now() - this.instanceCache.at >= INSTANCES_CACHE_MS)\n return this.measureInstances()\n return this.instanceCache.list\n }\n\n /**\n * The inventory for a caller that cannot await \u2014 a prompt provider runs\n * synchronously before a model step. Discovery is synchronous too, so this\n * re-measures a stale cache in place and never blocks the step.\n */\n instancesNow(): InstanceView[] {\n if (this.instanceCache !== null && Date.now() - this.instanceCache.at < INSTANCES_CACHE_MS)\n return this.instanceCache.list\n return this.measureInstances()\n }\n\n private measureInstances(): InstanceView[] {\n const now = Date.now()\n try {\n this.instanceCache = {\n at: now,\n list: discoverInstances({\n managedHome: this.options.home,\n extraRoots: this.options.instanceRoots,\n envHome: this.options.envHome === undefined ? process.env.HHOSTED_HOME ?? null : this.options.envHome,\n homeDir: this.options.homeDir,\n hostingEntryId: this.selfEntryId(),\n }),\n }\n }\n catch {\n // An unreadable machine is an empty inventory, never a failed model step.\n this.instanceCache = { at: now, list: [] }\n }\n return this.instanceCache.list\n }\n\n /** The inventory as last measured, without touching the disk. */\n instancesSnapshot(): InstanceView[] {\n return this.instanceCache?.list ?? []\n }\n\n // -------------------------------------------------------------------------\n // Acting on another panel\n // -------------------------------------------------------------------------\n\n /**\n * The state root a call targets: the managed one unless it names another panel\n * discovery reported. A caller-supplied path is never acted on directly, so a\n * stray string cannot become a way to write anywhere on the disk.\n */\n private async targetHome(requested: unknown): Promise<{ home: string, foreign: boolean }> {\n if (requested === undefined || requested === null || requested === '')\n return { home: this.options.home, foreign: false }\n if (typeof requested !== 'string')\n throw new HomeHostedError('a panel target must be a state root path', 'INSTANCE_INVALID')\n if (requested.trim().length === 0)\n return { home: this.options.home, foreign: false }\n const home = canonicalPath(requested)\n if (home === canonicalPath(this.options.home))\n return { home: this.options.home, foreign: false }\n const instances = await this.instances()\n if (!instances.some(instance => instance.home === home && !instance.managed))\n throw new HomeHostedError(`\"${requested}\" is not a home-hosted panel this machine reports`, 'INSTANCE_UNKNOWN')\n return { home, foreign: true }\n }\n\n /**\n * How a call reaches another panel. `via` names it; without one the endpoint's\n * least invasive mechanism is used, which is the order the shared list keeps.\n */\n private mechanismFor(endpoint: RpcEndpoint, requested: unknown): ForeignMechanism {\n const allowed = FOREIGN_MECHANISMS[endpoint]\n if (allowed === undefined)\n throw new HomeHostedError(`\"${endpoint}\" cannot be aimed at another panel`, 'INSTANCE_UNSUPPORTED')\n if (requested === undefined || requested === null)\n return allowed[0] as ForeignMechanism\n if (typeof requested === 'string' && (allowed as readonly string[]).includes(requested))\n return requested as ForeignMechanism\n throw new HomeHostedError(\n `\"${String(requested)}\" is not a way to reach another panel for ${endpoint}; use one of ${allowed.join(', ')}`,\n 'MECHANISM_UNSUPPORTED',\n )\n }\n\n /** Whether a token this plugin already holds for a panel still works. */\n private async foreignTokenWorks(home: string): Promise<boolean> {\n const runtime = readRuntime(home)\n if (runtime === null || runtime.url.length === 0)\n return false\n const stored = readStoredToken(this.options.stateDir, tokenSlot(home))\n return stored !== null && await verifyToken(runtime.url, stored)\n }\n\n /**\n * A client for another panel: the plugin's own credential for that state root,\n * minted and enrolled the first time it is needed, proved against the panel,\n * and replaced when the panel no longer accepts it.\n *\n * home-hosted keeps only a hash, so an existing token this plugin does not hold\n * can only be replaced \u2014 which is what choosing this mechanism means.\n */\n private async foreignClient(home: string): Promise<PanelClient> {\n const runtime = readRuntime(home)\n if (runtime === null || runtime.url.length === 0)\n throw new HomeHostedError(`no panel is running at ${home}, so its API cannot be used`, 'PANEL_UNAVAILABLE')\n const slot = tokenSlot(home)\n const exec = async (args: string[], env: Record<string, string | undefined>) => await this.cliExec(args, env)\n const stored = readStoredToken(this.options.stateDir, slot)\n if (stored !== null && await verifyToken(runtime.url, stored))\n return new PanelClient({ baseUrl: runtime.url, token: stored, workspace: this.managedWorkspace() })\n\n const enrolled = stored !== null || apiTokenEnrolled(home)\n ? await reclaimToken({ home, stateDir: this.options.stateDir, exec, slot })\n : await ensureToken({ home, stateDir: this.options.stateDir, exec, slot })\n if (enrolled.token === null)\n throw new HomeHostedError(enrolled.detail, 'TOKEN_UNAVAILABLE')\n return new PanelClient({ baseUrl: runtime.url, token: enrolled.token, workspace: this.managedWorkspace() })\n }\n\n /**\n * Another panel's own entries. Its managed workspace is not knowable from here,\n * so its default one is read and written.\n */\n private readForeignConfig(home: string, workspace: string = defaultWorkspace(home)): RawConfig {\n const read = readConfig(home, workspace)\n if (read.error !== null || read.raw === null)\n throw new HomeHostedError(read.error ?? `no servers config in ${home}`, 'CONFIG_UNREADABLE')\n return read.raw\n }\n\n /**\n * Every workspace a panel serves, with counts. The running panel answers with\n * live numbers; otherwise the registry plus each workspace's config file is\n * enough to name them and count their entries.\n */\n private async workspaceSummaries(home: string, foreign: boolean): Promise<WorkspaceSummary[]> {\n const registry = readWorkspaces(home)\n const fromFile: WorkspaceSummary[] = registry.workspaces.map(entry => ({\n id: entry.id,\n label: entry.label,\n servers: (readConfig(home, entry.id).raw?.servers ?? []).length,\n running: 0,\n source: 'file',\n }))\n try {\n const client = foreign\n ? (await this.foreignTokenWorks(home) ? await this.foreignClient(home) : null)\n : await this.tryClient()\n if (client !== null) {\n const live = await client.listWorkspaces()\n return live.map(entry => ({ id: entry.id, label: entry.label, servers: entry.serverCount, running: entry.runningCount, source: 'api' as const }))\n }\n }\n catch {\n // The files remain the best available truth.\n }\n return fromFile\n }\n\n /** Another panel's entries as its config holds them: live status needs its API token. */\n private listForeign(home: string, workspace: string = defaultWorkspace(home)): ServerEntryView[] {\n return (this.readForeignConfig(home, workspace).servers ?? []).map(entry => foreignView(entry, workspace))\n }\n\n /**\n * A workspace's entries read from disk, for when no panel client is available.\n * The page shows them with `status: 'unknown'`, which is the truth: nothing is\n * supervising them from this process's point of view.\n */\n private fileServers(home: string, workspace: string): { servers: ServerEntryView[], error: string | null } {\n const read = readConfig(home, workspace)\n return {\n servers: (read.raw?.servers ?? []).map(entry => foreignView(entry, workspace)),\n error: read.error,\n }\n }\n\n private createForeign(home: string, entry: ServerEntry, workspace: string = defaultWorkspace(home)): ServerEntryView {\n const raw = this.readForeignConfig(home, workspace)\n if (findEntry(raw, entry.id) !== null)\n throw new HomeHostedError(`\"${entry.id}\" already exists in ${serversFile(home, workspace)}`, 'DUPLICATE_SERVER')\n // The same two-phase add the managed path uses: that panel's watcher starts a\n // newly added `autostart` entry at once, while a changed definition is only\n // applied at that entry's next start.\n writeConfig(home, upsertEntry(raw, { ...entry, autostart: false }), this.writtenBy(home), workspace)\n const added = this.readForeignConfig(home, workspace)\n writeConfig(home, patchEntry(added, entry.id, { autostart: entry.autostart === true }), this.writtenBy(home), workspace)\n return this.writtenForeignEntry(home, entry.id, workspace)\n }\n\n private updateForeign(home: string, id: string, patch: ServerEntryPatch, workspace: string = defaultWorkspace(home)): ServerEntryView {\n const raw = this.readForeignConfig(home, workspace)\n if (findEntry(raw, id) === null)\n throw new HomeHostedError(`no server \"${id}\" exists in ${serversFile(home, workspace)}`, 'UNKNOWN_SERVER')\n // A patch never renames: the id is the key it was found by.\n const next = patchEntry(raw, id, { ...patch, id })\n writeConfig(home, next, this.writtenBy(home), workspace)\n return this.writtenForeignEntry(home, id, workspace)\n }\n\n /** What a write actually left on disk, never what it was asked to write. */\n private writtenForeignEntry(home: string, id: string, workspace: string = defaultWorkspace(home)): ServerEntryView {\n const entry = findEntry(this.readForeignConfig(home, workspace), id)\n if (entry === null)\n throw new HomeHostedError(`the write to ${serversFile(home, workspace)} did not take`, 'FOREIGN_WRITE_LOST')\n return foreignView(entry, workspace)\n }\n\n private deleteForeign(home: string, id: string, workspace: string = defaultWorkspace(home)): { id: string, home: string, via: 'file' } {\n const raw = this.readForeignConfig(home, workspace)\n if (findEntry(raw, id) === null)\n throw new HomeHostedError(`no server \"${id}\" exists in ${serversFile(home, workspace)}`, 'UNKNOWN_SERVER')\n writeConfig(home, removeConfigEntry(raw, id), this.writtenBy(home), workspace)\n return { id, home, via: 'file' }\n }\n\n /**\n * Start, stop or restart one entry through that panel's own CLI: this plugin\n * holds no API token for a panel it does not manage, and a config edit cannot\n * start anything. The CLI has no server restart, so a restart is a stop\n * followed by a start \u2014 a stop that is not followed by one is reported.\n */\n private async lifecycleForeign(home: string, action: 'start' | 'stop' | 'restart', id: string, workspace: string = defaultWorkspace(home)): Promise<{ home: string, workspace: string, id: string, action: string, via: 'cli' }> {\n const once = async (command: string): Promise<void> => {\n const result = await this.cliExec([command, id, '--workspace', workspace, '--home', home], { ...process.env, HHOSTED_HOME: home })\n if (result.code !== 0) {\n const output = [result.stderr, result.stdout, result.error].filter(part => typeof part === 'string' && part.trim().length > 0).join('\\n').trim()\n throw new HomeHostedError(output.length > 0 ? output : `home-hosted ${command} ${id} exited ${String(result.code)}`, 'CLI_FAILED')\n }\n }\n\n if (action === 'restart') {\n await once('stop')\n await once('start')\n }\n else {\n await once(action)\n }\n return { home, workspace, id, action, via: 'cli' }\n }\n\n // -------------------------------------------------------------------------\n // Facts\n // -------------------------------------------------------------------------\n\n private runtime() {\n return readRuntime(this.options.home)\n }\n\n /** The entry id this very process was started as, when the panel supervises us. */\n selfEntryId(): string | null {\n const id = process.env.HHOSTED_SERVER_ID\n return id !== undefined && id.length > 0 ? id : null\n }\n\n /** What the config's `meta.writtenBy` names: the panel release when we can read it. */\n private writtenBy(home = this.options.home): string {\n return readRuntime(home)?.version ?? 'dsh-home-hosted'\n }\n\n private webServer(): { port: number | null } {\n const service = this.ctx.get('webServer') as { port?: unknown } | undefined\n const port = typeof service?.port === 'number' ? service.port : null\n return { port }\n }\n\n private cliCache: { prefer: 'pinned' | 'global', resolution: CliResolution, launcher: string | null, launcherVersion: string | null, until: number } | null = null\n\n /**\n * Resolve the preferred CLI, refresh the stable launcher a boot entry runs, and\n * preflight that launcher the same way the entry will invoke it.\n */\n private async cli(): Promise<{ resolution: CliResolution, launcher: string | null, launcherVersion: string | null }> {\n const prefer = this.options.settings.get().cli.prefer\n if (this.cliCache !== null && this.cliCache.until > Date.now() && this.cliCache.prefer === prefer)\n return this.cliCache\n const resolution = await resolveCli({ override: this.options.homeHostedCommand, prefer })\n let launcher: string | null = null\n let launcherVersion: string | null = null\n if (resolution.launch !== null) {\n launcher = writeLauncher({\n stateDir: this.options.stateDir,\n dshHome: dshHome(),\n resolvedEntry: resolution.launch.cliEntry ?? resolution.launch.shimPath ?? null,\n resolvedVersion: resolution.status.version,\n pluginRoot: pluginRoot(),\n minVersion: MIN_SUPPORTED_VERSION,\n }).path\n launcherVersion = await preflightLauncher(this.options.stateDir)\n }\n this.cliCache = { prefer, resolution, launcher, launcherVersion, until: Date.now() + 60_000 }\n return this.cliCache\n }\n\n private async panelControlDeps(): Promise<PanelControlDeps> {\n const { resolution } = await this.cli()\n const launch = resolution.launch\n const projectDir = process.env.HHOSTED_PROJECT ?? this.runtime()?.projectDir ?? null\n return {\n launch,\n home: this.options.home,\n projectDir,\n stateDir: this.options.stateDir,\n env: launch === null ? {} : homeHostedEnv({ home: this.options.home, projectDir }, launch),\n exec: this.options.execCli,\n }\n }\n\n private async cliExec(args: string[], env: Record<string, string | undefined>) {\n if (this.options.execCli !== undefined)\n return await this.options.execCli(args, env)\n const { resolution } = await this.cli()\n const launch = resolution.launch\n if (launch === null)\n throw new HomeHostedError(resolution.status.detail, 'CLI_NOT_FOUND')\n return await run(launch.program, [...launch.args, ...args], { env, timeoutMs: 30_000 })\n }\n\n // -------------------------------------------------------------------------\n // Panel\n // -------------------------------------------------------------------------\n\n /** The panel page showing this session's log, where its tokenised URL is. */\n panelLogUrl(): string | null {\n const url = this.runtime()?.url\n if (url === undefined || url.length === 0)\n return null\n // The workspace is part of the URL; the legacy `/logs` path would land on the\n // panel's default workspace, which is not necessarily the managed one.\n return `${url.replace(/\\/+$/, '')}/w/${encodeURIComponent(this.managedWorkspace())}/logs?server=${encodeURIComponent(this.options.defaultEntryId)}`\n }\n\n /**\n * Relocate a pre-0.7 root into `.hh`. `up` does this on its own, but a person\n * looking at a root this plugin cannot write needs the one step that fixes it;\n * `migrate` is the CLI that does exactly that, non-interactively with `--yes`.\n */\n async migrateRoot(): Promise<PanelControlResult> {\n if (!isLegacyRoot(this.options.home))\n return { ok: true, detail: `${this.options.home} is already on the .hh layout` }\n const result = await this.cliExec(['migrate', '--yes', '--home', this.options.home], { ...process.env, HHOSTED_HOME: this.options.home })\n if (result.code !== 0) {\n const output = [result.stderr, result.stdout, result.error].filter(part => typeof part === 'string' && part.trim().length > 0).join('\\n').trim()\n return { ok: false, detail: output.length > 0 ? output : `home-hosted migrate exited ${String(result.code)}` }\n }\n return { ok: true, detail: `${this.options.home} was moved to the .hh layout` }\n }\n\n /** The port the panel's settings hold, without touching a running panel. */\n private configuredPort(): number | null {\n const control = readGlobalSettings(this.options.home).raw?.control as { port?: unknown } | undefined\n return typeof control?.port === 'number' ? control.port : null\n }\n\n /** Write the chosen panel port into the state the panel boots from. */\n private applyPanelPort(): void {\n const port = this.options.settings.get().panel.port\n if (port === null || port === this.configuredPort())\n return\n setControl(this.options.home, { port }, this.writtenBy())\n }\n\n /** Whether this exact token was already proved against this exact panel. */\n private tokenProven(url: string, token: string): boolean {\n return this.tokenProof !== null && this.tokenProof.until > Date.now()\n && this.tokenProof.url === url && this.tokenProof.token === token\n }\n\n private proveToken(url: string, token: string): void {\n this.tokenProof = { url, token, until: Date.now() + 30_000 }\n }\n\n async panelStatus(): Promise<PanelStatus> {\n const runtime = this.runtime()\n const stored = readStoredToken(this.options.stateDir)\n const enrolledOnDisk = apiTokenEnrolled(this.options.home)\n const reachable = runtime !== null && (pidAlive(runtime.pid) || await probePanel(runtime.url))\n const answered = runtime !== null && await probePanel(runtime.url)\n\n // The token state is measured, not assumed: when the panel answers, the\n // plugin's own token is proved against it. A refusal is `stale`; a panel\n // that would not answer the API call leaves the state `unknown` rather than\n // claiming a measurement that was never taken.\n let token: PanelStatus['token'] = 'unknown'\n let tokenVerified = false\n if (answered) {\n if (stored === null) {\n token = enrolledOnDisk ? 'present' : 'absent'\n }\n else if (this.tokenProven(runtime.url, stored)) {\n token = 'enrolled'\n tokenVerified = true\n }\n else {\n const probe = await probeToken(runtime.url, stored, TOKEN_PROBE_TIMEOUT_MS)\n if (probe === 'ok') {\n token = 'enrolled'\n tokenVerified = true\n this.proveToken(runtime.url, stored)\n }\n else if (probe === 'refused') {\n token = 'stale'\n tokenVerified = true\n }\n }\n }\n\n // A token the panel refuses is not a write path, however present it is on disk.\n const writeVia: PanelStatus['writeVia'] = token === 'enrolled' ? 'api' : 'file'\n\n return {\n home: this.options.home,\n reachable: answered,\n configPort: this.configuredPort(),\n url: runtime?.url ?? null,\n version: runtime?.version ?? null,\n pid: runtime?.pid ?? null,\n writeVia,\n token,\n tokenVerified,\n detail: answered\n ? (token === 'enrolled'\n ? (this.tokenDetail || 'the panel is answering and this plugin holds a token')\n : token === 'stale'\n ? 'the panel refused this plugin\\'s API token, so writes go straight to the workspace config file; regenerate the token'\n : token === 'present'\n ? 'the panel is answering, but home-hosted already holds an API token this plugin does not have'\n : token === 'absent'\n ? 'the panel is answering but no API token is enrolled for it yet; writes go to the workspace config file until one is'\n : 'the panel answered, but the plugin\\'s token could not be checked')\n : (runtime === null\n ? 'no .hh/run.json: the panel is not running, so entries are written straight to the workspace config file'\n : token === 'present' || token === 'stale'\n ? 'the panel process is up but does not answer with this plugin\\'s API token, so this page cannot reach it'\n : 'the panel process is alive but is not answering'),\n }\n }\n\n /**\n * Replace the panel's API token with a fresh one and prove it.\n *\n * home-hosted keeps only a hash, so a token this plugin does not hold cannot\n * be recovered: a new token replaces the old hash in one CLI write, and it is\n * proved against the answering panel before being reported.\n *\n * The panel is not required to be *answering*: a missing or refused token is a\n * common reason it cannot be reached at all, and refusing the repair for that\n * reason would leave no way out from the page. Only a panel that was never\n * started is refused \u2014 there is nothing to enrol a token against yet.\n */\n async reclaimPanelToken(): Promise<HomeHostedStatus> {\n const runtime = this.runtime()\n if (runtime === null) {\n throw new HomeHostedError(\n 'the panel has not been started, so there is nothing to enrol a token against; start it first',\n 'PANEL_UNAVAILABLE',\n )\n }\n const answering = await probePanel(runtime.url)\n\n const result = await reclaimToken({\n home: this.options.home,\n stateDir: this.options.stateDir,\n exec: (args, env) => this.cliExec(args, env),\n })\n // Whatever happened, nothing cached was built from the token in play now.\n this.clientCache = null\n this.tokenProof = null\n this.tokenDetail = result.detail\n if (result.token === null)\n throw new HomeHostedError(result.detail, 'TOKEN_RECLAIM_FAILED')\n\n if (!answering) {\n // Enrolled, but there is nothing to hand it to yet. The page says so\n // instead of claiming a verification it could not take.\n this.tokenDetail = `${result.detail}; the panel is not answering, so it could not be proved yet`\n return await this.status()\n }\n\n const probe = await probeToken(runtime.url, result.token, TOKEN_PROBE_TIMEOUT_MS)\n if (probe !== 'ok') {\n throw new HomeHostedError(\n probe === 'refused'\n ? 'a fresh token was enrolled, but the panel refused it; check the panel log before trying again'\n : 'a fresh token was enrolled, but the panel stopped answering before it could be verified',\n 'TOKEN_RECLAIM_UNVERIFIED',\n )\n }\n this.proveToken(runtime.url, result.token)\n return await this.status()\n }\n\n private async tryClient(): Promise<PanelClient | null> {\n if (this.clientCache !== null && this.clientCache.until > Date.now())\n return this.clientCache.client\n const runtime = this.runtime()\n if (runtime === null || !(await probePanel(runtime.url)))\n return null\n\n const ensured = await ensureToken({\n home: this.options.home,\n stateDir: this.options.stateDir,\n exec: (args, env) => this.cliExec(args, env),\n })\n this.tokenDetail = ensured.detail\n if (ensured.token === null)\n return null\n if (!(await verifyToken(runtime.url, ensured.token)))\n return null\n\n const client = new PanelClient({ baseUrl: runtime.url, token: ensured.token, workspace: this.managedWorkspace() })\n this.clientCache = { client, until: Date.now() + 30_000 }\n this.proveToken(runtime.url, ensured.token)\n return client\n }\n\n private async requireClient(): Promise<PanelClient> {\n const client = await this.tryClient()\n if (client === null) {\n const status = await this.panelStatus()\n throw new HomeHostedError(status.detail, 'PANEL_UNAVAILABLE')\n }\n return client\n }\n\n // -------------------------------------------------------------------------\n // Snapshot bookkeeping\n // -------------------------------------------------------------------------\n\n /**\n * What an adopted entry looked like before this plugin touched it, keyed by id\n * inside the workspace those snapshots belong to. A file from another workspace\n * is ignored: ids repeat across workspaces, and restoring the wrong one would\n * rewrite an entry this plugin never adopted.\n */\n private snapshots(): Record<string, ServerEntry> {\n const raw = readJson<Record<string, unknown>>(this.snapshotsFile)\n if (raw === null)\n return {}\n if (raw.version === 2 && isRecordValue(raw.entries))\n return raw.workspace === this.managedWorkspace() ? raw.entries as Record<string, ServerEntry> : {}\n // A file from before workspaces: there was one workspace, and it was `default`.\n return this.managedWorkspace() === DEFAULT_WORKSPACE ? raw as Record<string, ServerEntry> : {}\n }\n\n private saveSnapshots(snapshots: Record<string, ServerEntry>): void {\n writeJsonAtomic(this.snapshotsFile, { version: 2, workspace: this.managedWorkspace(), entries: snapshots }, 0o600)\n }\n\n // -------------------------------------------------------------------------\n // Entries\n // -------------------------------------------------------------------------\n\n private async liveEntries(): Promise<Map<string, LiveEntry>> {\n const map = new Map<string, LiveEntry>()\n const client = await this.tryClient()\n if (client !== null) {\n try {\n for (const view of await client.listServers())\n map.set(view.id, { view, config: view.config })\n return map\n }\n catch {\n // fall through to the file, which is still the best available truth\n }\n }\n const workspace = this.managedWorkspace()\n const read = readConfig(this.options.home, workspace)\n for (const entry of read.raw?.servers ?? [])\n map.set(entry.id, { view: foreignView(entry, workspace), config: entry })\n return map\n }\n\n private async createEntry(intent: EntryIntent, patch: ServerEntryPatch): Promise<ServerEntry | null> {\n if (intent.id !== this.options.defaultEntryId)\n return null\n const harnessHome = process.env.DSH_HOME ?? dshHome()\n // State dir and harness home are the plugin's own, not the defaults: an\n // override has to point the launcher at the same place everything else is.\n const dsh = await this.resolveDsh(harnessHome)\n const { port } = this.webServer()\n const generated = buildDshEntry({\n id: intent.id,\n port,\n profile: detectProfile(process.argv, process.env),\n dshHome: harnessHome,\n launch: dsh,\n launcherPath: dsh?.launcherPath ?? null,\n projectDir: this.projectDir(),\n })\n return { ...generated, ...patch }\n }\n\n /**\n * An entry written before the launcher existed still runs a clone's build\n * output directly. Point it at the stable launcher, or the fix would only\n * ever apply to freshly created entries \u2014 the plugin never rewrites an\n * existing entry's command.\n */\n private async dshCommandRepair(live: ServerEntry | null): Promise<{ command: string, args: string[] } | null> {\n if (live === null || !needsLauncherRepair(live))\n return null\n const harnessHome = process.env.DSH_HOME ?? dshHome()\n const dsh = await this.resolveDsh(harnessHome)\n // A row this plugin created carries `{ id }` as its snapshot; one it merely\n // adopted keeps the person's own command unless that command is a bare name.\n const owned = isCreatedEntry(this.snapshots()[live.id])\n return launcherRepair(live, dsh, { ownedCommand: owned, projectDir: this.projectDir() })\n }\n\n private async writeOwned(intent: EntryIntent): Promise<void> {\n const live = (await this.liveEntries()).get(intent.id) ?? null\n const snapshots = this.snapshots()\n if (snapshots[intent.id] === undefined) {\n snapshots[intent.id] = live === null ? { id: intent.id } : snapshotOwned(live.config)\n this.saveSnapshots(snapshots)\n }\n\n const patch = ownedPatch(intent, live?.config ?? null)\n const repair = intent.id === this.options.defaultEntryId ? await this.dshCommandRepair(live?.config ?? null) : null\n const client = await this.tryClient()\n if (client !== null) {\n if (live !== null) {\n await client.updateServer(intent.id, { ...patch, ...(repair ?? {}) })\n return\n }\n const entry = await this.createEntry(intent, patch)\n if (entry === null) {\n throw new HomeHostedError(\n `no server \"${intent.id}\" exists; create it first, then this plugin can adopt its autostart and conflict policy`,\n 'ENTRY_MISSING',\n )\n }\n await client.createServer(entry)\n return\n }\n\n const workspace = this.managedWorkspace()\n const read = readConfig(this.options.home, workspace)\n if (read.error !== null)\n throw new HomeHostedError(read.error, 'CONFIG_UNREADABLE')\n let raw = read.raw ?? {}\n if (findEntry(raw, intent.id) !== null) {\n writeConfig(this.options.home, patchEntry(raw, intent.id, { ...patch, ...(repair ?? {}) }), this.writtenBy(), workspace)\n return\n }\n\n const entry = await this.createEntry(intent, patch)\n if (entry === null)\n throw new HomeHostedError(`no server \"${intent.id}\" exists in ${serversFile(this.options.home, workspace)}`, 'ENTRY_MISSING')\n\n // While a panel is running, an external write is a real change and a newly\n // added autostart entry would be started at once. Adding it disabled and\n // flipping autostart in a second write makes that a changed definition\n // instead, which only takes effect at that entry's next start.\n if (await this.panelRunning()) {\n writeConfig(this.options.home, upsertEntry(raw, { ...entry, autostart: false }), this.writtenBy(), workspace)\n raw = readConfig(this.options.home, workspace).raw ?? raw\n writeConfig(this.options.home, patchEntry(raw, intent.id, { autostart: intent.autostart }), this.writtenBy(), workspace)\n return\n }\n\n writeConfig(this.options.home, upsertEntry(raw, entry), this.writtenBy(), workspace)\n }\n\n private async panelRunning(): Promise<boolean> {\n const runtime = this.runtime()\n return runtime !== null && (pidAlive(runtime.pid) || await probePanel(runtime.url))\n }\n\n private async applyIntents(intents: EntryIntent[]): Promise<ManagedEntryStatus[]> {\n for (const raw of intents) {\n if (!ENTRY_ID_PATTERN.test(raw.id))\n throw new HomeHostedError(`\"${raw.id}\" is not a valid server id`, 'INVALID_ID')\n const requested: unknown = raw.onPortConflict\n if (requested !== undefined && requested !== null && !isOnPortConflict(requested))\n throw new HomeHostedError(\n `\"${String(requested)}\" is not a port-conflict policy; use one of ${ON_PORT_CONFLICT_POLICIES.join(', ')}`,\n 'INVALID_POLICY',\n )\n const fallback = defaultIntent(raw.id)\n const intent: EntryIntent = {\n id: raw.id,\n autostart: raw.autostart === true,\n onPortConflict: isOnPortConflict(requested) ? requested : fallback.onPortConflict,\n stopKillPortHolders: raw.stopKillPortHolders ?? fallback.stopKillPortHolders,\n persistent: raw.persistent ?? fallback.persistent,\n }\n await this.writeOwned(intent)\n const current = this.options.settings.get()\n const entries = current.entries.filter(entry => entry.id !== intent.id)\n entries.push(intent)\n this.options.settings.update({\n // The page's toggle reads this flag, so managing the harness has to record\n // itself; restoring or removing that entry clears it again. A paused intent\n // (`autostart: false`) is not management, or the toggle could never go off.\n ...(intent.id === this.options.defaultEntryId ? { manageDsh: intent.autostart } : {}),\n entries,\n })\n }\n return await this.entriesStatus()\n }\n\n /**\n * Stop managing an entry: restore what it was when we only adopted it, or\n * remove it when this plugin created it. Removing an entry the panel\n * supervises stops that process \u2014 which may be this session.\n */\n private async removeManagedEntry(id: string): Promise<ManagedEntryStatus[]> {\n const snapshots = this.snapshots()\n const snapshot = snapshots[id]\n const adopted = snapshot !== undefined && Object.keys(snapshot).some(key => key !== 'id')\n if (adopted)\n return await this.restoreEntry(id)\n\n // Deleting this entry stops the process running this session, and nothing\n // would start it again \u2014 so \"stop managing\" pauses it instead: the entry\n // stays, it is simply no longer started for us.\n if (id === this.selfEntryId())\n return await this.pauseManagedEntry(id)\n\n const client = await this.tryClient()\n if (client !== null) {\n try {\n await client.deleteServer(id)\n }\n catch (error) {\n // Already gone is the outcome we wanted.\n const missing = error instanceof PanelError && (error.status === 404 || error.code === 'UNKNOWN_SERVER')\n if (!missing)\n throw error\n }\n }\n else {\n const read = readConfig(this.options.home, this.managedWorkspace())\n if (read.error !== null || read.raw === null)\n throw new HomeHostedError(read.error ?? 'the config file is unreadable', 'CONFIG_UNREADABLE')\n writeConfig(this.options.home, removeConfigEntry(read.raw, id), this.writtenBy(), this.managedWorkspace())\n }\n delete snapshots[id]\n this.saveSnapshots(snapshots)\n const current = this.options.settings.get()\n this.options.settings.update({\n // Only the harness entry drives the page's manage toggle.\n ...(id === this.options.defaultEntryId ? { manageDsh: false } : {}),\n entries: current.entries.filter(entry => entry.id !== id),\n })\n return await this.entriesStatus()\n }\n\n /** Turn off the autostart intent for one entry, keeping the entry itself. */\n private async pauseManagedEntry(id: string): Promise<ManagedEntryStatus[]> {\n const intent = this.options.settings.intentFor(id)\n await this.applyIntents([{ ...intent, autostart: false }])\n const current = this.options.settings.get()\n this.options.settings.update({\n ...(id === this.options.defaultEntryId ? { manageDsh: false } : {}),\n entries: current.entries,\n })\n return await this.entriesStatus()\n }\n\n /**\n * Drive the panel's own UI through its CLI, and report what is installed.\n *\n * `ui-switch --asset` is preferred over a local zip for an official UI: the\n * panel's own CLI picks the release for its version, matches the asset name\n * and downloads it, so nothing here has to reimplement a GitHub lookup or\n * guess which release belongs to the running panel.\n */\n async uiManage(action: UiAction, options: { file?: string, asset?: string, repo?: string, tag?: string } = {}, target?: string): Promise<UiResult> {\n const home = target ?? this.options.home\n const cli = action === 'status' ? null : await this.cli()\n\n const runUi = async (args: string[]): Promise<{ code: number | null, output: string }> => {\n const launch = cli?.resolution.launch ?? null\n if (launch === null)\n throw new HomeHostedError(cli?.resolution.status.detail ?? 'no home-hosted CLI could be resolved', 'CLI_NOT_FOUND')\n const full = [...launch.args, ...args, '--home', home]\n const env = { ...process.env, HHOSTED_HOME: home }\n const result = this.options.execCli !== undefined\n ? await this.options.execCli(full, env)\n : await run(launch.program, full, { env, timeoutMs: 300_000 })\n // A CLI that never started has no stdout to show, so its spawn error is the\n // only thing that explains the failure.\n const output = [result.error, result.stdout, result.stderr].filter(part => typeof part === 'string' && part.trim().length > 0).join('\\n').trim()\n return { code: result.code, output }\n }\n\n interface ActiveUi { name: string | null, version: string | null, repo: string | null, tag: string | null }\n const active = (): ActiveUi | null => {\n const meta = readJson<{ name?: string, version?: string | null, repo?: string, tag?: string }>(path.join(home, '.ui', 'ui.json'))\n return meta === null ? null : { name: meta.name ?? null, version: meta.version ?? null, repo: meta.repo ?? null, tag: meta.tag ?? null }\n }\n\n const releaseFlags = (): string[] => [\n ...(options.repo === undefined ? [] : ['--repo', options.repo]),\n ...(options.tag === undefined ? [] : ['--tag', options.tag]),\n ]\n\n switch (action) {\n case 'status': {\n const ui = active()\n return { ok: true, ui, detail: ui === null ? 'the panel is using its stock UI' : `${ui.name ?? 'a custom UI'}${ui.version === null ? '' : ` ${ui.version}`}` }\n }\n case 'update': {\n const result = await runUi(['ui-update', '--yes', ...releaseFlags()])\n return { ok: result.code === 0, detail: result.code === 0 ? 'the panel UI is up to date' : 'the update did not run', ui: active(), output: result.output }\n }\n case 'revert': {\n const result = await runUi(['ui-revert'])\n return { ok: result.code === 0, detail: result.code === 0 ? 'back to the stock UI' : 'the revert did not run', ui: active(), output: result.output }\n }\n case 'switch': {\n const file = options.file?.trim()\n const asset = options.asset?.trim()\n if (file === undefined || file.length === 0) {\n if (asset === undefined || asset.length === 0)\n throw new HomeHostedError('switching the UI needs an asset name or a local zip', 'UI_SOURCE_REQUIRED')\n const result = await runUi(['ui-switch', '--asset', asset, '--yes', ...releaseFlags()])\n return { ok: result.code === 0, detail: result.code === 0 ? `installed the ${asset} UI` : 'the switch did not run', ui: active(), output: result.output }\n }\n if (!fs.existsSync(file))\n throw new HomeHostedError(`no file at ${file}`, 'UI_FILE_MISSING')\n const result = await runUi(['ui-switch', '--file', file, '--yes'])\n return { ok: result.code === 0, detail: result.code === 0 ? 'the UI was replaced' : 'the switch did not run', ui: active(), output: result.output }\n }\n default:\n throw new HomeHostedError(`unknown ui action \"${String(action)}\"`, 'UNKNOWN_UI_ACTION')\n }\n }\n\n private async restoreEntry(id: string): Promise<ManagedEntryStatus[]> {\n const live = (await this.liveEntries()).get(id) ?? null\n if (live === null)\n throw new HomeHostedError(`no server \"${id}\" exists`, 'ENTRY_MISSING')\n const snapshots = this.snapshots()\n // A restore without a snapshot would write schema defaults over the person's\n // own autostart and conflict policy, silently and irreversibly.\n if (snapshots[id] === undefined)\n throw new HomeHostedError(`\"${id}\" was never adopted by this plugin, so there is nothing to restore; adopt it first`, 'NOT_ADOPTED')\n const patch = restorePatch(live.config, snapshots[id] ?? null)\n const client = await this.tryClient()\n if (client !== null)\n await client.updateServer(id, patch)\n else {\n const read = readConfig(this.options.home, this.managedWorkspace())\n if (read.error !== null || read.raw === null)\n throw new HomeHostedError(read.error ?? 'the config file is unreadable', 'CONFIG_UNREADABLE')\n writeConfig(this.options.home, patchEntry(read.raw, id, patch), this.writtenBy(), this.managedWorkspace())\n }\n delete snapshots[id]\n this.saveSnapshots(snapshots)\n const current = this.options.settings.get()\n this.options.settings.update({\n ...(id === this.options.defaultEntryId ? { manageDsh: false } : {}),\n entries: current.entries.filter(entry => entry.id !== id),\n })\n return await this.entriesStatus()\n }\n\n async entriesStatus(): Promise<ManagedEntryStatus[]> {\n const settings = this.options.settings.get()\n const live = await this.liveEntries()\n const snapshots = this.snapshots()\n const ids = new Set<string>([this.options.defaultEntryId, ...settings.entries.map(entry => entry.id)])\n return [...ids].map((id) => {\n const intent = this.options.settings.intentFor(id)\n const entry = live.get(id) ?? null\n return {\n intent,\n exists: entry !== null,\n managed: snapshots[id] !== undefined,\n drift: entry === null ? ['missing entry'] : ownedDrift(entry.config, intent),\n live: entry?.view ?? null,\n snapshot: snapshots[id] ?? null,\n }\n })\n }\n\n // -------------------------------------------------------------------------\n // Boot autostart\n // -------------------------------------------------------------------------\n\n /**\n * Everything account resolution reads.\n *\n * The state paths matter: they name the person the panel belongs to, so a root\n * process launched at boot (a system unit that names no `User=`) can still be\n * told whose panel it is running.\n */\n /** This process's environment, from the test seam when one is supplied. */\n private processEnv(): Readonly<Record<string, string | undefined>> {\n return this.options.env ?? process.env\n }\n\n private accountFacts(): Omit<AccountInput, 'warn'> {\n return {\n env: this.processEnv() as Record<string, string | undefined>,\n uid: this.options.uid,\n ownerPaths: [this.options.home, this.options.stateDir],\n passwd: this.options.passwd,\n }\n }\n\n /**\n * The ladder resolves the account per provider, and the spec below resolves it\n * for the entry's environment. Both are handed the *same* facts, so the `User=`\n * line and the `HOME=` line can never describe two different people.\n */\n private ladder(): BootLadderLike {\n if (this.options.createLadder !== undefined)\n return this.options.createLadder()\n return createBootLadder(this.accountFacts())\n }\n\n private async bootSpec(): Promise<BootSpec | null> {\n const { resolution, launcher } = await this.cli()\n const launch = resolution.launch\n if (launch === null)\n return null\n const runtime = this.runtime()\n // The account a system entry hands the panel to, resolved from the login that\n // elevated (or this process's own), never from `$USER`: under `sudo` the\n // environment says *root*, which is how a system unit came to run the panel as\n // root. A root answer travels as `root: true` so providers refuse to write it.\n const account = accountOf(this.accountFacts())\n const spec = buildHomeHostedBootSpec(\n {\n home: this.options.home,\n projectDir: process.env.HHOSTED_PROJECT ?? runtime?.projectDir ?? null,\n version: runtime?.version ?? null,\n },\n launch,\n {\n stateDir: this.options.stateDir,\n unitName: bootUnitName(this.options.stateDir),\n // `undefined` when no account was resolved: the entry then runs as this\n // process (or as root), and this process's own HOME is the right one. A\n // spec that merely repeats this environment stays byte-identical, so\n // reconcile does not rewrite the entry on every start.\n accountHome: account === null ? undefined : account.home,\n env: this.processEnv(),\n },\n )\n\n // The entry runs the stable launcher, not the pinned node_modules path: that\n // path carries a version and a peer hash, and moves on the next install.\n if (launcher !== null) {\n const cliArgs = spec.args.slice(launch.args.length)\n if (process.platform === 'win32') {\n spec.command = process.execPath\n spec.args = [launcher, ...cliArgs]\n }\n else {\n spec.command = launcher\n spec.args = cliArgs\n }\n }\n return spec\n }\n\n async bootStatus(mechanism?: BootMechanism): Promise<BootStatus> {\n const platform: BootStatus['platform'] = process.platform === 'linux'\n ? 'linux'\n : process.platform === 'darwin' ? 'darwin' : process.platform === 'win32' ? 'win32' : 'other'\n const spec = await this.bootSpec()\n if (spec === null) {\n return {\n platform,\n mechanism: null,\n recommended: null,\n state: 'unsupported',\n bootCapable: false,\n privileged: false,\n unitPath: null,\n commands: [],\n detail: 'no home-hosted CLI is available, so no boot entry can be generated; install the plugin with its dependencies',\n candidates: [],\n }\n }\n const settings = this.options.settings.get()\n const requested = mechanism ?? (settings.autostart.mechanism === 'auto' ? undefined : settings.autostart.mechanism)\n return await this.ladder().status(spec, requested)\n }\n\n async installBoot(mechanism?: BootMechanism): Promise<{ result: BootInstallResult, status: BootStatus }> {\n const spec = await this.bootSpec()\n if (spec === null)\n throw new HomeHostedError((await this.cli()).resolution.status.detail, 'CLI_NOT_FOUND')\n\n // Read what the panel is doing *before* the install: this decides whether the\n // entry has to be started, and whether that start must stop anything first.\n const panel = await this.panelStatus()\n const before = await this.bootStatus(mechanism)\n const result = await this.ladder().install(spec, mechanism)\n if (result.ok)\n await this.retireLegacyBootEntry(spec, mechanism)\n const installed = mechanism ?? result.mechanism ?? null\n const handover = result.ok\n ? await this.bootHandoverPlan(spec, installed ?? undefined, panel, before.mechanism)\n : { detail: null, commands: [], stateDir: null, plan: null, display: [] }\n\n const detail = [result.detail, handover.detail].filter((line): line is string => line !== null).join('; ')\n const commands = [...result.commands, ...handover.commands]\n const current = this.options.settings.get().autostart\n this.options.settings.update({\n autostart: {\n // The preference follows the outcome for a real install, and the last\n // attempt is recorded either way so a failure is visible after a reload.\n enabled: result.ok ? true : current.enabled,\n mechanism: result.ok\n ? (mechanism ?? result.mechanism ?? current.mechanism)\n : current.mechanism,\n lastAttempt: {\n ok: result.ok,\n action: 'install',\n mechanism: result.mechanism ?? mechanism ?? null,\n detail,\n commands,\n at: Date.now(),\n },\n },\n })\n\n // Only now, with the intent already on disk: the helper stops the panel, and\n // this process may not survive to write anything after that.\n if (handover.plan !== null && handover.stateDir !== null) {\n const spawned = (this.options.spawnActivation ?? spawnActivation)(handover.stateDir, handover.plan)\n this.clientCache = null\n this.tokenProof = null\n return {\n result: {\n ...result,\n detail: `${detail}; ${spawned.detail}`,\n // A helper that never started leaves the panel up, so a person needs the\n // exact steps to do it by hand.\n commands: spawned.ok ? commands : [...commands, ...handover.display],\n },\n status: await this.bootStatus(mechanism),\n }\n }\n\n return {\n result: { ...result, detail, commands },\n status: await this.bootStatus(mechanism),\n }\n }\n\n /**\n * The hand-over this install needs, if any: the plan for a detached helper, or\n * just the note explaining why the panel is being left alone.\n *\n * An install alone proves nothing: the panel the plugin drove keeps running, so\n * nothing shows whether the entry would ever start one. Starting it is the only\n * way to know. Nothing is spawned here \u2014 the caller persists the intent first,\n * because the helper stops the panel and this process may not outlive it.\n *\n * When the mechanism cannot start anything (a `Run` value, a `.desktop` file the\n * session reads at login), nothing is stopped: leaving a working panel up beats\n * killing it for an entry that would not bring it back.\n */\n private async bootHandoverPlan(\n spec: BootSpec,\n mechanism: BootMechanism | undefined,\n panel: PanelStatus,\n previousMechanism: BootMechanism | null,\n ): Promise<{ detail: string | null, commands: string[], stateDir: string | null, plan: ActivationPlan | null, display: string[] }> {\n const activation = await this.ladder().activate(spec, mechanism)\n if (activation === null) {\n return {\n detail: panel.reachable\n ? `${mechanism ?? 'this mechanism'} cannot start the panel itself, so it was left running rather than stopped with nothing to bring it back`\n : null,\n commands: [],\n stateDir: null,\n plan: null,\n display: [],\n }\n }\n\n if (!panel.reachable) {\n return {\n detail: 'no panel is answering, so the entry was not started now; it starts the panel at boot or login',\n commands: [],\n stateDir: null,\n plan: null,\n display: [],\n }\n }\n // A panel already running under this mechanism is where this is trying to\n // get to; stopping it would only interrupt a working panel.\n if (previousMechanism !== null && previousMechanism === mechanism) {\n return {\n detail: `the panel is already running under ${mechanism}, so it was left alone`,\n commands: [],\n stateDir: null,\n plan: null,\n display: [],\n }\n }\n\n const deps = await this.panelControlDeps()\n if (deps.launch === null)\n throw new HomeHostedError((await this.cli()).resolution.status.detail, 'CLI_NOT_FOUND')\n\n const retire = activation.retired.length > 0 ? `; retiring ${activation.retired.join(', ')}` : ''\n return {\n detail: `handing the panel to ${mechanism ?? 'the entry'} now; this page disconnects and comes back when it answers${retire}`,\n commands: [],\n stateDir: deps.stateDir,\n display: [...activation.display, ...activation.retireDisplay],\n plan: {\n commands: activation.commands,\n requires: activation.requires.commands,\n files: activation.requires.files,\n // The CLI's own `down`, never the mechanism's `restart`: home-hosted\n // refuses to start a second panel while `run.json` names a live pid, so\n // the old one has to be gone first or the entry just fails while the\n // orphan keeps the port.\n stop: [...deps.launch.args, 'down', '--home', deps.home],\n detach: activation.stop,\n oldPid: panel.pid,\n retire: activation.retire,\n logPath: activationLogPath(deps.stateDir),\n },\n }\n }\n\n async uninstallBoot(mechanism?: BootMechanism): Promise<{ result: BootInstallResult, status: BootStatus }> {\n const spec = await this.bootSpec()\n if (spec === null)\n throw new HomeHostedError((await this.cli()).resolution.status.detail, 'CLI_NOT_FOUND')\n const result = await this.ladder().uninstall(spec, mechanism)\n if (result.ok)\n await this.retireLegacyBootEntry(spec, mechanism)\n const current = this.options.settings.get().autostart\n this.options.settings.update({\n autostart: {\n ...current,\n enabled: result.ok ? false : current.enabled,\n lastAttempt: {\n ok: result.ok,\n action: 'uninstall',\n mechanism: result.mechanism ?? mechanism ?? null,\n detail: result.detail,\n commands: result.commands,\n at: Date.now(),\n },\n },\n })\n return { result, status: await this.bootStatus(mechanism) }\n }\n\n /**\n * Remove the pre-per-instance artifact once this state root has its own.\n *\n * An install that used to write `home-hosted` now writes `home-hosted-<hash>`,\n * and a machine left with both would autostart the panel twice \u2014 or keep\n * starting it from an entry the page no longer reports. Only an artifact\n * carrying this plugin's marker is touched, so somebody else's unit survives.\n */\n private async retireLegacyBootEntry(spec: BootSpec, mechanism?: BootMechanism): Promise<void> {\n if (spec.unitName === LEGACY_BOOT_UNIT_NAME)\n return\n const legacy = { ...spec, unitName: LEGACY_BOOT_UNIT_NAME }\n try {\n const status = await this.ladder().status(legacy, mechanism)\n if (status.unitPath === null || status.state === 'not-installed' || status.state === 'unsupported')\n return\n if (!fs.existsSync(status.unitPath))\n return\n await this.ladder().uninstall(legacy, status.mechanism ?? mechanism)\n }\n catch {\n // A legacy entry we cannot retire is reported by the boot status the page\n // reads; failing the install over it would be worse.\n }\n }\n\n // -------------------------------------------------------------------------\n // Panel lifecycle\n // -------------------------------------------------------------------------\n\n /** Out-of-the-box start: run the preferred CLI's `up`, which detaches itself. */\n async startPanelNow(): Promise<PanelControlResult> {\n const { resolution } = await this.cli()\n const deps = await this.panelControlDeps()\n if (deps.launch === null)\n throw new HomeHostedError(resolution.status.detail, 'CLI_NOT_FOUND')\n const runtime = this.runtime()\n if (runtime !== null && await probePanel(runtime.url)) {\n return { ok: true, detail: 'a panel is already answering', url: runtime.url, version: runtime.version }\n }\n this.applyPanelPort()\n const result = await startPanelProcess(deps)\n this.clientCache = null\n return result\n }\n\n /**\n * Stop the panel the plugin drives, and every server it supervises with it.\n *\n * Deliberately not a replacement for the takeover guard: stopping is safe to\n * lose (an `autostart` entry comes back), while replacing is not. If this\n * process is one of those servers the answer may never arrive, so the page\n * treats a dropped call as \"the panel is stopping\" rather than a failure.\n */\n async stopPanelNow(): Promise<PanelControlResult> {\n const { resolution } = await this.cli()\n const runtime = this.runtime()\n if (runtime === null || !await probePanel(runtime.url)) {\n return { ok: true, detail: 'no panel is answering, so there was nothing to stop' }\n }\n const deps = await this.panelControlDeps()\n if (deps.launch === null)\n throw new HomeHostedError(resolution.status.detail, 'CLI_NOT_FOUND')\n const result = await stopPanelProcess(deps)\n this.clientCache = null\n this.tokenProof = null\n return result\n }\n\n /**\n * Replace an answering panel with the preferred copy.\n *\n * That stops the servers the old panel supervises \u2014 this process included \u2014 so\n * the work is handed to a detached helper and the guard demands that this\n * session is an adopted, autostarting entry the new panel will bring back.\n */\n async takeoverPanel(force = false): Promise<PanelControlResult> {\n const { resolution } = await this.cli()\n const deps = await this.panelControlDeps()\n if (deps.launch === null)\n throw new HomeHostedError(resolution.status.detail, 'CLI_NOT_FOUND')\n\n const runtime = this.runtime()\n if (runtime === null || !(await probePanel(runtime.url)))\n return await this.startPanelNow()\n\n if (runtime.version !== null && resolution.status.version !== null && runtime.version === resolution.status.version) {\n return { ok: true, detail: `the answering panel is already ${runtime.version}`, url: runtime.url, version: runtime.version }\n }\n\n if (!force) {\n const id = this.selfEntryId()\n const live = id === null ? null : (await this.liveEntries()).get(id) ?? null\n if (id === null || live === null || live.config.autostart !== true) {\n throw new HomeHostedError(\n 'replacing the panel stops every server it supervises, including this session, and nothing would start it again: '\n + 'adopt this entry with autostart first, or pass force',\n 'TAKEOVER_UNSAFE',\n )\n }\n }\n\n return spawnTakeover(deps, runtime.pid)\n }\n\n /** Install the pinned range globally, so the `global` preference has a copy to run. */\n async installGlobalCli(): Promise<PanelControlResult & { output?: string }> {\n const result = await installGlobal(EXPECTED_RANGE)\n this.cliCache = null\n return { ok: result.ok, detail: result.detail, output: result.output }\n }\n\n /** Re-assert an entry that is already installed: a node or CLI upgrade moves the\n * paths a unit was written with, and the fix is to rewrite it.\n *\n * Never installs one that is not there. Installing is a deliberate act \u2014 it can\n * need root and it makes this machine start something at boot \u2014 so it happens on\n * an explicit click or an approved tool call, not as a side effect of startup.\n */\n async reconcile(): Promise<void> {\n // A managed entry that someone deleted (by mistake, from the panel or by hand)\n // stays \"managed\" in settings with nothing to manage, which is the state the\n // page reports as a missing entry. Recreate it \u2014 the toggle is the intent.\n await this.ensureManagedEntry()\n await this.repairManagedCommand()\n await this.repairBootEntry()\n await this.migrateBootEntryName()\n }\n\n /**\n * An upgrade from a release that named every artifact `home-hosted` leaves\n * that one behind while this state root now writes its own name. Once our own\n * artifact is in place, the old one is retired \u2014 otherwise the machine\n * autostarts the panel twice, or from an entry the page no longer reports.\n */\n private async migrateBootEntryName(): Promise<void> {\n if (!this.options.settings.get().autostart.enabled)\n return\n const spec = await this.bootSpec()\n if (spec === null || spec.unitName === LEGACY_BOOT_UNIT_NAME)\n return\n try {\n const own = await this.bootStatus()\n if (own.state !== 'enabled-running' && own.state !== 'enabled-failing' && own.state !== 'installed-disabled')\n return\n await this.retireLegacyBootEntry(spec, own.mechanism ?? undefined)\n }\n catch {\n // reported by the boot status the page reads\n }\n }\n\n /**\n * Put back a managed entry that no longer exists, while its intent still wants\n * autostart. A paused intent is a deliberate stop and is left alone.\n *\n * Called from startup `reconcile()` as well as the status read, because a person\n * who deletes the entry from the panel is looking at the page right then \u2014 not\n * at the next plugin start. A failed attempt is not repeated for a while, so a\n * config that cannot be written does not turn every poll into a write.\n */\n private async ensureManagedEntry(): Promise<void> {\n const { settings } = this.options\n const id = this.options.defaultEntryId\n if (!settings.get().manageDsh || !settings.intentFor(id).autostart)\n return\n // Claim the attempt before the first await, so two status calls racing each\n // other cannot both decide to create the same entry.\n const now = Date.now()\n if (now - this.entryRecoveryAt < ENTRY_RECOVERY_INTERVAL_MS)\n return\n this.entryRecoveryAt = now\n if ((await this.entriesStatus()).every(entry => entry.intent.id !== id || entry.exists))\n return\n try {\n await this.writeOwned({ ...settings.intentFor(id), autostart: true })\n }\n catch {\n // an unwritable config is reported by the status the page reads\n }\n }\n\n /**\n * Re-point a managed `dsh` row that runs the wrong dsh.\n *\n * A row created before the launcher existed, or by an older release that took\n * whatever PATH answered, keeps starting that copy \u2014 and no click would fix\n * it, because the plugin only writes the row when the toggle is applied. So\n * the drift is repaired where the entry's existence is already checked, under\n * the same throttle, and only for a row this plugin owns.\n */\n private async repairManagedCommand(): Promise<void> {\n const id = this.options.defaultEntryId\n if (!this.options.settings.get().manageDsh || !this.options.settings.intentFor(id).autostart)\n return\n const now = Date.now()\n // Its own throttle: the entry-recovery one is claimed by every status read,\n // so sharing it would mean this repair never gets a turn.\n if (now - this.commandRepairAt < ENTRY_RECOVERY_INTERVAL_MS)\n return\n const live = (await this.liveEntries()).get(id)?.config ?? null\n if (live === null || this.snapshots()[id] === undefined || !needsLauncherRepair(live))\n return\n const repair = await this.dshCommandRepair(live)\n if (repair === null)\n return\n this.commandRepairAt = now\n try {\n await this.writeOwned({ ...this.options.settings.intentFor(id), autostart: true })\n }\n catch {\n // reported by the status the page reads\n }\n }\n\n /** Re-assert the boot entry when the OS still has it but it stopped working. */\n private async repairBootEntry(): Promise<void> {\n if (!this.options.settings.get().autostart.enabled)\n return\n const status = await this.bootStatus()\n if (status.state !== 'enabled-failing' && status.state !== 'installed-disabled')\n return\n // Belt on top of the probe: a file-backed entry is only repaired when the file\n // is actually there, so a reporting slip can never turn startup into an install.\n const fileBacked = status.mechanism === 'systemd-user'\n || status.mechanism === 'systemd-system'\n || status.mechanism === 'xdg-autostart'\n || status.mechanism === 'launchd-agent'\n || status.mechanism === 'launchd-daemon'\n if (fileBacked && (status.unitPath === null || !fs.existsSync(status.unitPath)))\n return\n // `enabled-failing` is exactly what a system unit that runs the panel as root\n // looks like once a server refuses to run as root, so this repair now rewrites\n // it with the account resolved above. A *working* entry is deliberately not\n // touched: re-installing stops the panel and hands it to the entry, which is a\n // person's decision, not a startup side effect \u2014 and the status detail names\n // the wrong account so the page's install button is an informed choice.\n try {\n await this.installBoot()\n }\n catch {\n // a failed repair is reported by the status the page reads\n }\n }\n\n // -------------------------------------------------------------------------\n // Status\n // -------------------------------------------------------------------------\n\n async status(refresh = false): Promise<HomeHostedStatus> {\n // Resolve the client first: enrolling a token changes which write path is\n // available, and the page should see the state after that, not before.\n const client = await this.tryClient()\n const panel = await this.panelStatus()\n const { resolution, launcher, launcherVersion } = await this.cli()\n const cliDetail = resolution.status.detail\n const panelVersion = this.runtime()?.version ?? null\n const cli = {\n ...resolution.status,\n launcherPath: launcher,\n launcherVersion,\n // A panel left running from an older install is the usual reason to see\n // two versions on this page; say so instead of leaving it ambiguous.\n detail: panelVersion !== null && resolution.status.version !== null && panelVersion !== resolution.status.version\n ? `${cliDetail}; the running panel is ${panelVersion}`\n : cliDetail,\n }\n let servers: ServerEntryView[] = []\n let lastError: string | null = null\n if (client !== null) {\n try {\n servers = await client.listServers(this.managedWorkspace())\n }\n catch (error) {\n lastError = error instanceof Error ? error.message : String(error)\n }\n }\n else {\n if (panel.reachable)\n lastError = panel.detail\n // No client: the workspace's own file is still the best available truth,\n // and it is what the `source: 'file'` counts are read from.\n const read = this.fileServers(this.options.home, this.managedWorkspace())\n servers = read.servers\n if (read.error !== null)\n lastError ??= read.error\n }\n\n return {\n defaultEntryId: this.options.defaultEntryId,\n workspace: this.managedWorkspace(),\n workspaces: await this.workspaceSummaries(this.options.home, false).catch(() => []),\n legacyRoot: isLegacyRoot(this.options.home),\n panel,\n panelRoot: this.options.home,\n panelRootSource: this.options.panelHomeSource ?? 'instance',\n bootUnitName: bootUnitName(this.options.stateDir),\n instances: await this.instances(refresh),\n boot: await this.bootStatus(),\n // A deleted managed entry is put back here as well as at startup: whoever\n // deleted it is looking at the page that reports it missing.\n entries: await this.ensureManagedEntry()\n .then(async () => await this.repairManagedCommand())\n .then(async () => await this.entriesStatus()),\n servers,\n settings: this.options.settings.get(),\n cli,\n lastError,\n }\n }\n\n // -------------------------------------------------------------------------\n // Endpoint dispatch\n // -------------------------------------------------------------------------\n\n /**\n * Which workspace an endpoint call acts on. A caller may name any workspace the\n * panel serves; omitted means the one this plugin manages, which for another\n * panel is its default.\n */\n private callWorkspace(input: Record<string, unknown>, home: string, foreign: boolean): string {\n const named = typeof input.workspace === 'string' ? input.workspace.trim() : ''\n if (named.length > 0) {\n if (!isWorkspaceId(named))\n throw new HomeHostedError(`\"${named}\" is not a workspace id (expected ^[a-z0-9][a-z0-9_-]*$)`, 'INVALID_WORKSPACE')\n return named\n }\n return foreign ? defaultWorkspace(home) : this.managedWorkspace()\n }\n\n async call(endpoint: RpcEndpoint, payload: unknown): Promise<unknown> {\n const input = (payload ?? {}) as Record<string, unknown>\n // A target names a panel; an endpoint with no way to reach another one must\n // refuse it rather than quietly act somewhere else.\n if (typeof input.home === 'string' && input.home.trim().length > 0 && FOREIGN_MECHANISMS[endpoint] === undefined) {\n throw new HomeHostedError(\n `\"${endpoint}\" is about the panel this plugin manages and cannot be aimed at another one`,\n 'INSTANCE_UNSUPPORTED',\n )\n }\n switch (endpoint) {\n case 'status':\n return await this.status(input.refresh === true)\n\n case 'settings.update': {\n const patch = (input.patch ?? {}) as Parameters<SettingsStore['update']>[0]\n this.options.settings.update(patch)\n return await this.status()\n }\n\n case 'panel.migrate':\n return await this.migrateRoot()\n\n case 'workspaces.list': {\n const target = await this.targetHome(input.home)\n return await this.workspaceSummaries(target.home, target.foreign)\n }\n\n case 'servers.list': {\n const target = await this.targetHome(input.home)\n const workspace = this.callWorkspace(input, target.home, target.foreign)\n if (!target.foreign) {\n // A panel that is not answering is still a readable config: the page's\n // degraded view must not turn into an empty list.\n const client = await this.tryClient()\n return client === null ? this.fileServers(target.home, workspace).servers : await client.listServers(workspace)\n }\n const via = this.mechanismFor('servers.list', input.via)\n // A read never mints a credential: it uses one this plugin already holds\n // for that panel, and otherwise reads the config file it can always read.\n if (via === 'api' || (input.via === undefined && await this.foreignTokenWorks(target.home)))\n return await (await this.foreignClient(target.home)).listServers(workspace)\n return this.listForeign(target.home, workspace)\n }\n\n case 'servers.get':\n return await (await this.requireClient()).getServer(String(input.id), this.callWorkspace(input, this.options.home, false))\n\n case 'servers.create': {\n const entry = input.entry as ServerEntry\n if (typeof entry?.id !== 'string' || !ENTRY_ID_PATTERN.test(entry.id))\n throw new HomeHostedError('a server entry needs an id matching ^[a-z0-9][a-z0-9_-]*$', 'INVALID_ID')\n const target = await this.targetHome(input.home)\n const workspace = this.callWorkspace(input, target.home, target.foreign)\n if (!target.foreign)\n return await (await this.requireClient()).createServer(entry, workspace)\n return this.mechanismFor('servers.create', input.via) === 'api'\n ? await (await this.foreignClient(target.home)).createServer(entry, workspace)\n : this.createForeign(target.home, entry, workspace)\n }\n\n case 'servers.update': {\n const target = await this.targetHome(input.home)\n const id = String(input.id)\n const patch = (input.patch ?? {}) as ServerEntryPatch\n const workspace = this.callWorkspace(input, target.home, target.foreign)\n if (!target.foreign)\n return await (await this.requireClient()).updateServer(id, patch, workspace)\n return this.mechanismFor('servers.update', input.via) === 'api'\n ? await (await this.foreignClient(target.home)).updateServer(id, patch, workspace)\n : this.updateForeign(target.home, id, patch, workspace)\n }\n\n case 'servers.delete': {\n const id = String(input.id)\n const target = await this.targetHome(input.home)\n const workspace = this.callWorkspace(input, target.home, target.foreign)\n if (target.foreign) {\n return this.mechanismFor('servers.delete', input.via) === 'api'\n ? await (await this.foreignClient(target.home)).deleteServer(id, workspace).then(() => ({ id, workspace, home: target.home, via: 'api' as const }))\n : this.deleteForeign(target.home, id, workspace)\n }\n if (id === this.selfEntryId()) {\n throw new HomeHostedError(\n `\"${id}\" is the entry this very process runs as, and deleting it stops this session; `\n + 'pause it (autostart off) or restore it instead',\n 'SELF_ENTRY',\n )\n }\n await (await this.requireClient()).deleteServer(id, workspace)\n return { id, workspace }\n }\n\n case 'servers.start': {\n const target = await this.targetHome(input.home)\n const id = String(input.id)\n const workspace = this.callWorkspace(input, target.home, target.foreign)\n if (target.foreign) {\n if (this.mechanismFor('servers.start', input.via) === 'api') {\n await (await this.foreignClient(target.home)).startServer(id, workspace)\n return { home: target.home, workspace, id, action: 'start', via: 'api' }\n }\n return await this.lifecycleForeign(target.home, 'start', id, workspace)\n }\n await (await this.requireClient()).startServer(id, workspace)\n return await this.entriesStatus()\n }\n\n case 'servers.stop': {\n const target = await this.targetHome(input.home)\n const id = String(input.id)\n const workspace = this.callWorkspace(input, target.home, target.foreign)\n if (target.foreign) {\n if (this.mechanismFor('servers.stop', input.via) === 'api') {\n await (await this.foreignClient(target.home)).stopServer(id, workspace)\n return { home: target.home, workspace, id, action: 'stop', via: 'api' }\n }\n return await this.lifecycleForeign(target.home, 'stop', id, workspace)\n }\n await (await this.requireClient()).stopServer(id, workspace)\n return await this.entriesStatus()\n }\n\n case 'servers.restart': {\n const target = await this.targetHome(input.home)\n const id = String(input.id)\n const workspace = this.callWorkspace(input, target.home, target.foreign)\n if (target.foreign) {\n if (this.mechanismFor('servers.restart', input.via) === 'api') {\n await (await this.foreignClient(target.home)).restartServer(id, workspace)\n return { home: target.home, workspace, id, action: 'restart', via: 'api' }\n }\n return await this.lifecycleForeign(target.home, 'restart', id, workspace)\n }\n await (await this.requireClient()).restartServer(id, workspace)\n return await this.entriesStatus()\n }\n\n case 'servers.freePort':\n return await (await this.requireClient()).freePort(String(input.id), this.callWorkspace(input, this.options.home, false))\n\n case 'entries.apply': {\n const intents = Array.isArray(input.intents) ? input.intents as EntryIntent[] : []\n return await this.applyIntents(intents)\n }\n\n case 'entries.remove':\n return await this.removeManagedEntry(String(input.id))\n\n case 'entries.restore':\n return await this.restoreEntry(String(input.id))\n\n case 'ui.manage': {\n const target = await this.targetHome(input.home)\n if (target.foreign)\n this.mechanismFor('ui.manage', input.via)\n return await this.uiManage(\n input.action as UiAction,\n {\n file: typeof input.file === 'string' ? input.file : undefined,\n asset: typeof input.asset === 'string' ? input.asset : undefined,\n repo: typeof input.repo === 'string' ? input.repo : undefined,\n tag: typeof input.tag === 'string' ? input.tag : undefined,\n },\n target.foreign ? target.home : undefined,\n )\n }\n\n case 'boot.install':\n return await this.installBoot(input.mechanism as BootMechanism | undefined)\n\n case 'boot.uninstall':\n return await this.uninstallBoot(input.mechanism as BootMechanism | undefined)\n\n case 'boot.verify':\n return await this.bootStatus()\n\n case 'panel.start':\n return await this.startPanelNow()\n\n case 'panel.stop':\n return await this.stopPanelNow()\n\n case 'panel.takeover':\n return await this.takeoverPanel(input.force === true)\n\n case 'panel.reclaimToken':\n return await this.reclaimPanelToken()\n\n case 'cli.installGlobal':\n return await this.installGlobalCli()\n\n default:\n throw new HomeHostedError(`unknown endpoint \"${String(endpoint)}\"`, 'UNKNOWN_ENDPOINT')\n }\n }\n}\n\ndeclare module '@deepseek-ai/cordis' {\n interface Context {\n homeHosted: HomeHostedService\n }\n}\n", "/**\n * Shared pieces of the boot ladder: where each mechanism's artifact lives, how\n * ownership of an existing artifact is proven before it is touched, and how a\n * probe's answers collapse into one `BootState`. No process is started here.\n */\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { readText, writeFileAtomic } from '../util/fsx.js'\nimport type { BootState } from '../shared/contracts.js'\nimport type { BootAccount, BootActionResult, BootProviderContext, PasswdEntry } from './types.js'\n\nexport function errorMessage(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\n/** Existence check through the injected seam, so a probe is testable without touching the host. */\nexport function existsOf(ctx: BootProviderContext, file: string): boolean {\n return ctx.exists ? ctx.exists(file) : fs.existsSync(file)\n}\n\nexport function posixJoin(...parts: string[]): string {\n return path.posix.join(...parts)\n}\n\n/** `$XDG_CONFIG_HOME`, else `~/.config`. */\nexport function configHome(ctx: BootProviderContext): string {\n const xdg = ctx.env.XDG_CONFIG_HOME?.trim()\n return xdg ? path.posix.resolve(xdg) : posixJoin(ctx.home, '.config')\n}\n\n/** `%LOCALAPPDATA%`, else `~\\AppData\\Local`. */\nexport function localAppData(ctx: BootProviderContext): string {\n const value = ctx.env.LOCALAPPDATA?.trim()\n return value ? value : path.join(ctx.home, 'AppData', 'Local')\n}\n\n/** A writable scratch directory, used to stage a unit before `sudo install`. */\nexport function tempDir(ctx: BootProviderContext): string {\n const value = ctx.env.TMPDIR?.trim() || ctx.env.TEMP?.trim() || ctx.env.TMP?.trim()\n return value || os.tmpdir()\n}\n\n// ---------------------------------------------------------------------------\n// The invoking account\n// ---------------------------------------------------------------------------\n\n/**\n * Parse a passwd-format file. Exported so a test can read a fixture: the only\n * alternative is asserting against whatever accounts the machine running the\n * suite happens to have, which is exactly the host dependency this module exists\n * to remove from unit generation.\n */\nexport function parsePasswd(text: string): PasswdEntry[] {\n return text\n .split('\\n')\n .map((line) => {\n const fields = line.split(':')\n const uid = Number.parseInt(fields[2] ?? '', 10)\n return fields.length < 6 || !Number.isFinite(uid) || (fields[0] ?? '').length === 0\n ? null\n : { name: fields[0] ?? '', uid, home: fields[5] ?? '' }\n })\n .filter((entry): entry is PasswdEntry => entry !== null)\n}\n\n/**\n * Read the user database: a file read, never a subprocess.\n *\n * An empty result is a normal outcome, not a failure \u2014 macOS keeps local accounts\n * in Open Directory and its `/etc/passwd` holds system accounts only. Nothing here\n * depends on the lookup succeeding: a session name is used as itself, and the\n * database only supplies the home and confirms a uid.\n */\nfunction readPasswd(): PasswdEntry[] {\n try {\n return parsePasswd(fs.readFileSync('/etc/passwd', 'utf8'))\n }\n catch {\n return []\n }\n}\n\n/** A numeric env value, or null when it is absent or not a plain number. */\nfunction numberFrom(value: string | undefined): number | null {\n const text = value?.trim()\n if (text === undefined || text.length === 0)\n return null\n return /^\\d+$/.test(text) ? Number.parseInt(text, 10) : null\n}\n\n/**\n * The home a row gives, falling back to the ambient one only when the row has none.\n *\n * They agree in the normal case. When they do not, the row is the one that is\n * actually that login's home \u2014 and the ambient one is the value that arrives as\n * root's under `sudo`.\n */\nfunction accountFrom(row: PasswdEntry, ambientHome?: string): BootAccount {\n const home = row.home.length > 0 ? row.home : (ambientHome?.trim() || null)\n return { name: row.name, uid: row.uid, home, root: row.uid === 0, verified: true }\n}\n\n/** `SUDO_USER`/`PKEXEC_USER`: what an elevating tool says it elevated. */\nfunction declaredName(env: Record<string, string | undefined>): string | null {\n const value = env.SUDO_USER?.trim() || env.PKEXEC_USER?.trim()\n return value !== undefined && value.length > 0 ? value : null\n}\n\n/** `LOGNAME`, else `USER`: this process's own login, when nothing elevated it. */\nfunction sessionName(env: Record<string, string | undefined>): string | null {\n const value = env.LOGNAME?.trim() || env.USER?.trim() || env.USERNAME?.trim()\n return value !== undefined && value.length > 0 ? value : null\n}\n\n/** The owner of the first of `paths` that exists and is not root, as an account. */\nfunction ownerAccount(paths: readonly string[] | undefined, passwd: readonly PasswdEntry[]): BootAccount | null {\n for (const path of paths ?? []) {\n try {\n const uid = fs.statSync(path).uid\n if (uid === 0)\n continue\n const row = passwd.find(entry => entry.uid === uid)\n if (row !== undefined)\n return accountFrom(row)\n }\n catch {\n // an unreadable path is just not an answer\n }\n }\n return null\n}\n\n/** What resolving an account needs; {@link BootProviderContext} satisfies it. */\nexport interface AccountInput {\n env: Record<string, string | undefined>\n /** This process's own uid; defaults to `process.getuid()`. */\n uid?: number | null\n /** Paths whose owner names the person a root process's panel belongs to. */\n ownerPaths?: readonly string[]\n /** The user database to resolve against; defaults to the real one. */\n passwd?: readonly PasswdEntry[]\n /** Where a refusal or a root-account fallback is said out loud. */\n warn?: (message: string) => void\n}\n\n/**\n * The account a boot entry must run as.\n *\n * `$USER` is right in the common case and wrong in exactly the case this exists\n * for: `sudo` and `pkexec` rewrite `USER`, `LOGNAME` and `HOME` to the *target*\n * account, so a panel started from a root shell or a root unit reads\n * `USER=root` \u2014 and so does `os.userInfo()`, which reports the euid.\n *\n * Two rules keep the answer usable, and both are about what systemd does with it:\n *\n * 1. **Prefer the database row for the uid over the name in the environment.**\n * They agree in the normal case. When they do not, the row is the one that\n * names a real login, and `User=<a name systemd cannot resolve>` is worse than\n * no entry at all: systemd refuses to start it (`status=217/USER`) and\n * `Restart=always` turns that into a crash-loop, which is exactly the\n * unexplained failure this whole change exists to remove.\n * 2. **A uid this process has is the fact; `$UID` is a claim.** `$UID` is a shell\n * variable rather than an exported one, so when it is present at all it was\n * inherited, and it feeds the launchd domain (`gui/$UID`) as well as the owner\n * lookups. It is consulted only when there is no uid to be had from the\n * process itself.\n *\n * The order is therefore:\n *\n * 1. an elevating tool's `SUDO_UID`/`PKEXEC_UID` (an instruction, and the only\n * place the invoking login survives) \u2014 but only while it resolves, so a stale\n * value inherited from an ancestor cannot collapse the entry to root;\n * 2. this session's own login: its database row, else the row for this uid, else\n * the name itself for a login the local database does not hold (macOS keeps\n * accounts in Open Directory and its `/etc/passwd` has no user rows);\n * 3. the row for this uid;\n * 4. the owner of a state path \u2014 who the panel actually belongs to;\n * 5. and only then, honestly, root.\n *\n * A name that cannot be placed is reported with `verified: false`, so the writer\n * can warn instead of quietly installing a unit that will not start.\n */\nexport function accountOf(input: AccountInput): BootAccount | null {\n const passwd = input.passwd ?? readPasswd()\n const byUid = (uid: number | null): PasswdEntry | null => uid === null ? null : passwd.find(entry => entry.uid === uid) ?? null\n const byName = (name: string | null): PasswdEntry | null => name === null ? null : passwd.find(entry => entry.name === name) ?? null\n\n const elevatedUid = numberFrom(input.env.SUDO_UID) ?? numberFrom(input.env.PKEXEC_UID)\n const elevatedName = declaredName(input.env)\n if (elevatedUid !== null || elevatedName !== null) {\n const row = byUid(elevatedUid) ?? byName(elevatedName)\n if (row !== null)\n return accountFrom(row)\n // An elevating tool that named *both* a login and a nonzero uid has made an\n // explicit, consistent statement about an account the local database may not\n // hold \u2014 an LDAP or NIS login is a real account even though `/etc/passwd` has\n // no row for it. `$HOME` has already been rewritten to the target's, so it is\n // not this account's home and carrying `null` stops the entry inheriting it.\n if (elevatedUid !== null && elevatedUid !== 0 && elevatedName !== null)\n return { name: elevatedName, uid: elevatedUid, home: null, root: false, verified: false }\n // Anything less is not a statement about an account. Fall through rather than\n // let a stale `SUDO_UID` override a login this process can still name.\n }\n\n // This process's own uid. `$UID` is a claim and the process is the fact, so it\n // is only consulted when the platform gives no uid at all.\n const euid = input.uid === undefined ? (process.getuid?.() ?? null) : input.uid\n const ownUid = euid ?? numberFrom(input.env.UID)\n\n const session = sessionName(input.env)\n // systemd matches names case-sensitively, so `ROOT` is not root to it \u2014 but it is\n // also not a login, and writing it produces a 217/USER refusal.\n if (session !== null && session.toLowerCase() !== 'root') {\n const sessionRow = byName(session)\n if (sessionRow !== null)\n return accountFrom(sessionRow, input.env.HOME)\n // The name has no row. The uid's row is the better answer: it names a real\n // login, and a typo'd `$USER` would otherwise make `User=<typo>`.\n const uidRow = byUid(ownUid === 0 ? null : ownUid)\n if (uidRow !== null)\n return accountFrom(uidRow, input.env.HOME)\n return { name: session, uid: ownUid, home: input.env.HOME?.trim() || null, root: false, verified: false }\n }\n\n // The root row is deliberately not consulted here: `uid 0` always has one, so\n // accepting it would short-circuit the state-owner fallback below \u2014 which is the\n // only answer a root process has for \"whose panel is this?\". Root is the *last*\n // resort, after the panel's own files have been asked.\n const ownRow = ownUid === 0 ? null : byUid(ownUid)\n if (ownRow !== null)\n return accountFrom(ownRow)\n\n const owner = ownerAccount(input.ownerPaths, passwd)\n if (owner !== null)\n return owner\n\n // Nothing named a person. A root process's panel runs as root whether or not a\n // unit says so, so report that and let every writer refuse to dress it up.\n if (ownUid === 0 || session?.toLowerCase() === 'root')\n return { name: 'root', uid: 0, home: input.env.HOME?.trim() || '/root', root: true, verified: true }\n return null\n}\n\n/**\n * The account a *login-scoped* mechanism belongs to.\n *\n * The systemd user manager and the desktop session are the invoking user's, so\n * the environment that started this process is the right answer here \u2014 unlike a\n * system unit, which outlives that environment entirely. A root account is never\n * offered: it is not a login this plugin can put an entry in.\n */\nexport function currentUser(ctx: AccountInput): string | null {\n const account = accountOf(ctx)\n if (account !== null && !account.root)\n return account.name\n const value = sessionName(ctx.env)\n return value !== null && value.toLowerCase() !== 'root' ? value : null\n}\n\n/**\n * A name a **system** entry may safely carry, or null.\n *\n * `User=root` is never a choice a panel made: root is the silent default of a root\n * process, and a unit that names it looks deliberate while it is the bug. So a\n * root account is refused here, loudly, and the caller omits the directive.\n *\n * A name that cannot be written is refused the same way. `User=`/`UserName` are\n * unquoted, so a name carrying a newline would end the field and let the rest of it\n * become another directive \u2014 and the environment this reads is one a caller can\n * set. No name is better than an injected one.\n */\nexport function bootUserName(ctx: AccountInput): string | null {\n const account = accountOf(ctx)\n if (account === null) {\n // No account is the same runtime outcome as root \u2014 the entry runs as root \u2014\n // so it is refused just as loudly. The candidate reason says it too, but a\n // warning belongs at the point the decision is actually made.\n warnAccount(ctx, 'no account could be resolved for the entry, so a system unit would run the panel as root. Install it from the account that should own the panel, or set User= yourself.')\n return null\n }\n if (account.root) {\n warnAccount(ctx, 'the only account available is root; a system unit would run the panel as root. Install it from the account that should own the panel, or set User= yourself.')\n return null\n }\n if (!isUserName(account.name)) {\n warnAccount(ctx, `the account name ${JSON.stringify(account.name)} is not a usable user name, so no User= line was written`)\n return null\n }\n if (!account.verified) {\n // The name is writable but nothing here can place it: an LDAP or NIS login has\n // no `/etc/passwd` row, and a stale `$USER` names nobody at all. systemd cannot\n // resolve it either, and `User=<a name it cannot resolve>` makes the unit fail\n // with `status=217/USER` \u2014 which `Restart=always` turns into a crash-loop that\n // never mentions the account. Installing that is worse than installing nothing,\n // so the name is refused and the reason is said out loud; the page's install\n // button remains for anyone who knows the name is resolvable on that machine.\n warnAccount(ctx, `the account ${JSON.stringify(account.name)} could not be found in the user database, so no User= line was written: systemd would refuse to start the unit (status=217/USER). Set User= yourself if that account resolves through LDAP or NIS.`)\n return null\n }\n return account.name\n}\n\n/** The one definition of a writable account name; {@link assertUserName} enforces it in a payload. */\nfunction isUserName(name: string): boolean {\n return /^[A-Za-z_][A-Za-z0-9._-]*$/.test(name)\n}\n\nfunction warnAccount(ctx: AccountInput, message: string): void {\n try {\n (ctx.warn ?? ((line: string) => console.warn(`[dsh-home-hosted] ${line}`)))(message)\n }\n catch {\n // a diagnostic is never worth failing a boot entry over\n }\n}\n\n/** The numeric uid this process runs as, as text, for a launchd domain. */\nexport function uidOf(ctx: BootProviderContext): string | null {\n const configured = numberFrom(ctx.env.UID)\n if (configured !== null)\n return String(configured)\n const uid = ctx.uid === undefined ? (process.getuid?.() ?? null) : ctx.uid\n return uid === null ? null : String(uid)\n}\n\n// ---------------------------------------------------------------------------\n// Ownership\n// ---------------------------------------------------------------------------\n\nexport interface FileOwnership {\n exists: boolean\n owned: boolean\n text: string | null\n reason: string | null\n}\n\n/**\n * An artifact belongs to this plugin only when it carries the marker. A file\n * we cannot read is treated as foreign: we must never overwrite or delete a\n * unit somebody else wrote.\n */\nexport function inspectOwned(file: string, marker: string): FileOwnership {\n if (!fs.existsSync(file))\n return { exists: false, owned: false, text: null, reason: null }\n const text = readText(file)\n if (text === null)\n return { exists: true, owned: false, text: null, reason: `${file} exists but is unreadable, so it cannot be proven to be ours` }\n if (!text.includes(marker))\n return { exists: true, owned: false, text, reason: `${file} exists and does not carry this plugin's marker (${marker}); refusing to touch an artifact we did not write` }\n return { exists: true, owned: true, text, reason: null }\n}\n\n/** Write our artifact if the bytes differ; never overwrite a foreign one. */\nexport function writeOwned(file: string, marker: string, content: string, mode = 0o644): { changed: boolean, refusal: string | null } {\n const own = inspectOwned(file, marker)\n if (own.exists && !own.owned)\n return { changed: false, refusal: own.reason }\n if (own.exists && own.text === content)\n return { changed: false, refusal: null }\n writeFileAtomic(file, content, mode)\n return { changed: true, refusal: null }\n}\n\n/** Delete our artifact only; a missing file is already the desired state. */\nexport function removeOwned(file: string, marker: string): { removed: boolean, refusal: string | null } {\n const own = inspectOwned(file, marker)\n if (!own.exists)\n return { removed: false, refusal: null }\n if (!own.owned)\n return { removed: false, refusal: own.reason }\n try {\n fs.rmSync(file, { force: true })\n return { removed: true, refusal: null }\n }\n catch (error) {\n return { removed: false, refusal: `could not delete ${file}: ${errorMessage(error)}` }\n }\n}\n\n// ---------------------------------------------------------------------------\n// Results\n// ---------------------------------------------------------------------------\n\nexport function failed(detail: string, extra: Partial<BootActionResult> = {}): BootActionResult {\n return { ok: false, changed: false, detail, commands: [], needsPrivilege: false, ...extra }\n}\n\n/** Installed + enabled + last run failed is `enabled-failing`; never a guessed success. */\nexport function bootState(installed: boolean, enabled: boolean, failing: boolean): BootState {\n if (!installed)\n return 'not-installed'\n if (!enabled)\n return 'installed-disabled'\n return failing ? 'enabled-failing' : 'enabled-running'\n}\n\nexport function isInstalledState(state: BootState): boolean {\n return state === 'installed-disabled' || state === 'enabled-running' || state === 'enabled-failing'\n}\n", "/**\n * Small filesystem helpers. Every write is atomic (temp file + rename) so a\n * reader never sees half a file, and secret-bearing files are created 0600.\n */\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\n\nexport function fileExists(file: string): boolean {\n try {\n return fs.statSync(file).isFile()\n }\n catch {\n return false\n }\n}\n\nexport function readText(file: string): string | null {\n try {\n return fs.readFileSync(file, 'utf8')\n }\n catch {\n return null\n }\n}\n\nexport function readJson<T>(file: string): T | null {\n const text = readText(file)\n if (text === null)\n return null\n try {\n return JSON.parse(text) as T\n }\n catch {\n return null\n }\n}\n\nexport function ensureDir(dir: string, mode = 0o700): void {\n fs.mkdirSync(dir, { recursive: true, mode })\n}\n\n/** Write a file through a sibling temp file, then rename it into place. */\nexport function writeFileAtomic(file: string, data: string, mode?: number): void {\n ensureDir(path.dirname(file))\n const temp = path.join(path.dirname(file), `.${path.basename(file)}.${process.pid}.${Date.now()}.tmp`)\n const fd = fs.openSync(temp, 'w', mode ?? 0o644)\n try {\n fs.writeFileSync(fd, data, 'utf8')\n fs.fsyncSync(fd)\n }\n finally {\n fs.closeSync(fd)\n }\n if (mode !== undefined)\n fs.chmodSync(temp, mode)\n fs.renameSync(temp, file)\n}\n\nexport function writeJsonAtomic(file: string, value: unknown, mode?: number): void {\n writeFileAtomic(file, `${JSON.stringify(value, null, 2)}\\n`, mode)\n}\n\n/** Best-effort removal; a missing file is not an error. */\nexport function removeFile(file: string): void {\n try {\n fs.rmSync(file, { force: true })\n }\n catch {\n // ignored: the caller only cares that it is gone, and reconciliation reports the rest\n }\n}\n", "/**\n * The boot ladder: which mechanisms exist on this platform, which one is\n * installed, and which one this process should use. Everything platform\n * specific is inside a provider's `detect()`/`status()`/`install()` \u2014 this file\n * only orders them and never runs at import time.\n */\nimport os from 'node:os'\nimport process from 'node:process'\nimport type { BootCandidate, BootMechanism, BootStatus } from '../shared/contracts.js'\nimport { run as execRun, sudoAvailable } from '../util/exec.js'\nimport type {\n BootActionResult,\n BootActivation,\n BootLadder,\n BootLadderOptions,\n BootProvider,\n BootProviderContext,\n BootProviderStatus,\n BootRetirement,\n BootSpec,\n BootStart,\n} from './types.js'\nimport { errorMessage, failed, isInstalledState } from './common.js'\nimport { createSystemdSystemProvider, createSystemdUserProvider } from './systemd.js'\nimport { createLaunchdAgentProvider, createLaunchdDaemonProvider } from './launchd.js'\nimport { createXdgAutostartProvider } from './xdg.js'\nimport { createWindowsRunProvider, createWindowsTaskProvider } from './windows.js'\nimport { createContainerProvider, createUnsupportedProvider } from './fallback.js'\n\nfunction platformKind(platform: NodeJS.Platform): BootStatus['platform'] {\n if (platform === 'linux' || platform === 'darwin' || platform === 'win32')\n return platform\n return 'other'\n}\n\n/** Ordered by preference within a platform; the terminal providers come last. */\nexport function bootProviders(\n ctx: BootProviderContext,\n platform: NodeJS.Platform = ctx.platform,\n): BootProvider[] {\n const container = createContainerProvider(ctx)\n const unsupported = createUnsupportedProvider(platform)\n switch (platform) {\n case 'linux':\n return [createSystemdUserProvider(ctx), createSystemdSystemProvider(ctx), createXdgAutostartProvider(ctx), container, unsupported]\n case 'darwin':\n return [createLaunchdAgentProvider(ctx), createLaunchdDaemonProvider(ctx), unsupported]\n case 'win32':\n return [createWindowsRunProvider(ctx), createWindowsTaskProvider(ctx), unsupported]\n default:\n return [container, unsupported]\n }\n}\n\nexport function createBootLadder(options: BootLadderOptions = {}): BootLadder {\n const platform = options.platform ?? process.platform\n // `uid` is this process's own uid, so pinning it in a test also pins whether the\n // process counts as root \u2014 otherwise the answer depends on who ran the suite.\n const uid = options.uid === undefined ? (process.getuid?.() ?? null) : options.uid\n const isRoot = uid === 0\n const ctx: BootProviderContext = {\n platform,\n home: options.home ?? os.homedir(),\n env: options.env ?? process.env,\n run: options.run ?? execRun,\n sudo: options.sudo ?? (async () => (isRoot ? true : await sudoAvailable())),\n isRoot,\n uid: options.uid,\n ownerPaths: options.ownerPaths,\n passwd: options.passwd,\n warn: options.warn,\n exists: options.exists,\n }\n const providers = bootProviders(ctx, platform)\n const kind = platformKind(platform)\n\n async function detectAll(): Promise<{ platform: BootStatus['platform'], candidates: BootCandidate[] }> {\n const candidates = await Promise.all(providers.map(async (provider) => {\n try {\n return await provider.detect()\n }\n catch (error) {\n return {\n mechanism: provider.mechanism,\n available: false,\n bootCapable: false,\n privileged: false,\n reason: `detection failed: ${errorMessage(error)}`,\n } satisfies BootCandidate\n }\n }))\n return { platform: kind, candidates }\n }\n\n async function safeStatus(provider: BootProvider, spec: BootSpec): Promise<BootProviderStatus> {\n try {\n return await provider.status(spec)\n }\n catch (error) {\n return { state: 'not-installed', unitPath: null, detail: errorMessage(error), commands: [] }\n }\n }\n\n function recommend(candidates: BootCandidate[]): BootMechanism | null {\n const byMechanism = new Map(candidates.map(candidate => [candidate.mechanism, candidate]))\n const available = providers\n .map(provider => provider.mechanism)\n .filter(mechanism => byMechanism.get(mechanism)?.available === true)\n const usable = (mechanism: string): boolean => mechanism !== 'container' && mechanism !== 'unsupported'\n // Inside a container no OS entry starts anything, so the honest advice is the\n // restart policy \u2014 ahead of anything else that merely looks available.\n if (available.includes('container'))\n return 'container'\n // A boot-capable mechanism that needs a password prompt is offered, but it is\n // not the *recommendation*: automatic should mean zero manual steps.\n const readyBoot = available.find(mechanism => usable(mechanism)\n && byMechanism.get(mechanism)?.bootCapable === true\n && byMechanism.get(mechanism)?.privileged === true)\n if (readyBoot)\n return readyBoot\n const ready = available.find(mechanism => usable(mechanism) && byMechanism.get(mechanism)?.privileged === true)\n if (ready)\n return ready\n const bootCapable = available.find(mechanism => usable(mechanism) && byMechanism.get(mechanism)?.bootCapable === true)\n if (bootCapable)\n return bootCapable\n return available.find(mechanism => mechanism !== 'unsupported')\n ?? available[0]\n ?? null\n }\n\n async function buildStatus(spec: BootSpec, mechanism?: BootMechanism): Promise<BootStatus> {\n const { candidates } = await detectAll()\n const byMechanism = new Map(candidates.map(candidate => [candidate.mechanism, candidate]))\n const statuses = new Map<BootMechanism, BootProviderStatus>()\n for (const [provider, status] of await Promise.all(providers.map(async provider => [provider, await safeStatus(provider, spec)] as const)))\n statuses.set(provider.mechanism, status)\n\n const explicit = mechanism ? providers.find(provider => provider.mechanism === mechanism) : undefined\n const installedProvider = providers.find((provider) => {\n const status = statuses.get(provider.mechanism)\n return status !== undefined && isInstalledState(status.state)\n })\n const recommended = recommend(candidates)\n const target = explicit\n ?? installedProvider\n ?? (recommended ? providers.find(provider => provider.mechanism === recommended) : undefined)\n\n const explicitStatus = explicit ? statuses.get(explicit.mechanism) : undefined\n const installedMechanism = explicit && explicitStatus && isInstalledState(explicitStatus.state)\n ? explicit.mechanism\n : installedProvider?.mechanism ?? null\n\n const common = { platform: kind, mechanism: installedMechanism, recommended, candidates }\n if (!target) {\n return {\n ...common,\n state: 'unsupported',\n bootCapable: false,\n privileged: false,\n unitPath: null,\n commands: [],\n detail: `no boot mechanism is available on ${platform}`,\n }\n }\n\n const status = statuses.get(target.mechanism)\n const candidate = byMechanism.get(target.mechanism)\n const alsoInstalled = providers\n .filter(provider => provider.mechanism !== target.mechanism && isInstalledState(statuses.get(provider.mechanism)?.state ?? 'unsupported'))\n .map(provider => provider.mechanism)\n return {\n ...common,\n state: status?.state ?? 'unsupported',\n bootCapable: candidate?.bootCapable ?? false,\n privileged: candidate?.privileged ?? false,\n unitPath: status?.unitPath ?? null,\n commands: status?.commands ?? [],\n detail: `${status?.detail ?? 'no detail'}${alsoInstalled.length ? `; also installed: ${alsoInstalled.join(', ')}` : ''}`,\n }\n }\n\n async function safeInstall(provider: BootProvider, spec: BootSpec): Promise<BootActionResult> {\n try {\n return await provider.install(spec)\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n }\n\n async function safeUninstall(provider: BootProvider, spec: BootSpec): Promise<BootActionResult> {\n try {\n return await provider.uninstall(spec)\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n }\n\n async function safeActivate(provider: BootProvider, spec: BootSpec): Promise<BootStart | null> {\n if (provider.activate === undefined)\n return null\n try {\n return await provider.activate(spec)\n }\n catch {\n return null\n }\n }\n\n async function safeRetirement(provider: BootProvider, spec: BootSpec): Promise<BootRetirement | null> {\n if (provider.retireCommands === undefined)\n return null\n try {\n return await provider.retireCommands(spec)\n }\n catch {\n return null\n }\n }\n\n async function safeStop(provider: BootProvider, spec: BootSpec): Promise<string[][]> {\n if (provider.stopCommands === undefined)\n return []\n try {\n return await provider.stopCommands(spec)\n }\n catch {\n return []\n }\n }\n\n /** Every mechanism the OS currently has an entry for, whatever it is doing. */\n async function installedMechanisms(spec: BootSpec): Promise<BootProvider[]> {\n const probes = await Promise.all(providers.map(async provider => [provider, await safeStatus(provider, spec)] as const))\n return probes.filter(([, status]) => isInstalledState(status.state)).map(([provider]) => provider)\n }\n\n /**\n * Which mechanism a call means. An install may fall back to the\n * recommendation; every other call means one the OS actually has an entry\n * for, because uninstalling or starting a mechanism that was never installed\n * cannot succeed and must not be invented.\n */\n function pick(status: BootStatus, mechanism?: BootMechanism): BootProvider | undefined {\n return pickOf(mechanism ?? status.mechanism)\n }\n\n function pickOf(mechanism: BootMechanism | null | undefined): BootProvider | undefined {\n return mechanism === null || mechanism === undefined\n ? undefined\n : providers.find(provider => provider.mechanism === mechanism)\n }\n\n return {\n providers,\n\n detect: detectAll,\n\n status: buildStatus,\n\n async install(spec: BootSpec, mechanism?: BootMechanism) {\n const before = await buildStatus(spec, mechanism)\n const target = pickOf(mechanism ?? before.mechanism ?? before.recommended)\n if (!target) {\n return {\n ...failed(`no boot mechanism is available on ${platform}`),\n mechanism: null,\n status: before,\n }\n }\n\n // Retiring the mechanism we came from is *not* done here: its entry is the\n // one currently running the panel (and often this plugin), so removing it\n // first would stop everything before the new entry was told to start.\n // `activate()` carries that retirement, for the caller that stops the panel.\n const result = await safeInstall(target, spec)\n const status = await buildStatus(spec, mechanism)\n return {\n ...result,\n mechanism: result.ok ? target.mechanism : status.mechanism,\n status,\n }\n },\n\n async uninstall(spec: BootSpec, mechanism?: BootMechanism) {\n const before = await buildStatus(spec, mechanism)\n const target = pick(before, mechanism)\n if (!target) {\n return {\n ok: true,\n changed: false,\n detail: 'no OS boot entry is installed for this plugin, so there is nothing to uninstall',\n commands: [],\n needsPrivilege: false,\n status: before,\n }\n }\n const result = await safeUninstall(target, spec)\n const status = await buildStatus(spec, mechanism)\n return { ...result, status }\n },\n\n /**\n * The start steps for the installed mechanism, plus the retirement of every\n * other entry on this machine.\n *\n * A switch is the whole reason this exists: the old entry has to go, and it\n * can only go after the panel is down \u2014 retiring it first would stop the\n * panel, and the plugin with it, before the new entry was ever told to\n * start. So both halves are handed to one caller that runs them in order,\n * detached. Nothing is stopped here.\n */\n async activate(spec: BootSpec, mechanism?: BootMechanism): Promise<BootActivation | null> {\n const status = await buildStatus(spec, mechanism)\n const target = pick(status, mechanism)\n if (!target)\n return null\n const start = await safeActivate(target, spec)\n if (start === null)\n return null\n\n const others = (await installedMechanisms(spec))\n .filter(provider => provider.mechanism !== target.mechanism)\n const retirements = await Promise.all(others.map(async provider => [provider, await safeRetirement(provider, spec)] as const))\n const usable = retirements.filter((pair): pair is [BootProvider, BootRetirement] => pair[1] !== null)\n\n // The mechanism we are leaving has to stop the panel itself: its own\n // restart policy would bring the panel back the moment the CLI's `down`\n // returns, racing the entry that is about to start one.\n const supervised = status.mechanism !== null\n ? providers.find(provider => provider.mechanism === status.mechanism)\n : others.find(provider => provider.mechanism !== target.mechanism)\n const stop = supervised === undefined ? [] : await safeStop(supervised, spec)\n\n return {\n ...start,\n stop,\n // A mechanism whose entry cannot be removed without stopping the panel is\n // left alone here: one stale entry is better than a panel nothing starts.\n retire: usable.flatMap(([, retirement]) => retirement.commands),\n retired: usable.map(([provider]) => provider.mechanism),\n retireDisplay: usable.flatMap(([, retirement]) => retirement.display),\n }\n },\n }\n}\n", "/**\n * The only place this plugin starts a process.\n *\n * Every call is an argv array with no shell, so a path or a value can never\n * become a command. Callers pass absolute programs; nothing here resolves\n * through a shell profile.\n */\nimport { spawn } from 'node:child_process'\n\nexport interface RunOptions {\n cwd?: string\n env?: Record<string, string | undefined>\n timeoutMs?: number\n /** Cap on each captured stream; a chatty command is truncated, not buffered forever. */\n maxBytes?: number\n}\n\nexport interface RunResult {\n command: string\n args: string[]\n code: number | null\n signal: NodeJS.Signals | null\n stdout: string\n stderr: string\n timedOut: boolean\n error: string | null\n}\n\n/** Run a program and always resolve; a non-zero code is data, not a throw. */\nexport async function run(command: string, args: string[] = [], options: RunOptions = {}): Promise<RunResult> {\n const maxBytes = options.maxBytes ?? 256 * 1024\n const timeoutMs = options.timeoutMs ?? 20_000\n\n return await new Promise<RunResult>((resolve) => {\n let settled = false\n let stdout = ''\n let stderr = ''\n let timedOut = false\n\n const finish = (result: Partial<RunResult>): void => {\n if (settled)\n return\n settled = true\n resolve({\n command,\n args,\n code: result.code ?? null,\n signal: result.signal ?? null,\n stdout: result.stdout ?? stdout,\n stderr: result.stderr ?? stderr,\n timedOut,\n error: result.error ?? null,\n })\n }\n\n let child\n try {\n child = spawn(command, args, {\n cwd: options.cwd,\n env: { ...process.env, ...options.env },\n shell: false,\n windowsHide: true,\n stdio: ['ignore', 'pipe', 'pipe'],\n })\n }\n catch (error) {\n finish({ error: error instanceof Error ? error.message : String(error) })\n return\n }\n\n const timer = setTimeout(() => {\n timedOut = true\n child.kill('SIGKILL')\n }, timeoutMs)\n\n child.stdout?.on('data', (chunk: Buffer) => {\n if (stdout.length < maxBytes)\n stdout += chunk.toString('utf8').slice(0, maxBytes - stdout.length)\n })\n child.stderr?.on('data', (chunk: Buffer) => {\n if (stderr.length < maxBytes)\n stderr += chunk.toString('utf8').slice(0, maxBytes - stderr.length)\n })\n\n child.on('error', (error) => {\n clearTimeout(timer)\n finish({ error: error.message })\n })\n child.on('close', (code, signal) => {\n clearTimeout(timer)\n finish({ code, signal })\n })\n })\n}\n\n/** Run a program and fail loudly on anything but success. */\nexport async function runOk(command: string, args: string[] = [], options: RunOptions = {}): Promise<RunResult> {\n const result = await run(command, args, options)\n if (result.error !== null)\n throw new Error(`${command} failed: ${result.error}`)\n if (result.code !== 0)\n throw new Error(`${command} exited ${String(result.code)}: ${result.stderr.trim() || result.stdout.trim()}`)\n return result\n}\n\n/** True when the program exists and answers to `--version` or similar. */\nexport async function hasCommand(command: string): Promise<boolean> {\n const probe = process.platform === 'win32' ? 'where.exe' : 'which'\n const result = await run(probe, [command], { timeoutMs: 5000 })\n return result.code === 0\n}\n\n/**\n * Whether this process can run one privileged command with no prompt.\n * `sudo -n` never asks for a password, so a failure is a definitive \"not\n * available\" rather than something to retry interactively.\n */\nexport async function sudoAvailable(): Promise<boolean> {\n if (process.getuid?.() === 0)\n return true\n const result = await run('sudo', ['-n', 'true'], { timeoutMs: 5000 })\n return result.code === 0\n}\n", "/**\n * systemd: a user unit (`~/.config/systemd/user`) and a system unit\n * (`/etc/systemd/system`). The system one needs root, so every privileged call\n * goes through `sudo -n` \u2014 it never prompts \u2014 and the copy-pasteable `sudo \u2026`\n * form is reported when elevation is not available.\n */\nimport fs from 'node:fs'\nimport type { BootCandidate, BootState } from '../shared/contracts.js'\nimport type { BootActionResult, BootProvider, BootProviderContext, BootProviderStatus, BootRetirement, BootSpec, BootStart } from './types.js'\nimport {\n assertAbsolute,\n assertUserName,\n assertArg,\n assertEnvKey,\n assertLabel,\n assertMarker,\n assertUnitName,\n shellCommand,\n systemdEnvLine,\n systemdExecWord,\n systemdPath,\n systemdText,\n} from './escape.js'\nimport type { FileOwnership } from './common.js'\nimport {\n accountOf,\n bootState,\n bootUserName,\n configHome,\n currentUser,\n errorMessage,\n existsOf,\n failed,\n inspectOwned,\n posixJoin,\n removeOwned,\n tempDir,\n writeOwned,\n} from './common.js'\n\nconst ENABLED_UNIT_STATES = new Set(['enabled', 'enabled-runtime', 'alias', 'static', 'indirect', 'generated', 'transient'])\nconst FAILED_RESULTS = new Set(['failed', 'exit-code', 'signal', 'timeout', 'core-dump', 'watchdog', 'start-limit-hit', 'oom-kill'])\n\ntype Scope = 'user' | 'system'\n\nfunction validate(spec: BootSpec): void {\n assertUnitName(spec.unitName)\n assertMarker(spec.marker)\n assertLabel(spec.label)\n assertAbsolute(spec.command, 'spec.command')\n assertAbsolute(spec.cwd, 'spec.cwd')\n assertAbsolute(spec.logDir, 'spec.logDir')\n for (const arg of spec.args)\n assertArg(arg)\n for (const key of Object.keys(spec.env))\n assertEnvKey(key)\n}\n\nfunction envLines(spec: BootSpec): string[] {\n return Object.entries(spec.env).map(([key, value]) => `Environment=${systemdEnvLine(key, value)}`)\n}\n\nconst MANAGED = (spec: BootSpec): string => `# Managed by ${spec.marker}`\n\nfunction codeOf(result: { code: number | null, error?: string | null }): number | null {\n return result.error ? null : result.code\n}\n\n/**\n * What a status read says about the account the installed unit runs as.\n *\n * A unit this plugin wrote that names no `User=` runs the panel as root, and one\n * that names somebody else is running a panel that is not that person's \u2014 either\n * way a server which refuses root crash-loops with no hint that the panel's own\n * user is the cause. So the account is stated where the operator is already\n * looking, and the remedy is the install button already on the page.\n */\nfunction accountNote(own: FileOwnership, ctx: BootProviderContext): string {\n if (!own.owned || own.text === null)\n return ''\n // Only the root outcome is reachable: any other name in a unit this plugin wrote\n // came from this same resolver, so it is the name the resolver would pick now.\n const declared = /^User=(.*)$/m.exec(own.text)?.[1]?.trim() ?? ''\n return declared === '' || declared === 'root'\n ? '; the unit names no other user, so the panel runs as root \u2014 install again from the account that should own it'\n : ''\n}\n\n/** `Type=exec` with an explicit environment; boot capability comes from linger, not from this file. */\nexport function systemdUserUnit(spec: BootSpec): string {\n validate(spec)\n return `${[\n MANAGED(spec),\n '[Unit]',\n `Description=${systemdText(spec.label)}`,\n 'After=network-online.target',\n '',\n '[Service]',\n 'Type=exec',\n `ExecStart=${[spec.command, ...spec.args].map(systemdExecWord).join(' ')}`,\n `WorkingDirectory=${systemdPath(spec.cwd, 'spec.cwd')}`,\n // A persistent entry survives a panel restart only because its nanny is not\n // the panel. The nanny is spawned `detached`, which gives it its own session \u2014\n // but *not* its own cgroup, so the default `KillMode=control-group` kills it\n // with the panel and takes the entry down too, quietly falsifying what\n // `persistent: true` promises. `process` signals the panel alone. (An ordinary\n // entry is spawned detached as well, so this keeps every supervised server, not\n // only the nannies \u2014 the panel re-adopts such a survivor by its port.)\n 'KillMode=process',\n 'Restart=always',\n 'RestartSec=5',\n ...envLines(spec),\n '',\n '[Install]',\n 'WantedBy=default.target',\n '',\n ].join('\\n')}`\n}\n\n/**\n * The system unit. `StartLimit*` belongs in `[Unit]`, not `[Service]`.\n *\n * `User=` is written when the entry has a real account to run as, and *never* as\n * `root`: an entry with no `User=` already runs as root, so naming it changes\n * nothing except to make the silent default look deliberate. A root account is\n * therefore refused loudly (by {@link bootUserName}) and the directive omitted.\n */\nexport function systemdSystemUnit(spec: BootSpec, user: string | null = null): string {\n validate(spec)\n return `${[\n MANAGED(spec),\n '[Unit]',\n `Description=${systemdText(spec.label)}`,\n 'After=network-online.target',\n 'StartLimitIntervalSec=60',\n 'StartLimitBurst=5',\n '',\n '[Service]',\n ...(user === null || user === '' || user === 'root' ? [] : [`User=${assertUserName(user)}`]),\n 'Type=exec',\n `ExecStart=${[spec.command, ...spec.args].map(systemdExecWord).join(' ')}`,\n `WorkingDirectory=${systemdPath(spec.cwd, 'spec.cwd')}`,\n // See the user unit: `control-group` would kill the nannies too.\n 'KillMode=process',\n 'Restart=always',\n 'RestartSec=5',\n ...envLines(spec),\n '',\n '[Install]',\n 'WantedBy=multi-user.target',\n '',\n ].join('\\n')}`\n}\n\nfunction parseKeyValues(stdout: string): Record<string, string> {\n const out: Record<string, string> = {}\n for (const line of stdout.split('\\n')) {\n const eq = line.indexOf('=')\n if (eq > 0)\n out[line.slice(0, eq).trim()] = line.slice(eq + 1).trim()\n }\n return out\n}\n\ninterface UnitProbe {\n unitFileState: string\n activeState: string\n result: string\n nRestarts: string\n enabled: boolean\n installed: boolean\n reachable: boolean\n detail: string\n}\n\nasync function readUnit(ctx: BootProviderContext, unit: string, scope: Scope): Promise<UnitProbe> {\n const scopeArgs = scope === 'user' ? ['--user'] : []\n const show = await ctx.run('systemctl', [...scopeArgs, 'show', '-p', 'UnitFileState', '-p', 'ActiveState', '-p', 'Result', '-p', 'NRestarts', unit])\n const props = parseKeyValues(show.stdout)\n const isEnabled = await ctx.run('systemctl', [...scopeArgs, 'is-enabled', unit])\n const enabledCode = codeOf(isEnabled)\n const unitFileState = props.UnitFileState ?? ''\n /**\n * `is-enabled` exits 1 both for a disabled unit and for a unit it cannot even\n * look up, so only a known `UnitFileState` (or an explicit exit 0) proves the\n * unit exists. A user-bus failure must not read as `installed-disabled`.\n */\n const knownState = unitFileState !== '' && unitFileState !== 'not-found'\n const enabled = enabledCode === 0 || ENABLED_UNIT_STATES.has(unitFileState)\n const installed = enabled || knownState\n const result = props.Result ?? ''\n return {\n unitFileState,\n activeState: props.ActiveState ?? '',\n result,\n nRestarts: props.NRestarts ?? '',\n enabled,\n installed,\n reachable: show.error === undefined || show.error === null,\n detail: `UnitFileState=${unitFileState || '(none)'} ActiveState=${props.ActiveState || '(none)'} Result=${result || '(none)'} NRestarts=${props.NRestarts ?? '0'}`,\n }\n}\n\nasync function userScopeReachable(ctx: BootProviderContext): Promise<{ reachable: boolean, reason: string }> {\n const probe = await ctx.run('systemctl', ['--user', 'is-system-running'])\n if (probe.error)\n return { reachable: false, reason: `systemctl --user is not available here (${probe.error})` }\n if (`${probe.stdout}\\n${probe.stderr}`.includes('Failed to connect to user scope bus'))\n return { reachable: false, reason: 'systemctl --user cannot reach a user manager: Failed to connect to user scope bus' }\n return { reachable: true, reason: '' }\n}\n\nasync function lingerEnabled(ctx: BootProviderContext): Promise<boolean> {\n const user = currentUser(ctx)\n if (!user)\n return false\n const probe = await ctx.run('loginctl', ['show-user', user, '--property=Linger'])\n if (codeOf(probe) !== 0)\n return false\n return /(^|\\n)Linger=yes(\\n|$)/.test(probe.stdout)\n}\n\nfunction problem(run: { code: number | null, error?: string | null, stdout: string, stderr: string }, command: string): string | null {\n const code = codeOf(run)\n if (code === 0)\n return null\n if (code === null)\n return run.error ? `${command}: ${run.error}` : `${command}: no exit code`\n return `${command} exited ${code}: ${(run.stderr || run.stdout).trim() || '(no output)'}`\n}\n\nexport function createSystemdUserProvider(ctx: BootProviderContext): BootProvider {\n const mechanism = 'systemd-user' as const\n const unitOf = (spec: BootSpec): string => `${assertUnitName(spec.unitName)}.service`\n const pathOf = (spec: BootSpec): string => posixJoin(configHome(ctx), 'systemd', 'user', unitOf(spec))\n\n return {\n mechanism,\n\n async detect(): Promise<BootCandidate> {\n if (ctx.platform !== 'linux')\n return { mechanism, available: false, bootCapable: false, privileged: false, reason: 'systemd is Linux-only' }\n const bus = await userScopeReachable(ctx)\n if (!bus.reachable)\n return { mechanism, available: false, bootCapable: false, privileged: false, reason: bus.reason }\n const linger = await lingerEnabled(ctx)\n const privileged = linger || (await ctx.sudo())\n return {\n mechanism,\n available: true,\n bootCapable: linger,\n privileged,\n reason: linger\n ? 'a systemd user manager is reachable and linger is on, so the unit starts at boot'\n : 'a systemd user manager is reachable, but linger is off: the unit starts at login until `loginctl enable-linger` succeeds',\n }\n },\n\n async status(spec: BootSpec): Promise<BootProviderStatus> {\n try {\n const unit = unitOf(spec)\n const file = pathOf(spec)\n const own = inspectOwned(file, assertMarker(spec.marker))\n if (own.exists && !own.owned)\n return { state: 'not-installed', unitPath: file, detail: own.reason ?? 'foreign file', commands: [] }\n const probe = await readUnit(ctx, unit, 'user')\n const installed = own.owned || probe.installed\n const state: BootState = bootState(installed, probe.enabled, FAILED_RESULTS.has(probe.result))\n const user = currentUser(ctx)\n const linger = await lingerEnabled(ctx)\n const commands: string[] = []\n if (!linger && user)\n commands.push(shellCommand('sudo', ['loginctl', 'enable-linger', user]))\n const detail = `${probe.detail}; ${linger ? 'linger is on (boot-capable)' : 'linger is off (login-scoped)'}`\n return { state, unitPath: file, detail, commands }\n }\n catch (error) {\n return { state: 'not-installed', unitPath: null, detail: errorMessage(error), commands: [] }\n }\n },\n\n async install(spec: BootSpec): Promise<BootActionResult> {\n try {\n validate(spec)\n const unit = unitOf(spec)\n const file = pathOf(spec)\n const write = writeOwned(file, spec.marker, systemdUserUnit(spec), 0o644)\n if (write.refusal)\n return failed(write.refusal)\n\n const before = await readUnit(ctx, unit, 'user')\n let changed = write.changed\n if (write.changed || !before.enabled) {\n const reloadCommand = shellCommand('systemctl', ['--user', 'daemon-reload'])\n const reload = await ctx.run('systemctl', ['--user', 'daemon-reload'])\n const reloadProblem = problem(reload, reloadCommand)\n if (reloadProblem)\n return failed(reloadProblem, { changed, commands: [reloadCommand] })\n\n const enableCommand = shellCommand('systemctl', ['--user', 'enable', '--now', unit])\n const enable = await ctx.run('systemctl', ['--user', 'enable', '--now', unit])\n const enableProblem = problem(enable, enableCommand)\n if (enableProblem)\n return failed(enableProblem, { changed: true, commands: [enableCommand] })\n changed = true\n }\n\n const after = await readUnit(ctx, unit, 'user')\n if (!after.enabled)\n return failed(`unit ${unit} is not enabled after install (${after.detail})`, { changed })\n\n let linger = await lingerEnabled(ctx)\n const user = currentUser(ctx)\n const commands: string[] = []\n if (!linger && user) {\n const res = await ctx.run('loginctl', ['enable-linger', user])\n if (codeOf(res) === 0) {\n linger = true\n changed = true\n }\n else {\n commands.push(shellCommand('sudo', ['loginctl', 'enable-linger', user]))\n }\n }\n const detail = linger\n ? `${file} installed and enabled; linger is on, so ${unit} starts at boot`\n : `${file} installed and enabled, but linger is off: ${unit} starts at login only. ${commands.length ? `Run \\`${commands[0]}\\` to make it boot-capable.` : 'No target user could be determined to enable linger for.'}`\n return { ok: true, changed, detail, commands, needsPrivilege: !linger }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n async uninstall(spec: BootSpec): Promise<BootActionResult> {\n try {\n validate(spec)\n const unit = unitOf(spec)\n const file = pathOf(spec)\n const own = inspectOwned(file, spec.marker)\n if (own.exists && !own.owned)\n return failed(own.reason ?? 'foreign file')\n\n const before = await readUnit(ctx, unit, 'user')\n if (!before.reachable && !own.exists) {\n return { ok: true, changed: false, detail: `no unit file at ${file} and systemctl is unavailable; nothing to remove`, commands: [], needsPrivilege: false }\n }\n // A unit of the same name can come from anywhere on the search path; with\n // no file of ours, disabling by name would stop somebody else's unit.\n if (!own.owned && !own.exists && before.installed)\n return failed(`a unit named ${unit} exists but no unit file of ours is at ${file}; refusing to disable a unit this plugin did not install`)\n if (own.owned && before.reachable) {\n const disableCommand = shellCommand('systemctl', ['--user', 'disable', '--now', unit])\n const disable = await ctx.run('systemctl', ['--user', 'disable', '--now', unit])\n const disableCode = codeOf(disable)\n if (disableCode !== 0 && disableCode !== 1 && disableCode !== 4)\n return failed(problem(disable, disableCommand) ?? 'disable failed', { commands: [disableCommand] })\n }\n\n const remove = removeOwned(file, spec.marker)\n if (remove.refusal)\n return failed(remove.refusal)\n\n let changed = remove.removed\n if (before.installed || remove.removed) {\n await ctx.run('systemctl', ['--user', 'daemon-reload'])\n changed = true\n }\n\n const after = await readUnit(ctx, unit, 'user')\n if (after.installed)\n return failed(`unit ${unit} is still known to systemd after removal (${after.detail})`, { changed })\n\n return {\n ok: true,\n changed,\n detail: remove.removed ? `disabled and removed ${file}` : `${file} was already gone`,\n commands: [],\n needsPrivilege: false,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n /**\n * `restart` stops the panel and starts it again under the unit, so nothing\n * else may stop it first: stopping it from outside would race this call.\n * `is-active` is the proof the unit is really there, before anything is\n * killed.\n */\n async activate(spec: BootSpec): Promise<BootStart> {\n const unit = unitOf(spec)\n return {\n commands: [['systemctl', '--user', 'restart', unit]],\n requires: { commands: [['systemctl', '--user', 'is-active', unit]], files: [] },\n display: [shellCommand('systemctl', ['--user', 'restart', unit])],\n }\n },\n\n /** Stops the panel the unit supervises, keeping the unit enabled. */\n async stopCommands(spec: BootSpec): Promise<string[][]> {\n return [['systemctl', '--user', 'stop', unitOf(spec)]]\n },\n\n /** No `--now`: the panel is already down when a switch runs this. */\n async retireCommands(spec: BootSpec): Promise<BootRetirement> {\n const unit = unitOf(spec)\n const file = pathOf(spec)\n return {\n commands: [\n ['systemctl', '--user', 'disable', unit],\n ['rm', '-f', file],\n ['systemctl', '--user', 'daemon-reload'],\n ],\n display: [\n shellCommand('systemctl', ['--user', 'disable', unit]),\n shellCommand('rm', ['-f', file]),\n shellCommand('systemctl', ['--user', 'daemon-reload']),\n ],\n }\n },\n }\n}\n\nfunction stageUnit(ctx: BootProviderContext, unit: string, content: string): string {\n const staged = posixJoin(tempDir(ctx), `home-hosted-${unit}`)\n fs.writeFileSync(staged, content, { mode: 0o644 })\n return staged\n}\n\nexport function createSystemdSystemProvider(ctx: BootProviderContext): BootProvider {\n const mechanism = 'systemd-system' as const\n const unitOf = (spec: BootSpec): string => `${assertUnitName(spec.unitName)}.service`\n const pathOf = (spec: BootSpec): string => posixJoin('/etc', 'systemd', 'system', unitOf(spec))\n /**\n * The account the entry runs as \u2014 never this process's `$USER`.\n *\n * A system unit outlives the shell that wrote it and is restarted by root, so\n * the only honest answer is the login that asked for root (`SUDO_UID`) or, when\n * the panel itself is not root, the account it belongs to. A root answer is not\n * written as `User=root`: omitting the directive *is* running as root, so\n * naming it only disguises the default this exists to surface.\n */\n const contentOf = (spec: BootSpec): string => systemdSystemUnit(spec, bootUserName(ctx))\n\n const installCommands = (staged: string, file: string, unit: string): string[] => [\n shellCommand('sudo', ['install', '-m', '0644', staged, file]),\n shellCommand('sudo', ['systemctl', 'daemon-reload']),\n shellCommand('sudo', ['systemctl', 'enable', '--now', unit]),\n ]\n\n return {\n mechanism,\n\n async detect(): Promise<BootCandidate> {\n if (ctx.platform !== 'linux')\n return { mechanism, available: false, bootCapable: false, privileged: false, reason: 'systemd is Linux-only' }\n if (!existsOf(ctx, '/run/systemd/system'))\n return { mechanism, available: false, bootCapable: false, privileged: false, reason: 'systemd is not the init system here (no /run/systemd/system)' }\n const privileged = ctx.isRoot || (await ctx.sudo())\n // The account the entry would run as is a property of the candidate, not a\n // detail: a system unit with no account runs the panel as root, and that is\n // the whole reason a server that refuses root crash-loops.\n const who = accountOf(ctx)\n const scope = who === null || who.root\n ? 'no account was resolved for the entry, so it would run the panel as root'\n : `the entry runs the panel as ${who.name}`\n return {\n mechanism,\n available: privileged,\n bootCapable: privileged,\n privileged,\n reason: privileged\n ? `systemd is PID 1 and this process can use root, so a system unit starts at boot; ${scope}`\n : 'systemd is PID 1, but installing a system unit needs root or passwordless sudo',\n }\n },\n\n async status(spec: BootSpec): Promise<BootProviderStatus> {\n try {\n const unit = unitOf(spec)\n const file = pathOf(spec)\n const own = inspectOwned(file, assertMarker(spec.marker))\n if (own.exists && !own.owned)\n return { state: 'not-installed', unitPath: file, detail: own.reason ?? 'foreign file', commands: [] }\n const probe = await readUnit(ctx, unit, 'system')\n const installed = own.owned || probe.installed\n const state: BootState = bootState(installed, probe.enabled, FAILED_RESULTS.has(probe.result))\n const privileged = ctx.isRoot || (await ctx.sudo())\n const commands: string[] = []\n if (installed && !probe.enabled && !privileged)\n commands.push(shellCommand('sudo', ['systemctl', 'enable', '--now', unit]))\n return {\n state,\n unitPath: file,\n detail: `${probe.detail}${privileged ? '' : '; needs root to change'}${accountNote(own, ctx)}`,\n commands,\n }\n }\n catch (error) {\n return { state: 'not-installed', unitPath: null, detail: errorMessage(error), commands: [] }\n }\n },\n\n async install(spec: BootSpec): Promise<BootActionResult> {\n try {\n validate(spec)\n const unit = unitOf(spec)\n const file = pathOf(spec)\n const content = contentOf(spec)\n const own = inspectOwned(file, spec.marker)\n if (own.exists && !own.owned)\n return failed(own.reason ?? 'foreign file')\n\n const before = await readUnit(ctx, unit, 'system')\n const changed = !own.exists || own.text !== content\n\n if (!ctx.isRoot && !(await ctx.sudo())) {\n const staged = stageUnit(ctx, unit, content)\n return failed(`installing a system unit needs root; the unit is staged at ${staged}`, {\n changed: false,\n needsPrivilege: true,\n commands: installCommands(staged, file, unit),\n })\n }\n\n if (ctx.isRoot) {\n const write = writeOwned(file, spec.marker, content, 0o644)\n if (write.refusal)\n return failed(write.refusal)\n }\n else {\n const staged = stageUnit(ctx, unit, content)\n const installCommand = shellCommand('sudo', ['install', '-m', '0644', staged, file])\n const install = await ctx.run('sudo', ['-n', 'install', '-m', '0644', staged, file])\n const installProblem = problem(install, installCommand)\n if (installProblem)\n return failed(installProblem, { needsPrivilege: true, commands: installCommands(staged, file, unit) })\n fs.rmSync(staged, { force: true })\n }\n\n if (changed || !before.enabled) {\n const sudoPrefix = ctx.isRoot ? [] : ['-n']\n const reloadCommand = shellCommand('sudo', ['systemctl', 'daemon-reload'])\n const reload = await ctx.run('sudo', [...sudoPrefix, 'systemctl', 'daemon-reload'])\n const reloadProblem = problem(reload, reloadCommand)\n if (reloadProblem)\n return failed(reloadProblem, { changed, needsPrivilege: !ctx.isRoot, commands: [reloadCommand] })\n\n const enableCommand = shellCommand('sudo', ['systemctl', 'enable', '--now', unit])\n const enable = await ctx.run('sudo', [...sudoPrefix, 'systemctl', 'enable', '--now', unit])\n const enableProblem = problem(enable, enableCommand)\n if (enableProblem)\n return failed(enableProblem, { changed: true, needsPrivilege: !ctx.isRoot, commands: [enableCommand] })\n }\n\n const after = await readUnit(ctx, unit, 'system')\n if (!after.enabled)\n return failed(`unit ${unit} is not enabled after install (${after.detail})`, { changed })\n\n return {\n ok: true,\n changed: changed || !before.enabled,\n detail: `${file} installed and enabled as a system unit (${after.detail})`,\n commands: [],\n needsPrivilege: !ctx.isRoot,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n async uninstall(spec: BootSpec): Promise<BootActionResult> {\n try {\n const unit = unitOf(spec)\n const file = pathOf(spec)\n const own = inspectOwned(file, spec.marker)\n if (own.exists && !own.owned)\n return failed(own.reason ?? 'foreign file')\n\n const privileged = ctx.isRoot || (await ctx.sudo())\n if (!privileged) {\n if (!own.exists)\n return { ok: true, changed: false, detail: `no unit file at ${file} and no privilege to change systemd; nothing to remove`, commands: [], needsPrivilege: false }\n return failed('removing a system unit needs root', {\n needsPrivilege: true,\n commands: [\n shellCommand('sudo', ['systemctl', 'disable', '--now', unit]),\n shellCommand('sudo', ['rm', '-f', file]),\n shellCommand('sudo', ['systemctl', 'daemon-reload']),\n ],\n })\n }\n\n const prefix = ctx.isRoot ? [] : ['-n']\n // With no file of ours at the path we write, a same-named unit belongs to\n // somebody else (a vendor unit in /usr/lib/systemd/system): leave it alone.\n if (!own.exists && !own.owned)\n return failed(`no unit file of ours at ${file}; refusing to disable a unit this plugin did not install`)\n const disableCommand = shellCommand('sudo', ['systemctl', 'disable', '--now', unit])\n const disable = await ctx.run('sudo', [...prefix, 'systemctl', 'disable', '--now', unit])\n const disableCode = codeOf(disable)\n if (disableCode !== 0 && disableCode !== 1 && disableCode !== 4)\n return failed(problem(disable, disableCommand) ?? 'disable failed', { needsPrivilege: !ctx.isRoot, commands: [disableCommand] })\n\n if (own.exists) {\n if (ctx.isRoot) {\n const remove = removeOwned(file, spec.marker)\n if (remove.refusal)\n return failed(remove.refusal)\n }\n else {\n // The check above ran as the invoking user, which cannot read a\n // root-only file; re-prove ownership before the privileged delete.\n if (!own.owned)\n return failed(own.reason ?? 'foreign file')\n const rmCommand = shellCommand('sudo', ['rm', '-f', file])\n const rm = await ctx.run('sudo', ['-n', 'rm', '-f', file])\n const rmProblem = problem(rm, rmCommand)\n if (rmProblem)\n return failed(rmProblem, { needsPrivilege: true, commands: [rmCommand] })\n }\n }\n\n await ctx.run('sudo', [...prefix, 'systemctl', 'daemon-reload'])\n const after = await readUnit(ctx, unit, 'system')\n if (after.installed)\n return failed(`unit ${unit} is still known to systemd after removal (${after.detail})`, { changed: true, needsPrivilege: !ctx.isRoot })\n\n return {\n ok: true,\n changed: own.exists || disableCode === 0,\n detail: own.exists ? `disabled and removed ${file}` : `${file} was already gone`,\n commands: [],\n needsPrivilege: !ctx.isRoot,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n /**\n * `restart` stops the panel and starts it again under the unit, so nothing\n * else may stop it first. A system unit needs root; `sudo -n` never prompts,\n * which is the only elevation a detached helper can get.\n */\n async activate(spec: BootSpec): Promise<BootStart> {\n const unit = unitOf(spec)\n const elevate = (args: string[]): string[] => ctx.isRoot ? args : ['sudo', '-n', ...args]\n return {\n commands: [elevate(['systemctl', 'restart', unit])],\n requires: { commands: [elevate(['systemctl', 'is-active', unit])], files: [] },\n display: [shellCommand('sudo', ['systemctl', 'restart', unit])],\n }\n },\n\n /**\n * Stops the panel the unit supervises. Without this a switch would lose the\n * race: `Restart=always` brings the panel back as soon as the CLI's `down`\n * ends, before the new entry starts one.\n */\n async stopCommands(spec: BootSpec): Promise<string[][]> {\n const elevate = (args: string[]): string[] => ctx.isRoot ? args : ['sudo', '-n', ...args]\n return [elevate(['systemctl', 'stop', unitOf(spec)])]\n },\n\n /**\n * The retirement for a switch. Deliberately no `--now`: this runs in the\n * helper *after* the panel is already down, and `--now` here would race the\n * start command that follows.\n */\n async retireCommands(spec: BootSpec): Promise<BootRetirement> {\n const unit = unitOf(spec)\n const file = pathOf(spec)\n const elevate = (args: string[]): string[] => ctx.isRoot ? args : ['sudo', '-n', ...args]\n return {\n commands: [\n elevate(['systemctl', 'disable', unit]),\n elevate(['rm', '-f', file]),\n elevate(['systemctl', 'daemon-reload']),\n ],\n display: [\n shellCommand('sudo', ['systemctl', 'disable', unit]),\n shellCommand('sudo', ['rm', '-f', file]),\n shellCommand('sudo', ['systemctl', 'daemon-reload']),\n ],\n }\n },\n }\n}\n", "/**\n * Escaping for every format this plugin writes.\n *\n * None of this builds a shell command: a unit file, a plist, a `.desktop`\n * entry, a task XML and a registry value each have their own parser, and each\n * value is quoted for exactly that parser. Values that carry control\n * characters (newlines included) are rejected outright, because a newline is\n * how a value escapes its field in every one of these formats.\n */\n\nconst CONTROL = /[\\u0000-\\u001F\\u007F]/\n\nexport function assertNoControl(value: string, what: string): string {\n if (CONTROL.test(value))\n throw new Error(`${what} must not contain control characters or newlines`)\n return value\n}\n\nexport function assertUnitName(name: string): string {\n if (!/^[A-Za-z0-9._@-]+$/.test(name))\n throw new Error(`unit name ${JSON.stringify(name)} must match ^[A-Za-z0-9._@-]+$`)\n if (name.startsWith('-') || name.startsWith('.') || name.includes('..'))\n throw new Error(`unit name ${JSON.stringify(name)} must not be option-like or traverse paths`)\n return name\n}\n\nexport function assertMarker(marker: string): string {\n if (marker.trim() === '')\n throw new Error('marker must not be empty')\n return assertNoControl(marker, 'marker')\n}\n\n/**\n * A user name a unit or a plist may name.\n *\n * It is *not* quoted anywhere: systemd's `User=` and launchd's `UserName` take a\n * bare name, and a value carrying a newline would therefore end the line and let\n * the rest of it become another directive. So this is the one place that decides\n * what an account name may be, and every writer goes through it.\n */\nexport function assertUserName(name: string): string {\n if (!/^[A-Za-z_][A-Za-z0-9._-]*$/.test(name))\n throw new Error(`user name ${JSON.stringify(name)} must match ^[A-Za-z_][A-Za-z0-9._-]*$`)\n return name\n}\n\nexport function assertEnvKey(key: string): string {\n if (!/^[A-Za-z_][A-Za-z0-9_]*$/.test(key))\n throw new Error(`environment key ${JSON.stringify(key)} is not a valid name`)\n return key\n}\n\n/** POSIX-absolute, Windows-drive-absolute or UNC; checked without consulting the host OS. */\nexport function isAbsolutePath(value: string): boolean {\n return value.startsWith('/') || /^[A-Za-z]:[\\\\/]/.test(value) || value.startsWith('\\\\\\\\')\n}\n\nexport function assertAbsolute(value: string, what: string): string {\n if (!isAbsolutePath(value))\n throw new Error(`${what} must be absolute, got ${JSON.stringify(value)}`)\n return assertNoControl(value, what)\n}\n\nexport function assertLabel(value: string): string {\n if (value.trim() === '')\n throw new Error('label must not be empty')\n return assertNoControl(value, 'label')\n}\n\nexport function assertArg(value: string, what = 'argument'): string {\n return assertNoControl(value, what)\n}\n\nfunction replaceAll(value: string, pairs: Array<[string, string]>): string {\n let out = value\n for (const [from, to] of pairs)\n out = out.split(from).join(to)\n return out\n}\n\n/** Single-quote a value the way a POSIX shell would, for `commands[]`. */\nexport function shellQuote(value: string): string {\n if (value !== '' && /^[A-Za-z0-9._/:=@%+,-]+$/.test(value))\n return value\n return `'${value.split('\\'').join('\\'\\\\\\'\\'')}'`\n}\n\n/** The exact line a person can paste into a shell. */\nexport function shellCommand(program: string, args: string[]): string {\n return [program, ...args].map(shellQuote).join(' ')\n}\n\n// ---------------------------------------------------------------------------\n// systemd\n// ---------------------------------------------------------------------------\n\n/**\n * A word in `ExecStart=`/`ExecStop=`. systemd expands `%` specifiers and `$`\n * variables there before it splits the line, so both are pinned, and `;` \u2014 the\n * multi-command separator \u2014 is quoted rather than left bare.\n */\nexport function systemdExecWord(value: string): string {\n assertNoControl(value, 'ExecStart word')\n const escaped = replaceAll(value, [['\\\\', '\\\\\\\\'], ['\"', '\\\\\"'], ['$', '$$'], ['%', '%%']])\n return escaped !== value || /[\\s\"';]/.test(value) ? `\"${escaped}\"` : value\n}\n\n/**\n * A single-line free-text setting such as `Description=` (specifiers only).\n */\nexport function systemdText(value: string): string {\n assertNoControl(value, 'unit setting')\n // A trailing backslash would continue the directive onto the next line.\n return replaceAll(value, [['\\\\', '\\\\\\\\'], ['%', '%%']])\n}\n\n/**\n * A path setting (`WorkingDirectory=`). systemd does not unquote these, but it\n * does expand `%` specifiers (an unknown one is fatal, verified with\n * `systemd-analyze verify`), and a trailing `\\` continues the line into the next\n * directive. So both are escaped rather than passed raw.\n */\nexport function systemdPath(value: string, what = 'path'): string {\n assertAbsolute(value, what)\n assertNoControl(value, what)\n if (value.includes('\"'))\n throw new Error(`${what} must not contain a double quote: ${JSON.stringify(value)}`)\n return replaceAll(value, [['\\\\', '\\\\\\\\'], ['%', '%%']])\n}\n\n/** `Environment=KEY=value`, quoted as one assignment when the value needs it. */\nexport function systemdEnvLine(key: string, value: string): string {\n assertEnvKey(key)\n assertNoControl(value, `environment value for ${key}`)\n const escaped = replaceAll(value, [['\\\\', '\\\\\\\\'], ['\"', '\\\\\"'], ['%', '%%']])\n return escaped !== value || /[\\s\"']/.test(value) ? `\"${key}=${escaped}\"` : `${key}=${value}`\n}\n\n// ---------------------------------------------------------------------------\n// XML (launchd plist, scheduled-task XML)\n// ---------------------------------------------------------------------------\n\nexport function xmlEscape(value: string): string {\n assertNoControl(value, 'xml value')\n return replaceAll(value, [['&', '&amp;'], ['<', '&lt;'], ['>', '&gt;'], ['\"', '&quot;'], ['\\'', '&apos;']])\n}\n\nexport function xmlUnescape(value: string): string {\n return replaceAll(value, [['&lt;', '<'], ['&gt;', '>'], ['&quot;', '\"'], ['&apos;', '\\''], ['&amp;', '&']])\n}\n\n/** A marker inside an XML comment may not contain `--`. */\nexport function assertXmlCommentSafe(value: string): string {\n if (value.includes('--'))\n throw new Error('marker must not contain `--` when it is written inside an XML comment')\n return value\n}\n\n// ---------------------------------------------------------------------------\n// freedesktop .desktop\n// ---------------------------------------------------------------------------\n\nconst DESKTOP_RESERVED = /[\\s\"'\\\\><~|&;$*?#()`]/\n\nexport function desktopWord(value: string): string {\n assertNoControl(value, 'desktop Exec word')\n const escaped = replaceAll(value, [['\\\\', '\\\\\\\\'], ['\"', '\\\\\"'], ['`', '\\\\`'], ['$', '\\\\$'], ['%', '%%']])\n return DESKTOP_RESERVED.test(value) || escaped !== value ? `\"${escaped}\"` : value\n}\n\nexport function desktopExec(program: string, args: string[]): string {\n return [program, ...args].map(desktopWord).join(' ')\n}\n\n/** A `Key=value` line in a `.desktop` file; the marker is written verbatim. */\nexport function desktopValue(key: string, value: string): string {\n assertNoControl(value, `desktop ${key}`)\n if (key.includes('=') || key.includes('\\n'))\n throw new Error(`desktop key ${JSON.stringify(key)} is not valid`)\n return `${key}=${value}`\n}\n\n// ---------------------------------------------------------------------------\n// Windows\n// ---------------------------------------------------------------------------\n\n/** Quote one argv word the way `CreateProcess`/`cmd` parse a command line. */\nexport function windowsArg(value: string): string {\n assertNoControl(value, 'windows argument')\n if (value !== '' && !/[\\s\"]/.test(value))\n return value\n\n let out = '\"'\n let backslashes = 0\n for (const char of value) {\n if (char === '\\\\') {\n backslashes += 1\n continue\n }\n if (char === '\"') {\n out += `${'\\\\'.repeat(backslashes * 2 + 1)}\"`\n backslashes = 0\n continue\n }\n out += `${'\\\\'.repeat(backslashes)}${char}`\n backslashes = 0\n }\n out += `${'\\\\'.repeat(backslashes * 2)}\"`\n return out\n}\n\nexport function windowsCommandLine(program: string, args: string[]): string {\n return [program, ...args].map(windowsArg).join(' ')\n}\n\n/** A line for a `.cmd` wrapper: `%` is a batch metacharacter and is doubled. */\nexport function batchCommandLine(program: string, args: string[]): string {\n return batchEscape(windowsCommandLine(program, args))\n}\n\n/**\n * Make one already-quoted command line safe inside a batch file. `%` is doubled\n * last: the `^` inserted for a metacharacter must not itself gain a caret.\n */\nfunction batchEscape(line: string): string {\n assertNoControl(line, 'batch command line')\n return replaceAll(line, [\n ['^', '^^'],\n ['&', '^&'],\n ['|', '^|'],\n ['<', '^<'],\n ['>', '^>'],\n ['(', '^('],\n [')', '^)'],\n ['%', '%%'],\n ])\n}\n\n/** A single-quoted PowerShell literal. */\nexport function powershellLiteral(value: string): string {\n assertNoControl(value, 'powershell value')\n return `'${value.split('\\'').join('\\'\\'')}'`\n}\n\n/** Quote one word the way `cmd.exe` takes it, for a copy-pasteable `commands[]` entry. */\nexport function cmdQuote(value: string): string {\n if (value !== '' && !/[\\s\"&|<>^]/.test(value))\n return value\n return `\"${value.split('\"').join('\"\"')}\"`\n}\n\nexport function windowsDisplayCommand(program: string, args: string[]): string {\n return [program, ...args].map(cmdQuote).join(' ')\n}\n\n// ---------------------------------------------------------------------------\n// launchd / registry value names\n// ---------------------------------------------------------------------------\n\nexport function assertRegistryValueName(name: string): string {\n if (name.trim() === '')\n throw new Error('registry value name must not be empty')\n return assertNoControl(name, 'registry value name')\n}\n", "/**\n * launchd: a per-user agent (`~/Library/LaunchAgents`) and a system daemon\n * (`/Library/LaunchDaemons`). `launchctl print` is read for its exit code only\n * \u2014 its output is explicitly not API. Loading is `bootstrap`, unloading is\n * `bootout`, and exit 5 from bootstrap means the job is already loaded.\n */\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport type { BootCandidate, BootState } from '../shared/contracts.js'\nimport type { BootActionResult, BootProvider, BootProviderContext, BootProviderStatus, BootRetirement, BootSpec, BootStart } from './types.js'\nimport {\n assertAbsolute,\n assertUserName,\n assertArg,\n assertEnvKey,\n assertLabel,\n assertMarker,\n assertUnitName,\n assertXmlCommentSafe,\n shellCommand,\n xmlEscape,\n} from './escape.js'\nimport {\n bootState,\n bootUserName,\n errorMessage,\n failed,\n inspectOwned,\n posixJoin,\n removeOwned,\n tempDir,\n uidOf,\n writeOwned,\n} from './common.js'\n\n/** Both scopes label with the same reverse-DNS prefix. */\nexport const LAUNCHD_LABEL_PREFIX = 'dev.home-hosted.'\n/** bootstrap on an already-loaded job reports EIO. */\nconst LAUNCHCTL_ALREADY_LOADED = 5\n/** `launchctl print` for a label that is not loaded. */\nconst LAUNCHCTL_NOT_LOADED = 113\n/** `launchctl bootout` for a job that is not there. */\nconst LAUNCHCTL_NO_SUCH_PROCESS = 3\n\nfunction validate(spec: BootSpec): void {\n assertUnitName(spec.unitName)\n assertMarker(spec.marker)\n assertLabel(spec.label)\n assertAbsolute(spec.command, 'spec.command')\n assertAbsolute(spec.cwd, 'spec.cwd')\n assertAbsolute(spec.logDir, 'spec.logDir')\n for (const arg of spec.args)\n assertArg(arg)\n for (const key of Object.keys(spec.env))\n assertEnvKey(key)\n}\n\nexport function launchdLabel(spec: BootSpec): string {\n return `${LAUNCHD_LABEL_PREFIX}${assertUnitName(spec.unitName)}`\n}\n\n/** The full property list. The marker rides in an XML comment, which launchd ignores. */\nexport interface LaunchdPlistOptions {\n /** Set for a LaunchDaemon: launchd would otherwise run the panel as root. */\n userName?: string | null\n}\n\nexport function launchdPlist(spec: BootSpec, options: LaunchdPlistOptions = {}): string {\n validate(spec)\n const label = launchdLabel(spec)\n // XML escaping would keep a hostile name from breaking the plist, but `UserName`\n // also has to be a name launchd can resolve \u2014 so it is validated, not escaped.\n const named = options.userName?.trim()\n const userName = named === undefined || named.length === 0 ? undefined : assertUserName(named)\n const out = path.posix.join(spec.logDir, `${label}.out.log`)\n const err = path.posix.join(spec.logDir, `${label}.err.log`)\n const envKeys = Object.keys(spec.env)\n const lines = [\n '<?xml version=\"1.0\" encoding=\"UTF-8\"?>',\n '<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">',\n '<plist version=\"1.0\">',\n '<dict>',\n '\\t<key>Label</key>',\n `\\t<string>${xmlEscape(label)}</string>`,\n ...(userName === undefined || userName.length === 0\n ? []\n : ['\\t<key>UserName</key>', `\\t<string>${xmlEscape(userName)}</string>`]),\n '\\t<key>ProgramArguments</key>',\n '\\t<array>',\n ...[spec.command, ...spec.args].map(arg => `\\t\\t<string>${xmlEscape(arg)}</string>`),\n '\\t</array>',\n '\\t<key>RunAtLoad</key>',\n '\\t<true/>',\n '\\t<key>KeepAlive</key>',\n '\\t<dict>',\n '\\t\\t<key>SuccessfulExit</key>',\n '\\t\\t<false/>',\n '\\t</dict>',\n '\\t<key>ThrottleInterval</key>',\n '\\t<integer>10</integer>',\n '\\t<key>WorkingDirectory</key>',\n `\\t<string>${xmlEscape(spec.cwd)}</string>`,\n ...(envKeys.length\n ? [\n '\\t<key>EnvironmentVariables</key>',\n '\\t<dict>',\n ...envKeys.flatMap(key => [`\\t\\t<key>${xmlEscape(key)}</key>`, `\\t\\t<string>${xmlEscape(spec.env[key] ?? '')}</string>`]),\n '\\t</dict>',\n ]\n : []),\n '\\t<key>StandardOutPath</key>',\n `\\t<string>${xmlEscape(out)}</string>`,\n '\\t<key>StandardErrorPath</key>',\n `\\t<string>${xmlEscape(err)}</string>`,\n `\\t<!-- Managed by ${assertXmlCommentSafe(spec.marker)} -->`,\n '</dict>',\n '</plist>',\n '',\n ]\n return lines.join('\\n')\n}\n\nfunction codeOf(result: { code: number | null, error?: string | null }): number | null {\n return result.error ? null : result.code\n}\n\nfunction problem(run: { code: number | null, error?: string | null, stdout: string, stderr: string }, command: string): string | null {\n const code = codeOf(run)\n if (code === 0)\n return null\n if (code === null)\n return run.error ? `${command}: ${run.error}` : `${command}: no exit code`\n return `${command} exited ${code}: ${(run.stderr || run.stdout).trim() || '(no output)'}`\n}\n\ntype LaunchdMode = 'agent' | 'daemon'\n\n/** Best effort: `/Library/LaunchDaemons` exists on macOS, and only the privileged write can create it. */\nfunction ensureDirQuiet(dir: string): void {\n try {\n fs.mkdirSync(dir, { recursive: true })\n }\n catch {\n // the privileged install reports the real failure\n }\n}\n\nfunction createLaunchdProvider(ctx: BootProviderContext, mode: LaunchdMode): BootProvider {\n const mechanism = mode === 'agent' ? 'launchd-agent' as const : 'launchd-daemon' as const\n const pathOf = (spec: BootSpec): string => mode === 'agent'\n ? posixJoin(ctx.home, 'Library', 'LaunchAgents', `${launchdLabel(spec)}.plist`)\n : posixJoin('/Library', 'LaunchDaemons', `${launchdLabel(spec)}.plist`)\n\n /** `system` for the daemon; `gui/$UID`, else `user/$UID` for the agent. */\n const domainOf = async (): Promise<string | null> => {\n if (mode === 'daemon')\n return 'system'\n const uid = uidOf(ctx)\n if (!uid)\n return null\n const gui = `gui/${uid}`\n if (codeOf(await ctx.run('launchctl', ['print', gui])) === 0)\n return gui\n const user = `user/${uid}`\n if (codeOf(await ctx.run('launchctl', ['print', user])) === 0)\n return user\n return null\n }\n\n const privileged = async (): Promise<boolean> =>\n mode === 'agent' ? true : ctx.isRoot || (await ctx.sudo())\n\n /** Root runs launchctl directly; everything else goes through `sudo -n`. */\n const runPrivileged = async (program: string, args: string[]) =>\n mode === 'agent' || ctx.isRoot ? await ctx.run(program, args) : await ctx.run('sudo', ['-n', program, ...args])\n\n /** The sudo form of what is left to do when this process cannot write the file itself. */\n const commands = (file: string, label: string): string[] => mode === 'agent'\n ? []\n : [\n shellCommand('sudo', ['launchctl', 'bootstrap', 'system', file]),\n shellCommand('sudo', ['launchctl', 'enable', `system/${label}`]),\n ]\n\n return {\n mechanism,\n\n async detect(): Promise<BootCandidate> {\n if (ctx.platform !== 'darwin')\n return { mechanism, available: false, bootCapable: false, privileged: false, reason: 'launchd is macOS-only' }\n if (mode === 'agent') {\n const domain = await domainOf()\n return {\n mechanism,\n // A plist in ~/Library/LaunchAgents is loaded at login whether or not\n // this process can reach launchd right now, so an unreachable domain\n // is a diagnostic, not a reason to refuse.\n available: true,\n bootCapable: false,\n privileged: true,\n reason: domain === null\n ? `a LaunchAgent loads at login; launchd's domain is not reachable from this process for uid ${uidOf(ctx) ?? '?'} right now, so it is loaded at the next login`\n : `launchd ${domain} domain is reachable; a LaunchAgent loads at login, not at boot`,\n }\n }\n const canElevate = ctx.isRoot || (await ctx.sudo())\n return {\n mechanism,\n // Always selectable: without root the plugin stages the plist and hands\n // over the three sudo commands, which is the only way to start before\n // login on a Mac that has no passwordless sudo.\n available: true,\n bootCapable: true,\n privileged: canElevate,\n reason: canElevate\n ? 'this process can write /Library/LaunchDaemons, so a LaunchDaemon starts at boot'\n : 'a LaunchDaemon starts at boot but needs root here, so the plugin stages the plist and shows the sudo commands',\n }\n },\n\n async status(spec: BootSpec): Promise<BootProviderStatus> {\n try {\n const file = pathOf(spec)\n const label = launchdLabel(spec)\n const own = inspectOwned(file, assertMarker(spec.marker))\n if (own.exists && !own.owned)\n return { state: 'not-installed', unitPath: file, detail: own.reason ?? 'foreign file', commands: [] }\n const domain = await domainOf()\n if (!own.owned)\n return { state: 'not-installed', unitPath: file, detail: `no plist at ${file}`, commands: [] }\n if (domain === null)\n return { state: 'enabled-running', unitPath: file, detail: `${file} is present; launchd has no reachable domain for uid ${uidOf(ctx) ?? '?'}, so it loads at the next login`, commands: [] }\n const probe = await ctx.run('launchctl', ['print', `${domain}/${label}`])\n const loaded = codeOf(probe) === 0\n const state: BootState = bootState(true, true, false)\n return {\n state,\n unitPath: file,\n detail: `${file} is present; ${loaded ? `loaded in ${domain}` : `not loaded right now (RunAtLoad loads it at the next login)`}`,\n commands: [],\n }\n }\n catch (error) {\n return { state: 'not-installed', unitPath: null, detail: errorMessage(error), commands: [] }\n }\n },\n\n async install(spec: BootSpec): Promise<BootActionResult> {\n try {\n validate(spec)\n const file = pathOf(spec)\n const label = launchdLabel(spec)\n // A LaunchDaemon is the same hazard as a system unit: `UserName` missing\n // means launchd runs it as root, and `$USER` is root whenever the panel\n // was started from a root shell. So the account comes from the login that\n // elevated (or this process's own euid), never from the environment, and\n // root is refused rather than written as if it were a choice.\n const content = launchdPlist(spec, mode === 'daemon' ? { userName: bootUserName(ctx) } : {})\n const own = inspectOwned(file, spec.marker)\n if (own.exists && !own.owned)\n return failed(own.reason ?? 'foreign file')\n const staged = posixJoin(tempDir(ctx), `${label}.plist`)\n if (!(await privileged())) {\n // The command below installs this exact file, so it has to exist.\n ensureDirQuiet(path.posix.dirname(staged))\n fs.writeFileSync(staged, content, { mode: 0o644 })\n return failed('writing /Library/LaunchDaemons needs root; the plist is staged for you to install', {\n needsPrivilege: true,\n commands: [\n shellCommand('sudo', ['install', '-m', '0644', staged, file]),\n shellCommand('sudo', ['launchctl', 'bootstrap', 'system', file]),\n shellCommand('sudo', ['launchctl', 'enable', `system/${label}`]),\n ],\n })\n }\n\n const domain = await domainOf()\n\n ensureDirQuiet(path.posix.dirname(file))\n ensureDirQuiet(spec.logDir)\n\n const loaded = domain !== null && codeOf(await ctx.run('launchctl', ['print', `${domain}/${label}`])) === 0\n const before = { text: own.text, exists: own.exists }\n\n let changed = false\n if (mode === 'agent' || ctx.isRoot) {\n const write = writeOwned(file, spec.marker, content, 0o644)\n if (write.refusal)\n return failed(write.refusal)\n changed = write.changed\n }\n else {\n ensureDirQuiet(path.dirname(staged))\n fs.writeFileSync(staged, content, { mode: 0o644 })\n changed = !before.exists || before.text !== content\n if (!changed) {\n fs.rmSync(staged, { force: true })\n }\n else {\n const installCommand = shellCommand('sudo', ['install', '-m', '0644', staged, file])\n const install = await runPrivileged('install', ['-m', '0644', staged, file])\n if (codeOf(install) !== 0)\n return failed(problem(install, installCommand) ?? 'launchctl failed', { needsPrivilege: true, commands: [installCommand] })\n fs.rmSync(staged, { force: true })\n }\n }\n\n const lint = await runPrivileged('plutil', ['-lint', file])\n if (codeOf(lint) !== 0)\n return failed(`plutil -lint rejected the generated plist: ${(lint.stderr || lint.stdout).trim() || 'no output'}`, { changed, commands: commands(file, label) })\n\n if (domain === null) {\n return {\n ok: true,\n changed,\n detail: `${file} is in place; launchd's domain is not reachable from this process, so it loads at the next login`,\n commands: [],\n needsPrivilege: false,\n }\n }\n\n if (loaded && changed) {\n const bootout = await runPrivileged('launchctl', ['bootout', `${domain}/${label}`])\n const bootoutCode = codeOf(bootout)\n if (bootoutCode !== 0 && bootoutCode !== LAUNCHCTL_NO_SUCH_PROCESS && bootoutCode !== LAUNCHCTL_NOT_LOADED)\n return failed(problem(bootout, shellCommand('launchctl', ['bootout', `${domain}/${label}`])) ?? 'launchctl failed', { changed, commands: commands(file, label) })\n }\n\n if (changed || !loaded) {\n const boot = await runPrivileged('launchctl', ['bootstrap', domain, file])\n const bootCode = codeOf(boot)\n if (bootCode !== 0 && bootCode !== LAUNCHCTL_ALREADY_LOADED)\n return failed(problem(boot, shellCommand('launchctl', ['bootstrap', domain, file])) ?? 'launchctl failed', { changed, commands: commands(file, label) })\n if (bootCode === 0)\n changed = true\n const enable = await runPrivileged('launchctl', ['enable', `${domain}/${label}`])\n if (codeOf(enable) !== 0)\n return failed(problem(enable, shellCommand('launchctl', ['enable', `${domain}/${label}`])) ?? 'launchctl failed', { changed, commands: commands(file, label) })\n }\n\n const verify = await runPrivileged('launchctl', ['print', `${domain}/${label}`])\n if (codeOf(verify) !== 0)\n return failed(`launchctl print ${domain}/${label} did not confirm the job (exit ${String(codeOf(verify))})`, { changed, commands: commands(file, label) })\n\n return {\n ok: true,\n changed,\n detail: `${file} installed and loaded in ${domain}`,\n commands: [],\n needsPrivilege: mode === 'daemon' && !ctx.isRoot,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n async uninstall(spec: BootSpec): Promise<BootActionResult> {\n try {\n const file = pathOf(spec)\n const label = launchdLabel(spec)\n const own = inspectOwned(file, spec.marker)\n if (own.exists && !own.owned)\n return failed(own.reason ?? 'foreign file')\n if (mode === 'daemon' && !ctx.isRoot && !(await ctx.sudo())) {\n if (!own.exists)\n return { ok: true, changed: false, detail: `no plist at ${file} and no privilege to change launchd; nothing to remove`, commands: [], needsPrivilege: false }\n return failed('removing a LaunchDaemon needs root', {\n needsPrivilege: true,\n commands: [\n shellCommand('sudo', ['launchctl', 'bootout', `system/${label}`]),\n shellCommand('sudo', ['rm', '-f', file]),\n ],\n })\n }\n\n const domain = await domainOf()\n // A job under this label may belong to a plist we did not write; never\n // boot out a job whose own file is not ours.\n if (!own.exists && !own.owned)\n return failed(`no plist of ours at ${file}; refusing to unload a job this plugin did not install`)\n let changed = false\n if (domain !== null) {\n const bootout = await runPrivileged('launchctl', ['bootout', `${domain}/${label}`])\n const bootoutCode = codeOf(bootout)\n if (bootoutCode === 0)\n changed = true\n else if (bootoutCode !== LAUNCHCTL_NO_SUCH_PROCESS && bootoutCode !== LAUNCHCTL_NOT_LOADED)\n return failed(problem(bootout, shellCommand('launchctl', ['bootout', `${domain}/${label}`])) ?? 'launchctl failed', { commands: commands(file, label) })\n }\n\n if (own.exists) {\n if (mode === 'agent' || ctx.isRoot) {\n const remove = removeOwned(file, spec.marker)\n if (remove.refusal)\n return failed(remove.refusal)\n if (remove.removed)\n changed = true\n }\n else {\n // Re-prove ownership before the privileged delete: the check above\n // ran as the invoking user, which may not be able to read the file.\n if (!own.owned)\n return failed(own.reason ?? 'foreign file')\n const rmCommand = shellCommand('sudo', ['rm', '-f', file])\n const rm = await runPrivileged('rm', ['-f', file])\n if (codeOf(rm) !== 0)\n return failed(problem(rm, rmCommand) ?? 'launchctl failed', { needsPrivilege: true, commands: [rmCommand] })\n changed = true\n }\n }\n\n if (domain !== null) {\n const verify = await runPrivileged('launchctl', ['print', `${domain}/${label}`])\n if (codeOf(verify) === 0)\n return failed(`${label} is still loaded in ${domain} after bootout`, { changed })\n }\n\n return {\n ok: true,\n changed,\n detail: own.exists ? `unloaded and removed ${file}` : `no plist at ${file} (already gone)`,\n commands: [],\n needsPrivilege: mode === 'daemon' && !ctx.isRoot,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n /**\n * A LaunchAgent that was written but never booted is exactly the state an\n * install leaves behind, and nothing else can start it: `kickstart -k` needs\n * the label loaded first, so loading it is part of the start.\n */\n async activate(spec: BootSpec): Promise<BootStart | null> {\n const file = pathOf(spec)\n const label = launchdLabel(spec)\n const domain = await domainOf()\n if (domain === null)\n return null\n const job = `${domain}/${label}`\n if (mode === 'daemon') {\n return {\n commands: [\n ['sudo', '-n', 'launchctl', 'bootstrap', 'system', file],\n ['sudo', '-n', 'launchctl', 'kickstart', '-k', job],\n ],\n requires: { commands: [], files: [file] },\n display: [\n shellCommand('sudo', ['launchctl', 'bootstrap', 'system', file]),\n shellCommand('sudo', ['launchctl', 'kickstart', '-k', job]),\n ],\n }\n }\n const loaded = codeOf(await ctx.run('launchctl', ['print', job])) === 0\n return {\n commands: [\n ...(loaded ? [] : [['launchctl', 'bootstrap', domain, file]]),\n ['launchctl', 'kickstart', '-k', job],\n ],\n requires: { commands: [], files: [file] },\n display: [\n ...(loaded ? [] : [shellCommand('launchctl', ['bootstrap', domain, file])]),\n shellCommand('launchctl', ['kickstart', '-k', job]),\n ],\n }\n },\n\n /** `KeepAlive` would resurrect the panel, so the job itself has to stop it. */\n async stopCommands(spec: BootSpec): Promise<string[][]> {\n const label = launchdLabel(spec)\n const domain = await domainOf()\n if (domain === null)\n return []\n const job = `${domain}/${label}`\n return mode === 'daemon'\n ? [['sudo', '-n', 'launchctl', 'kill', 'SIGTERM', job]]\n : [['launchctl', 'kill', 'SIGTERM', job]]\n },\n\n /**\n * launchd has no \"unload but leave running\": `bootout` always stops the job.\n * That is why retirement is handed to the caller instead of run during the\n * install \u2014 here it runs once the panel is already down, and the entry being\n * started is a different mechanism's, so stopping this job is harmless.\n */\n async retireCommands(spec: BootSpec): Promise<BootRetirement> {\n const file = pathOf(spec)\n const label = launchdLabel(spec)\n const domain = await domainOf()\n const bootout = mode === 'daemon'\n ? ['sudo', '-n', 'launchctl', 'bootout', `system/${label}`]\n : domain === null ? null : ['launchctl', 'bootout', `${domain}/${label}`]\n const remove = mode === 'daemon' ? ['sudo', '-n', 'rm', '-f', file] : ['rm', '-f', file]\n return {\n commands: [...(bootout === null ? [] : [bootout]), remove],\n display: [\n ...(bootout === null ? [] : [shellCommand('sudo', ['launchctl', 'bootout', `system/${label}`])]),\n shellCommand('sudo', ['rm', '-f', file]),\n ],\n }\n },\n }\n}\n\nexport function createLaunchdAgentProvider(ctx: BootProviderContext): BootProvider {\n return createLaunchdProvider(ctx, 'agent')\n}\n\nexport function createLaunchdDaemonProvider(ctx: BootProviderContext): BootProvider {\n return createLaunchdProvider(ctx, 'daemon')\n}\n", "/**\n * freedesktop XDG autostart: `~/.config/autostart/<unitName>.desktop`.\n * This runs at login, never at boot, and needs no privilege. `Exec=` is not a\n * shell, so every word is quoted for the desktop entry parser and `$`/`%` are\n * escaped rather than expanded.\n */\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport type { BootCandidate, BootState } from '../shared/contracts.js'\nimport type { BootActionResult, BootProvider, BootProviderContext, BootProviderStatus, BootRetirement, BootSpec } from './types.js'\nimport { assertAbsolute, assertArg, assertEnvKey, assertLabel, assertMarker, assertUnitName, desktopExec, shellCommand } from './escape.js'\nimport { bootState, configHome, errorMessage, failed, inspectOwned, posixJoin, removeOwned, writeOwned } from './common.js'\n\nfunction validate(spec: BootSpec): void {\n assertUnitName(spec.unitName)\n assertMarker(spec.marker)\n assertLabel(spec.label)\n assertAbsolute(spec.command, 'spec.command')\n assertAbsolute(spec.cwd, 'spec.cwd')\n for (const arg of spec.args)\n assertArg(arg)\n for (const key of Object.keys(spec.env))\n assertEnvKey(key)\n}\n\nexport function xdgDesktopEntry(spec: BootSpec): string {\n validate(spec)\n return `${[\n '[Desktop Entry]',\n 'Type=Application',\n `Name=${spec.label}`,\n `Comment=home-hosted daemon, managed by ${spec.marker}`,\n `Exec=${desktopExec(spec.command, spec.args)}`,\n `Path=${spec.cwd}`,\n 'Terminal=false',\n 'X-GNOME-Autostart-enabled=true',\n `X-HomeHosted-Marker=${spec.marker}`,\n '',\n ].join('\\n')}`\n}\n\nfunction desktopFlag(text: string, key: string): string | null {\n for (const line of text.split('\\n')) {\n const trimmed = line.trim()\n if (trimmed.startsWith(`${key}=`))\n return trimmed.slice(key.length + 1).trim()\n }\n return null\n}\n\nexport function createXdgAutostartProvider(ctx: BootProviderContext): BootProvider {\n const mechanism = 'xdg-autostart' as const\n const unitOf = (spec: BootSpec): string => `${assertUnitName(spec.unitName)}.desktop`\n const pathOf = (spec: BootSpec): string => posixJoin(configHome(ctx), 'autostart', unitOf(spec))\n\n return {\n mechanism,\n\n async detect(): Promise<BootCandidate> {\n if (ctx.platform !== 'linux')\n return { mechanism, available: false, bootCapable: false, privileged: false, reason: 'XDG autostart is Linux-only' }\n return {\n mechanism,\n available: true,\n bootCapable: false,\n privileged: true,\n reason: `a .desktop entry under ${posixJoin(configHome(ctx), 'autostart')} needs no privilege, but it starts at login rather than at boot`,\n }\n },\n\n async status(spec: BootSpec): Promise<BootProviderStatus> {\n try {\n const file = pathOf(spec)\n const own = inspectOwned(file, assertMarker(spec.marker))\n if (own.exists && !own.owned)\n return { state: 'not-installed', unitPath: file, detail: own.reason ?? 'foreign file', commands: [] }\n if (!own.owned || own.text === null)\n return { state: 'not-installed', unitPath: file, detail: `no autostart entry at ${file}`, commands: [] }\n const enabled = desktopFlag(own.text, 'X-GNOME-Autostart-enabled') !== 'false'\n && desktopFlag(own.text, 'Hidden') !== 'true'\n const state: BootState = bootState(true, enabled, false)\n return {\n state,\n unitPath: file,\n detail: `${file} is present; ${enabled ? 'it starts at login' : 'it is disabled'} (login scope, not boot)`,\n commands: [],\n }\n }\n catch (error) {\n return { state: 'not-installed', unitPath: null, detail: errorMessage(error), commands: [] }\n }\n },\n\n async install(spec: BootSpec): Promise<BootActionResult> {\n try {\n validate(spec)\n const file = pathOf(spec)\n fs.mkdirSync(path.dirname(file), { recursive: true })\n const write = writeOwned(file, spec.marker, xdgDesktopEntry(spec), 0o644)\n if (write.refusal)\n return failed(write.refusal)\n\n const text = inspectOwned(file, spec.marker).text\n const enabled = text !== null\n && desktopFlag(text, 'X-GNOME-Autostart-enabled') !== 'false'\n && desktopFlag(text, 'Hidden') !== 'true'\n if (!enabled)\n return failed(`${file} was written but does not read back as enabled`, { changed: write.changed })\n\n return {\n ok: true,\n changed: write.changed,\n detail: write.changed\n ? `${file} installed; it starts at login (not at boot)`\n : `${file} was already installed and enabled`,\n commands: [],\n needsPrivilege: false,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n async uninstall(spec: BootSpec): Promise<BootActionResult> {\n try {\n const file = pathOf(spec)\n const own = inspectOwned(file, spec.marker)\n if (own.exists && !own.owned)\n return failed(own.reason ?? 'foreign file')\n const remove = removeOwned(file, spec.marker)\n if (remove.refusal)\n return failed(remove.refusal)\n return {\n ok: true,\n changed: remove.removed,\n detail: remove.removed ? `removed ${file}` : `${file} was already gone`,\n commands: [],\n needsPrivilege: false,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n /**\n * A `.desktop` entry has no launcher of its own: the desktop session reads\n * the file at login. Nothing here can start the panel, and claiming a start\n * command would be a fiction, so the caller falls back to the CLI.\n */\n async activate(): Promise<null> {\n return null\n },\n\n /** A file the session reads at login: deleting it stops nothing. */\n async retireCommands(spec: BootSpec): Promise<BootRetirement> {\n const file = pathOf(spec)\n return { commands: [['rm', '-f', file]], display: [shellCommand('rm', ['-f', file])] }\n },\n }\n}\n", "/**\n * Windows: the per-user `Run` key (login scope, no admin) and a scheduled task\n * (admin). `sc.exe` is never used \u2014 a plain Node process has no service\n * dispatcher and would fail with error 1053.\n *\n * The `Run` key starts *every* value it holds, so the ownership marker cannot\n * live beside the command there; it lives under this plugin's own key\n * (`HKCU\\Software\\home-hosted`), and a `Run` value is only touched when that\n * marker proves we wrote it.\n */\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport type { BootCandidate, BootState } from '../shared/contracts.js'\nimport type { BootActionResult, BootProvider, BootProviderContext, BootProviderStatus, BootRetirement, BootSpec, BootStart } from './types.js'\nimport {\n assertAbsolute,\n assertArg,\n assertEnvKey,\n assertLabel,\n assertMarker,\n assertRegistryValueName,\n assertUnitName,\n batchCommandLine,\n powershellLiteral,\n windowsArg,\n windowsCommandLine,\n windowsDisplayCommand,\n xmlEscape,\n xmlUnescape,\n} from './escape.js'\nimport { bootState, errorMessage, failed, inspectOwned, localAppData, removeOwned, tempDir, writeOwned } from './common.js'\n\nexport const RUN_KEY = 'HKCU\\\\Software\\\\Microsoft\\\\Windows\\\\CurrentVersion\\\\Run'\nexport const MARKER_KEY = 'HKCU\\\\Software\\\\home-hosted'\nconst RUN_VALUE_LIMIT = 260\n\nfunction validate(spec: BootSpec): void {\n assertUnitName(spec.unitName)\n assertMarker(spec.marker)\n assertLabel(spec.label)\n assertAbsolute(spec.command, 'spec.command')\n assertAbsolute(spec.cwd, 'spec.cwd')\n for (const arg of spec.args)\n assertArg(arg)\n for (const key of Object.keys(spec.env))\n assertEnvKey(key)\n}\n\nfunction codeOf(result: { code: number | null, error?: string | null }): number | null {\n return result.error ? null : result.code\n}\n\nfunction problem(run: { code: number | null, error?: string | null, stdout: string, stderr: string }, command: string): string | null {\n const code = codeOf(run)\n if (code === 0)\n return null\n if (code === null)\n return run.error ? `${command}: ${run.error}` : `${command}: no exit code`\n return `${command} exited ${code}: ${(run.stderr || run.stdout).trim() || '(no output)'}`\n}\n\ninterface RegValue {\n exists: boolean\n data: string | null\n}\n\nasync function queryReg(ctx: BootProviderContext, key: string, name: string): Promise<RegValue> {\n const res = await ctx.run('reg.exe', ['query', key, '/v', name])\n if (res.error || res.code !== 0)\n return { exists: false, data: null }\n for (const line of res.stdout.split(/\\r?\\n/)) {\n const match = /^\\s{2,}(.+?)\\s{2,}(REG_[A-Z_]+)\\s{2,}(.*)$/.exec(line)\n if (match && match[1]?.trim() === name)\n return { exists: true, data: (match[3] ?? '').trim() }\n }\n return { exists: false, data: null }\n}\n\nexport interface WindowsRunPayload {\n /** The exact `Run` value data. */\n data: string\n /** Set when the command line exceeded the `Run` value limit and a `.cmd` wrapper is needed. */\n wrapperPath: string | null\n wrapperContent: string | null\n}\n\nexport function windowsRunPayload(ctx: BootProviderContext, spec: BootSpec): WindowsRunPayload {\n const direct = windowsCommandLine(spec.command, spec.args)\n if (direct.length <= RUN_VALUE_LIMIT)\n return { data: direct, wrapperPath: null, wrapperContent: null }\n const wrapperPath = path.join(localAppData(ctx), 'home-hosted', `${spec.unitName}.cmd`)\n return {\n data: `cmd.exe /c ${windowsArg(wrapperPath)}`,\n wrapperPath,\n wrapperContent: `@echo off\\r\\nrem ${spec.marker}\\r\\n${batchCommandLine(spec.command, spec.args)}\\r\\n`,\n }\n}\n\nfunction createWindowsRunProviderImpl(ctx: BootProviderContext): BootProvider {\n const mechanism = 'windows-run' as const\n const nameOf = (spec: BootSpec): string => assertRegistryValueName(assertUnitName(spec.unitName))\n\n const addValue = (key: string, name: string, data: string) =>\n ctx.run('reg.exe', ['add', key, '/v', name, '/t', 'REG_SZ', '/d', data, '/f'])\n\n return {\n mechanism,\n\n async detect(): Promise<BootCandidate> {\n if (ctx.platform !== 'win32')\n return { mechanism, available: false, bootCapable: false, privileged: false, reason: 'the Run key is Windows-only' }\n return {\n mechanism,\n available: true,\n bootCapable: false,\n privileged: true,\n reason: `HKCU\\\\\u2026\\\\Run needs no admin, but it starts at login rather than at boot`,\n }\n },\n\n async status(spec: BootSpec): Promise<BootProviderStatus> {\n try {\n const name = nameOf(spec)\n const marker = assertMarker(spec.marker)\n const value = await queryReg(ctx, RUN_KEY, name)\n if (!value.exists)\n return { state: 'not-installed', unitPath: RUN_KEY, detail: `no ${RUN_KEY}\\\\${name} value`, commands: [] }\n const markerValue = await queryReg(ctx, MARKER_KEY, name)\n const owned = markerValue.data?.includes(marker) === true\n if (!owned)\n return { state: 'not-installed', unitPath: RUN_KEY, detail: `${name} exists under ${RUN_KEY} but is not marked as ours; refusing to treat it as this plugin's entry`, commands: [] }\n const state: BootState = bootState(true, true, false)\n return { state, unitPath: RUN_KEY, detail: `${RUN_KEY}\\\\${name} starts at login (not at boot)`, commands: [] }\n }\n catch (error) {\n return { state: 'not-installed', unitPath: RUN_KEY, detail: errorMessage(error), commands: [] }\n }\n },\n\n async install(spec: BootSpec): Promise<BootActionResult> {\n try {\n validate(spec)\n const name = nameOf(spec)\n const marker = assertMarker(spec.marker)\n const payload = windowsRunPayload(ctx, spec)\n const wrapperExists = payload.wrapperPath !== null && fs.existsSync(payload.wrapperPath)\n const wrapperOwn = payload.wrapperPath ? inspectOwned(payload.wrapperPath, marker) : null\n if (wrapperOwn?.exists && !wrapperOwn.owned)\n return failed(wrapperOwn.reason ?? 'foreign wrapper')\n\n const markerValue = await queryReg(ctx, MARKER_KEY, name)\n const owned = markerValue.data?.includes(marker) === true\n const current = await queryReg(ctx, RUN_KEY, name)\n // A value the marker does not prove is ours is never touched and never\n // claimed, even when it already equals what we would write.\n if (current.exists && !owned)\n return failed(`${RUN_KEY}\\\\${name} already exists and was not written by this plugin; refusing to overwrite it`)\n\n const commands: string[] = [\n windowsDisplayCommand('reg.exe', ['add', RUN_KEY, '/v', name, '/t', 'REG_SZ', '/d', payload.data, '/f']),\n ]\n\n let changed = false\n if (payload.wrapperPath && payload.wrapperContent) {\n const write = writeOwned(payload.wrapperPath, marker, payload.wrapperContent, 0o644)\n if (write.refusal)\n return failed(write.refusal)\n changed = write.changed || changed\n }\n\n if (!current.exists || current.data !== payload.data) {\n const add = await addValue(RUN_KEY, name, payload.data)\n const addProblem = problem(add, commands[0] ?? 'reg.exe add')\n if (addProblem)\n return failed(addProblem, { changed, commands })\n changed = true\n }\n if (!owned) {\n const mark = await addValue(MARKER_KEY, name, marker)\n const markProblem = problem(mark, windowsDisplayCommand('reg.exe', ['add', MARKER_KEY, '/v', name, '/t', 'REG_SZ', '/d', marker, '/f']))\n if (markProblem)\n return failed(markProblem, { changed, commands })\n changed = true\n }\n\n const after = await queryReg(ctx, RUN_KEY, name)\n if (after.data !== payload.data)\n return failed(`${RUN_KEY}\\\\${name} does not read back as the value that was written`, { changed, commands })\n\n // A wrapper we no longer need is cleaned up so the Run value converges on the direct form.\n if (!payload.wrapperPath && wrapperExists && wrapperOwn?.owned) {\n removeOwned(path.join(localAppData(ctx), 'home-hosted', `${spec.unitName}.cmd`), marker)\n }\n\n return {\n ok: true,\n changed,\n detail: changed ? `${RUN_KEY}\\\\${name} installed; it starts at login (not at boot)` : `${RUN_KEY}\\\\${name} was already installed`,\n commands: [],\n needsPrivilege: false,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n async uninstall(spec: BootSpec): Promise<BootActionResult> {\n try {\n const name = nameOf(spec)\n const marker = assertMarker(spec.marker)\n const current = await queryReg(ctx, RUN_KEY, name)\n const markerValue = await queryReg(ctx, MARKER_KEY, name)\n const owned = markerValue.data?.includes(marker) === true\n if (current.exists && !owned) {\n const payload = windowsRunPayload(ctx, spec)\n const wrapperOwned = payload.wrapperPath !== null ? inspectOwned(payload.wrapperPath, marker).owned : false\n if (current.data !== payload.data && !wrapperOwned)\n return failed(`${RUN_KEY}\\\\${name} is not marked as ours; refusing to delete a foreign Run value`)\n }\n\n let changed = false\n if (current.exists) {\n const del = await ctx.run('reg.exe', ['delete', RUN_KEY, '/v', name, '/f'])\n const delCode = codeOf(del)\n if (delCode !== 0 && delCode !== 1)\n return failed(problem(del, windowsDisplayCommand('reg.exe', ['delete', RUN_KEY, '/v', name, '/f'])) ?? 'reg.exe delete failed')\n changed = delCode === 0\n }\n if (owned) {\n await ctx.run('reg.exe', ['delete', MARKER_KEY, '/v', name, '/f'])\n changed = true\n }\n\n const payload = windowsRunPayload(ctx, spec)\n if (payload.wrapperPath) {\n const remove = removeOwned(payload.wrapperPath, marker)\n if (remove.refusal)\n return failed(remove.refusal)\n changed = remove.removed || changed\n }\n const stale = path.join(localAppData(ctx), 'home-hosted', `${spec.unitName}.cmd`)\n const staleRemove = removeOwned(stale, marker)\n if (staleRemove.refusal)\n return failed(staleRemove.refusal)\n changed = staleRemove.removed || changed\n\n const after = await queryReg(ctx, RUN_KEY, name)\n if (after.exists)\n return failed(`${RUN_KEY}\\\\${name} still exists after removal`, { changed })\n\n return {\n ok: true,\n changed,\n detail: changed ? `removed ${RUN_KEY}\\\\${name}` : `${RUN_KEY}\\\\${name} was already gone`,\n commands: [],\n needsPrivilege: false,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n /**\n * The `Run` key is read by the shell at logon; there is no way to make it\n * start something now. Reporting none is what sends the caller back to the\n * CLI, which is the only honest way to bring the panel up here.\n */\n async activate(): Promise<null> {\n return null\n },\n\n /** Deleting the value and its marker stops nothing that is already running. */\n async retireCommands(spec: BootSpec): Promise<BootRetirement> {\n const name = nameOf(spec)\n return {\n commands: [\n ['reg.exe', 'delete', RUN_KEY, '/v', name, '/f'],\n ['reg.exe', 'delete', MARKER_KEY, '/v', name, '/f'],\n ],\n display: [\n windowsDisplayCommand('reg.exe', ['delete', RUN_KEY, '/v', name, '/f']),\n windowsDisplayCommand('reg.exe', ['delete', MARKER_KEY, '/v', name, '/f']),\n ],\n }\n },\n }\n}\n\n// ---------------------------------------------------------------------------\n// Scheduled task\n// ---------------------------------------------------------------------------\n\nexport function scheduledTaskXml(spec: BootSpec): string {\n validate(spec)\n const args = spec.args.map(windowsArg).join(' ')\n return `${[\n '<?xml version=\"1.0\" encoding=\"UTF-16\"?>',\n '<Task version=\"1.2\" xmlns=\"http://schemas.microsoft.com/windows/2004/02/mit/task\">',\n '<RegistrationInfo>',\n `<Description>${xmlEscape(spec.marker)}</Description>`,\n '</RegistrationInfo>',\n '<Triggers>',\n '<LogonTrigger>',\n '<Enabled>true</Enabled>',\n '</LogonTrigger>',\n '</Triggers>',\n '<Principals>',\n '<Principal id=\"Author\">',\n '<LogonType>InteractiveToken</LogonType>',\n '<RunLevel>LeastPrivilege</RunLevel>',\n '</Principal>',\n '</Principals>',\n '<Settings>',\n '<MultipleInstancesPolicy>IgnoreNew</MultipleInstancesPolicy>',\n '<DisallowStartIfOnBatteries>false</DisallowStartIfOnBatteries>',\n '<StopIfGoingOnBatteries>false</StopIfGoingOnBatteries>',\n '<StartWhenAvailable>true</StartWhenAvailable>',\n '<RestartOnFailure>',\n '<Interval>PT1M</Interval>',\n '<Count>3</Count>',\n '</RestartOnFailure>',\n '<ExecutionTimeLimit>PT0S</ExecutionTimeLimit>',\n '</Settings>',\n '<Actions Context=\"Author\">',\n '<Exec>',\n `<Command>${xmlEscape(spec.command)}</Command>`,\n `<Arguments>${xmlEscape(args)}</Arguments>`,\n `<WorkingDirectory>${xmlEscape(spec.cwd)}</WorkingDirectory>`,\n '</Exec>',\n '</Actions>',\n '</Task>',\n '',\n ].join('\\r\\n')}`\n}\n\nexport function registerTaskScript(spec: BootSpec): string {\n validate(spec)\n const args = spec.args.map(windowsArg).join(' ')\n return [\n `$action = New-ScheduledTaskAction -Execute ${powershellLiteral(spec.command)} -Argument ${powershellLiteral(args)} -WorkingDirectory ${powershellLiteral(spec.cwd)}`,\n '$trigger = New-ScheduledTaskTrigger -AtLogOn',\n '$settings = New-ScheduledTaskSettingsSet -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1) -StartWhenAvailable -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries',\n `Register-ScheduledTask -TaskName ${powershellLiteral(spec.unitName)} -Action $action -Trigger $trigger -Settings $settings -Description ${powershellLiteral(spec.marker)} -Force | Out-Null`,\n ].join('; ')\n}\n\nexport function unregisterTaskScript(name: string): string {\n return `Unregister-ScheduledTask -TaskName ${powershellLiteral(assertUnitName(name))} -Confirm:$false`\n}\n\nfunction xmlTag(xml: string, tag: string): string | null {\n const match = new RegExp(`<${tag}>([\\\\s\\\\S]*?)</${tag}>`).exec(xml)\n return match?.[1] === undefined ? null : xmlUnescape(match[1].trim())\n}\n\ninterface TaskInfo {\n exists: boolean\n xml: string | null\n marked: boolean\n matches: boolean\n}\n\nexport function createWindowsTaskProvider(ctx: BootProviderContext): BootProvider {\n const mechanism = 'windows-task' as const\n const nameOf = (spec: BootSpec): string => assertUnitName(spec.unitName)\n\n const query = async (name: string): Promise<{ exists: boolean, xml: string | null }> => {\n const res = await ctx.run('schtasks.exe', ['/Query', '/TN', name, '/XML'])\n if (res.error || res.code !== 0)\n return { exists: false, xml: null }\n return { exists: true, xml: res.stdout }\n }\n\n const inspect = async (spec: BootSpec): Promise<TaskInfo> => {\n const found = await query(nameOf(spec))\n if (!found.exists || found.xml === null)\n return { exists: false, xml: null, marked: false, matches: false }\n const marked = xmlUnescape(found.xml).includes(spec.marker)\n const args = spec.args.map(windowsArg).join(' ')\n const matches = marked\n && xmlTag(found.xml, 'Command') === spec.command\n && xmlTag(found.xml, 'Arguments') === args\n && (xmlTag(found.xml, 'WorkingDirectory') ?? spec.cwd) === spec.cwd\n return { exists: true, xml: found.xml, marked, matches }\n }\n\n const hasRegisterCmdlet = async (): Promise<boolean> => {\n const probe = await ctx.run('powershell.exe', ['-NoProfile', '-Command', 'Get-Command Register-ScheduledTask'])\n return probe.error === undefined || probe.error === null\n ? /Register-ScheduledTask/.test(probe.stdout)\n : false\n }\n\n const isElevated = async (): Promise<boolean> => {\n const script = '([Security.Principal.WindowsPrincipal][Security.Principal.WindowsIdentity]::GetCurrent()).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator)'\n const probe = await ctx.run('powershell.exe', ['-NoProfile', '-Command', script])\n return probe.error === undefined || probe.error === null ? /^true$/im.test(probe.stdout.trim()) : false\n }\n\n return {\n mechanism,\n\n async detect(): Promise<BootCandidate> {\n if (ctx.platform !== 'win32')\n return { mechanism, available: false, bootCapable: false, privileged: false, reason: 'scheduled tasks are Windows-only' }\n const elevated = await isElevated()\n return {\n mechanism,\n available: elevated,\n bootCapable: false,\n privileged: elevated,\n reason: elevated\n ? 'administrator rights are available; the task is registered for logon (not for boot)'\n : 'registering a scheduled task needs an elevated process',\n }\n },\n\n async status(spec: BootSpec): Promise<BootProviderStatus> {\n try {\n const name = nameOf(spec)\n const info = await inspect(spec)\n if (!info.exists)\n return { state: 'not-installed', unitPath: null, detail: `no scheduled task named ${name}`, commands: [] }\n if (!info.marked)\n return { state: 'not-installed', unitPath: null, detail: `task ${name} exists but does not carry this plugin's marker; refusing to treat it as this plugin's entry`, commands: [] }\n const state: BootState = bootState(true, true, false)\n return { state, unitPath: null, detail: `scheduled task ${name} is registered (runs at logon, not at boot)`, commands: [] }\n }\n catch (error) {\n return { state: 'not-installed', unitPath: null, detail: errorMessage(error), commands: [] }\n }\n },\n\n async install(spec: BootSpec): Promise<BootActionResult> {\n try {\n validate(spec)\n const name = nameOf(spec)\n const info = await inspect(spec)\n if (info.exists && !info.marked)\n return failed(`scheduled task ${name} already exists and was not written by this plugin; refusing to overwrite it`)\n if (!(await isElevated()))\n return failed('registering a scheduled task needs an elevated process', { needsPrivilege: true })\n\n const elevatedCommands = [\n windowsDisplayCommand('powershell.exe', ['-NoProfile', '-Command', unregisterTaskScript(name)]),\n windowsDisplayCommand('schtasks.exe', ['/Query', '/TN', name, '/XML']),\n ]\n\n if (info.exists && info.matches) {\n const verify = await query(name)\n if (verify.exists)\n return { ok: true, changed: false, detail: `scheduled task ${name} is already registered`, commands: [], needsPrivilege: true }\n }\n\n const cmdlet = await hasRegisterCmdlet()\n if (cmdlet) {\n const script = registerTaskScript(spec)\n const res = await ctx.run('powershell.exe', ['-NoProfile', '-Command', script])\n if (codeOf(res) !== 0)\n return failed(problem(res, windowsDisplayCommand('powershell.exe', ['-NoProfile', '-Command', script])) ?? 'Register-ScheduledTask failed', { needsPrivilege: true, commands: elevatedCommands })\n }\n else {\n const xmlFile = path.join(tempDir(ctx), `${name}.xml`)\n fs.mkdirSync(path.dirname(xmlFile), { recursive: true })\n fs.writeFileSync(xmlFile, Buffer.from(`\\uFEFF${scheduledTaskXml(spec)}`, 'utf16le'))\n const createCommand = windowsDisplayCommand('schtasks.exe', ['/Create', '/TN', name, '/XML', xmlFile, '/F'])\n const create = await ctx.run('schtasks.exe', ['/Create', '/TN', name, '/XML', xmlFile, '/F'])\n if (codeOf(create) !== 0)\n return failed(problem(create, createCommand) ?? 'schtasks /Create failed', { needsPrivilege: true, commands: [createCommand] })\n fs.rmSync(xmlFile, { force: true })\n }\n\n const after = await query(name)\n if (!after.exists)\n return failed(`scheduled task ${name} was not found after registration`, { changed: true, needsPrivilege: true, commands: elevatedCommands })\n\n return {\n ok: true,\n changed: true,\n detail: `scheduled task ${name} registered (runs at logon, not at boot)`,\n commands: [],\n needsPrivilege: true,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n async uninstall(spec: BootSpec): Promise<BootActionResult> {\n try {\n const name = nameOf(spec)\n const info = await inspect(spec)\n if (!info.exists)\n return { ok: true, changed: false, detail: `no scheduled task named ${name}; already gone`, commands: [], needsPrivilege: false }\n if (!info.marked)\n return failed(`scheduled task ${name} is not marked as ours; refusing to delete a foreign task`)\n if (!(await isElevated()))\n return failed('deleting a scheduled task needs an elevated process', { needsPrivilege: true })\n\n const cmdlet = await hasRegisterCmdlet()\n const res = cmdlet\n ? await ctx.run('powershell.exe', ['-NoProfile', '-Command', unregisterTaskScript(name)])\n : await ctx.run('schtasks.exe', ['/Delete', '/TN', name, '/F'])\n const code = codeOf(res)\n if (code !== 0 && code !== 1)\n return failed(problem(res, cmdlet ? 'powershell.exe Unregister-ScheduledTask' : windowsDisplayCommand('schtasks.exe', ['/Delete', '/TN', name, '/F'])) ?? 'unregistering the task failed', { needsPrivilege: true })\n\n const after = await query(name)\n if (after.exists)\n return failed(`scheduled task ${name} still exists after removal`, { changed: true, needsPrivilege: true })\n\n return {\n ok: true,\n changed: true,\n detail: `unregistered scheduled task ${name}`,\n commands: [],\n needsPrivilege: true,\n }\n }\n catch (error) {\n return failed(errorMessage(error))\n }\n },\n\n /**\n * `/Run` starts the registered task now, and the query proves the task is\n * really registered before the panel is touched. A task does not restart a\n * panel it does not own, so the caller stops the one it drove first.\n */\n async activate(spec: BootSpec): Promise<BootStart> {\n const name = nameOf(spec)\n return {\n commands: [['schtasks.exe', '/Run', '/TN', name]],\n requires: { commands: [['schtasks.exe', '/Query', '/TN', name]], files: [] },\n display: [windowsDisplayCommand('schtasks.exe', ['/Run', '/TN', name])],\n }\n },\n\n /** Unregistering a task stops nothing that is already running. */\n async retireCommands(spec: BootSpec): Promise<BootRetirement> {\n const name = nameOf(spec)\n const cmdlet = await hasRegisterCmdlet()\n return {\n commands: [cmdlet\n ? ['powershell.exe', '-NoProfile', '-Command', unregisterTaskScript(name)]\n : ['schtasks.exe', '/Delete', '/TN', name, '/F']],\n display: [windowsDisplayCommand('schtasks.exe', ['/Delete', '/TN', name, '/F'])],\n }\n },\n }\n}\n\nexport function createWindowsRunProvider(ctx: BootProviderContext): BootProvider {\n return createWindowsRunProviderImpl(ctx)\n}\n", "/**\n * The two terminals of the ladder: a container (an OS boot entry would land\n * outside it, so the right answer is the container's own restart policy) and a\n * platform this plugin has no mechanism for.\n */\n\nimport type { BootCandidate } from '../shared/contracts.js'\nimport type { BootActionResult, BootProvider, BootProviderContext, BootProviderStatus, BootSpec } from './types.js'\nimport { readText } from '../util/fsx.js'\nimport { failed, existsOf } from './common.js'\n\nconst RESTART_POLICY = 'use the container restart policy instead: `docker run --restart unless-stopped`, or `restart: unless-stopped` in a compose file'\n\n/** A human-readable description of the container we are inside, or null. */\nexport function detectContainer(ctx: BootProviderContext): string | null {\n if (existsOf(ctx, '/.dockerenv'))\n return 'Docker (/.dockerenv)'\n if (existsOf(ctx, '/run/.containerenv'))\n return 'Podman (/run/.containerenv)'\n if (ctx.env.container?.trim())\n return `container environment (container=${ctx.env.container.trim()})`\n const cgroup = existsOf(ctx, '/proc/1/cgroup') ? readText('/proc/1/cgroup') : null\n if (cgroup) {\n if (cgroup.includes('kubepods'))\n return 'Kubernetes (cgroup kubepods)'\n if (cgroup.includes('docker'))\n return 'Docker (cgroup docker)'\n if (cgroup.includes('lxc'))\n return 'LXC (cgroup lxc)'\n if (cgroup.includes('containerd'))\n return 'containerd (cgroup)'\n }\n return null\n}\n\nexport function createContainerProvider(ctx: BootProviderContext): BootProvider {\n const mechanism = 'container' as const\n return {\n mechanism,\n\n async detect(): Promise<BootCandidate> {\n const container = detectContainer(ctx)\n return {\n mechanism,\n available: container !== null,\n bootCapable: false,\n privileged: false,\n reason: container === null\n ? 'this process does not look like it is inside a container'\n : `this process runs inside ${container}; an OS boot entry would live on the host, not here`,\n }\n },\n\n async status(): Promise<BootProviderStatus> {\n const container = detectContainer(ctx)\n return {\n state: 'unsupported',\n unitPath: null,\n detail: container === null\n ? 'no container detected'\n : `running inside ${container}; ${RESTART_POLICY}`,\n commands: [],\n }\n },\n\n async install(): Promise<BootActionResult> {\n const container = detectContainer(ctx)\n return failed(\n container === null\n ? `no container detected; ${RESTART_POLICY}`\n : `this process runs inside ${container}; an OS boot entry would be installed outside the container \u2014 ${RESTART_POLICY}`,\n )\n },\n\n async uninstall(): Promise<BootActionResult> {\n return failed('nothing to uninstall: this plugin installed no OS boot entry for a container; the container restart policy is what starts it')\n },\n\n async activate(): Promise<null> {\n return null\n },\n }\n}\n\nexport function createUnsupportedProvider(platform: NodeJS.Platform): BootProvider {\n const mechanism = 'unsupported' as const\n const reason = `no boot mechanism is known for ${platform}`\n return {\n mechanism,\n\n async detect(): Promise<BootCandidate> {\n return { mechanism, available: true, bootCapable: false, privileged: false, reason }\n },\n\n async status(_spec: BootSpec): Promise<BootProviderStatus> {\n return { state: 'unsupported', unitPath: null, detail: reason, commands: [] }\n },\n\n async install(_spec: BootSpec): Promise<BootActionResult> {\n return failed(`this plugin has no boot mechanism for ${platform}; start home-hosted from your platform's own service manager`)\n },\n\n async uninstall(_spec: BootSpec): Promise<BootActionResult> {\n return failed(`this plugin has no boot mechanism for ${platform}, so it installed nothing to remove`)\n },\n\n async activate(): Promise<null> {\n return null\n },\n }\n}\n", "/**\n * Writing a panel's files directly, for the case where its API cannot be used\n * (nobody is running, or its token was refused).\n *\n * On home-hosted 0.7 the state is two files, not one:\n *\n * - `.hh/<workspace>/servers.config.json` \u2014 the entries (`$schema`, `meta`, `servers`);\n * - `.hh/settings.json` \u2014 the panel's own `control` block (its port, its bind).\n *\n * When the panel *is* running this path is a fallback: its file watcher treats an\n * external edit as a real change, and a newly added `autostart` entry is started\n * at once. The managed add therefore writes the entry disabled first and only\n * then flips `autostart`, which the watcher sees as a changed definition \u2014\n * applied on that entry's next start, never started now.\n *\n * A pre-0.7 root is refused: starting the panel once relocates it.\n */\nimport fs from 'node:fs'\nimport type { ServerEntry, ServerEntryPatch } from '../shared/contracts.js'\nimport { readJson, writeFileAtomic } from '../util/fsx.js'\nimport { DEFAULT_WORKSPACE, globalSettingsFile, migrationRefusal, serversFile } from './layout.js'\n\nexport interface RawConfig {\n meta?: Record<string, unknown>\n servers?: ServerEntry[]\n [key: string]: unknown\n}\n\nexport interface GlobalSettings {\n meta?: Record<string, unknown>\n control?: Record<string, unknown>\n [key: string]: unknown\n}\n\nexport interface ConfigReadResult {\n raw: RawConfig | null\n exists: boolean\n error: string | null\n}\n\nexport interface SettingsReadResult {\n raw: GlobalSettings | null\n exists: boolean\n error: string | null\n}\n\n/** A write that cannot land where it was asked to: a root still on the old layout. */\nexport class ConfigLayoutError extends Error {\n override name = 'ConfigLayoutError'\n}\n\n/** One workspace's entries. A not-yet-migrated root is reported, never parsed. */\nexport function readConfig(home: string, workspace: string = DEFAULT_WORKSPACE): ConfigReadResult {\n const refusal = migrationRefusal(home)\n if (refusal !== null)\n return { raw: null, exists: false, error: refusal }\n const file = serversFile(home, workspace)\n const raw = readJson<RawConfig>(file)\n if (raw === null) {\n // Distinguish \"no file yet\" from \"unreadable\", because only the first is safe to create.\n const exists = fs.existsSync(file)\n return { raw: null, exists, error: exists ? 'the workspace config file could not be parsed' : null }\n }\n if (!Array.isArray(raw.servers) && raw.servers !== undefined)\n return { raw: null, exists: true, error: 'the workspace config file has a servers field that is not an array' }\n return { raw, exists: true, error: null }\n}\n\n/**\n * Write one workspace's entries back with home-hosted's own `meta` contract,\n * which records the release that wrote the file and the shape it wrote.\n *\n * `schema` is never invented here: a file that declared one keeps it, and one\n * that did not stays without it \u2014 home-hosted reads an absent schema as \"the\n * current one\", so stamping our own idea of the schema would one day fail its\n * own guard (\"this release understands schema N\") and stop the panel booting\n * from a file this plugin only patched.\n */\nexport function writeConfig(home: string, raw: RawConfig, writtenBy = 'dsh-home-hosted', workspace: string = DEFAULT_WORKSPACE): void {\n refuseUnmigrated(home)\n const meta = { ...(raw.meta ?? {}), writtenBy }\n const next: RawConfig = { ...raw, meta }\n writeFileAtomic(serversFile(home, workspace), `${JSON.stringify(next, null, 2)}\\n`)\n}\n\n/** The panel's own settings file, where 0.7 keeps `control` (listener, auth, TLS). */\nexport function readGlobalSettings(home: string): SettingsReadResult {\n const refusal = migrationRefusal(home)\n if (refusal !== null)\n return { raw: null, exists: false, error: refusal }\n const file = globalSettingsFile(home)\n const raw = readJson<GlobalSettings>(file)\n if (raw === null) {\n const exists = fs.existsSync(file)\n return { raw: null, exists, error: exists ? 'the panel settings file could not be parsed' : null }\n }\n if (raw.control !== undefined && (typeof raw.control !== 'object' || raw.control === null || Array.isArray(raw.control)))\n return { raw: null, exists: true, error: 'the panel settings file has a control field that is not an object' }\n return { raw, exists: true, error: null }\n}\n\nexport function writeGlobalSettings(home: string, raw: GlobalSettings, writtenBy = 'dsh-home-hosted'): void {\n refuseUnmigrated(home)\n const meta = { ...(raw.meta ?? {}), writtenBy }\n writeFileAtomic(globalSettingsFile(home), `${JSON.stringify({ ...raw, meta }, null, 2)}\\n`)\n}\n\n/**\n * Patch the panel's `control` block (its port, its bind), preserving every other\n * global key \u2014 `auth`, `tls`, `host` and `backups` live in the same file.\n */\nexport function setControl(home: string, patch: Record<string, unknown>, writtenBy = 'dsh-home-hosted'): void {\n const read = readGlobalSettings(home)\n if (read.error !== null)\n throw new ConfigLayoutError(read.error)\n writeGlobalSettings(home, { ...(read.raw ?? {}), control: { ...(read.raw?.control ?? {}), ...patch } }, writtenBy)\n}\n\nfunction refuseUnmigrated(home: string): void {\n const refusal = migrationRefusal(home)\n if (refusal !== null)\n throw new ConfigLayoutError(refusal)\n}\n\nexport function findEntry(raw: RawConfig, id: string): ServerEntry | null {\n return (raw.servers ?? []).find(entry => entry.id === id) ?? null\n}\n\n/** Insert or replace one entry, preserving every other key in the file. */\nexport function upsertEntry(raw: RawConfig, entry: ServerEntry): RawConfig {\n const servers = [...(raw.servers ?? [])]\n const index = servers.findIndex(candidate => candidate.id === entry.id)\n if (index >= 0)\n servers[index] = entry\n else\n servers.push(entry)\n return { ...raw, servers }\n}\n\nexport function patchEntry(raw: RawConfig, id: string, patch: ServerEntryPatch): RawConfig {\n const servers = (raw.servers ?? []).map((entry) => {\n if (entry.id !== id)\n return entry\n const merged: ServerEntry = { ...entry, ...patch }\n if (patch.stop !== undefined && typeof entry.stop === 'object' && entry.stop !== null)\n merged.stop = { ...entry.stop, ...patch.stop }\n return merged\n })\n return { ...raw, servers }\n}\n\n/** Patch a settings file's `control` block, as a pure value. */\nexport function patchControl(raw: GlobalSettings, patch: Record<string, unknown>): GlobalSettings {\n return { ...raw, control: { ...(raw.control ?? {}), ...patch } }\n}\n\nexport function removeEntry(raw: RawConfig, id: string): RawConfig {\n return { ...raw, servers: (raw.servers ?? []).filter(entry => entry.id !== id) }\n}\n", "/**\n * Where a panel keeps its state on home-hosted 0.7+.\n *\n * Everything lives under `<root>/.hh`: the global files at the top, one directory\n * per workspace. The plugin targets 0.7 and never reads a pre-0.7 root \u2014 but it\n * still *recognises* one (`isLegacyRoot`) so an install is not lost before its\n * first 0.7 start, and so a write can say what to do instead of corrupting it.\n */\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport { DEFAULT_WORKSPACE } from '../shared/contracts.js'\n\nexport { DEFAULT_WORKSPACE }\n\nexport const HH_DIR = '.hh'\n\nexport function hhDir(home: string): string {\n return path.join(home, HH_DIR)\n}\n\nexport function globalSettingsFile(home: string): string {\n return path.join(hhDir(home), 'settings.json')\n}\n\nexport function workspacesFile(home: string): string {\n return path.join(hhDir(home), 'workspaces.json')\n}\n\n/** The panel-wide secrets (password hash, API token hash), 0600. */\nexport function secretsFile(home: string): string {\n return path.join(hhDir(home), '.control-secrets.json')\n}\n\n/** The live daemon's identity: pid, url, token. */\nexport function runFile(home: string): string {\n return path.join(hhDir(home), 'run.json')\n}\n\nexport function workspaceDir(home: string, workspace: string): string {\n return path.join(hhDir(home), workspace)\n}\n\nexport function serversFile(home: string, workspace: string): string {\n return path.join(workspaceDir(home, workspace), 'servers.config.json')\n}\n\nexport function workspaceSettingsFile(home: string, workspace: string): string {\n return path.join(workspaceDir(home, workspace), 'settings.json')\n}\n\nexport function workspaceSecretsFile(home: string, workspace: string): string {\n return path.join(workspaceDir(home, workspace), '.secrets.json')\n}\n\nexport function workspaceLogsDir(home: string, workspace: string): string {\n return path.join(workspaceDir(home, workspace), '.logs')\n}\n\nexport function workspaceStateDir(home: string, workspace: string): string {\n return path.join(workspaceDir(home, workspace), '.state')\n}\n\nexport interface WorkspaceEntry {\n id: string\n label: string\n}\n\nexport interface WorkspacesRead {\n workspaces: WorkspaceEntry[]\n error: string | null\n}\n\nfunction isRecord(value: unknown): value is Record<string, unknown> {\n return typeof value === 'object' && value !== null && !Array.isArray(value)\n}\n\n/** The registry `.hh/workspaces.json` holds; an empty list is \"not started yet\". */\nexport function readWorkspaces(home: string): WorkspacesRead {\n const file = workspacesFile(home)\n if (!fs.existsSync(file))\n return { workspaces: [], error: null }\n let parsed: unknown\n try {\n parsed = JSON.parse(fs.readFileSync(file, 'utf8'))\n }\n catch {\n return { workspaces: [], error: `${path.relative(home, file)} could not be parsed` }\n }\n const list = isRecord(parsed) && Array.isArray(parsed.workspaces) ? parsed.workspaces : null\n if (list === null)\n return { workspaces: [], error: `${path.relative(home, file)} has no workspaces list` }\n const workspaces: WorkspaceEntry[] = []\n for (const entry of list) {\n if (!isRecord(entry) || typeof entry.id !== 'string' || entry.id.length === 0)\n return { workspaces: [], error: `${path.relative(home, file)} has an entry without an id` }\n workspaces.push({ id: entry.id, label: typeof entry.label === 'string' && entry.label.length > 0 ? entry.label : entry.id })\n }\n return { workspaces, error: null }\n}\n\n/** The workspace to act on when the caller names none: `default` when it exists. */\nexport function defaultWorkspace(home: string): string {\n const { workspaces } = readWorkspaces(home)\n return workspaces.find(entry => entry.id === DEFAULT_WORKSPACE)?.id ?? workspaces[0]?.id ?? DEFAULT_WORKSPACE\n}\n\n/** Whether the root has been started on 0.7: it has a registry or panel settings. */\nexport function isHhRoot(home: string): boolean {\n return fs.existsSync(workspacesFile(home)) || fs.existsSync(globalSettingsFile(home))\n}\n\n/** A pre-0.7 root: state at the top level and no `.hh` yet. Detection only. */\nexport function isLegacyRoot(home: string): boolean {\n if (isHhRoot(home))\n return false\n return ['servers.config.json', '.control-secrets.json', 'run.json', '.state']\n .some(name => fs.existsSync(path.join(home, name)))\n}\n\n/**\n * The refusal a write to a not-yet-migrated root gets, or null when it is safe.\n * Starting the panel once on 0.7 relocates the root; `home-hosted migrate` does\n * the same with a report and needs no terminal.\n */\nexport function migrationRefusal(home: string): string | null {\n if (!isLegacyRoot(home))\n return null\n return `${home} still uses the pre-0.7 layout (state at the top level). Start the panel once on home-hosted 0.7 \u2014 it moves everything into .hh \u2014 or run \\`home-hosted migrate --yes --home ${home}\\`.`\n}\n", "/**\n * The server entry that represents the running harness.\n *\n * An existing entry is never rewritten: this plugin only owns `autostart`,\n * `onPortConflict` and `stop.killPortHolders`. A generated entry (only used when\n * there is none) mirrors how this process was actually launched where that is\n * knowable, and otherwise falls back to the `dsh web` app arguments.\n *\n * The generated entry binds loopback only: `dsh` refuses `--host 0.0.0.0`, so a\n * running harness configured to bind the network cannot boot through an entry\n * that repeats its host.\n */\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport type { ServerEntry } from '../shared/contracts.js'\nimport { readJson } from '../util/fsx.js'\nimport { dshHome as resolveDshHome, pluginStateDir } from '../util/paths.js'\nimport type { CliLaunch } from './launch.js'\nimport { resolveShimmedCli, which } from './launch.js'\nimport { compareVersions } from './resolve.js'\nimport { dshCandidatesUnder, dshLauncherPath, dshSearchRoot, readDshLauncherRecord, writeDshLauncher } from './launcher.js'\n\n/**\n * Which profile this process is running.\n *\n * `DSH_PROFILE` alone is not trustworthy: it can be inherited from a parent\n * shell that ran a different profile (that is how an entry once ended up booting\n * `web` under another profile's home). The process's own argv is authoritative,\n * then the profile directory the launcher pointed at, then the env var.\n */\nexport function detectProfile(argv: readonly string[], env: Record<string, string | undefined> = {}): string {\n for (let index = 0; index < argv.length; index += 1) {\n const arg = argv[index]\n if (arg === '--profile' && typeof argv[index + 1] === 'string' && !argv[index + 1]!.startsWith('-'))\n return argv[index + 1]!\n if (arg?.startsWith('--profile='))\n return arg.slice('--profile='.length)\n }\n\n const dir = env.DSH_PROFILE_DIR\n if (typeof dir === 'string' && dir.length > 0) {\n const base = path.basename(dir)\n if (base.length > 0 && base !== '.' && base !== path.sep)\n return base\n }\n\n const app = argv.slice(2).find(candidate => candidate !== undefined && !candidate.startsWith('-'))\n if (app !== undefined && app.length > 0)\n return app\n\n const fromEnv = env.DSH_PROFILE\n return typeof fromEnv === 'string' && fromEnv.length > 0 ? fromEnv : 'web'\n}\n\nexport interface DshFacts {\n id: string\n port: number | null\n profile: string | null\n dshHome: string\n /** The project this row belongs to; defaults to this process's cwd. */\n projectDir?: string | null\n launch: CliLaunch | null\n /**\n * The stable launcher a local/clone entry must boot through, when one exists.\n * Null keeps the older shape: the resolved program and its own argv.\n */\n launcherPath?: string | null\n}\n\nexport interface DshLaunch extends CliLaunch {\n /** The stable launcher written for this launch, or null for `dsh` on PATH. */\n launcherPath: string | null\n}\n\nexport interface ResolveDshLaunchOptions {\n /** The plugin state dir the launcher is written into; defaults to `$DSH_HOME/dsh-home-hosted`. */\n stateDir?: string\n /** The harness home the resolver records as the launcher's search root. */\n dshHome?: string\n /** Test seam: what this process was launched as; defaults to `process.argv[1]`. */\n argv1?: string | null\n /** Test seam: locate a command on PATH. */\n findOnPath?: (command: string) => Promise<string | null>\n /** The project whose own dsh dependency should win; defaults to this process's cwd. */\n projectDir?: string | null\n}\n\n/** The nearest package.json's version, for ordering clone candidates. */\nfunction versionOf(entry: string): string | null {\n let dir = path.dirname(entry)\n for (let hop = 0; hop < 4; hop += 1) {\n const manifest = readJson<{ version?: string }>(path.join(dir, 'package.json'))\n if (typeof manifest?.version === 'string')\n return manifest.version\n dir = path.dirname(dir)\n }\n return null\n}\n\nfunction isEntry(file: string): boolean {\n return /\\.[cm]?js$/.test(file) && fs.existsSync(file) && !fs.statSync(file).isDirectory()\n}\n\n/** The resolved target behind a shim or symlink; the path itself when that fails. */\nfunction realPath(file: string): string {\n try {\n return fs.realpathSync(file)\n }\n catch {\n return file\n }\n}\n\n/**\n * A local clone's built entry, reconstructed from where a clone keeps its build.\n *\n * A clone installs dsh in its own tree, so `argv[1]` names a *build output*\n * (`lib/bin.js` inside `node_modules`, and after a rebuild a different one): the\n * install root is derived from it and every known build location is checked, so a\n * rebuild does not require a new boot entry.\n */\nexport function cloneEntryFrom(argv1: string): string | null {\n if (!isEntry(argv1))\n return null\n const root = path.dirname(path.dirname(argv1))\n for (const candidate of [\n path.join(root, 'lib', 'bin.js'),\n path.join(root, 'lib', 'bin.mjs'),\n path.join(root, 'lib', 'bin.cjs'),\n path.join(root, 'dist', 'bin.js'),\n path.join(root, 'bin', 'dsh.js'),\n path.join(root, 'bin', 'dsh.mjs'),\n ]) {\n if (isEntry(candidate))\n return candidate\n }\n return argv1\n}\n\n/**\n * The entry this process runs, however it was launched.\n *\n * `argv[1]` is directly the built entry for a clone or an installed copy. When\n * it is instead a package-manager shim (`.bin/dsh`), the shim's own recorded\n * target names the real entry, which is the path a boot entry must carry.\n */\nfunction launchedEntry(argv1: string): string | null {\n const real = realPath(argv1)\n const direct = cloneEntryFrom(real)\n if (direct !== null)\n return direct\n const shimmed = resolveShimmedCli(real, process.execPath)\n return shimmed?.cliEntry == null ? null : cloneEntryFrom(shimmed.cliEntry)\n}\n\n/** The best dsh entry installed under a harness home, flat or pnpm. */\nexport function findDshEntry(dshHome: string): string | null {\n const found: string[] = []\n found.push(...dshCandidatesUnder(dshHome))\n for (const name of readdirNames(path.join(dshHome, 'profiles')))\n found.push(...dshCandidatesUnder(path.join(dshHome, 'profiles', name)))\n const withVersion = found.map(entry => ({ entry, version: versionOf(entry) }))\n withVersion.sort((a, b) => compareVersions(b.version ?? '0.0.0', a.version ?? '0.0.0'))\n return withVersion[0]?.entry ?? null\n}\n\nfunction readdirNames(dir: string): string[] {\n try {\n return fs.readdirSync(dir)\n }\n catch {\n return []\n }\n}\n\n/**\n * The dsh a project declares as its own dependency.\n *\n * A project that depends on dsh has already decided which dsh belongs to it, and\n * running the global one there is how a build shares a data directory with a\n * version it was not made for. Resolution stays inside the project: the\n * package-manager shim it wrote, then the package layouts every manager\n * produces, then the pnpm store.\n *\n * Nothing here shells out to `npx`/`pnpm`: a boot entry must not need one of\n * them on PATH, must never fall back to downloading from the registry, and\n * `node_modules` is the contract npm, pnpm, yarn (node-modules linker) and bun\n * all keep.\n */\nexport function projectDshEntry(dir: string, depth = 4): string | null {\n let current = resolveDir(dir)\n for (let hop = 0; hop <= depth; hop += 1) {\n const shim = path.join(current, 'node_modules', '.bin', 'dsh')\n const shimmed = resolveShimmedCli(shim, process.execPath)\n if (shimmed?.cliEntry != null && isEntry(shimmed.cliEntry))\n return shimmed.cliEntry\n const found = dshCandidatesUnder(current)\n .map(entry => ({ entry, version: versionOf(entry) }))\n .sort((a, b) => compareVersions(b.version ?? '0.0.0', a.version ?? '0.0.0'))\n if (found[0] !== undefined)\n return found[0].entry\n const parent = path.dirname(current)\n if (parent === current)\n break\n current = parent\n }\n return null\n}\n\nfunction resolveDir(dir: string): string {\n try {\n return fs.realpathSync(dir)\n }\n catch {\n return path.resolve(dir)\n }\n}\n\n/**\n * Where the running harness's own CLI entry is.\n *\n * A local clone's build path is not stable enough to bake into a boot entry, so\n * the clone case records the entry in a generated launcher and returns that\n * launcher's path; the launcher re-finds dsh at boot. A plain `dsh` on PATH is\n * already resolved to a stable entry and keeps its current shape.\n */\nexport async function resolveDshLaunch(options: ResolveDshLaunchOptions = {}): Promise<DshLaunch | null> {\n const argv1 = options.argv1 === undefined ? process.argv[1] : options.argv1\n const dshHome = options.dshHome ?? resolveDshHome()\n const stateDir = options.stateDir ?? pluginStateDir()\n\n // A project that declares its own dsh keeps it: whoever started this process,\n // the panel must run the dsh that project installed, not one from PATH.\n const projectDir = options.projectDir === undefined ? process.cwd() : options.projectDir\n const declared = projectDir === null || projectDir.trim().length === 0 ? null : projectDshEntry(projectDir)\n if (declared !== null && realPath(declared) !== realPath(launcherFor(stateDir, declared))) {\n const launcher = writeDshLauncher({\n stateDir,\n dshHome,\n resolvedEntry: declared,\n pinnedEntry: declared,\n repin: true,\n entryExtension: path.extname(declared),\n searchRoots: [dshSearchRoot(declared), projectDir].filter((root): root is string => typeof root === 'string' && root.length > 0),\n })\n return { program: process.execPath, args: [declared], cliEntry: declared, shimPath: null, source: 'entry', launcherPath: launcher.path }\n }\n\n const local = typeof argv1 === 'string' && argv1.length > 0 ? launchedEntry(argv1) : null\n // A process already booted through the launcher is not a clone to snapshot:\n // one more layer would just reimplement the launcher. Both sides are\n // canonicalised first \u2014 on macOS a temp directory is reached as `/var/...`\n // and resolved as `/private/var/...`, and comparing those would stack a\n // second launcher on every boot.\n if (local !== null && realPath(local) !== realPath(dshLauncherPath(stateDir, path.extname(local)))) {\n const launcher = writeDshLauncher({\n stateDir,\n dshHome,\n resolvedEntry: local,\n // The running image is the one a boot entry must keep running: a global\n // copy that reports a higher version is not an upgrade of this install.\n pinnedEntry: local,\n repin: true,\n entryExtension: path.extname(local),\n // Where this install sits, so a rebuilt/moved copy is found next boot.\n searchRoots: [dshSearchRoot(local)].filter((root): root is string => root !== null),\n })\n return { program: process.execPath, args: [local], cliEntry: local, shimPath: null, source: 'entry', launcherPath: launcher.path }\n }\n\n // This process *is* the launcher's child. The launcher already decided which\n // dsh to run and recorded it; resolving again could only produce a different\n // answer (whatever `$DSH_HOME` or PATH happens to hold) and would then be\n // baked into the row \u2014 the exact drift this launcher exists to prevent.\n if (argv1 !== undefined && argv1 !== null && realPath(argv1) === realPath(dshLauncherPath(stateDir, path.extname(local ?? argv1)))) {\n const pinned = readDshLauncherRecord(stateDir)?.pinned ?? null\n if (pinned !== null) {\n const launcher = dshLauncherPath(stateDir, path.extname(pinned))\n const launch = resolveShimmedCli(pinned, process.execPath)\n const cliEntry = launch?.cliEntry ?? pinned\n // Re-assert the record for a launcher that was written before a pin\n // existed; a carried pin is not re-chosen.\n if (readDshLauncherRecord(stateDir)?.entry !== pinned)\n writeDshLauncher({ stateDir, dshHome, resolvedEntry: cliEntry, pinnedEntry: pinned, entryExtension: path.extname(pinned) })\n return { program: process.execPath, args: [launcher], cliEntry, shimPath: null, source: 'entry', launcherPath: launcher }\n }\n }\n\n // Not launched as an entry: a clone that is not on PATH still installs into\n // the harness home, and that is a far better answer than \"not found\". An\n // existing pin is the running image and outranks anything found here.\n const installed = findDshEntry(dshHome)\n if (installed !== null) {\n const launcher = writeDshLauncher({ stateDir, dshHome, resolvedEntry: installed, entryExtension: path.extname(installed) })\n return { program: process.execPath, args: [installed], cliEntry: installed, shimPath: null, source: 'entry', launcherPath: launcher.path }\n }\n\n const found = await (options.findOnPath ?? which)('dsh')\n if (found === null)\n return null\n // A launcher with no pin left is a decision for the user, not for this\n // process: returning the PATH shape would rewrite the row to that absolute\n // entry, and nothing would repair it once PATH changes again.\n if (readDshLauncherRecord(stateDir)?.pinned != null)\n return null\n const launch = resolveShimmedCli(found, process.execPath)\n return launch === null ? null : { ...launch, launcherPath: null }\n}\n\n/** The launcher a given entry would boot through, whatever its extension. */\nfunction launcherFor(stateDir: string, entry: string): string {\n return dshLauncherPath(stateDir, path.extname(entry))\n}\n\nfunction buildCommand(launch: CliLaunch, launcherPath: string | null): { command: string, entryArgs: string[] } {\n if (launcherPath !== null && launcherPath.length > 0)\n return { command: process.execPath, entryArgs: [launcherPath] }\n return { command: launch.program, entryArgs: launch.args }\n}\n\n/** The script a stored entry actually runs, out of its command and leading args. */\nfunction entryScript(config: { command?: string, args?: string[] }): string | null {\n const program = config.command\n if (typeof program !== 'string' || program.length === 0)\n return null\n // A bare command (`dsh`, `dsh.cmd`) is itself the executable. An absolute one\n // is `process.execPath` here, and the script is the argument that follows.\n if (!path.isAbsolute(program))\n return program\n const rest = config.args ?? []\n return rest.find(arg => path.isAbsolute(arg)) ?? program\n}\n\n/**\n * Re-point an entry at the stable launcher for the pinned image.\n *\n * Qualifying shapes, and only these: the entry runs the script this pattern\n * pins (a local build recorded in an earlier release), reaches that script\n * through a package-manager shim, or names a bare `dsh`/`dsh.cmd` that a later\n * PATH lookup would answer with a different copy. An entry that runs a\n * *different* dsh is somebody's deliberate choice and is never touched.\n *\n * An entry this process was booted through is also left alone: repairing it\n * would point it at itself, since that is already the launcher.\n */\n/**\n * Whether a bare `dsh` in this project reaches the image we mean.\n *\n * home-hosted resolves a bare command through `<cwd>/node_modules/.bin` first\n * and only then PATH, so a project that installed dsh as a dependency gets its\n * own copy by name \u2014 no absolute path to bake in, and it follows the package\n * manager the project actually uses.\n */\nexport function localDshCommand(projectDir: string | null | undefined, entry: string | null): string | null {\n if (typeof projectDir !== 'string' || projectDir.trim().length === 0 || entry === null)\n return null\n const shim = resolveShimmedCli(path.join(projectDir, 'node_modules', '.bin', 'dsh'))?.cliEntry ?? null\n if (shim === null)\n return null\n return realPath(shim) === realPath(entry) ? 'dsh' : null\n}\n\n/**\n * The script a boot row should run: `dsh` when this project's own shim reaches\n * the pinned image, else the stable launcher for it.\n */\nexport function pinnedScriptFor(launch: DshLaunch | null, projectDir?: string | null): string | null {\n if (launch === null)\n return null\n const entry = launch.cliEntry ?? launch.args.find(arg => path.isAbsolute(arg)) ?? null\n if (localDshCommand(projectDir, entry) === 'dsh')\n return 'dsh'\n const launcher = launch.launcherPath\n if (launcher !== null && launcher.length > 0)\n return launcher\n return entry\n}\n\n/** Whether a stored command is a bare name PATH decides, not a path we know. */\nfunction isBareCommand(stored: string): boolean {\n return !/[/\\\\]/.test(stored) && (stored === 'dsh' || /^dsh\\.(?:cmd|exe)$/i.test(stored))\n}\n\n/**\n * Whether an entry *could* need that repair, without resolving anything.\n *\n * Resolving a local dsh walks the harness home, so the write path asks this\n * first. Almost every shape can need one \u2014 an absolute command, an absolute\n * first argument, a bare `dsh` \u2014 and the cheap answer is therefore \"yes\" here\n * and a full comparison in `launcherRepair`, which is what decides whether\n * anything is actually rewritten.\n */\nexport function needsLauncherRepair(config: { command?: string, args?: string[] }): boolean {\n const program = config.command\n if (typeof program !== 'string' || program.length === 0)\n return false\n if (path.isAbsolute(program))\n return true\n if (isBareCommand(program))\n return true\n return (config.args ?? []).some(arg => path.isAbsolute(arg))\n}\n\nexport interface LauncherRepairOptions {\n /**\n * True for a row this plugin created: its command is the plugin's to set, so a\n * stored script that is neither the pinned one nor a shim to it is replaced\n * rather than respected as somebody's choice.\n */\n ownedCommand?: boolean\n}\n\nexport function launcherRepair(\n config: { command?: string, args?: string[] },\n launch: DshLaunch | null,\n options: LauncherRepairOptions & { projectDir?: string | null } = {},\n): { command: string, args: string[], cwd?: string } | null {\n const pinned = pinnedScriptFor(launch, options.projectDir)\n if (pinned === null || launch === null)\n return null\n // Two different questions: what the row should run (`targetBare`), and where\n // the old command kept its script (only a non-bare command inlined one).\n const targetBare = pinned === 'dsh'\n const storedBare = isBareCommand(entryScript(config) ?? '')\n const stored = entryScript(config)\n if (stored === null || stored === pinned)\n return null\n if (!storedBare && options.ownedCommand !== true) {\n // An entry this plugin did not create may be somebody's deliberate choice of\n // dsh, so only the script this pattern pins or a shim to it qualifies.\n const target = resolveShimmedCli(stored, process.execPath)?.cliEntry ?? stored\n if (target !== launch.cliEntry && target !== pinned)\n return null\n }\n // A bare stored command carries no script argument, so nothing is dropped: an\n // absolute app argument (`--home /Users/x/.dsh`) has to survive. Any other\n // shape loses its inlined script, wherever in the argv it sits.\n const args = config.args ?? []\n const script = storedBare ? -1 : args.findIndex(arg => path.isAbsolute(arg) || arg === pinned)\n const rest = script === -1 ? args : args.filter((_, index) => index !== script)\n // A bare command is only local-first with the right cwd, so the row carries\n // the project it was resolved from.\n return targetBare\n ? { command: 'dsh', args: rest, ...(typeof options.projectDir === 'string' && options.projectDir.length > 0 ? { cwd: options.projectDir } : {}) }\n : { command: process.execPath, args: [pinned, ...rest] }\n}\n\nexport function buildDshEntry(facts: DshFacts): ServerEntry {\n const launch = facts.launch\n const profile = facts.profile === null || facts.profile === undefined ? 'web' : facts.profile\n\n const appArgs: string[] = []\n if (facts.port !== null) {\n appArgs.push(\n '--port', '{port}',\n '--host', '127.0.0.1',\n '--no-open',\n '--trusted-host', `localhost:{port}`,\n )\n }\n\n const launcherArgs = profile === 'web' ? ['web'] : ['--profile', profile]\n // A project that declares dsh gets its own copy by name: home-hosted resolves\n // a bare command through the row's cwd and its own project dir before PATH, so\n // there is no absolute path to bake in and nothing to move when the project does.\n const projectDir = facts.projectDir === undefined || facts.projectDir === null ? process.cwd() : facts.projectDir\n const local = launch === null ? null : localDshCommand(projectDir, launch.cliEntry)\n const generated = launch === null || local === 'dsh'\n ? { command: 'dsh', entryArgs: [] as string[] }\n : buildCommand(launch, facts.launcherPath ?? null)\n\n return {\n id: facts.id,\n label: 'DSH web',\n enabled: true,\n autostart: true,\n command: generated.command,\n args: [...generated.entryArgs, ...launcherArgs, ...appArgs],\n bind: 'local',\n port: facts.port,\n cwd: projectDir,\n env: {},\n dataEnvs: { DSH_HOME: facts.dshHome },\n onPortConflict: 'kill',\n stop: { killPortHolders: true },\n health: {\n enabled: true,\n mode: 'http',\n // 401 is normal here: the panel answers the login route when auth is on.\n http: { path: '/', method: 'GET', expectStatusBelow: 500, expectBody: '' },\n },\n }\n}\n", "/**\n * Where things live. The harness home comes from `$DSH_HOME`; home-hosted's\n * state comes from `$HHOSTED_HOME`. Neither is ever hard-coded into a file this\n * plugin writes \u2014 the resolved value is written into generated units instead.\n */\nimport crypto from 'node:crypto'\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport { isHhRoot, runFile } from '../home-hosted/layout.js'\n\nexport function expandHome(value: string): string {\n if (value === '~')\n return os.homedir()\n if (value.startsWith('~/') || value.startsWith('~\\\\'))\n return path.join(os.homedir(), value.slice(2))\n return value\n}\n\n/** A configured path, resolved against one home \u2014 never against the cwd. */\nfunction resolveUnder(value: string, homeDir: string): string {\n if (value === '~')\n return path.resolve(homeDir)\n if (value.startsWith('~/') || value.startsWith('~\\\\'))\n return path.resolve(path.join(homeDir, value.slice(2)))\n return path.isAbsolute(value) ? path.resolve(value) : path.resolve(homeDir, value)\n}\n\n/** DeepSeek Harness home: `$DSH_HOME`, else `~/.dsh`. */\nexport function dshHome(): string {\n const configured = process.env.DSH_HOME?.trim()\n return configured ? path.resolve(expandHome(configured)) : path.join(os.homedir(), '.dsh')\n}\n\n/**\n * The panel root one plugin instance drives.\n *\n * Two dsh installs share `~/.dsh` far more often than they should, and a shared\n * panel root means two plugins editing one workspace config and one boot\n * entry. So each instance normally gets its own root beside its own state.\n *\n * An explicit `$HHOSTED_HOME` is an instruction and always wins. So is a panel\n * already living at the machine-wide default: an upgrade must never point a\n * running panel somewhere else, or it would look like every server vanished.\n */\nexport function resolveHomeHostedHome(\n stateDir: string,\n env: NodeJS.ProcessEnv = process.env,\n homeDir: string = os.homedir(),\n): { home: string, source: 'env' | 'legacy' | 'instance' } {\n const configured = env.HHOSTED_HOME?.trim()\n if (configured)\n return { home: resolveUnder(configured, homeDir), source: 'env' }\n const state = resolveUnder(stateDir, homeDir)\n const own = path.join(state, 'panel')\n // Once this instance has a root of its own, it stays there: a root it has\n // already used is never traded for the machine-wide one.\n if (fs.existsSync(own))\n return { home: own, source: 'instance' }\n // The machine-wide panel is only this instance's to adopt when this instance\n // is the machine's own harness home, or when it has run before (its settings\n // are there). A home created fresh \u2014 a scratch `DSH_HOME`, a test project \u2014\n // never reaches over and drives the panel some other install owns.\n const legacy = path.join(homeDir, '.home-hosted')\n if (isEstablishedInstance(state, homeDir) && hasLivePanel(legacy))\n return { home: legacy, source: 'legacy' }\n return { home: own, source: 'instance' }\n}\n\n/** Whether this state dir is the machine's own install, or one that has run before. */\nfunction isEstablishedInstance(stateDir: string, homeDir: string): boolean {\n if (stateDir === path.join(homeDir, '.dsh', 'dsh-home-hosted'))\n return true\n try {\n return fs.statSync(path.join(stateDir, 'settings.json')).isFile()\n }\n catch {\n return false\n }\n}\n\n/**\n * Whether a root holds a panel this instance may adopt: a 0.7 `.hh`, a pre-0.7\n * root with an actual servers config, or a `run.json` whose pid is still alive.\n *\n * A `run.json` alone is not proof \u2014 a killed panel leaves one behind \u2014 so it only\n * counts while its process answers. The same goes for the other top-level\n * leftovers (`.control-secrets.json`, `.state`): they are not a panel.\n */\nfunction hasLivePanel(home: string): boolean {\n if (isHhRoot(home) || fs.existsSync(path.join(home, 'servers.config.json')))\n return true\n for (const file of [runFile(home), path.join(home, 'run.json')]) {\n try {\n const raw = JSON.parse(fs.readFileSync(file, 'utf8')) as { pid?: unknown }\n if (typeof raw.pid !== 'number')\n continue\n process.kill(raw.pid, 0)\n return true\n }\n catch {\n // no runtime here; try the next one\n }\n }\n return false\n}\n\n/**\n * The boot artifact one state root owns. Only the machine-wide default keeps the\n * historical name: keying on the running process's own `$DSH_HOME` would give\n * two differently-homed installs the same name, which is the collision this\n * naming exists to prevent.\n */\nexport function bootUnitName(stateDir: string): string {\n if (isMachineDefaultStateDir(stateDir))\n return 'home-hosted'\n const key = path.resolve(expandHome(stateDir?.trim() ?? ''))\n return `home-hosted-${crypto.createHash('sha256').update(key).digest('hex').slice(0, 8)}`\n}\n\nfunction isMachineDefaultStateDir(stateDir: string): boolean {\n const bare = stateDir?.trim()\n if (bare === undefined || bare.length === 0)\n return true\n return path.resolve(expandHome(bare)) === path.join(os.homedir(), '.dsh', 'dsh-home-hosted')\n}\n\n/** Where this plugin keeps its own durable state (settings, snapshots, token). */\nexport function pluginStateDir(override?: string): string {\n const configured = override?.trim()\n if (configured)\n return path.resolve(expandHome(configured))\n return path.join(dshHome(), 'dsh-home-hosted')\n}\n\n", "/**\n * How the home-hosted daemon is launched, and what a boot entry for it contains.\n *\n * The executable is resolved to an absolute program plus argv: a generated boot\n * entry must not depend on a login shell's PATH. When the resolved command is a\n * package-manager shim, its `cmd-shim-target` comment names the real entry, and\n * node runs that directly \u2014 the same guarantee the shim itself gives.\n */\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport type { BootSpec } from '../boot/types.js'\nimport { run } from '../util/exec.js'\nimport { readText } from '../util/fsx.js'\nimport { bootUnitName } from '../util/paths.js'\n\n/** The artifact name every install wrote before it was per state root. */\nexport const LEGACY_BOOT_UNIT_NAME = 'home-hosted'\n\nexport interface CliLaunch {\n program: string\n /** Args that must precede the command, e.g. the CLI entry path. */\n args: string[]\n cliEntry: string | null\n shimPath: string | null\n source: 'override' | 'entry' | 'shim-target' | 'shim'\n}\n\nexport async function which(command: string): Promise<string | null> {\n const probe = process.platform === 'win32' ? 'where.exe' : 'which'\n const result = await run(probe, [command], { timeoutMs: 5000 })\n if (result.code !== 0)\n return null\n const first = result.stdout.split(/\\r?\\n/).map(line => line.trim()).filter(Boolean)[0]\n return first ?? null\n}\n\n/** Turn a resolved CLI path into an absolute program + argv. */\nexport function resolveShimmedCli(absolute: string, nodePath = process.execPath): CliLaunch | null {\n if (!fs.existsSync(absolute))\n return null\n if (/\\.(?:mjs|cjs|js)$/.test(absolute))\n return { program: nodePath, args: [absolute], cliEntry: absolute, shimPath: null, source: 'entry' }\n\n const shimTarget = /^#\\s*cmd-shim-target=(.+)$/m.exec(readText(absolute) ?? '')?.[1]?.trim()\n if (shimTarget !== undefined && fs.existsSync(shimTarget))\n return { program: nodePath, args: [shimTarget], cliEntry: shimTarget, shimPath: absolute, source: 'shim-target' }\n\n // A shim with no readable target still works when node is on the entry's PATH.\n return { program: absolute, args: [], cliEntry: null, shimPath: absolute, source: 'shim' }\n}\n\nexport async function resolveHomeHostedLaunch(override?: string | null): Promise<CliLaunch | null> {\n const configured = override?.trim()\n const found = configured && configured.length > 0 ? configured : (await which('home-hosted')) ?? (await which('hh'))\n if (found === null || found === undefined)\n return null\n\n const launch = resolveShimmedCli(path.resolve(found))\n if (launch === null)\n return null\n return configured ? { ...launch, source: 'override' } : launch\n}\n\nexport interface HomeHostedFacts {\n home: string\n projectDir?: string | null\n version?: string | null\n /**\n * The home of the account the entry runs as.\n *\n * `HOME` is written into every entry's environment, and that is the problem:\n * installed through `sudo`, it is root's home, so the panel the entry starts\n * resolves `~` (and anything else HOME-relative) into `/root` and dies with a\n * permission error that never mentions its own user.\n *\n * `undefined` means account resolution was not involved, so this process's own\n * `HOME` is right. A string is that account's home. `null` means an account *was*\n * resolved but its home is not knowable here \u2014 an LDAP or NIS login has no\n * `/etc/passwd` row \u2014 and then the line is *omitted* rather than left at the\n * installer's, because an explicit `Environment=HOME=` overrides `User=` and\n * systemd fills in the right one from NSS itself.\n */\n accountHome?: string | null\n /**\n * The environment this process's own values (`HOME`, `PATH`) are read from.\n *\n * Injected so a caller that decides the entry's account from a given environment\n * also builds the entry's own environment from it: reading `process.env` here\n * while resolving the account elsewhere is how the two came to describe\n * different people.\n */\n env?: Readonly<Record<string, string | undefined>>\n}\n\n/** A deterministic environment for a boot-time entry, with absolute values. */\nexport function homeHostedEnv(facts: HomeHostedFacts, launch: CliLaunch, extra: Record<string, string> = {}): Record<string, string> {\n const dirs = [\n launch.shimPath === null ? null : path.dirname(launch.shimPath),\n path.dirname(process.execPath),\n ...(process.env.PATH ?? '').split(path.delimiter),\n '/usr/local/bin',\n '/usr/bin',\n '/bin',\n ].filter((dir): dir is string => typeof dir === 'string' && dir.length > 0)\n const seen = new Set<string>()\n const pathValue = dirs.filter((dir) => {\n if (seen.has(dir))\n return false\n seen.add(dir)\n return true\n }).join(path.delimiter)\n\n const env: Record<string, string> = {\n PATH: pathValue,\n HOME: process.env.HOME ?? process.env.USERPROFILE ?? '',\n HHOSTED_HOME: facts.home,\n ...extra,\n }\n // Only when it is genuinely a *different* home: a spec that merely repeats this\n // process's own environment stays byte-identical, so an existing entry is not\n // rewritten on every plugin start.\n const accountHome = facts.accountHome?.trim()\n if (accountHome !== undefined && accountHome.length > 0) {\n if (accountHome !== env.HOME)\n env.HOME = accountHome\n }\n else if (facts.accountHome === null) {\n // An account was resolved but its home is not knowable here. Dropping the line\n // is the only safe answer: leaving it would hand the panel the installer's\n // home (root's), and `User=` cannot correct an explicit `Environment=HOME=`.\n delete env.HOME\n }\n if (facts.projectDir)\n env.HHOSTED_PROJECT = facts.projectDir\n return env\n}\n\nexport interface BootSpecOptions {\n stateDir: string\n /** The artifact name to write; defaults to the one this state root owns. */\n unitName?: string\n marker?: string\n /** The account's home, so an entry launched by root does not inherit `/root`. */\n accountHome?: string | null\n /** The environment this process's own values are read from. */\n env?: Readonly<Record<string, string | undefined>>\n}\n\nexport function buildHomeHostedBootSpec(\n facts: HomeHostedFacts,\n launch: CliLaunch,\n options: BootSpecOptions,\n): BootSpec {\n const logDir = path.join(facts.home, '.logs')\n fs.mkdirSync(logDir, { recursive: true })\n const args = [...launch.args, 'up', '--foreground', '--home', facts.home]\n if (facts.projectDir)\n args.push('--project', facts.projectDir)\n\n return {\n command: launch.program,\n args,\n cwd: facts.projectDir ?? facts.home,\n env: homeHostedEnv(\n {\n ...facts,\n // `undefined` means the option was not given; `null` is a decision (an\n // account whose home is unknowable), and `??` would erase it.\n accountHome: options.accountHome === undefined ? facts.accountHome : options.accountHome,\n env: options.env ?? facts.env,\n },\n launch,\n ),\n marker: options.marker ?? 'managed by dsh-home-hosted',\n // One artifact per state root, so two plugin instances cannot overwrite or\n // delete each other's entry. The machine-default install keeps the old name.\n unitName: options.unitName ?? bootUnitName(options.stateDir),\n label: 'home-hosted control panel',\n logDir,\n }\n}\n", "/**\n * Which home-hosted CLI this plugin drives.\n *\n * The plugin ships its own pinned copy as a dependency and prefers it, so the\n * version that runs the panel at boot is the version this plugin was built\n * against \u2014 not whatever `home-hosted` happens to be on PATH. Preferring a\n * global install is a supported setting, not an accident.\n *\n * Resolution order: the operator's `homeHostedCommand` override always wins,\n * then the preferred copy, then the other one as a fallback.\n */\nimport { createRequire } from 'node:module'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport type { CliCandidate, CliSource, CliStatus } from '../shared/contracts.js'\nimport { run } from '../util/exec.js'\nimport type { CliLaunch } from './launch.js'\nimport { resolveShimmedCli, which } from './launch.js'\n\n/** The range the plugin ships and is tested against. */\nexport const EXPECTED_RANGE = '^0.7.2'\n\n/** Oldest release whose API and config schema this plugin relies on. */\nexport const MIN_SUPPORTED_VERSION = '0.7.0'\n\nexport type CliPreference = 'pinned' | 'global'\n\nexport interface CliResolution {\n launch: CliLaunch | null\n status: CliStatus\n}\n\nexport interface ResolveCliOptions {\n override?: string | null\n /** Which copy to run when both exist. */\n prefer?: CliPreference\n /** Test seam: the pinned package's manifest path, or null when it is not installed. */\n packageManifest?: () => string | null\n /** Test seam: ask a CLI for its own version. */\n readVersion?: (launch: CliLaunch) => Promise<string | null>\n /** Test seam: locate a command on PATH. */\n findOnPath?: (command: string) => Promise<string | null>\n timeoutMs?: number\n}\n\ninterface Candidate {\n candidate: CliCandidate\n launch: CliLaunch\n}\n\n/** Where the pinned `home-hosted` manifest is, resolved from this module. */\nexport function pinnedManifestPath(): string | null {\n try {\n return createRequire(import.meta.url).resolve('home-hosted/package.json')\n }\n catch {\n return null\n }\n}\n\n/** The `home-hosted` bin entry a package manifest declares. */\nexport function binEntryFromManifest(manifestPath: string): string | null {\n try {\n const manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf8')) as { bin?: string | Record<string, string> }\n const declared = typeof manifest.bin === 'string'\n ? manifest.bin\n : manifest.bin?.['home-hosted'] ?? Object.values(manifest.bin ?? {})[0]\n return typeof declared === 'string' && declared.length > 0\n ? path.resolve(path.dirname(manifestPath), declared)\n : null\n }\n catch {\n return null\n }\n}\n\n/** The first `x.y.z[-pre]` in a CLI's output. */\nexport function parseVersion(output: string | null): string | null {\n const match = /(\\d+)\\.(\\d+)\\.(\\d+)(?:-([0-9A-Za-z.-]+))?/.exec(output ?? '')\n return match === null ? null : `${match[1]}.${match[2]}.${match[3]}${match[4] === undefined ? '' : `-${match[4]}`}`\n}\n\n/** Numeric comparison; a prerelease sorts below its own release. */\nexport function compareVersions(a: string, b: string): number {\n const split = (value: string): { parts: number[], pre: string | null } => {\n const [core, pre = null] = value.split('-', 2)\n return { parts: (core ?? '').split('.').map(part => Number.parseInt(part, 10) || 0), pre }\n }\n const left = split(a)\n const right = split(b)\n for (let index = 0; index < 3; index += 1) {\n const diff = (left.parts[index] ?? 0) - (right.parts[index] ?? 0)\n if (diff !== 0)\n return diff < 0 ? -1 : 1\n }\n if (left.pre === right.pre)\n return 0\n if (left.pre === null)\n return 1\n if (right.pre === null)\n return -1\n return left.pre < right.pre ? -1 : 1\n}\n\nfunction detailFor(source: CliSource, version: string | null, supported: boolean, prefer: CliPreference, unusableOverride: string | null = null): string {\n const shown = version ?? 'unknown version'\n switch (source) {\n case 'config':\n return unusableOverride === null\n ? `from the configured command (${shown})`\n : `the configured command could not be used: ${unusableOverride} does not exist (or is not a file this plugin can run); fix homeHostedCommand, or clear it to let the plugin choose`\n case 'dependency':\n return supported\n ? `the pinned dependency (${shown})`\n : `the pinned dependency is below the oldest supported release (${MIN_SUPPORTED_VERSION})`\n case 'path':\n if (!supported)\n return `the global install on PATH (${shown}) is below the oldest supported release (${MIN_SUPPORTED_VERSION})`\n return prefer === 'global'\n ? `the global install on PATH (${shown})`\n : `a global install on PATH (${shown}), not the pinned dependency ${EXPECTED_RANGE}`\n default:\n return 'no home-hosted CLI found: install the plugin with its dependencies, or put home-hosted on PATH'\n }\n}\n\nasync function defaultReadVersion(launch: CliLaunch, timeoutMs: number): Promise<string | null> {\n const result = await run(launch.program, [...launch.args, '--version'], { timeoutMs })\n return parseVersion(result.stdout) ?? parseVersion(result.stderr)\n}\n\nexport async function resolveCli(options: ResolveCliOptions = {}): Promise<CliResolution> {\n const timeoutMs = options.timeoutMs ?? 5000\n const prefer = options.prefer ?? 'pinned'\n const readVersion = options.readVersion ?? (launch => defaultReadVersion(launch, timeoutMs))\n const locate = options.findOnPath ?? which\n\n // 1. An explicit override is not a preference: it is an instruction. An\n // instruction that cannot be carried out is reported, never silently\n // replaced by a copy the operator did not ask for.\n const override = options.override?.trim()\n let unusableOverride: string | null = null\n if (override !== undefined && override.length > 0) {\n const launch = resolveShimmedCli(path.resolve(override))\n if (launch === null) {\n unusableOverride = override\n }\n else {\n const version = await readVersion(launch)\n const supported = version === null || compareVersions(version, MIN_SUPPORTED_VERSION) >= 0\n return {\n launch,\n status: {\n source: 'config',\n path: launch.cliEntry ?? launch.shimPath ?? launch.program,\n version,\n expectedRange: EXPECTED_RANGE,\n supported,\n prefer,\n dependency: null,\n global: null,\n detail: detailFor('config', version, supported, prefer),\n },\n }\n }\n }\n\n // 2. The two copies this plugin knows about.\n const manifest = (options.packageManifest ?? pinnedManifestPath)()\n const dependencyEntry = manifest === null ? null : binEntryFromManifest(manifest)\n let dependency: Candidate | null = null\n if (dependencyEntry !== null && fs.existsSync(dependencyEntry)) {\n let version: string | null = null\n try {\n version = (JSON.parse(fs.readFileSync(manifest as string, 'utf8')) as { version?: string }).version ?? null\n }\n catch {\n version = null\n }\n dependency = {\n candidate: { source: 'dependency', path: dependencyEntry, version },\n launch: { program: process.execPath, args: [dependencyEntry], cliEntry: dependencyEntry, shimPath: null, source: 'entry' },\n }\n }\n\n let global: Candidate | null = null\n const found = (await locate('home-hosted')) ?? (await locate('hh'))\n if (found !== null) {\n const launch = resolveShimmedCli(path.resolve(found))\n if (launch !== null) {\n global = {\n candidate: { source: 'path', path: launch.cliEntry ?? launch.shimPath ?? launch.program, version: await readVersion(launch) },\n launch,\n }\n }\n }\n\n const order = prefer === 'global' ? [global, dependency] : [dependency, global]\n const chosen = order.find(item => item !== null) ?? null\n const dependencySummary = dependency?.candidate ?? null\n const globalSummary = global?.candidate ?? null\n\n if (unusableOverride !== null) {\n return {\n launch: null,\n status: {\n source: 'config',\n path: unusableOverride,\n version: null,\n expectedRange: EXPECTED_RANGE,\n supported: false,\n prefer,\n dependency: dependencySummary,\n global: globalSummary,\n detail: detailFor('config', null, false, prefer, unusableOverride),\n },\n }\n }\n\n if (chosen === null) {\n return {\n launch: null,\n status: {\n source: 'none',\n path: null,\n version: null,\n expectedRange: EXPECTED_RANGE,\n supported: false,\n prefer,\n dependency: dependencySummary,\n global: globalSummary,\n detail: detailFor('none', null, false, prefer),\n },\n }\n }\n\n const version = chosen.candidate.version\n const supported = version === null || compareVersions(version, MIN_SUPPORTED_VERSION) >= 0\n return {\n launch: chosen.launch,\n status: {\n source: chosen.candidate.source,\n path: chosen.candidate.path,\n version,\n expectedRange: EXPECTED_RANGE,\n supported,\n prefer,\n dependency: dependencySummary,\n global: globalSummary,\n detail: detailFor(chosen.candidate.source, version, supported, prefer),\n },\n }\n}\n", "/**\n * A stable launcher for the pinned CLI.\n *\n * A dependency resolves to a pnpm path that carries the version and the peer\n * hash (`\u2026/.pnpm/home-hosted@0.6.1_zod@4.6.5/node_modules/home-hosted/\u2026`), which\n * moves on the next install and disappears when the profile is rebuilt. A boot\n * entry that baked that path in would fail at exactly the moment it exists for.\n *\n * So the plugin generates one small script in its own state directory and points\n * the boot entry at *that*. At boot the launcher finds the pinned copy again\n * (recorded path \u2192 profile node_modules, flat or pnpm) and falls back to PATH,\n * which is why the entry survives plugin upgrades and profile reinstalls.\n */\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { run } from '../util/exec.js'\nimport { readJson, writeFileAtomic, writeJsonAtomic } from '../util/fsx.js'\nimport { compareVersions, parseVersion } from './resolve.js'\n\n/** A package-manager shim's real target, as the shim itself records it. */\nconst SHIM_TARGET = /^#\\s*cmd-shim-target=(.+)$/m\n\nexport interface LauncherOptions {\n stateDir: string\n dshHome: string\n /** Where the pinned copy is right now; recorded as the fast path. */\n resolvedEntry: string | null\n /** The pinned copy's version, recorded for diagnostics. */\n resolvedVersion?: string | null\n /** The plugin's own package root, as a search hint. */\n pluginRoot?: string | null\n minVersion: string\n marker?: string\n}\n\nexport interface LauncherWrite {\n path: string\n changed: boolean\n}\n\ninterface LauncherRecord {\n entry: string | null\n version: string | null\n pluginRoot: string | null\n writtenAt: number\n}\n\nexport function launcherDir(stateDir: string): string {\n return path.join(stateDir, 'bin')\n}\n\nexport function launcherPath(stateDir: string): string {\n return path.join(launcherDir(stateDir), 'home-hosted.mjs')\n}\n\nexport function launcherRecordPath(stateDir: string): string {\n return path.join(launcherDir(stateDir), 'resolved.json')\n}\n\nexport function readLauncherRecord(stateDir: string): LauncherRecord | null {\n return readJson<LauncherRecord>(launcherRecordPath(stateDir))\n}\n\n/** The generated script's source; exported so a test can inspect it. */\nexport function buildLauncherSource(options: LauncherOptions): string {\n const marker = options.marker ?? 'managed by dsh-home-hosted'\n const record = launcherRecordPath(options.stateDir)\n return `#!/usr/bin/env node\n// ${marker} \u2014 rewritten on every plugin start; do not edit.\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { spawnSync } from 'node:child_process'\n\nconst DSH_HOME = ${JSON.stringify(options.dshHome)}\nconst RECORD = ${JSON.stringify(record)}\nconst MIN_VERSION = ${JSON.stringify(options.minVersion)}\nconst PLUGIN_ROOT = ${JSON.stringify(options.pluginRoot ?? null)}\n\n/** A package-manager shim's real target, as the shim itself records it. */\nconst SHIM_TARGET = /^#\\\\s*cmd-shim-target=(.+)$/m\n\nfunction readRecord() {\n try { return JSON.parse(fs.readFileSync(RECORD, 'utf8')) } catch { return null }\n}\n\n/**\n * The version of the package a CLI entry belongs to, following a package-manager\n * shim to the file it actually runs.\n *\n * A pnpm/npm shim is a shell script whose 'cmd-shim-target' comment names the real\n * entry \u2014 and it lives *inside* the plugin's own package, so walking up from the\n * shim reads this plugin's package.json and reports the plugin's version for the\n * CLI. That is not cosmetic: 'choose' sorts by version, so the plugin's own number\n * let a PATH fallback outrank the recorded pinned copy and boot a different\n * home-hosted than this plugin shipped.\n */\nfunction versionOf(entry) {\n let target = entry\n try {\n if (fs.statSync(entry).isFile()) {\n const shimTarget = SHIM_TARGET.exec(fs.readFileSync(entry, 'utf8'))?.[1]?.trim()\n if (shimTarget !== undefined && shimTarget.length > 0) target = shimTarget\n }\n } catch {}\n let dir = path.dirname(target)\n for (let hop = 0; hop < 4; hop += 1) {\n try {\n const manifest = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'))\n if (typeof manifest.version === 'string') return manifest.version\n } catch {}\n dir = path.dirname(dir)\n }\n return null\n}\n\nfunction compare(a, b) {\n const split = value => {\n const [core, pre = null] = String(value).split('-', 2)\n return { parts: core.split('.').map(part => Number.parseInt(part, 10) || 0), pre }\n }\n const left = split(a); const right = split(b)\n for (let i = 0; i < 3; i += 1) {\n const diff = (left.parts[i] ?? 0) - (right.parts[i] ?? 0)\n if (diff !== 0) return diff < 0 ? -1 : 1\n }\n if (left.pre === right.pre) return 0\n if (left.pre === null) return 1\n if (right.pre === null) return -1\n return left.pre < right.pre ? -1 : 1\n}\n\nfunction collect(out, entry, tier) {\n if (typeof entry === 'string' && entry.length > 0 && fs.existsSync(entry)) out.push({ entry, tier })\n}\n\nfunction collectRoot(root, out, tier) {\n collect(out, path.join(root, 'node_modules', 'home-hosted', 'bin', 'home-hosted.mjs'), tier)\n const store = path.join(root, 'node_modules', '.pnpm')\n let names = []\n try { names = fs.readdirSync(store) } catch { return }\n for (const name of names) {\n if (name.startsWith('home-hosted@') || name.startsWith('dsh-home-hosted@'))\n collect(out, path.join(store, name, 'node_modules', 'home-hosted', 'bin', 'home-hosted.mjs'), tier)\n }\n}\n\n/**\n * Where a copy can come from, best first. A plain version sort let a copy of the\n * plugin's own version \u2014 read from the package-manager shim, or an unrelated\n * global install \u2014 outrank the copy the plugin actually pinned. A recorded pin is\n * an instruction; PATH is a hope.\n */\nconst TIER_RECORD = 0\nconst TIER_LOCAL = 1\nconst TIER_PATH = 2\n\nfunction candidates() {\n const out = []\n const record = readRecord()\n if (record && typeof record.entry === 'string') collect(out, record.entry, TIER_RECORD)\n if (PLUGIN_ROOT !== null) collectRoot(PLUGIN_ROOT, out, TIER_LOCAL)\n try {\n for (const name of fs.readdirSync(path.join(DSH_HOME, 'profiles')))\n collectRoot(path.join(DSH_HOME, 'profiles', name), out, TIER_LOCAL)\n } catch {}\n collectRoot(path.join(DSH_HOME), out, TIER_LOCAL)\n // Last resort: a global install on the entry's own PATH.\n for (const dir of (process.env.PATH ?? '').split(path.delimiter)) {\n if (dir.length === 0) continue\n collect(out, path.join(dir, 'home-hosted'), TIER_PATH)\n collect(out, path.join(dir, 'home-hosted.cmd'), TIER_PATH)\n }\n return out\n}\n\n/** The best copy within a tier: the highest version, first-found when equal. */\nfunction bestOfTier(items) {\n const known = items.filter(item => item.version !== null)\n const pool = known.length === 0 ? items : known\n return [...pool].sort((a, b) => compare(b.version ?? '0.0.0', a.version ?? '0.0.0'))[0] ?? null\n}\n\nfunction choose() {\n const found = candidates().map(item => ({ ...item, version: versionOf(item.entry) }))\n const supported = found.filter(item => item.version !== null && compare(item.version, MIN_VERSION) >= 0)\n for (const tier of [TIER_RECORD, TIER_LOCAL, TIER_PATH]) {\n const best = bestOfTier(supported.filter(item => item.tier === tier))\n if (best !== null) return best.entry\n }\n // Nothing supported: a too-old copy still beats no panel at all.\n for (const tier of [TIER_RECORD, TIER_LOCAL, TIER_PATH]) {\n const best = bestOfTier(found.filter(item => item.tier === tier))\n if (best !== null) return best.entry\n }\n return null\n}\n\nconst entry = choose()\nif (entry === null) {\n console.error('[dsh-home-hosted] no home-hosted CLI found (looked in ' + DSH_HOME + ' and PATH). Reinstall the plugin with its dependencies, or set homeHostedCommand in the plugin row.')\n process.exit(1)\n}\n\nconst args = process.argv.slice(2)\nconst isScript = /\\\\.(mjs|cjs|js)$/.test(entry)\nconst result = isScript\n ? spawnSync(process.execPath, [entry, ...args], { stdio: 'inherit' })\n : spawnSync(entry, args, { stdio: 'inherit', shell: process.platform === 'win32' && /\\\\.(cmd|bat)$/i.test(entry) })\nprocess.exit(typeof result.status === 'number' ? result.status : 1)\n`\n}\n\nexport function writeLauncher(options: LauncherOptions): LauncherWrite {\n const file = launcherPath(options.stateDir)\n const source = buildLauncherSource(options)\n const previous = fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : null\n const changed = previous !== source\n if (changed) {\n writeFileAtomic(file, source, 0o755)\n fs.chmodSync(file, 0o755)\n }\n writeJsonAtomic(launcherRecordPath(options.stateDir), {\n entry: options.resolvedEntry,\n version: options.resolvedVersion ?? null,\n pluginRoot: options.pluginRoot ?? null,\n writtenAt: Date.now(),\n } satisfies LauncherRecord, 0o600)\n return { path: file, changed }\n}\n\n/**\n * Run the launcher the way the boot entry will, and read the version it answers.\n *\n * `env` is only for tests that have to describe the PATH the entry would see; a\n * boot entry inherits a login PATH, never the one this process happens to have.\n */\nexport async function preflightLauncher(stateDir: string, timeoutMs = 10_000, env?: Record<string, string | undefined>): Promise<string | null> {\n const file = launcherPath(stateDir)\n if (!fs.existsSync(file))\n return null\n const result = process.platform === 'win32'\n ? await run(process.execPath, [file, '--version'], { timeoutMs, ...(env === undefined ? {} : { env }) })\n : await run(file, ['--version'], { timeoutMs, ...(env === undefined ? {} : { env }) })\n return parseVersion(result.stdout) ?? parseVersion(result.stderr)\n}\n\n/**\n * The file a package-manager shim actually runs, or null when `entry` is a real\n * entry rather than a shim. Read for the same reason the generated launcher reads\n * it: the shim sits inside the plugin's own package.\n */\nfunction shimTargetOf(entry: string): string | null {\n try {\n if (!fs.statSync(entry).isFile())\n return null\n const target = SHIM_TARGET.exec(fs.readFileSync(entry, 'utf8'))?.[1]?.trim()\n return target !== undefined && target.length > 0 ? target : null\n }\n catch {\n return null\n }\n}\n\n/** The version a CLI entry belongs to, following a shim to what it really runs. */\nexport function versionOfEntry(entry: string): string | null {\n let target = entry\n const shimTarget = shimTargetOf(entry)\n if (shimTarget !== null) target = shimTarget\n let dir = path.dirname(target)\n for (let hop = 0; hop < 4; hop += 1) {\n const manifest = readJson<{ version?: string }>(path.join(dir, 'package.json'))\n if (typeof manifest?.version === 'string')\n return manifest.version\n dir = path.dirname(dir)\n }\n return null\n}\n\n/** Candidate entries under one root: a flat install, or a pnpm store. */\nexport function candidatesUnder(root: string): string[] {\n const out: string[] = []\n const direct = path.join(root, 'node_modules', 'home-hosted', 'bin', 'home-hosted.mjs')\n if (fs.existsSync(direct))\n out.push(direct)\n const store = path.join(root, 'node_modules', '.pnpm')\n if (fs.existsSync(store)) {\n for (const name of fs.readdirSync(store)) {\n if (!name.startsWith('home-hosted@') && !name.startsWith('dsh-home-hosted@'))\n continue\n const entry = path.join(store, name, 'node_modules', 'home-hosted', 'bin', 'home-hosted.mjs')\n if (fs.existsSync(entry))\n out.push(entry)\n }\n }\n return out\n}\n\n/** Highest satisfying candidate among explicit roots; exported for tests. */\nexport function findCandidate(roots: string[], minVersion: string): string | null {\n const found = roots.flatMap(root => candidatesUnder(root).map(entry => ({ entry, version: versionOfEntry(entry) })))\n const supported = found.filter(item => item.version !== null && compareVersions(item.version, minVersion) >= 0)\n supported.sort((a, b) => compareVersions(b.version ?? '0.0.0', a.version ?? '0.0.0'))\n return (supported[0] ?? found[0] ?? null)?.entry ?? null\n}\n\n// ---------------------------------------------------------------------------\n// The running harness's own launcher\n// ---------------------------------------------------------------------------\n\nexport interface DshLauncherOptions {\n stateDir: string\n dshHome: string\n /** Where the dsh entry is right now; recorded as the fast path. */\n resolvedEntry: string | null\n /**\n * The image this plugin was installed on: the dsh a boot entry must keep\n * running. Recorded and checked before anything else, so a copy that merely\n * reports a higher version never wins the boot.\n */\n pinnedEntry?: string | null\n /**\n * Adopt `pinnedEntry` even when the record already pins something else. Set\n * only where the caller *is* the running image; a process booted through the\n * launcher must carry the pin it started under instead of replacing it.\n */\n repin?: boolean\n /** The extension the launcher must match: a `.cjs` entry gets a `.cjs` launcher. */\n entryExtension?: string | null\n /**\n * Install roots that are neither the harness home nor PATH \u2014 a clone, or a\n * project whose own `node_modules` carries dsh. Recorded and searched, so a\n * moved build is re-found on the next boot.\n */\n searchRoots?: string[] | null\n}\n\nexport interface DshLauncherWrite {\n path: string\n changed: boolean\n}\n\nexport interface DshLauncherRecord {\n entry: string | null\n /**\n * The running image, while it still exists. A path here is an instruction:\n * `choose()` runs this one and never sorts it against another copy.\n */\n pinned?: string | null\n /** When that image was last confirmed. */\n pinnedAt?: number | null\n /** Where that entry's install sat, kept across rewrites while it stays valid. */\n roots: string[]\n writtenAt: number\n}\n\n/** The recorded entry a boot entry baked in, for the launcher's fast path. */\nexport function dshLauncherRecordPath(stateDir: string): string {\n return path.join(launcherDir(stateDir), 'dsh-resolved.json')\n}\n\n/** `bin/dsh.mjs`, or `bin/dsh.cjs` when the recorded entry is CommonJS. */\nexport function dshLauncherPath(stateDir: string, entryExtension?: string | null): string {\n const extension = (entryExtension ?? '').toLowerCase() === '.cjs' ? '.cjs' : '.mjs'\n return path.join(launcherDir(stateDir), `dsh${extension}`)\n}\n\nexport function readDshLauncherRecord(stateDir: string): DshLauncherRecord | null {\n return readJson<DshLauncherRecord>(dshLauncherRecordPath(stateDir))\n}\n\n/**\n * The dsh launcher's source.\n *\n * Like the home-hosted launcher, this exists because a clone's build output path\n * (or a versioned pnpm path) moves on the next install/rebuild: the boot entry\n * points at this stable script and the script re-finds dsh at every boot. A\n * `.cjs` entry gets a CommonJS launcher, so node loads it as one.\n */\nexport function buildDshLauncherSource(options: DshLauncherOptions): string {\n const esm = (options.entryExtension ?? '').toLowerCase() !== '.cjs'\n const prelude = esm\n ? `import fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport { spawnSync } from 'node:child_process'`\n : `const fs = require('node:fs')\nconst path = require('node:path')\nconst process = require('node:process')\nconst { spawnSync } = require('node:child_process')`\n // A clone or a project install is re-found from the install roots that were\n // recorded with it, so a moved build needs no new boot entry.\n const searchRoots = [\n options.resolvedEntry === null ? null : dshSearchRoot(options.resolvedEntry),\n options.pinnedEntry === null || options.pinnedEntry === undefined ? null : dshSearchRoot(options.pinnedEntry),\n ...(options.searchRoots ?? []),\n ].filter((root): root is string => typeof root === 'string' && root.length > 0)\n\n return `#!/usr/bin/env node\n// managed by dsh-home-hosted \u2014 rewritten on every plugin start; do not edit.\n${prelude}\n\nconst DSH_HOME = ${JSON.stringify(options.dshHome)}\nconst RECORD = ${JSON.stringify(dshLauncherRecordPath(options.stateDir))}\nconst SEARCH_ROOTS = ${JSON.stringify([...new Set(searchRoots)])}\n// The image this plugin was installed on, when the record had one to carry.\nconst PINNED = ${JSON.stringify(options.pinnedEntry ?? null)}\n\nfunction readRecord() {\n try { return JSON.parse(fs.readFileSync(RECORD, 'utf8')) } catch { return null }\n}\n\nfunction isFile(file) {\n let stat = null\n try { stat = fs.statSync(file) } catch { return false }\n return stat.isFile()\n}\n\n// One identity per file: a recorded path and a candidate reached through a\n// symlink must compare equal, or a macOS clone would look like a different copy.\nfunction canon(file) {\n try { return fs.realpathSync(file) } catch { return file }\n}\n\n// Keep what can actually boot: a JavaScript entry, or a shim whose recorded\n// target is one. A bare shim found on PATH is not a script, so requiring the\n// extension would silently drop the last resort.\n//\n// Each candidate carries how it was found, which is what ordering uses:\n// TIER_REAL a dsh by construction (a manifest, or node_modules/dsh)\n// TIER_PROBE a root that merely offers a build file \u2014 any package may\n// TIER_PATH a dsh executable found on PATH\nconst TIER_REAL = 2\nconst TIER_PROBE = 1\nconst TIER_PATH = 0\n\nfunction collect(out, entry, source, tier) {\n const argument = typeof source === 'string' && source.length > 0 ? source : entry\n const file = typeof entry === 'string' && isFile(entry) ? canon(entry) : null\n if (file !== null && /\\\\.(?:mjs|cjs|js)$/.test(file)) {\n if (!out.has(file) || (out.get(file) ?? 0) < tier) out.set(file, tier)\n return\n }\n // Anything else is a shim: only its recorded target can boot, and a shim\n // without one is not a candidate at all (never a second pass over itself).\n if (typeof argument !== 'string' || !isFile(argument)) return\n let text = ''\n try { text = fs.readFileSync(argument, 'utf8') } catch { return }\n const marker = 'cmd-shim-target='\n const at = text.indexOf(marker)\n if (at === -1) return\n const target = text.slice(at + marker.length).split('\\\\n')[0].trim()\n if (target.length === 0) return\n const real = canon(target)\n if (!/\\\\.(?:mjs|cjs|js)$/.test(real)) return\n if (!out.has(real) || (out.get(real) ?? 0) < tier) out.set(real, tier)\n}\n\nfunction collectStore(root, out) {\n const store = path.join(root, 'node_modules', '.pnpm')\n let names = []\n try { names = fs.readdirSync(store) } catch { return }\n for (const name of names) {\n if (!name.startsWith('dsh@') && !name.startsWith('@deepseek-ai+dsh@')) continue\n collect(out, path.join(store, name, 'node_modules', '@deepseek-ai', 'dsh', 'lib', 'bin.js'), null, TIER_REAL)\n collect(out, path.join(store, name, 'node_modules', 'dsh', 'bin', 'dsh.js'), null, TIER_REAL)\n }\n}\n\nfunction collectRoot(root, out) {\n collect(out, path.join(root, 'node_modules', '@deepseek-ai', 'dsh', 'lib', 'bin.js'), null, TIER_REAL)\n collect(out, path.join(root, 'node_modules', 'dsh', 'bin', 'dsh.js'), null, TIER_REAL)\n collectStore(root, out)\n // A clone keeps its build output at its own root, not under node_modules.\n for (const entry of ['lib/bin.js', 'lib/bin.mjs', 'lib/bin.cjs', 'dist/bin.js'])\n collect(out, path.join(root, entry), null, TIER_PROBE)\n}\n\nfunction candidates(skipPath) {\n const out = new Map()\n const record = readRecord()\n if (record && typeof record.entry === 'string') collect(out, record.entry, record.entry, TIER_REAL)\n for (const root of [...(Array.isArray(record?.roots) ? record.roots : []), ...SEARCH_ROOTS]) collectRoot(root, out)\n collectRoot(DSH_HOME, out)\n let profiles = []\n try { profiles = fs.readdirSync(path.join(DSH_HOME, 'profiles')) } catch {}\n for (const name of profiles) collectRoot(path.join(DSH_HOME, 'profiles', name), out)\n // Last resort: a dsh on the entry's own PATH. It is no longer an equal\n // candidate: a copy found there is only booted when nothing else answers.\n if (skipPath) return out\n for (const dir of (process.env.PATH ?? '').split(path.delimiter)) {\n const shim = path.join(dir, 'dsh')\n collect(out, shim, shim, TIER_PATH)\n collect(out, path.join(dir, 'dsh.cmd'), path.join(dir, 'dsh.cmd'), TIER_PATH)\n }\n return out\n}\n\nfunction versionOf(entry) {\n let dir = path.dirname(entry)\n for (let hop = 0; hop < 4; hop += 1) {\n try {\n const manifest = JSON.parse(fs.readFileSync(path.join(dir, 'package.json'), 'utf8'))\n if (typeof manifest.version === 'string') return manifest.version\n } catch {}\n dir = path.dirname(dir)\n }\n return null\n}\n\nfunction compare(a, b) {\n const split = value => {\n const [core, pre = null] = String(value).split('-', 2)\n return { parts: core.split('.').map(part => Number.parseInt(part, 10) || 0), pre }\n }\n const left = split(a); const right = split(b)\n for (let i = 0; i < 3; i += 1) {\n const diff = (left.parts[i] ?? 0) - (right.parts[i] ?? 0)\n if (diff !== 0) return diff < 0 ? -1 : 1\n }\n if (left.pre === right.pre) return 0\n if (left.pre === null) return 1\n if (right.pre === null) return -1\n return left.pre < right.pre ? -1 : 1\n}\n\nfunction findPin(pin, found) {\n if (typeof pin !== 'string' || pin.length === 0) return null\n const real = canon(pin)\n return found.find(item => item.entry === real)?.entry ?? null\n}\n\n// A root that only offers a build file (lib/bin.js) is a guess: any package may\n// have one. Those stay usable but never outrank a copy that is dsh by\n// construction, or a dsh executable from PATH below them.\nfunction bestOfTier(found, tier) {\n const at = found.filter(item => item.tier >= tier)\n const known = at.filter(item => item.version !== null)\n if (known.length > 0) {\n known.sort((a, b) => compare(b.version, a.version))\n return known[0].entry\n }\n return at[0]?.entry ?? null\n}\n\n// Choose the image this plugin was installed on, and only then fall back to a\n// version sort: a higher version elsewhere is not an invitation to boot it.\nfunction choose() {\n const record = readRecord()\n const pin = typeof record?.pinned === 'string' && record.pinned.length > 0 ? record.pinned : PINNED\n const found = [...candidates(true)].map(([entry, tier]) => ({ entry, tier, version: versionOf(entry) }))\n const pinned = findPin(pin, found)\n if (pinned !== null) return pinned\n // Real dsh installs first; a bare build-file guess only when nothing else\n // exists; PATH only when nothing local answered at all.\n return bestOfTier(found, TIER_REAL)\n ?? bestOfTier(found, TIER_PROBE)\n ?? bestOfTier([...candidates(false)].map(([entry, tier]) => ({ entry, tier, version: versionOf(entry) })), TIER_PATH)\n}\n\nconst entry = choose()\nif (entry === null) {\n console.error('[dsh-home-hosted] no dsh entry found for ' + DSH_HOME + ' (pinned path, recorded path, $DSH_HOME node_modules, profiles/*, PATH). Reinstall dsh, or restart: dsh web')\n process.exit(1)\n}\n\nconst args = process.argv.slice(2)\nconst result = /\\\\.(mjs|cjs|js)$/.test(entry)\n ? spawnSync(process.execPath, [entry, ...args], { stdio: 'inherit' })\n : spawnSync(entry, args, { stdio: 'inherit', shell: process.platform === 'win32' && /\\\\.(cmd|bat)$/i.test(entry) })\nprocess.exit(typeof result.status === 'number' ? result.status : 1)\n`\n}\n\n/** Write the launcher and record what `resolveDshLaunch` found. */\nexport function writeDshLauncher(options: DshLauncherOptions): DshLauncherWrite {\n const file = dshLauncherPath(options.stateDir, options.entryExtension)\n const previousRecord = readDshLauncherRecord(options.stateDir)\n // A pin is carried, never guessed. A caller that *is* the running image may\n // adopt a new one; everyone else keeps the existing pin while its file is\n // still a dsh entry. That is what stops a process booted through this\n // launcher from re-recording a weaker answer (its own `argv[1]` is the\n // launcher, so it resolves nothing local).\n const recordedPin = existingEntry(previousRecord?.pinned)\n const offered = existingEntry(options.pinnedEntry)\n const pinned = options.repin === true ? offered ?? recordedPin : recordedPin ?? offered\n const source = buildDshLauncherSource({\n ...options,\n pinnedEntry: pinned === null ? options.pinnedEntry ?? null : pinned,\n })\n const changed = (fs.existsSync(file) ? fs.readFileSync(file, 'utf8') : null) !== source\n if (changed) {\n writeFileAtomic(file, source, 0o755)\n fs.chmodSync(file, 0o755)\n }\n // Roots are kept while they still look like a dsh install: they are what lets\n // a later boot re-find dsh after this install has moved. The pinned image's\n // own root is kept too, or a rewrite (a process booted through this launcher\n // resolves nothing local) would drop the pin's only candidate.\n const raw = [dshSearchRoot(options.resolvedEntry), dshSearchRoot(pinned), ...(options.searchRoots ?? []), ...(previousRecord?.roots ?? [])]\n const known = raw.filter((root): root is string => typeof root === 'string' && root.length > 0 && dshRootUsable(root))\n const roots = [...new Set(known)]\n writeJsonAtomic(dshLauncherRecordPath(options.stateDir), {\n entry: options.resolvedEntry,\n pinned,\n pinnedAt: pinned === null\n ? null\n : pinned === recordedPin ? previousRecord?.pinnedAt ?? Date.now() : Date.now(),\n roots,\n writtenAt: Date.now(),\n } satisfies DshLauncherRecord, 0o600)\n return { path: file, changed }\n}\n\n/** The canonical path of a file that is still there, or null. */\nfunction existingEntry(file: string | null | undefined): string | null {\n if (typeof file !== 'string' || file.length === 0)\n return null\n if (!fs.existsSync(file))\n return null\n try {\n return fs.realpathSync(file)\n }\n catch {\n return null\n }\n}\n\n/** A dsh entry under one root: a flat install, or either pnpm store layout. */\nexport function dshCandidatesUnder(root: string): string[] {\n const out: string[] = []\n for (const entry of [\n path.join(root, 'node_modules', '@deepseek-ai', 'dsh', 'lib', 'bin.js'),\n path.join(root, 'node_modules', 'dsh', 'bin', 'dsh.js'),\n ]) {\n if (fs.existsSync(entry))\n out.push(entry)\n }\n const store = path.join(root, 'node_modules', '.pnpm')\n if (fs.existsSync(store)) {\n for (const name of fs.readdirSync(store)) {\n if (!name.startsWith('dsh@') && !name.startsWith('@deepseek-ai+dsh@'))\n continue\n for (const entry of [\n path.join(store, name, 'node_modules', '@deepseek-ai', 'dsh', 'lib', 'bin.js'),\n path.join(store, name, 'node_modules', 'dsh', 'bin', 'dsh.js'),\n ]) {\n if (fs.existsSync(entry))\n out.push(entry)\n }\n }\n }\n return out\n}\n\n/**\n * The install root a dsh entry belongs to: the directory whose `node_modules`\n * carries it \u2014 a clone root, or the project that installed dsh. That root is\n * what a rebuilt or upgraded install keeps.\n */\nexport function dshSearchRoot(entry: string | null | undefined): string | null {\n if (typeof entry !== 'string' || entry.length === 0)\n return null\n const parts = entry.split(path.sep)\n // A pnpm store path nests a second node_modules: the real root is the one\n // above `node_modules/.pnpm`, never the store's own package directory.\n const store = parts.lastIndexOf('.pnpm')\n const modules = parts.lastIndexOf('node_modules')\n const root = store > 0 ? parts.lastIndexOf('node_modules', store) : modules\n if (root > 0)\n return parts.slice(0, root).join(path.sep)\n // No node_modules anywhere: a source checkout whose build sits at its own\n // root. Derive it the way the runtime does \u2014 the manifest that owns the\n // entry, else the parent of the build directory \u2014 or the two would disagree\n // about which install a remembered path belongs to.\n return ownerRoot(entry) ?? (parts.length > 2 ? parts.slice(0, -2).join(path.sep) : null)\n}\n\n/** The nearest ancestor whose manifest declares this entry as its bin. */\nfunction ownerRoot(entry: string): string | null {\n let dir = path.dirname(entry)\n for (let hop = 0; hop < 4; hop += 1) {\n const manifestPath = path.join(dir, 'package.json')\n const manifest = readJson<{ bin?: string | Record<string, string> }>(manifestPath)\n if (manifest !== null) {\n const declared = typeof manifest.bin === 'string'\n ? manifest.bin\n : manifest.bin?.['dsh'] ?? Object.values(manifest.bin ?? {})[0]\n if (typeof declared === 'string' && path.resolve(path.dirname(manifestPath), declared) === entry)\n return dir\n }\n const parent = path.dirname(dir)\n if (parent === dir)\n break\n dir = parent\n }\n return null\n}\n\n/** Whether a root still holds a dsh install, or the store one could land in. */\nfunction dshRootUsable(root: string): boolean {\n return dshCandidatesUnder(root).length > 0\n || fs.existsSync(path.join(root, 'node_modules', '.pnpm'))\n // A clone's build output sits at its own root, not under node_modules.\n || ['lib/bin.js', 'lib/bin.mjs', 'lib/bin.cjs', 'dist/bin.js'].some(entry => fs.existsSync(path.join(root, entry)))\n}\n\n", "/**\n * Which home-hosted panels exist on this machine.\n *\n * `home-hosted` is not one panel per machine: a person can install it per\n * project, or run several panels from one install with different `--home`\n * roots. This plugin drives exactly one of them \u2014 the root it resolved \u2014 so\n * discovery exists to name the others, to keep every action on the panel this\n * plugin owns, and to tell the agent that the machine has more than one.\n *\n * Discovery is bounded on purpose: the roots the plugin already knows (the\n * managed one, `$HHOSTED_HOME`, the operator's declared ones) and the\n * `~/.home-hosted*` siblings. A `--home` somewhere else on the disk is not\n * found by scanning for it; it is declared through `instanceRoots`.\n */\nimport fs from 'node:fs'\nimport os from 'node:os'\nimport path from 'node:path'\nimport process from 'node:process'\nimport type { InstanceSource, InstanceView } from '../shared/contracts.js'\nimport { expandHome } from '../util/paths.js'\nimport { readConfig } from './config-file.js'\nimport { defaultWorkspace, isHhRoot, isLegacyRoot, runFile } from './layout.js'\nimport { pidAlive, readRuntime } from './runtime.js'\n\nexport interface InstanceDiscoveryOptions {\n /** The state root this plugin drives. */\n managedHome: string\n /** Extra roots an operator declared in the plugin row. */\n extraRoots?: readonly string[]\n /** Test seam: this process's `$HHOSTED_HOME`. */\n envHome?: string | null\n /** Test seam: `HHOSTED_SERVER_ID`, or null when the panel does not supervise us. */\n hostingEntryId?: string | null\n /** Test seam: the user's home directory. */\n homeDir?: string\n /** Test seam: directory listing. */\n listDir?: (dir: string) => string[] | null\n /** Test seam: whether a pid is alive. */\n alive?: (pid: number) => boolean\n}\n\n/** A sibling state root of the default one, e.g. `~/.home-hosted-staging`. */\nconst SIBLING_PATTERN = /^\\.home-hosted(?:[._-].*)?$/\n\n/** A path as one identity: `~` expanded, symlinks resolved (macOS `/var`). */\nexport function canonicalPath(value: string): string {\n const resolved = path.resolve(expandHome(value))\n try {\n return fs.realpathSync.native(resolved)\n }\n catch {\n return resolved\n }\n}\n\nfunction defaultListDir(dir: string): string[] | null {\n try {\n return fs.readdirSync(dir)\n }\n catch {\n return null\n }\n}\n\n/** A directory that holds a panel's state: a 0.7 `.hh`, a pre-0.7 root, or a runtime it wrote. */\nfunction isStateRoot(home: string): boolean {\n try {\n if (!fs.statSync(home).isDirectory())\n return false\n }\n catch {\n return false\n }\n if (isHhRoot(home) || isLegacyRoot(home))\n return true\n return fs.existsSync(runFile(home))\n}\n\nfunction viewOf(candidate: { home: string, source: InstanceSource }, managed: string, envHome: string | null, hosting: boolean, alive: (pid: number) => boolean): InstanceView {\n const runtime = readRuntime(candidate.home)\n // Another panel's entries are counted in the workspace it would default to;\n // its own managed workspace is not knowable from here.\n const read = readConfig(candidate.home, defaultWorkspace(candidate.home))\n return {\n home: candidate.home,\n managed: candidate.home === managed,\n hosting: hosting && candidate.home === envHome,\n url: runtime?.url ?? null,\n port: runtime?.port ?? null,\n pid: runtime?.pid ?? null,\n version: runtime?.version ?? null,\n // A `run.json` names the process that wrote it; a recycled pid is not worth\n // chasing here, because a stale one only mislabels a panel nobody acts on.\n running: runtime !== null && alive(runtime.pid),\n projectDir: runtime?.projectDir ?? null,\n servers: read.error === null ? (read.raw?.servers ?? []).length : null,\n source: candidate.source,\n }\n}\n\n/** Every panel found, the managed one first and the rest by state root. */\nexport function discoverInstances(options: InstanceDiscoveryOptions): InstanceView[] {\n const managed = canonicalPath(options.managedHome)\n const listDir = options.listDir ?? defaultListDir\n const alive = options.alive ?? pidAlive\n const envRaw = options.envHome?.trim()\n const envHome = envRaw !== undefined && envRaw.length > 0 ? canonicalPath(envRaw) : null\n const hostingEntryId = options.hostingEntryId?.trim() ?? ''\n\n const candidates: Array<{ home: string, source: InstanceSource }> = [{ home: managed, source: 'managed' }]\n if (envHome !== null)\n candidates.push({ home: envHome, source: 'env' })\n for (const root of options.extraRoots ?? []) {\n if (root.trim().length > 0)\n candidates.push({ home: canonicalPath(root), source: 'configured' })\n }\n const homeDir = options.homeDir ?? os.homedir()\n for (const name of listDir(homeDir) ?? []) {\n if (SIBLING_PATTERN.test(name))\n candidates.push({ home: canonicalPath(path.join(homeDir, name)), source: 'sibling' })\n }\n\n const seen = new Set<string>()\n const views: InstanceView[] = []\n for (const candidate of candidates) {\n if (seen.has(candidate.home))\n continue\n seen.add(candidate.home)\n // The managed root is listed even before its first start \u2014 it is the panel\n // the plugin drives. Any other candidate must hold a panel's state to count.\n if (candidate.source !== 'managed' && !isStateRoot(candidate.home))\n continue\n views.push(viewOf(candidate, managed, envHome, hostingEntryId.length > 0, alive))\n }\n views.sort((left, right) => (left.managed === right.managed ? left.home.localeCompare(right.home) : left.managed ? -1 : 1))\n return views\n}\n\n/**\n * One line naming a panel, for a prompt note or a refusal. A root whose\n * `run.json` is gone has no runtime to report \u2014 home-hosted drops a stale one \u2014\n * so it is never called \"stopped\".\n */\nexport function describeInstance(instance: InstanceView): string {\n if (instance.url === null)\n return `${instance.home} (no runtime)`\n return `${instance.home} (${instance.url}${instance.running ? '' : ', stopped'})`\n}\n\n/**\n * The note the agent is given when the machine holds more than one panel.\n *\n * `null` for a single panel: the plugin's preference is only worth stating when\n * there is something to prefer it over.\n */\nexport function instancesNoticeText(instances: readonly InstanceView[]): string | null {\n const managed = instances.find(instance => instance.managed)\n const others = instances.filter(instance => !instance.managed)\n if (managed === undefined || others.length === 0)\n return null\n return [\n `home-hosted: ${instances.length} panels were found on this machine, and this plugin manages only ${describeInstance(managed)}.`,\n `Other panels: ${others.map(describeInstance).join('; ')}.`,\n 'Act on the managed panel unless the user names another; a home_hosted_* call takes \"instance\" to name one.',\n 'Naming another panel is allowed for methods that can reach it: the plugin asks the user how, then edits that panel\\'s config file, runs the CLI against its state root, or \u2014 only if they choose it \u2014 mints a token for that panel and uses its API.',\n ].join(' ')\n}\n", "/**\n * Facts about the running home-hosted panel, read from the `run.json` it writes\n * at startup. Its token field is deliberately never read: the plugin\n * authenticates with its own enrolled API token instead.\n */\nimport http from 'node:http'\nimport https from 'node:https'\nimport process from 'node:process'\nimport { readJson } from '../util/fsx.js'\nimport { runFile } from './layout.js'\n\nexport interface PanelRuntime {\n version: string\n pid: number\n url: string\n probeUrl?: string\n protocol?: string\n port: number\n bindHost?: string\n projectDir?: string\n dataRoot?: string\n configPath?: string\n logFile?: string\n}\n\nexport function readRuntime(home: string): PanelRuntime | null {\n const raw = readJson<Record<string, unknown>>(runFile(home))\n if (raw === null)\n return null\n const url = typeof raw.url === 'string' ? raw.url : null\n const pid = typeof raw.pid === 'number' ? raw.pid : null\n if (url === null || pid === null)\n return null\n return {\n version: typeof raw.version === 'string' ? raw.version : 'unknown',\n pid,\n url: url.replace(/\\/+$/, ''),\n probeUrl: typeof raw.probeUrl === 'string' ? raw.probeUrl : undefined,\n protocol: typeof raw.protocol === 'string' ? raw.protocol : undefined,\n port: typeof raw.port === 'number' ? raw.port : 0,\n bindHost: typeof raw.bindHost === 'string' ? raw.bindHost : undefined,\n projectDir: typeof raw.projectDir === 'string' ? raw.projectDir : undefined,\n dataRoot: typeof raw.dataRoot === 'string' ? raw.dataRoot : undefined,\n configPath: typeof raw.configPath === 'string' ? raw.configPath : undefined,\n logFile: typeof raw.logFile === 'string' ? raw.logFile : undefined,\n }\n}\n\nexport function pidAlive(pid: number): boolean {\n try {\n process.kill(pid, 0)\n return true\n }\n catch {\n return false\n }\n}\n\n/**\n * Ask the panel's public health route whether it is answering. Uses the raw\n * http/https modules so a self-signed TLS pair answers too.\n */\nexport async function probePanel(url: string, timeoutMs = 2000): Promise<boolean> {\n const target = `${url.replace(/\\/+$/, '')}/healthz`\n return await new Promise<boolean>((resolve) => {\n let settled = false\n const done = (value: boolean): void => {\n if (settled)\n return\n settled = true\n resolve(value)\n }\n\n let parsed: URL\n try {\n parsed = new URL(target)\n }\n catch {\n done(false)\n return\n }\n\n const transport = parsed.protocol === 'https:' ? https : http\n const request = transport.get(\n {\n hostname: parsed.hostname,\n port: parsed.port,\n path: `${parsed.pathname}${parsed.search}`,\n timeout: timeoutMs,\n rejectUnauthorized: false,\n },\n (response) => {\n response.resume()\n done(typeof response.statusCode === 'number')\n },\n )\n request.on('timeout', () => {\n request.destroy()\n done(false)\n })\n request.on('error', () => done(false))\n })\n}\n", "/**\n * What this plugin decides about a server entry, and how that intent maps onto\n * the entry: only the keys in `OWNED_ENTRY_KEYS` are ever written, so a person's\n * own command, args, env, health block and everything else survive untouched.\n */\nimport type { EntryIntent, OnPortConflict, ServerEntry, ServerEntryPatch } from '../shared/contracts.js'\nimport { isOnPortConflict } from '../shared/contracts.js'\n\n/**\n * A detached restart of dsh is recognisable by its own marker/argv on POSIX, so\n * `follow` adopts it without killing anything. Windows cannot read another\n * process's environment and a `.cmd` shim hides the argv, so only `kill`\n * reliably reclaims the port there.\n */\nexport function defaultOnPortConflict(platform: NodeJS.Platform = process.platform): OnPortConflict {\n return platform === 'win32' ? 'kill' : 'follow'\n}\n\nexport function defaultIntent(id: string, platform: NodeJS.Platform = process.platform): EntryIntent {\n return {\n id,\n autostart: true,\n onPortConflict: defaultOnPortConflict(platform),\n stopKillPortHolders: true,\n persistent: true,\n }\n}\n\n/** The owned keys as a patch, preserving any sibling keys inside `stop`. */\nexport function ownedPatch(intent: EntryIntent, live?: ServerEntry | null): ServerEntryPatch {\n const stop = (live?.stop ?? {}) as Record<string, unknown>\n return {\n autostart: intent.autostart,\n onPortConflict: intent.onPortConflict,\n persistent: intent.persistent,\n stop: { ...stop, killPortHolders: intent.stopKillPortHolders },\n }\n}\n\n/** Owned keys whose live value differs from the intent. */\nexport function ownedDrift(live: ServerEntry | null, intent: EntryIntent): string[] {\n if (live === null)\n return ['missing entry']\n const drift: string[] = []\n if (live.autostart !== intent.autostart)\n drift.push('autostart')\n if (live.onPortConflict !== intent.onPortConflict)\n drift.push('onPortConflict')\n if ((live.persistent ?? false) !== intent.persistent)\n drift.push('persistent')\n const killPortHolders = (live.stop as Record<string, unknown> | undefined)?.killPortHolders\n if (killPortHolders !== intent.stopKillPortHolders)\n drift.push('stop.killPortHolders')\n return drift\n}\n\n/**\n * Only the owned keys, as they were before this plugin touched the entry.\n *\n * A key the entry left out is recorded as the panel's schema default rather than\n * omitted: `{ id }` alone is what marks an entry this plugin created, so an\n * adopted entry with nothing explicit has to be distinguishable from it \u2014 or\n * \"stop managing\" deletes a server the person wrote.\n */\n/**\n * Whether a snapshot marks an entry this plugin created rather than adopted.\n *\n * `{ id }` alone is that marker: a created entry carried nothing else when the\n * plugin first saw it, so its command is the plugin's to set.\n */\nexport function isCreatedEntry(snapshot: ServerEntry | undefined): boolean {\n if (snapshot === undefined)\n return false\n return Object.keys(snapshot).length === 1 && typeof snapshot.id === 'string'\n}\n\nexport function snapshotOwned(live: ServerEntry): ServerEntry {\n const killPortHolders = (live.stop as Record<string, unknown> | undefined)?.killPortHolders\n return {\n id: live.id,\n autostart: typeof live.autostart === 'boolean' ? live.autostart : false,\n onPortConflict: isOnPortConflict(live.onPortConflict) ? live.onPortConflict : 'block',\n persistent: live.persistent === true,\n stop: { killPortHolders: typeof killPortHolders === 'boolean' ? killPortHolders : false },\n }\n}\n\n/**\n * Restore an entry to what it was before adoption. A key the snapshot does not\n * carry was inherited from the panel's defaults, and a PATCH cannot delete a\n * key, so it is restored to that schema default instead.\n */\nexport function restorePatch(live: ServerEntry, snapshot: ServerEntry | null): ServerEntryPatch {\n const stop = (live.stop ?? {}) as Record<string, unknown>\n const previous = (snapshot?.stop ?? {}) as Record<string, unknown>\n return {\n autostart: snapshot?.autostart ?? false,\n onPortConflict: snapshot?.onPortConflict ?? 'block',\n persistent: snapshot?.persistent === true,\n stop: {\n ...stop,\n killPortHolders: typeof previous.killPortHolders === 'boolean' ? previous.killPortHolders : false,\n },\n }\n}\n", "/**\n * Starting, replacing and globally installing the panel.\n *\n * Starting it is the same thing a person does by hand: run the CLI's `up`, which\n * re-spawns itself detached, writes `run.json` and returns once the panel\n * answers. Replacing a panel *is* different \u2014 stopping the one that is answering\n * also stops the servers it supervises, including the dsh this plugin runs in \u2014\n * so the replacement is done by a detached helper that outlives this process.\n */\nimport { spawn } from 'node:child_process'\nimport type { ChildProcess } from 'node:child_process'\nimport fs from 'node:fs'\nimport path from 'node:path'\nimport process from 'node:process'\nimport type { PanelControlResult } from '../shared/contracts.js'\nimport { run } from '../util/exec.js'\nimport type { RunResult } from '../util/exec.js'\nimport { writeFileAtomic } from '../util/fsx.js'\nimport type { CliLaunch } from './launch.js'\nimport { launcherDir } from './launcher.js'\n\n/** How the panel's CLI is run: the resolved argv plus an environment. */\nexport type PanelCliExec = (args: string[], env: Record<string, string | undefined>) => Promise<RunResult>\n\nexport interface PanelControlDeps {\n /** The CLI to run, already resolved. */\n launch: CliLaunch | null\n home: string\n projectDir?: string | null\n stateDir: string\n /** Environment for the spawned CLI (absolute PATH, HOME, HHOSTED_HOME). */\n env: Record<string, string>\n timeoutMs?: number\n /** Test seam: run the resolved CLI without spawning it. */\n exec?: PanelCliExec\n}\n\nfunction cliArgs(deps: PanelControlDeps, command: 'up' | 'down' | 'restart'): string[] {\n const args = [...(deps.launch?.args ?? []), command, '--home', deps.home]\n if (deps.projectDir)\n args.push('--project', deps.projectDir)\n return args\n}\n\nasync function runCli(deps: PanelControlDeps, command: 'up' | 'down' | 'restart'): Promise<{ code: number | null, stdout: string, stderr: string, error: string | null }> {\n const args = cliArgs(deps, command)\n if (deps.exec !== undefined)\n return await deps.exec(args, deps.env)\n const launch = deps.launch\n if (launch === null)\n return { code: null, stdout: '', stderr: 'no home-hosted CLI is available', error: null }\n return await run(launch.program, args, {\n env: deps.env,\n timeoutMs: deps.timeoutMs ?? 90_000,\n })\n}\n\n/** Start the preferred CLI as a detached panel; the CLI itself does the detaching. */\nexport async function startPanel(deps: PanelControlDeps): Promise<PanelControlResult> {\n const result = await runCli(deps, 'up')\n if (result.code !== 0) {\n const detail = result.stderr.trim() || result.stdout.trim() || `the CLI exited ${String(result.code)}`\n return { ok: false, detail }\n }\n const match = /https?:\\/\\/[^\\s]+/.exec(result.stdout)\n return { ok: true, detail: 'the panel is answering', url: match?.[0] ?? null }\n}\n\n/**\n * Stop the panel through its own CLI.\n *\n * The panel is the parent of every server it supervises, so this stops those\n * too \u2014 including the dsh this plugin may be running in. That is the point of\n * the button, not a hazard to hide: the caller's UI says so. An entry with\n * `autostart` is started again on the next panel start; it is not restarted here.\n */\nexport async function stopPanel(deps: PanelControlDeps): Promise<PanelControlResult> {\n const result = await runCli(deps, 'down')\n if (result.code !== 0) {\n const detail = result.error ?? (result.stderr.trim() || result.stdout.trim() || `the CLI exited ${String(result.code)}`)\n return { ok: false, detail: `the panel did not stop: ${detail}` }\n }\n return { ok: true, detail: 'the panel stopped; the servers it supervised stopped with it' }\n}\n\nexport function takeoverHelperPath(stateDir: string): string {\n return path.join(launcherDir(stateDir), 'panel-takeover.mjs')\n}\n\nexport function takeoverLogPath(stateDir: string): string {\n return path.join(launcherDir(stateDir), 'panel-takeover.log')\n}\n\nexport function activationHelperPath(stateDir: string): string {\n return path.join(launcherDir(stateDir), 'panel-activate.mjs')\n}\n\nexport function activationLogPath(stateDir: string): string {\n return path.join(launcherDir(stateDir), 'panel-activate.log')\n}\n\n/**\n * The generated helper that hands the panel to an autostart mechanism.\n *\n * It has to outlive this process for the same reason the takeover helper does:\n * stopping the panel also stops the servers it supervises, and the plugin is\n * usually one of them, so nothing here may wait on the answer.\n *\n * The order is the whole design. `files` and `requires` are proven first, while\n * the panel is still up: a start that could never work must never be attempted\n * after the old panel is already gone. Then the panel is stopped \u2014 the CLI's\n * `down`, which is the only stop that also reports whether the pid went \u2014 because\n * a start over a *running* panel is refused by home-hosted itself (`run.json`\n * names a live pid). Only then does the start command run, and only after it\n * succeeded are the other mechanisms' entries removed: retiring them any earlier\n * would delete the entry that is currently keeping the panel alive.\n */\nexport interface ActivationPlan {\n /** Steps that start the panel under the mechanism, in order. */\n commands: string[][]\n /** Proof commands that must exit 0 before the panel is touched. */\n requires: string[][]\n /** Files that must exist before the panel is touched. */\n files: string[]\n /** The CLI's own stop, which also reports whether the panel went. */\n stop: string[]\n /**\n * Steps that stop the panel the *previous* mechanism supervises, run before\n * the CLI's `down`. Without them a switch loses the race: the old entry's own\n * restart policy brings the panel back as soon as `down` returns, before the\n * new entry starts one.\n */\n detach: string[][]\n /** The pid of the panel being replaced; escalation waits on it. */\n oldPid: number | null\n /** Steps that remove the other mechanisms' entries, run after the start. */\n retire: string[][]\n /** The log a person reads when the panel does not come back. */\n logPath: string\n}\n\n/** The generated helper's source; exported so a test can inspect it. */\nexport function buildActivationSource(plan: ActivationPlan, marker = 'managed by dsh-home-hosted'): string {\n return `#!/usr/bin/env node\n// ${marker} \u2014 starts the panel through its autostart entry; do not edit.\nimport fs from 'node:fs'\nimport { spawnSync } from 'node:child_process'\n\nconst STEPS = ${JSON.stringify(plan.commands)}\nconst REQUIRES = ${JSON.stringify(plan.requires)}\nconst FILES = ${JSON.stringify(plan.files)}\nconst STOP = ${JSON.stringify(plan.stop)}\nconst DETACH = ${JSON.stringify(plan.detach)}\nconst OLD_PID = ${JSON.stringify(plan.oldPid)}\nconst RETIRE = ${JSON.stringify(plan.retire)}\nconst LOG = ${JSON.stringify(plan.logPath)}\n\nfunction log(line) {\n try { fs.appendFileSync(LOG, new Date().toISOString() + ' ' + line + '\\\\n') } catch {}\n}\n\nfunction fail(line) {\n log(line)\n process.exit(1)\n}\n\nfunction alive(pid) {\n if (typeof pid !== 'number') return false\n try { process.kill(pid, 0); return true } catch { return false }\n}\n\nconst sleep = ms => new Promise(resolve => setTimeout(resolve, ms))\n\n// Everything is proven while the panel is still answering: a start that could\n// never work must not be attempted after the old panel is already gone.\nfor (const file of FILES) {\n if (!fs.existsSync(file))\n fail('refusing to stop the panel: ' + file + ' is not there')\n}\nfor (const probe of REQUIRES) {\n const result = spawnSync(probe[0], probe.slice(1), { stdio: 'ignore' })\n if (result.error || result.status !== 0)\n fail('refusing to stop the panel: ' + probe.join(' ') + ' did not confirm (' + String(result.status ?? result.error) + ')')\n}\n\n// A panel the previous mechanism supervises must be stopped through that\n// mechanism first: its restart policy would revive it the moment 'down' ends.\nfor (const step of DETACH) {\n log('detaching from the previous autostart entry: ' + step.join(' '))\n const result = spawnSync(step[0], step.slice(1), { stdio: 'ignore' })\n log('exited ' + String(result.status ?? result.error))\n}\n\n// The panel has to be gone before the entry runs: home-hosted refuses to start a\n// second panel while run.json names a live pid, so starting over a running one\n// leaves the entry failed while the old panel keeps the port.\nlog('stopping the answering panel: ' + STOP.join(' '))\nconst stopped = spawnSync(STOP[0], STOP.slice(1), { stdio: 'ignore', timeout: 90000 })\nlog('down exited ' + String(stopped.status ?? stopped.error))\n\nif (alive(OLD_PID)) {\n log('the panel is still alive; sending SIGTERM')\n try { process.kill(OLD_PID, 'SIGTERM') } catch {}\n for (let i = 0; i < 40 && alive(OLD_PID); i += 1)\n await sleep(250)\n}\n\nif (alive(OLD_PID)) {\n log('still alive; sending SIGKILL')\n try { process.kill(OLD_PID, 'SIGKILL') } catch {}\n for (let i = 0; i < 20 && alive(OLD_PID); i += 1)\n await sleep(250)\n}\n\nfor (const step of STEPS) {\n log('running ' + step.join(' '))\n const result = spawnSync(step[0], step.slice(1), { stdio: 'ignore' })\n log('exited ' + String(result.status ?? result.error))\n if (result.error || result.status !== 0)\n fail(step.join(' ') + ' did not succeed, so the panel is down')\n}\n\n// Only now: the entry being removed may be the one that just started the panel,\n// and a failure to remove it is logged rather than fatal \u2014 the panel is up.\nfor (const step of RETIRE) {\n log('retiring ' + step.join(' '))\n const result = spawnSync(step[0], step.slice(1), { stdio: 'ignore' })\n log('exited ' + String(result.status ?? result.error))\n}\n\nlog('the autostart entry was told to start the panel')\n`\n}\n\n/** The generated helper's source; exported so a test can inspect it. */\nexport function buildTakeoverSource(deps: PanelControlDeps, oldPid: number | null, marker = 'managed by dsh-home-hosted'): string {\n const program = deps.launch?.program ?? process.execPath\n const args = deps.launch === null\n ? []\n : [...deps.launch.args, 'up', '--home', deps.home, ...(deps.projectDir ? ['--project', deps.projectDir] : [])]\n const downArgs = [...(deps.launch?.args ?? []), 'down', '--home', deps.home]\n return `#!/usr/bin/env node\n// ${marker} \u2014 replaces the panel; do not edit.\nimport fs from 'node:fs'\nimport { spawnSync } from 'node:child_process'\n\nconst OLD_PID = ${JSON.stringify(oldPid)}\nconst DOWN_ARGS = ${JSON.stringify(downArgs)}\nconst PROGRAM = ${JSON.stringify(program)}\nconst ARGS = ${JSON.stringify(args)}\nconst ENV = ${JSON.stringify(deps.env)}\nconst LOG = ${JSON.stringify(takeoverLogPath(deps.stateDir))}\n\nfunction log(line) {\n try { fs.appendFileSync(LOG, new Date().toISOString() + ' ' + line + '\\\\n') } catch {}\n}\n\nfunction alive(pid) {\n if (typeof pid !== 'number') return false\n try { process.kill(pid, 0); return true } catch { return false }\n}\n\nconst sleep = ms => new Promise(resolve => setTimeout(resolve, ms))\n\nlog('stopping the answering panel: ' + PROGRAM + ' ' + DOWN_ARGS.join(' '))\nconst down = spawnSync(PROGRAM, DOWN_ARGS, { env: { ...process.env, ...ENV }, stdio: 'ignore' })\nlog('down exited ' + String(down.status))\n\nfor (let i = 0; i < 40 && alive(OLD_PID); i += 1)\n await sleep(250)\n\nif (alive(OLD_PID)) {\n log('still alive; sending SIGTERM')\n try { process.kill(OLD_PID, 'SIGTERM') } catch {}\n for (let i = 0; i < 40 && alive(OLD_PID); i += 1)\n await sleep(250)\n}\n\nif (alive(OLD_PID)) {\n log('still alive; sending SIGKILL')\n try { process.kill(OLD_PID, 'SIGKILL') } catch {}\n for (let i = 0; i < 20 && alive(OLD_PID); i += 1)\n await sleep(250)\n}\n\nlog('starting ' + PROGRAM + ' ' + ARGS.join(' '))\nconst result = spawnSync(PROGRAM, ARGS, { env: { ...process.env, ...ENV }, stdio: 'ignore' })\nlog('started with exit ' + String(result.status))\n`\n}\n\nexport function writeTakeoverHelper(deps: PanelControlDeps, oldPid: number | null): string {\n const file = takeoverHelperPath(deps.stateDir)\n writeFileAtomic(file, buildTakeoverSource(deps, oldPid), 0o755)\n fs.chmodSync(file, 0o755)\n return file\n}\n\nexport function writeActivationHelper(stateDir: string, plan: ActivationPlan): string {\n const file = activationHelperPath(stateDir)\n writeFileAtomic(file, buildActivationSource(plan), 0o755)\n fs.chmodSync(file, 0o755)\n return file\n}\n\n/**\n * Spawn the activation helper detached and return immediately: the panel's\n * shutdown is about to stop this very process when the plugin runs under it, so\n * waiting for the result would lose the answer.\n *\n * The helper is left to it: whatever it does to the panel, the entry it starts\n * is the one that brings the plugin back.\n */\nexport function spawnActivation(stateDir: string, plan: ActivationPlan, spawnChild: SpawnDetached = spawnDetached): PanelControlResult {\n const helper = writeActivationHelper(stateDir, plan)\n let child: ChildProcess\n try {\n child = spawnChild(process.execPath, [helper])\n }\n catch (error) {\n return { ok: false, detail: `could not start the autostart helper: ${error instanceof Error ? error.message : String(error)}` }\n }\n child.on('error', () => {})\n child.unref()\n return {\n ok: true,\n detail: `starting the panel through its autostart entry now; this page disconnects and comes back when it answers`,\n }\n}\n\n/** Spawn a helper process; injected so a test can drive the failure paths. */\nexport type SpawnDetached = (program: string, args: string[]) => ChildProcess\n\nfunction spawnDetached(program: string, args: string[]): ChildProcess {\n return spawn(program, args, { detached: true, stdio: 'ignore' })\n}\n\n/**\n * Spawn the helper detached and return immediately: the old panel's shutdown is\n * about to stop this very process, so waiting for the result would lose it.\n */\nexport function spawnTakeover(deps: PanelControlDeps, oldPid: number | null, spawnChild: SpawnDetached = spawnDetached): PanelControlResult {\n const helper = writeTakeoverHelper(deps, oldPid)\n let child: ChildProcess\n try {\n child = spawnChild(process.execPath, [helper])\n }\n catch (error) {\n return { ok: false, detail: `could not start the panel-replacement helper: ${error instanceof Error ? error.message : String(error)}` }\n }\n // A detached child reports a failed start asynchronously; an unhandled 'error'\n // event is thrown, and that would end this process instead of the helper's.\n child.on('error', () => {})\n child.unref()\n return {\n ok: true,\n detail: 'replacing the panel now; this page will disconnect and come back under the preferred copy',\n }\n}\n\n/** Install the pinned range globally, so the `global` preference has something to use. */\nexport async function installGlobal(\n range: string,\n options: { pnpm?: boolean, run?: typeof run } = {},\n): Promise<{ ok: boolean, detail: string, output: string }> {\n const execute = options.run ?? run\n const usePnpm = options.pnpm ?? true\n const result = usePnpm\n ? await execute('pnpm', ['add', '-g', `home-hosted@${range}`], { timeoutMs: 300_000 })\n : await execute('npm', ['install', '-g', `home-hosted@${range}`], { timeoutMs: 300_000 })\n const output = `${result.stdout}\\n${result.stderr}`.trim()\n if (result.code !== 0)\n return { ok: false, detail: result.error ?? `the installer exited ${String(result.code)}`, output }\n return { ok: true, detail: `installed home-hosted@${range} globally`, output }\n}\n", "/**\n * The home-hosted HTTP API client.\n *\n * Every call carries the plugin's own API token; the panel verifies it from its\n * secrets file on each request. A write through this client is the safe path:\n * the config store records the bytes it wrote, so the panel's own file watcher\n * never treats the change as an external edit.\n */\nimport type { ServerEntry, ServerEntryPatch, ServerEntryView } from '../shared/contracts.js'\n\n/** What the panel's `/api/servers*` actually answers with: it names the workspace `workspaceId`. */\ninterface ApiServerView extends Omit<ServerEntryView, 'workspace'> {\n workspaceId?: string\n}\n\nexport class PanelError extends Error {\n constructor(\n message: string,\n readonly code: string,\n readonly status: number | null = null,\n /** The panel's own diagnostic, when it sent one. */\n readonly detail: unknown = undefined,\n ) {\n super(message)\n this.name = 'PanelError'\n }\n}\n\nexport interface PanelClientOptions {\n baseUrl: string\n token: string\n timeoutMs?: number\n /** The workspace every call acts on unless it names another one. */\n workspace?: string\n}\n\nexport class PanelClient {\n private readonly baseUrl: string\n private readonly token: string\n private readonly timeoutMs: number\n private readonly workspace: string | null\n\n constructor(options: PanelClientOptions) {\n this.baseUrl = options.baseUrl.replace(/\\/+$/, '')\n this.token = options.token\n this.timeoutMs = options.timeoutMs ?? 10_000\n this.workspace = options.workspace ?? null\n }\n\n /**\n * A server path for one workspace. Server ids are only unique inside a\n * workspace, so `?workspace=` is what selects which one is meant; omitting it\n * on a panel that predates workspaces changes nothing.\n */\n /**\n * The panel names a server's workspace `workspaceId`; the plugin's view calls it\n * `workspace`. One mapping, so a caller never has to know both spellings.\n */\n private view(raw: ApiServerView, workspace?: string | null): ServerEntryView {\n const { workspaceId, ...rest } = raw\n return { ...rest, workspace: workspaceId ?? workspace ?? this.workspace ?? '' }\n }\n\n private ws(path: string, workspace?: string | null): string {\n const id = workspace ?? this.workspace\n if (id === null || id === undefined || id.length === 0)\n return path\n return `${path}${path.includes('?') ? '&' : '?'}workspace=${encodeURIComponent(id)}`\n }\n\n private async request<T>(method: string, path: string, body?: unknown): Promise<T> {\n let response: Response\n try {\n response = await fetch(`${this.baseUrl}${path}`, {\n method,\n headers: {\n authorization: `Bearer ${this.token}`,\n ...(body === undefined ? {} : { 'content-type': 'application/json' }),\n },\n body: body === undefined ? undefined : JSON.stringify(body),\n signal: AbortSignal.timeout(this.timeoutMs),\n })\n }\n catch (error) {\n throw new PanelError(\n error instanceof Error ? error.message : String(error),\n 'PANEL_UNREACHABLE',\n )\n }\n\n const text = await response.text()\n let parsed: unknown = null\n try {\n parsed = text.length > 0 ? JSON.parse(text) : null\n }\n catch {\n throw new PanelError(`the panel answered ${response.status} with a non-JSON body`, 'PANEL_BAD_RESPONSE', response.status)\n }\n\n if (!response.ok) {\n const record = parsed as { message?: unknown, code?: unknown, detail?: unknown } | null\n const message = typeof record?.message === 'string' ? record.message : `the panel answered ${response.status}`\n const code = typeof record?.code === 'string' ? record.code : 'PANEL_ERROR'\n throw new PanelError(message, code, response.status, record?.detail)\n }\n\n return parsed as T\n }\n\n async listServers(workspace?: string): Promise<ServerEntryView[]> {\n const answer = await this.request<{ servers: ApiServerView[] }>('GET', this.ws('/api/servers', workspace))\n return answer.servers.map(server => this.view(server, workspace))\n }\n\n async getServer(id: string, workspace?: string): Promise<ServerEntryView> {\n const answer = await this.request<{ server: ApiServerView }>('GET', this.ws(`/api/servers/${encodeURIComponent(id)}`, workspace))\n return this.view(answer.server, workspace)\n }\n\n async createServer(entry: ServerEntry, workspace?: string): Promise<ServerEntryView> {\n const answer = await this.request<{ server: ApiServerView }>('POST', this.ws('/api/servers', workspace), entry)\n return this.view(answer.server, workspace)\n }\n\n async updateServer(id: string, patch: ServerEntryPatch, workspace?: string): Promise<ServerEntryView> {\n const answer = await this.request<{ server: ApiServerView }>('PATCH', this.ws(`/api/servers/${encodeURIComponent(id)}`, workspace), patch)\n return this.view(answer.server, workspace)\n }\n\n async deleteServer(id: string, workspace?: string): Promise<void> {\n await this.request('DELETE', this.ws(`/api/servers/${encodeURIComponent(id)}`, workspace))\n }\n\n async startServer(id: string, workspace?: string): Promise<void> {\n await this.request('POST', this.ws(`/api/servers/${encodeURIComponent(id)}/start`, workspace))\n }\n\n async stopServer(id: string, workspace?: string): Promise<void> {\n await this.request('POST', this.ws(`/api/servers/${encodeURIComponent(id)}/stop`, workspace))\n }\n\n async restartServer(id: string, workspace?: string): Promise<void> {\n await this.request('POST', this.ws(`/api/servers/${encodeURIComponent(id)}/restart`, workspace))\n }\n\n /** The workspaces this panel serves, as its registry reports them. */\n async listWorkspaces(): Promise<{ id: string, label: string, serverCount: number, runningCount: number, crashedCount: number }[]> {\n const answer = await this.request<{ workspaces: Array<{ id: string, label: string, serverCount: number, runningCount: number, crashedCount: number }> }>('GET', '/api/workspaces')\n return answer.workspaces\n }\n\n /** The panel's own settings: `control` is the listener block a port change edits. */\n async settings(): Promise<{ control?: Record<string, unknown> }> {\n return await this.request<{ control?: Record<string, unknown> }>('GET', '/api/settings')\n }\n\n /** Re-list the port's listeners and stop what is not the panel's own tree. */\n async freePort(id: string, workspace?: string): Promise<{ stopped?: number[] }> {\n return await this.request<{ stopped?: number[] }>('POST', this.ws(`/api/servers/${encodeURIComponent(id)}/free-port`, workspace))\n }\n}\n\n/**\n * What a token probe measured: `ok` \u2014 the panel accepted it; `refused` \u2014 the\n * panel answered and rejected it; `unreachable` \u2014 the panel was not there to\n * ask, which is not the same as a refusal.\n */\nexport type TokenProbe = 'ok' | 'refused' | 'unreachable'\n\n/**\n * Prove a token with a non-mutating `listServers()`, and say whether the panel\n * refused it or merely was not answering. A timeout or connection error must\n * never be reported as a stale token.\n */\nexport async function probeToken(baseUrl: string, token: string, timeoutMs = 5000): Promise<TokenProbe> {\n try {\n await new PanelClient({ baseUrl, token, timeoutMs }).listServers()\n return 'ok'\n }\n catch (error) {\n if (error instanceof PanelError && (error.code === 'AUTH_REQUIRED' || error.status === 401 || error.status === 403))\n return 'refused'\n return 'unreachable'\n }\n}\n\n/** Prove a token works, without needing any write permission. */\nexport async function verifyToken(baseUrl: string, token: string, timeoutMs = 5000): Promise<boolean> {\n return await probeToken(baseUrl, token, timeoutMs) === 'ok'\n}\n", "/**\n * The plugin's own credential for the home-hosted panel.\n *\n * home-hosted keeps only the token's hash, so a token this plugin did not\n * create can never be recovered from disk. When none is enrolled, the plugin\n * mints one and hands it to `home-hosted set-token` through `HHOSTED_TOKEN`,\n * which stores the hash and prints nothing; when the panel refuses the stored\n * one, `reclaimToken` replaces it with a fresh token the same way.\n * The plaintext lives 0600 under the plugin state directory and is never logged,\n * rendered, or sent to the browser.\n */\nimport { createHash, randomBytes } from 'node:crypto'\nimport path from 'node:path'\nimport type { RunResult } from '../util/exec.js'\nimport { readJson, readText, writeFileAtomic } from '../util/fsx.js'\nimport { secretsFile } from './layout.js'\n\nexport type CliExecutor = (args: string[], env: Record<string, string | undefined>) => Promise<RunResult>\n\nexport interface EnsureTokenOptions {\n home: string\n stateDir: string\n exec: CliExecutor\n /** Which panel this credential is for; omitted is the panel the plugin manages. */\n slot?: string\n}\n\nexport interface EnsureTokenResult {\n token: string | null\n /** True when this call created the token. */\n enrolled: boolean\n detail: string\n}\n\nexport interface ReclaimTokenOptions {\n home: string\n stateDir: string\n exec: CliExecutor\n slot?: string\n}\n\nexport interface ReclaimTokenResult {\n token: string | null\n detail: string\n}\n\n/** A stable, unguessable file name for one state root's credential. */\nexport function tokenSlot(home: string): string {\n return createHash('sha256').update(path.resolve(home)).digest('hex').slice(0, 16)\n}\n\nexport function storedTokenPath(stateDir: string, slot?: string): string {\n return slot === undefined\n ? path.join(stateDir, 'panel-token')\n : path.join(stateDir, 'panel-tokens', `${slot}.token`)\n}\n\nexport function readStoredToken(stateDir: string, slot?: string): string | null {\n const text = readText(storedTokenPath(stateDir, slot))?.trim()\n return text !== undefined && text.length > 0 ? text : null\n}\n\nexport function storeToken(stateDir: string, token: string, slot?: string): void {\n writeFileAtomic(storedTokenPath(stateDir, slot), `${token}\\n`, 0o600)\n}\n\nexport function generateToken(): string {\n return randomBytes(32).toString('base64url')\n}\n\n/** Whether home-hosted already holds an API token (only its hash is on disk). */\nexport function apiTokenEnrolled(home: string): boolean {\n const secrets = readJson<{ apiToken?: unknown }>(secretsFile(home))\n return secrets !== null && secrets.apiToken !== null && secrets.apiToken !== undefined\n}\n\n/**\n * One in-flight token operation per state directory, so two callers cannot race.\n *\n * Two concurrent callers used to mint two tokens: both `set-token` calls land, and\n * the loser's token is the one stored on disk while the panel keeps the winner's\n * hash \u2014 after which the plugin can never authenticate, and never re-enrols,\n * because home-hosted already holds an API token. Every operation on one state\n * directory therefore runs after the previous one has settled.\n */\nconst queue = new Map<string, Promise<unknown>>()\n\nfunction serialised<T>(stateDir: string, slot: string | undefined, work: () => Promise<T>): Promise<T> {\n const key = `${path.resolve(stateDir)}\\u0000${slot ?? ''}`\n const previous = queue.get(key) ?? Promise.resolve()\n // Run even when the previous operation rejected: that failure is its caller's.\n const next = previous.then(work, work)\n const settled = next.then(() => undefined, () => undefined)\n queue.set(key, settled)\n void settled.then(() => {\n if (queue.get(key) === settled) queue.delete(key)\n })\n return next\n}\n\nexport async function ensureToken(options: EnsureTokenOptions): Promise<EnsureTokenResult> {\n return await serialised(options.stateDir, options.slot, async () => await enrollToken(options))\n}\n\n/**\n * Replace the panel's API token with a fresh one.\n *\n * home-hosted keeps only the token's hash, so a token this plugin does not hold\n * cannot be recovered: a fresh token is minted, enrolled through the same\n * `HHOSTED_TOKEN` path `ensureToken` uses \u2014 one write, since `set-token` replaces\n * whatever hash is there \u2014 and only then is the plaintext stored 0600. The\n * attempted write carries no risk to the stored token: a failure replaced\n * nothing, so the previous plaintext is kept.\n */\nexport async function reclaimToken(options: ReclaimTokenOptions): Promise<ReclaimTokenResult> {\n return await serialised(options.stateDir, options.slot, async () => await enrollFreshToken(options))\n}\n\ninterface CliAttempt {\n result?: RunResult\n failure?: string\n}\n\n/** Run one CLI step, turning both a non-zero exit and a spawn throw into text. */\nasync function runCli(exec: CliExecutor, args: string[], env: Record<string, string | undefined>): Promise<CliAttempt> {\n try {\n const result = await exec(args, env)\n if (result.code !== 0) {\n return {\n failure: result.error?.trim() || result.stderr.trim() || result.stdout.trim() || `exit ${String(result.code)}`,\n }\n }\n return { result }\n }\n catch (error) {\n return { failure: error instanceof Error ? error.message : String(error) }\n }\n}\n\n/** Mint a token when none is enrolled. */\nasync function enrollToken(options: EnsureTokenOptions): Promise<EnsureTokenResult> {\n const stored = readStoredToken(options.stateDir, options.slot)\n if (stored !== null)\n return { token: stored, enrolled: false, detail: 'using the stored panel token' }\n\n if (apiTokenEnrolled(options.home)) {\n return {\n token: null,\n enrolled: false,\n detail: 'home-hosted already has an API token, and this plugin does not have it; '\n + 'run `home-hosted set-token --generate` and store that token, or clear it and re-enable',\n }\n }\n\n const token = generateToken()\n const enrolled = await runCli(options.exec, ['--home', options.home, 'set-token'], { HHOSTED_TOKEN: token })\n if (enrolled.result === undefined) {\n return {\n token: null,\n enrolled: false,\n detail: `could not enrol an API token: ${enrolled.failure ?? 'the CLI did not answer'}`,\n }\n }\n\n storeToken(options.stateDir, token, options.slot)\n return { token, enrolled: true, detail: 'enrolled a new panel API token' }\n}\n\nasync function enrollFreshToken(options: ReclaimTokenOptions): Promise<ReclaimTokenResult> {\n // One write, not a clear-then-set pair: `set-token` replaces whatever hash is\n // there (proved against the real CLI), and clearing first would leave a window\n // in which the panel has no token at all.\n const token = generateToken()\n const enrolled = await runCli(options.exec, ['--home', options.home, 'set-token'], { HHOSTED_TOKEN: token })\n if (enrolled.result === undefined) {\n // Nothing was replaced, so the stored token is still the panel's and is left\n // alone: dropping it would throw away a credential that may still work.\n return {\n token: null,\n detail: `could not enrol a fresh API token: ${enrolled.failure ?? 'the CLI did not answer'}; `\n + 'the previously stored token was kept',\n }\n }\n\n storeToken(options.stateDir, token, options.slot)\n return { token, detail: 'enrolled a fresh panel API token' }\n}\n", "/**\n * The browser half's one entry point.\n *\n * The route is registered on DeepSeek Harness's shared `/api` channel, so the\n * physical carrier has already applied the Host/Origin fence and browser\n * authentication before this handler runs: only the local operator reaches it.\n * Domain failures travel inside the envelope, protocol failures as HTTP status.\n */\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { RpcEndpoint, RpcRequest } from './shared/contracts.js'\nimport { RPC_PATH, RPC_VERSION } from './shared/contracts.js'\nimport type { HomeHostedService } from './service.js'\nimport { HomeHostedError } from './service.js'\n\ninterface FetchRoute {\n path: string\n methods: readonly string[]\n requestBody: 'buffered' | 'streaming'\n fetch: (request: Request) => Promise<Response>\n}\n\ninterface ConnectionLike {\n fetch: { register: (route: FetchRoute) => () => unknown }\n}\n\ninterface ConnectionHost {\n connection: ConnectionLike\n}\n\ninterface LoggerLike {\n warn?: (message: string) => void\n}\n\nfunction answer(endpoint: RpcEndpoint | string, result: unknown): Response {\n return Response.json({ v: RPC_VERSION, endpoint, result })\n}\n\nexport function registerRpc(ctx: Context, service: HomeHostedService): void {\n ctx.inject(['connection'], (scoped) => {\n const connection = (scoped as unknown as ConnectionHost).connection\n if (connection?.fetch?.register === undefined)\n return\n\n scoped.effect(() => {\n let dispose: () => unknown\n try {\n dispose = connection.fetch.register({\n // The exact-route registry is keyed by the full request path, so the\n // `/api` prefix belongs here; the page composes the same URL from\n // API_BASE + RPC_PATH.\n path: `/api${RPC_PATH}`,\n methods: ['POST'],\n requestBody: 'buffered',\n fetch: async (request: Request) => {\n let body: RpcRequest\n try {\n body = await request.json() as RpcRequest\n }\n catch {\n return Response.json({ error: 'the request body is not JSON' }, { status: 400 })\n }\n\n if (body === null || typeof body !== 'object' || body.v !== RPC_VERSION) {\n return Response.json(\n { error: `this page and the host disagree on the protocol version (expected ${String(RPC_VERSION)})` },\n { status: 409 },\n )\n }\n\n try {\n const value = await service.call(body.endpoint, body.payload)\n return answer(body.endpoint, { ok: true, value })\n }\n catch (error) {\n const code = error instanceof HomeHostedError ? error.code : 'INTERNAL'\n const message = error instanceof Error ? error.message : String(error)\n const detail = (error as { detail?: unknown } | null)?.detail\n return answer(body.endpoint, { ok: false, error: { code, message, ...(detail === undefined ? {} : { detail }) } })\n }\n },\n })\n }\n catch (error) {\n // A registration the carrier refuses would otherwise look like a page\n // that silently 404s; say so where an operator can see it.\n const logger = (scoped as unknown as { logger?: LoggerLike }).logger\n logger?.warn?.(`[dsh-home-hosted] could not register ${RPC_PATH}: ${error instanceof Error ? error.message : String(error)}`)\n return () => {}\n }\n\n return () => {\n // A disposer that rejects would otherwise surface as an unhandled\n // rejection, which ends the process on Node 24.\n void Promise.resolve(dispose()).catch(() => {})\n }\n }, 'dsh-home-hosted: rpc route')\n })\n}\n", "/**\n * Telling the agent which home-hosted panels exist.\n *\n * A plugin that owns one panel must not look like the only one on the machine.\n * When discovery finds more than one, the inventory is contributed as dynamic\n * runtime context (`ctx.systemPrompt.context`), so every model step knows the\n * managed panel, the others, and that this plugin cannot write to them.\n *\n * The contribution reads the service's cached inventory: assembly happens\n * before a model step and must never block on the disk.\n */\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { InstanceView } from '../shared/contracts.js'\nimport { instancesNoticeText } from './instances.js'\n\n/** The subset of `ctx.systemPrompt` this uses; absent on a host without it. */\nexport interface SystemPromptLike {\n context: (contribution: {\n name: string\n order: number\n text: string | ((context: unknown) => string)\n }) => () => void\n}\n\nexport interface InstancesNoticeDeps {\n /** The inventory the plugin already measured; never a disk read at assembly. */\n instances: () => readonly InstanceView[]\n /** The `instancesNotice` setting, read per assembly. */\n enabled: () => boolean\n}\n\n/**\n * After the first-party runtime contexts (sandbox 110, approval 115,\n * subagent delegation 120) and before anything a repository owns.\n */\nexport const INSTANCES_CONTEXT_ORDER = 130\n\n/** The exact text one assembly contributes, or `''` for nothing. */\nexport function instancesContextText(instances: readonly InstanceView[], enabled: boolean): string {\n return enabled ? instancesNoticeText(instances) ?? '' : ''\n}\n\nexport function registerInstancesNotice(ctx: Context, deps: InstancesNoticeDeps): void {\n ctx.inject(['systemPrompt'], (scoped) => {\n const prompt = (scoped as unknown as { systemPrompt?: SystemPromptLike }).systemPrompt\n if (typeof prompt?.context !== 'function')\n return\n scoped.effect(() => prompt.context({\n name: 'dsh-home-hosted:instances',\n order: INSTANCES_CONTEXT_ORDER,\n text: () => instancesContextText(deps.instances(), deps.enabled()),\n }), 'dsh-home-hosted: instance notice')\n })\n}\n", "import type { Context } from '@deepseek-ai/cordis'\n\n/**\n * dsh web answers an unauthenticated browser with a plain-text 401 line written\n * by the in-box connection plugin. That text is not configurable and no event\n * covers it, so a home-hosted deployment \u2014 which is where this session's log,\n * and with it the tokenised URL, actually lives \u2014 appends where to look.\n *\n * It reaches one internal field, `connection.browserAuth.writeUnauthorized`, and\n * fails soft: if that shape is ever gone, dsh's own text is left untouched.\n */\nexport const STOCK_UNAUTHORIZED = 'dsh web authentication required; reopen the URL printed by dsh web.'\n\n/** The 401 body: dsh's own line, plus where this deployment's log page is. */\nexport function authNoticeBody(logUrl: string | null): string {\n if (logUrl === null)\n return `${STOCK_UNAUTHORIZED}\\n`\n return `${STOCK_UNAUTHORIZED}\\nhome-hosted: the URL with its token is in the dsh log at ${logUrl}. An authentication plugin can sign you in seamlessly instead.\\n`\n}\n\ninterface AuthLike {\n writeUnauthorized?: unknown\n}\n\n/**\n * Wrap the auth object's 401 writer so the body carries the notice.\n * @returns the disposer that puts the original back.\n */\nfunction wrap(auth: { writeUnauthorized: (req: unknown, res: unknown) => void }, logUrlOf: () => string | null): () => void {\n const original = auth.writeUnauthorized.bind(auth)\n auth.writeUnauthorized = (req, res) => {\n const response = res as {\n end?: (...args: unknown[]) => unknown\n once?: (event: string, listener: () => void) => unknown\n writableEnded?: boolean\n }\n const end = response.end\n if (typeof end !== 'function' || response.writableEnded === true) {\n original(req, res)\n return\n }\n\n let restored = false\n const restore = (): void => {\n if (restored) return\n restored = true\n try {\n response.end = end\n }\n catch {\n // a response that refuses the assignment keeps the original writer\n }\n }\n\n try {\n response.end = (...args: unknown[]) => {\n restore()\n const [chunk, ...rest] = args\n let body = chunk\n // Only the untouched stock line is ours to rewrite; an authentication\n // plugin's own page (or the same sentence inside longer text) is left alone.\n if (typeof chunk === 'string' && chunk.trim() === STOCK_UNAUTHORIZED) {\n try {\n body = authNoticeBody(logUrlOf())\n }\n catch {\n body = chunk\n }\n }\n return end.call(response, body, ...rest)\n }\n }\n catch {\n original(req, res)\n return\n }\n\n // A writer that ends later still gets the notice; one that never ends must\n // not leave our wrapper on the response.\n try {\n response.once?.('finish', restore)\n response.once?.('close', restore)\n }\n catch {\n // events are best-effort tidiness, never a reason to lose the 401\n }\n original(req, res)\n }\n return () => {\n auth.writeUnauthorized = original\n }\n}\n\n/**\n * Install the notice once the connection service is up. `logUrlOf` is read per\n * 401, so turning the setting off takes effect without re-installing anything.\n */\nexport function registerAuthNotice(ctx: Context, logUrlOf: () => string | null): void {\n ctx.inject(['connection'], (scoped) => {\n scoped.effect(() => {\n const connection = scoped.get('connection') as { browserAuth?: AuthLike } | undefined\n const auth = connection?.browserAuth\n if (typeof auth?.writeUnauthorized !== 'function')\n return () => {}\n return wrap(auth as { writeUnauthorized: (req: unknown, res: unknown) => void }, logUrlOf)\n }, 'dsh-home-hosted: sign-in notice')\n })\n}\n", "/**\n * The plugin's own settings, kept in a 0600 JSON file under its state directory.\n *\n * These are the things a person toggles; the Cordis row config stays for\n * operator overrides only, so neither surface can silently overwrite the other.\n */\nimport { AGENT_TOOL_NAMES, DEFAULT_SETTINGS, isOnPortConflict } from './shared/contracts.js'\nimport type { AgentToolName, EntryIntent, PluginSettings, SettingsPatch } from './shared/contracts.js'\nimport { SETTINGS_VERSION } from './shared/contracts.js'\nimport { readJson, writeJsonAtomic } from './util/fsx.js'\nimport { defaultIntent } from './home-hosted/entries.js'\n\nfunction knownTool(value: unknown): value is AgentToolName {\n return typeof value === 'string' && (AGENT_TOOL_NAMES as readonly string[]).includes(value)\n}\n\nfunction normalizeIntent(value: Partial<EntryIntent> | null | undefined, fallbackId: string): EntryIntent {\n const base = defaultIntent(typeof value?.id === 'string' && value.id.length > 0 ? value.id : fallbackId)\n return {\n id: base.id,\n autostart: typeof value?.autostart === 'boolean' ? value.autostart : base.autostart,\n // Absent means a stored intent from before persistence existed: it wants it.\n persistent: typeof value?.persistent === 'boolean' ? value.persistent : base.persistent,\n // Only a policy the panel's own schema parses may reach the config it boots from.\n onPortConflict: isOnPortConflict(value?.onPortConflict) ? value.onPortConflict : base.onPortConflict,\n stopKillPortHolders: typeof value?.stopKillPortHolders === 'boolean' ? value.stopKillPortHolders : base.stopKillPortHolders,\n }\n}\n\n/**\n * Tools that were split apart before 0.2.0 and merged since. An allowlist\n * written by an older release lists the old names, and an unknown name is\n * dropped \u2014 which would silently turn every tool off on upgrade.\n */\nconst LEGACY_TOOL_NAMES: Record<string, string> = {\n servers_start: 'servers_lifecycle',\n servers_stop: 'servers_lifecycle',\n servers_restart: 'servers_lifecycle',\n servers_create: 'servers_edit',\n servers_update: 'servers_edit',\n servers_delete: 'servers_edit',\n autostart_install: 'autostart_manage',\n autostart_uninstall: 'autostart_manage',\n}\n\n/** The pair an older release wrote when nobody had chosen anything. */\nfunction isLegacyDefaultAllowlist(names: readonly string[]): boolean {\n return names.length === 2 && names.includes('status') && names.includes('servers_list')\n}\n\n/**\n * The release that merged the split tools and introduced `manageDsh`. The\n * migration bound is that release, never the current `SETTINGS_VERSION`: bumping\n * the stamp for an unrelated field must not re-run a \"nobody chose\" reading over\n * a file that already speaks the current shape.\n */\nconst PRE_MERGE_SETTINGS_VERSION = 2\n\nconst BOOT_MECHANISMS: readonly string[] = ['auto', 'systemd-user', 'systemd-system', 'launchd-agent', 'launchd-daemon', 'xdg-autostart', 'windows-run', 'windows-task', 'container', 'unsupported']\n\nfunction isBootMechanism(value: unknown): value is PluginSettings['autostart']['mechanism'] {\n return typeof value === 'string' && BOOT_MECHANISMS.includes(value)\n}\n\nfunction normalize(raw: Partial<PluginSettings> | null, fallbackEntryId: string): PluginSettings {\n const entries = Array.isArray(raw?.entries)\n ? raw.entries.map(intent => normalizeIntent(intent, fallbackEntryId))\n : []\n const declared = Array.isArray(raw?.agentTools?.allow) ? raw.agentTools.allow : null\n // Only a file written before the version stamp needs translating: the release\n // that wrote it named tools this one merged, and dropping those names silently\n // would turn every tool off on upgrade.\n const legacyFile = (typeof raw?.version === 'number' ? raw.version : 1) < PRE_MERGE_SETTINGS_VERSION\n const migrated = declared === null\n ? null\n : [...new Set(declared.map(name => (legacyFile ? LEGACY_TOOL_NAMES[name] ?? name : name)).filter(knownTool))] as AgentToolName[]\n // In a legacy file the old default pair is not a choice, it is what that\n // release wrote for \"unset\".\n const choseNothing = legacyFile && declared !== null && migrated !== null && isLegacyDefaultAllowlist(declared)\n const allow = migrated === null || choseNothing ? [...DEFAULT_SETTINGS.agentTools.allow] : migrated\n const attempt = raw?.autostart?.lastAttempt\n return {\n version: SETTINGS_VERSION,\n autostart: {\n enabled: raw?.autostart?.enabled === true,\n mechanism: isBootMechanism(raw?.autostart?.mechanism) ? raw.autostart.mechanism : 'auto',\n ...(attempt === undefined ? {} : { lastAttempt: attempt }),\n },\n // A pre-0.2.0 file expressed management only by holding an intent for the\n // harness entry; the page's toggle needs the flag it never had.\n manageDsh: raw?.manageDsh === true\n || (legacyFile && entries.some(entry => entry.id === fallbackEntryId)),\n entries,\n panel: {\n // An integer in range, or nothing: the panel's schema rejects anything else,\n // and a value it cannot parse stops it booting at all.\n port: typeof raw?.panel?.port === 'number' && Number.isInteger(raw.panel.port) && raw.panel.port >= 1 && raw.panel.port <= 65535\n ? raw.panel.port\n : null,\n },\n authNotice: raw?.authNotice === undefined ? DEFAULT_SETTINGS.authNotice : raw.authNotice === true,\n // Absent means on: a stale token is a fault to repair, not a setting to opt into.\n reclaimToken: raw?.reclaimToken === undefined ? DEFAULT_SETTINGS.reclaimToken : raw.reclaimToken === true,\n // Absent means on: a plugin that owns one panel should say so by default.\n instancesNotice: raw?.instancesNotice === undefined ? DEFAULT_SETTINGS.instancesNotice : raw.instancesNotice === true,\n uiStyle: raw?.uiStyle === 'compact' ? 'compact' : 'detailed',\n agentTools: {\n // Absent means the default (on), and so does the pair the previous\n // release wrote for it; any other explicit false stays false.\n enabled: choseNothing || raw?.agentTools?.enabled === undefined\n ? DEFAULT_SETTINGS.agentTools.enabled\n : raw.agentTools.enabled === true,\n // Absent means every tool; an explicit empty list means none of them.\n allow,\n },\n cli: {\n prefer: raw?.cli?.prefer === 'global' ? 'global' : 'pinned',\n },\n }\n}\n\nexport class SettingsStore {\n private current: PluginSettings\n private readonly listeners = new Set<(settings: PluginSettings) => void>()\n\n constructor(\n readonly file: string,\n private readonly fallbackEntryId: string,\n ) {\n this.current = normalize(readJson<Partial<PluginSettings>>(file), fallbackEntryId)\n }\n\n get(): PluginSettings {\n return this.current\n }\n\n /** The intent for an entry, materialising the default when it has none yet. */\n intentFor(id: string): EntryIntent {\n return this.current.entries.find(entry => entry.id === id) ?? defaultIntent(id)\n }\n\n update(patch: SettingsPatch): PluginSettings {\n const next = normalize({\n // A patch is not a legacy file: the migration belongs to reading one.\n version: SETTINGS_VERSION,\n autostart: { ...this.current.autostart, ...(patch.autostart ?? {}) },\n manageDsh: patch.manageDsh ?? this.current.manageDsh,\n entries: patch.entries ?? this.current.entries,\n panel: { ...this.current.panel, ...(patch.panel ?? {}) },\n authNotice: patch.authNotice ?? this.current.authNotice,\n reclaimToken: patch.reclaimToken ?? this.current.reclaimToken,\n instancesNotice: patch.instancesNotice ?? this.current.instancesNotice,\n uiStyle: patch.uiStyle ?? this.current.uiStyle,\n agentTools: { ...this.current.agentTools, ...(patch.agentTools ?? {}) },\n cli: { ...this.current.cli, ...(patch.cli ?? {}) },\n }, this.fallbackEntryId)\n this.current = next\n writeJsonAtomic(this.file, next, 0o600)\n for (const listener of this.listeners)\n listener(next)\n return next\n }\n\n onChange(listener: (settings: PluginSettings) => void): () => void {\n this.listeners.add(listener)\n return () => {\n this.listeners.delete(listener)\n }\n }\n}\n", "/**\n * Agent tools, on by default and allowlisted by name.\n *\n * The list is read at registration time and re-read whenever the settings file\n * changes, so toggling a tool takes effect without a restart. A tool that\n * changes something asks the approval service only when the calling session is\n * not already `danger-full-access` \u2014 the session has then granted exactly what\n * the tool would be asking about, and asking anyway is what makes a\n * Full-access run fail against an auto-rejecting approval channel.\n */\nimport type { Context } from '@deepseek-ai/cordis'\nimport type { ParameterPropertySpec, ParameterSchemaSpec } from '@deepseek-ai/dsh-tools'\nimport { defineTool } from '@deepseek-ai/dsh-tools'\nimport type { AgentToolName, ForeignMechanism, HomeHostedStatus, InstanceView, RpcEndpoint, UiAction } from './shared/contracts.js'\nimport { FOREIGN_MECHANISMS, MUTATING_AGENT_TOOLS, ON_PORT_CONFLICT_POLICIES } from './shared/contracts.js'\nimport { canonicalPath, describeInstance } from './home-hosted/instances.js'\nimport type { HomeHostedService } from './service.js'\nimport type { SettingsStore } from './settings.js'\n\ninterface ApprovalLike {\n request: (request: { agent: unknown, toolName: string, reason?: string }) => Promise<string>\n}\n\ninterface SandboxPolicyLike {\n resolve?: (input: { session?: unknown }) => { mode?: string } | undefined\n}\n\n/** The subset of `ctx.userQuestions` this uses; absent on a host without a UI. */\ninterface UserQuestionsLike {\n ask: (request: {\n questions: Array<{\n id: string\n header?: string\n question: string\n detail?: string\n options?: Array<{ label: string, description?: string }>\n }>\n agent?: unknown\n signal?: unknown\n }) => Promise<{ answers: Array<{ id: string, selected: string[], custom?: string }> }>\n}\n\n/** The calling session's file-sandbox mode, or null when nothing can report it. */\nfunction sandboxMode(ctx: Context, exec: unknown): string | null {\n const policy = ctx.get('sandboxPolicy') as SandboxPolicyLike | undefined\n if (policy?.resolve === undefined)\n return null\n const session = (exec as { agent?: { session?: unknown } } | undefined)?.agent?.session\n try {\n return policy.resolve({ ...(session === undefined ? {} : { session }) })?.mode ?? null\n }\n catch {\n return null\n }\n}\n\ntype Input = Record<string, unknown>\n\n/** A payload carrying the panel a call targets and how to reach it. */\nfunction withTarget(payload: unknown, home: string, via: ForeignMechanism | null): unknown {\n const target = via === null ? { home } : { home, via }\n return typeof payload === 'object' && payload !== null ? { ...payload, ...target } : target\n}\n\nfunction stringArg(input: Input, key: string): string | null {\n const value = input[key]\n return typeof value === 'string' && value.trim().length > 0 ? value.trim() : null\n}\n\n/**\n * The workspace a server call acts on, which every such call names explicitly \u2014\n * a server id is only unique inside one workspace, so a default would make the\n * same call mean different things on different machines.\n */\nfunction requiredWorkspace(input: Input): string {\n const workspace = stringArg(input, 'workspace')\n if (workspace === null)\n throw new Error('workspace is required; a server id is only unique inside one')\n return workspace\n}\n\n/**\n * A structured argument. `entry`/`patch` are declared as objects, so the runtime\n * validates them before this runs; what remains is the absent case, which the\n * caller reports as such rather than as an unusable value.\n */\nfunction jsonArg(input: Input, key: string): Record<string, unknown> | null {\n const value = input[key]\n return typeof value === 'object' && value !== null && !Array.isArray(value) ? value as Record<string, unknown> : null\n}\n\n/** Why a structured argument was unusable, naming the type that arrived. */\nfunction jsonArgError(input: Input, key: string, purpose: string): Error {\n const value = input[key]\n const received = value === undefined ? 'missing' : Array.isArray(value) ? 'array' : typeof value\n return new Error(`${key} must be a JSON object ${purpose} (received: ${received})`)\n}\n\ninterface ToolSpec {\n description: string\n parameters: ParameterSchemaSpec\n /** The endpoint call this tool performs, after its own argument handling. */\n run: (input: Input) => { endpoint: RpcEndpoint, payload: unknown }\n}\n\n/**\n * Every tool takes the same optional panel selector. Omitting it means the panel\n * this plugin manages; naming another panel is neither ignored nor silently\n * retargeted \u2014 this plugin asks the user first, then reaches that panel the only\n * way it can: its config file, or its own CLI.\n */\nconst INSTANCE_PARAM = {\n type: 'string',\n description: 'Panel state root or URL; omit for the managed panel',\n} as const satisfies ParameterPropertySpec\n\n/** Which workspace a call acts on; a server id is only unique inside one. */\nconst WORKSPACE_PARAM = {\n type: 'string',\n required: true,\n description: 'Workspace id (^[a-z0-9][a-z0-9_-]*$)',\n} as const satisfies ParameterPropertySpec\n\n/**\n * A home-hosted server entry, as one object parameter.\n *\n * Declared field by field rather than as `type: 'json'`: an author-only `json`\n * node projects to a schema with no `type`, and the model then has nothing to\n * aim at \u2014 the same mistake that made `create`/`update` unusable. Only the keys\n * this plugin understands are named; the panel accepts more, so unknown keys\n * stay allowed.\n */\nconst ENTRY_FIELDS: ParameterSchemaSpec = {\n id: { type: 'string', required: true, description: 'Entry id, ^[a-z0-9][a-z0-9_-]*$' },\n command: { type: 'string', description: 'Executable to run' },\n args: { type: 'array', items: { type: 'string' }, description: 'Arguments' },\n cwd: { type: 'string', description: 'Working directory' },\n label: { type: 'string', description: 'Human label' },\n enabled: { type: 'boolean', description: 'Whether the entry may run' },\n autostart: { type: 'boolean', description: 'Start it when the panel starts' },\n // The panel's schema is `1..65535 | null`, and a patch that clears a port sends it.\n port: { oneOf: [{ type: 'number' }, { type: 'null' }], description: 'Port the panel tracks for this entry, or null' },\n bind: { type: 'string', description: 'Bind host or local/lan' },\n onPortConflict: { type: 'string', enum: [...ON_PORT_CONFLICT_POLICIES], description: 'Policy when the port is taken' },\n stop: { type: 'object', additionalProperties: true, description: 'Stop policy, e.g. killPortHolders' },\n health: { type: 'object', additionalProperties: true, description: 'Health check block' },\n restart: { type: 'object', additionalProperties: true, description: 'Restart policy block' },\n env: { type: 'object', additionalProperties: true, description: 'Environment variables' },\n dataEnvs: { type: 'object', additionalProperties: true, description: 'Environment entries the panel fills in' },\n}\n\nconst ENTRY_PARAM = {\n type: 'object',\n additionalProperties: true,\n description: 'A home-hosted server entry: at least id and command',\n properties: ENTRY_FIELDS,\n} as const satisfies ParameterPropertySpec\n\nconst PATCH_PARAM = {\n type: 'object',\n additionalProperties: true,\n description: 'Fields to change on an existing entry; only the given keys are touched',\n // The same fields, none required and without `id`: the entry patched is the one\n // the `id` argument names, and the panel rejects a patch that carries an id.\n properties: Object.fromEntries(Object.entries(ENTRY_FIELDS).filter(([key]) => key !== 'id')),\n} as const satisfies ParameterPropertySpec\n\nconst TOOL_SPECS: Record<AgentToolName, ToolSpec> = {\n status: {\n description: 'Report the panel state, the panels found on this machine, its autostart entry, and the entries this plugin manages.',\n parameters: { instance: INSTANCE_PARAM },\n run: () => ({ endpoint: 'status', payload: {} }),\n },\n workspaces_list: {\n description: 'List every workspace the panel serves, with its label, entry count and running count. Read-only.',\n parameters: { instance: INSTANCE_PARAM },\n run: () => ({ endpoint: 'workspaces.list', payload: {} }),\n },\n servers_list: {\n description: 'List the servers home-hosted supervises in one workspace, with status, pid and url.',\n parameters: { instance: INSTANCE_PARAM, workspace: WORKSPACE_PARAM },\n run: (input) => {\n const workspace = requiredWorkspace(input)\n return { endpoint: 'servers.list', payload: { workspace } }\n },\n },\n servers_lifecycle: {\n description: 'Start, stop or restart a server supervised by home-hosted. Restarting the entry this session runs as ends the session.',\n parameters: {\n instance: INSTANCE_PARAM,\n workspace: WORKSPACE_PARAM,\n action: { type: 'string', required: true, description: 'start, stop or restart' },\n id: { type: 'string', required: true, description: 'Server entry id' },\n },\n run: (input) => {\n const action = stringArg(input, 'action')\n if (action !== 'start' && action !== 'stop' && action !== 'restart')\n throw new Error('action must be start, stop or restart')\n const id = stringArg(input, 'id')\n if (id === null)\n throw new Error('id is required')\n return {\n endpoint: `servers.${action}` as RpcEndpoint,\n payload: { id, workspace: requiredWorkspace(input) },\n }\n },\n },\n servers_edit: {\n description: 'Create, update or delete a home-hosted server entry. Delete is refused for the entry this session runs as.',\n parameters: {\n instance: INSTANCE_PARAM,\n workspace: WORKSPACE_PARAM,\n action: { type: 'string', required: true, description: 'create, update or delete' },\n id: { type: 'string', description: 'Server entry id (update, delete)' },\n entry: ENTRY_PARAM,\n patch: PATCH_PARAM,\n },\n run: (input) => {\n const action = stringArg(input, 'action')\n const workspace = requiredWorkspace(input)\n if (action === 'create') {\n const entry = jsonArg(input, 'entry')\n if (entry === null)\n throw jsonArgError(input, 'entry', 'to create a server')\n return { endpoint: 'servers.create', payload: { entry, workspace } }\n }\n if (action === 'update') {\n const id = stringArg(input, 'id')\n const patch = jsonArg(input, 'patch')\n if (id === null)\n throw new Error('id is required to update a server')\n if (patch === null)\n throw jsonArgError(input, 'patch', 'to update a server')\n // A caller may echo the id back; it is the key, not a field to change.\n const { id: _key, ...fields } = patch\n return { endpoint: 'servers.update', payload: { id, patch: fields, workspace } }\n }\n if (action === 'delete') {\n const id = stringArg(input, 'id')\n if (id === null)\n throw new Error('id is required to delete a server')\n return { endpoint: 'servers.delete', payload: { id, workspace } }\n }\n throw new Error('action must be create, update or delete')\n },\n },\n autostart_manage: {\n description: 'Install or remove the OS entry that starts home-hosted at boot or login. Installing stops the panel (and every server it supervises, which can include this session) and starts it again through that entry.',\n parameters: {\n instance: INSTANCE_PARAM,\n action: { type: 'string', required: true, description: 'install or uninstall' },\n mechanism: { type: 'string', description: 'Mechanism, e.g. systemd-system; omit for the plugin setting' },\n },\n run: (input) => {\n const action = stringArg(input, 'action')\n if (action !== 'install' && action !== 'uninstall')\n throw new Error('action must be install or uninstall')\n const mechanism = stringArg(input, 'mechanism')\n return { endpoint: `boot.${action}` as RpcEndpoint, payload: mechanism === null ? {} : { mechanism } }\n },\n },\n ui_manage: {\n description: 'Inspect or change the panel\\'s own web UI: status, update, revert, or install a UI by release asset or local zip.',\n parameters: {\n instance: INSTANCE_PARAM,\n action: { type: 'string', required: true, description: 'status, update, revert or switch' },\n asset: { type: 'string', description: 'Release asset name to install (switch), e.g. noc-console' },\n repo: { type: 'string', description: 'owner/name the asset lives in (switch/update)' },\n tag: { type: 'string', description: 'Release tag (switch/update)' },\n file: { type: 'string', description: 'Absolute path to a local UI zip (switch)' },\n },\n run: (input) => {\n const action = stringArg(input, 'action')\n if (action !== 'status' && action !== 'update' && action !== 'revert' && action !== 'switch')\n throw new Error('action must be status, update, revert or switch')\n const optional = (key: string): Record<string, string> => {\n const value = stringArg(input, key)\n return value === null ? {} : { [key]: value }\n }\n return {\n endpoint: 'ui.manage',\n payload: {\n action: action as UiAction,\n ...optional('file'),\n ...optional('asset'),\n ...optional('repo'),\n ...optional('tag'),\n },\n }\n },\n },\n}\n\nexport function toolNameFor(name: AgentToolName): string {\n return `home_hosted_${name}`\n}\n\n/**\n * Actions of an otherwise mutating tool that only read. `ui_manage` carries both,\n * and a status call must not be blocked (or prompted for) as if it changed the UI.\n */\nconst READ_ONLY_ACTIONS: Partial<Record<AgentToolName, (input: Input) => boolean>> = {\n ui_manage: input => stringArg(input, 'action') === 'status',\n}\n\n/**\n * Which panel a call lands on, and whether the user still has to agree to it.\n *\n * The plugin drives one state root; a person may run several. A call that names\n * another panel is neither silently retargeted nor refused outright: the plugin\n * asks the user, names the way it would reach that panel (its config file, or\n * its own CLI), and only then changes it. A call that names none is the managed\n * panel by definition, unless several exist and the `instancesNotice` setting is\n * on, in which case the user is asked which panel was meant.\n */\ntype TargetPlan =\n | { ok: false, text: string }\n /** `home: null` is the managed panel; a null `ask` means nothing to confirm. */\n | { ok: true, home: string | null, ask: null }\n | { ok: true, home: null, ask: 'which', mechanisms: readonly ForeignMechanism[] }\n | { ok: true, home: string, ask: 'foreign', mechanisms: readonly ForeignMechanism[] }\n\n/** The plan a question settled on: the panel and how to reach it, or why not. */\ntype TargetDecision = { ok: true, home: string | null, via: ForeignMechanism | null } | { ok: false, text: string }\n\nconst CANCEL_LABEL = 'Cancel'\n\n/**\n * `userQuestions.ask` failures that mean no human could be reached: a host with\n * no answerer, and a delegated child (whose answers never reach a person).\n */\nconst NO_ANSWERER_CODES = new Set(['NO_PROVIDER', 'DELEGATED_CALLER', 'CALLER_NOT_LIVE'])\n\n/** How each mechanism is offered to a person, and how the result names it. */\nconst MECHANISM_WORDING: Record<ForeignMechanism, { option: string, phrase: string }> = {\n file: { option: 'Edit its config file', phrase: 'by editing its servers.config.json' },\n cli: { option: 'Run the home-hosted CLI', phrase: 'by running the home-hosted CLI against its state root' },\n api: { option: 'Generate a token and use its API', phrase: 'by enrolling a token for it and using its API' },\n}\n\n/** Why an endpoint cannot be aimed at another panel, when it cannot. */\nconst NO_FOREIGN_REASON: Partial<Record<RpcEndpoint, string>> = {\n 'status': 'home_hosted_status describes the panel this plugin manages, and it already lists every panel found on the machine.',\n 'boot.install': 'the OS boot entry starts the panel this plugin manages; it is one machine-wide entry and cannot be aimed at another panel.',\n 'boot.uninstall': 'the OS boot entry is one machine-wide entry and belongs to the panel this plugin manages.',\n}\n\n/** The panel inventory, when the host can produce one. */\nasync function inventoryOf(service: HomeHostedService): Promise<InstanceView[]> {\n const reader = (service as unknown as { instances?: () => Promise<InstanceView[]> }).instances\n if (typeof reader !== 'function')\n return []\n try {\n return await reader.call(service)\n }\n catch {\n return []\n }\n}\n\n/** The instance a caller named by state root, URL or project dir. */\nfunction matchInstance(instances: readonly InstanceView[], wanted: string): InstanceView | null {\n const target = wanted.trim()\n if (target.length === 0)\n return null\n const asPath = canonicalPath(target)\n return instances.find(instance =>\n instance.home === asPath\n || instance.url === target\n || (instance.projectDir !== null && instance.projectDir === target),\n ) ?? null\n}\n\nfunction inventoryText(instances: readonly InstanceView[]): string {\n return instances.map(instance => `${instance.managed ? 'managed: ' : ''}${describeInstance(instance)}`).join('; ')\n}\n\nfunction refusalForUnknownPanel(instances: readonly InstanceView[], wanted: string): string {\n return `refused: no home-hosted panel at \"${wanted}\" was found on this machine. `\n + `Panels found: ${inventoryText(instances)}. `\n + 'Ask the user which panel they mean, or read home_hosted_status for the inventory.'\n}\n\nfunction refusalForUnreachablePanel(instances: readonly InstanceView[], target: InstanceView, endpoint: RpcEndpoint): string {\n const reason = NO_FOREIGN_REASON[endpoint] ?? `this plugin has no way to change another panel's state for ${endpoint}`\n return `refused: ${describeInstance(target)} is another home-hosted panel, and ${reason} `\n + `Panels found: ${inventoryText(instances)}.`\n}\n\n/** Read an answer's option labels; a UI may also return free text. */\nfunction answerOf(answer: { answers?: Array<{ id: string, selected: string[], custom?: string }> }, id: string): { selected: string[], custom: string | null } {\n const item = (Array.isArray(answer?.answers) ? answer.answers : []).find(entry => entry.id === id)\n return {\n selected: Array.isArray(item?.selected) ? item.selected : [],\n custom: typeof item?.custom === 'string' && item.custom.trim().length > 0 ? item.custom.trim() : null,\n }\n}\n\n/**\n * Which panel the call lands on, validated before anything is asked. The\n * questions themselves run after the approval gate, so a call the session may\n * not make never puts a question to the user.\n */\nasync function planTarget(\n service: HomeHostedService,\n settings: SettingsStore,\n endpoint: RpcEndpoint,\n requested: string | null,\n mutating: boolean,\n): Promise<TargetPlan> {\n const instances = await inventoryOf(service)\n if (instances.length === 0) {\n // An unverifiable target must not become the managed panel by default: that\n // is the silent retarget this whole check exists to prevent.\n return requested === null\n ? { ok: true, home: null, ask: null }\n : { ok: false, text: `refused: this host could not list the home-hosted panels on this machine, so \"${requested}\" cannot be checked; ask the user, or call without instance to act on the panel this plugin manages.` }\n }\n const managed = instances.find(instance => instance.managed)\n const mechanisms = FOREIGN_MECHANISMS[endpoint]\n\n if (requested !== null) {\n const target = matchInstance(instances, requested)\n if (target === null)\n return { ok: false, text: refusalForUnknownPanel(instances, requested) }\n if (target.managed)\n return { ok: true, home: null, ask: null }\n if (mechanisms === undefined)\n return { ok: false, text: refusalForUnreachablePanel(instances, target, endpoint) }\n return mutating ? { ok: true, home: target.home, ask: 'foreign', mechanisms } : { ok: true, home: target.home, ask: null }\n }\n\n // Asking which panel is only worth it when this call could reach more than one.\n if (!mutating || !settings.get().instancesNotice || managed === undefined || mechanisms === undefined || instances.length < 2)\n return { ok: true, home: null, ask: null }\n return { ok: true, home: null, ask: 'which', mechanisms }\n}\n\n/** One ask, returning the answered option labels or how it failed. */\nasync function ask(\n ctx: Context,\n exec: unknown,\n questions: UserQuestionsLike,\n item: { id: string, question: string, detail?: string, options: Array<{ label: string, description: string }> },\n): Promise<{ ok: true, selected: string[], custom: string | null } | { ok: false, code: string | null, error: unknown }> {\n try {\n const answer = await questions.ask({\n questions: [{ id: item.id, header: 'home-hosted', question: item.question, detail: item.detail, options: item.options }],\n agent: (exec as { agent?: unknown } | undefined)?.agent,\n signal: (exec as { signal?: unknown } | undefined)?.signal,\n })\n const { selected, custom } = answerOf(answer, item.id)\n return { ok: true, selected, custom }\n }\n catch (error) {\n return { ok: false, code: errorCode(error), error }\n }\n}\n\n/**\n * Confirm a change to a panel the plugin does not manage, and how to reach it.\n *\n * Every mechanism that endpoint has is offered, least invasive first, so the\n * person chooses whether the plugin edits that panel's config, runs the\n * home-hosted CLI against it, or mints a token and uses its API. A host that\n * cannot reach a person proceeds on the mechanism the caller would have used\n * anyway; the tool result then says which one it was.\n */\nasync function askForeignPanel(\n ctx: Context,\n exec: unknown,\n instances: readonly InstanceView[],\n target: InstanceView,\n mechanisms: readonly ForeignMechanism[],\n): Promise<TargetDecision> {\n const questions = ctx.get('userQuestions') as UserQuestionsLike | undefined\n const first = mechanisms[0] as ForeignMechanism\n if (typeof questions?.ask !== 'function')\n return { ok: true, home: target.home, via: first }\n\n const answer = await ask(ctx, exec, questions, {\n id: 'home-hosted-foreign-panel',\n question: `${target.home} is not a panel this plugin manages. How should it be changed?`,\n detail: inventoryText(instances),\n options: [\n ...mechanisms.map(mechanism => ({\n label: MECHANISM_WORDING[mechanism].option,\n description: `${MECHANISM_WORDING[mechanism].phrase}${\n mechanism === 'api' ? '; that mints a token for it, replacing any it had' : '; this plugin holds no API token for that panel'}`,\n })),\n { label: CANCEL_LABEL, description: 'Change nothing; say which panel you meant instead.' },\n ],\n })\n\n if (!answer.ok) {\n if (answer.code === 'ASK_ABORTED')\n return { ok: false, text: 'cancelled: the user dismissed the panel question to speak instead; stop and wait for their message.' }\n if (answer.code !== null && NO_ANSWERER_CODES.has(answer.code))\n return { ok: true, home: target.home, via: first }\n return { ok: false, text: `refused: the panel question could not be asked (${messageOf(answer.error)}); ask the user before changing another panel.` }\n }\n\n // Cancel settles it: an answer that cancels, or cancels and types something,\n // must never be read as consent.\n if (answer.selected.includes(CANCEL_LABEL))\n return { ok: false, text: `cancelled: the user did not confirm changing ${target.home}; ask which panel they meant.` }\n const chosen = answer.selected.find(label => label !== CANCEL_LABEL)\n if (chosen !== undefined) {\n const mechanism = mechanisms.find(candidate => MECHANISM_WORDING[candidate].option === chosen)\n if (mechanism !== undefined)\n return { ok: true, home: target.home, via: mechanism }\n }\n if (chosen === undefined && answer.custom !== null) {\n const mechanism = mechanisms.find(candidate => matchesMechanism(answer.custom as string, candidate))\n if (mechanism !== undefined)\n return { ok: true, home: target.home, via: mechanism }\n }\n return { ok: false, text: `cancelled: the user did not confirm changing ${target.home}; ask which panel they meant.` }\n}\n\n/** Whether free text names a mechanism, so a typed answer is honoured. */\nfunction matchesMechanism(text: string, mechanism: ForeignMechanism): boolean {\n const value = text.toLowerCase()\n if (mechanism === 'file')\n return /file|config/.test(value)\n if (mechanism === 'cli')\n return /cli|command|terminal/.test(value)\n return /api|token/.test(value)\n}\n\n/**\n * Ask which panel a call that named none should hit, when more than one could\n * take it. The managed panel is the default, so a host with no answerer proceeds\n * there; the others are only offered when this call knows how to reach them.\n */\nasync function askWhichPanel(\n ctx: Context,\n exec: unknown,\n instances: readonly InstanceView[],\n managed: InstanceView,\n others: readonly InstanceView[],\n mechanisms: readonly ForeignMechanism[],\n): Promise<TargetDecision> {\n const questions = ctx.get('userQuestions') as UserQuestionsLike | undefined\n if (typeof questions?.ask !== 'function')\n return { ok: true, home: null, via: null }\n\n const answer = await ask(ctx, exec, questions, {\n id: 'home-hosted-instance',\n question: `${instances.length} home-hosted panels were found on this machine. Act on the panel this plugin manages?`,\n detail: inventoryText(instances),\n options: [\n { label: managed.home, description: `managed by this plugin${managed.url === null ? '' : ` \u00B7 ${managed.url}`}` },\n ...others.map(instance => ({\n label: instance.home,\n description: `not managed by this plugin${instance.url === null ? '' : ` \u00B7 ${instance.url}`}`\n + ` \u00B7 ${mechanisms.map(candidate => MECHANISM_WORDING[candidate].phrase).join(', or ')}`,\n })),\n { label: CANCEL_LABEL, description: 'Do nothing; say which panel you meant instead.' },\n ],\n })\n\n if (!answer.ok) {\n if (answer.code === 'ASK_ABORTED')\n return { ok: false, text: 'cancelled: the user dismissed the panel question to speak instead; stop and wait for their message.' }\n if (answer.code !== null && NO_ANSWERER_CODES.has(answer.code))\n return { ok: true, home: null, via: null }\n return { ok: false, text: `refused: the panel question could not be asked (${messageOf(answer.error)}); ask the user which panel they mean before acting.` }\n }\n\n if (answer.selected.includes(CANCEL_LABEL))\n return { ok: false, text: 'cancelled: the user chose not to pick a panel; ask them which one they mean.' }\n const chosen = answer.selected.find(label => label !== CANCEL_LABEL)\n if (chosen !== undefined) {\n const instance = matchInstance(instances, chosen)\n if (instance === null)\n return { ok: false, text: `cancelled: the answer \"${chosen}\" names no panel; ask the user which panel they mean.` }\n return instance.managed ? { ok: true, home: null, via: null } : { ok: true, home: instance.home, via: null }\n }\n if (answer.custom !== null) {\n const instance = matchInstance(instances, answer.custom)\n if (instance?.managed === true)\n return { ok: true, home: null, via: null }\n if (instance !== null)\n return { ok: true, home: instance.home, via: null }\n return { ok: false, text: `cancelled: the user answered \"${answer.custom}\", which names no panel; ask them which panel they mean.` }\n }\n return { ok: false, text: 'cancelled: the panel question was not answered; ask the user which panel they mean before acting.' }\n}\n\n/**\n * Run the questions a plan called for, and settle on the panel and mechanism.\n *\n * A call that named another panel is asked about the mechanism straight away; a\n * call that named none is asked which panel first, and when that answer is\n * another panel it is asked how to reach it \u2014 one decision per question.\n */\nasync function confirmPlan(\n ctx: Context,\n exec: unknown,\n service: HomeHostedService,\n plan: Extract<TargetPlan, { ok: true }>,\n): Promise<TargetDecision> {\n if (plan.ask === null)\n return { ok: true, home: plan.home, via: null }\n const instances = await inventoryOf(service)\n const managed = instances.find(instance => instance.managed)\n if (plan.ask === 'foreign') {\n const target = instances.find(instance => instance.home === plan.home)\n // A panel that vanished between validation and here is not worth a question.\n return target === undefined\n ? { ok: false, text: refusalForUnknownPanel(instances, plan.home) }\n : await askForeignPanel(ctx, exec, instances, target, plan.mechanisms)\n }\n if (managed === undefined)\n return { ok: true, home: null, via: null }\n const others = instances.filter(instance => !instance.managed)\n const picked = await askWhichPanel(ctx, exec, instances, managed, others, plan.mechanisms)\n if (!picked.ok || picked.home === null)\n return picked\n const chosen = instances.find(instance => instance.home === picked.home)\n if (chosen === undefined || chosen.managed)\n return picked\n return await askForeignPanel(ctx, exec, instances, chosen, plan.mechanisms)\n}\n\n/** Error codes that mean the panel refused the plugin's credential. */\nconst TOKEN_REFUSAL_CODES = new Set([\n 'AUTH_REQUIRED',\n 'AUTH_UNARMED',\n 'UNAUTHORIZED',\n 'FORBIDDEN',\n 'INVALID_TOKEN',\n 'TOKEN_STALE',\n 'TOKEN_REFUSED',\n])\n\nfunction errorCode(error: unknown): string | null {\n const code = (error as { code?: unknown } | null | undefined)?.code\n return typeof code === 'string' ? code : null\n}\n\nfunction errorStatus(error: unknown): number | null {\n const status = (error as { status?: unknown } | null | undefined)?.status\n return typeof status === 'number' ? status : null\n}\n\n/** A panel answer that refused the token, as opposed to a bad request or a 404. */\nfunction refusedByPanel(error: unknown): boolean {\n const status = errorStatus(error)\n if (status === 401 || status === 403)\n return true\n const code = errorCode(error)\n return code !== null && TOKEN_REFUSAL_CODES.has(code)\n}\n\n/**\n * Whether a failed call failed because the panel refused this plugin's token.\n *\n * A 401/403 or an auth code is that refusal directly. A client that could not be\n * established at all is ambiguous \u2014 the panel may simply be down \u2014 so the\n * panel's measured token state is asked instead; only `stale`, which the panel\n * sets from an actual refusal, justifies a repair.\n */\nasync function tokenRefused(service: Pick<HomeHostedService, 'call'>, error: unknown): Promise<boolean> {\n if (refusedByPanel(error))\n return true\n if (errorCode(error) !== 'PANEL_UNAVAILABLE')\n return false\n try {\n const status = await service.call('status', {}) as HomeHostedStatus | null\n return status?.panel.token === 'stale'\n }\n catch {\n return false\n }\n}\n\nfunction messageOf(error: unknown): string {\n return error instanceof Error ? error.message : String(error)\n}\n\nfunction failureText(error: unknown): string {\n return `failed: ${messageOf(error)}`\n}\n\nfunction registerOne(ctx: Context, service: HomeHostedService, settings: SettingsStore, name: AgentToolName): () => void {\n const spec = TOOL_SPECS[name]\n const toolName = toolNameFor(name)\n const mutatingTool = MUTATING_AGENT_TOOLS.includes(name)\n\n return ctx.tools.register(defineTool({\n name: toolName,\n description: spec.description,\n parameters: spec.parameters,\n output: {\n schema: { type: 'string' },\n render: (_args, value) => [{ type: 'text', text: value }],\n },\n async execute(args, exec) {\n const input = (args ?? {}) as Input\n // Argument handling is not a panel call: a bad input is reported as-is,\n // never \"repaired\" by minting a token.\n let request: { endpoint: RpcEndpoint, payload: unknown }\n try {\n request = spec.run(input)\n }\n catch (error) {\n return failureText(error)\n }\n\n const mode = sandboxMode(ctx, exec)\n // Fail closed: only a positively identified read-only action skips the\n // gate; an unknown action is rejected by the argument check above.\n const mutating = mutatingTool && !(READ_ONLY_ACTIONS[name]?.(input) ?? false)\n // The panel is settled before permission is asked: a refusal here changes\n // nothing, and no question is put to the user for a call that will not run.\n const plan = await planTarget(service, settings, request.endpoint, stringArg(input, 'instance'), mutating)\n if (!plan.ok)\n return plan.text\n\n if (mutating && mode !== 'danger-full-access') {\n const remedy = 'Set the session to Full access (danger-full-access), or use a session where approvals can be answered.'\n const approval = ctx.get('approval') as ApprovalLike | undefined\n if (approval?.request === undefined)\n return `refused: this session runs in ${mode ?? 'an unknown'} sandbox and the deployment has no approval service. ${remedy}`\n const outcome = await approval.request({\n agent: (exec as { agent?: unknown } | undefined)?.agent,\n toolName,\n reason: `${spec.description} (${JSON.stringify(input)})`,\n })\n if (outcome !== 'allowed-once')\n return `refused: approval answered \"${outcome}\" (session sandbox: ${mode ?? 'unknown'}). ${remedy}`\n }\n\n const target = await confirmPlan(ctx, exec, service, plan)\n if (!target.ok)\n return target.text\n const payload = target.home === null\n ? request.payload\n : withTarget(request.payload, target.home, target.via ?? FOREIGN_MECHANISMS[request.endpoint]?.[0] ?? null)\n // What another panel's result must carry: the model may not have been the\n // one that chose it, and a config edit has no page to show.\n const landing = target.home === null\n ? ''\n : `\\n\\nhome-hosted: ${plan.ask === null ? 'read from' : 'acted on'} ${target.home}, not the panel this plugin manages`\n + `${target.via === null ? '' : `, ${MECHANISM_WORDING[target.via].phrase}`}.`\n\n try {\n return `${JSON.stringify(await service.call(request.endpoint, payload), null, 2)}${landing}`\n }\n catch (error) {\n // A foreign call carries its own credential; repairing this plugin's\n // managed token would churn it and still fail the retry.\n if (target.home !== null || !settings.get().reclaimToken || !(await tokenRefused(service, error)))\n return failureText(error)\n\n // One repair, one retry. The reclaim is a write, but it repairs this\n // plugin's own credential, not the panel's state, so it runs behind the\n // call's existing approval gate rather than asking for a second one.\n try {\n await service.call('panel.reclaimToken', {})\n }\n catch (reclaimError) {\n return `${failureText(error)} (token reclaim failed: ${messageOf(reclaimError)})`\n }\n try {\n return `${JSON.stringify(await service.call(request.endpoint, payload), null, 2)}${landing}`\n }\n catch (retryError) {\n return failureText(retryError)\n }\n }\n },\n }))\n}\n\nexport function registerAgentTools(ctx: Context, service: HomeHostedService, settings: SettingsStore): void {\n ctx.inject(['tools'], (scoped) => {\n const disposers: Array<() => void> = []\n\n const sync = (): void => {\n while (disposers.length > 0)\n disposers.pop()?.()\n const current = settings.get()\n if (!current.agentTools.enabled)\n return\n for (const name of current.agentTools.allow) {\n try {\n disposers.push(registerOne(scoped, service, settings, name))\n }\n catch {\n // one unruly tool must not keep the rest of the allowlist off the model\n }\n }\n }\n\n scoped.effect(() => {\n sync()\n const off = settings.onChange(sync)\n return () => {\n off()\n while (disposers.length > 0)\n disposers.pop()?.()\n }\n }, 'dsh-home-hosted: agent tools')\n })\n}\n"],
5
5
  "mappings": "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",
6
6
  "names": ["path", "fs", "path", "process", "fs", "path", "process", "process", "fs", "path", "name", "os", "process", "fs", "name", "name", "run", "fs", "fs", "path", "validate", "path", "codeOf", "problem", "run", "fs", "fs", "path", "validate", "fs", "path", "fs", "path", "validate", "codeOf", "problem", "run", "name", "path", "fs", "payload", "process", "os", "candidate", "status", "provider", "fs", "fs", "path", "path", "fs", "path", "name", "fs", "fs", "path", "process", "fs", "os", "path", "os", "path", "fs", "fs", "path", "process", "process", "fs", "path", "process", "fs", "fs", "path", "process", "fs", "path", "version", "supported", "process", "fs", "path", "process", "path", "fs", "process", "path", "fs", "name", "path", "fs", "process", "dshHome", "name", "launch", "launcherPath", "fs", "os", "path", "process", "process", "path", "fs", "os", "name", "spawn", "fs", "path", "process", "path", "process", "fs", "spawn", "path", "answer", "path", "path", "runCli", "path", "process", "dshHome", "entry", "result", "fs", "cliArgs", "name", "name", "answer", "path"]
7
7
  }