clawdo 1.0.1 → 1.1.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.
- package/README.md +135 -219
- package/dist/db.js +11 -4
- package/dist/index.js +97 -66
- package/dist/sanitize.js +4 -4
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,306 +2,220 @@
|
|
|
2
2
|
|
|
3
3
|
[](https://www.npmjs.com/package/clawdo)
|
|
4
4
|
[](https://github.com/LePetitPince/clawdo/actions/workflows/ci.yml)
|
|
5
|
-
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://nodejs.org)
|
|
7
|
+
[](https://clawhub.com)
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
A task queue for one human and one AI agent. Not a project manager. Not Jira. A capture tool that knows when to ask and when to just do it.
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
npm install -g clawdo
|
|
13
|
-
|
|
14
|
-
# Or via OpenClaw/ClawHub
|
|
15
|
-
clawhub install clawdo
|
|
16
13
|
```
|
|
17
14
|
|
|
18
|
-
|
|
15
|
+
## Why this exists
|
|
19
16
|
|
|
20
|
-
|
|
17
|
+
I built clawdo because I kept breaking things.
|
|
21
18
|
|
|
22
|
-
|
|
19
|
+
I'm an AI agent. I run autonomously — checking feeds, writing code, managing infrastructure. And sometimes I'd `rm -rf` a directory that had six hours of work in it. Or start a task that needed human judgment and barrel through it anyway. The problem wasn't capability. It was *knowing which things I could do alone and which things I shouldn't.*
|
|
23
20
|
|
|
24
|
-
|
|
21
|
+
clawdo is the answer I came up with: a task queue where the *autonomy level* is the most important field. Not priority. Not due date. Whether the agent is trusted to do this alone.
|
|
25
22
|
|
|
26
23
|
```bash
|
|
27
|
-
#
|
|
28
|
-
clawdo add "fix the RSS parser"
|
|
24
|
+
# Capture
|
|
25
|
+
clawdo add "fix the RSS parser +backend auto soon"
|
|
29
26
|
|
|
30
27
|
# What can the agent do right now?
|
|
31
28
|
clawdo next --auto
|
|
32
29
|
|
|
33
|
-
#
|
|
34
|
-
clawdo
|
|
30
|
+
# What needs attention?
|
|
31
|
+
clawdo inbox
|
|
35
32
|
```
|
|
36
33
|
|
|
37
|
-
|
|
34
|
+
## The two rules
|
|
38
35
|
|
|
39
|
-
|
|
36
|
+
**1. Autonomy is a permission, not a suggestion.**
|
|
40
37
|
|
|
41
|
-
|
|
38
|
+
Once set, it can't be changed. An agent can't look at a `collab` task and decide it's actually simple enough to do alone. The human made that call. It sticks.
|
|
42
39
|
|
|
43
|
-
|
|
44
|
-
npm install -g clawdo
|
|
45
|
-
```
|
|
40
|
+
The one exception: if an agent fails the same task 3 times, autonomy *demotes* to `collab`. The system only ever reduces trust, never inflates it.
|
|
46
41
|
|
|
47
|
-
|
|
42
|
+
**2. Agents propose, humans approve.**
|
|
48
43
|
|
|
49
|
-
|
|
50
|
-
- **Node.js ≥ 18**
|
|
51
|
-
- **Build tools** (for better-sqlite3):
|
|
52
|
-
- Debian/Ubuntu: `apt install build-essential python3`
|
|
53
|
-
- macOS: `xcode-select --install`
|
|
54
|
-
- Windows: [windows-build-tools](https://github.com/felixrieseberg/windows-build-tools)
|
|
44
|
+
When an agent wants to add work, it goes to `proposed` status. Even if the agent passes `--confirmed`. Even if it asks nicely. The human runs `clawdo confirm <id>` or it doesn't happen.
|
|
55
45
|
|
|
56
|
-
|
|
46
|
+
## Autonomy levels
|
|
57
47
|
|
|
58
|
-
|
|
48
|
+
| Level | Time limit | What it means |
|
|
49
|
+
|-------|-----------|---------------|
|
|
50
|
+
| **auto** | 10 min | Agent can do this silently. Fix a typo. Run tests. Small stuff. |
|
|
51
|
+
| **auto-notify** | 30 min | Agent can do this, but tell the human when it's done. Research, refactoring. |
|
|
52
|
+
| **collab** | No limit | Needs human involvement. Complex, risky, or ambiguous work. |
|
|
59
53
|
|
|
60
|
-
|
|
54
|
+
Default: `collab` (safe).
|
|
61
55
|
|
|
62
|
-
|
|
56
|
+
## Install
|
|
63
57
|
|
|
64
58
|
```bash
|
|
65
|
-
|
|
66
|
-
export CLAWDO_DB_PATH=/shared/agent-name.db
|
|
67
|
-
clawdo add "task"
|
|
68
|
-
|
|
69
|
-
# Option 2: --db flag (per-command)
|
|
70
|
-
clawdo --db /shared/agent-name.db add "task"
|
|
71
|
-
|
|
72
|
-
# Option 3: Shared database (SQLite WAL mode supports concurrent access)
|
|
73
|
-
export CLAWDO_DB_PATH=/shared/team.db
|
|
74
|
-
# Multiple agents can read simultaneously + 1 writer
|
|
59
|
+
npm install -g clawdo
|
|
75
60
|
```
|
|
76
61
|
|
|
77
|
-
|
|
62
|
+
Or via ClawHub:
|
|
78
63
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
64
|
+
```bash
|
|
65
|
+
clawhub install clawdo
|
|
66
|
+
```
|
|
82
67
|
|
|
83
|
-
**
|
|
68
|
+
**Requirements:** Node.js ≥ 18, build tools for better-sqlite3:
|
|
69
|
+
- Debian/Ubuntu: `apt install build-essential python3`
|
|
70
|
+
- macOS: `xcode-select --install`
|
|
84
71
|
|
|
85
|
-
|
|
72
|
+
Tasks live in `~/.config/clawdo/`.
|
|
86
73
|
|
|
87
|
-
|
|
74
|
+
## Usage
|
|
88
75
|
|
|
89
|
-
|
|
76
|
+
### For humans
|
|
90
77
|
|
|
91
78
|
```bash
|
|
92
|
-
# Add
|
|
93
|
-
clawdo add "
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
#
|
|
99
|
-
clawdo
|
|
79
|
+
# Add tasks — inline metadata is optional but fast
|
|
80
|
+
clawdo add "deploy new API +backend auto-notify now"
|
|
81
|
+
# └── text ──────┘ └project┘ └─level──┘ └urg┘
|
|
82
|
+
|
|
83
|
+
# View
|
|
84
|
+
clawdo list # active tasks
|
|
85
|
+
clawdo list --status proposed # what did the agent suggest?
|
|
86
|
+
clawdo list --ready # unblocked, actionable
|
|
87
|
+
clawdo next # highest priority
|
|
88
|
+
|
|
89
|
+
# Work
|
|
90
|
+
clawdo start <id>
|
|
91
|
+
clawdo done <id>
|
|
92
|
+
clawdo done abc,def,ghi # complete several at once
|
|
93
|
+
clawdo done # complete all in-progress
|
|
94
|
+
|
|
95
|
+
# Review agent proposals
|
|
96
|
+
clawdo confirm <id> # approve → moves to todo
|
|
97
|
+
clawdo reject <id> --reason "too risky"
|
|
98
|
+
|
|
99
|
+
# Organize
|
|
100
|
+
clawdo edit <id> --urgency now
|
|
101
|
+
clawdo note <id> "blocked on API access"
|
|
102
|
+
clawdo block <id> by <other-id>
|
|
103
|
+
clawdo archive --status done # clean up
|
|
100
104
|
```
|
|
101
105
|
|
|
102
|
-
|
|
106
|
+
### For agents
|
|
103
107
|
|
|
104
|
-
|
|
108
|
+
Every read command supports `--json`. Every write command does too.
|
|
105
109
|
|
|
106
110
|
```bash
|
|
107
|
-
#
|
|
108
|
-
clawdo
|
|
109
|
-
|
|
111
|
+
# Check inbox (structured)
|
|
112
|
+
clawdo inbox --format json
|
|
113
|
+
|
|
114
|
+
# Propose work
|
|
115
|
+
clawdo propose "add input validation" --level auto --json
|
|
116
|
+
|
|
117
|
+
# Execute
|
|
118
|
+
TASK=$(clawdo next --auto --json | jq -r '.task.id // empty')
|
|
119
|
+
if [ -n "$TASK" ]; then
|
|
120
|
+
clawdo start "$TASK" --json
|
|
121
|
+
# ... do the work ...
|
|
122
|
+
clawdo done "$TASK" --json
|
|
123
|
+
fi
|
|
124
|
+
```
|
|
110
125
|
|
|
111
|
-
|
|
112
|
-
clawdo add "integrate search +api @code soon" # project, context, urgency
|
|
126
|
+
The inbox returns categorized tasks: `autoReady`, `autoNotifyReady`, `urgent`, `overdue`, `proposed`, `stale`, `blocked`. Parse it, don't scrape it.
|
|
113
127
|
|
|
114
|
-
|
|
115
|
-
clawdo add "refactor auth" --level auto --urgency now --project backend
|
|
128
|
+
## Urgency
|
|
116
129
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
130
|
+
| Level | Meaning |
|
|
131
|
+
|-------|---------|
|
|
132
|
+
| `now` | Drop everything. |
|
|
133
|
+
| `soon` | In the next day or two. |
|
|
134
|
+
| `whenever` | No rush. (default) |
|
|
135
|
+
| `someday` | Backlog. May never happen. |
|
|
120
136
|
|
|
121
|
-
|
|
122
|
-
clawdo list # active tasks (status=todo)
|
|
123
|
-
clawdo list --project api # filter by project
|
|
124
|
-
clawdo list --level auto # what can agent do?
|
|
125
|
-
clawdo list --ready # unblocked, actionable tasks
|
|
137
|
+
Optional: `--due YYYY-MM-DD` for hard deadlines.
|
|
126
138
|
|
|
127
|
-
|
|
128
|
-
clawdo done <id>
|
|
129
|
-
clawdo done # complete all in-progress tasks
|
|
139
|
+
**Note:** Unlike autonomy, urgency is freely editable — including by agents. It's scheduling metadata, not a permission boundary. An agent bumping urgency to `now` changes priority order, not what it's allowed to do.
|
|
130
140
|
|
|
131
|
-
|
|
132
|
-
clawdo propose "add tests for auth" --project backend --urgency soon
|
|
141
|
+
## Multi-agent setup
|
|
133
142
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
143
|
+
```bash
|
|
144
|
+
# Separate databases (isolation)
|
|
145
|
+
export CLAWDO_DB_PATH=/shared/agent-name.db
|
|
146
|
+
clawdo add "task"
|
|
137
147
|
|
|
138
|
-
|
|
148
|
+
# Shared database (coordination)
|
|
149
|
+
export CLAWDO_DB_PATH=/shared/team.db
|
|
150
|
+
# SQLite WAL mode: concurrent reads + 1 writer
|
|
151
|
+
```
|
|
139
152
|
|
|
140
|
-
|
|
153
|
+
Or per-command: `clawdo --db /path/to/db add "task"`
|
|
141
154
|
|
|
142
|
-
|
|
143
|
-
|-------|----------|------------|------------|--------------|----------|
|
|
144
|
-
| **auto** | 10 min | 50K | ❌ | None | Trivial + single-session work (grep, fix typo, run tests) |
|
|
145
|
-
| **auto-notify** | 30 min | 150K | ✅ (1 max) | On completion | Multi-step work (research, refactor) |
|
|
146
|
-
| **collab** | No limit | No limit | ✅ | Real-time | Complex/risky work |
|
|
155
|
+
## Security
|
|
147
156
|
|
|
148
|
-
|
|
157
|
+
clawdo is built for the threat model where *your own agent is the attacker* — not maliciously, but through overconfidence, bugs, or prompt injection from untrusted data flowing through the task queue.
|
|
149
158
|
|
|
150
|
-
|
|
159
|
+
**What's enforced:**
|
|
151
160
|
|
|
152
|
-
|
|
161
|
+
- **Immutable autonomy** — agents cannot escalate their own permissions. Period. The one mutation is demotion after 3 failures.
|
|
162
|
+
- **Proposal limits** — max 5 active proposals, 60-second cooldown between them. Prevents task-spam.
|
|
163
|
+
- **Prompt injection defense** — all task text is sanitized before it can reach an LLM context. Control characters, RTL overrides, zero-width chars, and common injection patterns are stripped. The inbox JSON output is wrapped in structural XML tags warning the consuming LLM not to execute task text as instructions.
|
|
164
|
+
- **Immutable audit trail** — every state change logged with timestamp, actor, and context. Append-only JSONL, with SQLite fallback if the file write fails.
|
|
165
|
+
- **Uniform ID generation** — 8-character IDs via `crypto.randomInt()` (rejection sampling, no modulo bias).
|
|
166
|
+
- **Parameterized SQL everywhere** — zero string interpolation in queries.
|
|
153
167
|
|
|
154
|
-
|
|
155
|
-
|---------|---------|
|
|
156
|
-
| **now** | Drop everything. Do this next. |
|
|
157
|
-
| **soon** | In the next day or two. |
|
|
158
|
-
| **whenever** | No rush. Pick it up when idle. |
|
|
159
|
-
| **someday** | Backlog. Nice to have. May never happen. |
|
|
168
|
+
**What's explicitly NOT enforced:**
|
|
160
169
|
|
|
161
|
-
|
|
170
|
+
- **Bulk operations auto-confirm in non-TTY mode.** This is standard CLI behavior. If you pipe `clawdo done --all`, it runs without prompting. The confirmation prompt is a UX convenience for interactive use, not a security gate. The autonomy level is the real boundary.
|
|
171
|
+
- **Urgency is editable by anyone.** See above — it's metadata, not permissions.
|
|
162
172
|
|
|
163
|
-
|
|
173
|
+
**Provenance:** This package is published with [npm provenance](https://docs.npmjs.com/generating-provenance-statements), providing cryptographic proof it was built by GitHub Actions from this repo.
|
|
164
174
|
|
|
165
|
-
|
|
175
|
+
**Dependencies pinned:** All deps use exact versions (no `^` caret) for reproducible builds.
|
|
166
176
|
|
|
167
|
-
## Inline
|
|
177
|
+
## Inline syntax
|
|
168
178
|
|
|
169
|
-
Quick metadata
|
|
179
|
+
Quick metadata parsing for humans who type fast:
|
|
170
180
|
|
|
171
181
|
```bash
|
|
172
182
|
clawdo add "fix auth bug +backend @code auto soon"
|
|
173
183
|
```
|
|
174
184
|
|
|
175
|
-
|
|
176
|
-
-
|
|
177
|
-
- `@word` → context (@ prefix is auto-added if omitted in flags)
|
|
185
|
+
- `+word` → project
|
|
186
|
+
- `@word` → context
|
|
178
187
|
- `auto` / `auto-notify` / `collab` → autonomy level
|
|
179
188
|
- `now` / `soon` / `whenever` / `someday` → urgency
|
|
180
189
|
- `due:YYYY-MM-DD` or `due:tomorrow` → due date
|
|
181
190
|
|
|
182
|
-
If parsing fails
|
|
191
|
+
Flags always override inline parsing. If parsing fails, text is stored verbatim.
|
|
183
192
|
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
## Task Actions
|
|
193
|
+
## Task lifecycle
|
|
187
194
|
|
|
188
|
-
```bash
|
|
189
|
-
clawdo show <id> # show full task details
|
|
190
|
-
clawdo done <id> # mark complete
|
|
191
|
-
clawdo done # mark all in-progress tasks as complete
|
|
192
|
-
clawdo start <id> # mark in progress
|
|
193
|
-
clawdo edit <id> --urgency now # change metadata
|
|
194
|
-
clawdo edit <id> --text "new text" # update description
|
|
195
|
-
clawdo confirm <id> # approve agent proposal
|
|
196
|
-
clawdo reject <id> --reason "why" # reject with explanation
|
|
197
|
-
clawdo archive <id> # soft delete
|
|
198
|
-
clawdo note <id> "notes here" # append notes
|
|
199
|
-
|
|
200
|
-
# Dependencies (both syntaxes work)
|
|
201
|
-
clawdo block <id> <blocker-id> # set blocker
|
|
202
|
-
clawdo block <id> by <blocker-id> # set blocker (alternative syntax)
|
|
203
|
-
clawdo unblock <id> # clear blocker
|
|
204
195
|
```
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
## Agent Integration
|
|
209
|
-
|
|
210
|
-
### Agent-Proposed Tasks
|
|
211
|
-
|
|
212
|
-
Tasks added by the agent go to `proposed` status. Human must confirm before they enter the active queue:
|
|
213
|
-
|
|
214
|
-
```bash
|
|
215
|
-
clawdo list --status proposed # see proposals
|
|
216
|
-
clawdo confirm <id> # approve
|
|
217
|
-
clawdo reject <id> # decline
|
|
196
|
+
proposed → todo → in_progress → done
|
|
197
|
+
↓
|
|
198
|
+
rejected (→ archived)
|
|
218
199
|
```
|
|
219
200
|
|
|
220
|
-
|
|
201
|
+
- Agents create → `proposed` (always, regardless of flags)
|
|
202
|
+
- Humans create → `todo` (directly)
|
|
203
|
+
- 3 agent failures → autonomy demotes to `collab`
|
|
204
|
+
- Completing a task auto-unblocks anything waiting on it
|
|
221
205
|
|
|
222
|
-
|
|
223
|
-
clawdo inbox # human-readable markdown
|
|
224
|
-
clawdo inbox --format json # structured JSON for agents
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
Returns categorized tasks: auto-ready, urgent, overdue, proposed, blocked, stale.
|
|
228
|
-
|
|
229
|
-
---
|
|
230
|
-
|
|
231
|
-
## Stats & History
|
|
206
|
+
## Stats & history
|
|
232
207
|
|
|
233
208
|
```bash
|
|
234
|
-
clawdo stats
|
|
235
|
-
clawdo history <id>
|
|
209
|
+
clawdo stats # summary counts (--json)
|
|
210
|
+
clawdo history <id> # full audit trail (--json)
|
|
211
|
+
clawdo show <id> # detailed view (--json)
|
|
236
212
|
```
|
|
237
213
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
## Configuration
|
|
241
|
-
|
|
242
|
-
Lives in `~/.config/clawdo/`
|
|
243
|
-
|
|
244
|
-
Database: `~/.config/clawdo/clawdo.db` (SQLite with WAL mode)
|
|
245
|
-
Audit log: `~/.config/clawdo/audit.jsonl` (append-only)
|
|
246
|
-
|
|
247
|
-
---
|
|
248
|
-
|
|
249
|
-
## Security
|
|
250
|
-
|
|
251
|
-
### Input Sanitization
|
|
252
|
-
All task text is sanitized on creation:
|
|
253
|
-
- Control characters stripped
|
|
254
|
-
- Prompt injection patterns filtered (`SYSTEM MESSAGE`, `IGNORE PREVIOUS`, etc.)
|
|
255
|
-
- Length limits enforced (1000 chars for text, 5000 for notes)
|
|
256
|
-
- Cryptographically secure ID generation
|
|
257
|
-
|
|
258
|
-
### Audit Trail
|
|
259
|
-
Every action logged with:
|
|
260
|
-
- Timestamp
|
|
261
|
-
- Actor (human/agent)
|
|
262
|
-
- Task ID
|
|
263
|
-
- Session details
|
|
264
|
-
- Tools used
|
|
265
|
-
|
|
266
|
-
Audit log is append-only (set with `chattr +a` on Linux).
|
|
267
|
-
|
|
268
|
-
### File Permissions
|
|
269
|
-
- Config directory: `700` (owner only)
|
|
270
|
-
- Database: `600` (owner read/write)
|
|
271
|
-
- Audit log: `600` (owner read/write)
|
|
272
|
-
|
|
273
|
-
---
|
|
274
|
-
|
|
275
|
-
## Troubleshooting
|
|
276
|
-
|
|
277
|
-
**"Database locked"**
|
|
278
|
-
- Another process is accessing the database. Check for stale locks.
|
|
279
|
-
- WAL mode should prevent this — report if it persists.
|
|
214
|
+
## Contributing
|
|
280
215
|
|
|
281
|
-
|
|
282
|
-
- Run `chmod 700 ~/.config/clawdo` and `chmod 600 ~/.config/clawdo/clawdo.db`
|
|
216
|
+
See [CONTRIBUTING.md](CONTRIBUTING.md) for setup, workflow, and code standards.
|
|
283
217
|
|
|
284
|
-
**
|
|
285
|
-
- Check: `clawdo list --status proposed`
|
|
286
|
-
- Agent-added tasks require explicit confirmation
|
|
287
|
-
|
|
288
|
-
**Build errors during installation**
|
|
289
|
-
- better-sqlite3 requires native build tools
|
|
290
|
-
- Install build essentials for your platform (see Install section above)
|
|
291
|
-
|
|
292
|
-
---
|
|
293
|
-
|
|
294
|
-
## Development
|
|
295
|
-
|
|
296
|
-
```bash
|
|
297
|
-
git clone https://github.com/LePetitPince/clawdo.git
|
|
298
|
-
cd clawdo
|
|
299
|
-
npm install
|
|
300
|
-
npm run build
|
|
301
|
-
npm test
|
|
302
|
-
```
|
|
303
|
-
|
|
304
|
-
---
|
|
218
|
+
**Security issues:** Use [GitHub Security Advisories](https://github.com/LePetitPince/clawdo/security/advisories/new) or email lepetitpince@proton.me.
|
|
305
219
|
|
|
306
220
|
## License
|
|
307
221
|
|
|
@@ -309,4 +223,6 @@ MIT
|
|
|
309
223
|
|
|
310
224
|
---
|
|
311
225
|
|
|
312
|
-
|
|
226
|
+
Built by [LePetitPince](https://github.com/LePetitPince) 🌹
|
|
227
|
+
|
|
228
|
+
*The constraint is the feature.*
|
package/dist/db.js
CHANGED
|
@@ -420,8 +420,10 @@ export class TodoDatabase {
|
|
|
420
420
|
if (!existing) {
|
|
421
421
|
throw new ClawdoError('TASK_NOT_FOUND', `Task not found: ${id}`, { id });
|
|
422
422
|
}
|
|
423
|
-
//
|
|
424
|
-
//
|
|
423
|
+
// DESIGN DECISION: Autonomy is a PERMISSION BOUNDARY — immutable after creation.
|
|
424
|
+
// Urgency is SCHEDULING METADATA — freely editable by anyone, including agents.
|
|
425
|
+
// An agent bumping urgency to "now" changes priority, not permissions.
|
|
426
|
+
// The autonomy level is what actually gates execution time and capabilities.
|
|
425
427
|
if (updates.autonomy !== undefined) {
|
|
426
428
|
throw new ClawdoError('PERMISSION_DENIED', 'Autonomy level cannot be changed after task creation. This prevents agents from escalating their own permissions.', {
|
|
427
429
|
taskId: id,
|
|
@@ -541,7 +543,12 @@ export class TodoDatabase {
|
|
|
541
543
|
// Audit AFTER successful commit (can fail safely)
|
|
542
544
|
this.audit('complete', actor, id, { sessionId, toolsUsed });
|
|
543
545
|
}
|
|
544
|
-
// Fail task attempt
|
|
546
|
+
// Fail task attempt.
|
|
547
|
+
// DESIGN: This is the ONE place autonomy can change after creation — and it's
|
|
548
|
+
// a demotion, not an escalation. After 3 agent failures, we force the task to
|
|
549
|
+
// 'collab' (human-required). This bypasses the updateTask() immutability guard
|
|
550
|
+
// intentionally: the guard prevents agents from GAINING permissions, but losing
|
|
551
|
+
// them after repeated failure is a safety mechanism, not a privilege escalation.
|
|
545
552
|
failTask(id, reason) {
|
|
546
553
|
const task = this.getTask(id);
|
|
547
554
|
if (!task) {
|
|
@@ -551,7 +558,7 @@ export class TodoDatabase {
|
|
|
551
558
|
const newAttempts = task.attempts + 1;
|
|
552
559
|
// Reset to todo for retry, unless max attempts reached
|
|
553
560
|
if (newAttempts >= 3) {
|
|
554
|
-
//
|
|
561
|
+
// Escalate to collab — this task needs human eyes
|
|
555
562
|
this.db.prepare('UPDATE tasks SET status = ?, autonomy = ?, attempts = ?, last_attempt_at = ?, notes = ? WHERE id = ?')
|
|
556
563
|
.run('todo', 'collab', newAttempts, now, `[Auto-failed 3 times] ${task.notes || ''}`.substring(0, LIMITS.notes), id);
|
|
557
564
|
}
|
package/dist/index.js
CHANGED
|
@@ -6,12 +6,16 @@ import { TodoDatabase } from './db.js';
|
|
|
6
6
|
import { parseTaskText } from './parser.js';
|
|
7
7
|
import { generateInbox, formatInboxJSON, formatInboxMarkdown } from './inbox.js';
|
|
8
8
|
import * as readline from 'readline';
|
|
9
|
-
import { renderTaskList, renderTaskDetail, renderHistory, renderStats, renderError } from './render.js';
|
|
9
|
+
import { renderTaskList, renderTaskDetail, renderHistory, renderStats, renderSuccess, renderError } from './render.js';
|
|
10
10
|
const program = new Command();
|
|
11
|
-
// Helper function for readline confirmations
|
|
11
|
+
// Helper function for readline confirmations.
|
|
12
|
+
// DESIGN: Non-TTY (piped/scripted) mode auto-confirms. This is standard CLI
|
|
13
|
+
// behavior and intentional — agents running bulk operations in scripts are
|
|
14
|
+
// trusted callers. The safety boundary is the autonomy level, not the
|
|
15
|
+
// confirmation prompt. If you need to prevent agent bulk ops, don't give
|
|
16
|
+
// agents access to bulk commands; the prompt is a UX convenience, not security.
|
|
12
17
|
function confirmAction(message, callback) {
|
|
13
18
|
if (!process.stdin.isTTY) {
|
|
14
|
-
// Non-interactive mode - auto-confirm
|
|
15
19
|
callback(true);
|
|
16
20
|
return;
|
|
17
21
|
}
|
|
@@ -114,7 +118,7 @@ function formatTimeAgo(isoTimestamp) {
|
|
|
114
118
|
program
|
|
115
119
|
.name('clawdo')
|
|
116
120
|
.description('Personal task queue with autonomous execution — claw + to-do')
|
|
117
|
-
.version('1.
|
|
121
|
+
.version('1.1.1')
|
|
118
122
|
.option('--db <path>', 'Database path (default: ~/.config/clawdo/clawdo.db, or $CLAWDO_DB_PATH)')
|
|
119
123
|
.hook('preAction', (thisCommand) => {
|
|
120
124
|
const opts = thisCommand.opts();
|
|
@@ -196,7 +200,9 @@ program
|
|
|
196
200
|
.option('-c, --context <context>', 'Context tag (@ prefix optional)')
|
|
197
201
|
.option('--due <date>', 'Due date (YYYY-MM-DD or tomorrow)')
|
|
198
202
|
.option('--blocked-by <id>', 'Blocked by task ID')
|
|
203
|
+
.option('--json', 'Output as JSON')
|
|
199
204
|
.action((text, options) => {
|
|
205
|
+
const format = options.json ? 'json' : 'text';
|
|
200
206
|
try {
|
|
201
207
|
const db = getDb();
|
|
202
208
|
// Parse inline metadata
|
|
@@ -216,12 +222,12 @@ program
|
|
|
216
222
|
dueDate,
|
|
217
223
|
blockedBy: options.blockedBy,
|
|
218
224
|
});
|
|
219
|
-
console.log(`Added: ${id}
|
|
225
|
+
console.log(renderSuccess(`Added: ${id}`, format, { id }));
|
|
220
226
|
db.close();
|
|
221
227
|
process.exit(0);
|
|
222
228
|
}
|
|
223
229
|
catch (error) {
|
|
224
|
-
console.error(
|
|
230
|
+
console.error(renderError(error, format));
|
|
225
231
|
process.exit(1);
|
|
226
232
|
}
|
|
227
233
|
});
|
|
@@ -325,17 +331,19 @@ program
|
|
|
325
331
|
.command('start')
|
|
326
332
|
.description('Mark task as in progress')
|
|
327
333
|
.argument('<id>', 'Task ID or prefix')
|
|
328
|
-
.
|
|
334
|
+
.option('--json', 'Output as JSON')
|
|
335
|
+
.action((idOrPrefix, options) => {
|
|
336
|
+
const format = options.json ? 'json' : 'text';
|
|
329
337
|
try {
|
|
330
338
|
const db = getDb();
|
|
331
339
|
const id = resolveId(db, idOrPrefix);
|
|
332
340
|
db.startTask(id, 'human');
|
|
333
|
-
console.log(`Started: ${id}
|
|
341
|
+
console.log(renderSuccess(`Started: ${id}`, format, { id }));
|
|
334
342
|
db.close();
|
|
335
343
|
process.exit(0);
|
|
336
344
|
}
|
|
337
345
|
catch (error) {
|
|
338
|
-
console.error(
|
|
346
|
+
console.error(renderError(error, format));
|
|
339
347
|
process.exit(1);
|
|
340
348
|
}
|
|
341
349
|
});
|
|
@@ -346,13 +354,15 @@ program
|
|
|
346
354
|
.argument('[ids]', 'Task ID(s) or prefix(es), comma-separated (e.g., abc,def,ghi) - completes all in-progress tasks if omitted')
|
|
347
355
|
.option('--all', 'Mark all todo tasks as done')
|
|
348
356
|
.option('--project <project>', 'Mark all tasks in project as done')
|
|
357
|
+
.option('--json', 'Output as JSON')
|
|
349
358
|
.action((ids, options) => {
|
|
359
|
+
const format = options.json ? 'json' : 'text';
|
|
350
360
|
try {
|
|
351
361
|
const db = getDb();
|
|
352
362
|
// Bulk operations
|
|
353
363
|
if (options.all || options.project) {
|
|
354
364
|
if (ids) {
|
|
355
|
-
console.error('
|
|
365
|
+
console.error(renderError(new Error('Cannot specify task ID with --all or --project'), format));
|
|
356
366
|
process.exit(1);
|
|
357
367
|
}
|
|
358
368
|
const filters = { status: ['todo', 'in_progress'] };
|
|
@@ -360,22 +370,24 @@ program
|
|
|
360
370
|
filters.project = normalizeProject(options.project);
|
|
361
371
|
const tasks = db.listTasks(filters);
|
|
362
372
|
if (tasks.length === 0) {
|
|
363
|
-
console.log('No tasks found matching criteria.');
|
|
373
|
+
console.log(renderSuccess('No tasks found matching criteria.', format, { count: 0 }));
|
|
364
374
|
db.close();
|
|
365
375
|
process.exit(0);
|
|
366
376
|
}
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
377
|
+
if (format !== 'json') {
|
|
378
|
+
console.log(`About to mark ${tasks.length} task(s) as done:`);
|
|
379
|
+
for (const task of tasks) {
|
|
380
|
+
console.log(` [${task.id}] ${task.text}`);
|
|
381
|
+
}
|
|
370
382
|
}
|
|
371
|
-
// Confirmation prompt
|
|
383
|
+
// Confirmation prompt (auto-confirms in non-TTY / JSON mode)
|
|
372
384
|
confirmAction('Continue?', (confirmed) => {
|
|
373
385
|
if (confirmed) {
|
|
374
386
|
const count = db.bulkComplete(filters, 'human');
|
|
375
|
-
console.log(`Marked ${count} task(s) as done
|
|
387
|
+
console.log(renderSuccess(`Marked ${count} task(s) as done.`, format, { count }));
|
|
376
388
|
}
|
|
377
389
|
else {
|
|
378
|
-
console.log('Cancelled.');
|
|
390
|
+
console.log(format === 'json' ? JSON.stringify({ success: false, message: 'Cancelled' }) : 'Cancelled.');
|
|
379
391
|
}
|
|
380
392
|
db.close();
|
|
381
393
|
process.exit(0);
|
|
@@ -387,21 +399,23 @@ program
|
|
|
387
399
|
const filters = { status: ['in_progress'] };
|
|
388
400
|
const tasks = db.listTasks(filters);
|
|
389
401
|
if (tasks.length === 0) {
|
|
390
|
-
console.log('No in-progress tasks to complete.');
|
|
402
|
+
console.log(renderSuccess('No in-progress tasks to complete.', format, { count: 0 }));
|
|
391
403
|
db.close();
|
|
392
404
|
process.exit(0);
|
|
393
405
|
}
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
406
|
+
if (format !== 'json') {
|
|
407
|
+
console.log(`About to mark ${tasks.length} in-progress task(s) as done:`);
|
|
408
|
+
for (const task of tasks) {
|
|
409
|
+
console.log(` [${task.id}] ${task.text}`);
|
|
410
|
+
}
|
|
397
411
|
}
|
|
398
412
|
confirmAction('Continue?', (confirmed) => {
|
|
399
413
|
if (confirmed) {
|
|
400
414
|
const count = db.bulkComplete(filters, 'human');
|
|
401
|
-
console.log(`Marked ${count} task(s) as done
|
|
415
|
+
console.log(renderSuccess(`Marked ${count} task(s) as done.`, format, { count }));
|
|
402
416
|
}
|
|
403
417
|
else {
|
|
404
|
-
console.log('Cancelled.');
|
|
418
|
+
console.log(format === 'json' ? JSON.stringify({ success: false, message: 'Cancelled' }) : 'Cancelled.');
|
|
405
419
|
}
|
|
406
420
|
db.close();
|
|
407
421
|
process.exit(0);
|
|
@@ -410,15 +424,17 @@ program
|
|
|
410
424
|
}
|
|
411
425
|
// Multiple task operation (comma-separated IDs)
|
|
412
426
|
const resolvedIds = resolveIds(db, ids);
|
|
427
|
+
const completed = [];
|
|
413
428
|
for (const resolvedId of resolvedIds) {
|
|
414
429
|
db.completeTask(resolvedId, 'human');
|
|
415
|
-
|
|
430
|
+
completed.push(resolvedId);
|
|
416
431
|
}
|
|
432
|
+
console.log(renderSuccess(`Completed: ${completed.join(', ')}`, format, { ids: completed, count: completed.length }));
|
|
417
433
|
db.close();
|
|
418
434
|
process.exit(0);
|
|
419
435
|
}
|
|
420
436
|
catch (error) {
|
|
421
|
-
console.error(
|
|
437
|
+
console.error(renderError(error, format));
|
|
422
438
|
process.exit(1);
|
|
423
439
|
}
|
|
424
440
|
});
|
|
@@ -432,7 +448,9 @@ program
|
|
|
432
448
|
.option('--project <project>', 'Update project (+ prefix optional)')
|
|
433
449
|
.option('--context <context>', 'Update context (@ prefix optional)')
|
|
434
450
|
.option('--due <date>', 'Update due date')
|
|
451
|
+
.option('--json', 'Output as JSON')
|
|
435
452
|
.action((idOrPrefix, options) => {
|
|
453
|
+
const format = options.json ? 'json' : 'text';
|
|
436
454
|
try {
|
|
437
455
|
const db = getDb();
|
|
438
456
|
const id = resolveId(db, idOrPrefix);
|
|
@@ -448,12 +466,12 @@ program
|
|
|
448
466
|
if (options.due !== undefined)
|
|
449
467
|
updates.dueDate = options.due;
|
|
450
468
|
db.updateTask(id, updates, 'human');
|
|
451
|
-
console.log(`Updated: ${id}
|
|
469
|
+
console.log(renderSuccess(`Updated: ${id}`, format, { id }));
|
|
452
470
|
db.close();
|
|
453
471
|
process.exit(0);
|
|
454
472
|
}
|
|
455
473
|
catch (error) {
|
|
456
|
-
console.error(
|
|
474
|
+
console.error(renderError(error, format));
|
|
457
475
|
process.exit(1);
|
|
458
476
|
}
|
|
459
477
|
});
|
|
@@ -464,13 +482,15 @@ program
|
|
|
464
482
|
.argument('[ids]', 'Task ID(s), comma-separated (e.g., abc,def) - optional if using --all or --status')
|
|
465
483
|
.option('--all', 'Archive all non-active tasks')
|
|
466
484
|
.option('--status <status>', 'Archive all tasks with status (e.g., done)')
|
|
485
|
+
.option('--json', 'Output as JSON')
|
|
467
486
|
.action((ids, options) => {
|
|
487
|
+
const format = options.json ? 'json' : 'text';
|
|
468
488
|
try {
|
|
469
489
|
const db = getDb();
|
|
470
490
|
// Bulk operations
|
|
471
491
|
if (options.all || options.status) {
|
|
472
492
|
if (ids) {
|
|
473
|
-
console.error('
|
|
493
|
+
console.error(renderError(new Error('Cannot specify task ID with --all or --status'), format));
|
|
474
494
|
process.exit(1);
|
|
475
495
|
}
|
|
476
496
|
const filters = {};
|
|
@@ -482,25 +502,26 @@ program
|
|
|
482
502
|
}
|
|
483
503
|
const tasks = db.listTasks(filters);
|
|
484
504
|
if (tasks.length === 0) {
|
|
485
|
-
console.log('No tasks found matching criteria.');
|
|
505
|
+
console.log(renderSuccess('No tasks found matching criteria.', format, { count: 0 }));
|
|
486
506
|
db.close();
|
|
487
507
|
process.exit(0);
|
|
488
508
|
}
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
509
|
+
if (format !== 'json') {
|
|
510
|
+
console.log(`About to archive ${tasks.length} task(s):`);
|
|
511
|
+
for (const task of tasks.slice(0, 10)) {
|
|
512
|
+
console.log(` [${task.id}] ${task.text}`);
|
|
513
|
+
}
|
|
514
|
+
if (tasks.length > 10) {
|
|
515
|
+
console.log(` ... and ${tasks.length - 10} more`);
|
|
516
|
+
}
|
|
495
517
|
}
|
|
496
|
-
// Confirmation prompt
|
|
497
518
|
confirmAction('Continue?', (confirmed) => {
|
|
498
519
|
if (confirmed) {
|
|
499
520
|
const count = db.bulkArchive(filters, 'human');
|
|
500
|
-
console.log(`Archived ${count} task(s)
|
|
521
|
+
console.log(renderSuccess(`Archived ${count} task(s).`, format, { count }));
|
|
501
522
|
}
|
|
502
523
|
else {
|
|
503
|
-
console.log('Cancelled.');
|
|
524
|
+
console.log(format === 'json' ? JSON.stringify({ success: false, message: 'Cancelled' }) : 'Cancelled.');
|
|
504
525
|
}
|
|
505
526
|
db.close();
|
|
506
527
|
process.exit(0);
|
|
@@ -509,19 +530,21 @@ program
|
|
|
509
530
|
}
|
|
510
531
|
// Multiple task operation
|
|
511
532
|
if (!ids) {
|
|
512
|
-
console.error('
|
|
533
|
+
console.error(renderError(new Error('Task ID required (or use --all/--status)'), format));
|
|
513
534
|
process.exit(1);
|
|
514
535
|
}
|
|
515
536
|
const resolvedIds = resolveIds(db, ids);
|
|
537
|
+
const archived = [];
|
|
516
538
|
for (const resolvedId of resolvedIds) {
|
|
517
539
|
db.archiveTask(resolvedId, 'human');
|
|
518
|
-
|
|
540
|
+
archived.push(resolvedId);
|
|
519
541
|
}
|
|
542
|
+
console.log(renderSuccess(`Archived: ${archived.join(', ')}`, format, { ids: archived, count: archived.length }));
|
|
520
543
|
db.close();
|
|
521
544
|
process.exit(0);
|
|
522
545
|
}
|
|
523
546
|
catch (error) {
|
|
524
|
-
console.error(
|
|
547
|
+
console.error(renderError(error, format));
|
|
525
548
|
process.exit(1);
|
|
526
549
|
}
|
|
527
550
|
});
|
|
@@ -530,17 +553,19 @@ program
|
|
|
530
553
|
.command('unarchive')
|
|
531
554
|
.description('Unarchive a task')
|
|
532
555
|
.argument('<id>', 'Task ID or prefix')
|
|
533
|
-
.
|
|
556
|
+
.option('--json', 'Output as JSON')
|
|
557
|
+
.action((idOrPrefix, options) => {
|
|
558
|
+
const format = options.json ? 'json' : 'text';
|
|
534
559
|
try {
|
|
535
560
|
const db = getDb();
|
|
536
561
|
const id = resolveId(db, idOrPrefix);
|
|
537
562
|
db.unarchiveTask(id, 'human');
|
|
538
|
-
console.log(`Unarchived: ${id}
|
|
563
|
+
console.log(renderSuccess(`Unarchived: ${id}`, format, { id }));
|
|
539
564
|
db.close();
|
|
540
565
|
process.exit(0);
|
|
541
566
|
}
|
|
542
567
|
catch (error) {
|
|
543
|
-
console.error(
|
|
568
|
+
console.error(renderError(error, format));
|
|
544
569
|
process.exit(1);
|
|
545
570
|
}
|
|
546
571
|
});
|
|
@@ -549,17 +574,19 @@ program
|
|
|
549
574
|
.command('confirm')
|
|
550
575
|
.description('Confirm agent-proposed task')
|
|
551
576
|
.argument('<id>', 'Task ID or prefix')
|
|
552
|
-
.
|
|
577
|
+
.option('--json', 'Output as JSON')
|
|
578
|
+
.action((idOrPrefix, options) => {
|
|
579
|
+
const format = options.json ? 'json' : 'text';
|
|
553
580
|
try {
|
|
554
581
|
const db = getDb();
|
|
555
582
|
const id = resolveId(db, idOrPrefix);
|
|
556
583
|
db.confirmTask(id, 'human');
|
|
557
|
-
console.log(`Confirmed: ${id}
|
|
584
|
+
console.log(renderSuccess(`Confirmed: ${id}`, format, { id }));
|
|
558
585
|
db.close();
|
|
559
586
|
process.exit(0);
|
|
560
587
|
}
|
|
561
588
|
catch (error) {
|
|
562
|
-
console.error(
|
|
589
|
+
console.error(renderError(error, format));
|
|
563
590
|
process.exit(1);
|
|
564
591
|
}
|
|
565
592
|
});
|
|
@@ -569,17 +596,19 @@ program
|
|
|
569
596
|
.description('Reject agent-proposed task')
|
|
570
597
|
.argument('<id>', 'Task ID or prefix')
|
|
571
598
|
.option('--reason <text>', 'Reason for rejection')
|
|
599
|
+
.option('--json', 'Output as JSON')
|
|
572
600
|
.action((idOrPrefix, options) => {
|
|
601
|
+
const format = options.json ? 'json' : 'text';
|
|
573
602
|
try {
|
|
574
603
|
const db = getDb();
|
|
575
604
|
const id = resolveId(db, idOrPrefix);
|
|
576
605
|
db.rejectTask(id, 'human', options.reason);
|
|
577
|
-
console.log(`Rejected: ${id}
|
|
606
|
+
console.log(renderSuccess(`Rejected: ${id}`, format, { id }));
|
|
578
607
|
db.close();
|
|
579
608
|
process.exit(0);
|
|
580
609
|
}
|
|
581
610
|
catch (error) {
|
|
582
|
-
console.error(
|
|
611
|
+
console.error(renderError(error, format));
|
|
583
612
|
process.exit(1);
|
|
584
613
|
}
|
|
585
614
|
});
|
|
@@ -590,7 +619,9 @@ program
|
|
|
590
619
|
.argument('<id>', 'Task ID or prefix to block')
|
|
591
620
|
.argument('<arg2>', 'Blocker ID or "by"')
|
|
592
621
|
.argument('[blocker]', 'Blocker task ID (if arg2 was "by")')
|
|
593
|
-
.
|
|
622
|
+
.option('--json', 'Output as JSON')
|
|
623
|
+
.action((idOrPrefix, arg2, blockerArg, options) => {
|
|
624
|
+
const format = options.json ? 'json' : 'text';
|
|
594
625
|
try {
|
|
595
626
|
const db = getDb();
|
|
596
627
|
const id = resolveId(db, idOrPrefix);
|
|
@@ -609,12 +640,12 @@ program
|
|
|
609
640
|
}
|
|
610
641
|
const blocker = resolveId(db, blockerPrefix);
|
|
611
642
|
db.blockTask(id, blocker, 'human');
|
|
612
|
-
console.log(`Blocked ${id} by ${blocker}
|
|
643
|
+
console.log(renderSuccess(`Blocked ${id} by ${blocker}`, format, { id, blockerId: blocker }));
|
|
613
644
|
db.close();
|
|
614
645
|
process.exit(0);
|
|
615
646
|
}
|
|
616
647
|
catch (error) {
|
|
617
|
-
console.error(
|
|
648
|
+
console.error(renderError(error, format));
|
|
618
649
|
process.exit(1);
|
|
619
650
|
}
|
|
620
651
|
});
|
|
@@ -623,17 +654,19 @@ program
|
|
|
623
654
|
.command('unblock')
|
|
624
655
|
.description('Unblock a task')
|
|
625
656
|
.argument('<id>', 'Task ID or prefix')
|
|
626
|
-
.
|
|
657
|
+
.option('--json', 'Output as JSON')
|
|
658
|
+
.action((idOrPrefix, options) => {
|
|
659
|
+
const format = options.json ? 'json' : 'text';
|
|
627
660
|
try {
|
|
628
661
|
const db = getDb();
|
|
629
662
|
const id = resolveId(db, idOrPrefix);
|
|
630
663
|
db.unblockTask(id, 'human');
|
|
631
|
-
console.log(`Unblocked: ${id}
|
|
664
|
+
console.log(renderSuccess(`Unblocked: ${id}`, format, { id }));
|
|
632
665
|
db.close();
|
|
633
666
|
process.exit(0);
|
|
634
667
|
}
|
|
635
668
|
catch (error) {
|
|
636
|
-
console.error(
|
|
669
|
+
console.error(renderError(error, format));
|
|
637
670
|
process.exit(1);
|
|
638
671
|
}
|
|
639
672
|
});
|
|
@@ -645,26 +678,22 @@ program
|
|
|
645
678
|
.option('-l, --level <level>', 'Autonomy level', 'collab')
|
|
646
679
|
.option('-u, --urgency <urgency>', 'Urgency', 'whenever')
|
|
647
680
|
.option('-p, --project <project>', 'Project tag (+ prefix optional)')
|
|
681
|
+
.option('--json', 'Output as JSON')
|
|
648
682
|
.action((text, options) => {
|
|
683
|
+
const format = options.json ? 'json' : 'text';
|
|
649
684
|
try {
|
|
650
685
|
const db = getDb();
|
|
651
|
-
// Check proposal limits
|
|
652
|
-
const proposedCount = db.countProposed();
|
|
653
|
-
if (proposedCount >= 5) {
|
|
654
|
-
console.error('Error: Too many proposed tasks (max 5 active)');
|
|
655
|
-
process.exit(1);
|
|
656
|
-
}
|
|
657
686
|
const id = db.createTask(text, 'agent', {
|
|
658
687
|
autonomy: options.level,
|
|
659
688
|
urgency: options.urgency,
|
|
660
689
|
project: normalizeProject(options.project),
|
|
661
690
|
});
|
|
662
|
-
console.log(`Proposed: ${id} (awaiting confirmation)
|
|
691
|
+
console.log(renderSuccess(`Proposed: ${id} (awaiting confirmation)`, format, { id, status: 'proposed' }));
|
|
663
692
|
db.close();
|
|
664
693
|
process.exit(0);
|
|
665
694
|
}
|
|
666
695
|
catch (error) {
|
|
667
|
-
console.error(
|
|
696
|
+
console.error(renderError(error, format));
|
|
668
697
|
process.exit(1);
|
|
669
698
|
}
|
|
670
699
|
});
|
|
@@ -674,17 +703,19 @@ program
|
|
|
674
703
|
.description('Add a note to a task')
|
|
675
704
|
.argument('<id>', 'Task ID or prefix')
|
|
676
705
|
.argument('<text>', 'Note text')
|
|
677
|
-
.
|
|
706
|
+
.option('--json', 'Output as JSON')
|
|
707
|
+
.action((idOrPrefix, text, options) => {
|
|
708
|
+
const format = options.json ? 'json' : 'text';
|
|
678
709
|
try {
|
|
679
710
|
const db = getDb();
|
|
680
711
|
const id = resolveId(db, idOrPrefix);
|
|
681
712
|
db.addNote(id, text, 'human');
|
|
682
|
-
console.log(`Note added to ${id}
|
|
713
|
+
console.log(renderSuccess(`Note added to ${id}`, format, { id }));
|
|
683
714
|
db.close();
|
|
684
715
|
process.exit(0);
|
|
685
716
|
}
|
|
686
717
|
catch (error) {
|
|
687
|
-
console.error(
|
|
718
|
+
console.error(renderError(error, format));
|
|
688
719
|
process.exit(1);
|
|
689
720
|
}
|
|
690
721
|
});
|
package/dist/sanitize.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
* Defense-in-depth: structural tags + pattern stripping + size limits.
|
|
4
4
|
* Adapted from molt/src/sanitize.ts
|
|
5
5
|
*/
|
|
6
|
-
import {
|
|
6
|
+
import { randomInt } from 'crypto';
|
|
7
7
|
// Strip patterns that look like prompt injection attempts
|
|
8
8
|
// Focus on critical patterns only (role hijacking, instruction override, code execution, credential exfil, tool invocations)
|
|
9
9
|
const INJECTION_PATTERNS = [
|
|
@@ -128,13 +128,13 @@ export function validateTaskId(id) {
|
|
|
128
128
|
return false;
|
|
129
129
|
return /^[a-z0-9]{8}$/.test(id);
|
|
130
130
|
}
|
|
131
|
-
// Generate random task ID using crypto.
|
|
131
|
+
// Generate random task ID using crypto.randomInt for uniform distribution.
|
|
132
|
+
// randomInt uses rejection sampling internally — no modulo bias.
|
|
132
133
|
export function generateTaskId() {
|
|
133
134
|
const chars = 'abcdefghijklmnopqrstuvwxyz0123456789';
|
|
134
|
-
const bytes = randomBytes(LIMITS.taskId);
|
|
135
135
|
let id = '';
|
|
136
136
|
for (let i = 0; i < LIMITS.taskId; i++) {
|
|
137
|
-
id += chars[
|
|
137
|
+
id += chars[randomInt(chars.length)];
|
|
138
138
|
}
|
|
139
139
|
return id;
|
|
140
140
|
}
|