@clawlabz/clawarena-cli 0.3.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.
@@ -0,0 +1,63 @@
1
+ // Parse action JSON from agent/LLM responses
2
+ // Handles: raw JSON, markdown code blocks, embedded JSON objects
3
+
4
+ /**
5
+ * Parse an action from text input (e.g. from stdin or LLM output).
6
+ * Tries multiple extraction strategies for robustness.
7
+ *
8
+ * @param {string} text - Raw text that should contain an action JSON
9
+ * @param {Array<{action: string}>} availableActions - Valid actions for validation
10
+ * @returns {object|null} Parsed action object, or null if unparseable
11
+ */
12
+ export function parseAction(text, availableActions) {
13
+ if (!text || typeof text !== 'string') return null
14
+
15
+ const trimmed = text.trim()
16
+ if (!trimmed) return null
17
+
18
+ // Strategy 1: Direct JSON.parse
19
+ const direct = tryParse(trimmed)
20
+ if (direct && isValidAction(direct, availableActions)) return direct
21
+
22
+ // Strategy 2: Extract from markdown ```json ... ``` blocks
23
+ const codeBlockMatch = trimmed.match(/```(?:json)?\s*\n?([\s\S]*?)```/)
24
+ if (codeBlockMatch) {
25
+ const parsed = tryParse(codeBlockMatch[1].trim())
26
+ if (parsed && isValidAction(parsed, availableActions)) return parsed
27
+ }
28
+
29
+ // Strategy 3: Find first { ... } in the text
30
+ const braceMatch = trimmed.match(/\{[^{}]*\}/)
31
+ if (braceMatch) {
32
+ const parsed = tryParse(braceMatch[0])
33
+ if (parsed && isValidAction(parsed, availableActions)) return parsed
34
+ }
35
+
36
+ // Strategy 4: Find nested { ... { ... } ... }
37
+ const nestedMatch = trimmed.match(/\{[^}]*\{[^}]*\}[^}]*\}/)
38
+ if (nestedMatch) {
39
+ const parsed = tryParse(nestedMatch[0])
40
+ if (parsed && isValidAction(parsed, availableActions)) return parsed
41
+ }
42
+
43
+ return null
44
+ }
45
+
46
+ function tryParse(text) {
47
+ try {
48
+ const parsed = JSON.parse(text)
49
+ if (parsed && typeof parsed === 'object' && !Array.isArray(parsed) && typeof parsed.action === 'string') {
50
+ return parsed
51
+ }
52
+ return null
53
+ } catch {
54
+ return null
55
+ }
56
+ }
57
+
58
+ function isValidAction(parsed, availableActions) {
59
+ if (!Array.isArray(availableActions) || availableActions.length === 0) {
60
+ return typeof parsed.action === 'string' && parsed.action.length > 0
61
+ }
62
+ return availableActions.some(a => a.action === parsed.action)
63
+ }
@@ -0,0 +1,59 @@
1
+ // Fallback strategy: pick a random valid action from availableActions
2
+ // Used when no LLM / agent-mode decision is available
3
+
4
+ /**
5
+ * Pick a random valid action from the available actions list.
6
+ * Prefers actions marked as isFallback=true.
7
+ * For params: default → use it, enum → random value, number → min value,
8
+ * string with validTargets → random target, string without → '(auto)'.
9
+ *
10
+ * @param {Array<{action: string, params?: Record<string, object>, isFallback?: boolean}>} availableActions
11
+ * @returns {object|null} An action object ready to submit, or null if no actions available
12
+ */
13
+ export function pickFallbackAction(availableActions) {
14
+ if (!Array.isArray(availableActions) || availableActions.length === 0) return null
15
+
16
+ // Prefer fallback-marked actions
17
+ const fallbacks = availableActions.filter(a => a.isFallback)
18
+ const candidates = fallbacks.length > 0 ? fallbacks : availableActions
19
+
20
+ const chosen = candidates[Math.floor(Math.random() * candidates.length)]
21
+ const action = { action: chosen.action }
22
+
23
+ if (chosen.params) {
24
+ for (const [key, constraint] of Object.entries(chosen.params)) {
25
+ action[key] = resolveParam(constraint)
26
+ }
27
+ }
28
+
29
+ return action
30
+ }
31
+
32
+ function resolveParam(constraint) {
33
+ if (!constraint || typeof constraint !== 'object') return null
34
+
35
+ // If a default is explicitly provided, prefer it
36
+ if (constraint.default !== undefined && constraint.default !== null) {
37
+ return constraint.default
38
+ }
39
+
40
+ switch (constraint.type) {
41
+ case 'enum': {
42
+ const values = Array.isArray(constraint.values) ? constraint.values : []
43
+ return values.length > 0 ? values[Math.floor(Math.random() * values.length)] : null
44
+ }
45
+ case 'number': {
46
+ const min = typeof constraint.min === 'number' ? constraint.min : 0
47
+ // Use min value as safest default
48
+ return min
49
+ }
50
+ case 'string': {
51
+ const targets = Array.isArray(constraint.validTargets) ? constraint.validTargets : []
52
+ return targets.length > 0 ? targets[Math.floor(Math.random() * targets.length)] : '(auto)'
53
+ }
54
+ case 'boolean':
55
+ return false
56
+ default:
57
+ return null
58
+ }
59
+ }
package/package.json ADDED
@@ -0,0 +1,23 @@
1
+ {
2
+ "name": "@clawlabz/clawarena-cli",
3
+ "version": "0.3.0",
4
+ "description": "ClawArena CLI for AI agents (Claude Code / Codex CLI / Gemini CLI)",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "private": false,
8
+ "bin": {
9
+ "clawarena-cli": "./bin/arena-runner.mjs",
10
+ "clawarena": "./bin/arena-runner.mjs"
11
+ },
12
+ "files": [
13
+ "bin",
14
+ "lib"
15
+ ],
16
+ "publishConfig": {
17
+ "access": "public"
18
+ },
19
+ "scripts": {
20
+ "build": "echo 'no build step'",
21
+ "lint": "node -e \"process.exit(0)\""
22
+ }
23
+ }