sfora-cli 0.15.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 +80 -1
- 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/files.d.ts +1 -1
- package/dist/local-core/files.js +2 -2
- package/dist/local-core/index.d.ts +12 -0
- package/dist/local-core/index.js +11 -0
- package/dist/local-core/skill-adapters.d.ts +21 -0
- package/dist/local-core/skill-adapters.js +19 -0
- package/dist/local-core/skill-discovery.d.ts +22 -0
- package/dist/local-core/skill-discovery.js +79 -0
- package/dist/local-core/skill-domain.d.ts +74 -0
- package/dist/local-core/skill-domain.js +1 -0
- package/dist/local-core/skill-executor.d.ts +23 -0
- package/dist/local-core/skill-executor.js +51 -0
- package/dist/local-core/skill-index.d.ts +54 -0
- package/dist/local-core/skill-index.js +115 -0
- package/dist/local-core/skill-local-executor.d.ts +18 -0
- package/dist/local-core/skill-local-executor.js +249 -0
- package/dist/local-core/skill-operations.d.ts +61 -0
- package/dist/local-core/skill-operations.js +268 -0
- package/dist/local-core/skill-review.d.ts +46 -0
- package/dist/local-core/skill-review.js +132 -0
- package/dist/local-core/skill-service.d.ts +96 -0
- package/dist/local-core/skill-service.js +157 -0
- package/dist/local-core/skill-store.d.ts +34 -0
- package/dist/local-core/skill-store.js +187 -0
- package/dist/local-core/skill-sync.d.ts +132 -0
- package/dist/local-core/skill-sync.js +111 -0
- package/dist/local-core/skills.d.ts +35 -0
- package/dist/local-core/skills.js +142 -37
- 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-client.d.ts +13 -2
- package/dist/skills-client.js +57 -5
- package/dist/skills-command.d.ts +1 -1
- package/dist/skills-command.js +171 -4
- 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 +4 -4
package/dist/cli.js
CHANGED
|
@@ -8,22 +8,28 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { CLI_VERSION } from "./version.js";
|
|
10
10
|
import { runSkillsCommand, SKILLS_HELP } from "./skills-command.js";
|
|
11
|
+
import { agentWebhookCommand } from "./agent-webhook.js";
|
|
11
12
|
import * as readline from "node:readline";
|
|
12
13
|
import { spawn } from "node:child_process";
|
|
13
14
|
import { realpath, readFile as readLocalFile } from "node:fs/promises";
|
|
15
|
+
import { realpathSync } from "node:fs";
|
|
16
|
+
import { fileURLToPath } from "node:url";
|
|
14
17
|
import { basename } from "node:path";
|
|
15
18
|
import { taskUploadFilename } from "./format/taskUploadFilename.js";
|
|
16
19
|
import { createSforaShell, createLocalShell, SforaApiError, } from "./index.js";
|
|
17
|
-
import { askWaitLoop, reshapeCandidatesError, validateAskOptions, } from "./ask.js";
|
|
20
|
+
import { askActionCommand, askWaitLoop, parseAskAction, reshapeCandidatesError, validateAskOptions, } from "./ask.js";
|
|
18
21
|
import { openerCommand } from "./opener.js";
|
|
19
|
-
import { blocksCommand, presenceNotice, putCommand, } from "./block-commands.js";
|
|
22
|
+
import { blocksCommand, backlinksCommand, presenceNotice, putCommand, } from "./block-commands.js";
|
|
20
23
|
import { colors, ndjson, presenceRecords, renderPresence, renderWriteEffect, urlLine, PRESENCE_NOTE, } from "./render.js";
|
|
21
|
-
import { parseWatchTarget, watchLoop } from "./watch.js";
|
|
24
|
+
import { editingPresence, myBlockIn, parseWatchTarget, watchLoop } from "./watch.js";
|
|
22
25
|
import { LocalWorkspace, initWorkspace, findWorkspace, migrateWorkspaceStages, } from "./local/workspace.js";
|
|
23
26
|
import { runMcpServer } from "./mcp-server.js";
|
|
24
27
|
import { readConfig, updateConfig, resolveSettings, upsertProfile, effectiveProfiles, profileKey, DEFAULT_URL, } from "./config.js";
|
|
25
28
|
import { parseArgs } from "./cli-args.js";
|
|
26
|
-
import { awaitReplyLoop, capitalizeName, chatMessageJson, chatTailLoop, detectClient, renderChatMessage, renderRoomList, resolveRoomRef, roomSlug, } from "./chat.js";
|
|
29
|
+
import { awaitReplyLoop, capitalizeName, chatMessageJson, chatMode, chatTailLoop, detectClient, renderChatMessage, renderRoomList, resolveRoomRef, roomSlug, } from "./chat.js";
|
|
30
|
+
import { typingCommand } from "./typing.js";
|
|
31
|
+
import { attachmentsCommand } from "./attachments.js";
|
|
32
|
+
import { nodeAttachmentIo } from "./attachments-node.js";
|
|
27
33
|
const HELP = `sfora — the CLI for your sfora workspace
|
|
28
34
|
|
|
29
35
|
Get started (no account needed):
|
|
@@ -57,6 +63,9 @@ Browse & read:
|
|
|
57
63
|
sfora me Show who you're signed in as
|
|
58
64
|
sfora ls [path] List a path (default /projects)
|
|
59
65
|
sfora cat <path> Print a file's markdown
|
|
66
|
+
sfora attachments <post> List a post's attachments (screenshots, files)
|
|
67
|
+
sfora attachments <post> --out <dir>
|
|
68
|
+
Download them into <dir>; prints each path
|
|
60
69
|
sfora url <path> Print the web URL for a path
|
|
61
70
|
sfora open <path> Open that URL in your browser
|
|
62
71
|
sfora desktop <file.md> Open local Markdown in Sfora for macOS
|
|
@@ -69,7 +78,9 @@ Chat:
|
|
|
69
78
|
sfora rooms List rooms (● joined · ○ open to join)
|
|
70
79
|
sfora join <room> Join an open room
|
|
71
80
|
sfora chat <room> [-n <count>] Read the room, then type to talk —
|
|
72
|
-
new messages stream in live
|
|
81
|
+
new messages stream in live. Without
|
|
82
|
+
a terminal (agents, pipes) it prints
|
|
83
|
+
the history and exits (--json: NDJSON)
|
|
73
84
|
sfora chat <room> -m "text" Send one message and exit (for scripts
|
|
74
85
|
and agents)
|
|
75
86
|
sfora chat <room> --follow Tail the room without a prompt — for
|
|
@@ -78,6 +89,10 @@ Chat:
|
|
|
78
89
|
Send, then wait for the next message
|
|
79
90
|
back and print it — --timeout <secs>
|
|
80
91
|
stops waiting (exit code 2)
|
|
92
|
+
sfora typing <room> [--for <secs>] Agents: show the room you're working on
|
|
93
|
+
a reply (30s by default, 5–120; run
|
|
94
|
+
again to extend). It ends when you send
|
|
95
|
+
there, or with --stop
|
|
81
96
|
|
|
82
97
|
Chat shows others what's on the line: the CLI reports itself in presence,
|
|
83
98
|
and a coding agent is named automatically (Claude Code, Codex, Cursor and
|
|
@@ -97,13 +112,23 @@ Ask a human:
|
|
|
97
112
|
the choice (give secs to stop waiting)
|
|
98
113
|
Add --project <slug> or --room <room> to say where it belongs; --json for
|
|
99
114
|
scripts (--wait prints a second JSON line when the answer lands).
|
|
115
|
+
sfora ask claim <ask-id> Take an open ask before working on it —
|
|
116
|
+
fails if someone else holds it
|
|
117
|
+
sfora ask resolve <ask-id> [-m "<resolution>"]
|
|
118
|
+
Report the ask done (the claimant, or
|
|
119
|
+
an admin). Ids: sfora cat
|
|
120
|
+
/projects/<slug>/asks.md
|
|
100
121
|
|
|
101
122
|
Write, and watch others write:
|
|
102
123
|
sfora blocks <path> List a document's addressable blocks
|
|
103
124
|
sfora put <path> <file.md> Write a file (add --block <id> for one block)
|
|
104
125
|
sfora put <path> --block <id> - …or pipe the block's markdown on stdin
|
|
105
126
|
sfora watch <path-or-project> Stream write pings (add --json for NDJSON)
|
|
127
|
+
sfora watch <doc> --block <id> …and show as editing that block: prints
|
|
128
|
+
who is here, again when that changes,
|
|
129
|
+
and leaves on exit (ids: sfora blocks)
|
|
106
130
|
sfora where [name] Who's in which document right now
|
|
131
|
+
sfora backlinks <path> What links to a doc, post or task
|
|
107
132
|
|
|
108
133
|
Every write prints what it did — "changed" (with how many block ids
|
|
109
134
|
survived) or "no change". Watching and writing make you visible in the
|
|
@@ -111,6 +136,13 @@ Write, and watch others write:
|
|
|
111
136
|
asking never puts you in a document.
|
|
112
137
|
|
|
113
138
|
Agents & config:
|
|
139
|
+
sfora agent webhook <name> --url <https://…> --events <a,b>
|
|
140
|
+
Set an agent's webhook with your key (its
|
|
141
|
+
owner, or a workspace owner/admin). Events:
|
|
142
|
+
message.created, mention, post.created,
|
|
143
|
+
post.commented. --clear removes it. Here
|
|
144
|
+
--url is the webhook's address; set the
|
|
145
|
+
API's with SFORA_URL
|
|
114
146
|
sfora --mcp [--org <slug>] Run as an MCP server (for agents)
|
|
115
147
|
sfora mcp-config Print an MCP config snippet to paste
|
|
116
148
|
sfora contexts List saved deployment+org keys (● = active)
|
|
@@ -129,10 +161,12 @@ const SHELL_HELP = `${colors.bold}sfora shell${colors.reset} ${colors.dim}— yo
|
|
|
129
161
|
Standard tools work against your workspace:
|
|
130
162
|
ls cat grep find head tail wc sed awk echo cd pwd
|
|
131
163
|
|
|
132
|
-
${colors.dim}And
|
|
164
|
+
${colors.dim}And five of sfora's own${colors.reset}
|
|
133
165
|
blocks <path> the blocks a --block write can aim at
|
|
134
166
|
put <path> [file|-] write a file (--block <id> writes one block)
|
|
135
167
|
url <path> where it lives on the web
|
|
168
|
+
backlinks <path> what links to it, with the token to paste
|
|
169
|
+
attachments <post> the files attached to a post
|
|
136
170
|
|
|
137
171
|
${colors.dim}Where things live${colors.reset}
|
|
138
172
|
/projects your projects
|
|
@@ -366,6 +400,8 @@ const VERBS = new Set([
|
|
|
366
400
|
"open",
|
|
367
401
|
"blocks",
|
|
368
402
|
"put",
|
|
403
|
+
"backlinks",
|
|
404
|
+
"attachments",
|
|
369
405
|
"watch",
|
|
370
406
|
"where",
|
|
371
407
|
"post",
|
|
@@ -382,7 +418,9 @@ const VERBS = new Set([
|
|
|
382
418
|
"rooms",
|
|
383
419
|
"join",
|
|
384
420
|
"chat",
|
|
421
|
+
"typing",
|
|
385
422
|
"ask",
|
|
423
|
+
"agent",
|
|
386
424
|
]);
|
|
387
425
|
async function runVerb(args, fs, client) {
|
|
388
426
|
const ok = (msg) => console.log(`${colors.green}✓${colors.reset} ${msg}`);
|
|
@@ -471,6 +509,21 @@ async function runVerb(args, fs, client) {
|
|
|
471
509
|
throw new Error("usage: sfora blocks <path> [--json]");
|
|
472
510
|
return writeCommandOutput(await blocksCommand(client, args.rest[0], { json: args.json }));
|
|
473
511
|
}
|
|
512
|
+
if (args.command === "backlinks") {
|
|
513
|
+
if (!args.rest[0])
|
|
514
|
+
throw new Error("usage: sfora backlinks <path> [--json]");
|
|
515
|
+
return writeCommandOutput(await backlinksCommand(client, args.rest[0], { json: args.json }));
|
|
516
|
+
}
|
|
517
|
+
if (args.command === "attachments") {
|
|
518
|
+
if (!args.rest[0]) {
|
|
519
|
+
throw new Error("usage: sfora attachments <post> [--out <dir>] [--json]");
|
|
520
|
+
}
|
|
521
|
+
return writeCommandOutput(await attachmentsCommand(client, args.rest[0], {
|
|
522
|
+
out: args.out,
|
|
523
|
+
json: args.json,
|
|
524
|
+
io: nodeAttachmentIo,
|
|
525
|
+
}));
|
|
526
|
+
}
|
|
474
527
|
if (args.command === "put") {
|
|
475
528
|
const path = args.rest[0];
|
|
476
529
|
if (!path) {
|
|
@@ -489,7 +542,7 @@ async function runVerb(args, fs, client) {
|
|
|
489
542
|
}
|
|
490
543
|
if (args.command === "watch") {
|
|
491
544
|
if (!args.rest[0]) {
|
|
492
|
-
throw new Error("usage: sfora watch <path-or-project> [--json] [--self] [--wait <secs>]");
|
|
545
|
+
throw new Error("usage: sfora watch <path-or-project> [--block <id>] [--json] [--self] [--wait <secs>]");
|
|
493
546
|
}
|
|
494
547
|
return runWatch(args, client, args.rest[0]);
|
|
495
548
|
}
|
|
@@ -546,9 +599,55 @@ async function runVerb(args, fs, client) {
|
|
|
546
599
|
}
|
|
547
600
|
return runChat(args, client);
|
|
548
601
|
}
|
|
602
|
+
if (args.command === "typing") {
|
|
603
|
+
const out = await typingCommand(client, args.rest[0], {
|
|
604
|
+
// `--for` is parsed once for `ask --for <member>`; here it is seconds.
|
|
605
|
+
seconds: args.target,
|
|
606
|
+
stop: args.stop,
|
|
607
|
+
json: args.json,
|
|
608
|
+
});
|
|
609
|
+
if (out.stdout)
|
|
610
|
+
process.stdout.write(out.stdout);
|
|
611
|
+
if (out.stderr)
|
|
612
|
+
process.stderr.write(out.stderr);
|
|
613
|
+
if (out.exitCode !== 0)
|
|
614
|
+
process.exitCode = out.exitCode;
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
549
617
|
if (args.command === "ask") {
|
|
618
|
+
// `ask claim <id>` / `ask resolve <id>` only when it cannot be a question
|
|
619
|
+
// (see parseAskAction); everything else creates an ask, as it always has.
|
|
620
|
+
const action = parseAskAction(args.rest, args.option);
|
|
621
|
+
if (action) {
|
|
622
|
+
const out = await askActionCommand(client, action.action, action.askId, {
|
|
623
|
+
resolution: args.message,
|
|
624
|
+
json: args.json,
|
|
625
|
+
});
|
|
626
|
+
if (out.stdout)
|
|
627
|
+
process.stdout.write(out.stdout);
|
|
628
|
+
if (out.stderr)
|
|
629
|
+
process.stderr.write(out.stderr);
|
|
630
|
+
if (out.exitCode !== 0)
|
|
631
|
+
process.exitCode = out.exitCode;
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
550
634
|
return runAsk(args, client);
|
|
551
635
|
}
|
|
636
|
+
if (args.command === "agent") {
|
|
637
|
+
const out = await agentWebhookCommand(client, args.rest, {
|
|
638
|
+
webhookUrl: args.webhookUrl,
|
|
639
|
+
events: args.events,
|
|
640
|
+
clear: args.clear,
|
|
641
|
+
json: args.json,
|
|
642
|
+
});
|
|
643
|
+
if (out.stdout)
|
|
644
|
+
process.stdout.write(out.stdout);
|
|
645
|
+
if (out.stderr)
|
|
646
|
+
process.stderr.write(out.stderr);
|
|
647
|
+
if (out.exitCode !== 0)
|
|
648
|
+
process.exitCode = out.exitCode;
|
|
649
|
+
return;
|
|
650
|
+
}
|
|
552
651
|
if (args.command === "me" || args.command === "whoami") {
|
|
553
652
|
const text = (await client.readMe()).trim();
|
|
554
653
|
if (args.json) {
|
|
@@ -712,7 +811,7 @@ async function runWatch(args, client, target) {
|
|
|
712
811
|
// Presence, if this path has a roster. Asked rather than inferred: a `422`
|
|
713
812
|
// is the server saying "no roster here", and posts and cards get one.
|
|
714
813
|
let canBeat = parsed.kind === "path";
|
|
715
|
-
|
|
814
|
+
let beat = async ({ leave }) => {
|
|
716
815
|
if (!canBeat || parsed.kind !== "path")
|
|
717
816
|
return;
|
|
718
817
|
const roster = await client.declarePresence(parsed.path, {
|
|
@@ -722,6 +821,25 @@ async function runWatch(args, client, target) {
|
|
|
722
821
|
if (roster === null)
|
|
723
822
|
canBeat = false;
|
|
724
823
|
};
|
|
824
|
+
// `--block <id>`: the editor of one block, not a viewer of the document.
|
|
825
|
+
// Declared once up front so a path with no roster, or an id that names no
|
|
826
|
+
// block, is a clear refusal instead of a watch that never shows up.
|
|
827
|
+
if (args.block !== undefined) {
|
|
828
|
+
if (parsed.kind !== "path") {
|
|
829
|
+
throw new Error(`--block needs a document path, not a project — sfora watch /projects/${parsed.slug}/docs/<file>.md --block <id>`);
|
|
830
|
+
}
|
|
831
|
+
const path = parsed.path;
|
|
832
|
+
const editing = editingPresence({
|
|
833
|
+
declare: (o) => client.declarePresence(path, o),
|
|
834
|
+
// A read, not a declare: where the server holds MY claim now, so the
|
|
835
|
+
// watch follows a block whose id an edit changed.
|
|
836
|
+
whereAmI: async () => myBlockIn(await client.listPresence("self"), { docId, path }),
|
|
837
|
+
write: (text) => process.stdout.write(text),
|
|
838
|
+
warn: (text) => process.stderr.write(`${colors.dim}${text}${colors.reset}\n`),
|
|
839
|
+
}, { path, block: args.block, json: args.json });
|
|
840
|
+
await editing.start();
|
|
841
|
+
beat = editing.beat;
|
|
842
|
+
}
|
|
725
843
|
// Start at NOW, not at the retained backlog: `watch` is a live channel, and
|
|
726
844
|
// opening one should not replay a day of history nobody was waiting for.
|
|
727
845
|
const since = Date.now();
|
|
@@ -754,7 +872,7 @@ async function runWatch(args, client, target) {
|
|
|
754
872
|
process.on("SIGINT", onSignal);
|
|
755
873
|
process.on("SIGTERM", onSignal);
|
|
756
874
|
if (!args.json) {
|
|
757
|
-
process.stderr.write(`${colors.dim}watching ${parsed.kind === "project" ? `project ${parsed.slug}` : parsed.path}${args.self ? "" : " (not your own writes)"} — ^C to stop${colors.reset}\n`);
|
|
875
|
+
process.stderr.write(`${colors.dim}watching ${parsed.kind === "project" ? `project ${parsed.slug}` : parsed.path}${args.block !== undefined ? `, editing block ${args.block}` : ""}${args.self ? "" : " (not your own writes)"} — ^C to stop${colors.reset}\n`);
|
|
758
876
|
}
|
|
759
877
|
try {
|
|
760
878
|
await watchLoop({
|
|
@@ -868,6 +986,20 @@ async function runChat(args, client) {
|
|
|
868
986
|
const seen = new Set(seeded.map((m) => m._id));
|
|
869
987
|
const pendingSendBodies = new Set();
|
|
870
988
|
const messages = seeded.slice(-limit);
|
|
989
|
+
// No terminal on stdin (an agent, a pipe): read the room and exit. Opening
|
|
990
|
+
// the prompt there would wait on stdin for EOF and send what it read.
|
|
991
|
+
if (chatMode({ message: args.message, follow: args.follow, stdinIsTty: isTty }) === "read") {
|
|
992
|
+
if (args.json) {
|
|
993
|
+
for (const msg of messages)
|
|
994
|
+
console.log(chatMessageJson(msg));
|
|
995
|
+
return;
|
|
996
|
+
}
|
|
997
|
+
if (messages.length === 0)
|
|
998
|
+
console.log(`${colors.dim}(no messages yet)${colors.reset}`);
|
|
999
|
+
for (const msg of messages)
|
|
1000
|
+
console.log(renderChatMessage(msg, Date.now()));
|
|
1001
|
+
return;
|
|
1002
|
+
}
|
|
871
1003
|
if (messages.length === 0) {
|
|
872
1004
|
console.log(`${colors.dim}(no messages yet)${colors.reset}`);
|
|
873
1005
|
}
|
|
@@ -1476,12 +1608,17 @@ ${colors.dim}Try${colors.reset}
|
|
|
1476
1608
|
Everything is git-versioned with your repo. ${colors.dim}Connect a team later with${colors.reset} ${colors.cyan}sfora login${colors.reset}.
|
|
1477
1609
|
Type ${colors.cyan}exit${colors.reset} to quit.
|
|
1478
1610
|
`;
|
|
1479
|
-
|
|
1480
|
-
|
|
1611
|
+
/**
|
|
1612
|
+
* The whole CLI for one argv (what follows `sfora`). Exported so a harness can
|
|
1613
|
+
* drive the real command table in-process; the bin below calls it with
|
|
1614
|
+
* `process.argv.slice(2)`.
|
|
1615
|
+
*/
|
|
1616
|
+
export async function runCli(argv) {
|
|
1617
|
+
if (["--version", "-v"].includes(argv[0] ?? "")) {
|
|
1481
1618
|
console.log(`sfora-cli ${CLI_VERSION}`);
|
|
1482
1619
|
return;
|
|
1483
1620
|
}
|
|
1484
|
-
const args = parseArgs(
|
|
1621
|
+
const args = parseArgs(argv);
|
|
1485
1622
|
if (args.help) {
|
|
1486
1623
|
process.stdout.write(HELP + "\n" + SKILLS_HELP);
|
|
1487
1624
|
return;
|
|
@@ -1537,7 +1674,8 @@ async function main() {
|
|
|
1537
1674
|
}
|
|
1538
1675
|
// Local mode — a .sfora/ workspace in (an ancestor of) cwd wins, unless the
|
|
1539
1676
|
// user explicitly targets the cloud (--cloud / --url / --org / --key / --bot).
|
|
1540
|
-
|
|
1677
|
+
// `agent …` is cloud-only: agents and their webhooks live in a workspace.
|
|
1678
|
+
const cloudIntent = args.cloud || !!args.url || !!args.org || !!args.key || !!args.bot || args.command === "agent";
|
|
1541
1679
|
const localRoot = cloudIntent
|
|
1542
1680
|
? undefined
|
|
1543
1681
|
: await findWorkspace(process.cwd());
|
|
@@ -1674,7 +1812,7 @@ client) {
|
|
|
1674
1812
|
"sed", "awk", "sort", "uniq", "mkdir", "rm", "mv", "cp", "touch", "help",
|
|
1675
1813
|
"exit",
|
|
1676
1814
|
// sfora's own, registered on the cloud shell (see `sforaShellCommands`).
|
|
1677
|
-
"blocks", "put", "url",
|
|
1815
|
+
"blocks", "put", "url", "backlinks", "attachments",
|
|
1678
1816
|
];
|
|
1679
1817
|
async function completePath(token) {
|
|
1680
1818
|
const slash = token.lastIndexOf("/");
|
|
@@ -1754,8 +1892,37 @@ client) {
|
|
|
1754
1892
|
if (isTty)
|
|
1755
1893
|
console.log("");
|
|
1756
1894
|
}
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1895
|
+
/**
|
|
1896
|
+
* True when this module is the process entry point. Compared through realpath:
|
|
1897
|
+
* npm's bin shim and `npx` reach dist/cli.js through symlinks, and those must
|
|
1898
|
+
* still run; an `import` of this module (a test, a harness) must not.
|
|
1899
|
+
*/
|
|
1900
|
+
export function isEntryPoint(entry = process.argv[1], self = import.meta.url) {
|
|
1901
|
+
if (!entry)
|
|
1902
|
+
return false;
|
|
1903
|
+
let me;
|
|
1904
|
+
try {
|
|
1905
|
+
me = realpathSync(fileURLToPath(self));
|
|
1906
|
+
}
|
|
1907
|
+
catch {
|
|
1908
|
+
return false;
|
|
1909
|
+
}
|
|
1910
|
+
// `node dist/cli` (no extension) names the same file as `node dist/cli.js`.
|
|
1911
|
+
for (const candidate of [entry, `${entry}.js`]) {
|
|
1912
|
+
try {
|
|
1913
|
+
if (realpathSync(candidate) === me)
|
|
1914
|
+
return true;
|
|
1915
|
+
}
|
|
1916
|
+
catch {
|
|
1917
|
+
// not this spelling
|
|
1918
|
+
}
|
|
1919
|
+
}
|
|
1920
|
+
return false;
|
|
1921
|
+
}
|
|
1922
|
+
if (isEntryPoint()) {
|
|
1923
|
+
runCli(process.argv.slice(2)).catch((e) => {
|
|
1924
|
+
const message = e instanceof Error ? e.message : String(e);
|
|
1925
|
+
process.stderr.write(`${colors.red}fatal:${colors.reset} ${message}\n`);
|
|
1926
|
+
process.exit(1);
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// GENERATED by scripts/sync-format.mjs from packages/markdown/src — DO NOT EDIT.
|
|
2
|
+
// Edit packages/markdown/src and re-run the sync (any sfora-cli build does it).
|
|
3
|
+
// Markdown links that can name a workspace entity — card #682 (fault B6).
|
|
4
|
+
//
|
|
5
|
+
// ONE scanner for both sides: the graph (`convex/references.ts`, through the
|
|
6
|
+
// `convex/lib/linkUrls.ts` shim) indexes exactly the URLs the reader asks
|
|
7
|
+
// `references.resolveTokens` about, so a path link cannot be live in one and
|
|
8
|
+
// dead in the other.
|
|
9
|
+
import { codeSpanMask, fencedRegions, isEscaped, lineText, } from "./lint/lintSource.js";
|
|
10
|
+
// Markdown links (`[text](url)`, not images) whose URL could name a workspace
|
|
11
|
+
// entity: an fs path (`/projects/<slug>/docs/<file>.md`, `./other.md`) or an
|
|
12
|
+
// app URL (`/org/<slug>/notes/<id>`, …). Same code geometry as the token
|
|
13
|
+
// scanner, so a link quoted in code is prose, not a link. Card #682 (B6):
|
|
14
|
+
// these used to be dead hrefs and no edge.
|
|
15
|
+
const MD_LINK_RE = /(!?)\[(?:[^\]\\]|\\.)*\]\(\s*<?([^\s)>]+)>?(?:\s+"[^"]*")?\s*\)/g;
|
|
16
|
+
const ENTITY_URL_RE = /^(?:https?:\/\/[^/]+)?\/org\/[^/]+\/(?:notes|posts|projects\/[^/]+\/(?:board\?detail=card:|pulls\/))/;
|
|
17
|
+
export function looksLikeEntityUrl(url) {
|
|
18
|
+
const bare = url.split("#")[0];
|
|
19
|
+
if (ENTITY_URL_RE.test(bare))
|
|
20
|
+
return true;
|
|
21
|
+
if (/^[a-z][a-z0-9+.-]*:/i.test(bare))
|
|
22
|
+
return false; // http:, mailto:, …
|
|
23
|
+
return /\.md$/i.test(bare.split("?")[0]);
|
|
24
|
+
}
|
|
25
|
+
export function scanBodyLinkUrls(body) {
|
|
26
|
+
const lines = body.split("\n");
|
|
27
|
+
const fenced = new Set();
|
|
28
|
+
for (const region of fencedRegions(lines)) {
|
|
29
|
+
for (let i = region.open; i <= region.close; i++)
|
|
30
|
+
fenced.add(i);
|
|
31
|
+
}
|
|
32
|
+
const out = [];
|
|
33
|
+
for (let i = 0; i < lines.length; i++) {
|
|
34
|
+
if (fenced.has(i))
|
|
35
|
+
continue;
|
|
36
|
+
const line = lineText(lines, i);
|
|
37
|
+
const mask = codeSpanMask(line);
|
|
38
|
+
MD_LINK_RE.lastIndex = 0;
|
|
39
|
+
let m;
|
|
40
|
+
while ((m = MD_LINK_RE.exec(line)) !== null) {
|
|
41
|
+
if (m[1] === "!" || mask[m.index] || isEscaped(line, m.index))
|
|
42
|
+
continue;
|
|
43
|
+
if (looksLikeEntityUrl(m[2]))
|
|
44
|
+
out.push(m[2]);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
@@ -17,6 +17,17 @@ export interface MarkdownAuthor {
|
|
|
17
17
|
name: string;
|
|
18
18
|
type: "human" | "agent";
|
|
19
19
|
}
|
|
20
|
+
/**
|
|
21
|
+
* A file attached to the post (card #822). Listed in the frontmatter so an
|
|
22
|
+
* agent reading the post knows it is there; the bytes are fetched from
|
|
23
|
+
* `<post path>/attachments/<id>`, never inlined.
|
|
24
|
+
*/
|
|
25
|
+
export interface MarkdownAttachment {
|
|
26
|
+
id: string;
|
|
27
|
+
name: string;
|
|
28
|
+
type: string;
|
|
29
|
+
size: number;
|
|
30
|
+
}
|
|
20
31
|
export interface MarkdownProject {
|
|
21
32
|
_id: string;
|
|
22
33
|
name: string;
|
|
@@ -28,5 +39,5 @@ export interface ParsedMarkdownPost {
|
|
|
28
39
|
frontmatter: Record<string, string | string[]>;
|
|
29
40
|
}
|
|
30
41
|
export declare function mdFilename(post: MarkdownPostInput): string;
|
|
31
|
-
export declare function postToMarkdown(post: MarkdownPostInput, author: MarkdownAuthor | null, project: MarkdownProject | null, commentsCount: number): string;
|
|
42
|
+
export declare function postToMarkdown(post: MarkdownPostInput, author: MarkdownAuthor | null, project: MarkdownProject | null, commentsCount: number, attachments?: MarkdownAttachment[]): string;
|
|
32
43
|
export declare function parseMarkdownPost(md: string): ParsedMarkdownPost;
|
|
@@ -29,7 +29,7 @@ export function mdFilename(post) {
|
|
|
29
29
|
return `${datePart(post)}-${slugify(post.title)}.md`;
|
|
30
30
|
}
|
|
31
31
|
// ─── Serialize + parse ─────────────────────────────────────────────
|
|
32
|
-
export function postToMarkdown(post, author, project, commentsCount) {
|
|
32
|
+
export function postToMarkdown(post, author, project, commentsCount, attachments = []) {
|
|
33
33
|
const { text, names } = renderMentions(post.body);
|
|
34
34
|
const fm = serializeFrontmatter([
|
|
35
35
|
["id", post._id],
|
|
@@ -47,7 +47,14 @@ export function postToMarkdown(post, author, project, commentsCount) {
|
|
|
47
47
|
["comments", String(commentsCount)],
|
|
48
48
|
["mentions", names],
|
|
49
49
|
]);
|
|
50
|
-
|
|
50
|
+
// One line of JSON — valid YAML flow, and a JSON.parse away for an agent.
|
|
51
|
+
// The tiny frontmatter YAML holds only flat string arrays, so objects ride
|
|
52
|
+
// as JSON rather than stretching it. Omitted when there are none, so a post
|
|
53
|
+
// without files reads byte-for-byte as it always has.
|
|
54
|
+
const attachmentLine = attachments.length
|
|
55
|
+
? `\nattachments: ${JSON.stringify(attachments.map(({ id, name, type, size }) => ({ id, name, type, size })))}`
|
|
56
|
+
: "";
|
|
57
|
+
return buildDocument(fm + attachmentLine, post.title, text);
|
|
51
58
|
}
|
|
52
59
|
// Parse a markdown file back to post fields (shared document parser).
|
|
53
60
|
export function parseMarkdownPost(md) {
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* workspace. The CLI also exposes first-class verbs (post/task/doc) and an MCP
|
|
8
8
|
* server so agents operate sfora natively.
|
|
9
9
|
*/
|
|
10
|
-
import { Bash, ReadWriteFs } from "just-bash";
|
|
10
|
+
import { Bash, ReadWriteFs, type CommandName } from "just-bash";
|
|
11
11
|
import { SforaApiClient } from "./api-client.js";
|
|
12
12
|
import { SforaFs } from "./SforaFs.js";
|
|
13
13
|
import { type PresenceNotice } from "./block-commands.js";
|
|
@@ -31,13 +31,32 @@ export interface CreateSforaShellOptions {
|
|
|
31
31
|
* that made them — the CLI passes `detectClient(process.env, --client)`.
|
|
32
32
|
*/
|
|
33
33
|
clientLabel?: string;
|
|
34
|
+
/** "mcp" from an MCP server (card #815, D11): sent as `X-Sfora-Transport`. */
|
|
35
|
+
transport?: "mcp";
|
|
34
36
|
/**
|
|
35
37
|
* The run's "you are visible" latch. Pass one when something OUTSIDE this
|
|
36
38
|
* shell can print the note too — the CLI does, after any line that wrote —
|
|
37
39
|
* so the two share a single "once". Omitted, the shell owns its own.
|
|
38
40
|
*/
|
|
39
41
|
presence?: PresenceNotice;
|
|
42
|
+
/**
|
|
43
|
+
* Restrict the shell to these built-in commands. Omitted, every built-in is
|
|
44
|
+
* available — the CLI and local shells run on the user's own machine with
|
|
45
|
+
* the user's own key. The hosted /mcp route passes
|
|
46
|
+
* {@link hostedShellCommands} (card #628).
|
|
47
|
+
*/
|
|
48
|
+
commands?: CommandName[];
|
|
40
49
|
}
|
|
50
|
+
/**
|
|
51
|
+
* Built-ins the HOSTED shell does not offer (card #628). `yq` is the only path
|
|
52
|
+
* to the TOML parser just-bash 3.0.1 inlines (smol-toml <= 1.6.1,
|
|
53
|
+
* GHSA-7w5x-hrqm-74c2: a malformed document hangs the parser). The fixed
|
|
54
|
+
* just-bash (3.4.x) cannot be adopted yet: its redirections write every target
|
|
55
|
+
* empty first, which the fs API rejects, so the parser is removed instead.
|
|
56
|
+
*/
|
|
57
|
+
export declare const HOSTED_SHELL_EXCLUDED: readonly string[];
|
|
58
|
+
/** Every built-in this just-bash registers, minus {@link HOSTED_SHELL_EXCLUDED}. */
|
|
59
|
+
export declare function hostedShellCommands(): CommandName[];
|
|
41
60
|
export interface SforaShell {
|
|
42
61
|
bash: Bash;
|
|
43
62
|
fs: SforaFs;
|
|
@@ -66,6 +85,7 @@ export interface LocalShell {
|
|
|
66
85
|
*/
|
|
67
86
|
export declare function createLocalShell(root: string, cwd?: string): LocalShell;
|
|
68
87
|
export { SforaFs } from "./SforaFs.js";
|
|
88
|
+
export { cloudToolDescription } from "./mcp-description.js";
|
|
69
89
|
export { LocalWorkspace, initWorkspace, findWorkspace, WORKSPACE_DIR, type TaskEntry, type TaskWriteResult, } from "./local/workspace.js";
|
|
70
90
|
export * from "./format/index.js";
|
|
71
91
|
export { SforaApiClient, SforaApiError, blockConflictFrom, writeEffectFrom, type SforaApiConfig, type Project, type Entry, type PostKind, type WriteResult, type WriteOptions, type WriteEffect, type RebindCounts, type ResponseInfo, type BlockView, type BlocksView, type BlockConflict, type BlockSummary, type AgentEvent, type AgentEventsPage, type DocPresence, type DocPresenceMember, } from "./api-client.js";
|
|
@@ -73,6 +93,8 @@ export { WEB_URL_HEADER, fsRequestPath, webUrlFromResponse, } from "./web-url.js
|
|
|
73
93
|
export { openerCommand, type OpenerCommand } from "./opener.js";
|
|
74
94
|
export { blocksCommand, putCommand, urlCommand, resolveFsPath, presenceNotice, type CommandOutput, type PresenceNotice, } from "./block-commands.js";
|
|
75
95
|
export { sforaShellCommands, parseShellArgs } from "./shell-commands.js";
|
|
96
|
+
export { ATTACHMENT_MAX_BYTES, ATTACHMENT_TOOLS, attachmentToolResult, attachmentsCommand, isAttachmentTool, safeAttachmentName, type AttachmentIo, type McpContent, type McpToolResult, } from "./attachments.js";
|
|
97
|
+
export type { AttachmentRow } from "./api-client.js";
|
|
76
98
|
export { watchLoop, parseWatchTarget, MAX_BACKOFF_MS, type WatchDeps, type WatchOptions, type WatchTarget, } from "./watch.js";
|
|
77
99
|
export { renderPing, renderBlocks, renderBlockConflict, renderWriteEffect, ndjson, type DocPing, } from "./render.js";
|
|
78
100
|
export { KNOWN_CLIENTS, detectClient, sanitizeClientSlug, chatMessageJson, chatTailLoop, awaitReplyLoop, type ChatTailDeps, type ChatTailOptions, type AwaitReplyDeps, type AwaitReplyOptions, type AwaitReplyResult, } from "./chat.js";
|
package/dist/index.js
CHANGED
|
@@ -7,17 +7,30 @@
|
|
|
7
7
|
* workspace. The CLI also exposes first-class verbs (post/task/doc) and an MCP
|
|
8
8
|
* server so agents operate sfora natively.
|
|
9
9
|
*/
|
|
10
|
-
import { Bash, ReadWriteFs } from "just-bash";
|
|
10
|
+
import { Bash, ReadWriteFs, getCommandNames } from "just-bash";
|
|
11
11
|
import { SforaApiClient } from "./api-client.js";
|
|
12
12
|
import { SforaFs } from "./SforaFs.js";
|
|
13
13
|
import { sforaShellCommands } from "./shell-commands.js";
|
|
14
14
|
import { presenceNotice } from "./block-commands.js";
|
|
15
|
+
/**
|
|
16
|
+
* Built-ins the HOSTED shell does not offer (card #628). `yq` is the only path
|
|
17
|
+
* to the TOML parser just-bash 3.0.1 inlines (smol-toml <= 1.6.1,
|
|
18
|
+
* GHSA-7w5x-hrqm-74c2: a malformed document hangs the parser). The fixed
|
|
19
|
+
* just-bash (3.4.x) cannot be adopted yet: its redirections write every target
|
|
20
|
+
* empty first, which the fs API rejects, so the parser is removed instead.
|
|
21
|
+
*/
|
|
22
|
+
export const HOSTED_SHELL_EXCLUDED = ["yq"];
|
|
23
|
+
/** Every built-in this just-bash registers, minus {@link HOSTED_SHELL_EXCLUDED}. */
|
|
24
|
+
export function hostedShellCommands() {
|
|
25
|
+
return getCommandNames().filter((name) => !HOSTED_SHELL_EXCLUDED.includes(name));
|
|
26
|
+
}
|
|
15
27
|
export function createSforaShell(options) {
|
|
16
28
|
const client = new SforaApiClient({
|
|
17
29
|
baseUrl: options.baseUrl,
|
|
18
30
|
apiKey: options.apiKey,
|
|
19
31
|
actAs: options.actAs,
|
|
20
32
|
clientLabel: options.clientLabel,
|
|
33
|
+
transport: options.transport,
|
|
21
34
|
});
|
|
22
35
|
const presence = options.presence ?? presenceNotice();
|
|
23
36
|
const fs = new SforaFs(client);
|
|
@@ -34,6 +47,7 @@ export function createSforaShell(options) {
|
|
|
34
47
|
// rather than special-cased in the REPL so they compose like every other
|
|
35
48
|
// command: `blocks x.md | grep heading`, `sed … | put x.md --block <id>`.
|
|
36
49
|
customCommands: sforaShellCommands(client, presence),
|
|
50
|
+
...(options.commands ? { commands: options.commands } : {}),
|
|
37
51
|
});
|
|
38
52
|
return { bash, fs, client, presence };
|
|
39
53
|
}
|
|
@@ -49,6 +63,7 @@ export function createLocalShell(root, cwd = "/") {
|
|
|
49
63
|
return { bash, fs };
|
|
50
64
|
}
|
|
51
65
|
export { SforaFs } from "./SforaFs.js";
|
|
66
|
+
export { cloudToolDescription } from "./mcp-description.js";
|
|
52
67
|
export { LocalWorkspace, initWorkspace, findWorkspace, WORKSPACE_DIR, } from "./local/workspace.js";
|
|
53
68
|
export * from "./format/index.js";
|
|
54
69
|
export { SforaApiClient, SforaApiError, blockConflictFrom, writeEffectFrom, } from "./api-client.js";
|
|
@@ -56,6 +71,7 @@ export { WEB_URL_HEADER, fsRequestPath, webUrlFromResponse, } from "./web-url.js
|
|
|
56
71
|
export { openerCommand } from "./opener.js";
|
|
57
72
|
export { blocksCommand, putCommand, urlCommand, resolveFsPath, presenceNotice, } from "./block-commands.js";
|
|
58
73
|
export { sforaShellCommands, parseShellArgs } from "./shell-commands.js";
|
|
74
|
+
export { ATTACHMENT_MAX_BYTES, ATTACHMENT_TOOLS, attachmentToolResult, attachmentsCommand, isAttachmentTool, safeAttachmentName, } from "./attachments.js";
|
|
59
75
|
export { watchLoop, parseWatchTarget, MAX_BACKOFF_MS, } from "./watch.js";
|
|
60
76
|
export { renderPing, renderBlocks, renderBlockConflict, renderWriteEffect, ndjson, } from "./render.js";
|
|
61
77
|
export { KNOWN_CLIENTS, detectClient, sanitizeClientSlug, chatMessageJson, chatTailLoop, awaitReplyLoop, } from "./chat.js";
|
|
@@ -17,7 +17,7 @@ export interface LocalMarkdownSnapshot {
|
|
|
17
17
|
export declare function readLocalMarkdown(path: string): Promise<LocalMarkdownSnapshot>;
|
|
18
18
|
export declare function saveLocalMarkdown(path: string, content: string, expectedRevision: string): Promise<LocalMarkdownSnapshot>;
|
|
19
19
|
/** Save As creates a new path only. Replacing an existing file requires its current revision. */
|
|
20
|
-
export declare function atomicCreate(path: string, content: string | Uint8Array): Promise<string>;
|
|
20
|
+
export declare function atomicCreate(path: string, content: string | Uint8Array, mode?: number): Promise<string>;
|
|
21
21
|
export declare function saveNewLocalMarkdown(path: string, content: string): Promise<LocalMarkdownSnapshot>;
|
|
22
22
|
/** Watch the parent so atomic replacement by another editor remains observable. */
|
|
23
23
|
export declare function watchLocalMarkdown(path: string, onChange: () => void): FSWatcher;
|
package/dist/local-core/files.js
CHANGED
|
@@ -82,11 +82,11 @@ export async function saveLocalMarkdown(path, content, expectedRevision) {
|
|
|
82
82
|
});
|
|
83
83
|
}
|
|
84
84
|
/** Save As creates a new path only. Replacing an existing file requires its current revision. */
|
|
85
|
-
export async function atomicCreate(path, content) {
|
|
85
|
+
export async function atomicCreate(path, content, mode = 0o600) {
|
|
86
86
|
const destination = join(await realpath(dirname(resolve(path))), basename(path));
|
|
87
87
|
const temporary = `${destination}.${randomUUID()}.tmp`;
|
|
88
88
|
try {
|
|
89
|
-
await atomicWrite(temporary, content);
|
|
89
|
+
await atomicWrite(temporary, content, mode);
|
|
90
90
|
// Hard link publishes atomically and fails EEXIST instead of overwriting a raced save.
|
|
91
91
|
await link(temporary, destination);
|
|
92
92
|
}
|
|
@@ -2,3 +2,15 @@
|
|
|
2
2
|
* Renderers must obtain native dialog grants; never expose these as arbitrary IPC paths. */
|
|
3
3
|
export * from "./files.js";
|
|
4
4
|
export * from "./skills.js";
|
|
5
|
+
export * from "./skill-domain.js";
|
|
6
|
+
export * from "./skill-discovery.js";
|
|
7
|
+
export * from "./skill-store.js";
|
|
8
|
+
export * from "./skill-service.js";
|
|
9
|
+
export * from "./skill-sync.js";
|
|
10
|
+
export * from "./skill-operations.js";
|
|
11
|
+
export { SKILL_AGENT_ADAPTERS, projectSkillSources } from "./skill-adapters.js";
|
|
12
|
+
export type { SkillAgentAdapter } from "./skill-adapters.js";
|
|
13
|
+
export * from "./skill-local-executor.js";
|
|
14
|
+
export * from "./skill-executor.js";
|
|
15
|
+
export { SkillIndex } from './skill-index.js';
|
|
16
|
+
export { SkillReviewService, type SkillReview } from './skill-review.js';
|
package/dist/local-core/index.js
CHANGED
|
@@ -2,3 +2,14 @@
|
|
|
2
2
|
* Renderers must obtain native dialog grants; never expose these as arbitrary IPC paths. */
|
|
3
3
|
export * from "./files.js";
|
|
4
4
|
export * from "./skills.js";
|
|
5
|
+
export * from "./skill-domain.js";
|
|
6
|
+
export * from "./skill-discovery.js";
|
|
7
|
+
export * from "./skill-store.js";
|
|
8
|
+
export * from "./skill-service.js";
|
|
9
|
+
export * from "./skill-sync.js";
|
|
10
|
+
export * from "./skill-operations.js";
|
|
11
|
+
export { SKILL_AGENT_ADAPTERS, projectSkillSources } from "./skill-adapters.js";
|
|
12
|
+
export * from "./skill-local-executor.js";
|
|
13
|
+
export * from "./skill-executor.js";
|
|
14
|
+
export { SkillIndex } from './skill-index.js';
|
|
15
|
+
export { SkillReviewService } from './skill-review.js';
|