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.
- package/dist/api/anthropic.js +9 -0
- package/dist/cli.d.ts +1 -1
- package/dist/cli.js +14 -3
- package/dist/ui/components/MarkdownRenderer.js +63 -16
- package/dist/utils/devMode.d.ts +13 -0
- package/dist/utils/devMode.js +54 -0
- package/package.json +4 -2
package/dist/api/anthropic.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
4
|
-
import
|
|
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
|
|
8
|
-
//
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
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
|
-
|
|
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.
|
|
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",
|