@sapiom/harness 0.0.0 → 0.1.1

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 (246) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +44 -43
  3. package/dist/cli/auth.d.ts +26 -0
  4. package/dist/cli/auth.d.ts.map +1 -0
  5. package/dist/cli/auth.js +39 -0
  6. package/dist/cli/auth.js.map +1 -0
  7. package/dist/cli/bin.d.ts +3 -0
  8. package/dist/cli/bin.d.ts.map +1 -0
  9. package/dist/cli/bin.js +163 -0
  10. package/dist/cli/bin.js.map +1 -0
  11. package/dist/cli/consent.d.ts +15 -0
  12. package/dist/cli/consent.d.ts.map +1 -0
  13. package/dist/cli/consent.js +81 -0
  14. package/dist/cli/consent.js.map +1 -0
  15. package/dist/cli/doctor.d.ts +27 -0
  16. package/dist/cli/doctor.d.ts.map +1 -0
  17. package/dist/cli/doctor.js +74 -0
  18. package/dist/cli/doctor.js.map +1 -0
  19. package/dist/cli/machine-id.d.ts +19 -0
  20. package/dist/cli/machine-id.d.ts.map +1 -0
  21. package/dist/cli/machine-id.js +75 -0
  22. package/dist/cli/machine-id.js.map +1 -0
  23. package/dist/cli/paths.d.ts +3 -0
  24. package/dist/cli/paths.d.ts.map +1 -0
  25. package/dist/cli/paths.js +11 -0
  26. package/dist/cli/paths.js.map +1 -0
  27. package/dist/cli/settings.d.ts +26 -0
  28. package/dist/cli/settings.d.ts.map +1 -0
  29. package/dist/cli/settings.js +123 -0
  30. package/dist/cli/settings.js.map +1 -0
  31. package/dist/core/adapters/claude-code.d.ts +41 -0
  32. package/dist/core/adapters/claude-code.d.ts.map +1 -0
  33. package/dist/core/adapters/claude-code.js +238 -0
  34. package/dist/core/adapters/claude-code.js.map +1 -0
  35. package/dist/core/adapters/codex.d.ts +59 -0
  36. package/dist/core/adapters/codex.d.ts.map +1 -0
  37. package/dist/core/adapters/codex.js +319 -0
  38. package/dist/core/adapters/codex.js.map +1 -0
  39. package/dist/core/canvas-body.d.ts +45 -0
  40. package/dist/core/canvas-body.d.ts.map +1 -0
  41. package/dist/core/canvas-body.js +94 -0
  42. package/dist/core/canvas-body.js.map +1 -0
  43. package/dist/core/canvas-cache.d.ts +23 -0
  44. package/dist/core/canvas-cache.d.ts.map +1 -0
  45. package/dist/core/canvas-cache.js +64 -0
  46. package/dist/core/canvas-cache.js.map +1 -0
  47. package/dist/core/canvas-enrich.d.ts +106 -0
  48. package/dist/core/canvas-enrich.d.ts.map +1 -0
  49. package/dist/core/canvas-enrich.js +178 -0
  50. package/dist/core/canvas-enrich.js.map +1 -0
  51. package/dist/core/canvas-enrichment.d.ts +144 -0
  52. package/dist/core/canvas-enrichment.d.ts.map +1 -0
  53. package/dist/core/canvas-enrichment.js +192 -0
  54. package/dist/core/canvas-enrichment.js.map +1 -0
  55. package/dist/core/canvas-graph.d.ts +71 -0
  56. package/dist/core/canvas-graph.d.ts.map +1 -0
  57. package/dist/core/canvas-graph.js +111 -0
  58. package/dist/core/canvas-graph.js.map +1 -0
  59. package/dist/core/canvas-index-classify.d.ts +41 -0
  60. package/dist/core/canvas-index-classify.d.ts.map +1 -0
  61. package/dist/core/canvas-index-classify.js +63 -0
  62. package/dist/core/canvas-index-classify.js.map +1 -0
  63. package/dist/core/canvas-interconnections.d.ts +24 -0
  64. package/dist/core/canvas-interconnections.d.ts.map +1 -0
  65. package/dist/core/canvas-interconnections.js +96 -0
  66. package/dist/core/canvas-interconnections.js.map +1 -0
  67. package/dist/core/canvas-manifest-check.d.ts +15 -0
  68. package/dist/core/canvas-manifest-check.d.ts.map +1 -0
  69. package/dist/core/canvas-manifest-check.js +90 -0
  70. package/dist/core/canvas-manifest-check.js.map +1 -0
  71. package/dist/core/canvas-render.d.ts +73 -0
  72. package/dist/core/canvas-render.d.ts.map +1 -0
  73. package/dist/core/canvas-render.js +145 -0
  74. package/dist/core/canvas-render.js.map +1 -0
  75. package/dist/core/canvas-svg.d.ts +50 -0
  76. package/dist/core/canvas-svg.d.ts.map +1 -0
  77. package/dist/core/canvas-svg.js +240 -0
  78. package/dist/core/canvas-svg.js.map +1 -0
  79. package/dist/core/canvas-template.d.ts +43 -0
  80. package/dist/core/canvas-template.d.ts.map +1 -0
  81. package/dist/core/canvas-template.js +365 -0
  82. package/dist/core/canvas-template.js.map +1 -0
  83. package/dist/core/canvas-watcher.d.ts +25 -0
  84. package/dist/core/canvas-watcher.d.ts.map +1 -0
  85. package/dist/core/canvas-watcher.js +144 -0
  86. package/dist/core/canvas-watcher.js.map +1 -0
  87. package/dist/core/collector/batcher.d.ts +57 -0
  88. package/dist/core/collector/batcher.d.ts.map +1 -0
  89. package/dist/core/collector/batcher.js +143 -0
  90. package/dist/core/collector/batcher.js.map +1 -0
  91. package/dist/core/collector/codex-tailer.d.ts +85 -0
  92. package/dist/core/collector/codex-tailer.d.ts.map +1 -0
  93. package/dist/core/collector/codex-tailer.js +270 -0
  94. package/dist/core/collector/codex-tailer.js.map +1 -0
  95. package/dist/core/collector/normalizer.d.ts +33 -0
  96. package/dist/core/collector/normalizer.d.ts.map +1 -0
  97. package/dist/core/collector/normalizer.js +94 -0
  98. package/dist/core/collector/normalizer.js.map +1 -0
  99. package/dist/core/collector/seq.d.ts +14 -0
  100. package/dist/core/collector/seq.d.ts.map +1 -0
  101. package/dist/core/collector/seq.js +20 -0
  102. package/dist/core/collector/seq.js.map +1 -0
  103. package/dist/core/collector/store.d.ts +16 -0
  104. package/dist/core/collector/store.d.ts.map +1 -0
  105. package/dist/core/collector/store.js +30 -0
  106. package/dist/core/collector/store.js.map +1 -0
  107. package/dist/core/collector/transcript.d.ts +38 -0
  108. package/dist/core/collector/transcript.d.ts.map +1 -0
  109. package/dist/core/collector/transcript.js +110 -0
  110. package/dist/core/collector/transcript.js.map +1 -0
  111. package/dist/core/event-bus.d.ts +9 -0
  112. package/dist/core/event-bus.d.ts.map +1 -0
  113. package/dist/core/event-bus.js +25 -0
  114. package/dist/core/event-bus.js.map +1 -0
  115. package/dist/core/example-seed.d.ts +34 -0
  116. package/dist/core/example-seed.d.ts.map +1 -0
  117. package/dist/core/example-seed.js +277 -0
  118. package/dist/core/example-seed.js.map +1 -0
  119. package/dist/core/inject/claude-settings.d.ts +34 -0
  120. package/dist/core/inject/claude-settings.d.ts.map +1 -0
  121. package/dist/core/inject/claude-settings.js +152 -0
  122. package/dist/core/inject/claude-settings.js.map +1 -0
  123. package/dist/core/inject/mcp-config.d.ts +26 -0
  124. package/dist/core/inject/mcp-config.d.ts.map +1 -0
  125. package/dist/core/inject/mcp-config.js +39 -0
  126. package/dist/core/inject/mcp-config.js.map +1 -0
  127. package/dist/core/inject/retention.d.ts +66 -0
  128. package/dist/core/inject/retention.d.ts.map +1 -0
  129. package/dist/core/inject/retention.js +119 -0
  130. package/dist/core/inject/retention.js.map +1 -0
  131. package/dist/core/inject/system-prompt.d.ts +11 -0
  132. package/dist/core/inject/system-prompt.d.ts.map +1 -0
  133. package/dist/core/inject/system-prompt.js +22 -0
  134. package/dist/core/inject/system-prompt.js.map +1 -0
  135. package/dist/core/macro-runner.d.ts +36 -0
  136. package/dist/core/macro-runner.d.ts.map +1 -0
  137. package/dist/core/macro-runner.js +66 -0
  138. package/dist/core/macro-runner.js.map +1 -0
  139. package/dist/core/macros.d.ts +10 -0
  140. package/dist/core/macros.d.ts.map +1 -0
  141. package/dist/core/macros.js +59 -0
  142. package/dist/core/macros.js.map +1 -0
  143. package/dist/core/path-safety.d.ts +33 -0
  144. package/dist/core/path-safety.d.ts.map +1 -0
  145. package/dist/core/path-safety.js +63 -0
  146. package/dist/core/path-safety.js.map +1 -0
  147. package/dist/core/paths.d.ts +27 -0
  148. package/dist/core/paths.d.ts.map +1 -0
  149. package/dist/core/paths.js +43 -0
  150. package/dist/core/paths.js.map +1 -0
  151. package/dist/core/port-detector.d.ts +69 -0
  152. package/dist/core/port-detector.d.ts.map +1 -0
  153. package/dist/core/port-detector.js +121 -0
  154. package/dist/core/port-detector.js.map +1 -0
  155. package/dist/core/session-manager.d.ts +275 -0
  156. package/dist/core/session-manager.d.ts.map +1 -0
  157. package/dist/core/session-manager.js +729 -0
  158. package/dist/core/session-manager.js.map +1 -0
  159. package/dist/core/task-manager.d.ts +138 -0
  160. package/dist/core/task-manager.d.ts.map +1 -0
  161. package/dist/core/task-manager.js +286 -0
  162. package/dist/core/task-manager.js.map +1 -0
  163. package/dist/core/task-stream.d.ts +32 -0
  164. package/dist/core/task-stream.d.ts.map +1 -0
  165. package/dist/core/task-stream.js +87 -0
  166. package/dist/core/task-stream.js.map +1 -0
  167. package/dist/core/workflow-registry.d.ts +36 -0
  168. package/dist/core/workflow-registry.d.ts.map +1 -0
  169. package/dist/core/workflow-registry.js +220 -0
  170. package/dist/core/workflow-registry.js.map +1 -0
  171. package/dist/core/workspace-context.d.ts +43 -0
  172. package/dist/core/workspace-context.d.ts.map +1 -0
  173. package/dist/core/workspace-context.js +108 -0
  174. package/dist/core/workspace-context.js.map +1 -0
  175. package/dist/core/workspace-watcher.d.ts +26 -0
  176. package/dist/core/workspace-watcher.d.ts.map +1 -0
  177. package/dist/core/workspace-watcher.js +174 -0
  178. package/dist/core/workspace-watcher.js.map +1 -0
  179. package/dist/index.d.ts +6 -0
  180. package/dist/index.d.ts.map +1 -0
  181. package/dist/index.js +6 -0
  182. package/dist/index.js.map +1 -0
  183. package/dist/profiles/canvas-guidelines.d.ts +20 -0
  184. package/dist/profiles/canvas-guidelines.d.ts.map +1 -0
  185. package/dist/profiles/canvas-guidelines.js +48 -0
  186. package/dist/profiles/canvas-guidelines.js.map +1 -0
  187. package/dist/profiles/default.d.ts +10 -0
  188. package/dist/profiles/default.d.ts.map +1 -0
  189. package/dist/profiles/default.js +70 -0
  190. package/dist/profiles/default.js.map +1 -0
  191. package/dist/server/auth.d.ts +9 -0
  192. package/dist/server/auth.d.ts.map +1 -0
  193. package/dist/server/auth.js +28 -0
  194. package/dist/server/auth.js.map +1 -0
  195. package/dist/server/canvas-render.d.ts +18 -0
  196. package/dist/server/canvas-render.d.ts.map +1 -0
  197. package/dist/server/canvas-render.js +24 -0
  198. package/dist/server/canvas-render.js.map +1 -0
  199. package/dist/server/canvas.d.ts +12 -0
  200. package/dist/server/canvas.d.ts.map +1 -0
  201. package/dist/server/canvas.js +174 -0
  202. package/dist/server/canvas.js.map +1 -0
  203. package/dist/server/events-ws.d.ts +11 -0
  204. package/dist/server/events-ws.d.ts.map +1 -0
  205. package/dist/server/events-ws.js +24 -0
  206. package/dist/server/events-ws.js.map +1 -0
  207. package/dist/server/fs.d.ts +4 -0
  208. package/dist/server/fs.d.ts.map +1 -0
  209. package/dist/server/fs.js +88 -0
  210. package/dist/server/fs.js.map +1 -0
  211. package/dist/server/index.d.ts +83 -0
  212. package/dist/server/index.d.ts.map +1 -0
  213. package/dist/server/index.js +0 -0
  214. package/dist/server/index.js.map +1 -0
  215. package/dist/server/ingest.d.ts +74 -0
  216. package/dist/server/ingest.d.ts.map +1 -0
  217. package/dist/server/ingest.js +74 -0
  218. package/dist/server/ingest.js.map +1 -0
  219. package/dist/server/macros.d.ts +32 -0
  220. package/dist/server/macros.d.ts.map +1 -0
  221. package/dist/server/macros.js +76 -0
  222. package/dist/server/macros.js.map +1 -0
  223. package/dist/server/rest.d.ts +75 -0
  224. package/dist/server/rest.d.ts.map +1 -0
  225. package/dist/server/rest.js +235 -0
  226. package/dist/server/rest.js.map +1 -0
  227. package/dist/server/static.d.ts +8 -0
  228. package/dist/server/static.d.ts.map +1 -0
  229. package/dist/server/static.js +36 -0
  230. package/dist/server/static.js.map +1 -0
  231. package/dist/server/terminal-ws.d.ts +9 -0
  232. package/dist/server/terminal-ws.d.ts.map +1 -0
  233. package/dist/server/terminal-ws.js +59 -0
  234. package/dist/server/terminal-ws.js.map +1 -0
  235. package/dist/server/ws-router.d.ts +16 -0
  236. package/dist/server/ws-router.d.ts.map +1 -0
  237. package/dist/server/ws-router.js +26 -0
  238. package/dist/server/ws-router.js.map +1 -0
  239. package/dist/shared/types.d.ts +485 -0
  240. package/dist/shared/types.d.ts.map +1 -0
  241. package/dist/shared/types.js +80 -0
  242. package/dist/shared/types.js.map +1 -0
  243. package/dist/web/assets/index-B9R4TzH7.css +32 -0
  244. package/dist/web/assets/index-Dh97QAgq.js +192 -0
  245. package/dist/web/index.html +27 -0
  246. package/package.json +84 -7
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Sapiom
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,45 +1,46 @@
1
1
  # @sapiom/harness
2
2
 
3
- ## ⚠️ IMPORTANT NOTICE ⚠️
4
-
5
- **This package is created solely for the purpose of setting up OIDC (OpenID Connect) trusted publishing with npm.**
6
-
7
- This is **NOT** a functional package and contains **NO** code or functionality beyond the OIDC setup configuration.
8
-
9
- ## Purpose
10
-
11
- This package exists to:
12
- 1. Configure OIDC trusted publishing for the package name `@sapiom/harness`
13
- 2. Enable secure, token-less publishing from CI/CD workflows
14
- 3. Establish provenance for packages published under this name
15
-
16
- ## What is OIDC Trusted Publishing?
17
-
18
- OIDC trusted publishing allows package maintainers to publish packages directly from their CI/CD workflows without needing to manage npm access tokens. Instead, it uses OpenID Connect to establish trust between the CI/CD provider (like GitHub Actions) and npm.
19
-
20
- ## Setup Instructions
21
-
22
- To properly configure OIDC trusted publishing for this package:
23
-
24
- 1. Go to [npmjs.com](https://www.npmjs.com/) and navigate to your package settings
25
- 2. Configure the trusted publisher (e.g., GitHub Actions)
26
- 3. Specify the repository and workflow that should be allowed to publish
27
- 4. Use the configured workflow to publish your actual package
28
-
29
- ## DO NOT USE THIS PACKAGE
30
-
31
- This package is a placeholder for OIDC configuration only. It:
32
- - Contains no executable code
33
- - Provides no functionality
34
- - Should not be installed as a dependency
35
- - Exists only for administrative purposes
36
-
37
- ## More Information
38
-
39
- For more details about npm's trusted publishing feature, see:
40
- - [npm Trusted Publishing Documentation](https://docs.npmjs.com/generating-provenance-statements)
41
- - [GitHub Actions OIDC Documentation](https://docs.github.com/en/actions/deployment/security-hardening-your-deployments/about-security-hardening-with-openid-connect)
42
-
43
- ---
44
-
45
- **Maintained for OIDC setup purposes only**
3
+ A local web app for building on Sapiom with your own coding agent.
4
+
5
+ ```bash
6
+ npx @sapiom/harness [dir]
7
+ ```
8
+
9
+ One command: checks your environment, signs you in, and opens a browser app
10
+ with your coding agent (Claude Code today, Codex next) running in an embedded
11
+ terminal pre-wired with the Sapiom MCP servers and a workflow-authoring
12
+ system prompt, in whatever project directory you choose.
13
+
14
+ ## What you get
15
+
16
+ - **Terminal sessions** your agent, your subscription, your machine; the
17
+ harness only configures it. Multiple sessions, resumable chat history.
18
+ - **Workflows rail** orchestration projects (`sapiom.json`) discovered and
19
+ tracked, with one-click local test run, deploy, production run, and
20
+ open-in-Sapiom actions.
21
+ - **Canvas** — a live pane that renders static HTML your agent writes to
22
+ `.sapiom/canvas/` (visualize your workflow, your docs, anything), plus a
23
+ preview mode for dev servers the agent starts.
24
+ - **Zero config mutation** everything is injected per-session via flags;
25
+ your global agent settings are never touched.
26
+
27
+ Uninstall: `rm -rf ~/.sapiom/harness` (all harness-owned state lives there).
28
+
29
+ ## Telemetry
30
+
31
+ With explicit opt-in, the harness collects usage events (prompts, tool calls,
32
+ session lifecycle) to improve Sapiom. Opt out any time; `--no-telemetry`
33
+ disables collection entirely. Events are also written locally to
34
+ `~/.sapiom/harness/events.ndjson` for your own inspection.
35
+
36
+ ## Development
37
+
38
+ ```bash
39
+ pnpm --filter @sapiom/harness dev # server (tsx) on :4100
40
+ pnpm --filter @sapiom/harness dev:web # Vite dev server, proxies to :4100
41
+ pnpm --filter @sapiom/harness build # server (tsc) + SPA (vite) → dist/
42
+ ```
43
+
44
+ Architecture: a single Node process (Express + ws + node-pty) serves the built
45
+ SPA, a small REST API, terminal WebSocket streams, and the local telemetry
46
+ ingest endpoint. The interface contract lives in `src/shared/types.ts`.
@@ -0,0 +1,26 @@
1
+ export interface HarnessIdentity {
2
+ userId: string;
3
+ tenantId: string;
4
+ organizationName: string;
5
+ apiKey: string;
6
+ /** "cached" when resolved from ~/.sapiom/credentials.json with no login
7
+ * prompt this run; "fresh" right after a browser login just completed.
8
+ * Lets the CLI make a silent cached-credential sign-in visible. */
9
+ source: "cached" | "fresh";
10
+ }
11
+ export interface EnsureAuthenticatedOptions {
12
+ /** Prompt a browser login when no cached credential exists. */
13
+ interactive: boolean;
14
+ /** Skip auth entirely (the `--no-auth` flag) — no fs/network access. */
15
+ noAuth?: boolean;
16
+ /** Overrides SAPIOM_ENVIRONMENT for this call. */
17
+ environment?: string;
18
+ }
19
+ /**
20
+ * Reuses `@sapiom/mcp`'s browser OAuth flow and `~/.sapiom/credentials.json`
21
+ * cache so the harness and the sapiom-dev MCP share one identity. Returns
22
+ * `null` when `noAuth` is set, or when not interactive and no credential is
23
+ * cached yet.
24
+ */
25
+ export declare function ensureAuthenticated(options: EnsureAuthenticatedOptions): Promise<HarnessIdentity | null>;
26
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/cli/auth.ts"],"names":[],"mappings":"AAOA,MAAM,WAAW,eAAe;IAC9B,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,gBAAgB,EAAE,MAAM,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;IACf;;wEAEoE;IACpE,MAAM,EAAE,QAAQ,GAAG,OAAO,CAAC;CAC5B;AAED,MAAM,WAAW,0BAA0B;IACzC,+DAA+D;IAC/D,WAAW,EAAE,OAAO,CAAC;IACrB,wEAAwE;IACxE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,kDAAkD;IAClD,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;GAKG;AACH,wBAAsB,mBAAmB,CACvC,OAAO,EAAE,0BAA0B,GAClC,OAAO,CAAC,eAAe,GAAG,IAAI,CAAC,CAiCjC"}
@@ -0,0 +1,39 @@
1
+ import { resolveEnvironment, readCredentials, writeCredentials, performBrowserAuth, } from "@sapiom/mcp/auth";
2
+ /**
3
+ * Reuses `@sapiom/mcp`'s browser OAuth flow and `~/.sapiom/credentials.json`
4
+ * cache so the harness and the sapiom-dev MCP share one identity. Returns
5
+ * `null` when `noAuth` is set, or when not interactive and no credential is
6
+ * cached yet.
7
+ */
8
+ export async function ensureAuthenticated(options) {
9
+ if (options.noAuth)
10
+ return null;
11
+ const env = await resolveEnvironment(options.environment ?? process.env.SAPIOM_ENVIRONMENT);
12
+ const existing = await readCredentials(env.name);
13
+ if (existing) {
14
+ return {
15
+ userId: existing.tenantId,
16
+ tenantId: existing.tenantId,
17
+ organizationName: existing.organizationName,
18
+ apiKey: existing.apiKey,
19
+ source: "cached",
20
+ };
21
+ }
22
+ if (!options.interactive)
23
+ return null;
24
+ const result = await performBrowserAuth(env.appURL, env.apiURL);
25
+ await writeCredentials(env.name, env.appURL, env.apiURL, {
26
+ apiKey: result.apiKey,
27
+ tenantId: result.tenantId,
28
+ organizationName: result.organizationName,
29
+ apiKeyId: result.apiKeyId,
30
+ });
31
+ return {
32
+ userId: result.tenantId,
33
+ tenantId: result.tenantId,
34
+ organizationName: result.organizationName,
35
+ apiKey: result.apiKey,
36
+ source: "fresh",
37
+ };
38
+ }
39
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/cli/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,kBAAkB,EAClB,eAAe,EACf,gBAAgB,EAChB,kBAAkB,GACnB,MAAM,kBAAkB,CAAC;AAsB1B;;;;;GAKG;AACH,MAAM,CAAC,KAAK,UAAU,mBAAmB,CACvC,OAAmC;IAEnC,IAAI,OAAO,CAAC,MAAM;QAAE,OAAO,IAAI,CAAC;IAEhC,MAAM,GAAG,GAAG,MAAM,kBAAkB,CAAC,OAAO,CAAC,WAAW,IAAI,OAAO,CAAC,GAAG,CAAC,kBAAkB,CAAC,CAAC;IAE5F,MAAM,QAAQ,GAAG,MAAM,eAAe,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC;IACjD,IAAI,QAAQ,EAAE,CAAC;QACb,OAAO;YACL,MAAM,EAAE,QAAQ,CAAC,QAAQ;YACzB,QAAQ,EAAE,QAAQ,CAAC,QAAQ;YAC3B,gBAAgB,EAAE,QAAQ,CAAC,gBAAgB;YAC3C,MAAM,EAAE,QAAQ,CAAC,MAAM;YACvB,MAAM,EAAE,QAAQ;SACjB,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,OAAO,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IAEtC,MAAM,MAAM,GAAG,MAAM,kBAAkB,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAChE,MAAM,gBAAgB,CAAC,GAAG,CAAC,IAAI,EAAE,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE;QACvD,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;QACzC,QAAQ,EAAE,MAAM,CAAC,QAAQ;KAC1B,CAAC,CAAC;IAEH,OAAO;QACL,MAAM,EAAE,MAAM,CAAC,QAAQ;QACvB,QAAQ,EAAE,MAAM,CAAC,QAAQ;QACzB,gBAAgB,EAAE,MAAM,CAAC,gBAAgB;QACzC,MAAM,EAAE,MAAM,CAAC,MAAM;QACrB,MAAM,EAAE,OAAO;KAChB,CAAC;AACJ,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=bin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.d.ts","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":""}
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * sapiom-harness CLI entry (workstream W4).
4
+ *
5
+ * Flow: doctor → auth (reuse @sapiom/mcp browser OAuth) → consent (first run)
6
+ * → generate boot token → startServer → open browser → print a startup banner.
7
+ *
8
+ * Flags: [dir] (default cwd), --port, --no-auth, --no-telemetry, --no-open,
9
+ * --no-session, --dev.
10
+ */
11
+ import * as path from "node:path";
12
+ import * as crypto from "node:crypto";
13
+ import open from "open";
14
+ import { DEFAULT_PORT } from "../shared/types.js";
15
+ import { runDoctor, printDoctorReport, pickDefaultHarness, CLAUDE_INSTALL_COMMAND, CODEX_INSTALL_COMMAND, } from "./doctor.js";
16
+ import { ensureAuthenticated } from "./auth.js";
17
+ import { ensureConsent } from "./consent.js";
18
+ import { loadSettings, recordRecentDir } from "./settings.js";
19
+ import { getOrCreateMachineId } from "./machine-id.js";
20
+ import { startServer } from "../server/index.js";
21
+ function parseArgs(argv) {
22
+ let dir;
23
+ let port = DEFAULT_PORT;
24
+ let noAuth = false;
25
+ let noTelemetry = false;
26
+ let noOpen = false;
27
+ let noSession = false;
28
+ let dev = false;
29
+ for (let i = 0; i < argv.length; i++) {
30
+ const arg = argv[i];
31
+ switch (arg) {
32
+ case "--port": {
33
+ const value = argv[++i];
34
+ if (!value || Number.isNaN(Number(value))) {
35
+ throw new Error("--port requires a numeric value");
36
+ }
37
+ port = Number(value);
38
+ break;
39
+ }
40
+ case "--no-auth":
41
+ noAuth = true;
42
+ break;
43
+ case "--no-telemetry":
44
+ noTelemetry = true;
45
+ break;
46
+ case "--no-open":
47
+ noOpen = true;
48
+ break;
49
+ case "--no-session":
50
+ noSession = true;
51
+ break;
52
+ case "--dev":
53
+ dev = true;
54
+ break;
55
+ default:
56
+ if (arg.startsWith("--")) {
57
+ throw new Error(`Unknown flag: ${arg}`);
58
+ }
59
+ if (dir !== undefined) {
60
+ throw new Error(`Unexpected extra argument: ${arg}`);
61
+ }
62
+ dir = arg;
63
+ }
64
+ }
65
+ return {
66
+ dir: path.resolve(dir ?? process.cwd()),
67
+ port,
68
+ noAuth,
69
+ noTelemetry,
70
+ noOpen,
71
+ noSession,
72
+ dev,
73
+ };
74
+ }
75
+ function printBanner(opts) {
76
+ const authLine = opts.identity
77
+ ? `${opts.identity.organizationName} (${opts.identity.userId})${opts.identity.source === "cached" ? " — cached" : ""}`
78
+ : "not authenticated";
79
+ console.log("");
80
+ console.log(" Sapiom Harness");
81
+ console.log(" --------------");
82
+ console.log(` directory ${opts.dir}`);
83
+ console.log(` auth ${authLine}`);
84
+ console.log(` telemetry ${opts.telemetryOptIn ? "on" : "off"}`);
85
+ // Always the full tokened URL — with --no-open (or a browser that failed
86
+ // to launch) this is the only way to reach the app; a bare host:port
87
+ // 401s on every /api call and can't open the WS connections.
88
+ console.log(` url ${opts.serverStarted ? `http://localhost:${opts.port}/?token=${opts.bootToken}` : "(server not started)"}`);
89
+ console.log("");
90
+ }
91
+ const main = async () => {
92
+ const options = parseArgs(process.argv.slice(2));
93
+ const doctorReport = await runDoctor();
94
+ printDoctorReport(doctorReport);
95
+ if (!doctorReport.ok) {
96
+ console.error("\nsapiom-harness requires Node >= 20 and at least one coding agent on PATH:\n" +
97
+ ` Claude Code: ${CLAUDE_INSTALL_COMMAND}\n` +
98
+ ` Codex: ${CODEX_INSTALL_COMMAND}\n` +
99
+ "Fix the checks above and try again.");
100
+ process.exit(1);
101
+ }
102
+ const defaultHarnessKind = pickDefaultHarness(doctorReport);
103
+ if (!doctorReport.availableHarnesses.includes("claude-code")) {
104
+ console.log(`\n⚠ Claude Code not found — install with: ${CLAUDE_INSTALL_COMMAND}\n` +
105
+ " Continuing with the Codex harness.");
106
+ }
107
+ const machineId = await getOrCreateMachineId();
108
+ const identity = await ensureAuthenticated({ interactive: true, noAuth: options.noAuth });
109
+ // A cached credential signs you in with no visible prompt at all — call it
110
+ // out explicitly so "auth silently worked" doesn't read as "nothing
111
+ // happened" (a fresh login is its own visible browser flow already).
112
+ if (identity?.source === "cached") {
113
+ console.log(`\nSigned in as ${identity.organizationName} (cached credential).`);
114
+ }
115
+ const telemetryOptIn = await ensureConsent({ noTelemetry: options.noTelemetry });
116
+ // First run = no recent directories recorded before this boot. Must be read
117
+ // BEFORE recordRecentDir below stamps the launch dir in — after that the
118
+ // signal is gone for good. Drives the SPA's welcome panel (AppState.firstRun)
119
+ // and suppresses the auto-created boot session, so a brand-new user lands on
120
+ // the welcome panel rather than a bare terminal in whatever directory they
121
+ // happened to launch from.
122
+ const firstRun = (await loadSettings()).recentDirs.length === 0;
123
+ await recordRecentDir(options.dir);
124
+ const bootToken = crypto.randomBytes(32).toString("hex");
125
+ let server = null;
126
+ try {
127
+ server = await startServer({
128
+ port: options.port,
129
+ bootToken,
130
+ telemetryOptIn,
131
+ identity,
132
+ machineId,
133
+ launchDir: options.dir,
134
+ autoCreateSession: !options.noSession && !firstRun,
135
+ defaultHarnessKind,
136
+ availableHarnesses: doctorReport.availableHarnesses,
137
+ firstRun,
138
+ });
139
+ }
140
+ catch (err) {
141
+ if (!options.dev)
142
+ throw err;
143
+ const message = err instanceof Error ? err.message : String(err);
144
+ console.error(`\n⚠ Harness server is not wired up yet: ${message}`);
145
+ console.error("--dev flow verified (doctor → auth → consent) without a live server.\n");
146
+ }
147
+ printBanner({
148
+ dir: options.dir,
149
+ port: server?.port ?? options.port,
150
+ bootToken,
151
+ identity,
152
+ telemetryOptIn,
153
+ serverStarted: server !== null,
154
+ });
155
+ if (server && !options.noOpen) {
156
+ await open(`http://localhost:${server.port}/?token=${bootToken}`);
157
+ }
158
+ };
159
+ main().catch((err) => {
160
+ console.error(err);
161
+ process.exit(1);
162
+ });
163
+ //# sourceMappingURL=bin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bin.js","sourceRoot":"","sources":["../../src/cli/bin.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ export interface EnsureConsentOptions {
2
+ /** The `--no-telemetry` flag — forces telemetry off without prompting. */
3
+ noTelemetry: boolean;
4
+ }
5
+ /**
6
+ * Resolves the telemetry opt-in state. Prompts once on first run and persists
7
+ * the answer; subsequent runs reuse the stored value silently. `--no-telemetry`
8
+ * always wins and never prompts. `SAPIOM_TELEMETRY_DISABLED`/`DO_NOT_TRACK`
9
+ * force telemetry off for this run — overriding a persisted opt-in and
10
+ * skipping the prompt entirely — without touching what's actually stored, so
11
+ * a later run without the env var set sees whatever the user answered (or
12
+ * will still be asked, if this env-vetoed run was their first).
13
+ */
14
+ export declare function ensureConsent(options: EnsureConsentOptions): Promise<boolean>;
15
+ //# sourceMappingURL=consent.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consent.d.ts","sourceRoot":"","sources":["../../src/cli/consent.ts"],"names":[],"mappings":"AAqCA,MAAM,WAAW,oBAAoB;IACnC,0EAA0E;IAC1E,WAAW,EAAE,OAAO,CAAC;CACtB;AAuBD;;;;;;;;GAQG;AACH,wBAAsB,aAAa,CAAC,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,OAAO,CAAC,CAiBnF"}
@@ -0,0 +1,81 @@
1
+ import * as readline from "node:readline/promises";
2
+ import { hasStoredSettings, loadSettings, saveSettings } from "./settings.js";
3
+ // Internal/friendlies phase: default to opted in (matches the on-by-default
4
+ // prompt below and the non-TTY fallback) — flip this the other way for a
5
+ // wider release.
6
+ const DEFAULT_TELEMETRY_OPT_IN = true;
7
+ const CONSENT_COPY = `
8
+ Sapiom Harness collects local usage analytics to improve the product:
9
+ - the prompts you send and the tool calls your agent makes
10
+ - session start/stop lifecycle events
11
+ This is always written locally to ~/.sapiom/harness/events.ndjson for your
12
+ own inspection.
13
+
14
+ Telemetry is ON to help us improve (opt out anytime in the app's settings
15
+ gear, or run with --no-telemetry).
16
+ `.trim();
17
+ async function promptConsent() {
18
+ console.log(`\n${CONSENT_COPY}\n`);
19
+ if (!process.stdin.isTTY) {
20
+ console.log(`Non-interactive session — defaulting telemetry to ${DEFAULT_TELEMETRY_OPT_IN ? "on" : "off"}.\n`);
21
+ return DEFAULT_TELEMETRY_OPT_IN;
22
+ }
23
+ const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
24
+ try {
25
+ const answer = (await rl.question("Keep it on? [Y/n] ")).trim().toLowerCase();
26
+ if (answer === "")
27
+ return DEFAULT_TELEMETRY_OPT_IN;
28
+ return answer === "y" || answer === "yes";
29
+ }
30
+ finally {
31
+ rl.close();
32
+ }
33
+ }
34
+ /**
35
+ * Environment opt-out, same precedence and value-parsing as
36
+ * `@sapiom/analytics-core`'s `resolveConsent()`: `SAPIOM_TELEMETRY_DISABLED`
37
+ * (Sapiom-specific) before `DO_NOT_TRACK` (the ecosystem-wide convention),
38
+ * both accepting `1`/`true` case-insensitively. Checked ahead of any stored
39
+ * settings — an operator setting either at the OS/shell level always wins
40
+ * for that run, regardless of a previously persisted opt-in.
41
+ */
42
+ function isEnvFlagSet(value) {
43
+ if (typeof value !== "string")
44
+ return false;
45
+ const normalized = value.trim().toLowerCase();
46
+ return normalized === "1" || normalized === "true";
47
+ }
48
+ /** Which env var (if any) forces telemetry off this run, for the printed reason. */
49
+ function envDisableReason() {
50
+ if (isEnvFlagSet(process.env.SAPIOM_TELEMETRY_DISABLED))
51
+ return "SAPIOM_TELEMETRY_DISABLED";
52
+ if (isEnvFlagSet(process.env.DO_NOT_TRACK))
53
+ return "DO_NOT_TRACK";
54
+ return null;
55
+ }
56
+ /**
57
+ * Resolves the telemetry opt-in state. Prompts once on first run and persists
58
+ * the answer; subsequent runs reuse the stored value silently. `--no-telemetry`
59
+ * always wins and never prompts. `SAPIOM_TELEMETRY_DISABLED`/`DO_NOT_TRACK`
60
+ * force telemetry off for this run — overriding a persisted opt-in and
61
+ * skipping the prompt entirely — without touching what's actually stored, so
62
+ * a later run without the env var set sees whatever the user answered (or
63
+ * will still be asked, if this env-vetoed run was their first).
64
+ */
65
+ export async function ensureConsent(options) {
66
+ if (options.noTelemetry)
67
+ return false;
68
+ const envReason = envDisableReason();
69
+ if (envReason) {
70
+ console.log(`Telemetry disabled via ${envReason} — skipping the consent prompt.\n`);
71
+ return false;
72
+ }
73
+ const isFirstRun = !(await hasStoredSettings());
74
+ const settings = await loadSettings();
75
+ if (!isFirstRun)
76
+ return settings.telemetryOptIn;
77
+ const telemetryOptIn = await promptConsent();
78
+ await saveSettings({ ...settings, telemetryOptIn });
79
+ return telemetryOptIn;
80
+ }
81
+ //# sourceMappingURL=consent.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consent.js","sourceRoot":"","sources":["../../src/cli/consent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,wBAAwB,CAAC;AACnD,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE9E,4EAA4E;AAC5E,yEAAyE;AACzE,iBAAiB;AACjB,MAAM,wBAAwB,GAAG,IAAI,CAAC;AAEtC,MAAM,YAAY,GAAG;;;;;;;;;CASpB,CAAC,IAAI,EAAE,CAAC;AAET,KAAK,UAAU,aAAa;IAC1B,OAAO,CAAC,GAAG,CAAC,KAAK,YAAY,IAAI,CAAC,CAAC;IAEnC,IAAI,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;QACzB,OAAO,CAAC,GAAG,CAAC,qDAAqD,wBAAwB,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC;QAC/G,OAAO,wBAAwB,CAAC;IAClC,CAAC;IAED,MAAM,EAAE,GAAG,QAAQ,CAAC,eAAe,CAAC,EAAE,KAAK,EAAE,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC;IACtF,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,oBAAoB,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;QAC9E,IAAI,MAAM,KAAK,EAAE;YAAE,OAAO,wBAAwB,CAAC;QACnD,OAAO,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,KAAK,CAAC;IAC5C,CAAC;YAAS,CAAC;QACT,EAAE,CAAC,KAAK,EAAE,CAAC;IACb,CAAC;AACH,CAAC;AAOD;;;;;;;GAOG;AACH,SAAS,YAAY,CAAC,KAAyB;IAC7C,IAAI,OAAO,KAAK,KAAK,QAAQ;QAAE,OAAO,KAAK,CAAC;IAC5C,MAAM,UAAU,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC9C,OAAO,UAAU,KAAK,GAAG,IAAI,UAAU,KAAK,MAAM,CAAC;AACrD,CAAC;AAED,oFAAoF;AACpF,SAAS,gBAAgB;IACvB,IAAI,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,yBAAyB,CAAC;QAAE,OAAO,2BAA2B,CAAC;IAC5F,IAAI,YAAY,CAAC,OAAO,CAAC,GAAG,CAAC,YAAY,CAAC;QAAE,OAAO,cAAc,CAAC;IAClE,OAAO,IAAI,CAAC;AACd,CAAC;AAED;;;;;;;;GAQG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,OAA6B;IAC/D,IAAI,OAAO,CAAC,WAAW;QAAE,OAAO,KAAK,CAAC;IAEtC,MAAM,SAAS,GAAG,gBAAgB,EAAE,CAAC;IACrC,IAAI,SAAS,EAAE,CAAC;QACd,OAAO,CAAC,GAAG,CAAC,0BAA0B,SAAS,mCAAmC,CAAC,CAAC;QACpF,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,UAAU,GAAG,CAAC,CAAC,MAAM,iBAAiB,EAAE,CAAC,CAAC;IAChD,MAAM,QAAQ,GAAG,MAAM,YAAY,EAAE,CAAC;IAEtC,IAAI,CAAC,UAAU;QAAE,OAAO,QAAQ,CAAC,cAAc,CAAC;IAEhD,MAAM,cAAc,GAAG,MAAM,aAAa,EAAE,CAAC;IAC7C,MAAM,YAAY,CAAC,EAAE,GAAG,QAAQ,EAAE,cAAc,EAAE,CAAC,CAAC;IACpD,OAAO,cAAc,CAAC;AACxB,CAAC"}
@@ -0,0 +1,27 @@
1
+ import type { DoctorCheck, HarnessKind } from "../shared/types.js";
2
+ /** Exact remedies surfaced in doctor output and the fatal-exit message —
3
+ * kept as named constants so bin.ts's messaging can't drift from what
4
+ * doctor.ts itself tells the user to run. */
5
+ export declare const CLAUDE_INSTALL_COMMAND = "npm i -g @anthropic-ai/claude-code";
6
+ export declare const CODEX_INSTALL_COMMAND = "npm i -g @openai/codex";
7
+ /**
8
+ * Node is a hard requirement. Neither coding agent is individually required —
9
+ * the harness runs with whichever of claude/codex is on PATH — but at least
10
+ * one of them must be, or there's nothing to launch.
11
+ */
12
+ export interface DoctorReport {
13
+ checks: DoctorCheck[];
14
+ ok: boolean;
15
+ /** Harness kinds with a working binary on PATH, in default-preference order
16
+ * (claude-code first). Empty only when `ok` is false. */
17
+ availableHarnesses: HarnessKind[];
18
+ }
19
+ export declare function runDoctor(): Promise<DoctorReport>;
20
+ /** First available harness in preference order, for callers that need a
21
+ * single default (e.g. the auto-created boot session). Only falls back to
22
+ * "claude-code" when the report itself is empty — main() already refuses to
23
+ * proceed past a report with no available harnesses, so real callers never
24
+ * hit that fallback. */
25
+ export declare function pickDefaultHarness(report: DoctorReport): HarnessKind;
26
+ export declare function printDoctorReport(report: DoctorReport): void;
27
+ //# sourceMappingURL=doctor.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AA8CnE;;8CAE8C;AAC9C,eAAO,MAAM,sBAAsB,uCAAuC,CAAC;AAC3E,eAAO,MAAM,qBAAqB,2BAA2B,CAAC;AAE9D;;;;GAIG;AACH,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,WAAW,EAAE,CAAC;IACtB,EAAE,EAAE,OAAO,CAAC;IACZ;8DAC0D;IAC1D,kBAAkB,EAAE,WAAW,EAAE,CAAC;CACnC;AAED,wBAAsB,SAAS,IAAI,OAAO,CAAC,YAAY,CAAC,CAoBvD;AAED;;;;yBAIyB;AACzB,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,YAAY,GAAG,WAAW,CAEpE;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI,CAM5D"}
@@ -0,0 +1,74 @@
1
+ import { execFile } from "node:child_process";
2
+ import { promisify } from "node:util";
3
+ const execFileAsync = promisify(execFile);
4
+ async function which(bin) {
5
+ try {
6
+ const { stdout } = await execFileAsync(process.platform === "win32" ? "where" : "which", [
7
+ bin,
8
+ ]);
9
+ return stdout.trim().split("\n")[0] ?? null;
10
+ }
11
+ catch {
12
+ return null;
13
+ }
14
+ }
15
+ async function version(bin, args = ["--version"]) {
16
+ try {
17
+ const { stdout } = await execFileAsync(bin, args);
18
+ return stdout.trim().split("\n")[0] ?? null;
19
+ }
20
+ catch {
21
+ return null;
22
+ }
23
+ }
24
+ function nodeCheck() {
25
+ const major = Number(process.versions.node.split(".")[0]);
26
+ const ok = major >= 20;
27
+ return {
28
+ name: "node",
29
+ ok,
30
+ detail: ok ? `v${process.versions.node}` : `v${process.versions.node} (need >= 20)`,
31
+ };
32
+ }
33
+ async function binaryCheck(name, bin, versionArgs, notFoundDetail) {
34
+ const found = await which(bin);
35
+ if (!found)
36
+ return { name, ok: false, detail: notFoundDetail };
37
+ const v = await version(bin, versionArgs);
38
+ return { name, ok: true, detail: v ?? found };
39
+ }
40
+ /** Exact remedies surfaced in doctor output and the fatal-exit message —
41
+ * kept as named constants so bin.ts's messaging can't drift from what
42
+ * doctor.ts itself tells the user to run. */
43
+ export const CLAUDE_INSTALL_COMMAND = "npm i -g @anthropic-ai/claude-code";
44
+ export const CODEX_INSTALL_COMMAND = "npm i -g @openai/codex";
45
+ export async function runDoctor() {
46
+ const node = nodeCheck();
47
+ const [claude, codex, git] = await Promise.all([
48
+ binaryCheck("claude", "claude", ["--version"], `not found on PATH — install: ${CLAUDE_INSTALL_COMMAND}`),
49
+ binaryCheck("codex", "codex", ["--version"], `not found on PATH — install: ${CODEX_INSTALL_COMMAND}`),
50
+ binaryCheck("git", "git", ["--version"], "not found on PATH"),
51
+ ]);
52
+ const availableHarnesses = [
53
+ ...(claude.ok ? ["claude-code"] : []),
54
+ ...(codex.ok ? ["codex"] : []),
55
+ ];
56
+ const checks = [node, claude, codex, git];
57
+ return { checks, ok: node.ok && availableHarnesses.length > 0, availableHarnesses };
58
+ }
59
+ /** First available harness in preference order, for callers that need a
60
+ * single default (e.g. the auto-created boot session). Only falls back to
61
+ * "claude-code" when the report itself is empty — main() already refuses to
62
+ * proceed past a report with no available harnesses, so real callers never
63
+ * hit that fallback. */
64
+ export function pickDefaultHarness(report) {
65
+ return report.availableHarnesses[0] ?? "claude-code";
66
+ }
67
+ export function printDoctorReport(report) {
68
+ console.log("Doctor:");
69
+ for (const check of report.checks) {
70
+ const mark = check.ok ? "✓" : "✗";
71
+ console.log(` ${mark} ${check.name.padEnd(8)} ${check.detail}`);
72
+ }
73
+ }
74
+ //# sourceMappingURL=doctor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"doctor.js","sourceRoot":"","sources":["../../src/cli/doctor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Stable anonymous install id, used to tag every analytics event
3
+ * (`AnalyticsEvent.machineId`) regardless of which Sapiom user is signed in.
4
+ * Created once on first run and reused forever after.
5
+ *
6
+ * Hardened per `@sapiom/analytics-core`'s `identity.ts` pattern: private
7
+ * file permissions (mode 0600, on both create and a pre-existing file),
8
+ * silent regeneration when the stored content is missing/corrupt, and
9
+ * graceful degradation — an unreadable/unwritable HOME must never crash
10
+ * server boot over an analytics nicety. Kept as our own plain-text
11
+ * `~/.sapiom/harness/machine-id` file for now; converging onto her
12
+ * `~/.sapiom/analytics.json` identity is a separate, later conversation.
13
+ *
14
+ * @param filePath Where the id file lives. Defaults to the real
15
+ * `HARNESS_PATHS.machineId`; tests and scripted checks pass a path under
16
+ * their scratch state root so a boot never writes to the real home dir.
17
+ */
18
+ export declare function getOrCreateMachineId(filePath?: string): Promise<string>;
19
+ //# sourceMappingURL=machine-id.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"machine-id.d.ts","sourceRoot":"","sources":["../../src/cli/machine-id.ts"],"names":[],"mappings":"AAQA;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAsB,oBAAoB,CACxC,QAAQ,GAAE,MAA4C,GACrD,OAAO,CAAC,MAAM,CAAC,CAsBjB"}