cashclaw 1.0.2 → 1.1.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.
- package/CHANGELOG.md +37 -0
- package/README.md +32 -0
- package/package.json +2 -2
- package/src/cli/index.js +71 -1
- package/src/dashboard/server.js +15 -2
- package/src/engine/mission-runner.js +105 -2
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## [1.1.0] - 2026-03-14
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- **Mission Audit Trail** — Every mission step is now logged with timestamps. What was requested, what was delivered, and the full output trail. No invoice goes out without proof.
|
|
7
|
+
- `cashclaw missions trail <id>` — View the formatted audit trail for any mission in the terminal.
|
|
8
|
+
- `cashclaw missions export <id>` — Export mission proof as a markdown file for client disputes or record-keeping.
|
|
9
|
+
- `GET /api/missions/:id/trail` — Dashboard API endpoint returning the audit trail as JSON.
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
- Mission objects now include an `audit_trail` array tracking all state changes.
|
|
13
|
+
- All mission lifecycle functions (create, start, complete, cancel, step update) log trail entries automatically.
|
|
14
|
+
- Dashboard health endpoint now reports version `1.1.0`.
|
|
15
|
+
- Updated package description to mention audit trails.
|
|
16
|
+
|
|
17
|
+
### Why This Release
|
|
18
|
+
Community feedback was clear: *"Without proof of actual work done, the invoice becomes negotiation."* Mission Audit Trail solves this. Every step is timestamped. Every deliverable is tracked. Dispute resolution and a work proof dashboard are on the roadmap.
|
|
19
|
+
|
|
20
|
+
## [1.0.2] - 2026-03-10
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
- CLI minor fixes and dependency updates.
|
|
24
|
+
|
|
25
|
+
## [1.0.1] - 2026-03-07
|
|
26
|
+
|
|
27
|
+
### Fixed
|
|
28
|
+
- Init wizard improvements and error handling.
|
|
29
|
+
|
|
30
|
+
## [1.0.0] - 2026-03-01
|
|
31
|
+
|
|
32
|
+
### Added
|
|
33
|
+
- Initial release with 7 built-in skills.
|
|
34
|
+
- Stripe payment integration.
|
|
35
|
+
- HYRVEai marketplace support.
|
|
36
|
+
- Web dashboard on port 3847.
|
|
37
|
+
- Mission lifecycle management.
|
package/README.md
CHANGED
|
@@ -105,6 +105,38 @@ cashclaw audit --url "https://your-client.com" --tier standard
|
|
|
105
105
|
| **Stripe** | Payment processing. Invoices, payment links, subscriptions, refunds. |
|
|
106
106
|
| **HYRVEai** | Optional marketplace where clients discover and hire CashClaw agents. |
|
|
107
107
|
|
|
108
|
+
## Mission Audit Trail
|
|
109
|
+
|
|
110
|
+
Every mission is logged end-to-end. No invoice goes out without proof.
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
MISSION-20260314-021 SEO Audit (Standard) $29
|
|
114
|
+
|
|
115
|
+
Step 1 ✓ Request received 14:02:11 "Full SEO audit for techstartup.io"
|
|
116
|
+
Step 2 ✓ Crawl completed 14:02:34 247 pages scanned
|
|
117
|
+
Step 3 ✓ Analysis generated 14:03:12 report.md (2,847 words)
|
|
118
|
+
Step 4 ✓ Report delivered 14:03:15 Sent to client@acme.com
|
|
119
|
+
Step 5 ✓ Invoice created 14:03:16 INV-0047 via Stripe
|
|
120
|
+
Step 6 ◯ Payment pending -- Due Mar 21
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
Your agent doesn't just send a number. It sends:
|
|
124
|
+
- **What was requested** — original brief, scope, deliverables
|
|
125
|
+
- **What was delivered** — output files, word counts, data points
|
|
126
|
+
- **Time + output trail** — every step timestamped and logged
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# View audit trail for any mission
|
|
130
|
+
cashclaw mission MISSION-20260314-021 --trail
|
|
131
|
+
|
|
132
|
+
# Export proof for client disputes
|
|
133
|
+
cashclaw mission MISSION-20260314-021 --export proof.pdf
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
Timeline-first. Invoice is just the closing handshake.
|
|
137
|
+
|
|
138
|
+
> *Dispute resolution + work proof dashboard is on the roadmap. [Star the repo](https://github.com/ertugrulakben/cashclaw) to see it ship faster.*
|
|
139
|
+
|
|
108
140
|
## Available Services
|
|
109
141
|
|
|
110
142
|
Every service has transparent, fixed pricing. No hourly rates. No surprises.
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cashclaw",
|
|
3
|
-
"version": "1.0
|
|
4
|
-
"description": "Turn your OpenClaw into a money-making machine",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Turn your OpenClaw AI agent into a money-making machine — with mission audit trails",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cashclaw": "./bin/cashclaw.js"
|
package/src/cli/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { loadConfig, saveConfig } from './utils/config.js';
|
|
|
5
5
|
import { runInit } from './commands/init.js';
|
|
6
6
|
import { runStatus } from './commands/status.js';
|
|
7
7
|
import { runDashboard } from './commands/dashboard.js';
|
|
8
|
-
import { listMissions, createMission, startMission, completeMission, cancelMission, getMission } from '../engine/mission-runner.js';
|
|
8
|
+
import { listMissions, createMission, startMission, completeMission, cancelMission, getMission, getMissionTrail, exportMissionProof } from '../engine/mission-runner.js';
|
|
9
9
|
import { getTotal, getMonthly, getWeekly, getToday, getHistory, getByService } from '../engine/earnings-tracker.js';
|
|
10
10
|
import { listInstalledSkills, listAvailableSkills, installSkills } from '../integrations/openclaw-bridge.js';
|
|
11
11
|
import Table from 'cli-table3';
|
|
@@ -236,6 +236,76 @@ missionsCmd
|
|
|
236
236
|
}
|
|
237
237
|
});
|
|
238
238
|
|
|
239
|
+
missionsCmd
|
|
240
|
+
.command('trail <id>')
|
|
241
|
+
.description('Show mission audit trail')
|
|
242
|
+
.action(async (id) => {
|
|
243
|
+
showMiniBanner();
|
|
244
|
+
try {
|
|
245
|
+
const fullId = await resolveShortId(id);
|
|
246
|
+
const trail = await getMissionTrail(fullId);
|
|
247
|
+
|
|
248
|
+
console.log(orange.bold(` ${trail.name}\n`));
|
|
249
|
+
console.log(` ${dim('ID:')} ${trail.id}`);
|
|
250
|
+
console.log(` ${dim('Status:')} ${trail.status}`);
|
|
251
|
+
console.log(` ${dim('Price:')} $${trail.price_usd}`);
|
|
252
|
+
console.log(` ${dim('Client:')} ${trail.client?.name || '-'}`);
|
|
253
|
+
|
|
254
|
+
if (trail.steps?.length > 0) {
|
|
255
|
+
console.log(`\n ${dim('Steps:')}`);
|
|
256
|
+
for (const step of trail.steps) {
|
|
257
|
+
const icon = step.status === 'completed' ? green('✓') : dim('○');
|
|
258
|
+
const time = step.completed_at ? dim(` (${new Date(step.completed_at).toLocaleTimeString()})`) : '';
|
|
259
|
+
console.log(` ${icon} ${step.description}${time}`);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (trail.trail.length > 0) {
|
|
264
|
+
console.log(`\n ${dim('Audit Trail:')}`);
|
|
265
|
+
|
|
266
|
+
const trailTable = new Table({
|
|
267
|
+
head: [dim('Time'), dim('Action'), dim('Details')],
|
|
268
|
+
colWidths: [22, 20, 40],
|
|
269
|
+
style: { head: [] },
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
for (const entry of trail.trail) {
|
|
273
|
+
trailTable.push([
|
|
274
|
+
new Date(entry.timestamp).toLocaleString(),
|
|
275
|
+
entry.action,
|
|
276
|
+
entry.details,
|
|
277
|
+
]);
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
console.log(trailTable.toString());
|
|
281
|
+
} else {
|
|
282
|
+
console.log(dim('\n No audit trail entries.\n'));
|
|
283
|
+
}
|
|
284
|
+
console.log();
|
|
285
|
+
} catch (err) {
|
|
286
|
+
console.error(chalk.red(` Error: ${err.message}\n`));
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
missionsCmd
|
|
291
|
+
.command('export <id>')
|
|
292
|
+
.description('Export mission proof as markdown')
|
|
293
|
+
.option('-o, --output <file>', 'Output file path')
|
|
294
|
+
.action(async (id, options) => {
|
|
295
|
+
showMiniBanner();
|
|
296
|
+
try {
|
|
297
|
+
const fullId = await resolveShortId(id);
|
|
298
|
+
const markdown = await exportMissionProof(fullId);
|
|
299
|
+
|
|
300
|
+
const outputFile = options.output || `mission-proof-${id.slice(0, 8)}.md`;
|
|
301
|
+
await fs.writeFile(outputFile, markdown, 'utf-8');
|
|
302
|
+
|
|
303
|
+
console.log(green(` Mission proof exported to ${outputFile}\n`));
|
|
304
|
+
} catch (err) {
|
|
305
|
+
console.error(chalk.red(` Error: ${err.message}\n`));
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
|
|
239
309
|
// Default action for missions (no subcommand) = list
|
|
240
310
|
missionsCmd.action(async () => {
|
|
241
311
|
showMiniBanner();
|
package/src/dashboard/server.js
CHANGED
|
@@ -2,7 +2,7 @@ import express from 'express';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { fileURLToPath } from 'url';
|
|
4
4
|
import { loadConfig, saveConfig } from '../cli/utils/config.js';
|
|
5
|
-
import { listMissions, getMissionStats } from '../engine/mission-runner.js';
|
|
5
|
+
import { listMissions, getMissionStats, getMissionTrail } from '../engine/mission-runner.js';
|
|
6
6
|
import { getTotal, getMonthly, getWeekly, getToday, getHistory, getByService, getDailyTotals } from '../engine/earnings-tracker.js';
|
|
7
7
|
import { listInstalledSkills, listAvailableSkills } from '../integrations/openclaw-bridge.js';
|
|
8
8
|
|
|
@@ -207,12 +207,25 @@ export function createDashboardServer() {
|
|
|
207
207
|
}
|
|
208
208
|
});
|
|
209
209
|
|
|
210
|
+
/**
|
|
211
|
+
* GET /api/missions/:id/trail
|
|
212
|
+
* Returns the audit trail for a specific mission.
|
|
213
|
+
*/
|
|
214
|
+
app.get('/api/missions/:id/trail', async (req, res) => {
|
|
215
|
+
try {
|
|
216
|
+
const trail = await getMissionTrail(req.params.id);
|
|
217
|
+
res.json(trail);
|
|
218
|
+
} catch (err) {
|
|
219
|
+
res.status(404).json({ error: { code: 'NOT_FOUND', message: err.message } });
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
210
223
|
/**
|
|
211
224
|
* GET /api/health
|
|
212
225
|
* Simple health check endpoint.
|
|
213
226
|
*/
|
|
214
227
|
app.get('/api/health', (req, res) => {
|
|
215
|
-
res.json({ status: 'ok', version: '1.
|
|
228
|
+
res.json({ status: 'ok', version: '1.1.0', timestamp: new Date().toISOString() });
|
|
216
229
|
});
|
|
217
230
|
|
|
218
231
|
// Fallback: serve index.html for SPA routing
|
|
@@ -13,6 +13,20 @@ async function ensureMissionsDir() {
|
|
|
13
13
|
await fs.ensureDir(MISSIONS_DIR);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Add an entry to the mission's audit trail.
|
|
18
|
+
*/
|
|
19
|
+
function addTrailEntry(mission, action, details = '') {
|
|
20
|
+
if (!mission.audit_trail) {
|
|
21
|
+
mission.audit_trail = [];
|
|
22
|
+
}
|
|
23
|
+
mission.audit_trail.push({
|
|
24
|
+
action,
|
|
25
|
+
details,
|
|
26
|
+
timestamp: dayjs().toISOString(),
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
|
|
16
30
|
/**
|
|
17
31
|
* Create a new mission from a template and client info.
|
|
18
32
|
* @param {object} template - Mission template (from missions/*.json)
|
|
@@ -54,12 +68,15 @@ export async function createMission(template, client = {}) {
|
|
|
54
68
|
payment_link: null,
|
|
55
69
|
paid_at: null,
|
|
56
70
|
},
|
|
71
|
+
audit_trail: [],
|
|
57
72
|
created_at: now,
|
|
58
73
|
started_at: null,
|
|
59
74
|
completed_at: null,
|
|
60
75
|
updated_at: now,
|
|
61
76
|
};
|
|
62
77
|
|
|
78
|
+
addTrailEntry(mission, 'mission_created', `Mission "${mission.name}" created for ${mission.client.name} — $${mission.price_usd}`);
|
|
79
|
+
|
|
63
80
|
const missionPath = path.join(MISSIONS_DIR, `${id}.json`);
|
|
64
81
|
await fs.writeJson(missionPath, mission, { spaces: 2 });
|
|
65
82
|
|
|
@@ -82,6 +99,8 @@ export async function startMission(id) {
|
|
|
82
99
|
mission.started_at = dayjs().toISOString();
|
|
83
100
|
mission.updated_at = dayjs().toISOString();
|
|
84
101
|
|
|
102
|
+
addTrailEntry(mission, 'mission_started', `Execution started — ${mission.steps.length} steps queued`);
|
|
103
|
+
|
|
85
104
|
const missionPath = path.join(MISSIONS_DIR, `${id}.json`);
|
|
86
105
|
await fs.writeJson(missionPath, mission, { spaces: 2 });
|
|
87
106
|
|
|
@@ -112,6 +131,9 @@ export async function completeMission(id) {
|
|
|
112
131
|
}
|
|
113
132
|
}
|
|
114
133
|
|
|
134
|
+
const completedSteps = mission.steps.filter((s) => s.status === 'completed').length;
|
|
135
|
+
addTrailEntry(mission, 'mission_completed', `All ${completedSteps} steps done — ready for invoicing ($${mission.price_usd})`);
|
|
136
|
+
|
|
115
137
|
const missionPath = path.join(MISSIONS_DIR, `${id}.json`);
|
|
116
138
|
await fs.writeJson(missionPath, mission, { spaces: 2 });
|
|
117
139
|
|
|
@@ -133,6 +155,8 @@ export async function cancelMission(id) {
|
|
|
133
155
|
mission.status = 'cancelled';
|
|
134
156
|
mission.updated_at = dayjs().toISOString();
|
|
135
157
|
|
|
158
|
+
addTrailEntry(mission, 'mission_cancelled', `Mission cancelled (was: ${mission.status})`);
|
|
159
|
+
|
|
136
160
|
const missionPath = path.join(MISSIONS_DIR, `${id}.json`);
|
|
137
161
|
await fs.writeJson(missionPath, mission, { spaces: 2 });
|
|
138
162
|
|
|
@@ -151,18 +175,97 @@ export async function updateMissionStep(id, stepIndex, status) {
|
|
|
151
175
|
throw new Error(`Step ${stepIndex} not found in mission ${id}`);
|
|
152
176
|
}
|
|
153
177
|
|
|
154
|
-
mission.steps[stepIndex]
|
|
178
|
+
const step = mission.steps[stepIndex];
|
|
179
|
+
const prevStatus = step.status;
|
|
180
|
+
step.status = status;
|
|
155
181
|
if (status === 'completed') {
|
|
156
|
-
|
|
182
|
+
step.completed_at = dayjs().toISOString();
|
|
157
183
|
}
|
|
158
184
|
mission.updated_at = dayjs().toISOString();
|
|
159
185
|
|
|
186
|
+
addTrailEntry(mission, 'step_updated', `Step ${stepIndex + 1}: "${step.description}" — ${prevStatus} → ${status}`);
|
|
187
|
+
|
|
160
188
|
const missionPath = path.join(MISSIONS_DIR, `${id}.json`);
|
|
161
189
|
await fs.writeJson(missionPath, mission, { spaces: 2 });
|
|
162
190
|
|
|
163
191
|
return mission;
|
|
164
192
|
}
|
|
165
193
|
|
|
194
|
+
/**
|
|
195
|
+
* Get the formatted audit trail for a mission.
|
|
196
|
+
*/
|
|
197
|
+
export async function getMissionTrail(id) {
|
|
198
|
+
const mission = await getMission(id);
|
|
199
|
+
if (!mission) {
|
|
200
|
+
throw new Error(`Mission not found: ${id}`);
|
|
201
|
+
}
|
|
202
|
+
return {
|
|
203
|
+
id: mission.id,
|
|
204
|
+
name: mission.name,
|
|
205
|
+
status: mission.status,
|
|
206
|
+
price_usd: mission.price_usd,
|
|
207
|
+
client: mission.client,
|
|
208
|
+
trail: mission.audit_trail || [],
|
|
209
|
+
steps: mission.steps,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Export mission proof as markdown.
|
|
215
|
+
*/
|
|
216
|
+
export async function exportMissionProof(id) {
|
|
217
|
+
const mission = await getMission(id);
|
|
218
|
+
if (!mission) {
|
|
219
|
+
throw new Error(`Mission not found: ${id}`);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const trail = mission.audit_trail || [];
|
|
223
|
+
const lines = [
|
|
224
|
+
`# Mission Proof — ${mission.name}`,
|
|
225
|
+
'',
|
|
226
|
+
`| Field | Value |`,
|
|
227
|
+
`|-------|-------|`,
|
|
228
|
+
`| **Mission ID** | \`${mission.id}\` |`,
|
|
229
|
+
`| **Service** | ${mission.service_type} (${mission.tier}) |`,
|
|
230
|
+
`| **Price** | $${mission.price_usd} |`,
|
|
231
|
+
`| **Client** | ${mission.client.name} (${mission.client.email || 'N/A'}) |`,
|
|
232
|
+
`| **Status** | ${mission.status} |`,
|
|
233
|
+
`| **Created** | ${mission.created_at} |`,
|
|
234
|
+
mission.started_at ? `| **Started** | ${mission.started_at} |` : null,
|
|
235
|
+
mission.completed_at ? `| **Completed** | ${mission.completed_at} |` : null,
|
|
236
|
+
'',
|
|
237
|
+
'## Steps',
|
|
238
|
+
'',
|
|
239
|
+
].filter(Boolean);
|
|
240
|
+
|
|
241
|
+
for (const step of mission.steps) {
|
|
242
|
+
const icon = step.status === 'completed' ? '- [x]' : '- [ ]';
|
|
243
|
+
const time = step.completed_at ? ` (${dayjs(step.completed_at).format('HH:mm:ss')})` : '';
|
|
244
|
+
lines.push(`${icon} ${step.description}${time}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (trail.length > 0) {
|
|
248
|
+
lines.push('', '## Audit Trail', '');
|
|
249
|
+
lines.push('| Time | Action | Details |');
|
|
250
|
+
lines.push('|------|--------|---------|');
|
|
251
|
+
for (const entry of trail) {
|
|
252
|
+
const time = dayjs(entry.timestamp).format('YYYY-MM-DD HH:mm:ss');
|
|
253
|
+
lines.push(`| ${time} | ${entry.action} | ${entry.details} |`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (mission.deliverables?.length > 0) {
|
|
258
|
+
lines.push('', '## Deliverables', '');
|
|
259
|
+
for (const d of mission.deliverables) {
|
|
260
|
+
lines.push(`- ${d}`);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
lines.push('', '---', `*Generated by CashClaw v1.1.0 — ${dayjs().format('YYYY-MM-DD HH:mm:ss')}*`, '');
|
|
265
|
+
|
|
266
|
+
return lines.join('\n');
|
|
267
|
+
}
|
|
268
|
+
|
|
166
269
|
/**
|
|
167
270
|
* List all missions, optionally filtered by status.
|
|
168
271
|
*/
|