cairn-work 1.0.1 → 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/README.md +44 -0
- package/bin/cairn.js +11 -0
- package/bin/postinstall.js +7 -1
- package/lib/commands/comment.js +186 -0
- package/package.json +2 -2
- package/templates/workspace/TOOLS.md.template +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Cairn
|
|
2
2
|
|
|
3
|
+
[](https://www.npmjs.com/package/cairn-work)
|
|
4
|
+
[](https://github.com/letcairnwork/cairn-cli/discussions)
|
|
5
|
+
[](https://x.com/letcairnwork)
|
|
6
|
+
|
|
3
7
|
Project management for AI agents. Markdown files are the source of truth.
|
|
4
8
|
|
|
5
9
|
## Setup
|
|
@@ -310,6 +314,28 @@ cairn log implement-auth "Implemented OAuth2 flow with GitHub provider"
|
|
|
310
314
|
cairn log implement-auth "Fixed edge case in token refresh" --title "Bug Fix"
|
|
311
315
|
```
|
|
312
316
|
|
|
317
|
+
### `cairn comment`
|
|
318
|
+
|
|
319
|
+
Add a comment that syncs to the Cairn app (visible in the web UI).
|
|
320
|
+
|
|
321
|
+
```bash
|
|
322
|
+
# Status update
|
|
323
|
+
cairn comment implement-auth "Started OAuth implementation"
|
|
324
|
+
|
|
325
|
+
# Ask a question
|
|
326
|
+
cairn comment implement-auth "Should we support Google SSO?" --type question
|
|
327
|
+
|
|
328
|
+
# Worker handoff
|
|
329
|
+
cairn comment implement-auth "Ready for code review" --type handoff --author "Engineer"
|
|
330
|
+
```
|
|
331
|
+
|
|
332
|
+
**Options:**
|
|
333
|
+
- `--type <type>`: Comment type (`progress`, `question`, `handoff`, `comment`)
|
|
334
|
+
- `--author <name>`: Author name (defaults to `$USER`)
|
|
335
|
+
- `--author-type <type>`: `worker` or `human` (defaults to `worker`)
|
|
336
|
+
|
|
337
|
+
Unlike `cairn note` (which writes to the task file), `cairn comment` writes to Convex and appears in the Cairn web app. Requires cairnsync authentication.
|
|
338
|
+
|
|
313
339
|
### `cairn update`
|
|
314
340
|
|
|
315
341
|
Update task properties programmatically.
|
|
@@ -628,6 +654,24 @@ These files are auto-generated during `cairn onboard` and updated with `cairn up
|
|
|
628
654
|
7. Task moves to `review` (if `autonomy: draft`) or `completed` (if `autonomy: execute`)
|
|
629
655
|
8. Human: Reviews artifacts and approves work
|
|
630
656
|
|
|
657
|
+
## Community & Support
|
|
658
|
+
|
|
659
|
+
- **[GitHub Discussions](https://github.com/letcairnwork/cairn-cli/discussions)** — Ask questions, share ideas, show what you're building, and give feedback. This is the best place to connect.
|
|
660
|
+
- **[GitHub Issues](https://github.com/letcairnwork/cairn-cli/issues)** — Bug reports only. For feature requests and questions, use Discussions.
|
|
661
|
+
- **[Twitter/X @letcairnwork](https://x.com/letcairnwork)** — Follow for updates, or drop a quick question.
|
|
662
|
+
|
|
663
|
+
New here? [Introduce yourself in Discussions](https://github.com/letcairnwork/cairn-cli/discussions/categories/introductions) — we'd love to hear what you're working on.
|
|
664
|
+
|
|
665
|
+
## Contributing
|
|
666
|
+
|
|
667
|
+
Contributions are welcome! If you have an idea for a feature or improvement, [start a discussion](https://github.com/letcairnwork/cairn-cli/discussions) first so we can talk through the approach before you invest time in a PR.
|
|
668
|
+
|
|
669
|
+
Found a bug? [Open an issue](https://github.com/letcairnwork/cairn-cli/issues).
|
|
670
|
+
|
|
671
|
+
## What's Next
|
|
672
|
+
|
|
673
|
+
The roadmap is shaped by community feedback. If there's something you'd like to see, [request it in Discussions](https://github.com/letcairnwork/cairn-cli/discussions/categories/ideas).
|
|
674
|
+
|
|
631
675
|
## License
|
|
632
676
|
|
|
633
677
|
MIT
|
package/bin/cairn.js
CHANGED
|
@@ -42,6 +42,7 @@ import search from '../lib/commands/search.js';
|
|
|
42
42
|
import triage from '../lib/commands/triage.js';
|
|
43
43
|
import learn from '../lib/commands/learn.js';
|
|
44
44
|
import worker from '../lib/commands/worker.js';
|
|
45
|
+
import comment from '../lib/commands/comment.js';
|
|
45
46
|
|
|
46
47
|
// Onboard command - workspace setup with context files
|
|
47
48
|
program
|
|
@@ -162,6 +163,16 @@ program
|
|
|
162
163
|
.option('--project <slug>', 'Project to search for the task')
|
|
163
164
|
.action(note);
|
|
164
165
|
|
|
166
|
+
// Comment command - add comment to Convex (shows in app)
|
|
167
|
+
program
|
|
168
|
+
.command('comment <task-slug> <message>')
|
|
169
|
+
.description('Add a comment to task (syncs to Cairn app)')
|
|
170
|
+
.option('--project <slug>', 'Project to search for the task')
|
|
171
|
+
.option('--type <type>', 'Comment type: question, progress, handoff, or comment', 'progress')
|
|
172
|
+
.option('--author <name>', 'Author name (defaults to $USER)')
|
|
173
|
+
.option('--author-type <type>', 'Author type: human or worker', 'worker')
|
|
174
|
+
.action(comment);
|
|
175
|
+
|
|
165
176
|
// View command - show full task details
|
|
166
177
|
program
|
|
167
178
|
.command('view <task-slug>')
|
package/bin/postinstall.js
CHANGED
|
@@ -51,5 +51,11 @@ try {
|
|
|
51
51
|
log();
|
|
52
52
|
}
|
|
53
53
|
} catch {
|
|
54
|
-
|
|
54
|
+
// fall through
|
|
55
55
|
}
|
|
56
|
+
|
|
57
|
+
log();
|
|
58
|
+
log(' \x1b[36mThanks for installing Cairn!\x1b[0m');
|
|
59
|
+
log(' Join the community: \x1b[4mhttps://github.com/letcairnwork/cairn-cli/discussions\x1b[0m');
|
|
60
|
+
log(' Follow for updates: \x1b[4mhttps://x.com/letcairnwork\x1b[0m');
|
|
61
|
+
log();
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { existsSync, readFileSync } from 'fs';
|
|
2
|
+
import { homedir } from 'os';
|
|
3
|
+
import { join } from 'path';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import { resolveWorkspace } from '../setup/workspace.js';
|
|
6
|
+
import { findTaskBySlug, getAgentName } from '../utils/task-helpers.js';
|
|
7
|
+
|
|
8
|
+
const CONVEX_URL = 'https://admired-wildebeest-654.convex.cloud';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Decode JWT payload without validation (for extracting userId)
|
|
12
|
+
*/
|
|
13
|
+
function decodeJwtPayload(token) {
|
|
14
|
+
try {
|
|
15
|
+
const parts = token.split('.');
|
|
16
|
+
if (parts.length !== 3) return null;
|
|
17
|
+
const payload = Buffer.from(parts[1], 'base64url').toString('utf-8');
|
|
18
|
+
return JSON.parse(payload);
|
|
19
|
+
} catch {
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Load auth token from workspace .cairn-token.json
|
|
26
|
+
*/
|
|
27
|
+
function loadAuthToken(workspacePath) {
|
|
28
|
+
const tokenPath = join(workspacePath, '.cairn-token.json');
|
|
29
|
+
|
|
30
|
+
if (!existsSync(tokenPath)) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
const raw = readFileSync(tokenPath, 'utf-8');
|
|
36
|
+
const data = JSON.parse(raw);
|
|
37
|
+
|
|
38
|
+
if (!data.accessToken) {
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Check if token is expired
|
|
43
|
+
if (data.expiresAt && Date.now() >= data.expiresAt) {
|
|
44
|
+
return null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return data.accessToken;
|
|
48
|
+
} catch {
|
|
49
|
+
return null;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Build task path from project and slug
|
|
55
|
+
*/
|
|
56
|
+
function buildTaskPath(project, taskSlug) {
|
|
57
|
+
return `projects/${project}/tasks/${taskSlug}.md`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Call Convex mutation via HTTP
|
|
62
|
+
*/
|
|
63
|
+
async function callConvexMutation(functionName, args, token) {
|
|
64
|
+
const url = `${CONVEX_URL}/api/mutation`;
|
|
65
|
+
|
|
66
|
+
const response = await fetch(url, {
|
|
67
|
+
method: 'POST',
|
|
68
|
+
headers: {
|
|
69
|
+
'Content-Type': 'application/json',
|
|
70
|
+
'Authorization': `Bearer ${token}`,
|
|
71
|
+
},
|
|
72
|
+
body: JSON.stringify({
|
|
73
|
+
path: functionName,
|
|
74
|
+
args,
|
|
75
|
+
format: 'json',
|
|
76
|
+
}),
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
if (!response.ok) {
|
|
80
|
+
const text = await response.text();
|
|
81
|
+
throw new Error(`Convex API error (${response.status}): ${text}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const result = await response.json();
|
|
85
|
+
|
|
86
|
+
if (result.status === 'error') {
|
|
87
|
+
throw new Error(result.errorMessage || 'Unknown Convex error');
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return result.value;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export default async function comment(taskSlug, message, options) {
|
|
94
|
+
// Try to resolve workspace, fallback to ~/pms for compatibility
|
|
95
|
+
let workspacePath = resolveWorkspace();
|
|
96
|
+
|
|
97
|
+
if (!workspacePath) {
|
|
98
|
+
// Fallback to ~/pms (common default)
|
|
99
|
+
const fallbackPath = join(homedir(), 'pms');
|
|
100
|
+
if (existsSync(join(fallbackPath, 'projects'))) {
|
|
101
|
+
workspacePath = fallbackPath;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
if (!workspacePath) {
|
|
106
|
+
console.error(chalk.red('Error:'), 'No workspace found. Run:', chalk.cyan('cairn onboard'));
|
|
107
|
+
process.exit(1);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (!taskSlug) {
|
|
111
|
+
console.error(chalk.red('Error:'), 'Missing task slug');
|
|
112
|
+
console.log(chalk.dim('Usage:'), chalk.cyan('cairn comment <task-slug> <message>'));
|
|
113
|
+
console.log(chalk.dim('Example:'), chalk.cyan('cairn comment implement-auth "Found a bug in OAuth flow"'));
|
|
114
|
+
process.exit(1);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (!message) {
|
|
118
|
+
console.error(chalk.red('Error:'), 'Missing comment message');
|
|
119
|
+
console.log(chalk.dim('Usage:'), chalk.cyan('cairn comment <task-slug> <message>'));
|
|
120
|
+
console.log(chalk.dim('Example:'), chalk.cyan('cairn comment implement-auth "Found a bug in OAuth flow"'));
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Find the task to get the correct project
|
|
125
|
+
const task = findTaskBySlug(workspacePath, taskSlug, options.project);
|
|
126
|
+
|
|
127
|
+
if (!task) {
|
|
128
|
+
if (options.project) {
|
|
129
|
+
console.error(chalk.red('Error:'), `Task "${taskSlug}" not found in project "${options.project}"`);
|
|
130
|
+
} else {
|
|
131
|
+
console.error(chalk.red('Error:'), `Task "${taskSlug}" not found in any project`);
|
|
132
|
+
console.log(chalk.dim('Tip:'), 'Use', chalk.cyan('--project <slug>'), 'to search within a specific project');
|
|
133
|
+
}
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Load auth token
|
|
138
|
+
const token = loadAuthToken(workspacePath);
|
|
139
|
+
|
|
140
|
+
if (!token) {
|
|
141
|
+
console.error(chalk.red('Error:'), 'Not authenticated. Cairn sync must be running and logged in.');
|
|
142
|
+
console.log(chalk.dim('Tip:'), 'Run', chalk.cyan('cairnsync auth login'), 'to authenticate');
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// Decode token to get userId
|
|
147
|
+
const payload = decodeJwtPayload(token);
|
|
148
|
+
|
|
149
|
+
if (!payload || !payload.sub) {
|
|
150
|
+
console.error(chalk.red('Error:'), 'Invalid auth token. Please re-authenticate.');
|
|
151
|
+
console.log(chalk.dim('Tip:'), 'Run', chalk.cyan('cairnsync auth login'), 'to re-authenticate');
|
|
152
|
+
process.exit(1);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const userId = payload.sub;
|
|
156
|
+
|
|
157
|
+
// Determine author info
|
|
158
|
+
const authorName = options.author || getAgentName();
|
|
159
|
+
const authorType = options.authorType || 'worker';
|
|
160
|
+
const authorId = authorType === 'human' ? userId : authorName.toLowerCase();
|
|
161
|
+
const commentType = options.type || 'progress';
|
|
162
|
+
|
|
163
|
+
// Build task path as Convex expects it
|
|
164
|
+
const taskPath = buildTaskPath(task.project, taskSlug);
|
|
165
|
+
|
|
166
|
+
try {
|
|
167
|
+
const result = await callConvexMutation('taskComments:add', {
|
|
168
|
+
taskPath,
|
|
169
|
+
userId,
|
|
170
|
+
authorId,
|
|
171
|
+
authorType,
|
|
172
|
+
authorName,
|
|
173
|
+
content: message,
|
|
174
|
+
commentType,
|
|
175
|
+
}, token);
|
|
176
|
+
|
|
177
|
+
console.log(chalk.green('✓'), `Comment added to task: ${chalk.cyan(taskSlug)}`);
|
|
178
|
+
console.log(chalk.dim(` Project: ${task.project}`));
|
|
179
|
+
console.log(chalk.dim(` Author: ${authorName} (${authorType})`));
|
|
180
|
+
console.log(chalk.dim(` Type: ${commentType}`));
|
|
181
|
+
console.log(chalk.dim(` Message: ${message.length > 60 ? message.substring(0, 60) + '...' : message}`));
|
|
182
|
+
} catch (error) {
|
|
183
|
+
console.error(chalk.red('Error:'), `Failed to add comment: ${error.message}`);
|
|
184
|
+
process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "cairn-work",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "AI-native project management - optimized CLI for AI agents and humans working together",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -37,7 +37,7 @@
|
|
|
37
37
|
"type": "git",
|
|
38
38
|
"url": "https://github.com/letcairnwork/cairn-cli.git"
|
|
39
39
|
},
|
|
40
|
-
"homepage": "https://
|
|
40
|
+
"homepage": "https://cairn.quest",
|
|
41
41
|
"bugs": {
|
|
42
42
|
"url": "https://github.com/letcairnwork/cairn-cli/issues"
|
|
43
43
|
},
|