@demicodes/host-local 0.5.0 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.mts CHANGED
@@ -8,7 +8,7 @@ import { Provider } from "@demicodes/provider";
8
8
  * Reads the invoked name from the symlink path, applies the stdin grace
9
9
  * contract from docs/command-bridge.md, and POSTs /run on the UDS socket.
10
10
  */
11
- declare const COMMAND_BRIDGE_SHIM_SOURCE = "#!/usr/bin/env node\nconst { request } = require('node:http')\nconst { basename } = require('node:path')\n\n// Stdin policy (docs/command-bridge.md): if no byte arrives within the grace\n// window, proceed with empty stdin. Once any byte arrives, clear the timer\n// and read until EOF with no time cap. Late data after empty-stdin dispatch\n// is reported on stderr and not pretended to have been delivered.\nconst STDIN_GRACE_MS = 300\n\nfunction readStdin() {\n if (process.stdin.isTTY) return Promise.resolve('')\n return new Promise((resolve, reject) => {\n const chunks = []\n let resolved = false\n let timedOut = false\n let timer\n const cleanup = () => {\n clearTimeout(timer)\n process.stdin.removeListener('data', onData)\n process.stdin.removeListener('end', onEnd)\n process.stdin.removeListener('error', onError)\n }\n const onData = (chunk) => {\n if (timedOut) {\n process.stderr.write(\n 'command bridge: ' + chunk.length + ' byte(s) of stdin arrived after the ' + STDIN_GRACE_MS +\n 'ms grace period elapsed; the command already ran with empty stdin\\n',\n )\n cleanup()\n return\n }\n clearTimeout(timer)\n chunks.push(chunk)\n }\n const onEnd = () => {\n if (timedOut) {\n cleanup()\n return\n }\n resolved = true\n cleanup()\n resolve(Buffer.concat(chunks).toString('utf8'))\n }\n const onError = (error) => {\n if (resolved || timedOut) return\n resolved = true\n cleanup()\n reject(error)\n }\n timer = setTimeout(() => {\n timedOut = true\n resolved = true\n resolve('')\n }, STDIN_GRACE_MS)\n process.stdin.on('data', onData)\n process.stdin.on('end', onEnd)\n process.stdin.on('error', onError)\n })\n}\n\nfunction postRun(socketPath, body) {\n return new Promise((resolve, reject) => {\n const req = request(\n { socketPath, path: '/run', method: 'POST', headers: { 'content-type': 'application/json' } },\n (res) => {\n const chunks = []\n res.on('data', (chunk) => chunks.push(chunk))\n res.on('end', () => resolve({ statusCode: res.statusCode || 0, body: Buffer.concat(chunks).toString('utf8') }))\n },\n )\n req.on('error', reject)\n req.end(body)\n })\n}\n\nasync function main() {\n const socketPath = process.env.DEMI_COMMAND_BRIDGE_SOCK\n // Shell sessions export DEMI_SESSION_ID as the command scope id (the agent\n // session id for agent-owned shells).\n const commandScopeId = process.env.DEMI_SESSION_ID\n if (!socketPath || !commandScopeId) {\n process.stderr.write('command bridge: DEMI_COMMAND_BRIDGE_SOCK / session id not set in this shell\\n')\n process.exit(1)\n }\n const name = basename(process.argv[1] || '')\n const args = process.argv.slice(2)\n const stdin = await readStdin()\n const body = JSON.stringify({ commandScopeId, name, args, cwd: process.cwd(), stdin })\n\n const response = await postRun(socketPath, body)\n if (response.statusCode !== 200) {\n let message = response.body\n try {\n message = JSON.parse(response.body).error || message\n } catch {}\n process.stderr.write('command bridge: ' + message + '\\n')\n process.exit(1)\n }\n const result = JSON.parse(response.body)\n if (result.stdout) {\n // Binary final streams arrive base64-encoded; write raw bytes so external\n // pipes (demi read a.png | ffmpeg -i - ...) stay byte-clean.\n process.stdout.write(result.stdoutEncoding === 'base64' ? Buffer.from(result.stdout, 'base64') : result.stdout)\n }\n if (result.stderr) process.stderr.write(result.stderr)\n process.exit(result.exitCode)\n}\n\nmain().catch((error) => {\n process.stderr.write('command bridge: ' + (error && error.message ? error.message : String(error)) + '\\n')\n process.exit(1)\n})\n";
11
+ declare const COMMAND_BRIDGE_SHIM_SOURCE = "#!/usr/bin/env node\nconst { request } = require('node:http')\nconst { basename } = require('node:path')\n\n// Stdin policy (docs/command-bridge.md): if no byte arrives within the grace\n// window, proceed with empty stdin. Once any byte arrives, clear the timer\n// and read until EOF with no time cap. Late data after empty-stdin dispatch\n// is reported on stderr and not pretended to have been delivered.\nconst STDIN_GRACE_MS = 300\n\nfunction readStdin() {\n if (process.stdin.isTTY) return Promise.resolve('')\n return new Promise((resolve, reject) => {\n const chunks = []\n let resolved = false\n let timedOut = false\n let timer\n const cleanup = () => {\n clearTimeout(timer)\n process.stdin.removeListener('data', onData)\n process.stdin.removeListener('end', onEnd)\n process.stdin.removeListener('error', onError)\n }\n const onData = (chunk) => {\n if (timedOut) {\n process.stderr.write(\n 'command bridge: ' + chunk.length + ' byte(s) of stdin arrived after the ' + STDIN_GRACE_MS +\n 'ms grace period elapsed; the command already ran with empty stdin\\n',\n )\n cleanup()\n return\n }\n clearTimeout(timer)\n chunks.push(chunk)\n }\n const onEnd = () => {\n if (timedOut) {\n cleanup()\n return\n }\n resolved = true\n cleanup()\n resolve(Buffer.concat(chunks).toString('utf8'))\n }\n const onError = (error) => {\n if (resolved || timedOut) return\n resolved = true\n cleanup()\n reject(error)\n }\n timer = setTimeout(() => {\n timedOut = true\n resolved = true\n resolve('')\n }, STDIN_GRACE_MS)\n process.stdin.on('data', onData)\n process.stdin.on('end', onEnd)\n process.stdin.on('error', onError)\n })\n}\n\nfunction postRun(socketPath, body) {\n return new Promise((resolve, reject) => {\n const req = request(\n { socketPath, path: '/run', method: 'POST', headers: { 'content-type': 'application/json' } },\n (res) => {\n const chunks = []\n res.on('data', (chunk) => chunks.push(chunk))\n res.on('end', () => resolve({ statusCode: res.statusCode || 0, body: Buffer.concat(chunks).toString('utf8') }))\n },\n )\n req.on('error', reject)\n req.end(body)\n })\n}\n\nasync function main() {\n const socketPath = process.env.DEMI_COMMAND_BRIDGE_SOCK\n const shellId = process.env.DEMI_SHELL_ID\n if (!socketPath || !shellId) {\n process.stderr.write('command bridge: DEMI_COMMAND_BRIDGE_SOCK / shell id not set in this shell\\n')\n process.exit(1)\n }\n const name = basename(process.argv[1] || '')\n const args = process.argv.slice(2)\n const stdin = await readStdin()\n const body = JSON.stringify({ shellId, name, args, cwd: process.cwd(), stdin })\n\n const response = await postRun(socketPath, body)\n if (response.statusCode !== 200) {\n let message = response.body\n try {\n message = JSON.parse(response.body).error || message\n } catch {}\n process.stderr.write('command bridge: ' + message + '\\n')\n process.exit(1)\n }\n const result = JSON.parse(response.body)\n if (result.stdout) {\n // Binary final streams arrive base64-encoded; write raw bytes so external\n // pipes (demi read a.png | ffmpeg -i - ...) stay byte-clean.\n process.stdout.write(result.stdoutEncoding === 'base64' ? Buffer.from(result.stdout, 'base64') : result.stdout)\n }\n if (result.stderr) process.stderr.write(result.stderr)\n process.exit(result.exitCode)\n}\n\nmain().catch((error) => {\n process.stderr.write('command bridge: ' + (error && error.message ? error.message : String(error)) + '\\n')\n process.exit(1)\n})\n";
12
12
  interface CommandBridgeOptions {
13
13
  socketPath: string;
14
14
  }
@@ -92,7 +92,7 @@ interface LocalAgentServerHandle {
92
92
  * bridge **on by default**.
93
93
  *
94
94
  * Owns the Node-only bridge transport (UDS + PATH shims under `stateDir`).
95
- * AgentServer only receives a generic `prepareSessionShell` hook and exposes
95
+ * AgentServer only receives a generic `prepareShell` hook and exposes
96
96
  * `runCommandLine` — it never sees bin dirs or sockets.
97
97
  *
98
98
  * Default layout under `stateDir` (`~/.demi` or `$DEMI_HOME`):
package/dist/index.mjs CHANGED
@@ -1,7 +1,7 @@
1
1
  import { existsSync, mkdirSync, unlinkSync } from "node:fs";
2
2
  import { dirname, isAbsolute, join, relative, resolve } from "node:path";
3
3
  import { createServer } from "node:http";
4
- import { AgentServer, RunCommandLineCommandNotRegisteredError, RunCommandLineSessionNotFoundError, RunCommandLineTimeoutError } from "@demicodes/agent";
4
+ import { AgentServer, RunCommandLineCommandNotRegisteredError, RunCommandLineShellNotFoundError, RunCommandLineTimeoutError } from "@demicodes/agent";
5
5
  import { encodeUtf8, errorMessage, isFileNotFoundError, parsePortableJson, stringifyPortableJson } from "@demicodes/utils";
6
6
  import { createHash, randomUUID } from "node:crypto";
7
7
  import { resolveDemiHome } from "@demicodes/provider/credentials-pool";
@@ -92,17 +92,15 @@ function postRun(socketPath, body) {
92
92
 
93
93
  async function main() {
94
94
  const socketPath = process.env.DEMI_COMMAND_BRIDGE_SOCK
95
- // Shell sessions export DEMI_SESSION_ID as the command scope id (the agent
96
- // session id for agent-owned shells).
97
- const commandScopeId = process.env.DEMI_SESSION_ID
98
- if (!socketPath || !commandScopeId) {
99
- process.stderr.write('command bridge: DEMI_COMMAND_BRIDGE_SOCK / session id not set in this shell\\n')
95
+ const shellId = process.env.DEMI_SHELL_ID
96
+ if (!socketPath || !shellId) {
97
+ process.stderr.write('command bridge: DEMI_COMMAND_BRIDGE_SOCK / shell id not set in this shell\\n')
100
98
  process.exit(1)
101
99
  }
102
100
  const name = basename(process.argv[1] || '')
103
101
  const args = process.argv.slice(2)
104
102
  const stdin = await readStdin()
105
- const body = JSON.stringify({ commandScopeId, name, args, cwd: process.cwd(), stdin })
103
+ const body = JSON.stringify({ shellId, name, args, cwd: process.cwd(), stdin })
106
104
 
107
105
  const response = await postRun(socketPath, body)
108
106
  if (response.statusCode !== 200) {
@@ -154,7 +152,7 @@ async function handleRun(server, req, res) {
154
152
  return;
155
153
  }
156
154
  if (!isRunRequestBody(body)) {
157
- sendJson(res, 400, { error: "bad request: expected { commandScopeId, name, args, cwd, stdin }" });
155
+ sendJson(res, 400, { error: "bad request: expected { shellId, name, args, cwd, stdin }" });
158
156
  return;
159
157
  }
160
158
  const controller = new AbortController();
@@ -164,7 +162,7 @@ async function handleRun(server, req, res) {
164
162
  };
165
163
  req.on("close", onClosedEarly);
166
164
  try {
167
- const result = await server.runCommandLine(body.commandScopeId, body.name, body.args, {
165
+ const result = await server.runCommandLine(body.shellId, body.name, body.args, {
168
166
  cwd: body.cwd,
169
167
  stdin: body.stdin,
170
168
  signal: controller.signal
@@ -179,7 +177,7 @@ async function handleRun(server, req, res) {
179
177
  }
180
178
  }
181
179
  function statusForError(error) {
182
- if (error instanceof RunCommandLineSessionNotFoundError) return 404;
180
+ if (error instanceof RunCommandLineShellNotFoundError) return 404;
183
181
  if (error instanceof RunCommandLineCommandNotRegisteredError) return 404;
184
182
  if (error instanceof RunCommandLineTimeoutError) return 504;
185
183
  return 500;
@@ -187,7 +185,7 @@ function statusForError(error) {
187
185
  function isRunRequestBody(value) {
188
186
  if (value === null || typeof value !== "object") return false;
189
187
  const record = value;
190
- return typeof record.commandScopeId === "string" && typeof record.name === "string" && Array.isArray(record.args) && record.args.every((arg) => typeof arg === "string") && typeof record.cwd === "string" && typeof record.stdin === "string";
188
+ return typeof record.shellId === "string" && typeof record.name === "string" && Array.isArray(record.args) && record.args.every((arg) => typeof arg === "string") && typeof record.cwd === "string" && typeof record.stdin === "string";
191
189
  }
192
190
  function readJsonBody(req) {
193
191
  return new Promise((resolve, reject) => {
@@ -579,7 +577,7 @@ async function* streamBytes(stream) {
579
577
  * bridge **on by default**.
580
578
  *
581
579
  * Owns the Node-only bridge transport (UDS + PATH shims under `stateDir`).
582
- * AgentServer only receives a generic `prepareSessionShell` hook and exposes
580
+ * AgentServer only receives a generic `prepareShell` hook and exposes
583
581
  * `runCommandLine` — it never sees bin dirs or sockets.
584
582
  *
585
583
  * Default layout under `stateDir` (`~/.demi` or `$DEMI_HOME`):
@@ -612,7 +610,7 @@ function createLocalAgentServer(options) {
612
610
  if (bridgeEnabled && socketPath && stateDir) {
613
611
  const bridgeStateDir = stateDir;
614
612
  const bridgeSocketPath = socketPath;
615
- serverOptions.prepareSessionShell = async ({ host, agentSessionId, commandNames, shell }) => {
613
+ serverOptions.prepareShell = async ({ host, agentSessionId, commandNames, shell }) => {
616
614
  const shimDir = await materializeCommandBridgeShims({
617
615
  host,
618
616
  agentSessionId,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@demicodes/host-local",
3
3
  "description": "Node LocalHost and open-box local AgentServer assembly (command bridge on by default).",
4
- "version": "0.5.0",
4
+ "version": "0.6.0",
5
5
  "private": false,
6
6
  "type": "module",
7
7
  "exports": {
@@ -11,10 +11,10 @@
11
11
  }
12
12
  },
13
13
  "dependencies": {
14
- "@demicodes/agent": "^0.5.0",
15
- "@demicodes/provider": "^0.5.0",
16
- "@demicodes/shell": "^0.5.0",
17
- "@demicodes/utils": "^0.5.0"
14
+ "@demicodes/agent": "^0.6.0",
15
+ "@demicodes/provider": "^0.6.0",
16
+ "@demicodes/shell": "^0.6.0",
17
+ "@demicodes/utils": "^0.6.0"
18
18
  },
19
19
  "license": "Apache-2.0",
20
20
  "main": "./dist/index.mjs",