@mtreeai/msapling-cli 2.3.6-beta.22 → 2.3.6-beta.24

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 (2) hide show
  1. package/dist/index.js +576 -6
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -266,8 +266,127 @@ var init_src = __esm({
266
266
  body: JSON.stringify(symbol)
267
267
  });
268
268
  }
269
+ // CLI-OLLAMA-COMMANDS-01 (Iter 35): Ollama BYOK helpers. Per LAB ollama.py
270
+ // routes are GET/POST/DELETE under /api/ollama. The CLI uses these to wire
271
+ // up the user's local Ollama instance as a free-tier model provider.
272
+ async getOllamaStatus() {
273
+ return await this.request("/api/ollama/status");
274
+ }
275
+ async configureOllama(opts) {
276
+ return await this.request("/api/ollama/configure", {
277
+ method: "POST",
278
+ body: JSON.stringify({
279
+ base_url: opts.baseUrl,
280
+ enabled: opts.enabled,
281
+ action: opts.action,
282
+ model: opts.model
283
+ })
284
+ });
285
+ }
286
+ async testOllama(baseUrl) {
287
+ return await this.request("/api/ollama/test", {
288
+ method: "POST",
289
+ body: JSON.stringify({ base_url: baseUrl })
290
+ });
291
+ }
292
+ async deleteOllamaModel(modelName) {
293
+ return await this.request(`/api/ollama/models/${encodeURIComponent(modelName)}`, {
294
+ method: "DELETE"
295
+ });
296
+ }
297
+ async disableOllama() {
298
+ return await this.request("/api/ollama/disable", { method: "POST" });
299
+ }
300
+ // CLI-KEYS-BYOK-01 (Iter 35): BYOK provider-key management. Per LAB
301
+ // routers/keys.py — /api/keys/{status,save,{provider}}.
302
+ async getProviderKeys() {
303
+ return await this.request("/api/keys/status");
304
+ }
305
+ async saveProviderKey(provider, apiKey) {
306
+ return await this.request("/api/keys/save", {
307
+ method: "POST",
308
+ body: JSON.stringify({ provider, api_key: apiKey })
309
+ });
310
+ }
311
+ async deleteProviderKey(provider) {
312
+ return await this.request(`/api/keys/${encodeURIComponent(provider)}`, { method: "DELETE" });
313
+ }
314
+ // CLI-CHAT-LIFECYCLE-01 (Iter 35): rename/delete/fork/pin. Per LAB
315
+ // projects.py:404 (PATCH), 879 (DELETE), 534 (POST fork), 726 (PATCH pin).
316
+ async renameChat(chatId, title) {
317
+ return await this.request(`/api/projects/chat/${encodeURIComponent(chatId)}`, {
318
+ method: "PATCH",
319
+ body: JSON.stringify({ title })
320
+ });
321
+ }
322
+ async deleteChat(chatId) {
323
+ return await this.request(`/api/projects/chat/${encodeURIComponent(chatId)}`, { method: "DELETE" });
324
+ }
325
+ async forkChat(chatId, body) {
326
+ return await this.request(`/api/projects/chat/${encodeURIComponent(chatId)}/fork`, {
327
+ method: "POST",
328
+ body: JSON.stringify(body ?? {})
329
+ });
330
+ }
331
+ async pinChat(chatId, pinned) {
332
+ return await this.request(`/api/projects/chat/${encodeURIComponent(chatId)}/pin`, {
333
+ method: "PATCH",
334
+ body: JSON.stringify({ pinned })
335
+ });
336
+ }
337
+ // CLI-MEMORIES-01 (Iter 35): backend-managed memories (vs the local notes
338
+ // /memory currently surfaces). Per LAB memories.py — list / create / delete.
339
+ async listMemories() {
340
+ return await this.request("/api/memories");
341
+ }
342
+ async createMemory(content) {
343
+ return await this.request("/api/memories", {
344
+ method: "POST",
345
+ body: JSON.stringify({ content })
346
+ });
347
+ }
348
+ async deleteMemory(memoryId) {
349
+ return await this.request(`/api/memories/${encodeURIComponent(memoryId)}`, { method: "DELETE" });
350
+ }
351
+ // CLI-PROJECT-CREATE-01 (Iter 34): create a new project. Per LAB
352
+ // projects.py:239 — POST /api/projects/ with {project_name}.
353
+ async createProject(name) {
354
+ return await this.request("/api/projects/", {
355
+ method: "POST",
356
+ body: JSON.stringify({ project_name: name })
357
+ });
358
+ }
359
+ // CLI-CHAT-CREATE-01 (Iter 34): create a new chat in a project. Per LAB
360
+ // projects.py:365 — POST /api/projects/chat/new with
361
+ // {project_name, slot_label?, chat_name?, model?, client_type?}.
362
+ async createChat(opts) {
363
+ return await this.request("/api/projects/chat/new", {
364
+ method: "POST",
365
+ body: JSON.stringify({
366
+ project_name: opts.projectName,
367
+ slot_label: opts.title,
368
+ chat_name: opts.title,
369
+ model: opts.model,
370
+ client_type: "cli"
371
+ })
372
+ });
373
+ }
374
+ // CLI-BROADCAST-01 (Iter 34): broadcast a prompt to sibling chats in a
375
+ // project. Per LAB broadcast.py:145 — POST /api/broadcast/execute with
376
+ // {project_name, prompt, source_chat_id?, max_targets?}.
377
+ async broadcastExecute(opts) {
378
+ return await this.request("/api/broadcast/execute", {
379
+ method: "POST",
380
+ body: JSON.stringify({
381
+ project_name: opts.projectName,
382
+ prompt: opts.prompt,
383
+ source_chat_id: opts.sourceChatId,
384
+ max_targets: opts.maxTargets
385
+ })
386
+ });
387
+ }
269
388
  async getModels() {
270
- return await this.request("/api/models");
389
+ return await this.request("/api/benchmark/models");
271
390
  }
272
391
  /**
273
392
  * Fetch the canonical tool registry from the backend.
@@ -8588,7 +8707,110 @@ function resolveChat(arg, chats) {
8588
8707
  }
8589
8708
  return { kind: "error", message: `no chat matches '${trimmed}'. Run /chat (no args) for the list.` };
8590
8709
  }
8710
+ async function createChat(args2, context) {
8711
+ let title = "";
8712
+ let model;
8713
+ for (let i = 0; i < args2.length; i++) {
8714
+ if (args2[i] === "--model" && i + 1 < args2.length) {
8715
+ model = args2[i + 1];
8716
+ i++;
8717
+ } else {
8718
+ title += (title ? " " : "") + args2[i];
8719
+ }
8720
+ }
8721
+ try {
8722
+ const overview = await context.client.me();
8723
+ const currentId = context.getProjectId();
8724
+ const project = overview.projects.find((p) => p.id === currentId) ?? overview.projects[0];
8725
+ if (!project) {
8726
+ context.addMessage("error", "No active project. Use /project new <name> or /project <id> first.");
8727
+ return;
8728
+ }
8729
+ const result = await context.client.createChat({
8730
+ projectName: project.name,
8731
+ title: title || void 0,
8732
+ model: model || void 0
8733
+ });
8734
+ context.addMessage("system", `Created chat "${result.title}" (${result.chat_id}) using ${result.model}`);
8735
+ context.setActiveChatId(result.chat_id);
8736
+ await context.refreshOverview();
8737
+ } catch (e) {
8738
+ context.addMessage("error", `Failed to create chat: ${e.message}`);
8739
+ }
8740
+ }
8741
+ async function resolveChatId(rest, context) {
8742
+ const candidate = rest[0]?.trim();
8743
+ if (candidate && /^[0-9a-fA-F-]{32,}$/.test(candidate)) return candidate;
8744
+ return context.activeChatId ?? null;
8745
+ }
8746
+ async function lifecycleSub(args2, context) {
8747
+ const sub = args2[0]?.toLowerCase();
8748
+ const rest = args2.slice(1);
8749
+ try {
8750
+ if (sub === "rename") {
8751
+ const newTitle = rest.slice(1).join(" ").trim() || rest[0]?.trim();
8752
+ const chatId = await resolveChatId(rest, context);
8753
+ if (!chatId || !newTitle) {
8754
+ context.addMessage("system", "Usage: /chat rename [<chat_id>] <new title>");
8755
+ return;
8756
+ }
8757
+ const r = await context.client.renameChat(chatId, newTitle);
8758
+ context.addMessage("system", `Renamed to "${r.title}"`);
8759
+ return;
8760
+ }
8761
+ if (sub === "delete" || sub === "rm") {
8762
+ const chatId = await resolveChatId(rest, context);
8763
+ if (!chatId) {
8764
+ context.addMessage("system", "Usage: /chat delete [<chat_id>]");
8765
+ return;
8766
+ }
8767
+ await context.client.deleteChat(chatId);
8768
+ context.addMessage("system", `Deleted chat ${chatId}`);
8769
+ if (chatId === context.activeChatId) context.setActiveChatId(null);
8770
+ return;
8771
+ }
8772
+ if (sub === "fork") {
8773
+ const chatId = await resolveChatId(rest, context);
8774
+ if (!chatId) {
8775
+ context.addMessage("system", "Usage: /chat fork [<chat_id>]");
8776
+ return;
8777
+ }
8778
+ const r = await context.client.forkChat(chatId);
8779
+ context.addMessage("system", `Forked \u2192 ${r.chat_id ?? r.title ?? "(ok)"}`);
8780
+ if (r.chat_id) context.setActiveChatId(r.chat_id);
8781
+ return;
8782
+ }
8783
+ if (sub === "pin") {
8784
+ const chatId = await resolveChatId(rest, context);
8785
+ if (!chatId) {
8786
+ context.addMessage("system", "Usage: /chat pin [<chat_id>]");
8787
+ return;
8788
+ }
8789
+ await context.client.pinChat(chatId, true);
8790
+ context.addMessage("system", `Pinned ${chatId}`);
8791
+ return;
8792
+ }
8793
+ if (sub === "unpin") {
8794
+ const chatId = await resolveChatId(rest, context);
8795
+ if (!chatId) {
8796
+ context.addMessage("system", "Usage: /chat unpin [<chat_id>]");
8797
+ return;
8798
+ }
8799
+ await context.client.pinChat(chatId, false);
8800
+ context.addMessage("system", `Unpinned ${chatId}`);
8801
+ return;
8802
+ }
8803
+ } catch (e) {
8804
+ context.addMessage("error", `Chat ${sub}: ${e.message}`);
8805
+ }
8806
+ }
8591
8807
  async function listChats(args2, context) {
8808
+ if (args2[0] === "new" || args2[0] === "create") {
8809
+ return createChat(args2.slice(1), context);
8810
+ }
8811
+ if (["rename", "delete", "rm", "fork", "pin", "unpin"].includes(args2[0])) {
8812
+ return lifecycleSub(args2, context);
8813
+ }
8592
8814
  const arg = args2.join(" ").trim();
8593
8815
  const result = await fetchChatsForCurrentProject(context);
8594
8816
  if ("error" in result) {
@@ -8643,6 +8865,291 @@ var init_chat = __esm({
8643
8865
  }
8644
8866
  });
8645
8867
 
8868
+ // src/commands/broadcast.ts
8869
+ var broadcastCommand;
8870
+ var init_broadcast = __esm({
8871
+ "src/commands/broadcast.ts"() {
8872
+ "use strict";
8873
+ init_esm_shims();
8874
+ broadcastCommand = {
8875
+ name: "broadcast",
8876
+ aliases: ["bcast"],
8877
+ args: "[--max N] [--exclude-current] <prompt>",
8878
+ description: "Send a prompt to every sibling chat in the active project (rate-limited 10/min).",
8879
+ category: "chat",
8880
+ handler: async (args2, context) => {
8881
+ let maxTargets = 8;
8882
+ let excludeCurrent = false;
8883
+ const promptParts = [];
8884
+ for (let i = 0; i < args2.length; i++) {
8885
+ if (args2[i] === "--max" && i + 1 < args2.length) {
8886
+ const n = Number(args2[i + 1]);
8887
+ if (Number.isFinite(n) && n > 0) maxTargets = Math.min(32, Math.floor(n));
8888
+ i++;
8889
+ continue;
8890
+ }
8891
+ if (args2[i] === "--exclude-current") {
8892
+ excludeCurrent = true;
8893
+ continue;
8894
+ }
8895
+ promptParts.push(args2[i]);
8896
+ }
8897
+ const prompt4 = promptParts.join(" ").trim();
8898
+ if (!prompt4) {
8899
+ context.addMessage("system", "Usage: /broadcast [--max N] [--exclude-current] <prompt>");
8900
+ return;
8901
+ }
8902
+ try {
8903
+ const overview = await context.client.me();
8904
+ const currentId = context.getProjectId();
8905
+ const project = overview.projects.find((p) => p.id === currentId) ?? overview.projects[0];
8906
+ if (!project) {
8907
+ context.addMessage("error", "No active project. Use /project <id> first.");
8908
+ return;
8909
+ }
8910
+ const result = await context.client.broadcastExecute({
8911
+ projectName: project.name,
8912
+ prompt: prompt4,
8913
+ sourceChatId: excludeCurrent ? context.activeChatId ?? void 0 : void 0,
8914
+ maxTargets
8915
+ });
8916
+ context.addMessage(
8917
+ "system",
8918
+ `Broadcast queued: ${result.broadcast_id} \u2192 ${result.target_chat_ids?.length ?? 0} chat(s) in project '${project.name}'.`
8919
+ );
8920
+ if (Array.isArray(result.target_chat_ids) && result.target_chat_ids.length > 0) {
8921
+ for (const cid of result.target_chat_ids.slice(0, 8)) {
8922
+ context.addMessage("system", ` - ${cid}`);
8923
+ }
8924
+ if (result.target_chat_ids.length > 8) {
8925
+ context.addMessage("system", ` \u2026and ${result.target_chat_ids.length - 8} more.`);
8926
+ }
8927
+ }
8928
+ } catch (e) {
8929
+ context.addMessage("error", `Broadcast failed: ${e.message}`);
8930
+ }
8931
+ }
8932
+ };
8933
+ }
8934
+ });
8935
+
8936
+ // src/commands/ollama.ts
8937
+ function formatSize(bytes) {
8938
+ if (!bytes) return "?";
8939
+ const gb = bytes / 1e9;
8940
+ if (gb >= 1) return `${gb.toFixed(1)} GB`;
8941
+ const mb = bytes / 1e6;
8942
+ return `${mb.toFixed(0)} MB`;
8943
+ }
8944
+ var DEFAULT_BASE_URL, ollamaCommand;
8945
+ var init_ollama = __esm({
8946
+ "src/commands/ollama.ts"() {
8947
+ "use strict";
8948
+ init_esm_shims();
8949
+ DEFAULT_BASE_URL = "http://localhost:11434";
8950
+ ollamaCommand = {
8951
+ name: "ollama",
8952
+ args: "[status|configure|enable|disable|test|models|pull|delete] [...args]",
8953
+ description: "Configure local Ollama integration (BYOK free tier) and manage local models.",
8954
+ category: "config",
8955
+ handler: async (args2, context) => {
8956
+ const sub = (args2[0] ?? "status").toLowerCase();
8957
+ const rest = args2.slice(1);
8958
+ try {
8959
+ if (sub === "status" || sub === "models") {
8960
+ const s = await context.client.getOllamaStatus();
8961
+ context.addMessage("system", "=== Ollama Status ===");
8962
+ context.addMessage("system", ` Enabled: ${s.enabled ? "yes" : "no"}`);
8963
+ context.addMessage("system", ` Base URL: ${s.base_url}`);
8964
+ context.addMessage("system", ` Connected: ${s.connected ? "yes" : "no"}${s.connection_message ? " \u2014 " + s.connection_message : ""}`);
8965
+ if (s.models?.length) {
8966
+ context.addMessage("system", ` Models (${s.models.length}):`);
8967
+ for (const m of s.models) {
8968
+ context.addMessage("system", ` - ${m.id} (${formatSize(m.size)})`);
8969
+ }
8970
+ } else if (s.enabled) {
8971
+ context.addMessage("system", " Models: (none pulled \u2014 try /ollama pull <name>, e.g. llama3.2)");
8972
+ } else {
8973
+ context.addMessage("system", "");
8974
+ context.addMessage("system", " Run '/ollama configure' to enable.");
8975
+ }
8976
+ return;
8977
+ }
8978
+ if (sub === "configure" || sub === "enable") {
8979
+ const url = rest[0] || DEFAULT_BASE_URL;
8980
+ const res = await context.client.configureOllama({ baseUrl: url, enabled: true });
8981
+ context.addMessage("system", `Ollama enabled at ${url}: ${res.message ?? res.status}`);
8982
+ return;
8983
+ }
8984
+ if (sub === "disable") {
8985
+ const res = await context.client.disableOllama();
8986
+ context.addMessage("system", `Ollama disabled: ${res.message ?? res.status}`);
8987
+ return;
8988
+ }
8989
+ if (sub === "test") {
8990
+ const url = rest[0] || DEFAULT_BASE_URL;
8991
+ const res = await context.client.testOllama(url);
8992
+ context.addMessage("system", `Connection to ${url}: ${res.connected ? "OK" : "FAILED"} \u2014 ${res.message}`);
8993
+ if (res.models?.length) {
8994
+ context.addMessage("system", ` Found ${res.models.length} model(s):`);
8995
+ for (const m of res.models.slice(0, 12)) {
8996
+ context.addMessage("system", ` - ${m.id}`);
8997
+ }
8998
+ if (res.models.length > 12) context.addMessage("system", ` \u2026and ${res.models.length - 12} more.`);
8999
+ }
9000
+ return;
9001
+ }
9002
+ if (sub === "pull") {
9003
+ const model = rest[0];
9004
+ if (!model) {
9005
+ context.addMessage("system", "Usage: /ollama pull <model> (e.g. /ollama pull llama3.2)");
9006
+ return;
9007
+ }
9008
+ const status = await context.client.getOllamaStatus();
9009
+ const res = await context.client.configureOllama({
9010
+ baseUrl: status.base_url || DEFAULT_BASE_URL,
9011
+ enabled: status.enabled !== false,
9012
+ action: "pull",
9013
+ model
9014
+ });
9015
+ context.addMessage("system", res.message ?? res.status);
9016
+ return;
9017
+ }
9018
+ if (sub === "delete" || sub === "remove" || sub === "rm") {
9019
+ const model = rest[0];
9020
+ if (!model) {
9021
+ context.addMessage("system", "Usage: /ollama delete <model>");
9022
+ return;
9023
+ }
9024
+ const res = await context.client.deleteOllamaModel(model);
9025
+ context.addMessage("system", res.message ?? res.status);
9026
+ return;
9027
+ }
9028
+ context.addMessage("error", `Unknown /ollama subcommand '${sub}'. Try: status, configure, disable, test, pull, delete.`);
9029
+ } catch (e) {
9030
+ context.addMessage("error", `Ollama: ${e.message}`);
9031
+ }
9032
+ }
9033
+ };
9034
+ }
9035
+ });
9036
+
9037
+ // src/commands/keys.ts
9038
+ var keysCommand;
9039
+ var init_keys = __esm({
9040
+ "src/commands/keys.ts"() {
9041
+ "use strict";
9042
+ init_esm_shims();
9043
+ keysCommand = {
9044
+ name: "keys",
9045
+ args: "[list|add|delete] [...args]",
9046
+ description: "Manage BYOK provider API keys (openai, anthropic, google, openrouter, \u2026).",
9047
+ category: "config",
9048
+ handler: async (args2, context) => {
9049
+ const sub = (args2[0] ?? "list").toLowerCase();
9050
+ const rest = args2.slice(1);
9051
+ try {
9052
+ if (sub === "list" || sub === "status") {
9053
+ const s = await context.client.getProviderKeys();
9054
+ const providers = s.providers ?? [];
9055
+ context.addMessage("system", `Provider Keys (${providers.length}):`);
9056
+ for (const p of providers) {
9057
+ const tag = p.configured ? "\u2713" : " ";
9058
+ const prefix = p.key_prefix ? ` (${p.key_prefix}\u2026)` : "";
9059
+ context.addMessage("system", ` [${tag}] ${p.provider}${prefix}`);
9060
+ }
9061
+ if (providers.length === 0) {
9062
+ context.addMessage("system", " (none \u2014 add via /keys add <provider> <key>)");
9063
+ }
9064
+ return;
9065
+ }
9066
+ if (sub === "add" || sub === "save") {
9067
+ const provider = rest[0];
9068
+ const key = rest.slice(1).join(" ").trim();
9069
+ if (!provider || !key) {
9070
+ context.addMessage("system", "Usage: /keys add <provider> <key> (e.g. /keys add openai sk-...)");
9071
+ return;
9072
+ }
9073
+ const res = await context.client.saveProviderKey(provider, key);
9074
+ context.addMessage("system", `Saved key for ${provider}: ${res.message ?? res.status}`);
9075
+ return;
9076
+ }
9077
+ if (sub === "delete" || sub === "rm" || sub === "remove") {
9078
+ const provider = rest[0];
9079
+ if (!provider) {
9080
+ context.addMessage("system", "Usage: /keys delete <provider>");
9081
+ return;
9082
+ }
9083
+ const res = await context.client.deleteProviderKey(provider);
9084
+ context.addMessage("system", `Removed key for ${provider}: ${res.message ?? res.status}`);
9085
+ return;
9086
+ }
9087
+ context.addMessage("error", `Unknown /keys subcommand '${sub}'. Try: list, add, delete.`);
9088
+ } catch (e) {
9089
+ context.addMessage("error", `Keys: ${e.message}`);
9090
+ }
9091
+ }
9092
+ };
9093
+ }
9094
+ });
9095
+
9096
+ // src/commands/memories.ts
9097
+ var memoriesCommand;
9098
+ var init_memories = __esm({
9099
+ "src/commands/memories.ts"() {
9100
+ "use strict";
9101
+ init_esm_shims();
9102
+ memoriesCommand = {
9103
+ name: "memories",
9104
+ args: "[list|add|delete] [...args]",
9105
+ description: "Manage the user-scoped memory bank (used across all chats).",
9106
+ category: "project",
9107
+ handler: async (args2, context) => {
9108
+ const sub = (args2[0] ?? "list").toLowerCase();
9109
+ const rest = args2.slice(1);
9110
+ try {
9111
+ if (sub === "list" || sub === "show") {
9112
+ const items = await context.client.listMemories();
9113
+ if (!items || items.length === 0) {
9114
+ context.addMessage("system", "No memories yet. Add one via /memories add <text>.");
9115
+ return;
9116
+ }
9117
+ context.addMessage("system", `Memories (${items.length}):`);
9118
+ for (const m of items) {
9119
+ const when = m.created_at ? new Date(m.created_at).toISOString().slice(0, 10) : "";
9120
+ context.addMessage("system", ` ${m.id?.slice(0, 8)} \xB7 ${when} ${m.content}`);
9121
+ }
9122
+ return;
9123
+ }
9124
+ if (sub === "add" || sub === "save") {
9125
+ const content = rest.join(" ").trim();
9126
+ if (!content) {
9127
+ context.addMessage("system", "Usage: /memories add <content>");
9128
+ return;
9129
+ }
9130
+ const m = await context.client.createMemory(content);
9131
+ context.addMessage("system", `Saved (${m.id?.slice(0, 8)}).`);
9132
+ return;
9133
+ }
9134
+ if (sub === "delete" || sub === "rm" || sub === "remove") {
9135
+ const id = rest[0]?.trim();
9136
+ if (!id) {
9137
+ context.addMessage("system", "Usage: /memories delete <id>");
9138
+ return;
9139
+ }
9140
+ await context.client.deleteMemory(id);
9141
+ context.addMessage("system", `Deleted ${id.slice(0, 8)}.`);
9142
+ return;
9143
+ }
9144
+ context.addMessage("error", `Unknown /memories subcommand '${sub}'. Try: list, add, delete.`);
9145
+ } catch (e) {
9146
+ context.addMessage("error", `Memories: ${e.message}`);
9147
+ }
9148
+ }
9149
+ };
9150
+ }
9151
+ });
9152
+
8646
9153
  // src/commands/clear.ts
8647
9154
  var clearCommand;
8648
9155
  var init_clear = __esm({
@@ -8800,8 +9307,47 @@ Current: ${current}`);
8800
9307
  }
8801
9308
  return;
8802
9309
  }
8803
- context.setModel(newModel);
8804
- context.addMessage("system", `Model switched to: ${newModel}`);
9310
+ try {
9311
+ const models = await context.client.getModels();
9312
+ const list = models.filter((m) => !!m.id);
9313
+ const trimmed = newModel.trim();
9314
+ const exact = list.find((m) => m.id === trimmed);
9315
+ if (exact) {
9316
+ context.setModel(exact.id);
9317
+ context.addMessage("system", `Model switched to: ${exact.id}`);
9318
+ return;
9319
+ }
9320
+ if (/^\d+$/.test(trimmed)) {
9321
+ const idx = parseInt(trimmed, 10) - 1;
9322
+ if (idx >= 0 && idx < list.length && list[idx].id) {
9323
+ context.setModel(list[idx].id);
9324
+ context.addMessage("system", `Model switched to: ${list[idx].id}`);
9325
+ return;
9326
+ }
9327
+ }
9328
+ const lower = trimmed.toLowerCase();
9329
+ const subs = list.filter((m) => (m.id ?? "").toLowerCase().includes(lower));
9330
+ if (subs.length === 1) {
9331
+ context.setModel(subs[0].id);
9332
+ context.addMessage("system", `Model switched to: ${subs[0].id}`);
9333
+ return;
9334
+ }
9335
+ if (subs.length > 1 && subs.length <= 10) {
9336
+ context.addMessage(
9337
+ "error",
9338
+ `'${trimmed}' matches ${subs.length} models: ${subs.map((m) => m.id).join(", ")}`
9339
+ );
9340
+ return;
9341
+ }
9342
+ if (subs.length > 10) {
9343
+ context.addMessage("error", `'${trimmed}' matches ${subs.length} models \u2014 be more specific.`);
9344
+ return;
9345
+ }
9346
+ context.addMessage("error", `No model matches '${trimmed}'. Run /model (no args) for the list of ${list.length} models.`);
9347
+ } catch (e) {
9348
+ context.setModel(newModel);
9349
+ context.addMessage("system", `Model switched to: ${newModel} (unverified \u2014 model catalog unreachable: ${e.message})`);
9350
+ }
8805
9351
  }
8806
9352
  };
8807
9353
  }
@@ -8946,6 +9492,22 @@ var init_project = __esm({
8946
9492
  handler: async (args2, context) => {
8947
9493
  const arg = args2.join(" ").trim();
8948
9494
  const currentId = context.getProjectId() || null;
9495
+ if (args2[0] === "new" || args2[0] === "create") {
9496
+ const name = args2.slice(1).join(" ").trim();
9497
+ if (!name) {
9498
+ context.addMessage("system", "Usage: /project new <name>");
9499
+ return;
9500
+ }
9501
+ try {
9502
+ const result = await context.client.createProject(name);
9503
+ context.addMessage("system", `Created project "${result.project}" (${result.project_id})`);
9504
+ context.setProjectId(result.project_id);
9505
+ await context.refreshOverview();
9506
+ } catch (e) {
9507
+ context.addMessage("error", `Failed to create project: ${e.message}`);
9508
+ }
9509
+ return;
9510
+ }
8949
9511
  let projects = [];
8950
9512
  try {
8951
9513
  const overview = await context.client.me();
@@ -9876,7 +10438,7 @@ var init_version = __esm({
9876
10438
  description: "Show version information for CLI and core packages",
9877
10439
  category: "debug",
9878
10440
  handler: async (_args, context) => {
9879
- const cliVersion = true ? "2.3.6-beta.22" : "(dev)";
10441
+ const cliVersion = true ? "2.3.6-beta.24" : "(dev)";
9880
10442
  const coreVersion = true ? "2.3.2" : "(dev)";
9881
10443
  const runtime = process.version;
9882
10444
  context.addMessage("system", "MSapling Version Info");
@@ -9885,7 +10447,7 @@ var init_version = __esm({
9885
10447
  context.addMessage("system", row2("Core (@msapling/core)", coreVersion));
9886
10448
  context.addMessage("system", row2("Runtime (Node/Bun)", runtime));
9887
10449
  try {
9888
- const ts = "2026-05-28T19:10:28.448Z";
10450
+ const ts = "2026-05-28T19:31:59.228Z";
9889
10451
  if (ts && ts !== "__BUILD_TIMESTAMP__") {
9890
10452
  context.addMessage("system", row2("Build Timestamp", ts));
9891
10453
  }
@@ -10624,6 +11186,10 @@ var init_commands = __esm({
10624
11186
  init_exit();
10625
11187
  init_help();
10626
11188
  init_chat();
11189
+ init_broadcast();
11190
+ init_ollama();
11191
+ init_keys();
11192
+ init_memories();
10627
11193
  init_clear();
10628
11194
  init_mode();
10629
11195
  init_model();
@@ -10659,6 +11225,10 @@ var init_commands = __esm({
10659
11225
  helpCommand,
10660
11226
  chatCommand,
10661
11227
  chatsCommand,
11228
+ broadcastCommand,
11229
+ ollamaCommand,
11230
+ keysCommand,
11231
+ memoriesCommand,
10662
11232
  clearCommand,
10663
11233
  modeCommand,
10664
11234
  modelCommand,
@@ -14288,7 +14858,7 @@ import { jsx, jsxs } from "react/jsx-runtime";
14288
14858
  var Header = () => /* @__PURE__ */ jsxs(Box, { borderStyle: "single", borderColor: "cyan", paddingX: 1, marginBottom: 1, children: [
14289
14859
  /* @__PURE__ */ jsxs(Text, { bold: true, color: "cyan", children: [
14290
14860
  "\u25CF MSapling CLI v",
14291
- "2.3.6-beta.22"
14861
+ "2.3.6-beta.24"
14292
14862
  ] }),
14293
14863
  /* @__PURE__ */ jsx(Box, { marginLeft: 2, children: /* @__PURE__ */ jsx(Text, { color: "gray", children: "Platinum Tier Architecture" }) })
14294
14864
  ] });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mtreeai/msapling-cli",
3
- "version": "2.3.6-beta.22",
3
+ "version": "2.3.6-beta.24",
4
4
  "description": "MSapling CLI — React/Ink terminal client for the MSapling backend (chat, projects, MDrive, agent tools). Proprietary; redistribution prohibited.",
5
5
  "license": "UNLICENSED",
6
6
  "author": "MSapling Team",