@mndrk/agx 1.3.0 → 1.4.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.
@@ -4,7 +4,8 @@
4
4
  "Bash(node --check:*)",
5
5
  "Bash(node index.js:*)",
6
6
  "Bash(python:*)",
7
- "Bash(npm publish:*)"
7
+ "Bash(npm publish:*)",
8
+ "Bash(npm uninstall:*)"
8
9
  ]
9
10
  }
10
11
  }
package/README.md CHANGED
@@ -1,242 +1,141 @@
1
1
  # agx
2
2
 
3
- Unified AI Agent Wrapper for Gemini, Claude, and Ollama.
4
-
5
- ## Installation
6
-
7
- ### Via npm (recommended)
3
+ Unified AI Agent CLI with persistent memory. Wraps Claude, Gemini, and Ollama with automatic state management via [mem](https://github.com/ramarlina/memx).
8
4
 
9
5
  ```bash
10
6
  npm install -g @mndrk/agx
11
7
  ```
12
8
 
13
- ### From source
14
-
15
- Clone the repository and link locally:
16
-
17
- ```bash
18
- git clone https://github.com/ramarlina/agx.git
19
- cd agx
20
- npm link
21
- ```
22
-
23
9
  ## Quick Start
24
10
 
25
- When you run `agx` for the first time, it will automatically start the setup wizard:
26
-
27
11
  ```bash
28
- agx
29
- ```
12
+ # Simple prompt
13
+ agx claude -p "explain this code"
30
14
 
31
- The setup wizard will:
32
- 1. Detect which AI providers are installed on your system
33
- 2. Guide you through installing any missing providers
34
- 3. Help you authenticate with your chosen providers
35
- 4. Set your default provider
15
+ # Use default provider
16
+ agx -p "what does this function do?"
36
17
 
37
- After setup, you can start using agx immediately:
18
+ # With persistent memory (auto-detected)
19
+ agx claude -p "continue working on the todo app"
38
20
 
39
- ```bash
40
- agx --prompt "hello world"
21
+ # Auto-create task (for agents, non-interactive)
22
+ agx claude --auto-task -p "Build a todo app with React"
41
23
  ```
42
24
 
43
- ## Usage
25
+ ## Memory Integration
44
26
 
45
- ### With Default Provider
46
-
47
- Once configured, you can run prompts without specifying a provider:
27
+ agx integrates with [mem](https://github.com/ramarlina/memx) for persistent state across sessions:
48
28
 
49
29
  ```bash
50
- agx --prompt "explain this code"
51
- agx -p "summarize the file"
52
- ```
53
-
54
- ### With Specific Provider
30
+ # If ~/.mem has a task mapped to cwd, context is auto-loaded
31
+ cd ~/Projects/my-app
32
+ agx claude -p "continue" # Knows where it left off
55
33
 
56
- ```bash
57
- agx <provider> [options] --prompt "<prompt>"
34
+ # Create task with explicit criteria
35
+ agx claude --task todo-app \
36
+ --criteria "CRUD working" \
37
+ --criteria "Tests passing" \
38
+ --criteria "Deployed to Vercel" \
39
+ -p "Build a todo app"
58
40
  ```
59
41
 
60
- ### Providers
61
-
62
- | Provider | Aliases | Backend |
63
- |----------|---------|---------|
64
- | `gemini` | `gem`, `g` | Google Gemini CLI |
65
- | `claude` | `cl`, `c` | Anthropic Claude CLI |
66
- | `ollama` | `ol`, `o` | Local Ollama via Claude interface |
67
-
68
- ### Options
69
-
70
- | Option | Short | Description |
71
- |--------|-------|-------------|
72
- | `--prompt <text>` | `-p` | The prompt to send |
73
- | `--model <name>` | `-m` | Model name to use |
74
- | `--yolo` | `-y` | Skip permission prompts |
75
- | `--print` | | Non-interactive mode (output and exit) |
76
- | `--interactive` | `-i` | Force interactive mode |
77
- | `--sandbox` | `-s` | Enable sandbox (gemini only) |
78
- | `--debug` | `-d` | Enable debug output |
79
- | `--mcp <config>` | | MCP config file (claude/ollama only) |
42
+ ## Output Markers
80
43
 
81
- ### Raw Passthrough
44
+ Agents control state via markers in their output:
82
45
 
83
- Use `--` to pass arguments directly to the underlying CLI:
84
- ```bash
85
- agx claude -- --resume
86
46
  ```
87
-
88
- ## Configuration Commands
89
-
90
- ### `agx init`
91
-
92
- Run the setup wizard manually. This is useful if you want to reconfigure agx or add new providers:
93
-
94
- ```bash
95
- agx init
47
+ [checkpoint: Hero section complete] # Save progress
48
+ [learn: Tailwind is fast] # Record learning
49
+ [next: Add auth system] # Set next step
50
+ [criteria: 2] # Mark criterion #2 done
51
+ [approve: Deploy to production?] # Halt for approval
52
+ [blocked: Need API key from client] # Mark stuck
53
+ [pause] # Stop, resume later
54
+ [continue] # Keep going (daemon)
55
+ [done] # Task complete
56
+ [split: auth "Handle authentication"] # Create subtask
96
57
  ```
97
58
 
98
- The wizard will:
99
- - Detect installed providers (claude, gemini, ollama)
100
- - Guide you through installation and authentication
101
- - Let you set or change your default provider
102
-
103
- ### `agx config`
104
-
105
- Open an interactive configuration menu to manage your agx settings:
106
-
107
- ```bash
108
- agx config
109
- ```
59
+ ## Providers
110
60
 
111
- ### `agx add <provider>`
61
+ | Provider | Aliases | Description |
62
+ |----------|---------|-------------|
63
+ | claude | c, cl | Anthropic Claude Code |
64
+ | gemini | g, gem | Google Gemini CLI |
65
+ | ollama | o, ol | Local Ollama models |
112
66
 
113
- Install a specific AI provider:
67
+ ## Options
114
68
 
115
- ```bash
116
- agx add claude # Install Claude CLI
117
- agx add gemini # Install Gemini CLI
118
- agx add ollama # Install Ollama
119
69
  ```
120
-
121
- ### `agx login <provider>`
122
-
123
- Authenticate with a provider:
124
-
125
- ```bash
126
- agx login claude # Login to Claude
127
- agx login gemini # Login to Gemini
70
+ --prompt, -p <text> Prompt to send
71
+ --model, -m <name> Model name
72
+ --yolo, -y Skip permission prompts
73
+ --print Non-interactive output
74
+ --interactive, -i Force interactive mode
75
+ --mem Enable mem integration (auto-detected)
76
+ --no-mem Disable mem integration
77
+ --auto-task Auto-create task from prompt
78
+ --task <name> Specific task name
79
+ --criteria <text> Success criterion (repeatable)
80
+ --daemon Loop on [continue] marker
128
81
  ```
129
82
 
130
- ### `agx status`
131
-
132
- View your current configuration, including installed providers and default settings:
83
+ ## Commands
133
84
 
134
85
  ```bash
135
- agx status
136
- ```
137
-
138
- Example output:
86
+ agx init # Setup wizard
87
+ agx config # Configuration menu
88
+ agx status # Show current config
89
+ agx skill # View LLM skill
90
+ agx skill install # Install skill to Claude/Gemini
139
91
  ```
140
- agx Configuration Status
141
- ------------------------
142
- Default Provider: claude
143
92
 
144
- Installed Providers:
145
- ✓ claude (authenticated)
146
- ✓ gemini (authenticated)
147
- ✓ ollama (running)
93
+ ## Loop Control
148
94
 
149
- Config file: ~/.agx/config.json
150
- ```
151
-
152
- ## Skill System
95
+ The agent controls execution flow via markers:
153
96
 
154
- agx includes a skill system that helps AI agents understand how to use agx effectively.
97
+ - `[done]` Task complete, exit
98
+ - `[pause]` → Save state, exit (resume later with same command)
99
+ - `[blocked: reason]` → Mark stuck, notify human, exit
100
+ - `[continue]` → Keep going (daemon mode loops)
101
+ - `[approve: question]` → Halt until human approves
155
102
 
156
- ### `agx skill`
103
+ ## Task Splitting
157
104
 
158
- View the agx skill (LLM instructions):
105
+ Break large tasks into subtasks:
159
106
 
160
- ```bash
161
- agx skill
162
107
  ```
108
+ Agent output:
109
+ This is too big. Breaking it down.
163
110
 
164
- This displays the skill file that describes agx's capabilities and usage patterns for AI agents.
165
-
166
- ### `agx skill install`
167
-
168
- Install the agx skill to Claude and/or Gemini so AI agents know how to use agx:
169
-
170
- ```bash
171
- agx skill install
111
+ [split: setup "Project scaffolding"]
112
+ [split: auth "Authentication system"]
113
+ [split: crud "CRUD operations"]
114
+ [next: Start with setup subtask]
115
+ [pause]
172
116
  ```
173
117
 
174
- This adds the agx skill to your AI provider's skill directory, enabling features like:
175
- - AI agents can call other AI providers through agx
176
- - Cross-provider collaboration (e.g., Claude can ask Gemini for help)
177
- - Consistent command patterns across all providers
118
+ agx creates subtask branches in ~/.mem linked to the parent.
178
119
 
179
- ## LLM-Predictable Command Patterns
180
-
181
- For LLMs constructing commands, use these canonical patterns:
120
+ ## Example: Full Workflow
182
121
 
183
122
  ```bash
184
- # Pattern: agx --prompt "<prompt>" (uses default provider)
185
- agx --prompt "explain this code"
186
-
187
- # Pattern: agx <provider> --prompt "<prompt>"
188
- agx claude --prompt "explain this code"
189
- agx gemini --prompt "summarize the file"
190
- agx ollama --prompt "write a function"
191
-
192
- # Pattern: agx <provider> --model <model> --prompt "<prompt>"
193
- agx claude --model claude-sonnet-4-20250514 --prompt "fix the bug"
194
- agx gemini --model gemini-2.0-flash --prompt "optimize this"
195
- agx ollama --model qwen3:8b --prompt "refactor"
123
+ # Day 1: Start project
124
+ mkdir ~/Projects/my-app && cd ~/Projects/my-app
125
+ agx claude --auto-task -p "Build a React todo app with auth"
196
126
 
197
- # Pattern: agx <provider> --yolo --prompt "<prompt>"
198
- agx claude --yolo --prompt "run the tests"
199
-
200
- # Pattern: agx <provider> --print --prompt "<prompt>"
201
- agx claude --print --prompt "what is 2+2"
202
- ```
203
-
204
- ### Command Structure
205
-
206
- ```
207
- agx [provider] [--model <name>] [--yolo] [--print] --prompt "<prompt>"
208
- ```
127
+ # Agent works, outputs markers
128
+ # [checkpoint: Scaffolded with Vite]
129
+ # [learn: Vite is faster than CRA]
130
+ # [next: Add todo list component]
131
+ # [pause]
209
132
 
210
- **Rules for LLMs:**
211
- 1. Always use `--prompt` flag for the prompt text
212
- 2. Quote the prompt with double quotes
213
- 3. Place options before `--prompt`
214
- 4. Use full provider names (`claude`, `gemini`, `ollama`) for clarity
215
- 5. Provider is optional if a default is configured
216
-
217
- ## Configuration File
218
-
219
- agx stores its configuration in `~/.agx/config.json`:
220
-
221
- ```json
222
- {
223
- "defaultProvider": "claude",
224
- "providers": {
225
- "claude": {
226
- "installed": true,
227
- "authenticated": true
228
- },
229
- "gemini": {
230
- "installed": true,
231
- "authenticated": true
232
- },
233
- "ollama": {
234
- "installed": true
235
- }
236
- }
237
- }
133
+ # Day 2: Continue
134
+ cd ~/Projects/my-app
135
+ agx claude -p "continue"
136
+ # Context auto-loaded, agent picks up where it left off
238
137
  ```
239
138
 
240
- ## Ollama Support
139
+ ## License
241
140
 
242
- `agx ollama` automatically configures the environment to use a local Ollama instance as the backend for Claude Code. Default model is `glm-4.7:cloud` unless specified with `--model`.
141
+ MIT
package/index.js CHANGED
@@ -12,18 +12,54 @@ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
12
12
  // agx skill - instructions for LLMs on how to use agx
13
13
  const AGX_SKILL = `---
14
14
  name: agx
15
- description: Use agx to run AI agents (Claude, Gemini, Ollama) from the command line
15
+ description: Run AI agents with persistent memory. Wraps Claude, Gemini, Ollama with automatic state management.
16
16
  ---
17
17
 
18
- # agx - Unified AI Agent CLI
18
+ # agx - AI Agent CLI with Persistent Memory
19
19
 
20
- Use \`agx\` to run AI agents from the command line. It wraps Claude Code, Gemini CLI, and Ollama.
20
+ Run AI agents with automatic state persistence via mem integration.
21
21
 
22
- ## Syntax
22
+ ## Basic Usage
23
23
 
24
24
  \`\`\`bash
25
- agx <provider> [options] --prompt "<prompt>"
26
- agx [options] --prompt "<prompt>" # uses default provider
25
+ agx claude -p "explain this code" # simple prompt
26
+ agx -p "what does this do?" # use default provider
27
+ agx c --yolo -p "fix the bug" # skip confirmations
28
+ \`\`\`
29
+
30
+ ## Memory Integration
31
+
32
+ agx auto-detects \`~/.mem\` and loads context:
33
+
34
+ \`\`\`bash
35
+ # Auto-load context if task exists for cwd
36
+ agx claude -p "continue working"
37
+
38
+ # Auto-create task (non-interactive, for agents)
39
+ agx claude --auto-task -p "Build todo app"
40
+
41
+ # Explicit task with criteria
42
+ agx claude --task todo-app \\
43
+ --criteria "CRUD working" \\
44
+ --criteria "Deployed" \\
45
+ -p "Build a todo app"
46
+ \`\`\`
47
+
48
+ ## Output Markers
49
+
50
+ Use these in agent output to save state:
51
+
52
+ \`\`\`
53
+ [checkpoint: message] Save progress point
54
+ [learn: insight] Record learning
55
+ [next: step] Set next step
56
+ [criteria: N] Mark criterion #N complete
57
+ [approve: question] Halt for human approval
58
+ [blocked: reason] Mark blocked, stop
59
+ [pause] Save and stop (resume later)
60
+ [continue] Keep going (daemon mode)
61
+ [done] Mark task complete
62
+ [split: name "goal"] Create subtask
27
63
  \`\`\`
28
64
 
29
65
  ## Providers
@@ -34,41 +70,50 @@ agx [options] --prompt "<prompt>" # uses default provider
34
70
  | gemini | g, gem | Google Gemini CLI |
35
71
  | ollama | o, ol | Local Ollama models |
36
72
 
37
- ## Common Options
73
+ ## Options
38
74
 
39
- | Option | Short | Description |
40
- |--------|-------|-------------|
41
- | --prompt | -p | The prompt to send |
42
- | --model | -m | Model name to use |
43
- | --yolo | -y | Skip permission prompts |
44
- | --print | | Non-interactive output |
45
- | --interactive | -i | Force interactive mode |
75
+ | Option | Description |
76
+ |--------|-------------|
77
+ | --prompt, -p | The prompt to send |
78
+ | --model, -m | Model name |
79
+ | --yolo, -y | Skip permission prompts |
80
+ | --mem | Enable mem (auto-detected) |
81
+ | --no-mem | Disable mem |
82
+ | --auto-task | Auto-create task from prompt |
83
+ | --task NAME | Specific task name |
84
+ | --criteria "..." | Success criterion (repeatable) |
85
+ | --daemon | Loop on [continue] marker |
86
+ | --until-done | Keep running until [done] |
46
87
 
47
- ## Examples
88
+ ## Continuous Work Loop
48
89
 
49
- \`\`\`bash
50
- # Simple prompt
51
- agx claude --prompt "explain this code"
90
+ New tasks auto-set \`wake: every 15m\`. The loop:
52
91
 
53
- # Use default provider
54
- agx --prompt "what does this function do?"
92
+ \`\`\`
93
+ WAKE load context review → work → save → SLEEP
94
+
95
+ repeat until [done]
96
+ \`\`\`
55
97
 
56
- # Skip confirmations
57
- agx c --yolo --prompt "fix the tests"
98
+ Agent keeps working by default. Only output stopping markers when needed:
99
+ - \`[done]\` task complete, stop loop
100
+ - \`[blocked: reason]\` → need human help, pause loop
101
+ - \`[approve: question]\` → need approval, wait
58
102
 
59
- # Specify model
60
- agx ollama --model qwen3:8b --prompt "write a function"
103
+ Install wake: \`mem cron export >> crontab\`
61
104
 
62
- # Non-interactive (just output)
63
- agx gemini --print --prompt "summarize README.md"
64
- \`\`\`
105
+ ## Task Splitting
106
+
107
+ Break large tasks into subtasks:
65
108
 
66
- ## When to use agx
109
+ \`\`\`
110
+ [split: setup "Project scaffolding"]
111
+ [split: auth "Authentication system"]
112
+ [split: crud "CRUD operations"]
113
+ [next: Start with setup]
114
+ \`\`\`
67
115
 
68
- - Quick AI queries from terminal
69
- - Running prompts across different AI providers
70
- - Scripting AI interactions
71
- - When you need --yolo mode to skip confirmations
116
+ Creates subtask branches in ~/.mem, linked to parent.
72
117
  `;
73
118
 
74
119
  // ANSI colors
@@ -94,6 +139,201 @@ function commandExists(cmd) {
94
139
  }
95
140
  }
96
141
 
142
+ // ==================== MEM INTEGRATION ====================
143
+
144
+ // Find .mem directory (walk up from cwd, or check ~/.mem)
145
+ function findMemDir(startDir = process.cwd()) {
146
+ // First check local .mem
147
+ let dir = startDir;
148
+ while (dir !== path.dirname(dir)) {
149
+ const memDir = path.join(dir, '.mem');
150
+ if (fs.existsSync(memDir) && fs.existsSync(path.join(memDir, '.git'))) {
151
+ return memDir;
152
+ }
153
+ dir = path.dirname(dir);
154
+ }
155
+
156
+ // Then check ~/.mem with index
157
+ const globalMem = path.join(process.env.HOME || process.env.USERPROFILE, '.mem');
158
+ if (fs.existsSync(globalMem)) {
159
+ const indexFile = path.join(globalMem, 'index.json');
160
+ if (fs.existsSync(indexFile)) {
161
+ try {
162
+ const index = JSON.parse(fs.readFileSync(indexFile, 'utf8'));
163
+ if (index[startDir]) {
164
+ return globalMem;
165
+ }
166
+ } catch {}
167
+ }
168
+ }
169
+
170
+ return null;
171
+ }
172
+
173
+ // Load mem context
174
+ function loadMemContext(memDir) {
175
+ try {
176
+ const result = execSync('mem context', {
177
+ cwd: path.dirname(memDir),
178
+ encoding: 'utf8',
179
+ stdio: ['pipe', 'pipe', 'pipe']
180
+ });
181
+ return result.trim();
182
+ } catch {
183
+ return null;
184
+ }
185
+ }
186
+
187
+ // Parse output markers
188
+ function parseMemMarkers(output) {
189
+ const markers = [];
190
+ const patterns = [
191
+ { type: 'checkpoint', regex: /\[checkpoint:\s*([^\]]+)\]/gi },
192
+ { type: 'learn', regex: /\[learn:\s*([^\]]+)\]/gi },
193
+ { type: 'next', regex: /\[next:\s*([^\]]+)\]/gi },
194
+ { type: 'stuck', regex: /\[stuck:\s*([^\]]+)\]/gi },
195
+ { type: 'blocked', regex: /\[blocked:\s*([^\]]+)\]/gi },
196
+ { type: 'done', regex: /\[done\]/gi },
197
+ { type: 'pause', regex: /\[pause(?::\s*([^\]]*))?\]/gi },
198
+ { type: 'continue', regex: /\[continue\]/gi },
199
+ { type: 'approve', regex: /\[approve:\s*([^\]]+)\]/gi },
200
+ { type: 'criteria', regex: /\[criteria:\s*(\d+)\]/gi },
201
+ { type: 'split', regex: /\[split:\s*([^\s\]]+)(?:\s+"([^"]+)")?\]/gi },
202
+ ];
203
+
204
+ for (const { type, regex } of patterns) {
205
+ let match;
206
+ while ((match = regex.exec(output)) !== null) {
207
+ if (type === 'split') {
208
+ markers.push({ type, name: match[1], goal: match[2] || match[1] });
209
+ } else {
210
+ markers.push({ type, value: match[1] || true });
211
+ }
212
+ }
213
+ }
214
+
215
+ return markers;
216
+ }
217
+
218
+ // Apply mem markers - returns control signals
219
+ function applyMemMarkers(markers, memDir) {
220
+ const workDir = path.dirname(memDir);
221
+ const result = {
222
+ approvals: [],
223
+ shouldContinue: false,
224
+ shouldPause: false,
225
+ isDone: false,
226
+ isBlocked: false,
227
+ splits: []
228
+ };
229
+
230
+ for (const marker of markers) {
231
+ try {
232
+ switch (marker.type) {
233
+ case 'checkpoint':
234
+ execSync(`mem checkpoint "${marker.value.replace(/"/g, '\\"')}"`, {
235
+ cwd: workDir, stdio: 'ignore'
236
+ });
237
+ console.log(`${c.green}✓${c.reset} ${c.dim}Checkpoint:${c.reset} ${marker.value}`);
238
+ break;
239
+ case 'learn':
240
+ execSync(`mem learn "${marker.value.replace(/"/g, '\\"')}"`, {
241
+ cwd: workDir, stdio: 'ignore'
242
+ });
243
+ console.log(`${c.green}✓${c.reset} ${c.dim}Learned:${c.reset} ${marker.value}`);
244
+ break;
245
+ case 'next':
246
+ execSync(`mem next "${marker.value.replace(/"/g, '\\"')}"`, {
247
+ cwd: workDir, stdio: 'ignore'
248
+ });
249
+ console.log(`${c.green}✓${c.reset} ${c.dim}Next:${c.reset} ${marker.value}`);
250
+ break;
251
+ case 'stuck':
252
+ case 'blocked':
253
+ execSync(`mem stuck "${marker.value.replace(/"/g, '\\"')}"`, {
254
+ cwd: workDir, stdio: 'ignore'
255
+ });
256
+ console.log(`${c.yellow}⚠${c.reset} ${c.dim}Blocked:${c.reset} ${marker.value}`);
257
+ result.isBlocked = true;
258
+ break;
259
+ case 'done':
260
+ console.log(`${c.green}✓${c.reset} ${c.dim}Task marked done${c.reset}`);
261
+ result.isDone = true;
262
+ break;
263
+ case 'pause':
264
+ console.log(`${c.cyan}⏸${c.reset} ${c.dim}Pausing${marker.value ? ': ' + marker.value : ''}${c.reset}`);
265
+ result.shouldPause = true;
266
+ break;
267
+ case 'continue':
268
+ console.log(`${c.cyan}▶${c.reset} ${c.dim}Continuing...${c.reset}`);
269
+ result.shouldContinue = true;
270
+ break;
271
+ case 'approve':
272
+ result.approvals.push(marker.value);
273
+ break;
274
+ case 'criteria':
275
+ execSync(`mem criteria ${marker.value}`, {
276
+ cwd: workDir, stdio: 'ignore'
277
+ });
278
+ console.log(`${c.green}✓${c.reset} ${c.dim}Criteria #${marker.value} complete${c.reset}`);
279
+ break;
280
+ case 'split':
281
+ result.splits.push({ name: marker.name, goal: marker.goal });
282
+ console.log(`${c.cyan}⑂${c.reset} ${c.dim}Split:${c.reset} ${marker.name} - ${marker.goal}`);
283
+ break;
284
+ }
285
+ } catch (err) {
286
+ console.error(`${c.red}mem error:${c.reset} ${err.message}`);
287
+ }
288
+ }
289
+
290
+ return result;
291
+ }
292
+
293
+ // Create subtasks from split markers
294
+ function createSubtasks(splits, memDir) {
295
+ const workDir = path.dirname(memDir);
296
+
297
+ for (const split of splits) {
298
+ try {
299
+ // Create subtask as new branch
300
+ execSync(`mem init ${split.name} "${split.goal.replace(/"/g, '\\"')}"`, {
301
+ cwd: workDir,
302
+ stdio: 'ignore'
303
+ });
304
+ console.log(`${c.green}✓${c.reset} Created subtask: ${c.bold}${split.name}${c.reset}`);
305
+ } catch (err) {
306
+ console.error(`${c.red}Failed to create subtask ${split.name}:${c.reset} ${err.message}`);
307
+ }
308
+ }
309
+ }
310
+
311
+ // Handle approval prompts
312
+ async function handleApprovals(approvals) {
313
+ if (approvals.length === 0) return true;
314
+
315
+ const rl = readline.createInterface({
316
+ input: process.stdin,
317
+ output: process.stdout
318
+ });
319
+
320
+ for (const approval of approvals) {
321
+ const answer = await new Promise(resolve => {
322
+ rl.question(`\n${c.yellow}⚠ APPROVAL REQUIRED:${c.reset} ${approval}\n${c.dim}Continue? [y/N]:${c.reset} `, resolve);
323
+ });
324
+
325
+ if (answer.toLowerCase() !== 'y' && answer.toLowerCase() !== 'yes') {
326
+ console.log(`${c.red}✗${c.reset} Rejected. Workflow halted.`);
327
+ rl.close();
328
+ return false;
329
+ }
330
+ console.log(`${c.green}✓${c.reset} Approved.`);
331
+ }
332
+
333
+ rl.close();
334
+ return true;
335
+ }
336
+
97
337
  // Load config
98
338
  function loadConfig() {
99
339
  try {
@@ -862,7 +1102,15 @@ const options = {
862
1102
  interactive: false,
863
1103
  sandbox: false,
864
1104
  debug: false,
865
- mcp: null
1105
+ mcp: null,
1106
+ mem: false,
1107
+ memDir: null,
1108
+ autoTask: false,
1109
+ taskName: null,
1110
+ criteria: [],
1111
+ daemon: false,
1112
+ untilDone: false,
1113
+ wakeInterval: null
866
1114
  };
867
1115
 
868
1116
  // Collect positional args (legacy support, but --prompt is preferred)
@@ -912,6 +1160,42 @@ for (let i = 0; i < processedArgs.length; i++) {
912
1160
  i++;
913
1161
  }
914
1162
  break;
1163
+ case '--mem':
1164
+ options.mem = true;
1165
+ break;
1166
+ case '--no-mem':
1167
+ options.mem = false;
1168
+ break;
1169
+ case '--auto-task':
1170
+ options.autoTask = true;
1171
+ options.mem = true;
1172
+ break;
1173
+ case '--task':
1174
+ if (nextArg && !nextArg.startsWith('-')) {
1175
+ options.taskName = nextArg;
1176
+ options.mem = true;
1177
+ i++;
1178
+ }
1179
+ break;
1180
+ case '--criteria':
1181
+ if (nextArg && !nextArg.startsWith('-')) {
1182
+ options.criteria.push(nextArg);
1183
+ i++;
1184
+ }
1185
+ break;
1186
+ case '--daemon':
1187
+ options.daemon = true;
1188
+ break;
1189
+ case '--until-done':
1190
+ options.untilDone = true;
1191
+ options.daemon = true;
1192
+ break;
1193
+ case '--wake':
1194
+ if (nextArg && !nextArg.startsWith('-')) {
1195
+ options.wakeInterval = nextArg;
1196
+ i++;
1197
+ }
1198
+ break;
915
1199
  default:
916
1200
  if (arg.startsWith('-')) {
917
1201
  // Unknown flag - pass through
@@ -986,29 +1270,209 @@ if (provider === 'gemini') {
986
1270
  // Append raw args at the end
987
1271
  translatedArgs.push(...rawArgs);
988
1272
 
989
- const child = spawn(command, translatedArgs, {
990
- env,
991
- stdio: 'inherit',
992
- shell: false
993
- });
994
-
995
- child.on('exit', (code) => {
996
- process.exit(code || 0);
997
- });
998
-
999
- child.on('error', (err) => {
1000
- if (err.code === 'ENOENT') {
1001
- console.error(`${c.red}Error:${c.reset} "${command}" command not found.`);
1002
- console.error(`\n${c.dim}Install it first:${c.reset}`);
1003
- if (command === 'claude') {
1004
- console.error(` npm install -g @anthropic-ai/claude-code`);
1005
- } else if (command === 'gemini') {
1006
- console.error(` npm install -g @anthropic-ai/gemini-cli`);
1273
+ // ==================== MEM INTEGRATION ====================
1274
+
1275
+ // Auto-detect mem if .mem exists (unless --no-mem)
1276
+ let memDir = options.mem !== false ? findMemDir() : null;
1277
+ if (memDir && options.mem !== false) {
1278
+ options.mem = true;
1279
+ options.memDir = memDir;
1280
+ }
1281
+
1282
+ // Auto-create task if --auto-task or --task specified but no mem found
1283
+ if ((options.autoTask || options.taskName) && !options.memDir && finalPrompt) {
1284
+ const taskName = options.taskName || finalPrompt
1285
+ .toLowerCase()
1286
+ .replace(/[^a-z0-9\s]/g, '')
1287
+ .split(/\s+/)
1288
+ .slice(0, 3)
1289
+ .join('-');
1290
+
1291
+ console.log(`${c.dim}[mem] Creating task: ${taskName}${c.reset}`);
1292
+
1293
+ try {
1294
+ // Build criteria args
1295
+ const criteriaArgs = options.criteria.length
1296
+ ? options.criteria.map(c => `--criteria "${c}"`).join(' ')
1297
+ : '';
1298
+
1299
+ // Create task non-interactively
1300
+ const centralMem = path.join(process.env.HOME || process.env.USERPROFILE, '.mem');
1301
+
1302
+ // Ensure central mem exists
1303
+ if (!fs.existsSync(centralMem)) {
1304
+ fs.mkdirSync(centralMem, { recursive: true });
1305
+ execSync('git init', { cwd: centralMem, stdio: 'ignore' });
1306
+ fs.writeFileSync(path.join(centralMem, 'playbook.md'), '# Playbook\n\nGlobal learnings that transfer across tasks.\n');
1307
+ execSync('git add -A && git commit -m "init: memory repo"', { cwd: centralMem, stdio: 'ignore', shell: true });
1007
1308
  }
1008
- } else {
1009
- console.error(`${c.red}Failed to start ${command}:${c.reset}`, err.message);
1309
+
1310
+ // Create task branch
1311
+ const branch = `task/${taskName}`;
1312
+ try {
1313
+ execSync(`git checkout main`, { cwd: centralMem, stdio: 'ignore' });
1314
+ } catch {}
1315
+ execSync(`git checkout -b ${branch}`, { cwd: centralMem, stdio: 'ignore' });
1316
+
1317
+ // Create task files
1318
+ const today = new Date().toISOString().split('T')[0];
1319
+ const criteriaText = options.criteria.length
1320
+ ? options.criteria.map(c => `- [ ] ${c}`).join('\n')
1321
+ : '- [ ] Define success criteria';
1322
+
1323
+ fs.writeFileSync(path.join(centralMem, 'goal.md'),
1324
+ `---\ntask: ${taskName}\ncreated: ${today}\n---\n\n# Goal\n\n${finalPrompt}\n\n## Definition of Done\n\n${criteriaText}\n\n## Progress: 0%`);
1325
+ fs.writeFileSync(path.join(centralMem, 'state.md'),
1326
+ `---\nstatus: active\n---\n\n# State\n\n## Next Step\n\nBegin work\n\n## Checkpoints\n\n- [ ] Started`);
1327
+ fs.writeFileSync(path.join(centralMem, 'memory.md'), '# Learnings\n\n');
1328
+
1329
+ execSync('git add -A && git commit -m "init: ' + taskName + '"', { cwd: centralMem, stdio: 'ignore', shell: true });
1330
+
1331
+ // Update index
1332
+ const indexFile = path.join(centralMem, 'index.json');
1333
+ let index = {};
1334
+ try { index = JSON.parse(fs.readFileSync(indexFile, 'utf8')); } catch {}
1335
+ index[process.cwd()] = branch;
1336
+ fs.writeFileSync(indexFile, JSON.stringify(index, null, 2));
1337
+
1338
+ options.memDir = centralMem;
1339
+ console.log(`${c.green}✓${c.reset} Created task: ${c.bold}${taskName}${c.reset}`);
1340
+ console.log(`${c.green}✓${c.reset} Mapped: ${c.dim}${process.cwd()} → ${branch}${c.reset}`);
1341
+
1342
+ // Auto-set wake schedule for new tasks with proper command
1343
+ try {
1344
+ const projectDir = process.cwd();
1345
+ const wakeCmd = `cd ${projectDir} && agx claude -p "continue"`;
1346
+ execSync(`mem wake "every 15m" --run "${wakeCmd}"`, { cwd: process.cwd(), stdio: 'ignore' });
1347
+ console.log(`${c.green}✓${c.reset} Wake: ${c.dim}every 15m (until done)${c.reset}`);
1348
+ console.log(`${c.dim} Run: agx claude -p "continue"${c.reset}\n`);
1349
+ } catch {}
1350
+
1351
+ } catch (err) {
1352
+ console.error(`${c.yellow}Warning: Could not create task:${c.reset} ${err.message}`);
1010
1353
  }
1011
- process.exit(1);
1012
- });
1354
+ }
1355
+
1356
+ // Prepend mem context to prompt if mem is enabled
1357
+ if (options.mem && options.memDir && finalPrompt) {
1358
+ const context = loadMemContext(options.memDir);
1359
+ if (context) {
1360
+ console.log(`${c.dim}[mem] Loaded context from ${options.memDir}${c.reset}\n`);
1361
+
1362
+ // Prepend context to prompt with full marker documentation
1363
+ const augmentedPrompt = `## Current Context (from mem)\n\n${context}\n\n## Task\n\n${finalPrompt}\n\n## Instructions\n\nYou are continuing work on this task. Review the context above, then continue where you left off.\n\n## Output Markers\n\nUse these markers to save state (will be parsed automatically):\n\n- [checkpoint: message] - save progress point\n- [learn: insight] - record a learning \n- [next: step] - set what to work on next\n- [criteria: N] - mark criterion #N complete\n- [split: name "goal"] - break into subtask\n\nStopping markers (only use when needed):\n- [done] - task complete (all criteria met)\n- [blocked: reason] - need human help, cannot proceed\n- [approve: question] - need human approval before continuing\n\nThe default is to keep working. You will wake again in 15 minutes to continue.`;
1364
+
1365
+ // Replace the prompt in translatedArgs
1366
+ const promptIndex = translatedArgs.indexOf(finalPrompt);
1367
+ if (promptIndex !== -1) {
1368
+ translatedArgs[promptIndex] = augmentedPrompt;
1369
+ }
1370
+ }
1371
+ }
1372
+
1373
+ // Run with mem output capture or normal mode
1374
+ if (options.mem && options.memDir) {
1375
+ // Capture output for marker parsing
1376
+ const child = spawn(command, translatedArgs, {
1377
+ env,
1378
+ stdio: ['inherit', 'pipe', 'inherit'],
1379
+ shell: false
1380
+ });
1381
+
1382
+ let output = '';
1383
+
1384
+ child.stdout.on('data', (data) => {
1385
+ const text = data.toString();
1386
+ output += text;
1387
+ process.stdout.write(text);
1388
+ });
1389
+
1390
+ child.on('exit', async (code) => {
1391
+ // Parse and apply mem markers
1392
+ const markers = parseMemMarkers(output);
1393
+
1394
+ if (markers.length > 0) {
1395
+ console.log(`\n${c.dim}[mem] Processing markers...${c.reset}`);
1396
+ const result = applyMemMarkers(markers, options.memDir);
1397
+
1398
+ // Create subtasks if any
1399
+ if (result.splits.length > 0) {
1400
+ createSubtasks(result.splits, options.memDir);
1401
+ }
1402
+
1403
+ // Handle approvals
1404
+ if (result.approvals.length > 0) {
1405
+ const approved = await handleApprovals(result.approvals);
1406
+ if (!approved) {
1407
+ console.log(`${c.yellow}Workflow halted. Resume later.${c.reset}`);
1408
+ process.exit(1);
1409
+ }
1410
+ }
1411
+
1412
+ // Handle loop control - default is CONTINUE (wake again in 15m)
1413
+ if (result.isDone) {
1414
+ console.log(`\n${c.green}✓ Task complete!${c.reset}`);
1415
+ // Clear wake schedule on done
1416
+ try {
1417
+ execSync('mem wake clear', { cwd: process.cwd(), stdio: 'ignore' });
1418
+ console.log(`${c.dim}Wake schedule cleared.${c.reset}`);
1419
+ } catch {}
1420
+ process.exit(0);
1421
+ } else if (result.isBlocked) {
1422
+ console.log(`\n${c.yellow}⚠ Task blocked. Human intervention needed.${c.reset}`);
1423
+ console.log(`${c.dim}Wake continues - run 'mem stuck clear' when unblocked.${c.reset}`);
1424
+ process.exit(1);
1425
+ } else if (options.untilDone) {
1426
+ // Local loop mode: wait and run again
1427
+ console.log(`\n${c.cyan}▶ Continuing in 10s...${c.reset}`);
1428
+ setTimeout(() => {
1429
+ const { spawnSync } = require('child_process');
1430
+ spawnSync(process.argv[0], process.argv.slice(1), { stdio: 'inherit' });
1431
+ }, 10000);
1432
+ return;
1433
+ } else {
1434
+ // Default: save and exit, wake will resume in 15m
1435
+ console.log(`\n${c.dim}Progress saved. Will continue on next wake (~15m).${c.reset}`);
1436
+ }
1437
+ }
1438
+
1439
+ process.exit(code || 0);
1440
+ });
1441
+
1442
+ child.on('error', (err) => {
1443
+ if (err.code === 'ENOENT') {
1444
+ console.error(`${c.red}Error:${c.reset} "${command}" command not found.`);
1445
+ } else {
1446
+ console.error(`${c.red}Failed to start ${command}:${c.reset}`, err.message);
1447
+ }
1448
+ process.exit(1);
1449
+ });
1450
+ } else {
1451
+ // Normal mode - stdio inherit
1452
+ const child = spawn(command, translatedArgs, {
1453
+ env,
1454
+ stdio: 'inherit',
1455
+ shell: false
1456
+ });
1457
+
1458
+ child.on('exit', (code) => {
1459
+ process.exit(code || 0);
1460
+ });
1461
+
1462
+ child.on('error', (err) => {
1463
+ if (err.code === 'ENOENT') {
1464
+ console.error(`${c.red}Error:${c.reset} "${command}" command not found.`);
1465
+ console.error(`\n${c.dim}Install it first:${c.reset}`);
1466
+ if (command === 'claude') {
1467
+ console.error(` npm install -g @anthropic-ai/claude-code`);
1468
+ } else if (command === 'gemini') {
1469
+ console.error(` npm install -g @anthropic-ai/gemini-cli`);
1470
+ }
1471
+ } else {
1472
+ console.error(`${c.red}Failed to start ${command}:${c.reset}`, err.message);
1473
+ }
1474
+ process.exit(1);
1475
+ });
1476
+ }
1013
1477
 
1014
1478
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mndrk/agx",
3
- "version": "1.3.0",
3
+ "version": "1.4.0",
4
4
  "description": "Unified AI Agent Wrapper for Gemini, Claude, and Ollama",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -16,12 +16,17 @@
16
16
  "gemini",
17
17
  "claude",
18
18
  "ollama",
19
- "llm"
19
+ "llm",
20
+ "memory",
21
+ "persistent"
20
22
  ],
21
- "author": "mndrk",
23
+ "author": "Mendrika Ramarlina",
22
24
  "license": "MIT",
23
25
  "repository": {
24
26
  "type": "git",
25
27
  "url": "https://github.com/ramarlina/agx"
28
+ },
29
+ "dependencies": {
30
+ "@mndrk/memx": "^0.2.0"
26
31
  }
27
32
  }