sfora-cli 0.6.0 → 0.8.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 +38 -10
- package/dist/SforaFs.d.ts +3 -0
- package/dist/SforaFs.js +231 -7
- package/dist/api-client.d.ts +70 -0
- package/dist/api-client.js +76 -3
- package/dist/cli.js +451 -27
- package/dist/config.d.ts +18 -0
- package/dist/config.js +97 -5
- package/dist/config.test.d.ts +1 -0
- package/dist/config.test.js +94 -0
- package/dist/format/cardMarkdown.d.ts +36 -0
- package/dist/format/cardMarkdown.js +83 -0
- package/dist/format/index.d.ts +13 -0
- package/dist/format/index.js +13 -0
- package/dist/format/markdown/dates.d.ts +3 -0
- package/dist/format/markdown/dates.js +26 -0
- package/dist/format/markdown/document.d.ts +7 -0
- package/dist/format/markdown/document.js +45 -0
- package/dist/format/markdown/index.d.ts +5 -0
- package/dist/format/markdown/index.js +9 -0
- package/dist/format/markdown/mentions.d.ts +8 -0
- package/dist/format/markdown/mentions.js +36 -0
- package/dist/format/markdown/slug.d.ts +2 -0
- package/dist/format/markdown/slug.js +19 -0
- package/dist/format/markdown/yaml.d.ts +4 -0
- package/dist/format/markdown/yaml.js +72 -0
- package/dist/format/noteMarkdown.d.ts +28 -0
- package/dist/format/noteMarkdown.js +66 -0
- package/dist/format/postMarkdown.d.ts +32 -0
- package/dist/format/postMarkdown.js +53 -0
- package/dist/index.d.ts +16 -1
- package/dist/index.js +15 -1
- package/dist/local/workspace.d.ts +68 -0
- package/dist/local/workspace.js +250 -0
- package/dist/mcp-server.d.ts +2 -0
- package/dist/mcp-server.js +19 -8
- package/package.json +2 -2
package/dist/api-client.js
CHANGED
|
@@ -26,14 +26,18 @@ function routeBase(kind) {
|
|
|
26
26
|
export class SforaApiClient {
|
|
27
27
|
#baseUrl;
|
|
28
28
|
#apiKey;
|
|
29
|
+
#actAs;
|
|
29
30
|
constructor(config) {
|
|
30
31
|
this.#baseUrl = config.baseUrl.replace(/\/+$/, "");
|
|
31
32
|
this.#apiKey = config.apiKey;
|
|
33
|
+
this.#actAs = config.actAs;
|
|
32
34
|
}
|
|
33
35
|
// ─── HTTP plumbing ──────────────────────────────────────────────
|
|
34
36
|
async #request(method, path, body) {
|
|
35
37
|
const headers = Object.create(null);
|
|
36
38
|
headers.Authorization = `Bearer ${this.#apiKey}`;
|
|
39
|
+
if (this.#actAs)
|
|
40
|
+
headers["X-Sfora-Act-As"] = this.#actAs;
|
|
37
41
|
if (body !== undefined)
|
|
38
42
|
headers["Content-Type"] = "text/markdown";
|
|
39
43
|
let res;
|
|
@@ -135,12 +139,47 @@ export class SforaApiClient {
|
|
|
135
139
|
const file = encodeURIComponent(filename);
|
|
136
140
|
await this.#request("DELETE", `/v1/fs/projects/${slug}/${base}/${file}`);
|
|
137
141
|
}
|
|
142
|
+
// ─── Notes (docs) ────────────────────────────────────────────────
|
|
143
|
+
/** `GET …/docs` — the project's notes, most-recently-edited first. */
|
|
144
|
+
async listNotes(projectSlug) {
|
|
145
|
+
const slug = encodeURIComponent(projectSlug);
|
|
146
|
+
const data = await this.#json(`/v1/fs/projects/${slug}/docs`);
|
|
147
|
+
return data.docs ?? [];
|
|
148
|
+
}
|
|
149
|
+
/** `GET …/docs/:filename.md`. Returns the raw markdown body. */
|
|
150
|
+
async readNote(projectSlug, filename) {
|
|
151
|
+
const slug = encodeURIComponent(projectSlug);
|
|
152
|
+
const file = encodeURIComponent(filename);
|
|
153
|
+
return this.#text(`/v1/fs/projects/${slug}/docs/${file}`);
|
|
154
|
+
}
|
|
155
|
+
// ─── Pull requests (read-only) ───────────────────────────────────
|
|
156
|
+
/** `GET …/pulls` — the project's synced pull requests, open first. */
|
|
157
|
+
async listPulls(projectSlug) {
|
|
158
|
+
const slug = encodeURIComponent(projectSlug);
|
|
159
|
+
const data = await this.#json(`/v1/fs/projects/${slug}/pulls`);
|
|
160
|
+
return data.pulls ?? [];
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* `GET …/pulls/:number` — one PR as markdown: metadata, the cards it resolves,
|
|
164
|
+
* and the unified diff. Returns the raw markdown body.
|
|
165
|
+
*/
|
|
166
|
+
async readPull(projectSlug, number) {
|
|
167
|
+
const slug = encodeURIComponent(projectSlug);
|
|
168
|
+
return this.#text(`/v1/fs/projects/${slug}/pulls/${number}`);
|
|
169
|
+
}
|
|
138
170
|
// ─── Kanban board ────────────────────────────────────────────────
|
|
139
|
-
/**
|
|
140
|
-
|
|
171
|
+
/**
|
|
172
|
+
* `GET …/board` — board sharing state + columns (auto-creates the board on
|
|
173
|
+
* first call). `publicSlug` is null unless the board is shared publicly.
|
|
174
|
+
*/
|
|
175
|
+
async getBoardMeta(projectSlug) {
|
|
141
176
|
const slug = encodeURIComponent(projectSlug);
|
|
142
177
|
const data = await this.#json(`/v1/fs/projects/${slug}/board`);
|
|
143
|
-
return data.columns;
|
|
178
|
+
return { publicSlug: data.publicSlug ?? null, columns: data.columns };
|
|
179
|
+
}
|
|
180
|
+
/** `GET …/board` — list the project's columns (auto-creates the board on first call). */
|
|
181
|
+
async listColumns(projectSlug) {
|
|
182
|
+
return (await this.getBoardMeta(projectSlug)).columns;
|
|
144
183
|
}
|
|
145
184
|
/** `GET …/board/:column` — list cards in a column. `column` is the `NN-slug` dirname. */
|
|
146
185
|
async listCards(projectSlug, columnDir) {
|
|
@@ -239,6 +278,40 @@ export class SforaApiClient {
|
|
|
239
278
|
throw await this.#toError(res);
|
|
240
279
|
return (await res.json());
|
|
241
280
|
}
|
|
281
|
+
// ─── Post actions (comment / react) ─────────────────────────────
|
|
282
|
+
/** `POST /v1/posts/:postId/comments` — add a comment to a post. */
|
|
283
|
+
async createComment(postId, body) {
|
|
284
|
+
const res = await fetch(`${this.#baseUrl}/v1/posts/${encodeURIComponent(postId)}/comments`, {
|
|
285
|
+
method: "POST",
|
|
286
|
+
headers: {
|
|
287
|
+
Authorization: `Bearer ${this.#apiKey}`,
|
|
288
|
+
"Content-Type": "application/json",
|
|
289
|
+
},
|
|
290
|
+
body: JSON.stringify({ body }),
|
|
291
|
+
});
|
|
292
|
+
if (!res.ok)
|
|
293
|
+
throw await this.#toError(res);
|
|
294
|
+
const data = (await res.json());
|
|
295
|
+
return { id: data.comment?._id ?? "" };
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* `POST /v1/posts/:postId/reactions` — toggle an emoji reaction on a post.
|
|
299
|
+
* Returns whether the reaction is now on (`reacted: true`) or off.
|
|
300
|
+
*/
|
|
301
|
+
async reactToPost(postId, emoji) {
|
|
302
|
+
const res = await fetch(`${this.#baseUrl}/v1/posts/${encodeURIComponent(postId)}/reactions`, {
|
|
303
|
+
method: "POST",
|
|
304
|
+
headers: {
|
|
305
|
+
Authorization: `Bearer ${this.#apiKey}`,
|
|
306
|
+
"Content-Type": "application/json",
|
|
307
|
+
},
|
|
308
|
+
body: JSON.stringify({ emoji }),
|
|
309
|
+
});
|
|
310
|
+
if (!res.ok)
|
|
311
|
+
throw await this.#toError(res);
|
|
312
|
+
const data = (await res.json());
|
|
313
|
+
return data.reaction ?? { content: emoji, reacted: true };
|
|
314
|
+
}
|
|
242
315
|
/** `GET /v1/fs/inbox/mentions.md` — markdown summary of unread mentions. */
|
|
243
316
|
async readInbox() {
|
|
244
317
|
return this.#text("/v1/fs/inbox/mentions.md");
|