@phuetz/code-buddy 0.1.0 → 0.1.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 (206) hide show
  1. package/.codebuddy/skills/bundled/brave-search/SKILL.md +490 -0
  2. package/.codebuddy/skills/bundled/exa-search/SKILL.md +1122 -0
  3. package/.codebuddy/skills/bundled/perplexity/SKILL.md +748 -0
  4. package/.codebuddy/skills/bundled/playwright/SKILL.md +520 -0
  5. package/.codebuddy/skills/bundled/puppeteer/SKILL.md +708 -0
  6. package/.codebuddy/skills/bundled/web-fetch/SKILL.md +1003 -0
  7. package/README.md +56 -0
  8. package/dist/agent/agent-state.d.ts +3 -3
  9. package/dist/agent/agent-state.js +6 -6
  10. package/dist/agent/agent-state.js.map +1 -1
  11. package/dist/agent/base-agent.d.ts +4 -4
  12. package/dist/agent/base-agent.js +22 -9
  13. package/dist/agent/base-agent.js.map +1 -1
  14. package/dist/agent/cache-trace.d.ts +56 -0
  15. package/dist/agent/cache-trace.js +98 -0
  16. package/dist/agent/cache-trace.js.map +1 -0
  17. package/dist/agent/codebuddy-agent.js +3 -2
  18. package/dist/agent/codebuddy-agent.js.map +1 -1
  19. package/dist/agent/execution/agent-executor.d.ts +4 -4
  20. package/dist/agent/execution/agent-executor.js +41 -7
  21. package/dist/agent/execution/agent-executor.js.map +1 -1
  22. package/dist/agent/facades/agent-context-facade.js +1 -3
  23. package/dist/agent/facades/agent-context-facade.js.map +1 -1
  24. package/dist/agent/facades/message-history-manager.js +14 -12
  25. package/dist/agent/facades/message-history-manager.js.map +1 -1
  26. package/dist/agent/facades/session-facade.d.ts +3 -3
  27. package/dist/agent/facades/session-facade.js +6 -6
  28. package/dist/agent/facades/session-facade.js.map +1 -1
  29. package/dist/agent/history-repair.d.ts +37 -0
  30. package/dist/agent/history-repair.js +124 -0
  31. package/dist/agent/history-repair.js.map +1 -0
  32. package/dist/agent/specialized/archive-agent.d.ts +3 -0
  33. package/dist/agent/specialized/archive-agent.js +71 -31
  34. package/dist/agent/specialized/archive-agent.js.map +1 -1
  35. package/dist/agent/specialized/security-review/agent.js +19 -8
  36. package/dist/agent/specialized/security-review/agent.js.map +1 -1
  37. package/dist/agent/tool-executor.js +5 -0
  38. package/dist/agent/tool-executor.js.map +1 -1
  39. package/dist/agent/turn-diff-tracker.d.ts +79 -0
  40. package/dist/agent/turn-diff-tracker.js +195 -0
  41. package/dist/agent/turn-diff-tracker.js.map +1 -0
  42. package/dist/checkpoints/checkpoint-versioning.js +78 -20
  43. package/dist/checkpoints/checkpoint-versioning.js.map +1 -1
  44. package/dist/cli/config-loader.js +2 -4
  45. package/dist/cli/config-loader.js.map +1 -1
  46. package/dist/commands/handlers/fcs-handlers.js +1 -1
  47. package/dist/commands/handlers/fcs-handlers.js.map +1 -1
  48. package/dist/commands/handlers/memory-handlers.js +2 -1
  49. package/dist/commands/handlers/memory-handlers.js.map +1 -1
  50. package/dist/commands/handlers/worktree-handlers.js +11 -0
  51. package/dist/commands/handlers/worktree-handlers.js.map +1 -1
  52. package/dist/commands/mcp.d.ts +1 -0
  53. package/dist/commands/mcp.js +66 -7
  54. package/dist/commands/mcp.js.map +1 -1
  55. package/dist/commands/pipeline.js +25 -13
  56. package/dist/commands/pipeline.js.map +1 -1
  57. package/dist/config/model-tools.d.ts +41 -0
  58. package/dist/config/model-tools.js +194 -0
  59. package/dist/config/model-tools.js.map +1 -0
  60. package/dist/context/context-manager-v2.d.ts +2 -1
  61. package/dist/context/context-manager-v2.js +34 -5
  62. package/dist/context/context-manager-v2.js.map +1 -1
  63. package/dist/daemon/daemon-manager.js +23 -19
  64. package/dist/daemon/daemon-manager.js.map +1 -1
  65. package/dist/database/database-manager.d.ts +4 -0
  66. package/dist/database/database-manager.js +16 -7
  67. package/dist/database/database-manager.js.map +1 -1
  68. package/dist/desktop-automation/nutjs-provider.js +89 -0
  69. package/dist/desktop-automation/nutjs-provider.js.map +1 -1
  70. package/dist/fcs/builtins.d.ts +2 -6
  71. package/dist/fcs/builtins.js +2 -568
  72. package/dist/fcs/builtins.js.map +1 -1
  73. package/dist/fcs/codebuddy-bindings.d.ts +3 -43
  74. package/dist/fcs/codebuddy-bindings.js +2 -606
  75. package/dist/fcs/codebuddy-bindings.js.map +1 -1
  76. package/dist/fcs/index.d.ts +2 -27
  77. package/dist/fcs/index.js +2 -53
  78. package/dist/fcs/index.js.map +1 -1
  79. package/dist/fcs/lexer.d.ts +2 -37
  80. package/dist/fcs/lexer.js +2 -459
  81. package/dist/fcs/lexer.js.map +1 -1
  82. package/dist/fcs/parser.d.ts +2 -68
  83. package/dist/fcs/parser.js +2 -893
  84. package/dist/fcs/parser.js.map +1 -1
  85. package/dist/fcs/runtime.d.ts +2 -59
  86. package/dist/fcs/runtime.js +2 -623
  87. package/dist/fcs/runtime.js.map +1 -1
  88. package/dist/fcs/script-registry.d.ts +3 -69
  89. package/dist/fcs/script-registry.js +2 -219
  90. package/dist/fcs/script-registry.js.map +1 -1
  91. package/dist/fcs/sync-bindings.d.ts +3 -101
  92. package/dist/fcs/sync-bindings.js +2 -410
  93. package/dist/fcs/sync-bindings.js.map +1 -1
  94. package/dist/fcs/types.d.ts +2 -285
  95. package/dist/fcs/types.js +2 -103
  96. package/dist/fcs/types.js.map +1 -1
  97. package/dist/hooks/use-input-handler.d.ts +1 -1
  98. package/dist/index.js +5 -2
  99. package/dist/index.js.map +1 -1
  100. package/dist/input/voice-control.js +11 -5
  101. package/dist/input/voice-control.js.map +1 -1
  102. package/dist/integrations/json-rpc/server.js +5 -5
  103. package/dist/integrations/json-rpc/server.js.map +1 -1
  104. package/dist/integrations/mcp/mcp-server.js +1 -1
  105. package/dist/integrations/mcp/mcp-server.js.map +1 -1
  106. package/dist/mcp/client.js +2 -1
  107. package/dist/mcp/client.js.map +1 -1
  108. package/dist/mcp/config.js +89 -5
  109. package/dist/mcp/config.js.map +1 -1
  110. package/dist/mcp/mcp-client.js +65 -14
  111. package/dist/mcp/mcp-client.js.map +1 -1
  112. package/dist/mcp/transports.d.ts +0 -1
  113. package/dist/mcp/transports.js +1 -5
  114. package/dist/mcp/transports.js.map +1 -1
  115. package/dist/mcp/types.d.ts +2 -0
  116. package/dist/persistence/session-lock.d.ts +42 -0
  117. package/dist/persistence/session-lock.js +165 -0
  118. package/dist/persistence/session-lock.js.map +1 -0
  119. package/dist/persistence/session-store.d.ts +18 -3
  120. package/dist/persistence/session-store.js +90 -21
  121. package/dist/persistence/session-store.js.map +1 -1
  122. package/dist/plugins/isolated-plugin-runner.d.ts +6 -0
  123. package/dist/plugins/isolated-plugin-runner.js +19 -1
  124. package/dist/plugins/isolated-plugin-runner.js.map +1 -1
  125. package/dist/providers/local-llm-provider.js +21 -4
  126. package/dist/providers/local-llm-provider.js.map +1 -1
  127. package/dist/sandbox/docker-sandbox.js +7 -4
  128. package/dist/sandbox/docker-sandbox.js.map +1 -1
  129. package/dist/scripting/builtins.d.ts +8 -3
  130. package/dist/scripting/builtins.js +506 -355
  131. package/dist/scripting/builtins.js.map +1 -1
  132. package/dist/scripting/codebuddy-bindings.d.ts +47 -0
  133. package/dist/scripting/codebuddy-bindings.js +487 -0
  134. package/dist/scripting/codebuddy-bindings.js.map +1 -0
  135. package/dist/scripting/index.d.ts +33 -30
  136. package/dist/scripting/index.js +41 -36
  137. package/dist/scripting/index.js.map +1 -1
  138. package/dist/scripting/lexer.d.ts +31 -13
  139. package/dist/scripting/lexer.js +379 -292
  140. package/dist/scripting/lexer.js.map +1 -1
  141. package/dist/scripting/parser.d.ts +63 -44
  142. package/dist/scripting/parser.js +700 -473
  143. package/dist/scripting/parser.js.map +1 -1
  144. package/dist/scripting/runtime.d.ts +55 -24
  145. package/dist/scripting/runtime.js +600 -288
  146. package/dist/scripting/runtime.js.map +1 -1
  147. package/dist/scripting/script-registry.d.ts +54 -0
  148. package/dist/scripting/script-registry.js +202 -0
  149. package/dist/scripting/script-registry.js.map +1 -0
  150. package/dist/scripting/sync-bindings.d.ts +105 -0
  151. package/dist/scripting/sync-bindings.js +353 -0
  152. package/dist/scripting/sync-bindings.js.map +1 -0
  153. package/dist/scripting/types.d.ts +297 -199
  154. package/dist/scripting/types.js +86 -60
  155. package/dist/scripting/types.js.map +1 -1
  156. package/dist/search/usearch-index.js +42 -7
  157. package/dist/search/usearch-index.js.map +1 -1
  158. package/dist/security/bash-parser.d.ts +51 -0
  159. package/dist/security/bash-parser.js +327 -0
  160. package/dist/security/bash-parser.js.map +1 -0
  161. package/dist/security/skill-scanner.d.ts +36 -0
  162. package/dist/security/skill-scanner.js +149 -0
  163. package/dist/security/skill-scanner.js.map +1 -0
  164. package/dist/security/trust-folders.d.ts +1 -0
  165. package/dist/security/trust-folders.js +19 -1
  166. package/dist/security/trust-folders.js.map +1 -1
  167. package/dist/server/websocket/handler.js +15 -5
  168. package/dist/server/websocket/handler.js.map +1 -1
  169. package/dist/skills/eligibility.js +26 -4
  170. package/dist/skills/eligibility.js.map +1 -1
  171. package/dist/tasks/background-tasks.js +5 -1
  172. package/dist/tasks/background-tasks.js.map +1 -1
  173. package/dist/tools/apply-patch.d.ts +55 -0
  174. package/dist/tools/apply-patch.js +273 -0
  175. package/dist/tools/apply-patch.js.map +1 -0
  176. package/dist/tools/registry/bash-tools.js +6 -3
  177. package/dist/tools/registry/bash-tools.js.map +1 -1
  178. package/dist/tools/registry/misc-tools.js +1 -2
  179. package/dist/tools/registry/misc-tools.js.map +1 -1
  180. package/dist/tools/registry/search-tools.js +1 -1
  181. package/dist/tools/registry/search-tools.js.map +1 -1
  182. package/dist/tools/registry/text-editor-tools.js +1 -1
  183. package/dist/tools/registry/text-editor-tools.js.map +1 -1
  184. package/dist/tools/registry/todo-tools.js +37 -5
  185. package/dist/tools/registry/todo-tools.js.map +1 -1
  186. package/dist/tools/registry/tool-registry.js +5 -4
  187. package/dist/tools/registry/tool-registry.js.map +1 -1
  188. package/dist/tools/registry/web-tools.d.ts +1 -1
  189. package/dist/tools/registry/web-tools.js +28 -8
  190. package/dist/tools/registry/web-tools.js.map +1 -1
  191. package/dist/tools/text-editor.d.ts +1 -1
  192. package/dist/tools/text-editor.js +23 -5
  193. package/dist/tools/text-editor.js.map +1 -1
  194. package/dist/tools/web-search.d.ts +52 -37
  195. package/dist/tools/web-search.js +368 -163
  196. package/dist/tools/web-search.js.map +1 -1
  197. package/dist/ui/components/ChatInterface.d.ts +1 -1
  198. package/dist/utils/head-tail-truncation.d.ts +34 -0
  199. package/dist/utils/head-tail-truncation.js +98 -0
  200. package/dist/utils/head-tail-truncation.js.map +1 -0
  201. package/dist/utils/sanitize.d.ts +5 -0
  202. package/dist/utils/sanitize.js +19 -0
  203. package/dist/utils/sanitize.js.map +1 -1
  204. package/dist/utils/settings-manager.js +4 -4
  205. package/dist/utils/settings-manager.js.map +1 -1
  206. package/package.json +3 -1
@@ -1,7 +1,10 @@
1
1
  /**
2
- * Buddy Script Built-in Functions
2
+ * Unified Script Built-in Functions
3
3
  *
4
- * Provides bindings to code-buddy features for scripting
4
+ * Merged from FCS builtins (100+ functions, date/time, format, enumerate/zip)
5
+ * and Buddy Script builtins (ai.*, console.*, lazy agent loading, sandboxing)
6
+ *
7
+ * Extensions: .bs (primary), .fcs (backward-compatible alias)
5
8
  */
6
9
  import * as fs from 'fs';
7
10
  import * as path from 'path';
@@ -13,15 +16,12 @@ let cachedAgent = null;
13
16
  * Get or create the AI agent for script execution
14
17
  */
15
18
  async function getOrCreateAgent(config) {
16
- // Use provided agent if available
17
19
  if (config.agent) {
18
20
  return config.agent;
19
21
  }
20
- // Return cached agent if already created
21
22
  if (cachedAgent) {
22
23
  return cachedAgent;
23
24
  }
24
- // Try to create a new agent with lazy loading
25
25
  const apiKey = process.env.GROK_API_KEY;
26
26
  if (!apiKey) {
27
27
  logger.warn('GROK_API_KEY not set, AI operations will not be available');
@@ -40,12 +40,12 @@ async function getOrCreateAgent(config) {
40
40
  }
41
41
  }
42
42
  /**
43
- * Create all built-in functions for the Buddy Script runtime
43
+ * Create all built-in functions for the unified scripting runtime
44
44
  */
45
45
  export function createBuiltins(config, print) {
46
46
  const builtins = {};
47
47
  // ============================================
48
- // Core Functions
48
+ // Core I/O
49
49
  // ============================================
50
50
  builtins.print = (...args) => {
51
51
  const message = args.map(a => stringify(a)).join(' ');
@@ -56,13 +56,47 @@ export function createBuiltins(config, print) {
56
56
  builtins.print(...args);
57
57
  return null;
58
58
  };
59
- builtins.typeof = (value) => {
60
- if (value === null)
61
- return 'null';
59
+ builtins.input = async (prompt) => {
60
+ if (prompt)
61
+ print(prompt);
62
+ return '';
63
+ };
64
+ // ============================================
65
+ // Type Conversion
66
+ // ============================================
67
+ builtins.int = (value) => {
68
+ if (typeof value === 'string') {
69
+ if (value.startsWith('0x') || value.startsWith('0X')) {
70
+ return parseInt(value, 16);
71
+ }
72
+ if (value.startsWith('0b') || value.startsWith('0B')) {
73
+ return parseInt(value.substring(2), 2);
74
+ }
75
+ return parseInt(value, 10);
76
+ }
77
+ return Math.floor(Number(value));
78
+ };
79
+ builtins.float = (value) => parseFloat(String(value));
80
+ builtins.num = (value) => Number(value);
81
+ builtins.str = (value) => stringify(value);
82
+ builtins.bool = (value) => {
83
+ if (value === null || value === undefined || value === false || value === 0 || value === '') {
84
+ return false;
85
+ }
86
+ return true;
87
+ };
88
+ builtins.array = (value) => {
62
89
  if (Array.isArray(value))
63
- return 'array';
64
- return typeof value;
90
+ return value;
91
+ if (typeof value === 'string')
92
+ return value.split('');
93
+ if (typeof value === 'object' && value !== null)
94
+ return Object.values(value);
95
+ return [value];
65
96
  };
97
+ // ============================================
98
+ // Collection Functions
99
+ // ============================================
66
100
  builtins.len = (value) => {
67
101
  if (typeof value === 'string')
68
102
  return value.length;
@@ -72,81 +106,47 @@ export function createBuiltins(config, print) {
72
106
  return Object.keys(value).length;
73
107
  return 0;
74
108
  };
75
- builtins.str = (value) => stringify(value);
76
- builtins.num = (value) => Number(value);
77
- builtins.bool = (value) => Boolean(value);
78
- builtins.int = (value) => Math.floor(Number(value));
79
- builtins.float = (value) => Number(value);
80
- // ============================================
81
- // Array/Object Functions
82
- // ============================================
83
- builtins.range = (start, end, step) => {
84
- const s = Number(start);
85
- const e = end !== undefined ? Number(end) : s;
86
- const st = step !== undefined ? Number(step) : 1;
87
- const actualStart = end !== undefined ? s : 0;
109
+ builtins.range = (...args) => {
110
+ let start = 0, end = 0, step = 1;
111
+ if (args.length === 1) {
112
+ end = args[0];
113
+ }
114
+ else if (args.length === 2) {
115
+ start = args[0];
116
+ end = args[1];
117
+ }
118
+ else if (args.length >= 3) {
119
+ start = args[0];
120
+ end = args[1];
121
+ step = args[2];
122
+ }
88
123
  const result = [];
89
- if (st > 0) {
90
- for (let i = actualStart; i < e; i += st)
124
+ if (step > 0) {
125
+ for (let i = start; i < end; i += step)
91
126
  result.push(i);
92
127
  }
93
- else if (st < 0) {
94
- for (let i = actualStart; i > e; i += st)
128
+ else if (step < 0) {
129
+ for (let i = start; i > end; i += step)
95
130
  result.push(i);
96
131
  }
97
132
  return result;
98
133
  };
99
- builtins.keys = (obj) => {
100
- if (typeof obj === 'object' && obj !== null) {
101
- return Object.keys(obj);
102
- }
103
- return [];
104
- };
105
- builtins.values = (obj) => {
106
- if (typeof obj === 'object' && obj !== null) {
107
- return Object.values(obj);
108
- }
109
- return [];
110
- };
111
- builtins.entries = (obj) => {
112
- if (typeof obj === 'object' && obj !== null) {
113
- return Object.entries(obj);
114
- }
115
- return [];
116
- };
117
- builtins.push = (arr, ...items) => {
118
- if (Array.isArray(arr)) {
119
- arr.push(...items);
120
- return arr; // Return array for chaining
121
- }
122
- throw new Error('push() requires an array as first argument. Got: ' + (typeof arr));
134
+ builtins.enumerate = (arr) => {
135
+ return arr.map((item, index) => [index, item]);
123
136
  };
124
- builtins.pop = (arr) => {
125
- if (Array.isArray(arr)) {
126
- return arr.pop();
127
- }
128
- throw new Error('pop() requires an array as argument. Got: ' + (typeof arr));
129
- };
130
- builtins.join = (arr, separator) => {
131
- if (Array.isArray(arr)) {
132
- return arr.map(stringify).join(separator !== undefined ? String(separator) : ',');
133
- }
134
- throw new Error('join() requires an array as first argument. Got: ' + (typeof arr));
135
- };
136
- builtins.split = (str, separator) => {
137
- return String(str).split(separator !== undefined ? String(separator) : '');
138
- };
139
- builtins.slice = (arr, start, end) => {
140
- if (Array.isArray(arr) || typeof arr === 'string') {
141
- return arr.slice(start !== undefined ? Number(start) : undefined, end !== undefined ? Number(end) : undefined);
137
+ builtins.zip = (...arrays) => {
138
+ const minLen = Math.min(...arrays.map(a => a.length));
139
+ const result = [];
140
+ for (let i = 0; i < minLen; i++) {
141
+ result.push(arrays.map(a => a[i]));
142
142
  }
143
- throw new Error('slice() requires an array or string as first argument. Got: ' + (typeof arr));
143
+ return result;
144
144
  };
145
145
  builtins.map = async (arr, fn) => {
146
146
  if (!Array.isArray(arr))
147
- throw new Error('map() requires an array as first argument. Got: ' + (typeof arr));
147
+ throw new Error('map() requires an array as first argument');
148
148
  if (typeof fn !== 'function')
149
- throw new Error('map() requires a function as second argument. Got: ' + (typeof fn));
149
+ throw new Error('map() requires a function as second argument');
150
150
  const results = [];
151
151
  for (let i = 0; i < arr.length; i++) {
152
152
  results.push(await fn(arr[i], i));
@@ -155,29 +155,82 @@ export function createBuiltins(config, print) {
155
155
  };
156
156
  builtins.filter = async (arr, fn) => {
157
157
  if (!Array.isArray(arr))
158
- throw new Error('filter() requires an array as first argument. Got: ' + (typeof arr));
158
+ throw new Error('filter() requires an array as first argument');
159
159
  if (typeof fn !== 'function')
160
- throw new Error('filter() requires a function as second argument. Got: ' + (typeof fn));
160
+ throw new Error('filter() requires a function as second argument');
161
161
  const results = [];
162
162
  for (let i = 0; i < arr.length; i++) {
163
- if (await fn(arr[i], i)) {
163
+ if (await fn(arr[i], i))
164
164
  results.push(arr[i]);
165
- }
166
165
  }
167
166
  return results;
168
167
  };
168
+ builtins.reduce = async (arr, fn, initial) => {
169
+ let acc = initial !== undefined ? initial : arr[0];
170
+ const startIdx = initial !== undefined ? 0 : 1;
171
+ for (let i = startIdx; i < arr.length; i++) {
172
+ acc = await fn(acc, arr[i], i);
173
+ }
174
+ return acc;
175
+ };
169
176
  builtins.find = async (arr, fn) => {
170
177
  if (!Array.isArray(arr))
171
- throw new Error('find() requires an array as first argument. Got: ' + (typeof arr));
178
+ throw new Error('find() requires an array as first argument');
172
179
  if (typeof fn !== 'function')
173
- throw new Error('find() requires a function as second argument. Got: ' + (typeof fn));
180
+ throw new Error('find() requires a function as second argument');
174
181
  for (let i = 0; i < arr.length; i++) {
175
- if (await fn(arr[i], i)) {
182
+ if (await fn(arr[i], i))
176
183
  return arr[i];
177
- }
178
184
  }
179
185
  return null;
180
186
  };
187
+ builtins.every = async (arr, fn) => {
188
+ for (const item of arr) {
189
+ if (!(await fn(item)))
190
+ return false;
191
+ }
192
+ return true;
193
+ };
194
+ builtins.some = async (arr, fn) => {
195
+ for (const item of arr) {
196
+ if (await fn(item))
197
+ return true;
198
+ }
199
+ return false;
200
+ };
201
+ builtins.sort = (arr, fn) => {
202
+ const copy = [...arr];
203
+ if (fn) {
204
+ copy.sort((a, b) => {
205
+ const result = fn(a, b);
206
+ return typeof result === 'number' ? result : 0;
207
+ });
208
+ }
209
+ else {
210
+ copy.sort((a, b) => {
211
+ if (typeof a === 'number' && typeof b === 'number')
212
+ return a - b;
213
+ return String(a).localeCompare(String(b));
214
+ });
215
+ }
216
+ return copy;
217
+ };
218
+ builtins.reverse = (arr) => [...arr].reverse();
219
+ builtins.slice = (arr, start, end) => arr.slice(start, end);
220
+ builtins.concat = (...arrays) => arrays.flat();
221
+ builtins.push = (arr, ...items) => {
222
+ if (!Array.isArray(arr))
223
+ throw new Error('push() requires an array as first argument');
224
+ arr.push(...items);
225
+ return arr;
226
+ };
227
+ builtins.pop = (arr) => {
228
+ if (!Array.isArray(arr))
229
+ throw new Error('pop() requires an array as first argument');
230
+ return arr.pop();
231
+ };
232
+ builtins.shift = (arr) => arr.shift();
233
+ builtins.unshift = (arr, ...items) => { arr.unshift(...items); return arr; };
181
234
  builtins.includes = (arr, item) => {
182
235
  if (Array.isArray(arr))
183
236
  return arr.includes(item);
@@ -185,28 +238,71 @@ export function createBuiltins(config, print) {
185
238
  return arr.includes(String(item));
186
239
  return false;
187
240
  };
241
+ builtins.indexOf = (arr, item) => arr.indexOf(item);
242
+ builtins.join = (arr, separator = ',') => {
243
+ if (!Array.isArray(arr))
244
+ throw new Error('join() requires an array as first argument');
245
+ return arr.map(a => stringify(a)).join(separator);
246
+ };
247
+ builtins.split = (str, separator) => String(str).split(separator);
248
+ builtins.keys = (obj) => {
249
+ if (typeof obj === 'object' && obj !== null)
250
+ return Object.keys(obj);
251
+ return [];
252
+ };
253
+ builtins.values = (obj) => {
254
+ if (typeof obj === 'object' && obj !== null)
255
+ return Object.values(obj);
256
+ return [];
257
+ };
258
+ builtins.entries = (obj) => {
259
+ if (typeof obj === 'object' && obj !== null)
260
+ return Object.entries(obj);
261
+ return [];
262
+ };
188
263
  // ============================================
189
264
  // String Functions
190
265
  // ============================================
191
- builtins.trim = (str) => String(str).trim();
192
- builtins.lower = (str) => String(str).toLowerCase();
193
266
  builtins.upper = (str) => String(str).toUpperCase();
194
- builtins.replace = (str, search, replacement) => {
195
- return String(str).replace(String(search), String(replacement));
196
- };
197
- builtins.replaceAll = (str, search, replacement) => {
198
- return String(str).replaceAll(String(search), String(replacement));
267
+ builtins.lower = (str) => String(str).toLowerCase();
268
+ builtins.trim = (str) => String(str).trim();
269
+ builtins.ltrim = (str) => String(str).trimStart();
270
+ builtins.rtrim = (str) => String(str).trimEnd();
271
+ builtins.startsWith = (str, prefix) => String(str).startsWith(prefix);
272
+ builtins.endsWith = (str, suffix) => String(str).endsWith(suffix);
273
+ builtins.contains = (str, substr) => String(str).includes(substr);
274
+ builtins.replace = (str, search, replace) => String(str).split(search).join(replace);
275
+ builtins.replaceAll = (str, search, replace) => String(str).split(search).join(replace);
276
+ builtins.substr = (str, start, length) => {
277
+ if (length !== undefined)
278
+ return String(str).substring(start, start + length);
279
+ return String(str).substring(start);
280
+ };
281
+ builtins.charAt = (str, index) => String(str).charAt(index);
282
+ builtins.repeat = (str, count) => String(str).repeat(count);
283
+ builtins.padStart = (str, length, char = ' ') => String(str).padStart(length, char);
284
+ builtins.padEnd = (str, length, char = ' ') => String(str).padEnd(length, char);
285
+ builtins.format = (template, ...args) => {
286
+ let result = template;
287
+ for (let i = 0; i < args.length; i++) {
288
+ result = result.replace(`{${i}}`, stringify(args[i]));
289
+ result = result.replace('{}', stringify(args[i]));
290
+ }
291
+ return result;
199
292
  };
200
- builtins.startsWith = (str, prefix) => String(str).startsWith(String(prefix));
201
- builtins.endsWith = (str, suffix) => String(str).endsWith(String(suffix));
202
- builtins.contains = (str, search) => String(str).includes(String(search));
203
293
  builtins.match = (str, pattern) => {
204
- const match = String(str).match(new RegExp(String(pattern)));
205
- return match ? Array.from(match) : null;
294
+ const m = String(str).match(new RegExp(String(pattern)));
295
+ return m ? Array.from(m) : null;
206
296
  };
207
297
  // ============================================
208
298
  // Math Functions
209
299
  // ============================================
300
+ builtins.abs = Math.abs;
301
+ builtins.ceil = Math.ceil;
302
+ builtins.floor = Math.floor;
303
+ builtins.round = Math.round;
304
+ builtins.sqrt = Math.sqrt;
305
+ builtins.pow = Math.pow;
210
306
  builtins.min = (...args) => {
211
307
  const nums = args.flat().map(Number);
212
308
  return Math.min(...nums);
@@ -215,323 +311,363 @@ export function createBuiltins(config, print) {
215
311
  const nums = args.flat().map(Number);
216
312
  return Math.max(...nums);
217
313
  };
218
- builtins.abs = (n) => Math.abs(Number(n));
219
- builtins.floor = (n) => Math.floor(Number(n));
220
- builtins.ceil = (n) => Math.ceil(Number(n));
221
- builtins.round = (n) => Math.round(Number(n));
222
- builtins.sqrt = (n) => Math.sqrt(Number(n));
223
- builtins.pow = (base, exp) => Math.pow(Number(base), Number(exp));
314
+ builtins.sin = Math.sin;
315
+ builtins.cos = Math.cos;
316
+ builtins.tan = Math.tan;
317
+ builtins.log = Math.log;
318
+ builtins.log10 = Math.log10;
319
+ builtins.exp = Math.exp;
224
320
  builtins.random = (max) => {
225
- if (max !== undefined) {
321
+ if (max !== undefined)
226
322
  return Math.floor(Math.random() * Number(max));
227
- }
228
323
  return Math.random();
229
324
  };
325
+ builtins.randomInt = (min, max) => {
326
+ return Math.floor(Math.random() * (max - min + 1)) + min;
327
+ };
328
+ builtins.sum = (arr) => arr.reduce((a, b) => a + b, 0);
329
+ builtins.avg = (arr) => arr.length > 0 ? arr.reduce((a, b) => a + b, 0) / arr.length : 0;
330
+ // Constants
331
+ builtins.PI = Math.PI;
332
+ builtins.E = Math.E;
230
333
  // ============================================
231
- // Time Functions
334
+ // Date/Time Functions
232
335
  // ============================================
233
- builtins.time = () => Date.now();
234
- builtins.now = () => new Date().toISOString();
235
- builtins.sleep = async (ms) => {
236
- await new Promise(resolve => setTimeout(resolve, Number(ms)));
237
- return null;
336
+ builtins.now = () => Date.now();
337
+ builtins.date = () => new Date().toISOString().split('T')[0];
338
+ builtins.time = () => new Date().toISOString().split('T')[1].split('.')[0];
339
+ builtins.datetime = () => new Date().toISOString();
340
+ builtins.timestamp = () => Math.floor(Date.now() / 1000);
341
+ builtins.formatDate = (timestamp, format) => {
342
+ const d = new Date(timestamp);
343
+ if (!format)
344
+ return d.toISOString();
345
+ return format
346
+ .replace('YYYY', String(d.getFullYear()))
347
+ .replace('MM', String(d.getMonth() + 1).padStart(2, '0'))
348
+ .replace('DD', String(d.getDate()).padStart(2, '0'))
349
+ .replace('HH', String(d.getHours()).padStart(2, '0'))
350
+ .replace('mm', String(d.getMinutes()).padStart(2, '0'))
351
+ .replace('ss', String(d.getSeconds()).padStart(2, '0'));
238
352
  };
239
353
  // ============================================
240
- // File Operations (grok.file namespace)
354
+ // JSON Functions
241
355
  // ============================================
242
- const file = {
243
- read: (filePath) => {
244
- if (!config.enableFileOps)
245
- throw new Error('File operations are disabled in the current script configuration. Enable them with { enableFileOps: true }.');
356
+ builtins.jsonEncode = (value) => JSON.stringify(value, null, 2);
357
+ builtins.jsonDecode = (str) => JSON.parse(str);
358
+ const json = {
359
+ parse: (str) => JSON.parse(String(str)),
360
+ stringify: (value, indent) => {
361
+ return JSON.stringify(value, null, indent ? Number(indent) : undefined);
362
+ },
363
+ };
364
+ builtins.json = json;
365
+ builtins.JSON = json;
366
+ // ============================================
367
+ // Console (from Buddy Script)
368
+ // ============================================
369
+ builtins.console = {
370
+ log: builtins.print,
371
+ info: builtins.print,
372
+ warn: (...args) => { print('[WARN] ' + args.map(stringify).join(' ')); return null; },
373
+ error: (...args) => { print('[ERROR] ' + args.map(stringify).join(' ')); return null; },
374
+ };
375
+ // ============================================
376
+ // Math namespace (from Buddy Script)
377
+ // ============================================
378
+ builtins.Math = {
379
+ min: builtins.min,
380
+ max: builtins.max,
381
+ abs: builtins.abs,
382
+ floor: builtins.floor,
383
+ ceil: builtins.ceil,
384
+ round: builtins.round,
385
+ sqrt: builtins.sqrt,
386
+ pow: builtins.pow,
387
+ random: () => Math.random(),
388
+ PI: Math.PI,
389
+ E: Math.E,
390
+ };
391
+ builtins.Date = {
392
+ now: () => Date.now(),
393
+ parse: (str) => Date.parse(String(str)),
394
+ };
395
+ // ============================================
396
+ // File Operations (merged FCS flat + BS namespaced)
397
+ // ============================================
398
+ if (config.enableFileOps) {
399
+ // Flat FCS-style file functions
400
+ builtins.readFile = (filePath) => {
246
401
  const fullPath = resolvePath(String(filePath), config.workdir);
402
+ return fs.readFileSync(fullPath, 'utf-8');
403
+ };
404
+ builtins.writeFile = (filePath, content) => {
247
405
  if (config.dryRun) {
248
- print(`[DRY RUN] file.read: ${fullPath}`);
249
- return '';
406
+ print(`[DRY RUN] Would write to: ${filePath}`);
407
+ return true;
250
408
  }
251
- return fs.readFileSync(fullPath, 'utf-8');
252
- },
253
- write: (filePath, content) => {
254
- if (!config.enableFileOps)
255
- throw new Error('File operations are disabled in the current script configuration. Enable them with { enableFileOps: true }.');
256
409
  const fullPath = resolvePath(String(filePath), config.workdir);
410
+ fs.writeFileSync(fullPath, content);
411
+ return true;
412
+ };
413
+ builtins.appendFile = (filePath, content) => {
257
414
  if (config.dryRun) {
258
- print(`[DRY RUN] file.write: ${fullPath}`);
415
+ print(`[DRY RUN] Would append to: ${filePath}`);
259
416
  return true;
260
417
  }
261
- fs.writeFileSync(fullPath, String(content));
262
- return true;
263
- },
264
- append: (filePath, content) => {
265
- if (!config.enableFileOps)
266
- throw new Error('File operations are disabled in the current script configuration. Enable them with { enableFileOps: true }.');
267
418
  const fullPath = resolvePath(String(filePath), config.workdir);
419
+ fs.appendFileSync(fullPath, content);
420
+ return true;
421
+ };
422
+ builtins.fileExists = (filePath) => fs.existsSync(resolvePath(String(filePath), config.workdir));
423
+ builtins.isFile = (filePath) => {
424
+ try {
425
+ return fs.statSync(resolvePath(String(filePath), config.workdir)).isFile();
426
+ }
427
+ catch {
428
+ return false;
429
+ }
430
+ };
431
+ builtins.isDir = (filePath) => {
432
+ try {
433
+ return fs.statSync(resolvePath(String(filePath), config.workdir)).isDirectory();
434
+ }
435
+ catch {
436
+ return false;
437
+ }
438
+ };
439
+ builtins.mkdir = (dirPath) => {
268
440
  if (config.dryRun) {
269
- print(`[DRY RUN] file.append: ${fullPath}`);
441
+ print(`[DRY RUN] Would create directory: ${dirPath}`);
270
442
  return true;
271
443
  }
272
- fs.appendFileSync(fullPath, String(content));
444
+ fs.mkdirSync(resolvePath(String(dirPath), config.workdir), { recursive: true });
273
445
  return true;
274
- },
275
- exists: (filePath) => {
276
- const fullPath = resolvePath(String(filePath), config.workdir);
277
- return fs.existsSync(fullPath);
278
- },
279
- delete: (filePath) => {
280
- if (!config.enableFileOps)
281
- throw new Error('File operations are disabled in the current script configuration. Enable them with { enableFileOps: true }.');
282
- const fullPath = resolvePath(String(filePath), config.workdir);
446
+ };
447
+ builtins.rmdir = (dirPath) => {
283
448
  if (config.dryRun) {
284
- print(`[DRY RUN] file.delete: ${fullPath}`);
449
+ print(`[DRY RUN] Would remove directory: ${dirPath}`);
285
450
  return true;
286
451
  }
287
- fs.unlinkSync(fullPath);
452
+ fs.rmSync(resolvePath(String(dirPath), config.workdir), { recursive: true });
288
453
  return true;
289
- },
290
- copy: (src, dest) => {
291
- if (!config.enableFileOps)
292
- throw new Error('File operations are disabled in the current script configuration. Enable them with { enableFileOps: true }.');
293
- const srcPath = resolvePath(String(src), config.workdir);
294
- const destPath = resolvePath(String(dest), config.workdir);
454
+ };
455
+ builtins.remove = (filePath) => {
295
456
  if (config.dryRun) {
296
- print(`[DRY RUN] file.copy: ${srcPath} -> ${destPath}`);
457
+ print(`[DRY RUN] Would remove: ${filePath}`);
297
458
  return true;
298
459
  }
299
- fs.copyFileSync(srcPath, destPath);
460
+ fs.unlinkSync(resolvePath(String(filePath), config.workdir));
300
461
  return true;
301
- },
302
- move: (src, dest) => {
303
- if (!config.enableFileOps)
304
- throw new Error('File operations are disabled in the current script configuration. Enable them with { enableFileOps: true }.');
305
- const srcPath = resolvePath(String(src), config.workdir);
306
- const destPath = resolvePath(String(dest), config.workdir);
462
+ };
463
+ builtins.rename = (oldPath, newPath) => {
307
464
  if (config.dryRun) {
308
- print(`[DRY RUN] file.move: ${srcPath} -> ${destPath}`);
465
+ print(`[DRY RUN] Would rename: ${oldPath} -> ${newPath}`);
309
466
  return true;
310
467
  }
311
- fs.renameSync(srcPath, destPath);
468
+ fs.renameSync(resolvePath(String(oldPath), config.workdir), resolvePath(String(newPath), config.workdir));
312
469
  return true;
313
- },
314
- list: (dirPath) => {
315
- const fullPath = resolvePath(String(dirPath), config.workdir);
316
- return fs.readdirSync(fullPath);
317
- },
318
- mkdir: (dirPath) => {
319
- if (!config.enableFileOps)
320
- throw new Error('File operations are disabled in the current script configuration. Enable them with { enableFileOps: true }.');
321
- const fullPath = resolvePath(String(dirPath), config.workdir);
470
+ };
471
+ builtins.copy = (src, dest) => {
322
472
  if (config.dryRun) {
323
- print(`[DRY RUN] file.mkdir: ${fullPath}`);
473
+ print(`[DRY RUN] Would copy: ${src} -> ${dest}`);
324
474
  return true;
325
475
  }
326
- fs.mkdirSync(fullPath, { recursive: true });
476
+ fs.copyFileSync(resolvePath(String(src), config.workdir), resolvePath(String(dest), config.workdir));
327
477
  return true;
328
- },
329
- stat: (filePath) => {
330
- const fullPath = resolvePath(String(filePath), config.workdir);
331
- const stats = fs.statSync(fullPath);
332
- return {
333
- size: stats.size,
334
- isFile: stats.isFile(),
335
- isDirectory: stats.isDirectory(),
336
- created: stats.birthtime.toISOString(),
337
- modified: stats.mtime.toISOString(),
338
- };
339
- },
340
- glob: (pattern) => {
341
- // Simple glob implementation
342
- const fullPath = resolvePath(String(pattern), config.workdir);
343
- const dir = path.dirname(fullPath);
344
- const base = path.basename(fullPath);
345
- if (!fs.existsSync(dir))
478
+ };
479
+ builtins.listDir = (dirPath) => fs.readdirSync(resolvePath(String(dirPath), config.workdir));
480
+ builtins.glob = (pattern) => {
481
+ const dir = path.dirname(pattern);
482
+ const filePattern = path.basename(pattern);
483
+ const fullDir = resolvePath(dir, config.workdir);
484
+ if (!fs.existsSync(fullDir))
346
485
  return [];
347
- const files = fs.readdirSync(dir);
348
- const regex = new RegExp('^' + base.replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
349
- return files.filter(f => regex.test(f)).map(f => path.join(dir, f));
350
- },
351
- };
352
- builtins.file = file;
486
+ const regex = new RegExp('^' + filePattern.replace(/\*/g, '.*').replace(/\?/g, '.') + '$');
487
+ return fs.readdirSync(fullDir).filter(f => regex.test(f)).map(f => path.join(dir, f));
488
+ };
489
+ builtins.fileSize = (filePath) => fs.statSync(resolvePath(String(filePath), config.workdir)).size;
490
+ builtins.fileMtime = (filePath) => fs.statSync(resolvePath(String(filePath), config.workdir)).mtimeMs;
491
+ // Path utilities
492
+ builtins.basename = path.basename;
493
+ builtins.dirname = path.dirname;
494
+ builtins.extname = path.extname;
495
+ builtins.joinPath = (...parts) => path.join(...parts);
496
+ builtins.resolvePath = (...parts) => path.resolve(config.workdir, ...parts);
497
+ // BS-style namespaced file object
498
+ builtins.file = {
499
+ read: (filePath) => builtins.readFile(String(filePath)),
500
+ write: (filePath, content) => builtins.writeFile(String(filePath), String(content)),
501
+ append: (filePath, content) => builtins.appendFile(String(filePath), String(content)),
502
+ exists: (filePath) => builtins.fileExists(String(filePath)),
503
+ delete: (filePath) => builtins.remove(String(filePath)),
504
+ copy: (src, dest) => builtins.copy(String(src), String(dest)),
505
+ move: (src, dest) => builtins.rename(String(src), String(dest)),
506
+ list: (dirPath) => builtins.listDir(String(dirPath)),
507
+ mkdir: (dirPath) => builtins.mkdir(String(dirPath)),
508
+ stat: (filePath) => {
509
+ const stats = fs.statSync(resolvePath(String(filePath), config.workdir));
510
+ return { size: stats.size, isFile: stats.isFile(), isDirectory: stats.isDirectory(), created: stats.birthtime.toISOString(), modified: stats.mtime.toISOString() };
511
+ },
512
+ glob: (pattern) => builtins.glob(String(pattern)),
513
+ };
514
+ }
353
515
  // ============================================
354
- // Bash/Shell Operations
516
+ // Shell/Bash Functions (merged FCS flat + BS namespaced)
355
517
  // ============================================
356
- const bash = {
357
- run: (command, options) => {
358
- if (!config.enableBash)
359
- throw new Error('Bash/shell commands are disabled in the current script configuration. Enable them with { enableBash: true }.');
360
- const opts = options || {};
518
+ if (config.enableBash) {
519
+ builtins.exec = (command) => {
361
520
  if (config.dryRun) {
362
- print(`[DRY RUN] bash: ${command}`);
363
- return { stdout: '', stderr: '', code: 0 };
521
+ print(`[DRY RUN] Would execute: ${command}`);
522
+ return '';
364
523
  }
365
524
  try {
366
- const stdout = execSync(String(command), {
367
- cwd: opts.cwd ? String(opts.cwd) : config.workdir,
368
- encoding: 'utf-8',
369
- timeout: opts.timeout ? Number(opts.timeout) : 30000,
370
- maxBuffer: 10 * 1024 * 1024,
371
- });
372
- return {
373
- stdout: stdout.trim(),
374
- stderr: '',
375
- code: 0,
376
- };
525
+ return execSync(command, { cwd: config.workdir, encoding: 'utf-8', timeout: config.timeout });
377
526
  }
378
- catch (error) {
379
- const execError = error;
380
- return {
381
- stdout: execError.stdout || '',
382
- stderr: execError.stderr || String(error),
383
- code: execError.status || 1,
384
- };
527
+ catch (err) {
528
+ throw new Error(`Command failed: ${err.message}`);
385
529
  }
386
- },
387
- exec: (command) => {
388
- const result = bash.run(command);
389
- if (result.code !== 0) {
390
- throw new Error(`Command failed with code ${result.code}`);
530
+ };
531
+ builtins.shell = async (command) => {
532
+ if (config.dryRun) {
533
+ print(`[DRY RUN] Would execute: ${command}`);
534
+ return { code: 0, stdout: '', stderr: '' };
391
535
  }
392
- return result.stdout;
393
- },
394
- spawn: async (command, args) => {
395
- if (!config.enableBash)
396
- throw new Error('Bash/shell commands are disabled in the current script configuration. Enable them with { enableBash: true }.');
397
- const argList = Array.isArray(args) ? args.map(String) : [];
398
- return new Promise((resolve, reject) => {
399
- const proc = spawn(String(command), argList, {
400
- cwd: config.workdir,
401
- shell: true,
402
- });
403
- let stdout = '';
404
- let stderr = '';
405
- proc.stdout?.on('data', (data) => {
406
- stdout += data.toString();
407
- if (config.verbose)
408
- print(data.toString());
409
- });
410
- proc.stderr?.on('data', (data) => {
411
- stderr += data.toString();
412
- });
413
- proc.on('close', (code) => {
414
- resolve({ stdout, stderr, code });
415
- });
416
- proc.on('error', reject);
536
+ return new Promise((resolve) => {
537
+ const child = spawn(command, { shell: true, cwd: config.workdir });
538
+ let stdout = '', stderr = '';
539
+ child.stdout.on('data', (data) => { stdout += data.toString(); });
540
+ child.stderr.on('data', (data) => { stderr += data.toString(); });
541
+ child.on('close', (code) => { resolve({ code: code ?? 0, stdout, stderr }); });
417
542
  });
418
- },
419
- };
420
- builtins.bash = bash;
543
+ };
544
+ // BS-style namespaced bash object
545
+ builtins.bash = {
546
+ run: (command, options) => {
547
+ if (!config.enableBash)
548
+ throw new Error('Bash commands are disabled');
549
+ const opts = options || {};
550
+ if (config.dryRun) {
551
+ print(`[DRY RUN] bash: ${command}`);
552
+ return { stdout: '', stderr: '', code: 0 };
553
+ }
554
+ try {
555
+ const stdout = execSync(String(command), {
556
+ cwd: opts.cwd ? String(opts.cwd) : config.workdir,
557
+ encoding: 'utf-8',
558
+ timeout: opts.timeout ? Number(opts.timeout) : 30000,
559
+ maxBuffer: 10 * 1024 * 1024,
560
+ });
561
+ return { stdout: stdout.trim(), stderr: '', code: 0 };
562
+ }
563
+ catch (error) {
564
+ const execError = error;
565
+ return { stdout: execError.stdout || '', stderr: execError.stderr || String(error), code: execError.status || 1 };
566
+ }
567
+ },
568
+ exec: (command) => {
569
+ const result = builtins.bash.run(command);
570
+ if (result.code !== 0)
571
+ throw new Error(`Command failed with code ${result.code}`);
572
+ return result.stdout;
573
+ },
574
+ spawn: async (command, args) => {
575
+ if (!config.enableBash)
576
+ throw new Error('Bash commands are disabled');
577
+ const argList = Array.isArray(args) ? args.map(String) : [];
578
+ return new Promise((resolve, reject) => {
579
+ const proc = spawn(String(command), argList, { cwd: config.workdir, shell: true });
580
+ let stdout = '', stderr = '';
581
+ proc.stdout?.on('data', (data) => { stdout += data.toString(); if (config.verbose)
582
+ print(data.toString()); });
583
+ proc.stderr?.on('data', (data) => { stderr += data.toString(); });
584
+ proc.on('close', (code) => { resolve({ stdout, stderr, code }); });
585
+ proc.on('error', reject);
586
+ });
587
+ },
588
+ };
589
+ }
421
590
  // ============================================
422
- // AI Operations (placeholder - will integrate with CodeBuddyAgent)
591
+ // AI Operations (from Buddy Script with lazy agent)
423
592
  // ============================================
424
- const ai = {
425
- ask: async (prompt) => {
426
- if (!config.enableAI)
427
- throw new Error('AI operations are disabled in the current script configuration. Enable them with { enableAI: true }.');
428
- if (config.dryRun) {
429
- print(`[DRY RUN] ai.ask: ${prompt}`);
430
- return '[AI Response Placeholder]';
431
- }
432
- const agent = await getOrCreateAgent(config);
433
- if (!agent) {
434
- throw new Error('AI agent not available. Ensure GROK_API_KEY is set.');
435
- }
436
- const result = await agent.processUserInput(String(prompt));
437
- return result.content || '';
438
- },
439
- chat: async (message) => {
440
- if (!config.enableAI)
441
- throw new Error('AI operations are disabled in the current script configuration. Enable them with { enableAI: true }.');
442
- if (config.dryRun) {
443
- print(`[DRY RUN] ai.chat: ${message}`);
444
- return '[AI Response Placeholder]';
445
- }
446
- const agent = await getOrCreateAgent(config);
447
- if (!agent) {
448
- throw new Error('AI agent not available. Ensure GROK_API_KEY is set.');
449
- }
450
- const result = await agent.processUserInput(String(message));
451
- return result.content || '';
452
- },
453
- complete: async (prompt, options) => {
454
- if (!config.enableAI)
455
- throw new Error('AI operations are disabled in the current script configuration. Enable them with { enableAI: true }.');
456
- if (config.dryRun) {
457
- print(`[DRY RUN] ai.complete: ${prompt}`);
458
- return '[AI Response Placeholder]';
459
- }
460
- const agent = await getOrCreateAgent(config);
461
- if (!agent) {
462
- throw new Error('AI agent not available. Ensure GROK_API_KEY is set.');
463
- }
464
- const opts = typeof options === 'object' && options !== null ? options : {};
465
- const result = await agent.processUserInput(String(prompt), opts);
466
- return result.content || '';
467
- },
468
- };
469
- builtins.ai = ai;
593
+ if (config.enableAI) {
594
+ const ai = {
595
+ ask: async (prompt) => {
596
+ if (config.dryRun) {
597
+ print(`[DRY RUN] ai.ask: ${prompt}`);
598
+ return '[AI Response Placeholder]';
599
+ }
600
+ const agent = await getOrCreateAgent(config);
601
+ if (!agent)
602
+ throw new Error('AI agent not available. Ensure GROK_API_KEY is set.');
603
+ const result = await agent.processUserInput(String(prompt));
604
+ return result.content || '';
605
+ },
606
+ chat: async (message) => {
607
+ if (config.dryRun) {
608
+ print(`[DRY RUN] ai.chat: ${message}`);
609
+ return '[AI Response Placeholder]';
610
+ }
611
+ const agent = await getOrCreateAgent(config);
612
+ if (!agent)
613
+ throw new Error('AI agent not available. Ensure GROK_API_KEY is set.');
614
+ const result = await agent.processUserInput(String(message));
615
+ return result.content || '';
616
+ },
617
+ complete: async (prompt, options) => {
618
+ if (config.dryRun) {
619
+ print(`[DRY RUN] ai.complete: ${prompt}`);
620
+ return '[AI Response Placeholder]';
621
+ }
622
+ const agent = await getOrCreateAgent(config);
623
+ if (!agent)
624
+ throw new Error('AI agent not available. Ensure GROK_API_KEY is set.');
625
+ const opts = typeof options === 'object' && options !== null ? options : {};
626
+ const result = await agent.processUserInput(String(prompt), opts);
627
+ return result.content || '';
628
+ },
629
+ };
630
+ builtins.ai = ai;
631
+ // FCS-style flat ai/grok function
632
+ builtins.grok = async (prompt) => {
633
+ return ai.ask(prompt);
634
+ };
635
+ }
470
636
  // ============================================
471
637
  // Environment & Config
472
638
  // ============================================
473
- const env = {
639
+ builtins.env = {
474
640
  get: (name) => process.env[String(name)] || null,
475
- set: (name, value) => {
476
- process.env[String(name)] = String(value);
477
- return true;
478
- },
641
+ set: (name, value) => { process.env[String(name)] = String(value); return true; },
479
642
  all: () => ({ ...process.env }),
480
643
  };
481
- builtins.env = env;
482
- const grok = {
483
- workdir: () => config.workdir,
484
- verbose: () => config.verbose,
485
- dryRun: () => config.dryRun,
486
- version: () => '1.0.0',
487
- };
488
- builtins.grok = grok;
489
- // ============================================
490
- // JSON
491
- // ============================================
492
- const json = {
493
- parse: (str) => JSON.parse(String(str)),
494
- stringify: (value, indent) => {
495
- return JSON.stringify(value, null, indent ? Number(indent) : undefined);
496
- },
497
- };
498
- builtins.json = json;
499
- builtins.JSON = json;
500
- // ============================================
501
- // Console (alias for print functions)
502
- // ============================================
503
- const console = {
504
- log: builtins.print,
505
- info: builtins.print,
506
- warn: (...args) => {
507
- print('[WARN] ' + args.map(stringify).join(' '));
508
- return null;
509
- },
510
- error: (...args) => {
511
- print('[ERROR] ' + args.map(stringify).join(' '));
512
- return null;
513
- },
514
- };
515
- builtins.console = console;
644
+ builtins.cwd = () => config.workdir;
516
645
  // ============================================
517
- // Math namespace (for compatibility)
646
+ // Utility Functions
518
647
  // ============================================
519
- builtins.Math = {
520
- min: builtins.min,
521
- max: builtins.max,
522
- abs: builtins.abs,
523
- floor: builtins.floor,
524
- ceil: builtins.ceil,
525
- round: builtins.round,
526
- sqrt: builtins.sqrt,
527
- pow: builtins.pow,
528
- random: () => Math.random(),
529
- PI: Math.PI,
530
- E: Math.E,
648
+ builtins.typeof = (value) => {
649
+ if (value === null)
650
+ return 'null';
651
+ if (Array.isArray(value))
652
+ return 'array';
653
+ return typeof value;
531
654
  };
532
- builtins.Date = {
533
- now: () => Date.now(),
534
- parse: (str) => Date.parse(String(str)),
655
+ builtins.isNull = (value) => value === null;
656
+ builtins.isNumber = (value) => typeof value === 'number';
657
+ builtins.isString = (value) => typeof value === 'string';
658
+ builtins.isBool = (value) => typeof value === 'boolean';
659
+ builtins.isArray = (value) => Array.isArray(value);
660
+ builtins.isDict = (value) => typeof value === 'object' && value !== null && !Array.isArray(value);
661
+ builtins.isFunction = (value) => typeof value === 'function';
662
+ builtins.sleep = (ms) => new Promise(resolve => setTimeout(resolve, ms));
663
+ builtins.exit = (code = 0) => process.exit(code);
664
+ builtins.assert = (condition, message) => {
665
+ if (!condition)
666
+ throw new Error(message || 'Assertion failed');
667
+ };
668
+ builtins.expect = (actual, expected, message) => {
669
+ if (actual !== expected)
670
+ throw new Error(message || `Expected ${stringify(expected)}, got ${stringify(actual)}`);
535
671
  };
536
672
  return builtins;
537
673
  }
@@ -543,8 +679,21 @@ function stringify(value) {
543
679
  return 'null';
544
680
  if (value === undefined)
545
681
  return 'undefined';
546
- if (typeof value === 'object')
547
- return JSON.stringify(value);
682
+ if (typeof value === 'string')
683
+ return value;
684
+ if (typeof value === 'function')
685
+ return '[Function]';
686
+ if (Array.isArray(value)) {
687
+ return '[' + value.map(v => stringify(v)).join(', ') + ']';
688
+ }
689
+ if (typeof value === 'object') {
690
+ try {
691
+ return JSON.stringify(value);
692
+ }
693
+ catch {
694
+ return '[Object]';
695
+ }
696
+ }
548
697
  return String(value);
549
698
  }
550
699
  function resolvePath(filePath, workdir) {
@@ -552,4 +701,6 @@ function resolvePath(filePath, workdir) {
552
701
  return filePath;
553
702
  return path.resolve(workdir, filePath);
554
703
  }
704
+ /** @deprecated Use createBuiltins instead */
705
+ export const createFCSBuiltins = createBuiltins;
555
706
  //# sourceMappingURL=builtins.js.map