@mtreeai/msapling-cli 2.3.6-beta.54 → 2.3.6-beta.56

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 (4) hide show
  1. package/LICENSE +44 -44
  2. package/README.md +174 -174
  3. package/dist/index.js +126 -59
  4. package/package.json +1 -1
package/LICENSE CHANGED
@@ -1,44 +1,44 @@
1
- MSapling CLI — Proprietary License
2
- Copyright (c) 2026 MSapling / MTreeAI. All rights reserved.
3
-
4
- This software, the source code in this repository, and the published
5
- `@mtreeai/msapling-cli` and `msapling` npm packages (collectively, "the Software") are the
6
- proprietary property of MSapling / MTreeAI.
7
-
8
- PERMITTED USE
9
- -------------
10
- You may:
11
- 1. Install and run the published binary distribution of the Software
12
- ("msapling" / "ms" CLI) for personal or internal-business use against
13
- the official MSapling backend at https://api.msapling.com.
14
- 2. Configure the Software with your own API keys (BYOK) and local model
15
- providers (Ollama, etc.) as supported by the Software's documented
16
- features.
17
-
18
- PROHIBITED USE
19
- --------------
20
- Without prior written permission from MSapling / MTreeAI you may not:
21
- 1. Copy, fork, mirror, redistribute, sublicense, or republish the Software
22
- or any substantial portion of its source code or compiled artifacts.
23
- 2. Reverse-engineer, decompile, or disassemble the Software except to the
24
- extent permitted by applicable law that cannot be contractually waived.
25
- 3. Use the Software to operate a competing service or to circumvent any
26
- authentication, billing, rate-limiting, or tier-gating mechanism of the
27
- MSapling backend.
28
- 4. Remove or alter any copyright notice, trademark, or proprietary legend
29
- embedded in the Software.
30
-
31
- NO WARRANTY
32
- -----------
33
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35
- FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. IN NO EVENT SHALL
36
- THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
37
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING
38
- FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
39
- DEALINGS IN THE SOFTWARE.
40
-
41
- CONTACT
42
- -------
43
- For licensing inquiries, partnership requests, or permission to redistribute:
44
- https://msapling.com/support
1
+ MSapling CLI — Proprietary License
2
+ Copyright (c) 2026 MSapling / MTreeAI. All rights reserved.
3
+
4
+ This software, the source code in this repository, and the published
5
+ `@mtreeai/msapling-cli` and `msapling` npm packages (collectively, "the Software") are the
6
+ proprietary property of MSapling / MTreeAI.
7
+
8
+ PERMITTED USE
9
+ -------------
10
+ You may:
11
+ 1. Install and run the published binary distribution of the Software
12
+ ("msapling" / "ms" CLI) for personal or internal-business use against
13
+ the official MSapling backend at https://api.msapling.com.
14
+ 2. Configure the Software with your own API keys (BYOK) and local model
15
+ providers (Ollama, etc.) as supported by the Software's documented
16
+ features.
17
+
18
+ PROHIBITED USE
19
+ --------------
20
+ Without prior written permission from MSapling / MTreeAI you may not:
21
+ 1. Copy, fork, mirror, redistribute, sublicense, or republish the Software
22
+ or any substantial portion of its source code or compiled artifacts.
23
+ 2. Reverse-engineer, decompile, or disassemble the Software except to the
24
+ extent permitted by applicable law that cannot be contractually waived.
25
+ 3. Use the Software to operate a competing service or to circumvent any
26
+ authentication, billing, rate-limiting, or tier-gating mechanism of the
27
+ MSapling backend.
28
+ 4. Remove or alter any copyright notice, trademark, or proprietary legend
29
+ embedded in the Software.
30
+
31
+ NO WARRANTY
32
+ -----------
33
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
34
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
35
+ FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. IN NO EVENT SHALL
36
+ THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER
37
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT, OR OTHERWISE, ARISING
38
+ FROM, OUT OF, OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
39
+ DEALINGS IN THE SOFTWARE.
40
+
41
+ CONTACT
42
+ -------
43
+ For licensing inquiries, partnership requests, or permission to redistribute:
44
+ https://msapling.com/support
package/README.md CHANGED
@@ -1,174 +1,174 @@
1
- # MSapling CLI
2
-
3
- MSapling CLI is the React/TypeScript terminal client for the MSapling backend.
4
-
5
- ## Install
6
-
7
- Install the MSapling CLI globally via npm:
8
-
9
- ```bash
10
- npm install -g msapling@beta
11
- ```
12
-
13
- **Requirements:** Node.js 20.10 or higher.
14
-
15
- The short `msapling` package and compatibility package `@mtreeai/msapling-cli` contain the same CLI bundle. Both use the `beta` dist-tag during prerelease testing.
16
-
17
- ## First Run
18
-
19
- Start the interactive REPL:
20
-
21
- ```bash
22
- msapling
23
- ```
24
-
25
- Inside the REPL, authenticate using one of these methods:
26
-
27
- ```
28
- /login your@email.com # Email + password login (with optional 2FA)
29
- /login github # GitHub device flow (opens browser)
30
- /login <token> # Paste an API token from msapling.com Settings
31
- ```
32
-
33
- All methods store the token via the OS keychain (Windows DPAPI, macOS Keychain, or Linux libsecret).
34
-
35
- Once logged in, start chatting:
36
-
37
- ```
38
- /chat hello
39
- ```
40
-
41
- ## CLI Subcommands (argv)
42
-
43
- These are passed directly on the command line before the REPL starts:
44
-
45
- | Command | Description |
46
- |---------|-------------|
47
- | *(none)* | Start interactive REPL |
48
- | `--compact` | Start REPL in compact mode (thin separators, no footer) |
49
- | `mcp serve` | Run as MCP stdio server (Claude Code / Cursor / Windsurf integration) |
50
- | `--version` | Print version and exit |
51
- | `--help` | Print usage and exit |
52
-
53
- ## Inside the REPL — Slash Commands
54
-
55
- Once the REPL is running, type `/help` to see all available slash commands. Common ones:
56
-
57
- | Slash Command | Description |
58
- |---------------|-------------|
59
- | `/login your@email.com` | Sign in with email + password (recommended for end users) |
60
- | `/login github` | GitHub device flow (for developers) |
61
- | `/login <token>` | Paste an API token from Settings → API Keys |
62
- | `/logout` | Clear stored token |
63
- | `/chat <message>` | Send a message and stream a response |
64
- | `/chat new <title>` | Create a chat inside the fixed CLI project |
65
- | `/chat import <chat_id>` | Copy another owned chat into a new CLI chat without mutating the source |
66
- | `/model` | Open the loopback visual catalog (complete LAB catalog remotely; direct provider catalog standalone) |
67
- | `/model --terminal` | Use the provider-first terminal selector instead |
68
- | `/keys add <provider>` | Enter a BYOK key through hidden terminal input or the one-time local browser prompt |
69
- | `/keys use <provider> [base_url]` | Select OpenRouter or an OpenAI-compatible provider in standalone mode |
70
- | `/footer [compact|expanded|field on|off]` | Configure telemetry visibility |
71
- | `/cost` | Show usage and billing data |
72
- | `/config` | View and edit configuration |
73
- | `/help` | Show all slash-command help |
74
-
75
- > **Note:** `chat`, `login`, `model`, `footer`, `cost`, and `config` are REPL slash-commands (prefixed with `/`), not top-level argv subcommands. The CLI is single-chat: fanout/swarm commands are hidden and disabled. Run `msapling --help` to see the argv interface.
76
-
77
- See `packages/cli/src/commands/index.ts` for the canonical slash-command list.
78
-
79
- ---
80
-
81
-
82
-
83
- The installed CLI has explicit remote and standalone profiles. In remote mode, MSapling's backend is the source of truth for auth, projects, chats, model routing, usage, billing, MDrive, locks, and audit data. In `--local` mode, model calls, provider keys, transcripts, tools, and project settings remain local and LAB is not contacted for turns.
84
-
85
- ## Product Direction
86
-
87
- MSapling started as a multi-chat browser workspace: multiple AI chats, many model choices, BYOK, OpenRouter, and Ollama support in one UI.
88
-
89
- The mobile app extends that shared workspace to one-chat-at-a-time usage and phone-first workflows such as sending a photo into a chat.
90
-
91
- The CLI uses the same backend state through a fixed project named `CLI` and one active chat at a time. It should be fast, low-resource, and useful without duplicating the backend.
92
-
93
- ## Architecture
94
-
95
- ```text
96
- Website \
97
- Mobile App -> MSapling Backend -> chats, projects, usage, billing, MDrive, locks
98
- CLI /
99
- ```
100
-
101
- The CLI should:
102
-
103
- - authenticate with the backend
104
- - read the fixed `CLI` project and active chat from the backend
105
- - stream chat responses from `/api/chat/message`
106
- - display backend usage and quota data
107
- - respect backend stream locks
108
- - use backend MDrive/edit proposal flows
109
- - keep local terminal work lightweight
110
-
111
- The CLI should not:
112
-
113
- - invent separate chat state or switch arbitrary web chats
114
- - expose multi-chat/swarm fanout commands
115
- - compute billing locally
116
- - simulate MDrive state
117
- - bypass backend locks
118
- - duplicate backend orchestration unless there is a clear reason
119
-
120
- ## Current State
121
-
122
- This directory is a Bun monorepo scaffold using React and Ink.
123
-
124
- Port useful behavior selectively. Do not port backend-like responsibilities.
125
-
126
- Known current gaps:
127
-
128
- - workspace package manifests are missing
129
- - the Ink input loop is incomplete
130
- - `Agent` is referenced but not implemented
131
- - MDrive logic is simulated locally
132
- - backend route contracts need to be aligned
133
- - tests and build are not yet passing
134
-
135
- ## Near-Term Goal
136
-
137
- Build a stable thin client:
138
-
139
- 1. Login and secure token storage.
140
- 2. Fetch backend project overview.
141
- 3. Open or create chats.
142
- 4. Stream backend chat responses.
143
- 5. Show shared chat history across website, app, and CLI.
144
- 6. Handle lock, quota, and auth errors clearly.
145
- 7. Add `/doctor`.
146
-
147
- ## Commands
148
-
149
- ```bash
150
- bun install
151
- bun run build
152
- bun test
153
- bun run start
154
- ```
155
-
156
- These commands are target commands. They may not pass until the workspace manifests and dependencies are completed.
157
-
158
- ## Environment
159
-
160
- ```bash
161
- MSAPLING_API_URL=https://api.msapling.com
162
- ```
163
-
164
- For local development, point `MSAPLING_API_URL` at the local backend.
165
-
166
- ## Design Principle
167
-
168
- Keep the CLI small.
169
-
170
- The backend should do the heavy work. The CLI should make that backend fast and comfortable from a terminal.
171
-
172
- ---
173
-
174
- **Full architecture overview:** see ARCHITECTURE.md (when published) or `packages/core/README.md`.
1
+ # MSapling CLI
2
+
3
+ MSapling CLI is the React/TypeScript terminal client for the MSapling backend.
4
+
5
+ ## Install
6
+
7
+ Install the MSapling CLI globally via npm:
8
+
9
+ ```bash
10
+ npm install -g msapling@beta
11
+ ```
12
+
13
+ **Requirements:** Node.js 20.10 or higher.
14
+
15
+ The short `msapling` package and compatibility package `@mtreeai/msapling-cli` contain the same CLI bundle. Both use the `beta` dist-tag during prerelease testing.
16
+
17
+ ## First Run
18
+
19
+ Start the interactive REPL:
20
+
21
+ ```bash
22
+ msapling
23
+ ```
24
+
25
+ Inside the REPL, authenticate using one of these methods:
26
+
27
+ ```
28
+ /login your@email.com # Email + password login (with optional 2FA)
29
+ /login github # GitHub device flow (opens browser)
30
+ /login <token> # Paste an API token from msapling.com Settings
31
+ ```
32
+
33
+ All methods store the token via the OS keychain (Windows DPAPI, macOS Keychain, or Linux libsecret).
34
+
35
+ Once logged in, start chatting:
36
+
37
+ ```
38
+ /chat hello
39
+ ```
40
+
41
+ ## CLI Subcommands (argv)
42
+
43
+ These are passed directly on the command line before the REPL starts:
44
+
45
+ | Command | Description |
46
+ |---------|-------------|
47
+ | *(none)* | Start interactive REPL |
48
+ | `--compact` | Start REPL in compact mode (thin separators, no footer) |
49
+ | `mcp serve` | Run as MCP stdio server (Claude Code / Cursor / Windsurf integration) |
50
+ | `--version` | Print version and exit |
51
+ | `--help` | Print usage and exit |
52
+
53
+ ## Inside the REPL — Slash Commands
54
+
55
+ Once the REPL is running, type `/help` to see all available slash commands. Common ones:
56
+
57
+ | Slash Command | Description |
58
+ |---------------|-------------|
59
+ | `/login your@email.com` | Sign in with email + password (recommended for end users) |
60
+ | `/login github` | GitHub device flow (for developers) |
61
+ | `/login <token>` | Paste an API token from Settings → API Keys |
62
+ | `/logout` | Clear stored token |
63
+ | `/chat <message>` | Send a message and stream a response |
64
+ | `/chat new <title>` | Create a chat inside the fixed CLI project |
65
+ | `/chat import <chat_id>` | Copy another owned chat into a new CLI chat without mutating the source |
66
+ | `/model` | Open the loopback visual catalog (complete LAB catalog remotely; direct provider catalog standalone) |
67
+ | `/model --terminal` | Use the provider-first terminal selector instead |
68
+ | `/keys add <provider>` | Enter a BYOK key through hidden terminal input or the one-time local browser prompt |
69
+ | `/keys use <provider> [base_url]` | Select OpenRouter or an OpenAI-compatible provider in standalone mode |
70
+ | `/footer [compact|expanded|field on|off]` | Configure telemetry visibility |
71
+ | `/cost` | Show usage and billing data |
72
+ | `/config` | View and edit configuration |
73
+ | `/help` | Show all slash-command help |
74
+
75
+ > **Note:** `chat`, `login`, `model`, `footer`, `cost`, and `config` are REPL slash-commands (prefixed with `/`), not top-level argv subcommands. The CLI is single-chat: fanout/swarm commands are hidden and disabled. Run `msapling --help` to see the argv interface.
76
+
77
+ See `packages/cli/src/commands/index.ts` for the canonical slash-command list.
78
+
79
+ ---
80
+
81
+
82
+
83
+ The installed CLI has explicit remote and standalone profiles. In remote mode, MSapling's backend is the source of truth for auth, projects, chats, model routing, usage, billing, MDrive, locks, and audit data. In `--local` mode, model calls, provider keys, transcripts, tools, and project settings remain local and LAB is not contacted for turns.
84
+
85
+ ## Product Direction
86
+
87
+ MSapling started as a multi-chat browser workspace: multiple AI chats, many model choices, BYOK, OpenRouter, and Ollama support in one UI.
88
+
89
+ The mobile app extends that shared workspace to one-chat-at-a-time usage and phone-first workflows such as sending a photo into a chat.
90
+
91
+ The CLI uses the same backend state through a fixed project named `CLI` and one active chat at a time. It should be fast, low-resource, and useful without duplicating the backend.
92
+
93
+ ## Architecture
94
+
95
+ ```text
96
+ Website \
97
+ Mobile App -> MSapling Backend -> chats, projects, usage, billing, MDrive, locks
98
+ CLI /
99
+ ```
100
+
101
+ The CLI should:
102
+
103
+ - authenticate with the backend
104
+ - read the fixed `CLI` project and active chat from the backend
105
+ - stream chat responses from `/api/chat/message`
106
+ - display backend usage and quota data
107
+ - respect backend stream locks
108
+ - use backend MDrive/edit proposal flows
109
+ - keep local terminal work lightweight
110
+
111
+ The CLI should not:
112
+
113
+ - invent separate chat state or switch arbitrary web chats
114
+ - expose multi-chat/swarm fanout commands
115
+ - compute billing locally
116
+ - simulate MDrive state
117
+ - bypass backend locks
118
+ - duplicate backend orchestration unless there is a clear reason
119
+
120
+ ## Current State
121
+
122
+ This directory is a Bun monorepo scaffold using React and Ink.
123
+
124
+ Port useful behavior selectively. Do not port backend-like responsibilities.
125
+
126
+ Known current gaps:
127
+
128
+ - workspace package manifests are missing
129
+ - the Ink input loop is incomplete
130
+ - `Agent` is referenced but not implemented
131
+ - MDrive logic is simulated locally
132
+ - backend route contracts need to be aligned
133
+ - tests and build are not yet passing
134
+
135
+ ## Near-Term Goal
136
+
137
+ Build a stable thin client:
138
+
139
+ 1. Login and secure token storage.
140
+ 2. Fetch backend project overview.
141
+ 3. Open or create chats.
142
+ 4. Stream backend chat responses.
143
+ 5. Show shared chat history across website, app, and CLI.
144
+ 6. Handle lock, quota, and auth errors clearly.
145
+ 7. Add `/doctor`.
146
+
147
+ ## Commands
148
+
149
+ ```bash
150
+ bun install
151
+ bun run build
152
+ bun test
153
+ bun run start
154
+ ```
155
+
156
+ These commands are target commands. They may not pass until the workspace manifests and dependencies are completed.
157
+
158
+ ## Environment
159
+
160
+ ```bash
161
+ MSAPLING_API_URL=https://api.msapling.com
162
+ ```
163
+
164
+ For local development, point `MSAPLING_API_URL` at the local backend.
165
+
166
+ ## Design Principle
167
+
168
+ Keep the CLI small.
169
+
170
+ The backend should do the heavy work. The CLI should make that backend fast and comfortable from a terminal.
171
+
172
+ ---
173
+
174
+ **Full architecture overview:** see ARCHITECTURE.md (when published) or `packages/core/README.md`.
package/dist/index.js CHANGED
@@ -15483,6 +15483,84 @@ var init_exit = __esm({
15483
15483
  }
15484
15484
  });
15485
15485
 
15486
+ // src/commands/billing/open-browser.ts
15487
+ import { spawn as spawn10 } from "child_process";
15488
+ function isSafeBrowserUrl(url) {
15489
+ if (typeof url !== "string" || url.length === 0 || url.length > 4096) return false;
15490
+ for (let index = 0; index < url.length; index++) {
15491
+ const code = url.charCodeAt(index);
15492
+ if (code <= 32 || code === 127 || code === 34 || code === 92 || code === 96) return false;
15493
+ }
15494
+ try {
15495
+ const parsed = new URL(url);
15496
+ return parsed.protocol === "https:" || parsed.protocol === "http:";
15497
+ } catch {
15498
+ return false;
15499
+ }
15500
+ }
15501
+ function hasGraphicalBrowserEnvironment(platform5 = process.platform, env = process.env) {
15502
+ if (platform5 === "win32" || platform5 === "darwin") return true;
15503
+ return Boolean(env.DISPLAY || env.WAYLAND_DISPLAY);
15504
+ }
15505
+ async function openBrowser(url, launch = spawn10) {
15506
+ if (!isSafeBrowserUrl(url)) return false;
15507
+ const platform5 = process.platform;
15508
+ const [command, args2] = platform5 === "win32" ? ["rundll32.exe", ["url.dll,FileProtocolHandler", url]] : platform5 === "darwin" ? ["open", [url]] : ["xdg-open", [url]];
15509
+ return new Promise((resolve23) => {
15510
+ let settled = false;
15511
+ const finish = (opened) => {
15512
+ if (settled) return;
15513
+ settled = true;
15514
+ resolve23(opened);
15515
+ };
15516
+ try {
15517
+ const child = launch(command, [...args2], { stdio: "ignore", detached: true });
15518
+ child.once("error", () => finish(false));
15519
+ child.once("spawn", () => {
15520
+ child.unref();
15521
+ finish(true);
15522
+ });
15523
+ } catch {
15524
+ finish(false);
15525
+ }
15526
+ });
15527
+ }
15528
+ var init_open_browser = __esm({
15529
+ "src/commands/billing/open-browser.ts"() {
15530
+ "use strict";
15531
+ init_esm_shims();
15532
+ }
15533
+ });
15534
+
15535
+ // src/runtime/webLinks.ts
15536
+ function msaplingWebUrl(path3, env = process.env) {
15537
+ const configured = (env.MSAPLING_WEB_URL ?? DEFAULT_WEB_ORIGIN).trim();
15538
+ const origin = new URL(configured);
15539
+ const local = origin.hostname === "localhost" || origin.hostname === "127.0.0.1";
15540
+ if (origin.protocol !== "https:" && !(local && origin.protocol === "http:")) {
15541
+ throw new Error("MSAPLING_WEB_URL must use HTTPS (HTTP is allowed only for localhost)");
15542
+ }
15543
+ if (origin.username || origin.password || origin.search || origin.hash) {
15544
+ throw new Error("MSAPLING_WEB_URL must be an origin without credentials, query, or fragment");
15545
+ }
15546
+ return new URL(path3.startsWith("/") ? path3 : `/${path3}`, `${origin.origin}/`).toString();
15547
+ }
15548
+ function cliHelpUrl(env) {
15549
+ return msaplingWebUrl("/cli/help", env);
15550
+ }
15551
+ function cliChatUrl(chatId, env) {
15552
+ if (!/^[A-Za-z0-9_-]{1,128}$/.test(chatId)) throw new Error("Invalid CLI chat id");
15553
+ return msaplingWebUrl(`/chat/${encodeURIComponent(chatId)}`, env);
15554
+ }
15555
+ var DEFAULT_WEB_ORIGIN;
15556
+ var init_webLinks = __esm({
15557
+ "src/runtime/webLinks.ts"() {
15558
+ "use strict";
15559
+ init_esm_shims();
15560
+ DEFAULT_WEB_ORIGIN = "https://msapling.com";
15561
+ }
15562
+ });
15563
+
15486
15564
  // src/commands/help.ts
15487
15565
  function getDisplayCategory(cmd) {
15488
15566
  return DISPLAY_CATEGORIES[cmd.name] ?? cmd.category ?? "Other";
@@ -15493,6 +15571,8 @@ var init_help = __esm({
15493
15571
  "use strict";
15494
15572
  init_esm_shims();
15495
15573
  init_commands();
15574
+ init_open_browser();
15575
+ init_webLinks();
15496
15576
  DISPLAY_CATEGORIES = {
15497
15577
  // Navigation
15498
15578
  chat: "Navigation",
@@ -15531,9 +15611,20 @@ var init_help = __esm({
15531
15611
  CATEGORY_ORDER = ["Navigation", "Chat", "Tools", "Config", "Export/Import", "System"];
15532
15612
  helpCommand = {
15533
15613
  name: "help",
15534
- description: "Show available commands grouped by category",
15614
+ args: "[--web|--terminal]",
15615
+ description: "Open searchable CLI help or list commands in the terminal",
15535
15616
  category: "debug",
15536
- handler: (_args, context) => {
15617
+ handler: async (args2, context) => {
15618
+ const forceWeb = args2[0]?.toLowerCase() === "--web";
15619
+ const forceTerminal = args2[0]?.toLowerCase() === "--terminal";
15620
+ if (!forceTerminal && (forceWeb || process.stdout.isTTY && hasGraphicalBrowserEnvironment())) {
15621
+ const url = cliHelpUrl();
15622
+ if (await openBrowser(url)) {
15623
+ context.addMessage("system", "CLI help opened on msapling.com.");
15624
+ return;
15625
+ }
15626
+ if (forceWeb) context.addMessage("error", `Could not open a browser. Visit ${url}`);
15627
+ }
15537
15628
  context.addMessage("system", "Available Commands:");
15538
15629
  const grouped = {};
15539
15630
  for (const cmd of commands) {
@@ -15838,55 +15929,6 @@ var init_resume = __esm({
15838
15929
  }
15839
15930
  });
15840
15931
 
15841
- // src/commands/billing/open-browser.ts
15842
- import { spawn as spawn10 } from "child_process";
15843
- function isSafeBrowserUrl(url) {
15844
- if (typeof url !== "string" || url.length === 0 || url.length > 4096) return false;
15845
- for (let index = 0; index < url.length; index++) {
15846
- const code = url.charCodeAt(index);
15847
- if (code <= 32 || code === 127 || code === 34 || code === 92 || code === 96) return false;
15848
- }
15849
- try {
15850
- const parsed = new URL(url);
15851
- return parsed.protocol === "https:" || parsed.protocol === "http:";
15852
- } catch {
15853
- return false;
15854
- }
15855
- }
15856
- function hasGraphicalBrowserEnvironment(platform5 = process.platform, env = process.env) {
15857
- if (platform5 === "win32" || platform5 === "darwin") return true;
15858
- return Boolean(env.DISPLAY || env.WAYLAND_DISPLAY);
15859
- }
15860
- async function openBrowser(url, launch = spawn10) {
15861
- if (!isSafeBrowserUrl(url)) return false;
15862
- const platform5 = process.platform;
15863
- const [command, args2] = platform5 === "win32" ? ["rundll32.exe", ["url.dll,FileProtocolHandler", url]] : platform5 === "darwin" ? ["open", [url]] : ["xdg-open", [url]];
15864
- return new Promise((resolve23) => {
15865
- let settled = false;
15866
- const finish = (opened) => {
15867
- if (settled) return;
15868
- settled = true;
15869
- resolve23(opened);
15870
- };
15871
- try {
15872
- const child = launch(command, [...args2], { stdio: "ignore", detached: true });
15873
- child.once("error", () => finish(false));
15874
- child.once("spawn", () => {
15875
- child.unref();
15876
- finish(true);
15877
- });
15878
- } catch {
15879
- finish(false);
15880
- }
15881
- });
15882
- }
15883
- var init_open_browser = __esm({
15884
- "src/commands/billing/open-browser.ts"() {
15885
- "use strict";
15886
- init_esm_shims();
15887
- }
15888
- });
15889
-
15890
15932
  // src/runtime/modelPickerArtifact.ts
15891
15933
  import { randomBytes as randomBytes15, timingSafeEqual } from "crypto";
15892
15934
  import { createServer } from "http";
@@ -16097,6 +16139,9 @@ var init_projectSettings = __esm({
16097
16139
  function resetModelCatalogCache() {
16098
16140
  catalogCache = /* @__PURE__ */ new WeakMap();
16099
16141
  }
16142
+ function modelPickerReadyMessage(_secretUrl) {
16143
+ return "Model picker opened in your browser (expires in five minutes).";
16144
+ }
16100
16145
  function price(value) {
16101
16146
  if (value === void 0 || value === null || value === "") return "?";
16102
16147
  const n2 = typeof value === "string" ? Number.parseFloat(value) : value;
@@ -16230,7 +16275,9 @@ var init_model = __esm({
16230
16275
  contextLength: model2.context_length ?? null
16231
16276
  }));
16232
16277
  const selected = context.pickModel ? await context.pickModel(pickerRows, current) : await openModelPicker(pickerRows, current, {
16233
- onReady: (url) => context.addMessage("system", `Model picker: ${url}`)
16278
+ // The URL fragment contains the one-time API credential. The
16279
+ // browser receives it directly; never persist it in chat logs.
16280
+ onReady: (url) => context.addMessage("system", modelPickerReadyMessage(url))
16234
16281
  });
16235
16282
  if (!selected) {
16236
16283
  context.addMessage("system", "Model selection cancelled or expired.");
@@ -18328,14 +18375,34 @@ var init_history2 = __esm({
18328
18375
  "src/commands/history.ts"() {
18329
18376
  "use strict";
18330
18377
  init_esm_shims();
18378
+ init_open_browser();
18379
+ init_webLinks();
18331
18380
  historyCommand = {
18332
18381
  name: "history",
18333
18382
  aliases: ["hist"],
18334
- args: "[N]",
18335
- description: "Print the last N messages without virtualization (default 50)",
18383
+ args: "[--web|--terminal] [N]",
18384
+ description: "Open the complete active chat on MSapling or print its last N messages",
18336
18385
  category: "chat",
18337
- handler: (args2, context) => {
18338
- const n2 = args2[0] ? parseInt(args2[0], 10) : 50;
18386
+ handler: async (args2, context) => {
18387
+ const forceWeb = args2[0]?.toLowerCase() === "--web";
18388
+ const forceTerminal = args2[0]?.toLowerCase() === "--terminal";
18389
+ const remaining = forceWeb || forceTerminal ? args2.slice(1) : args2;
18390
+ const shouldOpenWeb = context.executionMode !== "local" && Boolean(context.activeChatId) && !forceTerminal && (forceWeb || remaining.length === 0 && process.stdout.isTTY && hasGraphicalBrowserEnvironment());
18391
+ if (shouldOpenWeb) {
18392
+ const url = cliChatUrl(context.activeChatId);
18393
+ if (await openBrowser(url)) {
18394
+ context.addMessage("system", "Complete CLI chat opened on msapling.com.");
18395
+ return;
18396
+ }
18397
+ if (forceWeb) context.addMessage("error", `Could not open a browser. Visit ${url}`);
18398
+ } else if (forceWeb && context.executionMode === "local") {
18399
+ context.addMessage("error", "Standalone history is local-only. Use /history --terminal [N].");
18400
+ return;
18401
+ } else if (forceWeb && !context.activeChatId) {
18402
+ context.addMessage("error", "No active CLI chat to open.");
18403
+ return;
18404
+ }
18405
+ const n2 = remaining[0] ? parseInt(remaining[0], 10) : 50;
18339
18406
  if (isNaN(n2) || n2 <= 0) {
18340
18407
  context.addMessage("system", "Usage: /history [N] \u2014 N must be a positive integer");
18341
18408
  return;
@@ -18599,7 +18666,7 @@ var init_version = __esm({
18599
18666
  description: "Show version information for CLI and core packages",
18600
18667
  category: "debug",
18601
18668
  handler: async (_args, context) => {
18602
- const cliVersion = true ? "2.3.6-beta.54" : "(dev)";
18669
+ const cliVersion = true ? "2.3.6-beta.56" : "(dev)";
18603
18670
  const coreVersion = true ? "2.3.6-beta.43" : "(dev)";
18604
18671
  const runtime = process.version;
18605
18672
  context.addMessage("system", "MSapling Version Info");
@@ -18608,7 +18675,7 @@ var init_version = __esm({
18608
18675
  context.addMessage("system", row2("Core (@msapling/core)", coreVersion));
18609
18676
  context.addMessage("system", row2("Runtime (Node/Bun)", runtime));
18610
18677
  try {
18611
- const ts = "2026-08-12T06:13:46.315Z";
18678
+ const ts = "2026-08-12T21:55:24.426Z";
18612
18679
  if (ts && ts !== "__BUILD_TIMESTAMP__") {
18613
18680
  context.addMessage("system", row2("Build Timestamp", ts));
18614
18681
  }
@@ -23122,7 +23189,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
23122
23189
  var Header = () => /* @__PURE__ */ jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, marginBottom: 1, children: [
23123
23190
  /* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
23124
23191
  "\u25CF MSapling CLI v",
23125
- "2.3.6-beta.54"
23192
+ "2.3.6-beta.56"
23126
23193
  ] }),
23127
23194
  /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "Platinum Tier Architecture" }) })
23128
23195
  ] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mtreeai/msapling-cli",
3
- "version": "2.3.6-beta.54",
3
+ "version": "2.3.6-beta.56",
4
4
  "description": "MSapling CLI — React/Ink terminal client for the MSapling backend (chat, projects, MDrive, agent tools). Proprietary; redistribution prohibited.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "author": "MSapling Team",