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.
Files changed (66) hide show
  1. package/README.md +18 -0
  2. package/dist/agent-webhook.d.ts +20 -0
  3. package/dist/agent-webhook.js +42 -0
  4. package/dist/api-client.d.ts +97 -0
  5. package/dist/api-client.js +68 -0
  6. package/dist/ask.d.ts +51 -0
  7. package/dist/ask.js +70 -0
  8. package/dist/attachments-node.d.ts +7 -0
  9. package/dist/attachments-node.js +15 -0
  10. package/dist/attachments.d.ts +112 -0
  11. package/dist/attachments.js +254 -0
  12. package/dist/block-commands.d.ts +10 -0
  13. package/dist/block-commands.js +28 -0
  14. package/dist/chat.d.ts +15 -0
  15. package/dist/chat.js +7 -0
  16. package/dist/cli-args.d.ts +7 -0
  17. package/dist/cli-args.js +28 -1
  18. package/dist/cli.d.ts +12 -1
  19. package/dist/cli.js +186 -19
  20. package/dist/format/linkUrls.d.ts +2 -0
  21. package/dist/format/linkUrls.js +48 -0
  22. package/dist/format/postMarkdown.d.ts +12 -1
  23. package/dist/format/postMarkdown.js +9 -2
  24. package/dist/index.d.ts +23 -1
  25. package/dist/index.js +17 -1
  26. package/dist/local-core/skills.d.ts +25 -0
  27. package/dist/local-core/skills.js +93 -0
  28. package/dist/mcp-description.d.ts +11 -0
  29. package/dist/mcp-description.js +29 -0
  30. package/dist/mcp-server.d.ts +5 -1
  31. package/dist/mcp-server.js +28 -18
  32. package/dist/shell-commands.d.ts +7 -1
  33. package/dist/shell-commands.js +49 -3
  34. package/dist/skills-command.d.ts +1 -1
  35. package/dist/skills-command.js +20 -1
  36. package/dist/skills-packet/sfora-asks/SKILL.md +49 -0
  37. package/dist/skills-packet/sfora-asks/references/asks.md +41 -0
  38. package/dist/skills-packet/sfora-board/SKILL.md +53 -0
  39. package/dist/skills-packet/sfora-board/references/board.md +60 -0
  40. package/dist/skills-packet/sfora-board/references/plan.md +25 -0
  41. package/dist/skills-packet/sfora-chat/SKILL.md +55 -0
  42. package/dist/skills-packet/sfora-chat/references/rooms.md +45 -0
  43. package/dist/skills-packet/sfora-chat/references/waiting.md +37 -0
  44. package/dist/skills-packet/sfora-live-edit/SKILL.md +62 -0
  45. package/dist/skills-packet/sfora-live-edit/references/collisions.md +54 -0
  46. package/dist/skills-packet/sfora-live-edit/references/http.md +63 -0
  47. package/dist/skills-packet/sfora-live-edit/references/live-editing.md +49 -0
  48. package/dist/skills-packet/sfora-setup/SKILL.md +37 -0
  49. package/dist/skills-packet/sfora-setup/references/sign-in.md +43 -0
  50. package/dist/skills-packet/sfora-skills/SKILL.md +54 -0
  51. package/dist/skills-packet/sfora-skills/references/skills.md +78 -0
  52. package/dist/skills-packet/sfora-troubleshoot/SKILL.md +39 -0
  53. package/dist/skills-packet/sfora-troubleshoot/references/sharp-edges.md +80 -0
  54. package/dist/skills-packet/sfora-write/SKILL.md +53 -0
  55. package/dist/skills-packet/sfora-write/references/attachments.md +15 -0
  56. package/dist/skills-packet/sfora-write/references/blocks.md +36 -0
  57. package/dist/skills-packet/sfora-write/references/posts-and-docs.md +56 -0
  58. package/dist/skills-packet.d.ts +63 -0
  59. package/dist/skills-packet.js +166 -0
  60. package/dist/typing.d.ts +23 -0
  61. package/dist/typing.js +62 -0
  62. package/dist/version.d.ts +1 -1
  63. package/dist/version.js +1 -1
  64. package/dist/watch.d.ts +78 -1
  65. package/dist/watch.js +109 -0
  66. 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.16.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": "^3.0.1"
45
+ "just-bash": "3.0.1"
46
46
  },
47
47
  "devDependencies": {
48
48
  "@types/mdast": "^4.0.4",