sfora-cli 0.16.0 → 0.17.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 +18 -0
- package/dist/agent-webhook.d.ts +20 -0
- package/dist/agent-webhook.js +42 -0
- package/dist/api-client.d.ts +97 -0
- package/dist/api-client.js +68 -0
- package/dist/ask.d.ts +51 -0
- package/dist/ask.js +70 -0
- package/dist/attachments-node.d.ts +7 -0
- package/dist/attachments-node.js +15 -0
- package/dist/attachments.d.ts +112 -0
- package/dist/attachments.js +254 -0
- package/dist/block-commands.d.ts +10 -0
- package/dist/block-commands.js +28 -0
- package/dist/chat.d.ts +15 -0
- package/dist/chat.js +7 -0
- package/dist/cli-args.d.ts +7 -0
- package/dist/cli-args.js +28 -1
- package/dist/cli.d.ts +12 -1
- package/dist/cli.js +186 -19
- package/dist/format/linkUrls.d.ts +2 -0
- package/dist/format/linkUrls.js +48 -0
- package/dist/format/postMarkdown.d.ts +12 -1
- package/dist/format/postMarkdown.js +9 -2
- package/dist/index.d.ts +23 -1
- package/dist/index.js +17 -1
- package/dist/local-core/skills.d.ts +25 -0
- package/dist/local-core/skills.js +93 -0
- package/dist/mcp-description.d.ts +11 -0
- package/dist/mcp-description.js +29 -0
- package/dist/mcp-server.d.ts +5 -1
- package/dist/mcp-server.js +28 -18
- package/dist/shell-commands.d.ts +7 -1
- package/dist/shell-commands.js +49 -3
- package/dist/skills-command.d.ts +1 -1
- package/dist/skills-command.js +20 -1
- package/dist/skills-packet/sfora-asks/SKILL.md +49 -0
- package/dist/skills-packet/sfora-asks/references/asks.md +41 -0
- package/dist/skills-packet/sfora-board/SKILL.md +53 -0
- package/dist/skills-packet/sfora-board/references/board.md +60 -0
- package/dist/skills-packet/sfora-board/references/plan.md +25 -0
- package/dist/skills-packet/sfora-chat/SKILL.md +55 -0
- package/dist/skills-packet/sfora-chat/references/rooms.md +45 -0
- package/dist/skills-packet/sfora-chat/references/waiting.md +37 -0
- package/dist/skills-packet/sfora-live-edit/SKILL.md +62 -0
- package/dist/skills-packet/sfora-live-edit/references/collisions.md +54 -0
- package/dist/skills-packet/sfora-live-edit/references/http.md +63 -0
- package/dist/skills-packet/sfora-live-edit/references/live-editing.md +49 -0
- package/dist/skills-packet/sfora-setup/SKILL.md +37 -0
- package/dist/skills-packet/sfora-setup/references/sign-in.md +43 -0
- package/dist/skills-packet/sfora-skills/SKILL.md +54 -0
- package/dist/skills-packet/sfora-skills/references/skills.md +78 -0
- package/dist/skills-packet/sfora-troubleshoot/SKILL.md +39 -0
- package/dist/skills-packet/sfora-troubleshoot/references/sharp-edges.md +80 -0
- package/dist/skills-packet/sfora-write/SKILL.md +53 -0
- package/dist/skills-packet/sfora-write/references/attachments.md +15 -0
- package/dist/skills-packet/sfora-write/references/blocks.md +36 -0
- package/dist/skills-packet/sfora-write/references/posts-and-docs.md +56 -0
- package/dist/skills-packet.d.ts +63 -0
- package/dist/skills-packet.js +166 -0
- package/dist/typing.d.ts +23 -0
- package/dist/typing.js +62 -0
- package/dist/version.d.ts +1 -1
- package/dist/version.js +1 -1
- package/dist/watch.d.ts +78 -1
- package/dist/watch.js +109 -0
- package/package.json +3 -3
package/dist/watch.js
CHANGED
|
@@ -111,3 +111,112 @@ export function parseWatchTarget(target) {
|
|
|
111
111
|
}
|
|
112
112
|
return { kind: "path", path: trimmed.startsWith("/") ? trimmed : `/${trimmed}` };
|
|
113
113
|
}
|
|
114
|
+
/**
|
|
115
|
+
* My own row in one document, out of a `GET /v1/presence?member=self` answer:
|
|
116
|
+
* `view.member` is me, and each document lists who is in it with the block
|
|
117
|
+
* they hold. Matched on the document id, or on its path (the fs has two
|
|
118
|
+
* spellings of a document path, and the server answers with one).
|
|
119
|
+
*/
|
|
120
|
+
export function myBlockIn(view, doc) {
|
|
121
|
+
const me = view.member?.memberId;
|
|
122
|
+
if (!me)
|
|
123
|
+
return undefined;
|
|
124
|
+
const found = view.documents.find((d) => (doc.docId !== undefined && d.docId === doc.docId) || d.path === doc.path);
|
|
125
|
+
const row = found?.here.find((h) => h.memberId === me);
|
|
126
|
+
return row ? { block: row.block } : undefined;
|
|
127
|
+
}
|
|
128
|
+
/** A roster as one comparable string: who, how, and on which block. */
|
|
129
|
+
export function rosterKey(here) {
|
|
130
|
+
return here
|
|
131
|
+
.map((m) => `${m.memberId}:${m.kind}:${m.blockId ?? ""}`)
|
|
132
|
+
.sort()
|
|
133
|
+
.join("|");
|
|
134
|
+
}
|
|
135
|
+
/** `here: Ada (editing block k1a2), sfora-bot [agent] (viewing)`. */
|
|
136
|
+
export function renderRoster(presence) {
|
|
137
|
+
if (!presence.here.length)
|
|
138
|
+
return `here: nobody — ${presence.document}`;
|
|
139
|
+
const who = presence.here.map((m) => {
|
|
140
|
+
const what = m.kind === "editing" && m.blockId ? `editing block ${m.blockId}` : m.kind;
|
|
141
|
+
return `${m.name}${m.type === "agent" ? " [agent]" : ""} (${what})`;
|
|
142
|
+
});
|
|
143
|
+
return `here: ${who.join(", ")}`;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Presence as the editor of one block: `kind=editing&block=<id>` on every
|
|
147
|
+
* beat, the roster printed on joining and again whenever it changes, and
|
|
148
|
+
* `?leave` on the way out (the watch loop's `finally` sends it).
|
|
149
|
+
*
|
|
150
|
+
* `start()` is the first declaration, made before the loop so a refusal is a
|
|
151
|
+
* clear error rather than a watch that quietly never shows up:
|
|
152
|
+
* - no roster (`null`, the server's 422): only documents have one;
|
|
153
|
+
* - a block id that resolves to nothing: the server keeps the row at
|
|
154
|
+
* document level and says `blockResolved: false`
|
|
155
|
+
* (convex/lib/docPresence.ts claimableBlockId), so this retracts it and
|
|
156
|
+
* names the fix.
|
|
157
|
+
*
|
|
158
|
+
* FOLLOWING THE BLOCK. Block ids are fingerprints of the text, so an edit to
|
|
159
|
+
* the claimed block gives it a new id. The server moves claims along with it:
|
|
160
|
+
* the writer's own `?block=` write rebinds the writer's row to the new id
|
|
161
|
+
* (convex/httpHelpers.ts:3116-3132, the `reboundOwn` touch after a doc PUT), and
|
|
162
|
+
* everybody else's claims ride the same ledger (carryDocPresenceBlocks,
|
|
163
|
+
* convex/lib/docPresence.ts:417-442) — but only on fs writes: its one caller
|
|
164
|
+
* is httpHelpers.ts:3125. An edit saved from the app carries no claims, so a
|
|
165
|
+
* person editing the claimed block in the app reads here as "gone". But a declare names its block, and a declare
|
|
166
|
+
* of the OLD id resolves to nothing (claimableBlockId) and touchDocPresence
|
|
167
|
+
* overwrites the rebound row with a
|
|
168
|
+
* block-less one, so the answer to that declare can no longer say what the
|
|
169
|
+
* new id was. So each beat first READS my row (`whereAmI`, a GET that joins
|
|
170
|
+
* nothing) and, when the server holds a different block for me, adopts it
|
|
171
|
+
* and declares that. Only a block whose old id no longer resolves and that
|
|
172
|
+
* the server did not move my claim to (removed, or edited in the app) is a
|
|
173
|
+
* warning, once,
|
|
174
|
+
* after which the watch stays in the document without a block.
|
|
175
|
+
*/
|
|
176
|
+
export function editingPresence(deps, opts) {
|
|
177
|
+
let lastKey;
|
|
178
|
+
/** The block being declared; `undefined` once it is gone (document level). */
|
|
179
|
+
let current = opts.block;
|
|
180
|
+
const show = (presence) => {
|
|
181
|
+
const key = rosterKey(presence.here);
|
|
182
|
+
if (key === lastKey)
|
|
183
|
+
return;
|
|
184
|
+
lastKey = key;
|
|
185
|
+
deps.write(opts.json
|
|
186
|
+
? `${ndjson({ type: "presence", document: presence.document, block: presence.block, blockResolved: presence.blockResolved, here: presence.here })}\n`
|
|
187
|
+
: `${renderRoster(presence)}\n`);
|
|
188
|
+
};
|
|
189
|
+
return {
|
|
190
|
+
async start() {
|
|
191
|
+
const presence = await deps.declare({ kind: "editing", block: opts.block });
|
|
192
|
+
if (presence === null) {
|
|
193
|
+
throw new Error(`${opts.path} has no roster to edit in: --block works on documents ` +
|
|
194
|
+
"(/projects/<slug>/docs/<file>.md), not on posts or board cards");
|
|
195
|
+
}
|
|
196
|
+
if (presence.blockResolved === false) {
|
|
197
|
+
await deps.declare({ kind: "editing", block: opts.block, leave: true }).catch(() => null);
|
|
198
|
+
throw new Error(`no block ${opts.block} in ${opts.path} — ids follow the content, so it may have ` +
|
|
199
|
+
`changed; run \`sfora blocks ${opts.path}\` for the current ids`);
|
|
200
|
+
}
|
|
201
|
+
show(presence);
|
|
202
|
+
},
|
|
203
|
+
async beat({ leave }) {
|
|
204
|
+
if (!leave && current !== undefined && deps.whereAmI) {
|
|
205
|
+
const mine = await deps.whereAmI().catch(() => undefined);
|
|
206
|
+
if (mine?.block && mine.block !== current) {
|
|
207
|
+
deps.warn(`following block ${current} → ${mine.block} (it was edited; the server moved your claim)`);
|
|
208
|
+
current = mine.block;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const presence = await deps.declare({ kind: "editing", ...(current !== undefined ? { block: current } : {}), leave });
|
|
212
|
+
if (!presence || leave)
|
|
213
|
+
return;
|
|
214
|
+
if (presence.blockResolved === false && current !== undefined) {
|
|
215
|
+
deps.warn(`block ${current} is gone (it was edited or removed) — you show as in the document, ` +
|
|
216
|
+
`not on a block; run \`sfora blocks ${opts.path}\` and watch a current id`);
|
|
217
|
+
current = undefined;
|
|
218
|
+
}
|
|
219
|
+
show(presence);
|
|
220
|
+
},
|
|
221
|
+
};
|
|
222
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sfora-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.17.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Your sfora workspace as a markdown filesystem \u2014 a CLI + MCP server. Post/task/doc, ls/cat/grep, and a shell so agents operate sfora natively.",
|
|
6
6
|
"keywords": [
|
|
@@ -36,13 +36,13 @@
|
|
|
36
36
|
"README.md"
|
|
37
37
|
],
|
|
38
38
|
"scripts": {
|
|
39
|
-
"build": "node scripts/sync-format.mjs && tsc -p .",
|
|
39
|
+
"build": "node scripts/sync-format.mjs && tsc -p . && node scripts/bundle-skills-packet.mjs",
|
|
40
40
|
"dev": "node scripts/sync-format.mjs && tsx src/cli.ts",
|
|
41
41
|
"typecheck": "node scripts/sync-format.mjs && tsc --noEmit"
|
|
42
42
|
},
|
|
43
43
|
"dependencies": {
|
|
44
44
|
"@modelcontextprotocol/sdk": "^1.0.0",
|
|
45
|
-
"just-bash": "
|
|
45
|
+
"just-bash": "3.0.1"
|
|
46
46
|
},
|
|
47
47
|
"devDependencies": {
|
|
48
48
|
"@types/mdast": "^4.0.4",
|