easymd-cli 0.1.2 → 0.1.4
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/package.json +1 -1
- package/src/cli/mcp.mjs +28 -0
- package/src/cli/sync.js +21 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "easymd-cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.4",
|
|
4
4
|
"description": "Google Docs for markdown — collaborate on the actual .md file in your repo, live with humans and AI agents. CLI: login, auto-sync, and open .md files for real-time editing.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/cli/mcp.mjs
CHANGED
|
@@ -120,6 +120,34 @@ server.registerTool(
|
|
|
120
120
|
},
|
|
121
121
|
);
|
|
122
122
|
|
|
123
|
+
server.registerTool(
|
|
124
|
+
'update_task_state',
|
|
125
|
+
{
|
|
126
|
+
title: 'Update task state',
|
|
127
|
+
description:
|
|
128
|
+
'Update the document\'s "Task State" block — the shared execution-handoff surface between humans and agents. Only the fields you pass change; the rest are preserved. Use this to keep the doc reflecting the current goal, decisions, open questions, failed approaches, the last command you validated (with its result), and the next concrete action. Updates appear live in the open editor.',
|
|
129
|
+
inputSchema: {
|
|
130
|
+
name: z.string().describe('Document name'),
|
|
131
|
+
goal: z.string().optional().describe('Current goal / what we are trying to achieve'),
|
|
132
|
+
decisions: z.string().optional().describe('Decisions made so far'),
|
|
133
|
+
openQuestions: z.string().optional().describe('Unresolved questions'),
|
|
134
|
+
failedApproaches: z.string().optional().describe('Approaches tried that did not work'),
|
|
135
|
+
lastValidated: z.string().optional().describe('Last command you ran to validate, and its result (e.g. "`npm test` → 18 passing, 2 failing")'),
|
|
136
|
+
nextAction: z.string().optional().describe('The next concrete action'),
|
|
137
|
+
},
|
|
138
|
+
},
|
|
139
|
+
async ({ name, ...fields }) => {
|
|
140
|
+
try {
|
|
141
|
+
const state = Object.fromEntries(Object.entries(fields).filter(([, v]) => v !== undefined && v !== ''));
|
|
142
|
+
if (!Object.keys(state).length) return fail('Provide at least one field to update.');
|
|
143
|
+
await api('/api/cli/documents', { method: 'POST', body: JSON.stringify({ name, op: 'task', state }) });
|
|
144
|
+
return ok(`Updated Task State for "${name}" (${Object.keys(state).join(', ')}).`);
|
|
145
|
+
} catch (e) {
|
|
146
|
+
return fail(e.message);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
);
|
|
150
|
+
|
|
123
151
|
const transport = new StdioServerTransport();
|
|
124
152
|
await server.connect(transport);
|
|
125
153
|
console.error(`easymd MCP ready → ${BASE} ${TOKEN ? '(authenticated)' : '(NOT logged in — run `easymd login`)'}`);
|
package/src/cli/sync.js
CHANGED
|
@@ -5,6 +5,22 @@ import { requireCredentials } from './config.js';
|
|
|
5
5
|
|
|
6
6
|
const IGNORE_DIRS = new Set(['node_modules', '.git', 'dist', '.next', '.turbo', 'build', 'coverage']);
|
|
7
7
|
|
|
8
|
+
// "Load-bearing" agent files excluded from auto-sync by default. The account's
|
|
9
|
+
// `sync_special` setting (toggled in the dashboard) opts back in.
|
|
10
|
+
const SPECIAL_MD = new Set(['claude.md', 'readme.md', 'agents.md']);
|
|
11
|
+
const isSpecial = (filePath) => SPECIAL_MD.has(basename(filePath).toLowerCase());
|
|
12
|
+
|
|
13
|
+
// Ask the server whether this account wants the special files included.
|
|
14
|
+
async function syncSpecialEnabled(creds) {
|
|
15
|
+
try {
|
|
16
|
+
const res = await fetch(`${creds.url}/api/cli/settings`, { headers: { Authorization: `Bearer ${creds.token}` } });
|
|
17
|
+
if (res.ok) return Boolean((await res.json()).syncSpecial);
|
|
18
|
+
} catch {
|
|
19
|
+
/* default to excluding on error */
|
|
20
|
+
}
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
|
|
8
24
|
// Document name sent to the server: repo-relative path without the .md extension.
|
|
9
25
|
// The server slugs it (e.g. "docs/spec" → "docs-spec") and namespaces it to the account.
|
|
10
26
|
function docNameFor(filePath, root) {
|
|
@@ -50,7 +66,9 @@ async function collectMarkdown(dir, root, out = []) {
|
|
|
50
66
|
// One-shot: push every .md under `root` to the account.
|
|
51
67
|
export async function syncDir(root, { quiet = false } = {}) {
|
|
52
68
|
const creds = await requireCredentials();
|
|
53
|
-
const
|
|
69
|
+
const includeSpecial = await syncSpecialEnabled(creds);
|
|
70
|
+
let files = await collectMarkdown(root, root);
|
|
71
|
+
if (!includeSpecial) files = files.filter((f) => !isSpecial(f));
|
|
54
72
|
if (!files.length) {
|
|
55
73
|
if (!quiet) console.log('No .md files found.');
|
|
56
74
|
return { ok: 0, fail: 0 };
|
|
@@ -74,10 +92,12 @@ export async function syncDir(root, { quiet = false } = {}) {
|
|
|
74
92
|
// Long-running: watch `root` and push .md files as they're added/changed.
|
|
75
93
|
export async function watchDir(root, { quiet = false } = {}) {
|
|
76
94
|
const creds = await requireCredentials();
|
|
95
|
+
const includeSpecial = await syncSpecialEnabled(creds);
|
|
77
96
|
const log = (...a) => !quiet && console.log(...a);
|
|
78
97
|
|
|
79
98
|
const pending = new Map(); // path -> timer (debounce)
|
|
80
99
|
const push = (filePath) => {
|
|
100
|
+
if (!includeSpecial && isSpecial(filePath)) return; // skip CLAUDE/README/AGENTS by default
|
|
81
101
|
clearTimeout(pending.get(filePath));
|
|
82
102
|
pending.set(
|
|
83
103
|
filePath,
|