snow-ai 0.4.4 → 0.4.5

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.
@@ -5,6 +5,7 @@ import { withRetryGenerator, parseJsonWithFix } from '../utils/retryUtils.js';
5
5
  import { logger } from '../utils/logger.js';
6
6
  import { addProxyToFetchOptions } from '../utils/proxyUtils.js';
7
7
  import { saveUsageToFile } from '../utils/usageLogger.js';
8
+ import { isDevMode, getDevUserId } from '../utils/devMode.js';
8
9
  let anthropicConfig = null;
9
10
  // Persistent userId that remains the same until application restart
10
11
  let persistentUserId = null;
@@ -35,8 +36,16 @@ export function resetAnthropicClient() {
35
36
  * Generate a persistent user_id that remains the same until application restart
36
37
  * Format: user_<hash>_account__session_<uuid>
37
38
  * This matches Anthropic's expected format for tracking and caching
39
+ *
40
+ * In dev mode (--dev flag), uses a persistent userId from ~/.snow/dev-user-id
41
+ * instead of generating a new one each session
38
42
  */
39
43
  function getPersistentUserId() {
44
+ // Check if dev mode is enabled
45
+ if (isDevMode()) {
46
+ return getDevUserId();
47
+ }
48
+ // Normal mode: generate userId per session
40
49
  if (!persistentUserId) {
41
50
  const sessionId = randomUUID();
42
51
  const hash = createHash('sha256')
package/dist/cli.d.ts CHANGED
@@ -1,2 +1,2 @@
1
1
  #!/usr/bin/env node
2
- import './utils/patch-highlight.js';
2
+ export {};
package/dist/cli.js CHANGED
@@ -1,7 +1,4 @@
1
1
  #!/usr/bin/env node
2
- // CRITICAL: Patch cli-highlight BEFORE any other imports
3
- // This must be the first import to ensure the patch is applied before cli-markdown loads
4
- import './utils/patch-highlight.js';
5
2
  import React from 'react';
6
3
  import { render, Text, Box } from 'ink';
7
4
  import Spinner from 'ink-spinner';
@@ -13,6 +10,7 @@ import { vscodeConnection } from './utils/vscodeConnection.js';
13
10
  import { resourceMonitor } from './utils/resourceMonitor.js';
14
11
  import { initializeProfiles } from './utils/configManager.js';
15
12
  import { processManager } from './utils/processManager.js';
13
+ import { enableDevMode, getDevUserId } from './utils/devMode.js';
16
14
  const execAsync = promisify(exec);
17
15
  // Check for updates asynchronously
18
16
  async function checkForUpdates(currentVersion) {
@@ -43,6 +41,7 @@ Options
43
41
  --update Update to latest version
44
42
  -c Skip welcome screen and resume last conversation
45
43
  --ask Quick question mode (headless mode with single prompt)
44
+ --dev Enable developer mode with persistent userId for testing
46
45
  `, {
47
46
  importMeta: import.meta,
48
47
  flags: {
@@ -57,6 +56,10 @@ Options
57
56
  ask: {
58
57
  type: 'string',
59
58
  },
59
+ dev: {
60
+ type: 'boolean',
61
+ default: false,
62
+ },
60
63
  },
61
64
  });
62
65
  // Handle update flag
@@ -72,6 +75,14 @@ if (cli.flags.update) {
72
75
  process.exit(1);
73
76
  }
74
77
  }
78
+ // Handle dev mode flag
79
+ if (cli.flags.dev) {
80
+ enableDevMode();
81
+ const userId = getDevUserId();
82
+ console.log('🔧 Developer mode enabled');
83
+ console.log(`📝 Using persistent userId: ${userId}`);
84
+ console.log(`📂 Stored in: ~/.snow/dev-user-id\n`);
85
+ }
75
86
  // Start resource monitoring in development/debug mode
76
87
  if (process.env['NODE_ENV'] === 'development' || process.env['DEBUG']) {
77
88
  resourceMonitor.startMonitoring(30000); // Monitor every 30 seconds
@@ -1,23 +1,70 @@
1
1
  import React from 'react';
2
2
  import { Text, Box } from 'ink';
3
- // @ts-expect-error - cli-markdown doesn't have TypeScript definitions
4
- import cliMarkdown from 'cli-markdown';
3
+ import { marked } from 'marked';
4
+ import TerminalRenderer from 'marked-terminal';
5
5
  import logger from '../../utils/logger.js';
6
+ // Configure marked to use terminal renderer
7
+ marked.setOptions({
8
+ // @ts-expect-error - marked-terminal types mismatch with marked v15
9
+ renderer: new TerminalRenderer({
10
+ reflowText: true,
11
+ width: 80,
12
+ emoji: false,
13
+ tab: 2,
14
+ }),
15
+ });
16
+ /**
17
+ * Sanitize markdown content to prevent number-to-alphabet errors
18
+ * Fixes invalid ordered list start attributes (0 or negative values)
19
+ */
20
+ function sanitizeMarkdownContent(content) {
21
+ // Replace <ol start="0">, <ol start="-1">, etc. with <ol start="1">
22
+ return content.replace(/<ol\s+start=["']?(0|-\d+)["']?>/gi, '<ol start="1">');
23
+ }
24
+ /**
25
+ * Fallback renderer for when cli-markdown fails
26
+ * Renders content as plain text to ensure visibility
27
+ */
28
+ function renderFallback(content) {
29
+ const lines = content.split('\n');
30
+ return (React.createElement(Box, { flexDirection: "column" }, lines.map((line, index) => (React.createElement(Text, { key: index }, line)))));
31
+ }
6
32
  export default function MarkdownRenderer({ content }) {
7
- // Use cli-markdown for elegant markdown rendering with syntax highlighting
8
- // The patched highlight function will gracefully handle unknown languages
9
- const rendered = cliMarkdown(content);
10
- // Split into lines and render each separately
11
- // This prevents Ink's Text component from creating mysterious whitespace
12
- // when handling multi-line content with \n characters
13
- const lines = rendered.split('\n');
14
- // Safety check: prevent rendering issues with excessively long output
15
- if (lines.length > 500) {
16
- logger.warn('[MarkdownRenderer] Rendered output has too many lines', {
17
- totalLines: lines.length,
18
- truncatedTo: 500,
33
+ // Use marked + marked-terminal for elegant markdown rendering with syntax highlighting
34
+ // marked provides better stability and cross-platform support
35
+ try {
36
+ // Stage 1: Sanitize content to prevent invalid list numbering
37
+ const sanitizedContent = sanitizeMarkdownContent(content);
38
+ // Stage 2: Render with marked
39
+ const rendered = marked.parse(sanitizedContent);
40
+ // Split into lines and render each separately
41
+ // This prevents Ink's Text component from creating mysterious whitespace
42
+ // when handling multi-line content with \n characters
43
+ const lines = rendered.split('\n');
44
+ // Safety check: prevent rendering issues with excessively long output
45
+ if (lines.length > 500) {
46
+ logger.warn('[MarkdownRenderer] Rendered output has too many lines', {
47
+ totalLines: lines.length,
48
+ truncatedTo: 500,
49
+ });
50
+ return (React.createElement(Box, { flexDirection: "column" }, lines.slice(0, 500).map((line, index) => (React.createElement(Text, { key: index }, line)))));
51
+ }
52
+ return (React.createElement(Box, { flexDirection: "column" }, lines.map((line, index) => (React.createElement(Text, { key: index }, line)))));
53
+ }
54
+ catch (error) {
55
+ // Stage 3: Error handling - catch number-to-alphabet errors
56
+ if (error?.message?.includes('Number must be >')) {
57
+ logger.warn('[MarkdownRenderer] Invalid list numbering detected, falling back to plain text', {
58
+ error: error.message,
59
+ });
60
+ return renderFallback(content);
61
+ }
62
+ // Re-throw other errors for debugging
63
+ logger.error('[MarkdownRenderer] Unexpected error during markdown rendering', {
64
+ error: error.message,
65
+ stack: error.stack,
19
66
  });
20
- return (React.createElement(Box, { flexDirection: "column" }, lines.slice(0, 500).map((line, index) => (React.createElement(Text, { key: index }, line)))));
67
+ // Still provide fallback to prevent crash
68
+ return renderFallback(content);
21
69
  }
22
- return (React.createElement(Box, { flexDirection: "column" }, lines.map((line, index) => (React.createElement(Text, { key: index }, line)))));
23
70
  }
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Get or create persistent dev userId
3
+ * The userId is stored in ~/.snow/dev-user-id and persists across sessions
4
+ */
5
+ export declare function getDevUserId(): string;
6
+ /**
7
+ * Check if dev mode is enabled
8
+ */
9
+ export declare function isDevMode(): boolean;
10
+ /**
11
+ * Enable dev mode by setting environment variable
12
+ */
13
+ export declare function enableDevMode(): void;
@@ -0,0 +1,54 @@
1
+ import { createHash, randomUUID } from 'crypto';
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
3
+ import { homedir } from 'os';
4
+ import { join } from 'path';
5
+ const SNOW_DIR = join(homedir(), '.snow');
6
+ const DEV_USER_ID_FILE = join(SNOW_DIR, 'dev-user-id');
7
+ /**
8
+ * Ensure .snow directory exists
9
+ */
10
+ function ensureSnowDir() {
11
+ if (!existsSync(SNOW_DIR)) {
12
+ mkdirSync(SNOW_DIR, { recursive: true });
13
+ }
14
+ }
15
+ /**
16
+ * Generate a persistent dev userId following Anthropic's format
17
+ * Format: user_<hash>_account__session_<uuid>
18
+ */
19
+ function generateDevUserId() {
20
+ const sessionId = randomUUID();
21
+ const hash = createHash('sha256')
22
+ .update(`anthropic_dev_user_${sessionId}`)
23
+ .digest('hex');
24
+ return `user_${hash}_account__session_${sessionId}`;
25
+ }
26
+ /**
27
+ * Get or create persistent dev userId
28
+ * The userId is stored in ~/.snow/dev-user-id and persists across sessions
29
+ */
30
+ export function getDevUserId() {
31
+ ensureSnowDir();
32
+ if (existsSync(DEV_USER_ID_FILE)) {
33
+ const userId = readFileSync(DEV_USER_ID_FILE, 'utf-8').trim();
34
+ if (userId) {
35
+ return userId;
36
+ }
37
+ }
38
+ // Generate new userId if file doesn't exist or is empty
39
+ const userId = generateDevUserId();
40
+ writeFileSync(DEV_USER_ID_FILE, userId, 'utf-8');
41
+ return userId;
42
+ }
43
+ /**
44
+ * Check if dev mode is enabled
45
+ */
46
+ export function isDevMode() {
47
+ return process.env['SNOW_DEV_MODE'] === 'true';
48
+ }
49
+ /**
50
+ * Enable dev mode by setting environment variable
51
+ */
52
+ export function enableDevMode() {
53
+ process.env['SNOW_DEV_MODE'] = 'true';
54
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "snow-ai",
3
- "version": "0.4.4",
3
+ "version": "0.4.5",
4
4
  "description": "Intelligent Command Line Assistant powered by AI",
5
5
  "license": "MIT",
6
6
  "bin": {
@@ -44,8 +44,9 @@
44
44
  "@types/better-sqlite3": "^7.6.13",
45
45
  "better-sqlite3": "^12.4.1",
46
46
  "cli-highlight": "^2.1.11",
47
- "cli-markdown": "^3.5.1",
48
47
  "diff": "^8.0.2",
48
+ "marked": "^15.0.6",
49
+ "marked-terminal": "^7.3.0",
49
50
  "fzf": "^0.5.2",
50
51
  "http-proxy-agent": "^7.0.2",
51
52
  "https-proxy-agent": "^7.0.6",
@@ -66,6 +67,7 @@
66
67
  "devDependencies": {
67
68
  "@sindresorhus/tsconfig": "^3.0.1",
68
69
  "@types/diff": "^7.0.2",
70
+ "@types/marked-terminal": "^6.1.1",
69
71
  "@types/prettier": "^2.7.3",
70
72
  "@types/react": "^18.0.32",
71
73
  "@types/ws": "^8.5.8",