claudeos-core 1.7.1 → 2.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.
@@ -1,143 +1,206 @@
1
- /**
2
- * ClaudeOS-Core — CLI Utilities
3
- *
4
- * Shared constants, execution helpers, and filesystem utilities for the CLI.
5
- */
6
-
7
- const { execSync } = require("child_process");
8
- const fs = require("fs");
9
- const path = require("path");
10
- const { ensureDir, existsSafe } = require("../../lib/safe-fs");
11
-
12
- // ─── Path configuration ──────────────────────────────────────────
13
- const TOOLS_DIR = path.resolve(__dirname, "../..");
14
- const PROJECT_ROOT = process.cwd();
15
- const GENERATED_DIR = path.join(PROJECT_ROOT, "claudeos-core/generated");
16
-
17
- // ─── Language configuration ──────────────────────────────────────
18
- const SUPPORTED_LANGS = {
19
- en: "English",
20
- ko: "한국어 (Korean)",
21
- "zh-CN": "简体中文 (Chinese Simplified)",
22
- ja: "日本語 (Japanese)",
23
- es: "Español (Spanish)",
24
- vi: "Tiếng Việt (Vietnamese)",
25
- hi: "हिन्दी (Hindi)",
26
- ru: "Русский (Russian)",
27
- fr: "Français (French)",
28
- de: "Deutsch (German)",
29
- };
30
- const LANG_CODES = Object.keys(SUPPORTED_LANGS);
31
-
32
- function isValidLang(lang) {
33
- return LANG_CODES.includes(lang);
34
- }
35
-
36
- // ─── Output ─────────────────────────────────────────────────────
37
- function log(msg) {
38
- console.log(msg);
39
- }
40
-
41
- function header(title) {
42
- log("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
43
- log(title);
44
- log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
45
- }
46
-
47
- // ─── Execution ──────────────────────────────────────────────────
48
- function run(cmd, options = {}) {
49
- try {
50
- execSync(cmd, {
51
- cwd: options.cwd || PROJECT_ROOT,
52
- stdio: options.silent ? ["pipe", "pipe", "pipe"] : "inherit",
53
- encoding: "utf-8",
54
- timeout: options.timeout || 0,
55
- });
56
- return true;
57
- } catch (e) {
58
- if (options.ignoreError) return false;
59
- throw e;
60
- }
61
- }
62
-
63
- // Run claude -p: pass prompt via stdin (no shell pipe — avoids command injection)
64
- function runClaudePrompt(prompt, options = {}) {
65
- try {
66
- execSync("claude -p --dangerously-skip-permissions", {
67
- input: prompt,
68
- cwd: options.cwd || PROJECT_ROOT,
69
- stdio: ["pipe", "inherit", "inherit"],
70
- encoding: "utf-8",
71
- timeout: 0,
72
- });
73
- return true;
74
- } catch (e) {
75
- if (options.ignoreError) return false;
76
- throw e;
77
- }
78
- }
79
-
80
- // ─── Filesystem ─────────────────────────────────────────────────
81
- // ensureDir: delegated to lib/safe-fs.js (single source of truth)
82
- // fileExists: alias for existsSafe from lib/safe-fs.js
83
- const fileExists = existsSafe;
84
-
85
- // readFile: intentionally throws on error (CLI needs hard failure, unlike safe-fs fallback)
86
- function readFile(p) {
87
- return fs.readFileSync(p, "utf-8");
88
- }
89
-
90
- function injectProjectRoot(text) {
91
- // Normalize to forward slashes for prompts (Claude interprets backslashes as escapes)
92
- const normalizedRoot = PROJECT_ROOT.replace(/\\/g, "/");
93
- return text.replace(/\{\{PROJECT_ROOT\}\}/g, normalizedRoot);
94
- }
95
-
96
- // ─── Helpers ────────────────────────────────────────────────────
97
- function pad(str, len) {
98
- return str.length >= len ? str : str + " ".repeat(len - str.length);
99
- }
100
-
101
- function countFiles() {
102
- try {
103
- let count = 0;
104
- const skipDirs = ["node_modules", "generated"];
105
- const visited = new Set();
106
- const scan = (dir) => {
107
- if (!fs.existsSync(dir)) return;
108
- let realDir;
109
- try { realDir = fs.realpathSync(dir); } catch (_e) { realDir = dir; }
110
- if (visited.has(realDir)) return;
111
- visited.add(realDir);
112
- for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
113
- if (skipDirs.includes(entry.name)) continue;
114
- const full = path.join(dir, entry.name);
115
- if (entry.isDirectory()) scan(full);
116
- else count++;
117
- }
118
- };
119
- scan(path.join(PROJECT_ROOT, ".claude"));
120
- scan(path.join(PROJECT_ROOT, "claudeos-core"));
121
- return count;
122
- } catch (e) {
123
- return "?";
124
- }
125
- }
126
-
127
- function countPass1Files() {
128
- try {
129
- return fs
130
- .readdirSync(GENERATED_DIR)
131
- .filter((f) => f.startsWith("pass1-") && f.endsWith(".json")).length;
132
- } catch (e) {
133
- return 0;
134
- }
135
- }
136
-
137
- module.exports = {
138
- TOOLS_DIR, PROJECT_ROOT, GENERATED_DIR,
139
- SUPPORTED_LANGS, LANG_CODES, isValidLang,
140
- log, header, run, runClaudePrompt,
141
- ensureDir, fileExists, readFile, injectProjectRoot,
142
- pad, countFiles, countPass1Files,
143
- };
1
+ /**
2
+ * ClaudeOS-Core — CLI Utilities
3
+ *
4
+ * Shared constants, execution helpers, and filesystem utilities for the CLI.
5
+ */
6
+
7
+ const { execSync, spawn } = require("child_process");
8
+ const fs = require("fs");
9
+ const path = require("path");
10
+ const { ensureDir, existsSafe } = require("../../lib/safe-fs");
11
+
12
+ // ─── Path configuration ──────────────────────────────────────────
13
+ const TOOLS_DIR = path.resolve(__dirname, "../..");
14
+ const PROJECT_ROOT = process.cwd();
15
+ const GENERATED_DIR = path.join(PROJECT_ROOT, "claudeos-core/generated");
16
+
17
+ // ─── Language configuration ──────────────────────────────────────
18
+ // Single source of truth: lib/language-config.js. We re-export under the
19
+ // historical name SUPPORTED_LANGS so existing imports keep working.
20
+ const { LANGUAGES: SUPPORTED_LANGS, LANG_CODES, isValidLang } = require("../../lib/language-config");
21
+
22
+ // ─── Output ─────────────────────────────────────────────────────
23
+ function log(msg) {
24
+ console.log(msg);
25
+ }
26
+
27
+ function header(title) {
28
+ log("\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
29
+ log(title);
30
+ log("━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━");
31
+ }
32
+
33
+ // ─── Execution ──────────────────────────────────────────────────
34
+ function run(cmd, options = {}) {
35
+ try {
36
+ execSync(cmd, {
37
+ cwd: options.cwd || PROJECT_ROOT,
38
+ stdio: options.silent ? ["pipe", "pipe", "pipe"] : "inherit",
39
+ encoding: "utf-8",
40
+ timeout: options.timeout || 0,
41
+ });
42
+ return true;
43
+ } catch (e) {
44
+ if (options.ignoreError) return false;
45
+ throw e;
46
+ }
47
+ }
48
+
49
+ // Run claude -p: pass prompt via stdin (no shell pipe — avoids command injection)
50
+ function runClaudePrompt(prompt, options = {}) {
51
+ try {
52
+ execSync("claude -p --dangerously-skip-permissions", {
53
+ input: prompt,
54
+ cwd: options.cwd || PROJECT_ROOT,
55
+ stdio: ["pipe", "inherit", "inherit"],
56
+ encoding: "utf-8",
57
+ timeout: 0,
58
+ });
59
+ return true;
60
+ } catch (e) {
61
+ if (options.ignoreError) return false;
62
+ throw e;
63
+ }
64
+ }
65
+
66
+ // Async variant of runClaudePrompt using spawn — does NOT block the event loop,
67
+ // so the caller can run setInterval-based progress callbacks concurrently.
68
+ // Resolves to true on exit code 0, false otherwise (no throw; mirrors ignoreError:true).
69
+ // onTick is invoked every tickMs while claude is running; stopped on close.
70
+ // shell:true on Windows so that `claude.cmd`/`claude.ps1` shims resolve via PATH.
71
+ function runClaudePromptAsync(prompt, options = {}) {
72
+ return new Promise((resolve) => {
73
+ let child = null;
74
+ let tickTimer = null;
75
+ const cleanup = () => { if (tickTimer) { clearInterval(tickTimer); tickTimer = null; } };
76
+ const bail = () => {
77
+ cleanup();
78
+ // Best-effort kill so we don't leave orphaned claude processes when we
79
+ // fail to hand off the prompt or encounter an unexpected spawn error.
80
+ if (child && !child.killed) { try { child.kill(); } catch (_e) { /* ignore */ } }
81
+ resolve(false);
82
+ };
83
+ try {
84
+ // Node 18+ emits DEP0190 when mixing shell:true with an args array (the
85
+ // args aren't escaped just concatenated). On Windows we need shell:true
86
+ // so `claude.cmd`/`claude.ps1` shims resolve via PATH, so we build the
87
+ // whole command as a single string there and pass an empty args array.
88
+ // The flags are hardcoded literals (no user input) so there's no
89
+ // injection surface either way; this just silences the warning.
90
+ const isWin = process.platform === "win32";
91
+ const spawnCmd = isWin ? "claude -p --dangerously-skip-permissions" : "claude";
92
+ const spawnArgs = isWin ? [] : ["-p", "--dangerously-skip-permissions"];
93
+ child = spawn(spawnCmd, spawnArgs, {
94
+ cwd: options.cwd || PROJECT_ROOT,
95
+ stdio: ["pipe", "inherit", "inherit"],
96
+ shell: isWin,
97
+ });
98
+ if (typeof options.onTick === "function" && options.tickMs > 0) {
99
+ // Fire once immediately so the user sees the progress line right away,
100
+ // instead of waiting a full tick interval for any feedback.
101
+ try { options.onTick(); } catch (_e) { /* swallow */ }
102
+ tickTimer = setInterval(() => {
103
+ try { options.onTick(); } catch (_e) { /* swallow — progress is best-effort */ }
104
+ }, options.tickMs);
105
+ }
106
+ child.on("close", (code) => { cleanup(); resolve(code === 0); });
107
+ child.on("error", () => { cleanup(); resolve(false); });
108
+ child.stdin.on("error", () => { bail(); });
109
+ child.stdin.write(prompt);
110
+ child.stdin.end();
111
+ } catch (_e) {
112
+ // Catches synchronous throws from spawn (e.g. ENAMETOOLONG on some
113
+ // platforms) or from the initial stdin.write before listeners were
114
+ // attached. Either way: no orphan child, no leaked interval.
115
+ bail();
116
+ }
117
+ });
118
+ }
119
+
120
+ // Run claude -p but CAPTURE stdout instead of inheriting it.
121
+ // Returns the captured stdout string on success, or null on failure.
122
+ // Used for short tasks where we need the response content (e.g. translation).
123
+ // maxBuffer default is 10MB — enough for document translation.
124
+ function runClaudeCapture(prompt, options = {}) {
125
+ try {
126
+ const out = execSync("claude -p --dangerously-skip-permissions", {
127
+ input: prompt,
128
+ cwd: options.cwd || PROJECT_ROOT,
129
+ stdio: ["pipe", "pipe", "pipe"],
130
+ encoding: "utf-8",
131
+ timeout: options.timeout || 0,
132
+ maxBuffer: options.maxBuffer || 10 * 1024 * 1024,
133
+ });
134
+ return out;
135
+ } catch (_e) {
136
+ return null;
137
+ }
138
+ }
139
+
140
+ // ─── Filesystem ─────────────────────────────────────────────────
141
+ // ensureDir: delegated to lib/safe-fs.js (single source of truth)
142
+ // fileExists: alias for existsSafe from lib/safe-fs.js
143
+ const fileExists = existsSafe;
144
+
145
+ // readFile: intentionally throws on error (CLI needs hard failure, unlike safe-fs fallback)
146
+ function readFile(p) {
147
+ return fs.readFileSync(p, "utf-8");
148
+ }
149
+
150
+ function injectProjectRoot(text) {
151
+ // Normalize to forward slashes for prompts (Claude interprets backslashes as escapes)
152
+ const normalizedRoot = PROJECT_ROOT.replace(/\\/g, "/");
153
+ // Use a replacement function so that `$`, `$1`, `$&`, `$$` in the
154
+ // project path (rare but possible on some systems) are preserved as
155
+ // literal characters rather than interpreted as regex specials.
156
+ return text.replace(/\{\{PROJECT_ROOT\}\}/g, () => normalizedRoot);
157
+ }
158
+
159
+ // ─── Helpers ────────────────────────────────────────────────────
160
+ function pad(str, len) {
161
+ return str.length >= len ? str : str + " ".repeat(len - str.length);
162
+ }
163
+
164
+ function countFiles() {
165
+ try {
166
+ let count = 0;
167
+ const skipDirs = ["node_modules", "generated"];
168
+ const visited = new Set();
169
+ const scan = (dir) => {
170
+ if (!fs.existsSync(dir)) return;
171
+ let realDir;
172
+ try { realDir = fs.realpathSync(dir); } catch (_e) { realDir = dir; }
173
+ if (visited.has(realDir)) return;
174
+ visited.add(realDir);
175
+ for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
176
+ if (skipDirs.includes(entry.name)) continue;
177
+ const full = path.join(dir, entry.name);
178
+ if (entry.isDirectory()) scan(full);
179
+ else count++;
180
+ }
181
+ };
182
+ scan(path.join(PROJECT_ROOT, ".claude"));
183
+ scan(path.join(PROJECT_ROOT, "claudeos-core"));
184
+ return count;
185
+ } catch (e) {
186
+ return "?";
187
+ }
188
+ }
189
+
190
+ function countPass1Files() {
191
+ try {
192
+ return fs
193
+ .readdirSync(GENERATED_DIR)
194
+ .filter((f) => f.startsWith("pass1-") && f.endsWith(".json")).length;
195
+ } catch (e) {
196
+ return 0;
197
+ }
198
+ }
199
+
200
+ module.exports = {
201
+ TOOLS_DIR, PROJECT_ROOT, GENERATED_DIR,
202
+ SUPPORTED_LANGS, LANG_CODES, isValidLang,
203
+ log, header, run, runClaudePrompt, runClaudePromptAsync, runClaudeCapture,
204
+ ensureDir, fileExists, readFile, injectProjectRoot,
205
+ pad, countFiles, countPass1Files,
206
+ };