terminal-quest 1.0.0

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 (131) hide show
  1. package/LICENSE +21 -0
  2. package/bin/terminal-quest.js +2 -0
  3. package/dist/App.d.ts +2 -0
  4. package/dist/App.js +33 -0
  5. package/dist/components/HintBar.d.ts +9 -0
  6. package/dist/components/HintBar.js +11 -0
  7. package/dist/components/MenuItem.d.ts +9 -0
  8. package/dist/components/MenuItem.js +9 -0
  9. package/dist/components/ObjectivePanel.d.ts +8 -0
  10. package/dist/components/ObjectivePanel.js +10 -0
  11. package/dist/components/ProgressBar.d.ts +8 -0
  12. package/dist/components/ProgressBar.js +10 -0
  13. package/dist/components/TerminalOutput.d.ts +11 -0
  14. package/dist/components/TerminalOutput.js +25 -0
  15. package/dist/components/TerminalPrompt.d.ts +10 -0
  16. package/dist/components/TerminalPrompt.js +46 -0
  17. package/dist/data/commands-meta.d.ts +17 -0
  18. package/dist/data/commands-meta.js +256 -0
  19. package/dist/data/stories/00-beginner-pc.d.ts +3 -0
  20. package/dist/data/stories/00-beginner-pc.js +841 -0
  21. package/dist/data/stories/01-first-server.d.ts +3 -0
  22. package/dist/data/stories/01-first-server.js +364 -0
  23. package/dist/data/stories/02-messy-project.d.ts +3 -0
  24. package/dist/data/stories/02-messy-project.js +433 -0
  25. package/dist/data/stories/03-log-detective.d.ts +3 -0
  26. package/dist/data/stories/03-log-detective.js +291 -0
  27. package/dist/data/stories/04-deploy-day.d.ts +3 -0
  28. package/dist/data/stories/04-deploy-day.js +337 -0
  29. package/dist/data/stories/05-git-incident.d.ts +3 -0
  30. package/dist/data/stories/05-git-incident.js +534 -0
  31. package/dist/data/stories/06-pipe-master.d.ts +3 -0
  32. package/dist/data/stories/06-pipe-master.js +377 -0
  33. package/dist/data/stories/07-dangerous-commands.d.ts +3 -0
  34. package/dist/data/stories/07-dangerous-commands.js +411 -0
  35. package/dist/data/stories/index.d.ts +4 -0
  36. package/dist/data/stories/index.js +14 -0
  37. package/dist/data/stories/k1-treasure-hunt.d.ts +3 -0
  38. package/dist/data/stories/k1-treasure-hunt.js +815 -0
  39. package/dist/data/types.d.ts +97 -0
  40. package/dist/data/types.js +2 -0
  41. package/dist/engine/Achievements.d.ts +5 -0
  42. package/dist/engine/Achievements.js +93 -0
  43. package/dist/engine/CommandHandler.d.ts +17 -0
  44. package/dist/engine/CommandHandler.js +177 -0
  45. package/dist/engine/HintEngine.d.ts +10 -0
  46. package/dist/engine/HintEngine.js +26 -0
  47. package/dist/engine/MissionEngine.d.ts +17 -0
  48. package/dist/engine/MissionEngine.js +84 -0
  49. package/dist/engine/TabCompletion.d.ts +14 -0
  50. package/dist/engine/TabCompletion.js +93 -0
  51. package/dist/engine/VirtualFS.d.ts +33 -0
  52. package/dist/engine/VirtualFS.js +276 -0
  53. package/dist/engine/commands/cat.d.ts +4 -0
  54. package/dist/engine/commands/cat.js +18 -0
  55. package/dist/engine/commands/cd.d.ts +4 -0
  56. package/dist/engine/commands/cd.js +12 -0
  57. package/dist/engine/commands/chmod.d.ts +4 -0
  58. package/dist/engine/commands/chmod.js +98 -0
  59. package/dist/engine/commands/clear.d.ts +4 -0
  60. package/dist/engine/commands/clear.js +4 -0
  61. package/dist/engine/commands/cp.d.ts +4 -0
  62. package/dist/engine/commands/cp.js +26 -0
  63. package/dist/engine/commands/cut.d.ts +4 -0
  64. package/dist/engine/commands/cut.js +76 -0
  65. package/dist/engine/commands/echo.d.ts +4 -0
  66. package/dist/engine/commands/echo.js +4 -0
  67. package/dist/engine/commands/find.d.ts +4 -0
  68. package/dist/engine/commands/find.js +60 -0
  69. package/dist/engine/commands/git.d.ts +4 -0
  70. package/dist/engine/commands/git.js +510 -0
  71. package/dist/engine/commands/grep.d.ts +4 -0
  72. package/dist/engine/commands/grep.js +127 -0
  73. package/dist/engine/commands/head.d.ts +4 -0
  74. package/dist/engine/commands/head.js +59 -0
  75. package/dist/engine/commands/help.d.ts +4 -0
  76. package/dist/engine/commands/help.js +32 -0
  77. package/dist/engine/commands/hint.d.ts +4 -0
  78. package/dist/engine/commands/hint.js +4 -0
  79. package/dist/engine/commands/index.d.ts +8 -0
  80. package/dist/engine/commands/index.js +51 -0
  81. package/dist/engine/commands/ls.d.ts +4 -0
  82. package/dist/engine/commands/ls.js +50 -0
  83. package/dist/engine/commands/man.d.ts +4 -0
  84. package/dist/engine/commands/man.js +51 -0
  85. package/dist/engine/commands/mkdir.d.ts +4 -0
  86. package/dist/engine/commands/mkdir.js +31 -0
  87. package/dist/engine/commands/mv.d.ts +4 -0
  88. package/dist/engine/commands/mv.js +15 -0
  89. package/dist/engine/commands/pwd.d.ts +4 -0
  90. package/dist/engine/commands/pwd.js +4 -0
  91. package/dist/engine/commands/rm.d.ts +4 -0
  92. package/dist/engine/commands/rm.js +49 -0
  93. package/dist/engine/commands/sort.d.ts +4 -0
  94. package/dist/engine/commands/sort.js +100 -0
  95. package/dist/engine/commands/tail.d.ts +4 -0
  96. package/dist/engine/commands/tail.js +59 -0
  97. package/dist/engine/commands/touch.d.ts +4 -0
  98. package/dist/engine/commands/touch.js +18 -0
  99. package/dist/engine/commands/uniq.d.ts +4 -0
  100. package/dist/engine/commands/uniq.js +61 -0
  101. package/dist/engine/commands/wc.d.ts +4 -0
  102. package/dist/engine/commands/wc.js +67 -0
  103. package/dist/index.d.ts +3 -0
  104. package/dist/index.js +6 -0
  105. package/dist/screens/MissionBriefScreen.d.ts +9 -0
  106. package/dist/screens/MissionBriefScreen.js +27 -0
  107. package/dist/screens/MissionCompleteScreen.d.ts +9 -0
  108. package/dist/screens/MissionCompleteScreen.js +30 -0
  109. package/dist/screens/ProgressScreen.d.ts +8 -0
  110. package/dist/screens/ProgressScreen.js +24 -0
  111. package/dist/screens/SettingsScreen.d.ts +8 -0
  112. package/dist/screens/SettingsScreen.js +45 -0
  113. package/dist/screens/StorySelectScreen.d.ts +8 -0
  114. package/dist/screens/StorySelectScreen.js +81 -0
  115. package/dist/screens/TerminalScreen.d.ts +12 -0
  116. package/dist/screens/TerminalScreen.js +150 -0
  117. package/dist/screens/TitleScreen.d.ts +7 -0
  118. package/dist/screens/TitleScreen.js +27 -0
  119. package/dist/state/GameState.d.ts +8 -0
  120. package/dist/state/GameState.js +12 -0
  121. package/dist/state/ProgressStore.d.ts +9 -0
  122. package/dist/state/ProgressStore.js +45 -0
  123. package/dist/state/useGameState.d.ts +11 -0
  124. package/dist/state/useGameState.js +92 -0
  125. package/dist/utils/ascii-art.d.ts +4 -0
  126. package/dist/utils/ascii-art.js +22 -0
  127. package/dist/utils/colors.d.ts +17 -0
  128. package/dist/utils/colors.js +17 -0
  129. package/dist/utils/text.d.ts +4 -0
  130. package/dist/utils/text.js +28 -0
  131. package/package.json +58 -0
@@ -0,0 +1,276 @@
1
+ export class VirtualFS {
2
+ root;
3
+ cwd;
4
+ constructor(initialFS, initialCwd = '/') {
5
+ this.root = this.deepClone(initialFS);
6
+ this.cwd = initialCwd;
7
+ }
8
+ deepClone(node) {
9
+ if (node.type === 'file') {
10
+ return { type: 'file', content: node.content ?? '', permissions: node.permissions };
11
+ }
12
+ const children = {};
13
+ if (node.children) {
14
+ for (const [name, child] of Object.entries(node.children)) {
15
+ children[name] = this.deepClone(child);
16
+ }
17
+ }
18
+ return { type: 'directory', children, permissions: node.permissions };
19
+ }
20
+ getCwd() {
21
+ return this.cwd;
22
+ }
23
+ resolvePath(path) {
24
+ if (path.startsWith('/')) {
25
+ return this.normalizePath(path);
26
+ }
27
+ if (this.cwd === '/') {
28
+ return this.normalizePath('/' + path);
29
+ }
30
+ return this.normalizePath(this.cwd + '/' + path);
31
+ }
32
+ normalizePath(path) {
33
+ const parts = path.split('/').filter(Boolean);
34
+ const resolved = [];
35
+ for (const part of parts) {
36
+ if (part === '.')
37
+ continue;
38
+ if (part === '..') {
39
+ resolved.pop();
40
+ }
41
+ else {
42
+ resolved.push(part);
43
+ }
44
+ }
45
+ return '/' + resolved.join('/');
46
+ }
47
+ getNode(path) {
48
+ const resolved = this.resolvePath(path);
49
+ if (resolved === '/')
50
+ return this.root;
51
+ const parts = resolved.split('/').filter(Boolean);
52
+ let current = this.root;
53
+ for (const part of parts) {
54
+ if (current.type !== 'directory' || !current.children?.[part]) {
55
+ return null;
56
+ }
57
+ current = current.children[part];
58
+ }
59
+ return current;
60
+ }
61
+ getParentAndName(path) {
62
+ const resolved = this.resolvePath(path);
63
+ if (resolved === '/')
64
+ return null;
65
+ const parts = resolved.split('/').filter(Boolean);
66
+ const name = parts.pop();
67
+ const parentPath = '/' + parts.join('/');
68
+ const parent = this.getNode(parentPath);
69
+ if (!parent || parent.type !== 'directory')
70
+ return null;
71
+ return { parent, name };
72
+ }
73
+ exists(path) {
74
+ return this.getNode(path) !== null;
75
+ }
76
+ isDirectory(path) {
77
+ const node = this.getNode(path);
78
+ return node != null && node.type === 'directory';
79
+ }
80
+ isFile(path) {
81
+ const node = this.getNode(path);
82
+ return node != null && node.type === 'file';
83
+ }
84
+ readFile(path) {
85
+ const node = this.getNode(path);
86
+ if (!node)
87
+ throw new Error(`No such file: ${path}`);
88
+ if (node.type !== 'file')
89
+ throw new Error(`Is a directory: ${path}`);
90
+ return node.content ?? '';
91
+ }
92
+ writeFile(path, content) {
93
+ const existing = this.getNode(path);
94
+ if (existing) {
95
+ if (existing.type !== 'file')
96
+ throw new Error(`Is a directory: ${path}`);
97
+ existing.content = content;
98
+ return;
99
+ }
100
+ const info = this.getParentAndName(path);
101
+ if (!info)
102
+ throw new Error(`Cannot create file: ${path}`);
103
+ if (!info.parent.children)
104
+ info.parent.children = {};
105
+ info.parent.children[info.name] = { type: 'file', content };
106
+ }
107
+ appendFile(path, content) {
108
+ const existing = this.getNode(path);
109
+ if (existing) {
110
+ if (existing.type !== 'file')
111
+ throw new Error(`Is a directory: ${path}`);
112
+ existing.content = (existing.content ?? '') + content;
113
+ return;
114
+ }
115
+ this.writeFile(path, content);
116
+ }
117
+ listDir(path) {
118
+ const node = this.getNode(path);
119
+ if (!node)
120
+ throw new Error(`No such directory: ${path}`);
121
+ if (node.type !== 'directory')
122
+ throw new Error(`Not a directory: ${path}`);
123
+ return Object.keys(node.children ?? {}).sort();
124
+ }
125
+ listDirDetailed(path) {
126
+ const node = this.getNode(path);
127
+ if (!node)
128
+ throw new Error(`No such directory: ${path}`);
129
+ if (node.type !== 'directory')
130
+ throw new Error(`Not a directory: ${path}`);
131
+ return Object.entries(node.children ?? {})
132
+ .map(([name, child]) => ({ name, type: child.type }))
133
+ .sort((a, b) => a.name.localeCompare(b.name));
134
+ }
135
+ mkdir(path, recursive = false) {
136
+ if (this.exists(path)) {
137
+ if (this.isDirectory(path))
138
+ return;
139
+ throw new Error(`File exists: ${path}`);
140
+ }
141
+ if (recursive) {
142
+ const resolved = this.resolvePath(path);
143
+ const parts = resolved.split('/').filter(Boolean);
144
+ let current = '/';
145
+ for (const part of parts) {
146
+ current = current === '/' ? '/' + part : current + '/' + part;
147
+ if (!this.exists(current)) {
148
+ this.mkdirSingle(current);
149
+ }
150
+ else if (!this.isDirectory(current)) {
151
+ throw new Error(`Not a directory: ${current}`);
152
+ }
153
+ }
154
+ }
155
+ else {
156
+ this.mkdirSingle(path);
157
+ }
158
+ }
159
+ mkdirSingle(path) {
160
+ const info = this.getParentAndName(path);
161
+ if (!info)
162
+ throw new Error(`Cannot create directory: ${path}`);
163
+ if (!info.parent.children)
164
+ info.parent.children = {};
165
+ if (info.parent.children[info.name])
166
+ throw new Error(`Already exists: ${path}`);
167
+ info.parent.children[info.name] = { type: 'directory', children: {} };
168
+ }
169
+ remove(path, recursive = false) {
170
+ const node = this.getNode(path);
171
+ if (!node)
172
+ throw new Error(`No such file or directory: ${path}`);
173
+ if (node.type === 'directory' && !recursive) {
174
+ const children = Object.keys(node.children ?? {});
175
+ if (children.length > 0) {
176
+ throw new Error(`Directory not empty: ${path}`);
177
+ }
178
+ }
179
+ const info = this.getParentAndName(path);
180
+ if (!info)
181
+ throw new Error(`Cannot remove root`);
182
+ delete info.parent.children[info.name];
183
+ }
184
+ copy(src, dest, recursive = false) {
185
+ const srcNode = this.getNode(src);
186
+ if (!srcNode)
187
+ throw new Error(`No such file or directory: ${src}`);
188
+ if (srcNode.type === 'directory' && !recursive) {
189
+ throw new Error(`Is a directory (use -r): ${src}`);
190
+ }
191
+ const cloned = this.deepClone(srcNode);
192
+ if (this.exists(dest) && this.isDirectory(dest)) {
193
+ const srcParts = this.resolvePath(src).split('/').filter(Boolean);
194
+ const srcName = srcParts[srcParts.length - 1];
195
+ const destNode = this.getNode(dest);
196
+ if (!destNode.children)
197
+ destNode.children = {};
198
+ destNode.children[srcName] = cloned;
199
+ }
200
+ else {
201
+ const info = this.getParentAndName(dest);
202
+ if (!info)
203
+ throw new Error(`Cannot copy to: ${dest}`);
204
+ if (!info.parent.children)
205
+ info.parent.children = {};
206
+ info.parent.children[info.name] = cloned;
207
+ }
208
+ }
209
+ move(src, dest) {
210
+ const srcNode = this.getNode(src);
211
+ if (!srcNode)
212
+ throw new Error(`No such file or directory: ${src}`);
213
+ const cloned = this.deepClone(srcNode);
214
+ if (this.exists(dest) && this.isDirectory(dest)) {
215
+ const srcParts = this.resolvePath(src).split('/').filter(Boolean);
216
+ const srcName = srcParts[srcParts.length - 1];
217
+ const destNode = this.getNode(dest);
218
+ if (!destNode.children)
219
+ destNode.children = {};
220
+ destNode.children[srcName] = cloned;
221
+ }
222
+ else {
223
+ const info = this.getParentAndName(dest);
224
+ if (!info)
225
+ throw new Error(`Cannot move to: ${dest}`);
226
+ if (!info.parent.children)
227
+ info.parent.children = {};
228
+ info.parent.children[info.name] = cloned;
229
+ }
230
+ const srcInfo = this.getParentAndName(src);
231
+ if (srcInfo) {
232
+ delete srcInfo.parent.children[srcInfo.name];
233
+ }
234
+ }
235
+ changeCwd(path) {
236
+ const resolved = this.resolvePath(path);
237
+ const node = this.getNode(resolved);
238
+ if (!node)
239
+ throw new Error(`No such directory: ${path}`);
240
+ if (node.type !== 'directory')
241
+ throw new Error(`Not a directory: ${path}`);
242
+ this.cwd = resolved;
243
+ }
244
+ getPermissions(path) {
245
+ const node = this.getNode(path);
246
+ if (!node)
247
+ throw new Error(`No such file or directory: ${path}`);
248
+ return node.permissions ?? (node.type === 'directory' ? 'drwxr-xr-x' : '-rw-r--r--');
249
+ }
250
+ setPermissions(path, permissions) {
251
+ const node = this.getNode(path);
252
+ if (!node)
253
+ throw new Error(`No such file or directory: ${path}`);
254
+ node.permissions = permissions;
255
+ }
256
+ find(startPath, predicate) {
257
+ const results = [];
258
+ const resolved = this.resolvePath(startPath);
259
+ const walk = (currentPath, node) => {
260
+ if (predicate(currentPath, node)) {
261
+ results.push(currentPath);
262
+ }
263
+ if (node.type === 'directory' && node.children) {
264
+ for (const [name, child] of Object.entries(node.children)) {
265
+ walk(currentPath === '/' ? '/' + name : currentPath + '/' + name, child);
266
+ }
267
+ }
268
+ };
269
+ const startNode = this.getNode(resolved);
270
+ if (startNode) {
271
+ walk(resolved, startNode);
272
+ }
273
+ return results;
274
+ }
275
+ }
276
+ //# sourceMappingURL=VirtualFS.js.map
@@ -0,0 +1,4 @@
1
+ import { VirtualFS } from '../VirtualFS.js';
2
+ import type { CommandResult } from './index.js';
3
+ export declare function cat(fs: VirtualFS, args: string[]): CommandResult;
4
+ //# sourceMappingURL=cat.d.ts.map
@@ -0,0 +1,18 @@
1
+ export function cat(fs, args) {
2
+ if (args.length === 0) {
3
+ return { output: '', error: 'cat: missing file operand' };
4
+ }
5
+ const outputs = [];
6
+ for (const filePath of args) {
7
+ try {
8
+ const content = fs.readFile(filePath);
9
+ outputs.push(content);
10
+ }
11
+ catch (e) {
12
+ const msg = e instanceof Error ? e.message : String(e);
13
+ return { output: '', error: `cat: ${msg}` };
14
+ }
15
+ }
16
+ return { output: outputs.join('') };
17
+ }
18
+ //# sourceMappingURL=cat.js.map
@@ -0,0 +1,4 @@
1
+ import { VirtualFS } from '../VirtualFS.js';
2
+ import type { CommandResult } from './index.js';
3
+ export declare function cd(fs: VirtualFS, args: string[]): CommandResult;
4
+ //# sourceMappingURL=cd.d.ts.map
@@ -0,0 +1,12 @@
1
+ export function cd(fs, args) {
2
+ const target = args[0] ?? '/';
3
+ try {
4
+ fs.changeCwd(target);
5
+ return { output: '' };
6
+ }
7
+ catch (e) {
8
+ const msg = e instanceof Error ? e.message : String(e);
9
+ return { output: '', error: `cd: ${msg}` };
10
+ }
11
+ }
12
+ //# sourceMappingURL=cd.js.map
@@ -0,0 +1,4 @@
1
+ import { VirtualFS } from '../VirtualFS.js';
2
+ import type { CommandResult } from './index.js';
3
+ export declare function chmod(fs: VirtualFS, args: string[]): CommandResult;
4
+ //# sourceMappingURL=chmod.d.ts.map
@@ -0,0 +1,98 @@
1
+ export function chmod(fs, args) {
2
+ if (args.length < 2) {
3
+ return { output: '', error: 'chmod: missing operand' };
4
+ }
5
+ const modeStr = args[0];
6
+ const filePath = args[1];
7
+ if (!fs.exists(filePath)) {
8
+ return { output: '', error: `chmod: cannot access '${filePath}': No such file or directory` };
9
+ }
10
+ const currentPerms = fs.getPermissions(filePath);
11
+ let newPerms;
12
+ // Check if numeric mode (e.g., 755, 644)
13
+ if (/^\d{3,4}$/.test(modeStr)) {
14
+ newPerms = numericToSymbolic(modeStr, fs.isDirectory(filePath));
15
+ }
16
+ else {
17
+ // Symbolic mode (e.g., +x, u+w, go-r)
18
+ newPerms = applySymbolicMode(currentPerms, modeStr);
19
+ if (!newPerms) {
20
+ return { output: '', error: `chmod: invalid mode: '${modeStr}'` };
21
+ }
22
+ }
23
+ try {
24
+ fs.setPermissions(filePath, newPerms);
25
+ }
26
+ catch (e) {
27
+ return { output: '', error: `chmod: ${e.message}` };
28
+ }
29
+ return { output: '' };
30
+ }
31
+ function numericToSymbolic(mode, isDir) {
32
+ // Take last 3 digits
33
+ const digits = mode.slice(-3);
34
+ const prefix = isDir ? 'd' : '-';
35
+ const perms = digits.split('').map(d => {
36
+ const n = parseInt(d, 10);
37
+ return (((n & 4) ? 'r' : '-') +
38
+ ((n & 2) ? 'w' : '-') +
39
+ ((n & 1) ? 'x' : '-'));
40
+ });
41
+ return prefix + perms.join('');
42
+ }
43
+ function applySymbolicMode(current, modeStr) {
44
+ // Parse symbolic mode: [ugoa][+-=][rwx]
45
+ const chars = current.split('');
46
+ // Support multiple comma-separated modes
47
+ const modes = modeStr.split(',');
48
+ for (const mode of modes) {
49
+ const match = mode.match(/^([ugoa]*)([-+=])([rwx]+)$/);
50
+ if (!match)
51
+ return '';
52
+ let who = match[1];
53
+ const op = match[2];
54
+ const perms = match[3];
55
+ // If no who specified, default to 'a' (all)
56
+ if (who === '' || who === 'a') {
57
+ who = 'ugo';
58
+ }
59
+ for (const w of who) {
60
+ let offset;
61
+ if (w === 'u')
62
+ offset = 1;
63
+ else if (w === 'g')
64
+ offset = 4;
65
+ else if (w === 'o')
66
+ offset = 7;
67
+ else
68
+ continue;
69
+ for (const p of perms) {
70
+ let permOffset;
71
+ if (p === 'r')
72
+ permOffset = 0;
73
+ else if (p === 'w')
74
+ permOffset = 1;
75
+ else if (p === 'x')
76
+ permOffset = 2;
77
+ else
78
+ continue;
79
+ const idx = offset + permOffset;
80
+ if (op === '+') {
81
+ chars[idx] = p;
82
+ }
83
+ else if (op === '-') {
84
+ chars[idx] = '-';
85
+ }
86
+ else if (op === '=') {
87
+ // First clear all, then set
88
+ chars[offset] = '-';
89
+ chars[offset + 1] = '-';
90
+ chars[offset + 2] = '-';
91
+ chars[idx] = p;
92
+ }
93
+ }
94
+ }
95
+ }
96
+ return chars.join('');
97
+ }
98
+ //# sourceMappingURL=chmod.js.map
@@ -0,0 +1,4 @@
1
+ import { VirtualFS } from '../VirtualFS.js';
2
+ import type { CommandResult } from './index.js';
3
+ export declare function clear(_fs: VirtualFS, _args: string[]): CommandResult;
4
+ //# sourceMappingURL=clear.d.ts.map
@@ -0,0 +1,4 @@
1
+ export function clear(_fs, _args) {
2
+ return { output: 'CLEAR_SCREEN' };
3
+ }
4
+ //# sourceMappingURL=clear.js.map
@@ -0,0 +1,4 @@
1
+ import { VirtualFS } from '../VirtualFS.js';
2
+ import type { CommandResult } from './index.js';
3
+ export declare function cp(fs: VirtualFS, args: string[]): CommandResult;
4
+ //# sourceMappingURL=cp.d.ts.map
@@ -0,0 +1,26 @@
1
+ export function cp(fs, args) {
2
+ let recursive = false;
3
+ const paths = [];
4
+ for (const arg of args) {
5
+ if (arg === '-r' || arg === '-R' || arg === '--recursive') {
6
+ recursive = true;
7
+ }
8
+ else {
9
+ paths.push(arg);
10
+ }
11
+ }
12
+ if (paths.length < 2) {
13
+ return { output: '', error: 'cp: missing file operand' };
14
+ }
15
+ const src = paths[0];
16
+ const dest = paths[1];
17
+ try {
18
+ fs.copy(src, dest, recursive);
19
+ return { output: '' };
20
+ }
21
+ catch (e) {
22
+ const msg = e instanceof Error ? e.message : String(e);
23
+ return { output: '', error: `cp: ${msg}` };
24
+ }
25
+ }
26
+ //# sourceMappingURL=cp.js.map
@@ -0,0 +1,4 @@
1
+ import { VirtualFS } from '../VirtualFS.js';
2
+ import type { CommandResult } from './index.js';
3
+ export declare function cut(fs: VirtualFS, args: string[]): CommandResult;
4
+ //# sourceMappingURL=cut.d.ts.map
@@ -0,0 +1,76 @@
1
+ export function cut(fs, args) {
2
+ // Extract stdin
3
+ let stdin;
4
+ const stdinIdx = args.findIndex(a => a.startsWith('__stdin__:'));
5
+ if (stdinIdx !== -1) {
6
+ stdin = args[stdinIdx].slice('__stdin__:'.length);
7
+ args = [...args.slice(0, stdinIdx), ...args.slice(stdinIdx + 1)];
8
+ }
9
+ let delimiter = '\t';
10
+ let fieldsStr;
11
+ const files = [];
12
+ for (let i = 0; i < args.length; i++) {
13
+ const arg = args[i];
14
+ if (arg === '-d') {
15
+ i++;
16
+ if (i >= args.length) {
17
+ return { output: '', error: 'cut: option requires an argument -- d' };
18
+ }
19
+ delimiter = args[i];
20
+ }
21
+ else if (arg.startsWith('-d')) {
22
+ delimiter = arg.slice(2);
23
+ }
24
+ else if (arg === '-f') {
25
+ i++;
26
+ if (i >= args.length) {
27
+ return { output: '', error: 'cut: option requires an argument -- f' };
28
+ }
29
+ fieldsStr = args[i];
30
+ }
31
+ else if (arg.startsWith('-f')) {
32
+ fieldsStr = arg.slice(2);
33
+ }
34
+ else if (arg.startsWith('-')) {
35
+ return { output: '', error: `cut: invalid option -- '${arg}'` };
36
+ }
37
+ else {
38
+ files.push(arg);
39
+ }
40
+ }
41
+ if (!fieldsStr) {
42
+ return { output: '', error: 'cut: you must specify a list of fields' };
43
+ }
44
+ // Parse field numbers (1-based, comma-separated)
45
+ const fields = fieldsStr.split(',').map(f => {
46
+ const n = parseInt(f.trim(), 10);
47
+ if (isNaN(n) || n < 1)
48
+ return -1;
49
+ return n;
50
+ });
51
+ if (fields.some(f => f === -1)) {
52
+ return { output: '', error: `cut: invalid field value '${fieldsStr}'` };
53
+ }
54
+ let content;
55
+ if (files.length > 0) {
56
+ try {
57
+ content = fs.readFile(files[0]);
58
+ }
59
+ catch (e) {
60
+ return { output: '', error: `cut: ${e.message}` };
61
+ }
62
+ }
63
+ else if (stdin !== undefined) {
64
+ content = stdin;
65
+ }
66
+ else {
67
+ return { output: '', error: 'cut: missing file operand' };
68
+ }
69
+ const lines = content.split('\n');
70
+ const result = lines.map(line => {
71
+ const parts = line.split(delimiter);
72
+ return fields.map(f => parts[f - 1] ?? '').join(delimiter);
73
+ });
74
+ return { output: result.join('\n') };
75
+ }
76
+ //# sourceMappingURL=cut.js.map
@@ -0,0 +1,4 @@
1
+ import { VirtualFS } from '../VirtualFS.js';
2
+ import type { CommandResult } from './index.js';
3
+ export declare function echo(_fs: VirtualFS, args: string[]): CommandResult;
4
+ //# sourceMappingURL=echo.d.ts.map
@@ -0,0 +1,4 @@
1
+ export function echo(_fs, args) {
2
+ return { output: args.join(' ') };
3
+ }
4
+ //# sourceMappingURL=echo.js.map
@@ -0,0 +1,4 @@
1
+ import { VirtualFS } from '../VirtualFS.js';
2
+ import type { CommandResult } from './index.js';
3
+ export declare function find(fs: VirtualFS, args: string[]): CommandResult;
4
+ //# sourceMappingURL=find.d.ts.map
@@ -0,0 +1,60 @@
1
+ export function find(fs, args) {
2
+ let searchPath = '.';
3
+ let namePattern;
4
+ let typeFilter;
5
+ let i = 0;
6
+ // First non-flag argument is the path (if it doesn't start with -)
7
+ if (i < args.length && !args[i].startsWith('-')) {
8
+ searchPath = args[i];
9
+ i++;
10
+ }
11
+ while (i < args.length) {
12
+ const arg = args[i];
13
+ if (arg === '-name') {
14
+ i++;
15
+ if (i >= args.length) {
16
+ return { output: '', error: 'find: missing argument to -name' };
17
+ }
18
+ namePattern = args[i];
19
+ }
20
+ else if (arg === '-type') {
21
+ i++;
22
+ if (i >= args.length) {
23
+ return { output: '', error: 'find: missing argument to -type' };
24
+ }
25
+ const t = args[i];
26
+ if (t !== 'f' && t !== 'd') {
27
+ return { output: '', error: `find: invalid type '${t}'` };
28
+ }
29
+ typeFilter = t;
30
+ }
31
+ else {
32
+ return { output: '', error: `find: unknown option '${arg}'` };
33
+ }
34
+ i++;
35
+ }
36
+ if (!fs.exists(searchPath)) {
37
+ return { output: '', error: `find: '${searchPath}': No such file or directory` };
38
+ }
39
+ const results = fs.find(searchPath, (path, node) => {
40
+ if (typeFilter === 'f' && node.type !== 'file')
41
+ return false;
42
+ if (typeFilter === 'd' && node.type !== 'directory')
43
+ return false;
44
+ if (namePattern !== undefined) {
45
+ const name = path.split('/').filter(Boolean).pop() ?? '';
46
+ if (path === '/' && !namePattern)
47
+ return false;
48
+ return matchWildcard(name, namePattern);
49
+ }
50
+ return true;
51
+ });
52
+ return { output: results.join('\n') };
53
+ }
54
+ function matchWildcard(str, pattern) {
55
+ // Convert wildcard pattern to regex
56
+ const escaped = pattern.replace(/[.+^${}()|[\]\\]/g, '\\$&');
57
+ const regexStr = '^' + escaped.replace(/\*/g, '.*').replace(/\?/g, '.') + '$';
58
+ return new RegExp(regexStr).test(str);
59
+ }
60
+ //# sourceMappingURL=find.js.map
@@ -0,0 +1,4 @@
1
+ import { VirtualFS } from '../VirtualFS.js';
2
+ import type { CommandResult } from './index.js';
3
+ export declare function git(fs: VirtualFS, args: string[]): CommandResult;
4
+ //# sourceMappingURL=git.d.ts.map