anentrypoint-design 0.0.421 → 0.0.423

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 (2) hide show
  1. package/package.json +1 -1
  2. package/src/shell.js +173 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anentrypoint-design",
3
- "version": "0.0.421",
3
+ "version": "0.0.423",
4
4
  "description": "247420 design system SDK — webjsx + modified ripple-ui, single-file ESM bundle for reproducible use of the AnEntrypoint design.",
5
5
  "type": "module",
6
6
  "main": "./dist/247420.js",
package/src/shell.js ADDED
@@ -0,0 +1,173 @@
1
+ // A real command interpreter for the terminal kit.
2
+ //
3
+ // The kit used to push '(stub) ran: <input>' into an array and render it -- the
4
+ // word "stub" was visible to users, and nothing executed. This module is the
5
+ // execution half: a virtual filesystem plus a command table, both synchronous
6
+ // and dependency-free, so the terminal surface has something genuine to drive.
7
+ //
8
+ // Deliberately NOT a real OS. Commands operate on an in-memory tree; there is
9
+ // no network, no eval, and no host access, so a demo page cannot be turned into
10
+ // an exfiltration surface by typing into it. Unknown input reports a real
11
+ // not-found error the way a shell does, rather than echoing success.
12
+
13
+ const FS = {
14
+ 'readme.md': 'the 247420 design system\n\nan editorial component library. run `ls` to look around,\n`help` for the command list.\n',
15
+ 'colors_and_type.css': '/* the token bible: --acid, --ink, --paper, the --fs-* scale */\n',
16
+ src: {
17
+ 'components.js': '// barrel over src/components/<group>.js\n',
18
+ 'bootstrap.js': '// mountKit() -- every kit boots through here\n',
19
+ css: { 'app-shell.css': '/* @import barrel over app-shell/*.css */\n' },
20
+ },
21
+ ui_kits: {
22
+ 'terminal/': null,
23
+ 'dashboard/': null,
24
+ 'file-browser/': null,
25
+ },
26
+ };
27
+
28
+ function nodeAt(path) {
29
+ let cur = FS;
30
+ for (const seg of path) {
31
+ if (!cur || typeof cur !== 'object') return undefined;
32
+ cur = cur[seg];
33
+ }
34
+ return cur;
35
+ }
36
+
37
+ const isDir = (n) => n && typeof n === 'object';
38
+
39
+ // Resolve a user-typed path against cwd, honouring . and .. and a leading /.
40
+ function resolvePath(cwd, raw) {
41
+ const parts = String(raw).split('/').filter(Boolean);
42
+ const out = String(raw).startsWith('/') ? [] : cwd.slice();
43
+ for (const p of parts) {
44
+ if (p === '.') continue;
45
+ if (p === '..') out.pop();
46
+ else out.push(p);
47
+ }
48
+ return out;
49
+ }
50
+
51
+ const promptPath = (cwd) => '~/' + cwd.join('/');
52
+
53
+ // Each command returns an array of {kind, text} lines, matching the six line
54
+ // kinds the kit's Line() renderer already knows: cmt, cmd, out, ok, warn, log.
55
+ const COMMANDS = {
56
+ help(_args, ctx) {
57
+ const names = Object.keys(COMMANDS).sort();
58
+ return [
59
+ { kind: 'cmt', text: '# available commands' },
60
+ ...names.map((n) => ({ kind: 'out', text: n.padEnd(10) + COMMANDS[n].desc })),
61
+ { kind: 'out', text: '' },
62
+ { kind: 'out', text: 'up/down walks history, tab completes, ctrl+l or clear wipes the screen.' },
63
+ ];
64
+ },
65
+ ls(args, ctx) {
66
+ const target = args[0] ? resolvePath(ctx.cwd, args[0]) : ctx.cwd;
67
+ const node = nodeAt(target);
68
+ if (node === undefined) return [{ kind: 'warn', text: 'ls: ' + args[0] + ': no such file or directory' }];
69
+ if (!isDir(node)) return [{ kind: 'out', text: args[0] }];
70
+ const keys = Object.keys(node).sort();
71
+ if (!keys.length) return [{ kind: 'out', text: '(empty)' }];
72
+ return keys.map((k) => ({ kind: 'out', text: isDir(node[k]) ? k + '/' : k }));
73
+ },
74
+ cd(args, ctx) {
75
+ if (!args[0] || args[0] === '~') { ctx.cwd.length = 0; return []; }
76
+ const next = resolvePath(ctx.cwd, args[0]);
77
+ const node = nodeAt(next);
78
+ if (node === undefined) return [{ kind: 'warn', text: 'cd: ' + args[0] + ': no such file or directory' }];
79
+ if (!isDir(node)) return [{ kind: 'warn', text: 'cd: ' + args[0] + ': not a directory' }];
80
+ ctx.cwd.length = 0;
81
+ next.forEach((s) => ctx.cwd.push(s));
82
+ return [];
83
+ },
84
+ cat(args, ctx) {
85
+ if (!args[0]) return [{ kind: 'warn', text: 'cat: missing operand' }];
86
+ const node = nodeAt(resolvePath(ctx.cwd, args[0]));
87
+ if (node === undefined) return [{ kind: 'warn', text: 'cat: ' + args[0] + ': no such file or directory' }];
88
+ if (isDir(node)) return [{ kind: 'warn', text: 'cat: ' + args[0] + ': is a directory' }];
89
+ if (node === null) return [{ kind: 'out', text: '(binary or generated)' }];
90
+ return String(node).split('\n').map((text) => ({ kind: 'out', text }));
91
+ },
92
+ pwd(_args, ctx) { return [{ kind: 'out', text: promptPath(ctx.cwd) }]; },
93
+ echo(args) { return [{ kind: 'out', text: args.join(' ') }]; },
94
+ whoami() { return [{ kind: 'out', text: 'visitor@247420' }]; },
95
+ date() { return [{ kind: 'out', text: new Date().toString() }]; },
96
+ theme(args, ctx) {
97
+ const want = args[0];
98
+ if (want !== 'light' && want !== 'dark') {
99
+ return [{ kind: 'warn', text: 'theme: expected `light` or `dark`' }];
100
+ }
101
+ ctx.setTheme(want);
102
+ return [{ kind: 'ok', text: 'theme set to ' + want }];
103
+ },
104
+ clear(_args, ctx) { ctx.clear(); return []; },
105
+ };
106
+
107
+ COMMANDS.help.desc = 'list these commands';
108
+ COMMANDS.ls.desc = 'list directory contents';
109
+ COMMANDS.cd.desc = 'change directory';
110
+ COMMANDS.cat.desc = 'print a file';
111
+ COMMANDS.pwd.desc = 'print working directory';
112
+ COMMANDS.echo.desc = 'write arguments to output';
113
+ COMMANDS.whoami.desc = 'print the current user';
114
+ COMMANDS.date.desc = 'print the current date';
115
+ COMMANDS.theme.desc = 'switch light/dark';
116
+ COMMANDS.clear.desc = 'clear the scrollback';
117
+
118
+ // Split a command line into argv, honouring single and double quotes so
119
+ // `echo "two words"` is one argument rather than two.
120
+ export function tokenize(line) {
121
+ const out = [];
122
+ let cur = '';
123
+ let quote = null;
124
+ for (const ch of String(line)) {
125
+ if (quote) {
126
+ if (ch === quote) quote = null;
127
+ else cur += ch;
128
+ } else if (ch === '"' || ch === "'") {
129
+ quote = ch;
130
+ } else if (/\s/.test(ch)) {
131
+ if (cur) { out.push(cur); cur = ''; }
132
+ } else {
133
+ cur += ch;
134
+ }
135
+ }
136
+ if (cur) out.push(cur);
137
+ return out;
138
+ }
139
+
140
+ // Complete a partial word against command names (first token) or the current
141
+ // directory's entries (any later token). Returns the completed line, or the
142
+ // original when there is no unambiguous single match.
143
+ export function complete(line, cwd) {
144
+ const argv = tokenize(line);
145
+ const trailing = /\s$/.test(line);
146
+ const head = trailing ? '' : (argv[argv.length - 1] || '');
147
+ const pool = (argv.length <= 1 && !trailing)
148
+ ? Object.keys(COMMANDS)
149
+ : Object.keys(nodeAt(cwd) || {});
150
+ const hits = pool.filter((k) => k.startsWith(head));
151
+ if (hits.length !== 1) return line;
152
+ const base = trailing ? argv : argv.slice(0, -1);
153
+ return [...base, hits[0]].join(' ');
154
+ }
155
+
156
+ export const commandNames = () => Object.keys(COMMANDS).sort();
157
+
158
+ // Run one line. ctx carries { cwd, clear, setTheme } so commands can mutate the
159
+ // session without this module reaching into the DOM itself.
160
+ export function run(line, ctx) {
161
+ const argv = tokenize(line);
162
+ if (!argv.length) return [];
163
+ const [name, ...args] = argv;
164
+ const cmd = COMMANDS[name];
165
+ if (!cmd) {
166
+ return [{ kind: 'warn', text: name + ': command not found — try `help`' }];
167
+ }
168
+ try {
169
+ return cmd(args, ctx) || [];
170
+ } catch (err) {
171
+ return [{ kind: 'warn', text: name + ': ' + (err && err.message ? err.message : 'failed') }];
172
+ }
173
+ }