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 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.2",
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();
@@ -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.0.0', timestamp: new Date().toISOString() });
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].status = status;
178
+ const step = mission.steps[stepIndex];
179
+ const prevStatus = step.status;
180
+ step.status = status;
155
181
  if (status === 'completed') {
156
- mission.steps[stepIndex].completed_at = dayjs().toISOString();
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
  */