spck 0.3.1

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 (155) hide show
  1. package/.oxlintrc.json +49 -0
  2. package/LICENSE +21 -0
  3. package/README.md +631 -0
  4. package/bin/cli.js +20 -0
  5. package/bin/validate-cwd.js +41 -0
  6. package/dist/config/__tests__/config.test.d.ts +2 -0
  7. package/dist/config/__tests__/config.test.js +262 -0
  8. package/dist/config/__tests__/credentials.test.d.ts +2 -0
  9. package/dist/config/__tests__/credentials.test.js +360 -0
  10. package/dist/config/config.d.ts +33 -0
  11. package/dist/config/config.js +185 -0
  12. package/dist/config/credentials.d.ts +75 -0
  13. package/dist/config/credentials.js +259 -0
  14. package/dist/config/server-selection.d.ts +40 -0
  15. package/dist/config/server-selection.js +130 -0
  16. package/dist/connection/__tests__/firebase-auth.test.d.ts +2 -0
  17. package/dist/connection/__tests__/firebase-auth.test.js +96 -0
  18. package/dist/connection/__tests__/hmac.test.d.ts +2 -0
  19. package/dist/connection/__tests__/hmac.test.js +372 -0
  20. package/dist/connection/auth.d.ts +13 -0
  21. package/dist/connection/auth.js +91 -0
  22. package/dist/connection/firebase-auth.d.ts +40 -0
  23. package/dist/connection/firebase-auth.js +429 -0
  24. package/dist/connection/hmac.d.ts +24 -0
  25. package/dist/connection/hmac.js +109 -0
  26. package/dist/i18n/index.d.ts +25 -0
  27. package/dist/i18n/index.js +101 -0
  28. package/dist/i18n/locales/en.json +313 -0
  29. package/dist/i18n/locales/es.json +302 -0
  30. package/dist/i18n/locales/fr.json +302 -0
  31. package/dist/i18n/locales/id.json +302 -0
  32. package/dist/i18n/locales/ja.json +302 -0
  33. package/dist/i18n/locales/ko.json +302 -0
  34. package/dist/i18n/locales/locales/en.json +309 -0
  35. package/dist/i18n/locales/locales/es.json +302 -0
  36. package/dist/i18n/locales/locales/fr.json +302 -0
  37. package/dist/i18n/locales/locales/id.json +302 -0
  38. package/dist/i18n/locales/locales/ja.json +302 -0
  39. package/dist/i18n/locales/locales/ko.json +302 -0
  40. package/dist/i18n/locales/locales/pt.json +302 -0
  41. package/dist/i18n/locales/locales/zh-Hans.json +302 -0
  42. package/dist/i18n/locales/pt.json +302 -0
  43. package/dist/i18n/locales/zh-Hans.json +302 -0
  44. package/dist/index.d.ts +25 -0
  45. package/dist/index.js +493 -0
  46. package/dist/proxy/ProxyClient.d.ts +125 -0
  47. package/dist/proxy/ProxyClient.js +781 -0
  48. package/dist/proxy/ProxySocketWrapper.d.ts +43 -0
  49. package/dist/proxy/ProxySocketWrapper.js +98 -0
  50. package/dist/proxy/__tests__/ProxyClient.test.d.ts +2 -0
  51. package/dist/proxy/__tests__/ProxyClient.test.js +445 -0
  52. package/dist/proxy/__tests__/ProxySocketWrapper.test.d.ts +2 -0
  53. package/dist/proxy/__tests__/ProxySocketWrapper.test.js +190 -0
  54. package/dist/proxy/__tests__/handshake-validation.test.d.ts +2 -0
  55. package/dist/proxy/__tests__/handshake-validation.test.js +282 -0
  56. package/dist/proxy/__tests__/token-refresh-race.test.d.ts +14 -0
  57. package/dist/proxy/__tests__/token-refresh-race.test.js +173 -0
  58. package/dist/proxy/chunking.d.ts +53 -0
  59. package/dist/proxy/chunking.js +127 -0
  60. package/dist/proxy/handshake-validation.d.ts +21 -0
  61. package/dist/proxy/handshake-validation.js +49 -0
  62. package/dist/rpc/__tests__/router.test.d.ts +2 -0
  63. package/dist/rpc/__tests__/router.test.js +262 -0
  64. package/dist/rpc/router.d.ts +37 -0
  65. package/dist/rpc/router.js +132 -0
  66. package/dist/services/BrowserProxyService.d.ts +13 -0
  67. package/dist/services/BrowserProxyService.js +139 -0
  68. package/dist/services/FilesystemService.d.ts +99 -0
  69. package/dist/services/FilesystemService.js +742 -0
  70. package/dist/services/GitService.d.ts +243 -0
  71. package/dist/services/GitService.js +1439 -0
  72. package/dist/services/SearchService.d.ts +93 -0
  73. package/dist/services/SearchService.js +670 -0
  74. package/dist/services/TerminalService.d.ts +62 -0
  75. package/dist/services/TerminalService.js +337 -0
  76. package/dist/services/__tests__/BrowserProxyService.test.d.ts +2 -0
  77. package/dist/services/__tests__/BrowserProxyService.test.js +145 -0
  78. package/dist/services/__tests__/FilesystemService.test.d.ts +2 -0
  79. package/dist/services/__tests__/FilesystemService.test.js +609 -0
  80. package/dist/services/__tests__/GitService.test.d.ts +2 -0
  81. package/dist/services/__tests__/GitService.test.js +953 -0
  82. package/dist/services/__tests__/SearchService.test.d.ts +2 -0
  83. package/dist/services/__tests__/SearchService.test.js +384 -0
  84. package/dist/services/__tests__/TerminalService.test.d.ts +2 -0
  85. package/dist/services/__tests__/TerminalService.test.js +513 -0
  86. package/dist/setup/wizard.d.ts +10 -0
  87. package/dist/setup/wizard.js +172 -0
  88. package/dist/types.d.ts +196 -0
  89. package/dist/types.js +44 -0
  90. package/dist/utils/__tests__/gitignore.test.d.ts +2 -0
  91. package/dist/utils/__tests__/gitignore.test.js +127 -0
  92. package/dist/utils/gitignore.d.ts +24 -0
  93. package/dist/utils/gitignore.js +77 -0
  94. package/dist/utils/logger.d.ts +96 -0
  95. package/dist/utils/logger.js +456 -0
  96. package/dist/utils/project-dir.d.ts +51 -0
  97. package/dist/utils/project-dir.js +191 -0
  98. package/dist/utils/ripgrep.d.ts +34 -0
  99. package/dist/utils/ripgrep.js +148 -0
  100. package/dist/utils/tool-detection.d.ts +17 -0
  101. package/dist/utils/tool-detection.js +126 -0
  102. package/dist/watcher/FileWatcher.d.ts +10 -0
  103. package/dist/watcher/FileWatcher.js +42 -0
  104. package/package.json +70 -0
  105. package/src/config/__tests__/config.test.ts +318 -0
  106. package/src/config/__tests__/credentials.test.ts +494 -0
  107. package/src/config/config.ts +206 -0
  108. package/src/config/credentials.ts +302 -0
  109. package/src/config/server-selection.ts +150 -0
  110. package/src/connection/__tests__/firebase-auth.test.ts +121 -0
  111. package/src/connection/__tests__/hmac.test.ts +509 -0
  112. package/src/connection/auth.ts +140 -0
  113. package/src/connection/firebase-auth.ts +504 -0
  114. package/src/connection/hmac.ts +139 -0
  115. package/src/i18n/index.ts +119 -0
  116. package/src/i18n/locales/en.json +313 -0
  117. package/src/i18n/locales/es.json +302 -0
  118. package/src/i18n/locales/fr.json +302 -0
  119. package/src/i18n/locales/id.json +302 -0
  120. package/src/i18n/locales/ja.json +302 -0
  121. package/src/i18n/locales/ko.json +302 -0
  122. package/src/i18n/locales/pt.json +302 -0
  123. package/src/i18n/locales/zh-Hans.json +302 -0
  124. package/src/index.ts +542 -0
  125. package/src/proxy/ProxyClient.ts +968 -0
  126. package/src/proxy/ProxySocketWrapper.ts +113 -0
  127. package/src/proxy/__tests__/ProxyClient.test.ts +575 -0
  128. package/src/proxy/__tests__/ProxySocketWrapper.test.ts +251 -0
  129. package/src/proxy/__tests__/handshake-validation.test.ts +367 -0
  130. package/src/proxy/chunking.ts +162 -0
  131. package/src/proxy/handshake-validation.ts +64 -0
  132. package/src/rpc/__tests__/router.test.ts +400 -0
  133. package/src/rpc/router.ts +183 -0
  134. package/src/services/BrowserProxyService.ts +179 -0
  135. package/src/services/FilesystemService.ts +841 -0
  136. package/src/services/GitService.ts +1639 -0
  137. package/src/services/SearchService.ts +809 -0
  138. package/src/services/TerminalService.ts +413 -0
  139. package/src/services/__tests__/BrowserProxyService.test.ts +155 -0
  140. package/src/services/__tests__/FilesystemService.test.ts +1002 -0
  141. package/src/services/__tests__/GitService.test.ts +1552 -0
  142. package/src/services/__tests__/SearchService.test.ts +484 -0
  143. package/src/services/__tests__/TerminalService.test.ts +702 -0
  144. package/src/setup/wizard.ts +242 -0
  145. package/src/types/fossil-delta.d.ts +4 -0
  146. package/src/types.ts +287 -0
  147. package/src/utils/__tests__/gitignore.test.ts +174 -0
  148. package/src/utils/gitignore.ts +91 -0
  149. package/src/utils/logger.ts +578 -0
  150. package/src/utils/project-dir.ts +218 -0
  151. package/src/utils/ripgrep.ts +180 -0
  152. package/src/utils/tool-detection.ts +141 -0
  153. package/src/watcher/FileWatcher.ts +53 -0
  154. package/tsconfig.json +24 -0
  155. package/vitest.config.ts +19 -0
@@ -0,0 +1,191 @@
1
+ /**
2
+ * Project directory management with symlink support
3
+ * .spck-editor/ is a regular directory with .spck-editor/config symlinked to ~/.spck-editor/projects/{id}
4
+ * This prevents accidental git commits of secrets while avoiding cross-device link errors
5
+ */
6
+ import * as fs from 'fs';
7
+ import * as path from 'path';
8
+ import * as os from 'os';
9
+ import * as crypto from 'crypto';
10
+ import { t } from '../i18n/index.js';
11
+ const PROJECT_DIR_NAME = '.spck-editor';
12
+ const CONFIG_SUBDIR_NAME = 'config';
13
+ /**
14
+ * Get the home base directory for projects
15
+ * Called at runtime instead of module load time to avoid issues in test environments
16
+ */
17
+ function getHomeBaseDir() {
18
+ return path.join(os.homedir(), '.spck-editor', 'projects');
19
+ }
20
+ /**
21
+ * Generate a consistent project ID from the project root path
22
+ */
23
+ export function generateProjectId(projectRoot) {
24
+ // Resolve to absolute path and normalize
25
+ const absolutePath = path.resolve(projectRoot);
26
+ // Generate SHA256 hash of the absolute path
27
+ const hash = crypto.createHash('sha256').update(absolutePath).digest('hex');
28
+ // Use first 16 characters for readability (still unique enough)
29
+ return hash.substring(0, 16);
30
+ }
31
+ /**
32
+ * Get the home directory location for a project's data
33
+ */
34
+ export function getProjectDataPath(projectRoot) {
35
+ const projectId = generateProjectId(projectRoot);
36
+ return path.join(getHomeBaseDir(), projectId);
37
+ }
38
+ /**
39
+ * Get the .spck-editor directory path in the project
40
+ */
41
+ export function getProjectDirPath(projectRoot) {
42
+ return path.join(projectRoot, PROJECT_DIR_NAME);
43
+ }
44
+ /**
45
+ * Get the config symlink path (.spck-editor/config)
46
+ */
47
+ export function getConfigSymlinkPath(projectRoot) {
48
+ return path.join(projectRoot, PROJECT_DIR_NAME, CONFIG_SUBDIR_NAME);
49
+ }
50
+ /**
51
+ * @deprecated Use getConfigSymlinkPath() instead
52
+ * Legacy compatibility - returns config symlink path
53
+ */
54
+ export function getProjectSymlinkPath(projectRoot) {
55
+ return getConfigSymlinkPath(projectRoot);
56
+ }
57
+ /**
58
+ * Check if the project directory exists and is properly set up
59
+ */
60
+ export function isProjectDirSetup(projectRoot) {
61
+ const projectDir = getProjectDirPath(projectRoot);
62
+ const configSymlink = getConfigSymlinkPath(projectRoot);
63
+ const dataPath = getProjectDataPath(projectRoot);
64
+ // Check if .spck-editor directory exists
65
+ if (!fs.existsSync(projectDir)) {
66
+ return false;
67
+ }
68
+ // Check if .spck-editor is a directory (not old-style symlink)
69
+ try {
70
+ const stats = fs.lstatSync(projectDir);
71
+ if (stats.isSymbolicLink()) {
72
+ // Old structure - needs migration
73
+ return false;
74
+ }
75
+ if (!stats.isDirectory()) {
76
+ return false;
77
+ }
78
+ }
79
+ catch (error) {
80
+ return false;
81
+ }
82
+ // Check if config symlink exists
83
+ if (!fs.existsSync(configSymlink)) {
84
+ return false;
85
+ }
86
+ // Check if config is a symlink
87
+ try {
88
+ const stats = fs.lstatSync(configSymlink);
89
+ if (!stats.isSymbolicLink()) {
90
+ return false;
91
+ }
92
+ }
93
+ catch (error) {
94
+ return false;
95
+ }
96
+ // Check if data directory exists
97
+ if (!fs.existsSync(dataPath)) {
98
+ return false;
99
+ }
100
+ return true;
101
+ }
102
+ /**
103
+ * Setup the project directory with config symlink
104
+ * Creates ~/.spck-editor/projects/{project_id}/ and symlinks .spck-editor/config to it
105
+ */
106
+ export function setupProjectDir(projectRoot) {
107
+ const projectDir = getProjectDirPath(projectRoot);
108
+ const configSymlink = getConfigSymlinkPath(projectRoot);
109
+ const dataPath = getProjectDataPath(projectRoot);
110
+ const homeBaseDir = getHomeBaseDir();
111
+ // Ensure home base directory exists
112
+ if (!fs.existsSync(homeBaseDir)) {
113
+ fs.mkdirSync(homeBaseDir, { recursive: true, mode: 0o700 });
114
+ }
115
+ // Ensure project data directory exists
116
+ if (!fs.existsSync(dataPath)) {
117
+ fs.mkdirSync(dataPath, { recursive: true, mode: 0o700 });
118
+ }
119
+ // Create .spck-editor directory if it doesn't exist
120
+ if (!fs.existsSync(projectDir)) {
121
+ fs.mkdirSync(projectDir, { mode: 0o700 });
122
+ }
123
+ // Create subdirectories (.tmp, .trash, logs)
124
+ const subdirs = ['.tmp', '.trash', 'logs'];
125
+ for (const subdir of subdirs) {
126
+ const subdirPath = path.join(projectDir, subdir);
127
+ if (!fs.existsSync(subdirPath)) {
128
+ fs.mkdirSync(subdirPath, { mode: 0o700 });
129
+ }
130
+ }
131
+ // Create config symlink if it doesn't exist
132
+ if (!fs.existsSync(configSymlink)) {
133
+ fs.symlinkSync(dataPath, configSymlink, 'dir');
134
+ }
135
+ else {
136
+ // Verify existing symlink points to correct location
137
+ const stats = fs.lstatSync(configSymlink);
138
+ if (stats.isSymbolicLink()) {
139
+ const target = fs.readlinkSync(configSymlink);
140
+ if (target !== dataPath) {
141
+ // Points to wrong location - remove and recreate
142
+ fs.unlinkSync(configSymlink);
143
+ fs.symlinkSync(dataPath, configSymlink, 'dir');
144
+ }
145
+ }
146
+ }
147
+ console.log(`✅ ${t('projectDir.configured')}`);
148
+ console.log(` ${t('projectDir.directory', { path: projectDir })}`);
149
+ console.log(` ${t('projectDir.configLink', { symlink: configSymlink, dataPath })}\n`);
150
+ }
151
+ /**
152
+ * Ensure project directory is set up, creating if needed
153
+ */
154
+ export function ensureProjectDir(projectRoot) {
155
+ if (!isProjectDirSetup(projectRoot)) {
156
+ setupProjectDir(projectRoot);
157
+ }
158
+ }
159
+ /**
160
+ * Get the absolute path to a file within the project directory
161
+ * Files go in .spck-editor/{filename} (local) or .spck-editor/config/{filename} (symlinked)
162
+ * Config files (like connection-settings.json) go in the config subdirectory
163
+ */
164
+ export function getProjectFilePath(projectRoot, filename) {
165
+ const projectDir = getProjectDirPath(projectRoot);
166
+ // Config files go in the symlinked config directory
167
+ const configFiles = ['connection-settings.json', '.credentials.json', 'spck-cli.config.json'];
168
+ if (configFiles.includes(filename)) {
169
+ const configSymlink = getConfigSymlinkPath(projectRoot);
170
+ return path.join(configSymlink, filename);
171
+ }
172
+ // All other files go in the local .spck-editor directory
173
+ return path.join(projectDir, filename);
174
+ }
175
+ /**
176
+ * Remove project directory and all associated data
177
+ * WARNING: This deletes the data directory in home and the local .spck-editor directory
178
+ */
179
+ export function removeProjectDir(projectRoot) {
180
+ const projectDir = getProjectDirPath(projectRoot);
181
+ const dataPath = getProjectDataPath(projectRoot);
182
+ // Remove .spck-editor directory if exists (includes config symlink and local files)
183
+ if (fs.existsSync(projectDir)) {
184
+ fs.rmSync(projectDir, { recursive: true, force: true });
185
+ }
186
+ // Remove data directory in home if exists
187
+ if (fs.existsSync(dataPath)) {
188
+ fs.rmSync(dataPath, { recursive: true, force: true });
189
+ }
190
+ }
191
+ //# sourceMappingURL=project-dir.js.map
@@ -0,0 +1,34 @@
1
+ /**
2
+ * Ripgrep wrapper utility
3
+ *
4
+ * Uses system-installed ripgrep (rg) if available
5
+ */
6
+ /**
7
+ * Get the ripgrep command name for the current platform
8
+ */
9
+ export declare function getRipgrepCommand(): string;
10
+ /**
11
+ * Check if ripgrep is available on the system PATH
12
+ */
13
+ export declare function isRipgrepAvailable(): Promise<boolean>;
14
+ /**
15
+ * Execute ripgrep search (buffered - waits for completion)
16
+ */
17
+ export declare function executeRipgrep(path: string, args: string[], options?: {
18
+ timeout?: number;
19
+ }): Promise<{
20
+ stdout: string;
21
+ stderr: string;
22
+ exitCode: number;
23
+ }>;
24
+ /**
25
+ * Execute ripgrep search with streaming output
26
+ * Calls onLine for each line of output as it arrives
27
+ */
28
+ export declare function executeRipgrepStream(path: string, args: string[], options: {
29
+ timeout?: number;
30
+ onLine: (line: string) => void;
31
+ }): Promise<{
32
+ exitCode: number;
33
+ }>;
34
+ //# sourceMappingURL=ripgrep.d.ts.map
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Ripgrep wrapper utility
3
+ *
4
+ * Uses system-installed ripgrep (rg) if available
5
+ */
6
+ import { spawn } from 'child_process';
7
+ /**
8
+ * Get the ripgrep command name for the current platform
9
+ */
10
+ export function getRipgrepCommand() {
11
+ // On Windows, try both 'rg.exe' and 'rg'
12
+ // On Unix-like systems, use 'rg'
13
+ return process.platform === 'win32' ? 'rg.exe' : 'rg';
14
+ }
15
+ /**
16
+ * Check if ripgrep is available on the system PATH
17
+ */
18
+ export async function isRipgrepAvailable() {
19
+ const rgCommand = getRipgrepCommand();
20
+ // Try to execute ripgrep --version to verify it works
21
+ return new Promise((resolve) => {
22
+ try {
23
+ const proc = spawn(rgCommand, ['--version'], {
24
+ cwd: process.cwd(),
25
+ stdio: 'ignore',
26
+ shell: false
27
+ });
28
+ proc.on('error', () => resolve(false));
29
+ proc.on('close', (code) => resolve(code === 0));
30
+ }
31
+ catch (err) {
32
+ resolve(false);
33
+ }
34
+ });
35
+ }
36
+ /**
37
+ * Execute ripgrep search (buffered - waits for completion)
38
+ */
39
+ export async function executeRipgrep(path, args, options) {
40
+ const rgCommand = getRipgrepCommand();
41
+ return new Promise((resolve, reject) => {
42
+ const proc = spawn(rgCommand, args, {
43
+ cwd: path,
44
+ stdio: 'pipe',
45
+ shell: false
46
+ });
47
+ let stdout = '';
48
+ let stderr = '';
49
+ let killed = false;
50
+ // Set timeout if specified
51
+ const timeout = options?.timeout;
52
+ let timeoutId;
53
+ if (timeout) {
54
+ timeoutId = setTimeout(() => {
55
+ killed = true;
56
+ proc.kill();
57
+ reject(new Error(`Ripgrep execution timed out after ${timeout}ms`));
58
+ }, timeout);
59
+ }
60
+ proc.stdout?.on('data', (data) => {
61
+ stdout += data.toString();
62
+ });
63
+ proc.stderr?.on('data', (data) => {
64
+ stderr += data.toString();
65
+ });
66
+ proc.on('error', (error) => {
67
+ if (timeoutId)
68
+ clearTimeout(timeoutId);
69
+ if (!killed) {
70
+ reject(error);
71
+ }
72
+ });
73
+ proc.on('close', (code) => {
74
+ if (timeoutId)
75
+ clearTimeout(timeoutId);
76
+ if (!killed) {
77
+ resolve({
78
+ stdout,
79
+ stderr,
80
+ exitCode: code ?? -1
81
+ });
82
+ }
83
+ });
84
+ });
85
+ }
86
+ /**
87
+ * Execute ripgrep search with streaming output
88
+ * Calls onLine for each line of output as it arrives
89
+ */
90
+ export async function executeRipgrepStream(path, args, options) {
91
+ const rgCommand = getRipgrepCommand();
92
+ return new Promise((resolve, reject) => {
93
+ const proc = spawn(rgCommand, args, {
94
+ cwd: path,
95
+ stdio: 'pipe',
96
+ shell: false
97
+ });
98
+ let killed = false;
99
+ let buffer = '';
100
+ // Set timeout if specified
101
+ const timeout = options.timeout;
102
+ let timeoutId;
103
+ if (timeout) {
104
+ timeoutId = setTimeout(() => {
105
+ killed = true;
106
+ proc.kill();
107
+ reject(new Error(`Ripgrep execution timed out after ${timeout}ms`));
108
+ }, timeout);
109
+ }
110
+ // Process stdout line by line as it arrives
111
+ proc.stdout?.on('data', (data) => {
112
+ buffer += data.toString();
113
+ const lines = buffer.split('\n');
114
+ // Keep the last incomplete line in the buffer
115
+ buffer = lines.pop() || '';
116
+ // Process complete lines
117
+ for (const line of lines) {
118
+ if (line.trim()) {
119
+ options.onLine(line);
120
+ }
121
+ }
122
+ });
123
+ proc.stderr?.on('data', (_data) => {
124
+ // Ignore stderr for now
125
+ });
126
+ proc.on('error', (error) => {
127
+ if (timeoutId)
128
+ clearTimeout(timeoutId);
129
+ if (!killed) {
130
+ reject(error);
131
+ }
132
+ });
133
+ proc.on('close', (code) => {
134
+ if (timeoutId)
135
+ clearTimeout(timeoutId);
136
+ // Process any remaining buffered data
137
+ if (buffer.trim()) {
138
+ options.onLine(buffer);
139
+ }
140
+ if (!killed) {
141
+ resolve({
142
+ exitCode: code ?? -1
143
+ });
144
+ }
145
+ });
146
+ });
147
+ }
148
+ //# sourceMappingURL=ripgrep.js.map
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Tool detection for git and ripgrep
3
+ * Checks if required tools are installed and displays warnings
4
+ */
5
+ import { ToolDetectionResult } from '../types.js';
6
+ /**
7
+ * Detect available tools (git and ripgrep)
8
+ */
9
+ export declare function detectTools(options?: {
10
+ disableGit?: boolean;
11
+ disableRipgrep?: boolean;
12
+ }): Promise<ToolDetectionResult>;
13
+ /**
14
+ * Display feature summary based on detected tools
15
+ */
16
+ export declare function displayFeatureSummary(tools: ToolDetectionResult, terminalEnabled: boolean, userAuthEnabled?: boolean, browserProxyEnabled?: boolean): void;
17
+ //# sourceMappingURL=tool-detection.d.ts.map
@@ -0,0 +1,126 @@
1
+ /**
2
+ * Tool detection for git and ripgrep
3
+ * Checks if required tools are installed and displays warnings
4
+ */
5
+ import { exec } from 'child_process';
6
+ import { promisify } from 'util';
7
+ import { t } from '../i18n/index.js';
8
+ const execAsync = promisify(exec);
9
+ /**
10
+ * Check if a command is available
11
+ */
12
+ async function isCommandAvailable(command) {
13
+ try {
14
+ await execAsync(`${command} --version`);
15
+ return true;
16
+ }
17
+ catch {
18
+ return false;
19
+ }
20
+ }
21
+ /**
22
+ * Detect available tools (git and ripgrep)
23
+ */
24
+ export async function detectTools(options) {
25
+ console.log(`\n=== ${t('tools.title')} ===\n`);
26
+ const result = {
27
+ git: false,
28
+ ripgrep: false
29
+ };
30
+ // Check Git (unless force-disabled for development)
31
+ if (options?.disableGit) {
32
+ console.log(`⚠️ ${t('tools.gitForceDisabled')}`);
33
+ }
34
+ else {
35
+ result.git = await isCommandAvailable('git');
36
+ if (result.git) {
37
+ try {
38
+ const { stdout } = await execAsync('git --version');
39
+ console.log(`✅ ${t('tools.gitDetected', { version: stdout.trim() })}`);
40
+ }
41
+ catch {
42
+ console.log(`✅ ${t('tools.gitDetectedShort')}`);
43
+ }
44
+ }
45
+ else {
46
+ console.warn(`⚠️ ${t('tools.gitNotDetected')}`);
47
+ console.warn(` ${t('tools.gitDisabledHint')}`);
48
+ console.warn(` ${t('tools.gitInstallHint')}`);
49
+ console.warn(` ${t('tools.gitInstallUrl')}\n`);
50
+ }
51
+ }
52
+ // Check Ripgrep (unless force-disabled for development)
53
+ if (options?.disableRipgrep) {
54
+ console.log(`⚠️ ${t('tools.ripgrepForceDisabled')}`);
55
+ }
56
+ else {
57
+ result.ripgrep = await isCommandAvailable('rg');
58
+ if (result.ripgrep) {
59
+ try {
60
+ const { stdout } = await execAsync('rg --version');
61
+ const firstLine = stdout.split('\n')[0];
62
+ console.log(`✅ ${t('tools.ripgrepDetected', { version: firstLine })}`);
63
+ }
64
+ catch {
65
+ console.log(`✅ ${t('tools.ripgrepDetectedShort')}`);
66
+ }
67
+ }
68
+ else {
69
+ console.warn(`⚠️ ${t('tools.ripgrepNotDetected')}`);
70
+ console.warn(` ${t('tools.ripgrepDisabledHint')}`);
71
+ console.warn(` ${t('tools.ripgrepInstallHint')}`);
72
+ console.warn(` ${t('tools.ripgrepInstallUrl')}\n`);
73
+ }
74
+ }
75
+ return result;
76
+ }
77
+ /**
78
+ * Display feature summary based on detected tools
79
+ */
80
+ export function displayFeatureSummary(tools, terminalEnabled, userAuthEnabled, browserProxyEnabled) {
81
+ console.log(`\n=== ${t('features.title')} ===\n`);
82
+ const features = [];
83
+ // Always available
84
+ features.push(`✅ ${t('features.filesystem')}`);
85
+ // Conditional features
86
+ if (tools.git) {
87
+ features.push(`✅ ${t('features.gitEnabled')}`);
88
+ }
89
+ else {
90
+ features.push(`❌ ${t('features.gitDisabled')}`);
91
+ }
92
+ if (tools.ripgrep) {
93
+ features.push(`✅ ${t('features.searchFast')}`);
94
+ }
95
+ else {
96
+ features.push(`⚠️ ${t('features.searchBasic')}`);
97
+ }
98
+ if (terminalEnabled) {
99
+ features.push(`✅ ${t('features.terminalEnabled')}`);
100
+ }
101
+ else {
102
+ features.push(`❌ ${t('features.terminalDisabled')}`);
103
+ }
104
+ if (browserProxyEnabled !== false) {
105
+ features.push(`✅ ${t('features.browserProxyEnabled')}`);
106
+ }
107
+ else {
108
+ features.push(`❌ ${t('features.browserProxyDisabled')}`);
109
+ }
110
+ features.forEach(feature => console.log(` ${feature}`));
111
+ // Display authentication mode
112
+ console.log(`\n=== ${t('features.securityTitle')} ===\n`);
113
+ if (userAuthEnabled) {
114
+ console.log(` 🔐 ${t('features.userAuthEnabled')}`);
115
+ console.log(` → ${t('features.userAuthEnabledHint1')}`);
116
+ console.log(` → ${t('features.userAuthEnabledHint2')}`);
117
+ console.log(` → ${t('features.userAuthEnabledHint3')}\n`);
118
+ }
119
+ else {
120
+ console.log(` 🔓 ${t('features.userAuthDisabled')}`);
121
+ console.log(` → ${t('features.userAuthDisabledHint1')}`);
122
+ console.log(` → ${t('features.userAuthDisabledHint2')}`);
123
+ console.log(` → ${t('features.userAuthDisabledHint3')}\n`);
124
+ }
125
+ }
126
+ //# sourceMappingURL=tool-detection.js.map
@@ -0,0 +1,10 @@
1
+ /**
2
+ * File system watcher using chokidar
3
+ */
4
+ import { EventEmitter } from 'events';
5
+ export declare class FileWatcher extends EventEmitter {
6
+ private watcher;
7
+ constructor(rootPath: string, ignorePatterns: string[]);
8
+ close(): void;
9
+ }
10
+ //# sourceMappingURL=FileWatcher.d.ts.map
@@ -0,0 +1,42 @@
1
+ /**
2
+ * File system watcher using chokidar
3
+ */
4
+ import * as chokidar from 'chokidar';
5
+ import { EventEmitter } from 'events';
6
+ export class FileWatcher extends EventEmitter {
7
+ constructor(rootPath, ignorePatterns) {
8
+ super();
9
+ this.watcher = chokidar.watch(rootPath, {
10
+ ignored: ignorePatterns,
11
+ persistent: true,
12
+ ignoreInitial: true,
13
+ awaitWriteFinish: {
14
+ stabilityThreshold: 100,
15
+ pollInterval: 50,
16
+ },
17
+ });
18
+ this.watcher.on('change', (path, stats) => {
19
+ this.emit('change', path, stats?.mtimeMs);
20
+ });
21
+ this.watcher.on('unlink', (path) => {
22
+ this.emit('removed', path);
23
+ });
24
+ this.watcher.on('add', (path, stats) => {
25
+ this.emit('added', path, stats?.mtimeMs);
26
+ });
27
+ this.watcher.on('unlinkDir', (path) => {
28
+ this.emit('removed', path);
29
+ });
30
+ this.watcher.on('addDir', (path) => {
31
+ this.emit('added', path);
32
+ });
33
+ this.watcher.on('error', (error) => {
34
+ console.error('File watcher error:', error);
35
+ this.emit('error', error);
36
+ });
37
+ }
38
+ close() {
39
+ this.watcher.close();
40
+ }
41
+ }
42
+ //# sourceMappingURL=FileWatcher.js.map
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "spck",
3
+ "version": "0.3.1",
4
+ "description": "CLI tool for Spck Editor - provides remote filesystem, git, browser proxing, and terminal access",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "spck": "./bin/cli.js"
9
+ },
10
+ "scripts": {
11
+ "build": "tsc && cp src/i18n/locales/*.json dist/i18n/locales/",
12
+ "start": "node dist/index.js",
13
+ "dev": "ts-node src/index.ts",
14
+ "watch": "tsc --watch",
15
+ "test": "vitest run",
16
+ "test:watch": "vitest",
17
+ "test:coverage": "vitest run --coverage",
18
+ "lint": "oxlint",
19
+ "lint:fix": "oxlint --fix",
20
+ "prepublishOnly": "npm run lint && npm test",
21
+ "prepare": "npm run build"
22
+ },
23
+ "keywords": [
24
+ "spck",
25
+ "editor",
26
+ "remote",
27
+ "filesystem",
28
+ "git",
29
+ "terminal"
30
+ ],
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/spck-io/spck-cli"
34
+ },
35
+ "author": "Spck Editor",
36
+ "license": "MIT",
37
+ "dependencies": {
38
+ "@xterm/addon-serialize": "0.13.0",
39
+ "@xterm/headless": "5.5.0",
40
+ "chalk": "4.1.2",
41
+ "chokidar": "3.6.0",
42
+ "default-shell": "2.2.0",
43
+ "fossil-delta": "1.0.2",
44
+ "jsonwebtoken": "9.0.3",
45
+ "node-fetch": "3.3.2",
46
+ "node-pty": "1.2.0-beta.10",
47
+ "open": "8.4.2",
48
+ "os-locale": "8.0.0",
49
+ "qrcode-terminal": "0.12.0",
50
+ "socket.io-client": "4.8.3",
51
+ "write-file-atomic": "7.0.0",
52
+ "yargs": "17.7.2"
53
+ },
54
+ "devDependencies": {
55
+ "@types/jsonwebtoken": "^9.0.5",
56
+ "@types/node": "^20.0.0",
57
+ "@types/node-fetch": "^2.6.11",
58
+ "@types/qrcode-terminal": "^0.12.2",
59
+ "@types/socket.io-client": "^3.0.0",
60
+ "@types/write-file-atomic": "^4.0.3",
61
+ "@types/ws": "^8.5.10",
62
+ "@types/yargs": "^17.0.32",
63
+ "ts-node": "^10.9.2",
64
+ "vitest": "^3.0.0",
65
+ "typescript": "^5.3.0"
66
+ },
67
+ "engines": {
68
+ "node": ">=18.0.0"
69
+ }
70
+ }