@mndrk/agx 1.3.0 → 1.4.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.
@@ -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
  }
@@ -0,0 +1,7 @@
1
+ {
2
+ "name": "agx",
3
+ "description": "Unified AI agent wrapper with persistent memory integration - spawn background agents that wake and continue",
4
+ "version": "1.4.0",
5
+ "author": "mndrk",
6
+ "repository": "https://github.com/ramarlina/agx"
7
+ }
package/README.md CHANGED
@@ -1,242 +1,153 @@
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
44
-
45
- ### With Default Provider
25
+ ## Memory Integration
46
26
 
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
- ```
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
53
33
 
54
- ### With Specific Provider
55
-
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 |
42
+ ## Output Markers
67
43
 
68
- ### Options
44
+ Agents control state via markers in their output:
69
45
 
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) |
80
-
81
- ### Raw Passthrough
82
-
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
59
+ ## Providers
102
60
 
103
- ### `agx config`
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 |
104
66
 
105
- Open an interactive configuration menu to manage your agx settings:
67
+ ## Options
106
68
 
107
- ```bash
108
- agx config
109
69
  ```
110
-
111
- ### `agx add <provider>`
112
-
113
- Install a specific AI provider:
114
-
115
- ```bash
116
- agx add claude # Install Claude CLI
117
- agx add gemini # Install Gemini CLI
118
- agx add ollama # Install Ollama
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
119
81
  ```
120
82
 
121
- ### `agx login <provider>`
83
+ ## Claude Code Plugin
122
84
 
123
- Authenticate with a provider:
85
+ Install as a Claude Code plugin:
124
86
 
125
87
  ```bash
126
- agx login claude # Login to Claude
127
- agx login gemini # Login to Gemini
88
+ claude plugin install github:ramarlina/agx
128
89
  ```
129
90
 
130
- ### `agx status`
91
+ This adds:
92
+ - **Skill**: Claude learns how to spawn background agents
93
+ - **Commands**: `/agx:spawn <goal>`, `/agx:continue`
131
94
 
132
- View your current configuration, including installed providers and default settings:
95
+ ## Commands
133
96
 
134
97
  ```bash
135
- agx status
136
- ```
137
-
138
- Example output:
98
+ agx init # Setup wizard
99
+ agx config # Configuration menu
100
+ agx status # Show current config
101
+ agx skill # View LLM skill
102
+ agx skill install # Install skill to Claude/Gemini
139
103
  ```
140
- agx Configuration Status
141
- ------------------------
142
- Default Provider: claude
143
-
144
- Installed Providers:
145
- ✓ claude (authenticated)
146
- ✓ gemini (authenticated)
147
- ✓ ollama (running)
148
104
 
149
- Config file: ~/.agx/config.json
150
- ```
105
+ ## Loop Control
151
106
 
152
- ## Skill System
107
+ The agent controls execution flow via markers:
153
108
 
154
- agx includes a skill system that helps AI agents understand how to use agx effectively.
109
+ - `[done]` Task complete, exit
110
+ - `[pause]` → Save state, exit (resume later with same command)
111
+ - `[blocked: reason]` → Mark stuck, notify human, exit
112
+ - `[continue]` → Keep going (daemon mode loops)
113
+ - `[approve: question]` → Halt until human approves
155
114
 
156
- ### `agx skill`
115
+ ## Task Splitting
157
116
 
158
- View the agx skill (LLM instructions):
117
+ Break large tasks into subtasks:
159
118
 
160
- ```bash
161
- agx skill
162
119
  ```
120
+ Agent output:
121
+ This is too big. Breaking it down.
163
122
 
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
123
+ [split: setup "Project scaffolding"]
124
+ [split: auth "Authentication system"]
125
+ [split: crud "CRUD operations"]
126
+ [next: Start with setup subtask]
127
+ [pause]
172
128
  ```
173
129
 
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
178
-
179
- ## LLM-Predictable Command Patterns
130
+ agx creates subtask branches in ~/.mem linked to the parent.
180
131
 
181
- For LLMs constructing commands, use these canonical patterns:
132
+ ## Example: Full Workflow
182
133
 
183
134
  ```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"
196
-
197
- # Pattern: agx <provider> --yolo --prompt "<prompt>"
198
- agx claude --yolo --prompt "run the tests"
135
+ # Day 1: Start project
136
+ mkdir ~/Projects/my-app && cd ~/Projects/my-app
137
+ agx claude --auto-task -p "Build a React todo app with auth"
199
138
 
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
- ```
139
+ # Agent works, outputs markers
140
+ # [checkpoint: Scaffolded with Vite]
141
+ # [learn: Vite is faster than CRA]
142
+ # [next: Add todo list component]
143
+ # [pause]
209
144
 
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
- }
145
+ # Day 2: Continue
146
+ cd ~/Projects/my-app
147
+ agx claude -p "continue"
148
+ # Context auto-loaded, agent picks up where it left off
238
149
  ```
239
150
 
240
- ## Ollama Support
151
+ ## License
241
152
 
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`.
153
+ MIT
@@ -0,0 +1,16 @@
1
+ # /agx:continue - Continue Current Task
2
+
3
+ Continue working on the current task from the last checkpoint.
4
+
5
+ ## Usage
6
+ ```
7
+ /agx:continue
8
+ ```
9
+
10
+ ## Implementation
11
+
12
+ ```bash
13
+ agx claude -p "continue"
14
+ ```
15
+
16
+ This loads the mem context and continues from where the task left off.
@@ -0,0 +1,31 @@
1
+ # /agx:spawn - Spawn Background Agent
2
+
3
+ Spawn a background agent task with automatic wake schedule.
4
+
5
+ ## Usage
6
+ ```
7
+ /agx:spawn <goal>
8
+ ```
9
+
10
+ ## Arguments
11
+ - `$ARGUMENTS` - The goal/task description for the agent
12
+
13
+ ## Implementation
14
+
15
+ First, ensure you're in the correct project directory, then:
16
+
17
+ ```bash
18
+ agx claude --auto-task -p "$ARGUMENTS"
19
+ ```
20
+
21
+ This will:
22
+ 1. Create a mem task branch
23
+ 2. Set wake schedule (every 15m)
24
+ 3. Start the agent working
25
+
26
+ After spawning, install the wake cron:
27
+ ```bash
28
+ (crontab -l 2>/dev/null; mem cron export) | crontab -
29
+ ```
30
+
31
+ Report the task name and confirm wake schedule is set.
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,214 @@ 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
+ // Detect agx path dynamically for cron (no PATH in cron env)
1344
+ try {
1345
+ const projectDir = process.cwd();
1346
+ let agxPath = 'agx';
1347
+ try {
1348
+ agxPath = execSync('which agx', { encoding: 'utf8' }).trim();
1349
+ } catch {}
1350
+ const wakeCmd = `cd ${projectDir} && ${agxPath} claude -y -p "continue"`;
1351
+ execSync(`mem wake "every 15m" --run "${wakeCmd}"`, { cwd: process.cwd(), stdio: 'ignore' });
1352
+ console.log(`${c.green}✓${c.reset} Wake: ${c.dim}every 15m (until done)${c.reset}`);
1353
+ console.log(`${c.dim} Run: agx claude -y -p "continue"${c.reset}\n`);
1354
+ } catch {}
1355
+
1356
+ } catch (err) {
1357
+ console.error(`${c.yellow}Warning: Could not create task:${c.reset} ${err.message}`);
1010
1358
  }
1011
- process.exit(1);
1012
- });
1359
+ }
1360
+
1361
+ // Prepend mem context to prompt if mem is enabled
1362
+ if (options.mem && options.memDir && finalPrompt) {
1363
+ const context = loadMemContext(options.memDir);
1364
+ if (context) {
1365
+ console.log(`${c.dim}[mem] Loaded context from ${options.memDir}${c.reset}\n`);
1366
+
1367
+ // Prepend context to prompt with full marker documentation
1368
+ 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.`;
1369
+
1370
+ // Replace the prompt in translatedArgs
1371
+ const promptIndex = translatedArgs.indexOf(finalPrompt);
1372
+ if (promptIndex !== -1) {
1373
+ translatedArgs[promptIndex] = augmentedPrompt;
1374
+ }
1375
+ }
1376
+ }
1377
+
1378
+ // Run with mem output capture or normal mode
1379
+ if (options.mem && options.memDir) {
1380
+ // Capture output for marker parsing
1381
+ const child = spawn(command, translatedArgs, {
1382
+ env,
1383
+ stdio: ['inherit', 'pipe', 'inherit'],
1384
+ shell: false
1385
+ });
1386
+
1387
+ let output = '';
1388
+
1389
+ child.stdout.on('data', (data) => {
1390
+ const text = data.toString();
1391
+ output += text;
1392
+ process.stdout.write(text);
1393
+ });
1394
+
1395
+ child.on('exit', async (code) => {
1396
+ // Parse and apply mem markers
1397
+ const markers = parseMemMarkers(output);
1398
+
1399
+ if (markers.length > 0) {
1400
+ console.log(`\n${c.dim}[mem] Processing markers...${c.reset}`);
1401
+ const result = applyMemMarkers(markers, options.memDir);
1402
+
1403
+ // Create subtasks if any
1404
+ if (result.splits.length > 0) {
1405
+ createSubtasks(result.splits, options.memDir);
1406
+ }
1407
+
1408
+ // Handle approvals
1409
+ if (result.approvals.length > 0) {
1410
+ const approved = await handleApprovals(result.approvals);
1411
+ if (!approved) {
1412
+ console.log(`${c.yellow}Workflow halted. Resume later.${c.reset}`);
1413
+ process.exit(1);
1414
+ }
1415
+ }
1416
+
1417
+ // Handle loop control - default is CONTINUE (wake again in 15m)
1418
+ if (result.isDone) {
1419
+ console.log(`\n${c.green}✓ Task complete!${c.reset}`);
1420
+ // Clear wake schedule on done
1421
+ try {
1422
+ execSync('mem wake clear', { cwd: process.cwd(), stdio: 'ignore' });
1423
+ console.log(`${c.dim}Wake schedule cleared.${c.reset}`);
1424
+ } catch {}
1425
+ process.exit(0);
1426
+ } else if (result.isBlocked) {
1427
+ console.log(`\n${c.yellow}⚠ Task blocked. Human intervention needed.${c.reset}`);
1428
+ console.log(`${c.dim}Wake continues - run 'mem stuck clear' when unblocked.${c.reset}`);
1429
+ process.exit(1);
1430
+ } else if (options.untilDone) {
1431
+ // Local loop mode: wait and run again
1432
+ console.log(`\n${c.cyan}▶ Continuing in 10s...${c.reset}`);
1433
+ setTimeout(() => {
1434
+ const { spawnSync } = require('child_process');
1435
+ spawnSync(process.argv[0], process.argv.slice(1), { stdio: 'inherit' });
1436
+ }, 10000);
1437
+ return;
1438
+ } else {
1439
+ // Default: save and exit, wake will resume in 15m
1440
+ console.log(`\n${c.dim}Progress saved. Will continue on next wake (~15m).${c.reset}`);
1441
+ }
1442
+ }
1443
+
1444
+ process.exit(code || 0);
1445
+ });
1446
+
1447
+ child.on('error', (err) => {
1448
+ if (err.code === 'ENOENT') {
1449
+ console.error(`${c.red}Error:${c.reset} "${command}" command not found.`);
1450
+ } else {
1451
+ console.error(`${c.red}Failed to start ${command}:${c.reset}`, err.message);
1452
+ }
1453
+ process.exit(1);
1454
+ });
1455
+ } else {
1456
+ // Normal mode - stdio inherit
1457
+ const child = spawn(command, translatedArgs, {
1458
+ env,
1459
+ stdio: 'inherit',
1460
+ shell: false
1461
+ });
1462
+
1463
+ child.on('exit', (code) => {
1464
+ process.exit(code || 0);
1465
+ });
1466
+
1467
+ child.on('error', (err) => {
1468
+ if (err.code === 'ENOENT') {
1469
+ console.error(`${c.red}Error:${c.reset} "${command}" command not found.`);
1470
+ console.error(`\n${c.dim}Install it first:${c.reset}`);
1471
+ if (command === 'claude') {
1472
+ console.error(` npm install -g @anthropic-ai/claude-code`);
1473
+ } else if (command === 'gemini') {
1474
+ console.error(` npm install -g @anthropic-ai/gemini-cli`);
1475
+ }
1476
+ } else {
1477
+ console.error(`${c.red}Failed to start ${command}:${c.reset}`, err.message);
1478
+ }
1479
+ process.exit(1);
1480
+ });
1481
+ }
1013
1482
 
1014
1483
  })();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mndrk/agx",
3
- "version": "1.3.0",
3
+ "version": "1.4.1",
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.3.0"
26
31
  }
27
32
  }
@@ -0,0 +1,123 @@
1
+ # agx - Unified AI Agent Wrapper
2
+
3
+ Use `agx` to run AI agents (Claude Code, Gemini, Ollama) with persistent memory integration. Spawn background tasks that wake automatically and continue working.
4
+
5
+ ## When to Use
6
+
7
+ - **Spawning background agents** for long-running tasks
8
+ - **Auto-task creation** with wake schedules
9
+ - **Running different AI providers** with unified interface
10
+ - **Autonomous work loops** that continue until done
11
+
12
+ ## Basic Usage
13
+
14
+ ```bash
15
+ agx claude -p "prompt" # Run Claude Code
16
+ agx gemini -p "prompt" # Run Gemini
17
+ agx ollama -p "prompt" # Run Ollama (local)
18
+ ```
19
+
20
+ ## Auto-Task Mode (Recommended)
21
+
22
+ Create a task with automatic wake schedule:
23
+
24
+ ```bash
25
+ cd ~/Projects/my-app
26
+ agx claude --auto-task -p "Build a React todo app with auth"
27
+ # ✓ Created task: build-react-todo
28
+ # ✓ Mapped: ~/Projects/my-app → task/build-react-todo
29
+ # ✓ Wake: every 15m (until done)
30
+ ```
31
+
32
+ This:
33
+ 1. Creates a mem task branch
34
+ 2. Sets wake schedule (every 15m)
35
+ 3. Installs cron to continue automatically
36
+ 4. Agent works until [done] or [blocked]
37
+
38
+ ## Wake Loop
39
+
40
+ ```
41
+ WAKE (cron) → Load context → Agent works → Save state → SLEEP
42
+
43
+ repeat until [done]
44
+ ```
45
+
46
+ Install the wake schedule:
47
+ ```bash
48
+ mem cron export # View entry
49
+ (crontab -l; mem cron export) | crontab - # Install
50
+ ```
51
+
52
+ ## Output Markers
53
+
54
+ Use these in agent output to control the loop:
55
+
56
+ ### Progress (parsed automatically)
57
+ ```
58
+ [checkpoint: message] # Save progress point
59
+ [learn: insight] # Record learning
60
+ [next: step] # Set next step
61
+ [criteria: N] # Mark criterion #N complete
62
+ ```
63
+
64
+ ### Stopping Markers
65
+ ```
66
+ [done] # Task complete, clear wake, stop
67
+ [blocked: reason] # Need human help, pause loop
68
+ [approve: question] # Need approval, wait
69
+ [pause] # Stop, resume on next wake
70
+ ```
71
+
72
+ ### Loop Control
73
+ ```
74
+ [continue] # Keep going (--daemon mode loops locally)
75
+ ```
76
+
77
+ **Default behavior:** Keep working. Only output stopping markers when needed.
78
+
79
+ ## Provider Aliases
80
+
81
+ | Provider | Aliases | Description |
82
+ |----------|---------|-------------|
83
+ | claude | c, cl | Anthropic Claude Code |
84
+ | gemini | g, gem | Google Gemini |
85
+ | ollama | o, oll | Local Ollama |
86
+
87
+ ## Common Flags
88
+
89
+ ```bash
90
+ --auto-task # Auto-create task from prompt
91
+ --task NAME # Specific task name
92
+ --criteria "..." # Success criterion (repeatable)
93
+ --daemon # Loop on [continue] marker
94
+ --until-done # Keep running until [done]
95
+ -y # Skip confirmations
96
+ ```
97
+
98
+ ## Examples
99
+
100
+ ### Quick one-shot
101
+ ```bash
102
+ agx claude -p "Explain this error" -y
103
+ ```
104
+
105
+ ### Background task with wake
106
+ ```bash
107
+ cd ~/Projects/api
108
+ agx claude --auto-task -p "Add user authentication with JWT"
109
+ # Agent works, saves progress, wakes every 15m to continue
110
+ ```
111
+
112
+ ### Check on a running task
113
+ ```bash
114
+ mem status # Quick summary
115
+ mem progress # % complete
116
+ mem context # Full state dump
117
+ ```
118
+
119
+ ### Manual continue
120
+ ```bash
121
+ cd ~/Projects/api
122
+ agx claude -p "continue" # Resume from last checkpoint
123
+ ```