@ryuu-reinzz/ryuu-cli 0.0.0-stage → 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 (59) hide show
  1. package/CHANGELOG.md +560 -0
  2. package/LICENSE +21 -0
  3. package/README.md +1213 -2
  4. package/package.json +45 -4
  5. package/src/agent/agent.js +1194 -0
  6. package/src/agent/approvals.js +76 -0
  7. package/src/agent/context.js +106 -0
  8. package/src/agent/planner.js +237 -0
  9. package/src/agent/worklog.js +318 -0
  10. package/src/cli/app.js +463 -0
  11. package/src/cli/commands.js +1150 -0
  12. package/src/cli/input.js +287 -0
  13. package/src/cli/markdown.js +527 -0
  14. package/src/cli/prompts.js +255 -0
  15. package/src/cli/renderer.js +2155 -0
  16. package/src/cli/settings-editor.js +280 -0
  17. package/src/cli/settings-tui.js +669 -0
  18. package/src/cli/tui.js +1737 -0
  19. package/src/cli/width.js +48 -0
  20. package/src/config/load.js +132 -0
  21. package/src/config/scaffold.js +50 -0
  22. package/src/config/settings-registry.js +495 -0
  23. package/src/config/settings.js +304 -0
  24. package/src/config/validate.js +451 -0
  25. package/src/index.js +449 -0
  26. package/src/providers/catalog.js +282 -0
  27. package/src/providers/index.js +84 -0
  28. package/src/providers/nvidia.js +645 -0
  29. package/src/providers/registry.js +104 -0
  30. package/src/services/clipboard.js +38 -0
  31. package/src/services/diff.js +153 -0
  32. package/src/services/doctor.js +126 -0
  33. package/src/services/logger.js +190 -0
  34. package/src/services/preferences.js +249 -0
  35. package/src/services/processes.js +345 -0
  36. package/src/services/provider-settings.js +226 -0
  37. package/src/services/sessions.js +279 -0
  38. package/src/services/undo.js +133 -0
  39. package/src/services/ushell.js +146 -0
  40. package/src/services/websearch/manager.js +616 -0
  41. package/src/services/websearch/mcp.js +233 -0
  42. package/src/services/websearch/normalize.js +284 -0
  43. package/src/services/websearch/providers.js +178 -0
  44. package/src/services/websearch/serper.js +263 -0
  45. package/src/services/workspace.js +589 -0
  46. package/src/tools/filesystem.js +304 -0
  47. package/src/tools/git.js +114 -0
  48. package/src/tools/index.js +48 -0
  49. package/src/tools/project.js +160 -0
  50. package/src/tools/registry.js +118 -0
  51. package/src/tools/search.js +210 -0
  52. package/src/tools/shell.js +279 -0
  53. package/src/tools/url.js +542 -0
  54. package/src/tools/web.js +67 -0
  55. package/src/utils/errors.js +160 -0
  56. package/src/utils/network.js +129 -0
  57. package/src/utils/redaction.js +73 -0
  58. package/src/utils/validation.js +370 -0
  59. package/src/utils/version.js +24 -0
@@ -0,0 +1,146 @@
1
+ import { spawn } from 'node:child_process';
2
+
3
+ /**
4
+ * Shell commands the user runs themselves (`/bash …` or `!…`).
5
+ *
6
+ * These are not model tool calls: the user typed the command, so it runs with
7
+ * their own permissions and without an approval prompt. The environment is
8
+ * still stripped of credential-looking variables, output is captured with a
9
+ * bound, and the child is killed on abort so Ctrl+C cannot leave it behind.
10
+ */
11
+
12
+ const SECRET_ENV = /(api[_-]?key|token|secret|password|passwd|credential|private[_-]?key)/i;
13
+
14
+ /** Copy of `env` without credential-looking variables. */
15
+ export function sanitizedEnv(env = process.env) {
16
+ const clean = {};
17
+ for (const [key, value] of Object.entries(env)) {
18
+ if (SECRET_ENV.test(key)) continue;
19
+ if (value === undefined) continue;
20
+ clean[key] = value;
21
+ }
22
+ return clean;
23
+ }
24
+
25
+ /** Keep the tail of an over-long capture, which is where failures are. */
26
+ function appendBounded(current, text, maxBytes) {
27
+ const next = current + text;
28
+ if (next.length <= maxBytes) return { value: next, truncated: false };
29
+ return { value: next.slice(next.length - maxBytes), truncated: true };
30
+ }
31
+
32
+ /**
33
+ * Run one shell command to completion.
34
+ *
35
+ * @returns {Promise<{command:string, cwd:string, stdout:string, stderr:string,
36
+ * exitCode:number|null, signal:string|null, durationMs:number,
37
+ * truncated:boolean, timedOut:boolean, error:string|null}>}
38
+ */
39
+ export function runUserShell({
40
+ command,
41
+ cwd = process.cwd(),
42
+ env = process.env,
43
+ maxBytes = 200_000,
44
+ timeoutMs = 0,
45
+ signal = null,
46
+ } = {}) {
47
+ const startedAt = Date.now();
48
+ return new Promise((resolve) => {
49
+ let stdout = '';
50
+ let stderr = '';
51
+ let truncated = false;
52
+ let timedOut = false;
53
+ let settled = false;
54
+ let killTimer = null;
55
+ let timeoutTimer = null;
56
+ let child = null;
57
+
58
+ /**
59
+ * Kill the whole process group, not just the shell wrapper.
60
+ *
61
+ * `shell: true` starts `/bin/sh -c <command>`; for a compound command the
62
+ * shell forks the real process instead of exec'ing it, so signalling only
63
+ * the shell leaves the child running and its pipes open — Ctrl+C would look
64
+ * ignored until the command finished on its own.
65
+ */
66
+ const killTree = (signalName) => {
67
+ if (!child?.pid) return;
68
+ try {
69
+ process.kill(-child.pid, signalName);
70
+ } catch {
71
+ try {
72
+ child.kill(signalName);
73
+ } catch {
74
+ // already gone
75
+ }
76
+ }
77
+ };
78
+
79
+ const finish = (exitCode, signalName, error = null) => {
80
+ if (settled) return;
81
+ settled = true;
82
+ if (killTimer) clearTimeout(killTimer);
83
+ if (timeoutTimer) clearTimeout(timeoutTimer);
84
+ signal?.removeEventListener('abort', onAbort);
85
+ resolve({
86
+ command,
87
+ cwd,
88
+ stdout,
89
+ stderr,
90
+ exitCode: Number.isInteger(exitCode) ? exitCode : null,
91
+ signal: signalName || null,
92
+ durationMs: Date.now() - startedAt,
93
+ truncated,
94
+ timedOut,
95
+ error: error ? String(error.message || error) : null,
96
+ });
97
+ };
98
+
99
+ const onAbort = () => {
100
+ killTree('SIGTERM');
101
+ killTimer = setTimeout(() => killTree('SIGKILL'), 2000);
102
+ killTimer.unref?.();
103
+ };
104
+
105
+ try {
106
+ child = spawn(String(command), {
107
+ shell: true,
108
+ cwd,
109
+ env: sanitizedEnv(env),
110
+ stdio: ['ignore', 'pipe', 'pipe'],
111
+ detached: true,
112
+ });
113
+ } catch (error) {
114
+ finish(null, null, error);
115
+ return;
116
+ }
117
+
118
+ if (signal) {
119
+ if (signal.aborted) onAbort();
120
+ else signal.addEventListener('abort', onAbort, { once: true });
121
+ }
122
+
123
+ if (Number(timeoutMs) > 0) {
124
+ timeoutTimer = setTimeout(() => {
125
+ timedOut = true;
126
+ killTree('SIGTERM');
127
+ killTimer = setTimeout(() => killTree('SIGKILL'), 2000);
128
+ killTimer.unref?.();
129
+ }, Number(timeoutMs));
130
+ timeoutTimer.unref?.();
131
+ }
132
+
133
+ child.stdout?.on('data', (chunk) => {
134
+ const next = appendBounded(stdout, chunk.toString(), maxBytes);
135
+ stdout = next.value;
136
+ truncated = truncated || next.truncated;
137
+ });
138
+ child.stderr?.on('data', (chunk) => {
139
+ const next = appendBounded(stderr, chunk.toString(), maxBytes);
140
+ stderr = next.value;
141
+ truncated = truncated || next.truncated;
142
+ });
143
+ child.on('error', (error) => finish(null, null, error));
144
+ child.on('close', (code, signalName) => finish(code, signalName));
145
+ });
146
+ }