@lanonasis/cli 3.9.4 → 3.9.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog - @lanonasis/cli
2
2
 
3
+ ## [3.9.6] - 2026-02-21
4
+
5
+ ### šŸ› Bug Fixes
6
+
7
+ - **Reliable Memory Auth Routing**: Memory CRUD and search operations now consistently route through the API gateway (`https://api.lanonasis.com`) to avoid MCP endpoint contract mismatches.
8
+ - **Legacy Endpoint Compatibility**: Added fallback support for deployments that still expose RPC-style memory routes (`/api/v1/memory/*`) when REST routes return `400/405`.
9
+ - **Auth Status Accuracy**: `status` now validates live auth state against the auth verify endpoint before reporting authenticated session state.
10
+ - **OAuth Session Stability**: Requests proactively refresh OAuth/JWT sessions to reduce intermittent `memory login required` errors during long-running CLI usage.
11
+ - **Response Normalization**: Memory get/list/search handlers normalize wrapped gateway responses (`{ data: ... }`) for consistent CLI behavior across environments.
12
+
13
+ ### šŸ“š Documentation
14
+
15
+ - Clarified auth flow behavior for vendor keys and bearer tokens.
16
+ - Added release notes for endpoint override guidance and memory transport behavior.
17
+
3
18
  ## [3.9.3] - 2026-02-02
4
19
 
5
20
  ### ✨ Features
package/README.md CHANGED
@@ -1,11 +1,11 @@
1
- # @lanonasis/cli v3.9.3 - Enterprise Security & Professional UX
1
+ # @lanonasis/cli v3.9.6 - Auth Routing & Memory Reliability
2
2
 
3
3
  [![NPM Version](https://img.shields.io/npm/v/@lanonasis/cli)](https://www.npmjs.com/package/@lanonasis/cli)
4
4
  [![Downloads](https://img.shields.io/npm/dt/@lanonasis/cli)](https://www.npmjs.com/package/@lanonasis/cli)
5
5
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
6
  [![Golden Contract](https://img.shields.io/badge/Onasis--Core-v0.1%20Compliant-gold)](https://api.lanonasis.com/.well-known/onasis.json)
7
7
 
8
- šŸŽ‰ **NEW IN v3.9.3**: Fixed JWT authentication routing for username/password login, resolved frozen terminal during interactive input, and added non-interactive vendor key authentication (`-k` flag). Professional CLI UX with seamless inline text editing, intelligent MCP connection management, and first-run onboarding.
8
+ šŸŽ‰ **NEW IN v3.9.6**: Fixed memory auth routing to the API gateway, added compatibility fallbacks for legacy memory endpoints, improved auth status verification, and stabilized OAuth/JWT refresh behavior during CLI memory operations.
9
9
 
10
10
  ## šŸš€ Quick Start
11
11
 
@@ -129,23 +129,19 @@ maas memory list
129
129
 
130
130
  ## šŸ” Security & Authentication
131
131
 
132
- ### Enterprise-Grade SHA-256 Security (v3.7.0+)
132
+ ### Enterprise-Grade API Key Handling
133
133
 
134
- All API keys are now secured with SHA-256 cryptographic hashing:
134
+ The CLI uses secure local storage and sends credentials in the expected wire format:
135
135
 
136
- - āœ… **Automatic Hash Normalization**: Keys are automatically hashed before transmission
137
- - āœ… **Double-Hash Prevention**: Smart detection prevents re-hashing already hashed keys
138
- - āœ… **Cross-Platform Compatibility**: Works seamlessly across Node.js and browser environments
139
- - āœ… **Zero Configuration**: Security is automatic and transparent
140
-
141
- ```typescript
142
- // Hash utilities are built-in and automatic
143
- // Your vendor keys are automatically secured
144
- onasis login --vendor-key pk_xxxxx.sk_xxxxx // āœ… Automatically hashed
145
- ```
136
+ - āœ… **Encrypted At Rest**: Vendor keys are stored in encrypted local storage (keytar when available, encrypted file fallback otherwise).
137
+ - āœ… **Correct On-Wire Format**: Vendor key auth sends the raw vendor key in `X-API-Key` over HTTPS.
138
+ - āœ… **Single Server-Side Hash Validation**: The API validates keys with server-side hashing and does not require client-side hashing.
139
+ - āœ… **Token-First Sessions**: OAuth/JWT sessions use `Authorization: Bearer <token>` and refresh automatically before expiry.
146
140
 
147
141
  ### Authentication Methods
148
142
 
143
+ > Transport note: for memory commands, keep `manualEndpointOverrides=false` so requests route through `https://api.lanonasis.com`.
144
+
149
145
  ### 1. Vendor Key Authentication (Recommended)
150
146
 
151
147
  Best for API integrations and automation. Copy the vendor key value exactly as shown in your LanOnasis dashboard (keys may vary in format):
@@ -242,6 +238,12 @@ onasis memory list --type context --limit 20
242
238
  onasis memory create -t "Project Notes" -c "Important information"
243
239
  onasis memory create -t "Reference" --type reference --tags "docs,api"
244
240
 
241
+ # Create memory via JSON payload
242
+ onasis memory create --json '{"title":"Design decisions","type":"project","content":"Summary...","tags":["architecture","design"]}'
243
+
244
+ # Create memory from a file
245
+ onasis memory create -t "Session notes" --content-file ./notes.md
246
+
245
247
  # Create memories (interactive)
246
248
  onasis memory create -i # Interactive mode with inline editor
247
249
  onasis memory create # Prompts for missing fields
@@ -258,6 +260,34 @@ onasis memory delete <id> # Delete memory
258
260
  onasis memory stats # Memory statistics
259
261
  ```
260
262
 
263
+ #### `onasis memory save-session`
264
+
265
+ Save the current CLI session context (CWD + git branch/status + changed files) as a memory entry so you can persist what you worked on and pick it up later.
266
+
267
+ - `--test-summary`: Stores a human-readable test result summary (e.g., `Vitest: 53 passed, 1 skipped`) in the saved session memory.
268
+ - `--title`: Sets the memory title (default: `Session summary`).
269
+ - `--type`: Sets the memory type (default: `project`).
270
+ - `--tags`: Comma-separated tags for session metadata (default: `session,cli`).
271
+
272
+ **Examples**
273
+ ```bash
274
+ onasis memory save-session --test-summary "Vitest: 53 passed, 1 skipped"
275
+ onasis memory save-session --title "API client fixes" --type project --tags "session,cli,testing"
276
+ ```
277
+
278
+ See **Session management** below for related commands.
279
+
280
+ #### Session management
281
+
282
+ Sessions are stored as memory entries (tagged `session,cli` by default). Related commands:
283
+
284
+ ```bash
285
+ onasis memory save-session
286
+ onasis memory list-sessions
287
+ onasis memory load-session <id>
288
+ onasis memory delete-session <id>
289
+ ```
290
+
261
291
  **Create/Update Options:**
262
292
  | Short | Long | Description |
263
293
  |-------|------|-------------|
@@ -266,6 +296,8 @@ onasis memory stats # Memory statistics
266
296
  | `-i` | `--interactive` | Interactive mode |
267
297
  | | `--type` | Memory type (context, project, knowledge, etc.) |
268
298
  | | `--tags` | Comma-separated tags |
299
+ | | `--json` | JSON payload (title, content, type/memory_type, tags, topic_id) |
300
+ | | `--content-file` | Read content from a file |
269
301
 
270
302
  ### Topic Management
271
303
 
@@ -637,4 +669,3 @@ The CLI will show the authorization URL - copy and paste it into your browser ma
637
669
 
638
670
  **Token refresh failed:**
639
671
  Run `onasis auth login` to re-authenticate.
640
-
@@ -11,11 +11,13 @@ export function configCommands(program) {
11
11
  .action(async (key, value) => {
12
12
  const config = new CLIConfig();
13
13
  await config.init();
14
+ let shouldSave = true;
14
15
  // Handle special cases
15
16
  switch (key) {
16
17
  case 'api-url':
17
18
  await config.setApiUrl(value);
18
19
  console.log(chalk.green('āœ“ API URL updated:'), value);
20
+ shouldSave = false; // setApiUrl already persists
19
21
  break;
20
22
  case 'ai-integration':
21
23
  if (value === 'claude-mcp') {
@@ -42,11 +44,19 @@ export function configCommands(program) {
42
44
  config.set('mcpServerUrl', value);
43
45
  console.log(chalk.green('āœ“ MCP server URL updated:'), value);
44
46
  break;
47
+ case 'force-api':
48
+ config.set('forceApi', value === 'true');
49
+ config.set('connectionTransport', value === 'true' ? 'api' : 'auto');
50
+ console.log(chalk.green('āœ“ Force direct API mode:'), value === 'true' ? 'enabled' : 'disabled');
51
+ break;
45
52
  default:
46
53
  // Generic config set
47
54
  config.set(key, value);
48
55
  console.log(chalk.green(`āœ“ ${key} set to:`), value);
49
56
  }
57
+ if (shouldSave) {
58
+ await config.save();
59
+ }
50
60
  });
51
61
  // Generic config get command
52
62
  program
@@ -101,6 +111,7 @@ export function configCommands(program) {
101
111
  { key: 'mcp-use-remote', description: 'Use remote MCP server', current: config.get('mcpUseRemote') || false },
102
112
  { key: 'mcp-server-path', description: 'Local MCP server path', current: config.get('mcpServerPath') || 'default' },
103
113
  { key: 'mcp-server-url', description: 'Remote MCP server URL', current: config.get('mcpServerUrl') || 'https://mcp.lanonasis.com' },
114
+ { key: 'force-api', description: 'Force direct API transport', current: config.get('forceApi') || false },
104
115
  { key: 'mcpEnabled', description: 'MCP integration enabled', current: config.get('mcpEnabled') || false }
105
116
  ];
106
117
  configOptions.forEach(opt => {
@@ -8,6 +8,30 @@ import { apiClient } from '../utils/api.js';
8
8
  import { formatBytes, truncateText } from '../utils/formatting.js';
9
9
  import { CLIConfig } from '../utils/config.js';
10
10
  import { createTextInputHandler } from '../ux/index.js';
11
+ import * as fs from 'fs/promises';
12
+ import { exec as execCb } from 'node:child_process';
13
+ import { promisify } from 'node:util';
14
+ const exec = promisify(execCb);
15
+ const MEMORY_TYPE_CHOICES = [
16
+ 'context',
17
+ 'project',
18
+ 'knowledge',
19
+ 'reference',
20
+ 'personal',
21
+ 'workflow',
22
+ ];
23
+ const coerceMemoryType = (value) => {
24
+ if (typeof value !== 'string')
25
+ return undefined;
26
+ const normalized = value.trim().toLowerCase();
27
+ // Backward compatibility for older docs/examples.
28
+ if (normalized === 'conversation')
29
+ return 'context';
30
+ if (MEMORY_TYPE_CHOICES.includes(normalized)) {
31
+ return normalized;
32
+ }
33
+ return undefined;
34
+ };
11
35
  const resolveInputMode = async () => {
12
36
  const config = new CLIConfig();
13
37
  await config.init();
@@ -41,13 +65,53 @@ export function memoryCommands(program) {
41
65
  .description('Create a new memory entry')
42
66
  .option('-t, --title <title>', 'memory title')
43
67
  .option('-c, --content <content>', 'memory content')
44
- .option('--type <type>', 'memory type (conversation, knowledge, project, context, reference)', 'context')
68
+ .option('--type <type>', `memory type (${MEMORY_TYPE_CHOICES.join(', ')})`)
45
69
  .option('--tags <tags>', 'comma-separated tags')
46
70
  .option('--topic-id <id>', 'topic ID')
47
71
  .option('-i, --interactive', 'interactive mode')
72
+ .option('--json <json>', 'JSON payload (title, content, type/memory_type, tags[], topic_id)')
73
+ .option('--content-file <path>', 'Read memory content from a file (overrides --content)')
48
74
  .action(async (options) => {
49
75
  try {
50
- let { title, content, type, tags, topicId, interactive } = options;
76
+ let { title, content, type, tags, topicId, interactive, json, contentFile } = options;
77
+ // 1) JSON payload (optional)
78
+ if (json) {
79
+ let parsed;
80
+ try {
81
+ parsed = JSON.parse(json);
82
+ }
83
+ catch (err) {
84
+ const msg = err instanceof Error ? err.message : 'Invalid JSON';
85
+ throw new Error(`Invalid --json payload: ${msg}`);
86
+ }
87
+ if (!title && typeof parsed.title === 'string')
88
+ title = parsed.title;
89
+ if (!content && typeof parsed.content === 'string')
90
+ content = parsed.content;
91
+ const parsedType = parsed.memory_type ?? parsed.type;
92
+ if (!type && parsedType !== undefined) {
93
+ const coerced = coerceMemoryType(parsedType);
94
+ if (!coerced) {
95
+ throw new Error(`Invalid memory type in --json payload. Expected one of: ${MEMORY_TYPE_CHOICES.join(', ')}`);
96
+ }
97
+ type = coerced;
98
+ }
99
+ if (!tags) {
100
+ if (Array.isArray(parsed.tags)) {
101
+ tags = parsed.tags.map((t) => String(t)).join(',');
102
+ }
103
+ else if (typeof parsed.tags === 'string') {
104
+ tags = parsed.tags;
105
+ }
106
+ }
107
+ const parsedTopic = parsed.topic_id ?? parsed.topicId;
108
+ if (!topicId && typeof parsedTopic === 'string')
109
+ topicId = parsedTopic;
110
+ }
111
+ // 2) Content file (optional)
112
+ if (contentFile) {
113
+ content = await fs.readFile(contentFile, 'utf-8');
114
+ }
51
115
  if (interactive || (!title || !content)) {
52
116
  const inputMode = await resolveInputMode();
53
117
  const answers = await inquirer.prompt([
@@ -62,7 +126,7 @@ export function memoryCommands(program) {
62
126
  type: 'list',
63
127
  name: 'type',
64
128
  message: 'Memory type:',
65
- choices: ['conversation', 'knowledge', 'project', 'context', 'reference'],
129
+ choices: [...MEMORY_TYPE_CHOICES],
66
130
  default: type || 'context',
67
131
  },
68
132
  {
@@ -86,11 +150,12 @@ export function memoryCommands(program) {
86
150
  if (!content || content.trim().length === 0) {
87
151
  throw new Error('Content is required');
88
152
  }
153
+ const resolvedType = type ?? 'context';
89
154
  const spinner = ora('Creating memory...').start();
90
155
  const memoryData = {
91
156
  title,
92
157
  content,
93
- memory_type: type
158
+ memory_type: resolvedType
94
159
  };
95
160
  if (tags) {
96
161
  memoryData.tags = tags.split(',').map((tag) => tag.trim()).filter(Boolean);
@@ -115,6 +180,211 @@ export function memoryCommands(program) {
115
180
  process.exit(1);
116
181
  }
117
182
  });
183
+ // Save current working session context as a memory
184
+ program
185
+ .command('save-session')
186
+ .description('Save current session context (git branch/status + optional test summary) as a memory')
187
+ .option('-t, --title <title>', 'memory title', 'Session summary')
188
+ .option('--type <type>', `memory type (${MEMORY_TYPE_CHOICES.join(', ')})`, 'project')
189
+ .option('--tags <tags>', 'comma-separated tags', 'session,cli')
190
+ .option('--test-summary <text>', 'Optional test summary to include')
191
+ .action(async (options) => {
192
+ try {
193
+ const spinner = ora('Collecting session info...').start();
194
+ const cwd = process.cwd();
195
+ const runGit = async (cmd) => {
196
+ try {
197
+ const { stdout } = await exec(cmd, { cwd });
198
+ return String(stdout || '').trim();
199
+ }
200
+ catch {
201
+ return null;
202
+ }
203
+ };
204
+ const branch = await runGit('git rev-parse --abbrev-ref HEAD');
205
+ const status = await runGit('git status --porcelain');
206
+ const changedFiles = status
207
+ ? status
208
+ .split('\n')
209
+ .map((line) => line.trim())
210
+ .filter(Boolean)
211
+ .map((line) => line.replace(/^.. /, ''))
212
+ : [];
213
+ const lines = [];
214
+ lines.push(`# Session Summary`);
215
+ lines.push('');
216
+ lines.push(`- Date: ${new Date().toISOString()}`);
217
+ lines.push(`- CWD: ${cwd}`);
218
+ if (branch)
219
+ lines.push(`- Git branch: ${branch}`);
220
+ lines.push('');
221
+ lines.push('## Changes');
222
+ if (changedFiles.length === 0) {
223
+ lines.push('No uncommitted changes detected (or git not available).');
224
+ }
225
+ else {
226
+ lines.push(changedFiles.map((f) => `- ${f}`).join('\n'));
227
+ }
228
+ lines.push('');
229
+ if (options.testSummary) {
230
+ lines.push('## Test Summary');
231
+ lines.push(options.testSummary);
232
+ lines.push('');
233
+ }
234
+ spinner.text = 'Saving session memory...';
235
+ const resolvedType = coerceMemoryType(options.type) ?? 'project';
236
+ const memoryData = {
237
+ title: options.title || 'Session summary',
238
+ content: lines.join('\n'),
239
+ memory_type: resolvedType
240
+ };
241
+ if (options.tags) {
242
+ memoryData.tags = options.tags.split(',').map((t) => t.trim()).filter(Boolean);
243
+ }
244
+ const memory = await apiClient.createMemory(memoryData);
245
+ spinner.succeed('Session saved');
246
+ console.log();
247
+ console.log(chalk.green('āœ“ Memory created:'));
248
+ console.log(` ID: ${chalk.cyan(memory.id)}`);
249
+ console.log(` Title: ${memory.title}`);
250
+ console.log(` Type: ${memory.memory_type}`);
251
+ }
252
+ catch (error) {
253
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
254
+ console.error(chalk.red('āœ– Failed to save session:'), errorMessage);
255
+ process.exit(1);
256
+ }
257
+ });
258
+ // Session management helpers (sessions are stored as memory entries tagged `session,cli` by default)
259
+ program
260
+ .command('list-sessions')
261
+ .description('List saved CLI sessions (memories tagged session,cli by default)')
262
+ .option('-p, --page <page>', 'page number', '1')
263
+ .option('-l, --limit <limit>', 'number of entries per page', '20')
264
+ .option('--type <type>', 'filter by memory type', 'project')
265
+ .option('--tags <tags>', 'filter by tags (comma-separated)', 'session,cli')
266
+ .option('--sort <field>', 'sort by field (created_at, updated_at, title, last_accessed)', 'created_at')
267
+ .option('--order <order>', 'sort order (asc, desc)', 'desc')
268
+ .action(async (options) => {
269
+ try {
270
+ const spinner = ora('Fetching sessions...').start();
271
+ const params = {
272
+ page: parseInt(options.page || '1'),
273
+ limit: parseInt(options.limit || '20'),
274
+ sort: options.sort || 'created_at',
275
+ order: options.order || 'desc'
276
+ };
277
+ if (options.type)
278
+ params.memory_type = options.type;
279
+ if (options.tags)
280
+ params.tags = options.tags;
281
+ const result = await apiClient.getMemories(params);
282
+ spinner.stop();
283
+ const memories = result.memories || result.data || [];
284
+ if (memories.length === 0) {
285
+ console.log(chalk.yellow('No sessions found'));
286
+ return;
287
+ }
288
+ console.log(chalk.blue.bold(`\nšŸ“š Sessions (${result.pagination.total} total)`));
289
+ console.log(chalk.gray(`Page ${result.pagination.page || 1} of ${result.pagination.pages || Math.ceil(result.pagination.total / result.pagination.limit)}`));
290
+ console.log();
291
+ const outputFormat = process.env.CLI_OUTPUT_FORMAT || 'table';
292
+ if (outputFormat === 'json') {
293
+ console.log(JSON.stringify(result, null, 2));
294
+ return;
295
+ }
296
+ const tableData = memories.map((memory) => [
297
+ truncateText(memory.title, 30),
298
+ memory.memory_type,
299
+ memory.tags.slice(0, 3).join(', '),
300
+ format(new Date(memory.created_at), 'MMM dd, yyyy'),
301
+ memory.access_count
302
+ ]);
303
+ const tableConfig = {
304
+ header: ['Title', 'Type', 'Tags', 'Created', 'Access'],
305
+ columnDefault: {
306
+ width: 20,
307
+ wrapWord: true
308
+ },
309
+ columns: [
310
+ { width: 30 },
311
+ { width: 12 },
312
+ { width: 20 },
313
+ { width: 12 },
314
+ { width: 8 }
315
+ ]
316
+ };
317
+ console.log(table([tableConfig.header, ...tableData], {
318
+ columnDefault: tableConfig.columnDefault,
319
+ columns: tableConfig.columns
320
+ }));
321
+ if (result.pagination.pages > 1) {
322
+ console.log(chalk.gray(`\nUse --page ${result.pagination.page + 1} for next page`));
323
+ }
324
+ }
325
+ catch (error) {
326
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
327
+ console.error(chalk.red('āœ– Failed to list sessions:'), errorMessage);
328
+ process.exit(1);
329
+ }
330
+ });
331
+ program
332
+ .command('load-session')
333
+ .description('Load a saved session by memory ID (prints the saved session context)')
334
+ .argument('<id>', 'session memory ID')
335
+ .action(async (id) => {
336
+ try {
337
+ const spinner = ora('Loading session...').start();
338
+ const memory = await apiClient.getMemory(id);
339
+ spinner.stop();
340
+ const outputFormat = process.env.CLI_OUTPUT_FORMAT || 'text';
341
+ if (outputFormat === 'json') {
342
+ console.log(JSON.stringify(memory, null, 2));
343
+ return;
344
+ }
345
+ console.log(chalk.blue.bold('\nšŸ“Œ Session'));
346
+ console.log(chalk.gray(`${memory.title} (${memory.id})`));
347
+ console.log();
348
+ console.log(memory.content);
349
+ }
350
+ catch (error) {
351
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
352
+ console.error(chalk.red('āœ– Failed to load session:'), errorMessage);
353
+ process.exit(1);
354
+ }
355
+ });
356
+ program
357
+ .command('delete-session')
358
+ .description('Delete a saved session by memory ID')
359
+ .argument('<id>', 'session memory ID')
360
+ .option('-f, --force', 'skip confirmation')
361
+ .action(async (id, options) => {
362
+ try {
363
+ if (!options.force) {
364
+ const memory = await apiClient.getMemory(id);
365
+ const answer = await inquirer.prompt([
366
+ {
367
+ type: 'confirm',
368
+ name: 'confirm',
369
+ message: `Are you sure you want to delete session "${memory.title}"?`,
370
+ default: false
371
+ }
372
+ ]);
373
+ if (!answer.confirm) {
374
+ console.log(chalk.yellow('Deletion cancelled'));
375
+ return;
376
+ }
377
+ }
378
+ const spinner = ora('Deleting session...').start();
379
+ await apiClient.deleteMemory(id);
380
+ spinner.succeed('Session deleted successfully');
381
+ }
382
+ catch (error) {
383
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error';
384
+ console.error(chalk.red('āœ– Failed to delete session:'), errorMessage);
385
+ process.exit(1);
386
+ }
387
+ });
118
388
  // List memories
119
389
  program
120
390
  .command('list')
@@ -294,7 +564,7 @@ export function memoryCommands(program) {
294
564
  .argument('<id>', 'memory ID')
295
565
  .option('-t, --title <title>', 'new title')
296
566
  .option('-c, --content <content>', 'new content')
297
- .option('--type <type>', 'new memory type')
567
+ .option('--type <type>', `new memory type (${MEMORY_TYPE_CHOICES.join(', ')})`)
298
568
  .option('--tags <tags>', 'new tags (comma-separated)')
299
569
  .option('-i, --interactive', 'interactive mode')
300
570
  .action(async (id, options) => {
@@ -317,7 +587,7 @@ export function memoryCommands(program) {
317
587
  type: 'list',
318
588
  name: 'type',
319
589
  message: 'Memory type:',
320
- choices: ['conversation', 'knowledge', 'project', 'context', 'reference'],
590
+ choices: [...MEMORY_TYPE_CHOICES],
321
591
  default: currentMemory.memory_type,
322
592
  },
323
593
  {
@@ -64,12 +64,23 @@ program
64
64
  process.env.MEMORY_API_URL = opts.apiUrl;
65
65
  }
66
66
  process.env.CLI_OUTPUT_FORMAT = opts.output;
67
+ const forceApiFromEnv = process.env.LANONASIS_FORCE_API === 'true' ||
68
+ process.env.CLI_FORCE_API === 'true' ||
69
+ process.env.ONASIS_FORCE_API === 'true';
70
+ const forceApiFromConfig = cliConfig.get('forceApi') === true ||
71
+ cliConfig.get('connectionTransport') === 'api';
72
+ const forceDirectApi = forceApiFromEnv || forceApiFromConfig || opts.mcp === false;
73
+ if (forceDirectApi) {
74
+ process.env.LANONASIS_FORCE_API = 'true';
75
+ }
67
76
  // Auto-initialize MCP unless disabled
68
77
  const isMcpFlow = actionCommand.name() === 'mcp' ||
69
78
  actionCommand.parent?.name?.() === 'mcp' ||
70
79
  actionCommand.name() === 'mcp-server' ||
71
80
  actionCommand.parent?.name?.() === 'mcp-server';
72
- if (opts.mcp !== false && !isMcpFlow && !['init', 'auth', 'login', 'health', 'status'].includes(actionCommand.name())) {
81
+ const isConfigFlow = actionCommand.name() === 'config' ||
82
+ actionCommand.parent?.name?.() === 'config';
83
+ if (!forceDirectApi && !isMcpFlow && !isConfigFlow && !['init', 'auth', 'login', 'health', 'status'].includes(actionCommand.name())) {
73
84
  try {
74
85
  const client = getMCPClient();
75
86
  if (!client.isConnectedToServer()) {
@@ -87,6 +98,9 @@ program
87
98
  }
88
99
  }
89
100
  }
101
+ else if (forceDirectApi && process.env.CLI_VERBOSE === 'true') {
102
+ console.log(colors.muted('MCP auto-connect skipped (force direct API enabled)'));
103
+ }
90
104
  });
91
105
  // Enhanced global error handler
92
106
  process.on('uncaughtException', (error) => {
@@ -324,11 +338,10 @@ const topicCmd = program
324
338
  .description('Topic management commands');
325
339
  requireAuth(topicCmd);
326
340
  topicCommands(topicCmd);
327
- // Configuration commands (require auth)
341
+ // Configuration commands (no auth required)
328
342
  const configCmd = program
329
343
  .command('config')
330
344
  .description('Configuration management');
331
- requireAuth(configCmd);
332
345
  configCommands(configCmd);
333
346
  // Organization commands (require auth)
334
347
  const orgCmd = program
@@ -540,18 +553,30 @@ program
540
553
  .action(async () => {
541
554
  // Initialize config first
542
555
  await cliConfig.init();
543
- const isAuth = await cliConfig.isAuthenticated();
556
+ const verification = await cliConfig.verifyCurrentCredentialsWithServer().catch((error) => ({
557
+ valid: false,
558
+ method: 'none',
559
+ endpoint: undefined,
560
+ reason: error instanceof Error ? error.message : String(error)
561
+ }));
562
+ const isAuth = verification.valid;
544
563
  const apiUrl = cliConfig.getApiUrl();
545
564
  console.log(chalk.blue.bold('MaaS CLI Status'));
546
565
  console.log(`API URL: ${apiUrl}`);
547
566
  console.log(`Authenticated: ${isAuth ? chalk.green('Yes') : chalk.red('No')}`);
567
+ if (process.env.CLI_VERBOSE === 'true' && verification.endpoint) {
568
+ console.log(`Verified via: ${verification.endpoint}`);
569
+ }
548
570
  if (isAuth) {
549
571
  const user = await cliConfig.getCurrentUser();
550
572
  if (user) {
551
573
  console.log(`User: ${user.email}`);
552
574
  console.log(`Plan: ${user.plan}`);
553
575
  }
576
+ return;
554
577
  }
578
+ console.log(chalk.yellow(`Auth check: ${verification.reason || 'Credential validation failed'}`));
579
+ console.log(chalk.yellow('Please run:'), chalk.white('lanonasis auth login'));
555
580
  });
556
581
  // Health command using the healthCheck function
557
582
  program