@openscout/scout 0.2.70 → 0.2.73

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/LICENSE +202 -0
  2. package/README.md +59 -3
  3. package/bin/openscout-runtime.mjs +23 -2
  4. package/bin/scout +34 -0
  5. package/bin/scout.mjs +195 -18
  6. package/bin/scoutd +0 -0
  7. package/dist/client/apple-touch-icon.png +0 -0
  8. package/dist/client/assets/RepoDiffViewer-D8gg36W3.js +56 -0
  9. package/dist/client/assets/{addon-fit-BNV7JPhj.js → addon-fit-D4KdY-pj.js} +1 -1
  10. package/dist/client/assets/{addon-webgl-B-_Y5L3F.js → addon-webgl-Bo_tmKBp.js} +1 -1
  11. package/dist/client/assets/arc.es-D0gujIKy.js +188 -0
  12. package/dist/client/assets/embed-entry-CUVNwyB_.js +1 -0
  13. package/dist/client/assets/index-CRkjiso5.js +257 -0
  14. package/dist/client/assets/index-D_RX2cRb.css +1 -0
  15. package/dist/client/assets/{xterm-Cx1oABPt.js → xterm-RfflDFod.js} +1 -1
  16. package/dist/client/favicon-16.png +0 -0
  17. package/dist/client/favicon-32.png +0 -0
  18. package/dist/client/favicon.ico +0 -0
  19. package/dist/client/favicon.svg +48 -0
  20. package/dist/client/index.html +42 -2
  21. package/dist/client/openscout-icon.png +0 -0
  22. package/dist/client/site.webmanifest +19 -0
  23. package/dist/client/web-app-icon-192.png +0 -0
  24. package/dist/client/web-app-icon-512.png +0 -0
  25. package/dist/drizzle/0000_curly_iron_monger.sql +592 -0
  26. package/dist/drizzle/0001_invocation-status-columns.sql +7 -0
  27. package/dist/drizzle/0002_invocation-flight-metadata.sql +21 -0
  28. package/dist/drizzle/README.md +81 -0
  29. package/dist/drizzle/meta/0000_snapshot.json +4575 -0
  30. package/dist/drizzle/meta/0001_snapshot.json +4624 -0
  31. package/dist/drizzle/meta/0002_snapshot.json +4631 -0
  32. package/dist/drizzle/meta/_journal.json +27 -0
  33. package/dist/main.mjs +77080 -60152
  34. package/dist/node/main.mjs +7607 -0
  35. package/dist/openscout-terminal-relay.mjs +3830 -152
  36. package/dist/{pair-supervisor.mjs → pairing-runtime-controller.mjs} +50837 -41212
  37. package/dist/runtime/base-daemon.mjs +2219 -488
  38. package/dist/runtime/broker-daemon.mjs +39082 -25118
  39. package/dist/runtime/broker-process-manager.mjs +2048 -392
  40. package/dist/runtime/mesh-discover.mjs +2012 -391
  41. package/dist/scout-control-plane-web.mjs +58128 -34736
  42. package/dist/scout-web-server.mjs +58128 -34736
  43. package/dist/statusline.mjs +287 -0
  44. package/package.json +8 -4
  45. package/dist/client/assets/index-BJri_z5a.css +0 -1
  46. package/dist/client/assets/index-Ccjo5BZz.js +0 -199
package/LICENSE ADDED
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
package/README.md CHANGED
@@ -20,7 +20,7 @@ scout whoami
20
20
  scout who
21
21
  scout latest
22
22
  scout runtimes
23
- scout ask --to dewey "can you review our docs?"
23
+ scout ask --project ../talkie --harness claude "can you review our docs?"
24
24
  ```
25
25
 
26
26
  `scout setup` is the canonical onboarding entry point. It creates or updates:
@@ -31,7 +31,24 @@ scout ask --to dewey "can you review our docs?"
31
31
 
32
32
  It also discovers local and project-backed agents from your configured workspace roots, installs the base Scout service, attempts to start it, and ensures Caddy is available for the local `scout.local` edge. On macOS, setup installs missing Caddy with `brew install caddy`; otherwise install Caddy yourself or set `OPENSCOUT_CADDY_BIN`.
33
33
 
34
- `scout init` writes `~/.openscout/config.json` with the broker, web, and pairing ports that every Scout component reads. Run it once after install, or with `--force` to overwrite.
34
+ `scout doctor --fix` asks the native `scoutd` daemon to run conservative repairs
35
+ when that daemon version exposes them. Use `scout doctor --fix --yes` for
36
+ non-interactive install scripts; older or missing `scoutd` binaries simply leave
37
+ the normal doctor report intact.
38
+
39
+ For a CLI-only onboarding pass, save the same identity, workspace roots, and
40
+ runtime choice used by the app flows:
41
+
42
+ ```bash
43
+ scout config set name "Ada"
44
+ scout setup --source-root ~/dev --default-harness codex
45
+ scout runtimes
46
+ ```
47
+
48
+ `scout setup` creates `~/.openscout/config.json` when it is missing. Use
49
+ `scout init` when you only want to rewrite the local host/port config, for
50
+ example `scout init --force --broker-port 43110 --web-port 43120
51
+ --pairing-port 43130`.
35
52
 
36
53
  When the input is not a known subcommand and includes exactly one `@agent` mention, Scout treats it as an implicit `ask`: it records durable work with the broker, waits for the target to acknowledge or complete immediately, and leaves later completion visible through the conversation or flight follow-up. For example:
37
54
 
@@ -52,6 +69,13 @@ The routing rules do not change by harness, UI, or host:
52
69
  - owned work / requested reply -> `scout ask`
53
70
  - follow-up stays in the same DM or explicit channel
54
71
 
72
+ The lowest-churn fresh start is **capability first**: pass the project path and
73
+ optional harness, then let the broker choose or create the concrete worker.
74
+ Do not guess generic names like `claude.main` just because you want Claude. Use
75
+ the returned `ref`, flight, conversation, work, or session handle for follow-up.
76
+ If the worker proves useful, promote it to a named/pinned sibling after the fact
77
+ using the broker-suggested handle when one is returned.
78
+
55
79
  When sender, target, or recent activity is unclear, the shortest orientation loop is:
56
80
 
57
81
  ```bash
@@ -95,11 +119,24 @@ Default sender resolution is:
95
119
  3. the current project-scoped sender inferred from your working directory
96
120
  4. your operator name when you're outside a project context
97
121
 
122
+ Coding-agent hosts use the project-scoped sender for `scout ask` automatically when
123
+ the CLI detects a host harness signal:
124
+
125
+ - **Scout-managed:** `OPENSCOUT_AGENT`, `OPENSCOUT_MANAGED_AGENT`
126
+ - **Cursor:** `CURSOR_AGENT=1`
127
+ - **Claude Code:** `CLAUDECODE=1`, `CLAUDE_CODE_CHILD_SESSION`, `CLAUDE_CODE_REMOTE`, session ids
128
+ - **Codex:** `CODEX_THREAD_ID`, `CODEX_CI=1`, `CODEX_SANDBOX`, proposed `AGENT=codex`
129
+
130
+ Run `scout whoami --json` to see which signal matched. Human terminal shells still
131
+ default to `operator` unless `--as` or a host signal is present.
132
+
98
133
  That keeps ordinary collaboration simple:
99
134
 
100
135
  ```bash
101
136
  scout send --to vox "heads up: I’m on the runtime side"
102
137
  scout ask --to vox "can you confirm the broker fix?"
138
+ scout ask --project ../talkie --harness claude "review the build spec"
139
+ scout ask --harness codex "review this in a fresh Codex worker"
103
140
  ```
104
141
 
105
142
  Known on-demand or offline agents are supposed to wake on first delivery. `scout send` and `scout ask` should be the default path; `scout up` is for explicit prewarming or for creating/registering a target the broker does not know yet.
@@ -201,6 +238,25 @@ Aliases: `runtime:` = `harness:`, `persona:` = `profile:`, `branch:` / `worktree
201
238
 
202
239
  If direct send/ask still comes back unresolved, treat that as a routing problem, not a mere "target is offline" problem. The right follow-up is to disambiguate the target, inspect broker context with `scout who` / `scout latest`, or create/register the missing identity. Do not default to pushing the bring-up step back onto the operator for a known target.
203
240
 
241
+ For current-project work where the harness is the only important choice, omit
242
+ `--to` and `--project`; Scout infers the current project and creates or chooses
243
+ a compatible worker:
244
+
245
+ ```bash
246
+ scout ask --harness codex "take a fresh pass on this repo"
247
+ ```
248
+
249
+ That routes by the current project path and asks the broker to create or choose
250
+ a compatible worker. For another repo, pass the repo path explicitly:
251
+
252
+ ```bash
253
+ scout ask --project ../talkie --harness claude "review the modular build spec"
254
+ ```
255
+
256
+ Use an exact `--ref` or `session:<id>` only when the intent is to continue prior
257
+ context. If the broker returns a friendly worker handle, treat it as the human
258
+ mnemonic; promote/pin it only after the routed worker proves useful.
259
+
204
260
  Local product handoffs use the public Scout address:
205
261
 
206
262
  ```bash
@@ -281,7 +337,7 @@ scout who
281
337
  scout latest
282
338
  scout server open
283
339
  scout server start
284
- scout server start --port 3200
340
+ scout server start --port 43120
285
341
  scout server open --path /agents/arc-codex-2.master.mini
286
342
  scout server start --public-origin http://scout.local
287
343
  scout server edge --local-name m1
@@ -44,10 +44,31 @@ const child = spawn(process.execPath, [entrypoint, ...args], {
44
44
  stdio: "inherit",
45
45
  });
46
46
 
47
+ let forwardingSignal = false;
48
+ let childExited = false;
49
+ for (const signal of ["SIGINT", "SIGTERM"]) {
50
+ process.once(signal, () => {
51
+ forwardingSignal = true;
52
+ if (!child.killed) {
53
+ child.kill(signal);
54
+ }
55
+ setTimeout(() => {
56
+ if (!childExited) {
57
+ child.kill("SIGKILL");
58
+ }
59
+ }, 10_000).unref();
60
+ });
61
+ }
62
+
63
+ child.on("error", (error) => {
64
+ console.error(error.message);
65
+ process.exit(1);
66
+ });
47
67
  child.on("exit", (code, signal) => {
48
- if (signal) {
68
+ childExited = true;
69
+ if (signal && !forwardingSignal) {
49
70
  process.kill(process.pid, signal);
50
71
  } else {
51
- process.exit(code ?? 0);
72
+ process.exit(code ?? (signal ? 0 : 1));
52
73
  }
53
74
  });
package/bin/scout ADDED
@@ -0,0 +1,34 @@
1
+ #!/bin/sh
2
+ # Runtime delegator for the Scout CLI.
3
+ #
4
+ # scout.mjs is the real entrypoint. When Bun is available we prefer it for the
5
+ # full CLI; otherwise we hand off to Node for the packaged headless entrypoint.
6
+ #
7
+ # Latency: this is on the statusline hot path (invoked per prompt). `command -v`
8
+ # is a shell builtin (no fork). We only fork `readlink` when the CLI is reached
9
+ # through a bin symlink and the fast sibling lookup misses.
10
+ self="$0"
11
+ dir="${self%/*}"
12
+ if [ ! -f "$dir/scout.mjs" ]; then
13
+ link=$(readlink "$self" 2>/dev/null) || link=""
14
+ if [ -n "$link" ]; then
15
+ case "$link" in
16
+ /*) self="$link" ;;
17
+ *) self="$dir/$link" ;;
18
+ esac
19
+ dir="${self%/*}"
20
+ fi
21
+ fi
22
+
23
+ if command -v bun >/dev/null 2>&1; then
24
+ exec bun "$dir/scout.mjs" "$@"
25
+ fi
26
+
27
+ if command -v node >/dev/null 2>&1; then
28
+ exec node "$dir/scout.mjs" "$@"
29
+ fi
30
+
31
+ echo "Scout requires Bun or Node.js." >&2
32
+ echo "Install Bun: curl -fsSL https://bun.sh/install | bash" >&2
33
+ echo "Or install Node.js and rebuild @openscout/scout for the headless CLI." >&2
34
+ exit 1
package/bin/scout.mjs CHANGED
@@ -1,36 +1,213 @@
1
- #!/usr/bin/env bun
1
+ #!/usr/bin/env node
2
2
 
3
- if (typeof globalThis.Bun === "undefined") {
3
+ import { constants, existsSync, readFileSync, accessSync } from "node:fs";
4
+ import { spawnSync } from "node:child_process";
5
+ import { delimiter, dirname, join, resolve } from "node:path";
6
+ import { fileURLToPath, pathToFileURL } from "node:url";
7
+
8
+ const binDir = dirname(fileURLToPath(import.meta.url));
9
+ const bunDistEntry = resolve(binDir, "../dist/main.mjs");
10
+ const bunStatuslineEntry = resolve(binDir, "../dist/statusline.mjs");
11
+ const nodeDistEntry = resolve(binDir, "../dist/node/main.mjs");
12
+ const nodeStatuslineEntry = resolve(binDir, "../dist/node/statusline.mjs");
13
+ const packageJsonPath = resolve(binDir, "../package.json");
14
+
15
+ const packageVersion = readPackageVersion();
16
+ if (!process.env.SCOUT_APP_VERSION && packageVersion) {
17
+ process.env.SCOUT_APP_VERSION = packageVersion;
18
+ }
19
+
20
+ const command = process.argv[2];
21
+ if (command === "--version" || command === "-v" || command === "version") {
22
+ process.stdout.write(`${packageVersion ?? "0.0.0"}\n`);
23
+ process.exit(0);
24
+ }
25
+
26
+ const wantsHelp = !command || command === "--help" || command === "-h" || command === "help";
27
+ const wantsStatusline = command === "statusline" && process.argv[3] === "claude";
28
+ const preferredHost = normalizeHost(process.env.OPENSCOUT_RUNTIME_HOST);
29
+ const bunPath = preferredHost === "node" ? null : resolveBunExecutable();
30
+ const shouldTryNode = preferredHost === "node" || !bunPath || wantsStatusline;
31
+
32
+ if (shouldTryNode && preferredHost !== "bun") {
33
+ const nodeEntry = wantsStatusline && existsSync(bunStatuslineEntry)
34
+ ? bunStatuslineEntry
35
+ : wantsStatusline
36
+ ? nodeStatuslineEntry
37
+ : nodeDistEntry;
38
+ if (existsSync(nodeEntry)) {
39
+ try {
40
+ if (!bunPath) {
41
+ process.env.OPENSCOUT_RUNTIME_HOST ??= "node";
42
+ }
43
+ await import(pathToFileURL(nodeEntry).href);
44
+ process.exit(process.exitCode ?? 0);
45
+ } catch (error) {
46
+ if (preferredHost !== "node" && bunPath) {
47
+ // A dev checkout can have a stale or incomplete dist/node bundle. Prefer
48
+ // the known-good Bun CLI unless the caller explicitly requested Node.
49
+ } else {
50
+ if (wantsHelp) {
51
+ process.stdout.write(renderNodeFallbackHelp(packageVersion));
52
+ process.exit(0);
53
+ }
54
+ console.error(describeNodeEntrypointFailure(error));
55
+ process.exit(1);
56
+ }
57
+ }
58
+ } else if (preferredHost === "node" && wantsHelp) {
59
+ process.stdout.write(renderNodeFallbackHelp(packageVersion));
60
+ process.exit(0);
61
+ }
62
+ }
63
+
64
+ if (bunPath) {
65
+ const bunEntry = wantsStatusline ? bunStatuslineEntry : bunDistEntry;
66
+ if (!existsSync(bunEntry)) {
67
+ console.error(`Scout ${wantsStatusline ? "statusline" : "CLI"} Bun entry is missing. Reinstall @openscout/scout or rebuild the package.`);
68
+ process.exit(1);
69
+ }
70
+
71
+ if (isCurrentRuntimeBun()) {
72
+ await import(pathToFileURL(bunEntry).href);
73
+ process.exit(0);
74
+ }
75
+
76
+ const result = spawnSync(bunPath, [bunEntry, ...process.argv.slice(2)], {
77
+ env: process.env,
78
+ stdio: "inherit",
79
+ });
80
+ if (result.error) {
81
+ console.error(`Scout could not run Bun at ${bunPath}: ${result.error.message}`);
82
+ process.exit(1);
83
+ }
84
+ process.exit(result.status ?? (result.signal ? 1 : 0));
85
+ }
86
+
87
+ if (preferredHost === "bun") {
4
88
  console.error(
5
- "Scout requires the Bun runtime (bun:sqlite is not available in Node.js).\n" +
89
+ "Scout was asked to use the Bun runtime, but Bun was not found.\n" +
6
90
  "Install Bun: curl -fsSL https://bun.sh/install | bash\n" +
7
- "Then run: bun x @openscout/scout"
91
+ "Or unset OPENSCOUT_RUNTIME_HOST to allow the Node headless runtime when it is packaged.",
8
92
  );
9
93
  process.exit(1);
10
94
  }
11
95
 
12
- import { existsSync, readFileSync } from "node:fs";
13
- import { dirname, resolve } from "node:path";
14
- import { fileURLToPath, pathToFileURL } from "node:url";
96
+ if (wantsHelp) {
97
+ process.stdout.write(renderNodeFallbackHelp(packageVersion));
98
+ process.exit(0);
99
+ }
15
100
 
16
- const binDir = dirname(fileURLToPath(import.meta.url));
17
- const distEntry = resolve(binDir, "../dist/main.mjs");
18
- const packageJsonPath = resolve(binDir, "../package.json");
101
+ console.error(
102
+ "Scout could not find a runnable headless runtime.\n" +
103
+ "This package version still needs Bun for the full CLI because the Node headless entrypoint is not packaged yet.\n" +
104
+ "Install Bun: curl -fsSL https://bun.sh/install | bash\n" +
105
+ "Future @openscout/scout packages should run the headless broker through Node when Bun is unavailable.",
106
+ );
107
+ process.exit(1);
19
108
 
20
- if (!process.env.SCOUT_APP_VERSION && existsSync(packageJsonPath)) {
109
+ function readPackageVersion() {
110
+ if (!existsSync(packageJsonPath)) return null;
21
111
  try {
22
112
  const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf8"));
23
- if (typeof packageJson.version === "string" && packageJson.version.trim()) {
24
- process.env.SCOUT_APP_VERSION = packageJson.version.trim();
113
+ return typeof packageJson.version === "string" && packageJson.version.trim()
114
+ ? packageJson.version.trim()
115
+ : null;
116
+ } catch {
117
+ return null;
118
+ }
119
+ }
120
+
121
+ function normalizeHost(value) {
122
+ const normalized = value?.trim().toLowerCase();
123
+ if (normalized === "bun") return "bun";
124
+ if (normalized === "node") return "node";
125
+ return null;
126
+ }
127
+
128
+ function isCurrentRuntimeBun() {
129
+ return typeof globalThis.Bun !== "undefined";
130
+ }
131
+
132
+ function resolveBunExecutable() {
133
+ for (const key of ["OPENSCOUT_BUN_BIN", "SCOUT_BUN_BIN", "BUN_BIN"]) {
134
+ const explicit = process.env[key]?.trim();
135
+ if (!explicit) continue;
136
+ const resolved = explicit.includes("/") || explicit.startsWith(".")
137
+ ? resolve(expandHomePath(explicit))
138
+ : findOnPath(explicit);
139
+ if (isExecutable(resolved)) return resolved;
140
+ }
141
+
142
+ return findOnPath("bun", [
143
+ ...(process.env.HOME ? [join(process.env.HOME, ".bun", "bin")] : []),
144
+ "/opt/homebrew/bin",
145
+ "/usr/local/bin",
146
+ ]);
147
+ }
148
+
149
+ function findOnPath(name, extraDirectories = []) {
150
+ const pathEntries = (process.env.PATH ?? "").split(delimiter).filter(Boolean);
151
+ const extensions = process.platform === "win32"
152
+ ? (process.env.PATHEXT ?? ".EXE;.CMD;.BAT;.COM").split(";")
153
+ : [""];
154
+ for (const directory of [...pathEntries, ...extraDirectories]) {
155
+ for (const ext of extensions) {
156
+ const candidate = join(expandHomePath(directory), `${name}${ext}`);
157
+ if (isExecutable(candidate)) return candidate;
25
158
  }
159
+ }
160
+ return null;
161
+ }
162
+
163
+ function expandHomePath(value) {
164
+ if (value === "~") return process.env.HOME ?? value;
165
+ if (value.startsWith("~/") && process.env.HOME) return join(process.env.HOME, value.slice(2));
166
+ return value;
167
+ }
168
+
169
+ function isExecutable(candidate) {
170
+ if (!candidate) return false;
171
+ try {
172
+ accessSync(candidate, constants.X_OK);
173
+ return true;
26
174
  } catch {
27
- // Ignore package metadata read failures and fall back to the shared constant.
175
+ return false;
28
176
  }
29
177
  }
30
178
 
31
- if (!existsSync(distEntry)) {
32
- console.error("Scout CLI dist entry is missing. Reinstall @openscout/scout or rebuild the package.");
33
- process.exit(1);
179
+ function describeNodeEntrypointFailure(error) {
180
+ const reason = error instanceof Error && error.message
181
+ ? `\nNode entrypoint failure: ${error.message}`
182
+ : "";
183
+ return "Scout could not start the Node headless entrypoint.\n" +
184
+ "The Node headless entrypoint is not packaged yet or is incomplete, so this package version still needs Bun for the full CLI.\n" +
185
+ "Install Bun: curl -fsSL https://bun.sh/install | bash\n" +
186
+ "Future @openscout/scout packages should run the headless broker through Node when Bun is unavailable." +
187
+ reason;
34
188
  }
35
189
 
36
- await import(pathToFileURL(distEntry).href);
190
+ function renderNodeFallbackHelp(version) {
191
+ const label = version ? `Scout ${version}` : "Scout";
192
+ return `${label}
193
+
194
+ Usage:
195
+ scout <command> [options]
196
+
197
+ Available without Bun:
198
+ scout --version Print the installed Scout package version.
199
+ scout --help Print this fallback help.
200
+ scout statusline claude Print the Claude statusline segment.
201
+
202
+ Full CLI commands:
203
+ scout setup
204
+ scout doctor
205
+ scout whoami
206
+ scout send
207
+ scout ask
208
+
209
+ This package can be installed with npm, but the full CLI currently needs Bun
210
+ until the Node headless entrypoint is packaged. Install Bun with:
211
+ curl -fsSL https://bun.sh/install | bash
212
+ `;
213
+ }
package/bin/scoutd ADDED
Binary file
Binary file