hilos-agent 0.9.3 → 0.9.4

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/README.md CHANGED
@@ -25,16 +25,20 @@ machine until then).
25
25
 
26
26
  ## Quick start
27
27
 
28
- In hilos: open your agent's profile → **Connect** → copy the
29
- `hilos-agent --join …` command. Then on your machine, **run it from inside your
30
- repo's folder** the daemon matches the repo by its git remote, so no config is
31
- needed:
28
+ In hilos: open your agent's profile → **Connect** → **Run in channel**. Copy the
29
+ terminal command and run it from inside your repo's folder. It waits at a hidden
30
+ prompt; copy the private join code from Hilos and paste it there. The daemon
31
+ matches the repo by its git remote, so no config is needed, and the reusable
32
+ credential never enters shell history or the process list:
32
33
 
33
34
  ```sh
34
35
  cd ~/code/your-repo
35
- npx hilos-agent@latest --join <blob> # token + endpoint from the link; repo auto-detected from cwd
36
+ npx hilos-agent@latest --join-stdin # then paste the private join code when asked
36
37
  ```
37
38
 
39
+ Previously copied `--join <blob>` commands remain compatible. New commands use
40
+ stdin because the blob contains the agent token and should not live in argv.
41
+
38
42
  Running from elsewhere, or want to map several repos explicitly? Use a config:
39
43
 
40
44
  ```jsonc
@@ -5,6 +5,7 @@
5
5
  //
6
6
  // Usage:
7
7
  // hilos-agent --join <blob> connect with a copy-paste link from hilos
8
+ // hilos-agent --join-stdin read a private link without exposing it in argv
8
9
  // hilos-agent init write a starter config (~/.hilos/agent.json)
9
10
  // hilos-agent run with the resolved config (default)
10
11
  // hilos-agent run same as above, explicit
@@ -20,6 +21,7 @@ import { readFileSync } from "node:fs";
20
21
  import { fileURLToPath } from "node:url";
21
22
 
22
23
  import { resolveConfig, decodeJoin, writeStarterConfig, GLOBAL_CONFIG } from "../src/config.mjs";
24
+ import { readPrivateJoin } from "../src/join-input.mjs";
23
25
  import { run } from "../src/run.mjs";
24
26
  import { hookMain, hooksMain } from "../src/hook.mjs";
25
27
 
@@ -34,6 +36,7 @@ function parseArgs(argv) {
34
36
  for (let i = 0; i < argv.length; i++) {
35
37
  const a = argv[i];
36
38
  if (a === "--join") flags.join = argv[++i];
39
+ else if (a === "--join-stdin") flags.joinStdin = true;
37
40
  else if (a === "--config") flags.config = argv[++i];
38
41
  else if (a === "--channel") flags.channelId = argv[++i];
39
42
  else if (a === "--url") flags.url = argv[++i];
@@ -62,6 +65,7 @@ function parseArgs(argv) {
62
65
  const HELP = `hilos-agent — your coding agent as a teammate in hilos
63
66
 
64
67
  hilos-agent --join <blob> connect using a link copied from hilos
68
+ hilos-agent --join-stdin paste the private link at a no-echo prompt
65
69
  hilos-agent init write a starter config to ~/.hilos/agent.json
66
70
  hilos-agent run the daemon (watch @mentions, propose diffs)
67
71
  hilos-agent hooks install stream this repo's Codex, Claude, and Cursor
@@ -118,9 +122,18 @@ async function main() {
118
122
  return;
119
123
  }
120
124
 
121
- const joinPayload = flags.join ? decodeJoin(flags.join) : undefined;
122
- if (flags.join && !joinPayload) {
123
- console.error("That --join link is invalid. Re-copy it from hilos.");
125
+ if (flags.join && flags.joinStdin) {
126
+ console.error("Use either --join or --join-stdin, not both.");
127
+ process.exit(1);
128
+ }
129
+ const joinBlob = flags.joinStdin ? await readPrivateJoin() : flags.join;
130
+ if (flags.joinStdin && !joinBlob) {
131
+ console.error("No private join code was provided. Re-copy it from hilos.");
132
+ process.exit(1);
133
+ }
134
+ const joinPayload = joinBlob ? decodeJoin(joinBlob) : undefined;
135
+ if (joinBlob && !joinPayload) {
136
+ console.error("That private join code is invalid. Re-copy it from hilos.");
124
137
  process.exit(1);
125
138
  }
126
139
 
@@ -140,6 +153,7 @@ async function main() {
140
153
  // run (default) — when --join is passed without init, connect straight away.
141
154
  const cliFlags = { ...flags };
142
155
  delete cliFlags.join;
156
+ delete cliFlags.joinStdin;
143
157
  delete cliFlags.help;
144
158
  const cfg = resolveConfig({ flags: cliFlags, join: joinPayload });
145
159
  await run(cfg);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hilos-agent",
3
- "version": "0.9.3",
3
+ "version": "0.9.4",
4
4
  "description": "Run your own coding agent (Claude Code / Codex / Cursor) as an autonomous teammate in a hilos channel. Picks up @mentions in channels and threads, makes the change, and opens a PR for review — your code and credentials never leave your machine. (Approve-before-push is available via gate:true.)",
5
5
  "type": "module",
6
6
  "bin": {
@@ -0,0 +1,80 @@
1
+ const MAX_JOIN_BYTES = 64 * 1024;
2
+
3
+ /** @typedef {import("node:stream").Readable & {
4
+ * isTTY?: boolean,
5
+ * isRaw?: boolean,
6
+ * setRawMode?: (enabled: boolean) => unknown,
7
+ * }} PrivateJoinInput */
8
+
9
+ /**
10
+ * Read a private join blob without putting it in process argv or shell history.
11
+ * Piped stdin is consumed normally. On a terminal, raw mode keeps pasted input
12
+ * off-screen while still supporting backspace and Ctrl-C.
13
+ * @param {PrivateJoinInput} input
14
+ * @param {NodeJS.WritableStream} output
15
+ */
16
+ export async function readPrivateJoin(input = process.stdin, output = process.stderr) {
17
+ if (!input?.isTTY || typeof input.setRawMode !== "function") {
18
+ let value = "";
19
+ for await (const chunk of input) {
20
+ value += Buffer.isBuffer(chunk) ? chunk.toString("utf8") : String(chunk);
21
+ if (Buffer.byteLength(value, "utf8") > MAX_JOIN_BYTES) {
22
+ throw new Error("The private join code is too large.");
23
+ }
24
+ }
25
+ return value.trim();
26
+ }
27
+
28
+ output.write("Paste the private join code from hilos, then press Enter: ");
29
+ const wasRaw = Boolean(input.isRaw);
30
+ const wasPaused = typeof input.isPaused === "function" ? input.isPaused() : false;
31
+ const setRawMode = input.setRawMode.bind(input);
32
+
33
+ return new Promise((resolve, reject) => {
34
+ let value = "";
35
+ let settled = false;
36
+
37
+ const cleanup = () => {
38
+ input.off("data", onData);
39
+ input.off("error", onError);
40
+ setRawMode(wasRaw);
41
+ if (wasPaused) input.pause();
42
+ };
43
+ const finish = (error) => {
44
+ if (settled) return;
45
+ settled = true;
46
+ cleanup();
47
+ output.write("\n");
48
+ if (error) reject(error);
49
+ else resolve(value.trim());
50
+ };
51
+ const onError = (error) => finish(error);
52
+ const onData = (chunk) => {
53
+ for (const char of String(chunk)) {
54
+ if (char === "\u0003") {
55
+ finish(new Error("Private join cancelled."));
56
+ return;
57
+ }
58
+ if (char === "\r" || char === "\n") {
59
+ finish();
60
+ return;
61
+ }
62
+ if (char === "\u007f" || char === "\b") {
63
+ value = value.slice(0, -1);
64
+ continue;
65
+ }
66
+ value += char;
67
+ if (Buffer.byteLength(value, "utf8") > MAX_JOIN_BYTES) {
68
+ finish(new Error("The private join code is too large."));
69
+ return;
70
+ }
71
+ }
72
+ };
73
+
74
+ input.setEncoding?.("utf8");
75
+ setRawMode(true);
76
+ input.on("data", onData);
77
+ input.on("error", onError);
78
+ input.resume();
79
+ });
80
+ }