passalong 0.15.1 → 0.16.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/bin/passalong +31 -4
- package/package.json +1 -1
- package/skill/SKILL.md +39 -47
- package/src/api.js +59 -3
- package/src/mcp.js +197 -4
- package/src/passalong.js +13 -0
package/bin/passalong
CHANGED
|
@@ -26,7 +26,8 @@
|
|
|
26
26
|
// passalong hub open your synced guides in the browser
|
|
27
27
|
// passalong take <id> you are doing it — said before the work, so the sender stops guessing
|
|
28
28
|
// passalong pass <id> <why> it is not yours — hands it back to its author with your reason
|
|
29
|
-
// passalong assign <id> <to> give it to @handle, a #group, or "team" — yours, or assigned to you
|
|
29
|
+
// passalong assign <id> <to> give it to @handle (or several: @ada @bola), a #group, or "team" — yours, or assigned to you
|
|
30
|
+
// passalong recall <id> take back something you sent: private again, gone from their inboxes
|
|
30
31
|
// passalong admin create <email> make an account that runs Passalong (owner only)
|
|
31
32
|
// passalong admin list who runs it
|
|
32
33
|
// passalong admin grant|revoke <@handle> make an existing account an admin, or stop it being one
|
|
@@ -50,6 +51,8 @@
|
|
|
50
51
|
// passalong team invite [email] make an invite link (mailed when an email is given)
|
|
51
52
|
// passalong team join <link|code> accept an invite
|
|
52
53
|
// passalong team use <slug> switch the current team
|
|
54
|
+
// passalong team promote|demote <@handle> make a teammate an admin, or a member again
|
|
55
|
+
// passalong team remove <@handle> take a teammate out; `team leave` leaves yourself
|
|
53
56
|
// passalong setup install the Claude Code capture skill, MCP server and hooks
|
|
54
57
|
// passalong now what this worktree holds and what is waiting (the hooks call --hook)
|
|
55
58
|
// passalong mcp run the MCP server over stdio
|
|
@@ -666,12 +669,20 @@ const commands = {
|
|
|
666
669
|
|
|
667
670
|
async assign(f) {
|
|
668
671
|
const id = f._[0] || die('usage: passalong assign <id> <@handle | #group | team>');
|
|
669
|
-
const to = f._
|
|
672
|
+
const to = f._.slice(1).join(",") || die('say who: @handle (or several), #group, or "team" for everyone');
|
|
670
673
|
const r = await passalong.assign(id, to).catch((e) => die(e.message));
|
|
671
674
|
const back = r.taken_back ? ` — taken back from ${r.taken_back} who had it, and they were told` : "";
|
|
672
675
|
console.error(`${r.id} is now for ${r.to || "everyone in the team"}${back}`);
|
|
673
676
|
},
|
|
674
677
|
|
|
678
|
+
async recall(f) {
|
|
679
|
+
const id = f._[0] || die("usage: passalong recall <id>");
|
|
680
|
+
const r = await passalong.recall(id).catch((e) => die(e.message));
|
|
681
|
+
console.error(`${r.id} is private again, and gone from their inboxes`);
|
|
682
|
+
if (r.already_opened_by)
|
|
683
|
+
console.error(`${r.already_opened_by} ${r.already_opened_by === 1 ? "person" : "people"} had already opened it, and keep what they pulled`);
|
|
684
|
+
},
|
|
685
|
+
|
|
675
686
|
// ---- who runs the product ---------------------------------------------------------------
|
|
676
687
|
// `passalong admin <create|list|grant|revoke>`. An admin signs in like anybody else: the role is
|
|
677
688
|
// a column, so there is no admin password and no admin token — only an account that has it.
|
|
@@ -901,7 +912,7 @@ const commands = {
|
|
|
901
912
|
const t = await api.team(cfg.team).catch((e) => die(e.message));
|
|
902
913
|
console.log(`${t.name} (${t.slug}) — ${t.members.length} member${t.members.length === 1 ? "" : "s"}, ${t.guides} guide${t.guides === 1 ? "" : "s"}`);
|
|
903
914
|
for (const m of t.members)
|
|
904
|
-
console.log(` ${m.handle ? `@${m.handle}` : "(no handle)"}${m.name ? ` ${m.name}` : ""} ${m.role} joined ${relDate(m.joined)}`);
|
|
915
|
+
console.log(` ${m.handle ? `@${m.handle}` : "(no handle)"}${m.name ? ` ${m.name}` : ""} ${m.role === "owner" ? "admin" : m.role} joined ${relDate(m.joined)}`);
|
|
905
916
|
return;
|
|
906
917
|
}
|
|
907
918
|
if (sub === "create") {
|
|
@@ -934,6 +945,22 @@ const commands = {
|
|
|
934
945
|
if (who && !who.handle) console.error("teammates address you by handle: passalong me --handle <you>");
|
|
935
946
|
return;
|
|
936
947
|
}
|
|
948
|
+
if (sub === "promote" || sub === "demote") {
|
|
949
|
+
const slug = cfg.team || die("no current team — passalong team use <slug>");
|
|
950
|
+
const who = rest[0] || die(`usage: passalong team ${sub} <@handle>`);
|
|
951
|
+
const r = await api.setMemberRole(slug, who, sub === "promote" ? "admin" : "member").catch((e) => die(e.message));
|
|
952
|
+
console.error(`${who.startsWith("@") ? who : `@${who}`} is ${r.role === "admin" ? "an admin" : "a member"} of ${slug}${sub === "promote" ? " — they are told" : ""}`);
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
if (sub === "remove" || sub === "leave") {
|
|
956
|
+
const slug = cfg.team || die("no current team — passalong team use <slug>");
|
|
957
|
+
const me = sub === "leave" ? (await api.me().catch((e) => die(e.message))).account : "";
|
|
958
|
+
const who = sub === "leave" ? me : rest[0] || die("usage: passalong team remove <@handle>");
|
|
959
|
+
const r = await api.removeMember(slug, who).catch((e) => die(e.message));
|
|
960
|
+
console.error(sub === "leave" ? `you left ${slug}` : `${who.startsWith("@") ? who : `@${who}`} is out of ${slug}${r.released ? `; ${r.released} hold${r.released === 1 ? "" : "s"} of theirs went back to waiting` : ""}`);
|
|
961
|
+
if (sub === "leave") api.setTeam("");
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
937
964
|
if (sub === "use") {
|
|
938
965
|
const slug = rest[0] || die("usage: passalong team use <slug>");
|
|
939
966
|
await api.team(slug).catch((e) => die(e.message));
|
|
@@ -941,7 +968,7 @@ const commands = {
|
|
|
941
968
|
console.error(`current team: ${slug}`);
|
|
942
969
|
return;
|
|
943
970
|
}
|
|
944
|
-
die("usage: passalong team [create <name> | invite [email] | join <link> | use <slug>]");
|
|
971
|
+
die("usage: passalong team [create <name> | invite [email] | join <link> | use <slug> | promote <@handle> | demote <@handle> | remove <@handle> | leave]");
|
|
945
972
|
},
|
|
946
973
|
|
|
947
974
|
async setup() {
|
package/package.json
CHANGED
package/skill/SKILL.md
CHANGED
|
@@ -14,7 +14,7 @@ session's history. Write for execution, not for permanence.
|
|
|
14
14
|
**Check what is already open, and whether a guide is wanted at all.** Run `passalong now` (or
|
|
15
15
|
`take` with no id over MCP) to see what this worktree holds and what was handed to you.
|
|
16
16
|
|
|
17
|
-
- **The session answers something you hold** —
|
|
17
|
+
- **The session answers something you hold** — use the MCP `hand_in` tool instead.
|
|
18
18
|
Publishing a second guide about it leaves two records of one piece of work. Every hand-in carries
|
|
19
19
|
what you ran and what came back — the command and the lines that decided it, a test summary, a
|
|
20
20
|
link to the change, or a screenshot url. Copy each one out of the session as you go; at the end
|
|
@@ -44,7 +44,8 @@ machine, another agent session, or a teammate without your branch.
|
|
|
44
44
|
what was decided and why, what finally worked, and how it was verified. Read the diff
|
|
45
45
|
(`git diff`, `git log`) only to confirm details; the reasoning is in the transcript.
|
|
46
46
|
3. **Write the guide** with the structure below. Be concrete: file paths, commands, exact error
|
|
47
|
-
strings, version numbers.
|
|
47
|
+
strings, version numbers. A transfer has no required body sections; keep only the context the
|
|
48
|
+
next agent needs.
|
|
48
49
|
4. **Mark context-specific parts** so the receiver can adapt rather than copy. Put
|
|
49
50
|
`ASSUMES: <thing>. If <alternative>, <what changes>.` at the start of any step that depends on
|
|
50
51
|
this repo's stack, layout, or conventions.
|
|
@@ -60,14 +61,13 @@ machine, another agent session, or a teammate without your branch.
|
|
|
60
61
|
who it was handed to) on stderr.
|
|
61
62
|
|
|
62
63
|
**If what you are capturing is more context for an existing guide** — a missing detail, a step
|
|
63
|
-
that needed explaining, what changed since
|
|
64
|
+
that needed explaining, or what changed since — add `--follows <that id>`.
|
|
64
65
|
That publishes it as a follow-up: its own guide, listed under the original, and handed to
|
|
65
66
|
whoever opens the original, person or agent. It travels the other way too — whoever opens the
|
|
66
67
|
follow-up gets the guide it came out of, and where that guide has got to — so write it as a
|
|
67
|
-
note on that work rather than as a standalone brief.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
`passalong broken <id> <why>`.
|
|
68
|
+
note on that work rather than as a standalone brief. What you did or found while working on a
|
|
69
|
+
guide belongs in its `hand_in`, not a follow-up. Whether a guide worked is its own answer:
|
|
70
|
+
`passalong works <id> <screenshot>` or `passalong broken <id> <why>`.
|
|
71
71
|
6. **Report** the id (and link if synced) and one line on how to use it on the other side:
|
|
72
72
|
`passalong start <id>` in the target repo, or "start passalong <id>" to an agent with the
|
|
73
73
|
Passalong MCP server. `start` rather than `pull` on purpose: it takes the handoff as well as
|
|
@@ -77,26 +77,25 @@ machine, another agent session, or a teammate without your branch.
|
|
|
77
77
|
If the `passalong` command is missing, tell the user to run `npm i -g passalong && passalong setup` and
|
|
78
78
|
still write the draft file so nothing is lost.
|
|
79
79
|
|
|
80
|
-
##
|
|
80
|
+
## Choose the right Passalong form
|
|
81
81
|
|
|
82
|
-
|
|
83
|
-
each. Absent means `transfer`, which is what every guide written before bug reports existed is.
|
|
82
|
+
Every guide must state `kind:`. An absent kind is refused; the server never guesses one.
|
|
84
83
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
84
|
+
- `transfer` is context for a different repo, machine or agent session. It has no required
|
|
85
|
+
sections. Write only what the receiver needs to avoid starting cold.
|
|
86
|
+
- `bug` is a defect to fix. Put the reproduction under `## Reproduce`, never `## Steps`, so the
|
|
87
|
+
receiver does not mistake the steps that show the defect for a remedy. Use `file_bugs` when
|
|
88
|
+
filing several independent defects.
|
|
89
|
+
- `task` is work nobody has done yet. Use `plan_tasks` for a larger goal; its `## Acceptance`
|
|
90
|
+
gives the reviewer something to check. Do not write implementation steps for the taker.
|
|
89
91
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
pulls a guide, and steps that reproduce a defect are the one list that must never be run as a
|
|
98
|
-
remedy — an agent that follows them reproduces the bug, checks the Verification, finds it false
|
|
99
|
-
because the bug is real, and reports that the guide does not work.
|
|
92
|
+
**A project folder is not a guide.** When asked to keep a script, brief, screenshots or other
|
|
93
|
+
assets together for continuing work, call `list_folders` first. Reuse a fitting folder, or call
|
|
94
|
+
`create_folder` with a title and short description drawn from the person's request. Create the
|
|
95
|
+
first Markdown document with `create_folder_document`; do not leave an empty folder or ask the
|
|
96
|
+
person to fill a form. Omit `team` unless they asked to share it. Read a document and its version
|
|
97
|
+
before `save_folder_document`; reread after a stale-version refusal. Add files with
|
|
98
|
+
`add_folder_asset` and link an existing guide with `link_folder_guide` only when it uses the folder.
|
|
100
99
|
|
|
101
100
|
## Say it to a person
|
|
102
101
|
|
|
@@ -116,6 +115,7 @@ billing.ts per the Paystack spec, see PR 412."
|
|
|
116
115
|
---
|
|
117
116
|
title: <what this accomplishes, as a verb phrase: "Add Paystack webhook verification">
|
|
118
117
|
summary: <one or two plain sentences for a PERSON, 400 characters at most, no ids or paths: what this is and whether anyone has to act>
|
|
118
|
+
kind: transfer
|
|
119
119
|
author: <git user.name, if known>
|
|
120
120
|
source_context: <repo or product this came from>
|
|
121
121
|
status: published
|
|
@@ -123,26 +123,19 @@ stack_assumptions: [<runtime/framework/db/service versions the steps depend on>]
|
|
|
123
123
|
tags: [<3 to 6 lowercase keywords>]
|
|
124
124
|
---
|
|
125
125
|
|
|
126
|
-
##
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
## Solution shape
|
|
130
|
-
The approach at a high level, before any code. One short paragraph.
|
|
131
|
-
|
|
132
|
-
## Decisions and rationale
|
|
133
|
-
- **Chose X over Y** because Z. (Each real decision, with what was rejected.)
|
|
126
|
+
## What changed
|
|
127
|
+
The information the receiving agent needs, in the shape that makes it easiest to use.
|
|
134
128
|
|
|
135
|
-
##
|
|
136
|
-
|
|
137
|
-
2. ...
|
|
129
|
+
## Why
|
|
130
|
+
Decisions or constraints the receiver could not infer from the final code.
|
|
138
131
|
|
|
139
|
-
##
|
|
140
|
-
Commands
|
|
141
|
-
|
|
142
|
-
## Gotchas
|
|
143
|
-
- What failed along the way, the exact error, and why. What looked right but wasn't.
|
|
132
|
+
## How to check
|
|
133
|
+
Commands and expected results, if the receiver needs to repeat or verify the work.
|
|
144
134
|
```
|
|
145
135
|
|
|
136
|
+
Those headings are examples, not a required template. A short transfer may be a paragraph.
|
|
137
|
+
Include paths, assumptions, verification or gotchas only when they help the next agent act.
|
|
138
|
+
|
|
146
139
|
## Bug structure
|
|
147
140
|
|
|
148
141
|
```markdown
|
|
@@ -174,16 +167,15 @@ The behaviour that should have happened, as something the fixer can check.
|
|
|
174
167
|
|
|
175
168
|
## Quality bar
|
|
176
169
|
|
|
177
|
-
- **
|
|
178
|
-
|
|
179
|
-
- **
|
|
180
|
-
- **Every ASSUMES has an alternative.** The point is adaptation on the other side.
|
|
170
|
+
- **Write for the crossing.** State what another agent cannot learn from the destination repo.
|
|
171
|
+
- **Verification, when relevant, must be checkable.** Give the command and expected result.
|
|
172
|
+
- **State assumptions where they matter.** Say how the next environment may differ.
|
|
181
173
|
- **One problem per guide.** A lesson from the same session that is not part of this fix goes in
|
|
182
|
-
its own guide
|
|
174
|
+
its own guide only when it is separate work worth handing over.
|
|
183
175
|
- **No session narration.** "First I looked at..." is noise. State the finding.
|
|
184
176
|
- **No secrets.** Redact tokens, keys, and internal hostnames that would not apply elsewhere.
|
|
185
|
-
-
|
|
186
|
-
-
|
|
177
|
+
- Every guide needs a title, summary and explicit kind. A transfer needs no fixed body section.
|
|
178
|
+
- A bug needs Problem and Reproduce. Never give a bug a Steps section.
|
|
187
179
|
- The line above Problem is written by `file_bugs` and the hub's form. It is in the document, not
|
|
188
180
|
added by whatever served it, so an agent that fetched the share link over plain HTTP — with no
|
|
189
181
|
MCP server and no knowledge of Passalong — still reads what the document is before acting on it.
|
package/src/api.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
// Client for the hosted sync API (apps/api). Everything here is optional: with no token the
|
|
2
2
|
// CLI is a purely local tool, and every function throws an ApiError the CLI turns into a hint.
|
|
3
3
|
import { readFileSync } from "node:fs";
|
|
4
|
+
import { readFile, stat } from "node:fs/promises";
|
|
5
|
+
import { basename } from "node:path";
|
|
4
6
|
import { nameFor, readAccounts, readConfig, saveTeam } from "./store.js";
|
|
5
7
|
|
|
6
8
|
/**
|
|
@@ -145,7 +147,10 @@ export async function meWith(tokenValue, base = baseUrl()) {
|
|
|
145
147
|
|
|
146
148
|
export { nameFor };
|
|
147
149
|
|
|
148
|
-
async function call(
|
|
150
|
+
async function call(
|
|
151
|
+
path,
|
|
152
|
+
{ method = "GET", body, auth = true, raw = false, uploadName = "" } = {},
|
|
153
|
+
) {
|
|
149
154
|
const headers = { ...CLIENT };
|
|
150
155
|
if (auth) {
|
|
151
156
|
if (needsChoice()) throw new ApiError(409, choiceMessage());
|
|
@@ -154,14 +159,24 @@ async function call(path, { method = "GET", body, auth = true, raw = false } = {
|
|
|
154
159
|
headers.authorization = `Bearer ${t}`;
|
|
155
160
|
}
|
|
156
161
|
if (body !== undefined) {
|
|
157
|
-
headers["content-type"] =
|
|
162
|
+
headers["content-type"] = uploadName
|
|
163
|
+
? "application/octet-stream"
|
|
164
|
+
: typeof body === "string"
|
|
165
|
+
? "text/markdown"
|
|
166
|
+
: "application/json";
|
|
158
167
|
}
|
|
168
|
+
if (uploadName) headers["x-file-name"] = uploadName.replace(/[^\x20-\x7e]/g, "").slice(0, 120);
|
|
159
169
|
let res;
|
|
160
170
|
try {
|
|
161
171
|
res = await fetch(`${baseUrl()}${path}`, {
|
|
162
172
|
method,
|
|
163
173
|
headers,
|
|
164
|
-
body:
|
|
174
|
+
body:
|
|
175
|
+
body === undefined
|
|
176
|
+
? undefined
|
|
177
|
+
: uploadName || typeof body === "string"
|
|
178
|
+
? body
|
|
179
|
+
: JSON.stringify(body),
|
|
165
180
|
});
|
|
166
181
|
} catch (err) {
|
|
167
182
|
throw new ApiError(0, `could not reach ${baseUrl()} (${err.message})`);
|
|
@@ -273,10 +288,49 @@ export const team = (slug) => call(`/v1/teams/${encodeURIComponent(slug)}`);
|
|
|
273
288
|
export const createTeam = (name) => call("/v1/teams", { method: "POST", body: { name } });
|
|
274
289
|
export const invite = (slug, email = "") =>
|
|
275
290
|
call(`/v1/teams/${encodeURIComponent(slug)}/invites`, { method: "POST", body: { email } });
|
|
291
|
+
export const setMemberRole = (slug, who, role) =>
|
|
292
|
+
call(`/v1/teams/${encodeURIComponent(slug)}/members/${encodeURIComponent(who)}`, {
|
|
293
|
+
method: "PATCH",
|
|
294
|
+
body: { role },
|
|
295
|
+
});
|
|
296
|
+
export const removeMember = (slug, who) =>
|
|
297
|
+
call(`/v1/teams/${encodeURIComponent(slug)}/members/${encodeURIComponent(who)}`, {
|
|
298
|
+
method: "DELETE",
|
|
299
|
+
});
|
|
276
300
|
export const join = (code) =>
|
|
277
301
|
call(`/v1/invites/${encodeURIComponent(code)}/accept`, { method: "POST" });
|
|
278
302
|
|
|
279
303
|
export const board = () => call("/v1/board");
|
|
304
|
+
export const folders = (scope = "all") => call(`/v1/folders${q({ scope })}`);
|
|
305
|
+
export const createFolder = (title, description = "", team = "") =>
|
|
306
|
+
call("/v1/folders", { method: "POST", body: { title, description, team } });
|
|
307
|
+
export const folder = (id) => call(`/v1/folders/${encodeURIComponent(id)}`);
|
|
308
|
+
export const folderDocument = (folderId, documentId) =>
|
|
309
|
+
call(`/v1/folders/${encodeURIComponent(folderId)}/documents/${encodeURIComponent(documentId)}`);
|
|
310
|
+
export const folderAsset = (folderId, assetId) =>
|
|
311
|
+
call(`/v1/folders/${encodeURIComponent(folderId)}/assets/${encodeURIComponent(assetId)}/agent`);
|
|
312
|
+
export async function addFolderAsset(folderId, path, name = "") {
|
|
313
|
+
const file = await stat(path);
|
|
314
|
+
if (!file.isFile()) throw new ApiError(400, "Choose a file to add to the folder.");
|
|
315
|
+
if (file.size > 10 * 1024 * 1024) throw new ApiError(413, "That file is over 10 MB.");
|
|
316
|
+
return call(`/v1/folders/${encodeURIComponent(folderId)}/assets`, {
|
|
317
|
+
method: "POST",
|
|
318
|
+
body: new Uint8Array(await readFile(path)),
|
|
319
|
+
uploadName: name || basename(path),
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
export const createFolderDocument = (folderId, name, body) =>
|
|
323
|
+
call(`/v1/folders/${encodeURIComponent(folderId)}/documents`, {
|
|
324
|
+
method: "POST",
|
|
325
|
+
body: { name, body },
|
|
326
|
+
});
|
|
327
|
+
export const saveFolderDocument = (folderId, documentId, version, body) =>
|
|
328
|
+
call(`/v1/folders/${encodeURIComponent(folderId)}/documents/${encodeURIComponent(documentId)}`, {
|
|
329
|
+
method: "PUT",
|
|
330
|
+
body: { version, body },
|
|
331
|
+
});
|
|
332
|
+
export const linkFolderGuide = (folderId, guide) =>
|
|
333
|
+
call(`/v1/folders/${encodeURIComponent(folderId)}/guides`, { method: "POST", body: { guide } });
|
|
280
334
|
/** What you did, newest first. `since` is a date prefix: 2026, 2026-09, 2026-09-11. */
|
|
281
335
|
export const log = ({ repo = "", since = "", limit = 0 } = {}) =>
|
|
282
336
|
call(`/v1/log${q({ repo, since, limit: limit || "" })}`);
|
|
@@ -347,6 +401,8 @@ export const release = (id) =>
|
|
|
347
401
|
/** The four verbs every guide answers to. See docs/V2.md §11. */
|
|
348
402
|
export const working = () => call("/v1/working");
|
|
349
403
|
export const handedIn = () => call("/v1/handed_in");
|
|
404
|
+
export const recall = (id) =>
|
|
405
|
+
call(`/v1/guides/${encodeURIComponent(id)}/recall`, { method: "POST", body: {} });
|
|
350
406
|
export const assign = (id, to) =>
|
|
351
407
|
call(`/v1/guides/${encodeURIComponent(id)}/assign`, { method: "POST", body: { to } });
|
|
352
408
|
export const take = (body) => call("/v1/take", { method: "POST", body });
|
package/src/mcp.js
CHANGED
|
@@ -298,7 +298,8 @@ export function buildServer() {
|
|
|
298
298
|
"is a spelling that is not one of the three. " +
|
|
299
299
|
`kind: task is work nobody has done yet. Sections: ${TASK_SECTIONS.join(", ")}. ` +
|
|
300
300
|
"It has no Steps: work out how to reach Goal within Constraints, leave Out of scope " +
|
|
301
|
-
"alone, and treat Acceptance as the definition of done.
|
|
301
|
+
"alone, and treat Acceptance as the definition of done. New tasks say `status: draft`; " +
|
|
302
|
+
"a proposal awaiting a team decision stays Draft until a person makes it Ready. hand_in with `markdown`: a " +
|
|
302
303
|
"transfer guide about what you did (guide_template kind transfer shows the shape) — its " +
|
|
303
304
|
"author reviews it against Acceptance.\n" +
|
|
304
305
|
`kind: transfer (or no kind: line) is a finished implementation to repeat here. Sections: ${SECTIONS.join(", ")}. ` +
|
|
@@ -343,6 +344,19 @@ export function buildServer() {
|
|
|
343
344
|
"the user asks what they have been working on, or wants a standup or a summary of a " +
|
|
344
345
|
"period, call log — but say that it holds what they passed along and not everything they " +
|
|
345
346
|
"did. Gotchas are the highest-value section: record what failed and why.\n" +
|
|
347
|
+
"FOLDERS ARE REUSABLE PROJECT CONTEXT, NOT GUIDES. When the user asks for a folder for a " +
|
|
348
|
+
"script, brief, assets or work spanning several guides, call list_folders first to avoid " +
|
|
349
|
+
"making a duplicate. If none fits, call create_folder with a title and short description. " +
|
|
350
|
+
"A short request is enough: name it from the person's purpose and draft the first Markdown " +
|
|
351
|
+
"document with create_folder_document. Do not leave an empty folder or make them fill a form. " +
|
|
352
|
+
"Pass `team` only when the user wants it shared with that team; use the slug they named " +
|
|
353
|
+
"or one returned by list_folders, and omit it for a private folder. Use the returned " +
|
|
354
|
+
"folder id with create_folder_document to add Markdown, get_folder to see its contents, " +
|
|
355
|
+
"get_folder_document to read text and its version, and save_folder_document with that " +
|
|
356
|
+
"version when editing. On a stale-version refusal, reread before retrying so another " +
|
|
357
|
+
"person's work is not lost. Use add_folder_asset for a screenshot or file available on " +
|
|
358
|
+
"this machine, get_folder_asset to read one already there, and link_folder_guide only " +
|
|
359
|
+
"when an existing guide uses that folder.\n" +
|
|
346
360
|
"A FOLLOW-UP IS MORE CONTEXT FOR A GUIDE, WRITTEN AS ITS OWN GUIDE. When a guide needs " +
|
|
347
361
|
"more context — a missing detail, a step that needed explaining, what changed since, what " +
|
|
348
362
|
"you found doing it — publish that context with publish_guide `parent` set to the guide's " +
|
|
@@ -490,7 +504,7 @@ export function buildServer() {
|
|
|
490
504
|
annotations: ADDS,
|
|
491
505
|
description:
|
|
492
506
|
"Reassign a guide or task the user wrote, or one assigned to them, to someone else in its team, when the user asks: " +
|
|
493
|
-
'`to` is @handle for one person, #group for the people who do a thing, or "team" for ' +
|
|
507
|
+
'`to` is @handle for one person, "@ada,@bola" for several named people, #group for the people who do a thing, or "team" for ' +
|
|
494
508
|
"everyone. Whoever held it and is left out has it taken back and is told; a task then only " +
|
|
495
509
|
"goes to the new assignee's agents. Its author, or whoever it is assigned to, can do this.",
|
|
496
510
|
inputSchema: {
|
|
@@ -507,6 +521,181 @@ export function buildServer() {
|
|
|
507
521
|
},
|
|
508
522
|
);
|
|
509
523
|
|
|
524
|
+
server.registerTool(
|
|
525
|
+
"list_folders",
|
|
526
|
+
{
|
|
527
|
+
title: "List project folders",
|
|
528
|
+
annotations: READS,
|
|
529
|
+
description: "Find project folders of documents, assets and guides. Requires sync sign-in.",
|
|
530
|
+
inputSchema: { scope: z.string().optional().describe('"all", "mine", or a team slug') },
|
|
531
|
+
},
|
|
532
|
+
async ({ scope }) => {
|
|
533
|
+
try {
|
|
534
|
+
return json(await api.folders(scope || "all"));
|
|
535
|
+
} catch (err) {
|
|
536
|
+
return fail(err);
|
|
537
|
+
}
|
|
538
|
+
},
|
|
539
|
+
);
|
|
540
|
+
server.registerTool(
|
|
541
|
+
"create_folder",
|
|
542
|
+
{
|
|
543
|
+
title: "Create project folder",
|
|
544
|
+
annotations: ADDS,
|
|
545
|
+
description:
|
|
546
|
+
"Create a folder for project documents and assets after checking list_folders. Omit team for a private folder; pass a team slug only when sharing was requested.",
|
|
547
|
+
inputSchema: {
|
|
548
|
+
title: z.string().describe("Name of the project or body of work"),
|
|
549
|
+
description: z.string().optional().describe("Short purpose of the folder"),
|
|
550
|
+
team: z.string().optional().describe("Team slug; omit for a private folder"),
|
|
551
|
+
},
|
|
552
|
+
},
|
|
553
|
+
async ({ title, description, team }) => {
|
|
554
|
+
try {
|
|
555
|
+
return json(await api.createFolder(title, description || "", team || ""));
|
|
556
|
+
} catch (err) {
|
|
557
|
+
return fail(err);
|
|
558
|
+
}
|
|
559
|
+
},
|
|
560
|
+
);
|
|
561
|
+
server.registerTool(
|
|
562
|
+
"get_folder",
|
|
563
|
+
{
|
|
564
|
+
title: "Open project folder",
|
|
565
|
+
annotations: READS,
|
|
566
|
+
description: "List a folder's documents, assets and linked guides.",
|
|
567
|
+
inputSchema: { folder: z.string() },
|
|
568
|
+
},
|
|
569
|
+
async ({ folder }) => {
|
|
570
|
+
try {
|
|
571
|
+
return json(await api.folder(folder));
|
|
572
|
+
} catch (err) {
|
|
573
|
+
return fail(err);
|
|
574
|
+
}
|
|
575
|
+
},
|
|
576
|
+
);
|
|
577
|
+
server.registerTool(
|
|
578
|
+
"get_folder_document",
|
|
579
|
+
{
|
|
580
|
+
title: "Read folder document",
|
|
581
|
+
annotations: READS,
|
|
582
|
+
description: "Read the full Markdown and version before editing it.",
|
|
583
|
+
inputSchema: { folder: z.string(), document: z.string() },
|
|
584
|
+
},
|
|
585
|
+
async ({ folder, document }) => {
|
|
586
|
+
try {
|
|
587
|
+
return json(await api.folderDocument(folder, document));
|
|
588
|
+
} catch (err) {
|
|
589
|
+
return fail(err);
|
|
590
|
+
}
|
|
591
|
+
},
|
|
592
|
+
);
|
|
593
|
+
server.registerTool(
|
|
594
|
+
"get_folder_asset",
|
|
595
|
+
{
|
|
596
|
+
title: "Read folder asset",
|
|
597
|
+
annotations: READS,
|
|
598
|
+
description:
|
|
599
|
+
"Read an image or small text file from a folder into this agent's context. Other file types stay private downloads.",
|
|
600
|
+
inputSchema: { folder: z.string(), asset: z.string() },
|
|
601
|
+
},
|
|
602
|
+
async ({ folder, asset }) => {
|
|
603
|
+
try {
|
|
604
|
+
const file = await api.folderAsset(folder, asset);
|
|
605
|
+
const { data, text: body, ...meta } = file;
|
|
606
|
+
return {
|
|
607
|
+
content: data
|
|
608
|
+
? [
|
|
609
|
+
{ type: "text", text: file.name },
|
|
610
|
+
{ type: "image", data, mimeType: file.type },
|
|
611
|
+
]
|
|
612
|
+
: [{ type: "text", text: `${file.name}\n\n${body || ""}` }],
|
|
613
|
+
structuredContent: meta,
|
|
614
|
+
};
|
|
615
|
+
} catch (err) {
|
|
616
|
+
return fail(err);
|
|
617
|
+
}
|
|
618
|
+
},
|
|
619
|
+
);
|
|
620
|
+
|
|
621
|
+
server.registerTool(
|
|
622
|
+
"add_folder_asset",
|
|
623
|
+
{
|
|
624
|
+
title: "Add file to project folder",
|
|
625
|
+
annotations: { ...ADDS, openWorldHint: true },
|
|
626
|
+
description:
|
|
627
|
+
"Add a screenshot, reference file or other asset from this machine to a folder. Use a real file path you can read; the bytes are uploaded directly, not copied into a guide. Images, PDF, ZIP and text are accepted up to 10 MB.",
|
|
628
|
+
inputSchema: {
|
|
629
|
+
folder: z.string().describe("folder id returned by create_folder or list_folders"),
|
|
630
|
+
file: z.string().describe("path to the file on this machine"),
|
|
631
|
+
name: z.string().optional().describe("label in the folder; defaults to the filename"),
|
|
632
|
+
},
|
|
633
|
+
},
|
|
634
|
+
async ({ folder, file, name }) => {
|
|
635
|
+
try {
|
|
636
|
+
return json(await api.addFolderAsset(folder, file, name || ""));
|
|
637
|
+
} catch (err) {
|
|
638
|
+
return fail(err);
|
|
639
|
+
}
|
|
640
|
+
},
|
|
641
|
+
);
|
|
642
|
+
|
|
643
|
+
server.registerTool(
|
|
644
|
+
"create_folder_document",
|
|
645
|
+
{
|
|
646
|
+
title: "Write new folder document",
|
|
647
|
+
annotations: ADDS,
|
|
648
|
+
description: "Add Markdown such as a brief or script to a folder.",
|
|
649
|
+
inputSchema: { folder: z.string(), name: z.string(), body: z.string() },
|
|
650
|
+
},
|
|
651
|
+
async ({ folder, name, body }) => {
|
|
652
|
+
try {
|
|
653
|
+
return json(await api.createFolderDocument(folder, name, body));
|
|
654
|
+
} catch (err) {
|
|
655
|
+
return fail(err);
|
|
656
|
+
}
|
|
657
|
+
},
|
|
658
|
+
);
|
|
659
|
+
server.registerTool(
|
|
660
|
+
"save_folder_document",
|
|
661
|
+
{
|
|
662
|
+
title: "Save folder document",
|
|
663
|
+
annotations: ADDS,
|
|
664
|
+
description:
|
|
665
|
+
"Save a new revision. Send the version returned when you read it; a newer edit is refused.",
|
|
666
|
+
inputSchema: {
|
|
667
|
+
folder: z.string(),
|
|
668
|
+
document: z.string(),
|
|
669
|
+
version: z.number().int().positive(),
|
|
670
|
+
body: z.string(),
|
|
671
|
+
},
|
|
672
|
+
},
|
|
673
|
+
async ({ folder, document, version, body }) => {
|
|
674
|
+
try {
|
|
675
|
+
return json(await api.saveFolderDocument(folder, document, version, body));
|
|
676
|
+
} catch (err) {
|
|
677
|
+
return fail(err);
|
|
678
|
+
}
|
|
679
|
+
},
|
|
680
|
+
);
|
|
681
|
+
server.registerTool(
|
|
682
|
+
"link_folder_guide",
|
|
683
|
+
{
|
|
684
|
+
title: "Link guide to folder",
|
|
685
|
+
annotations: ADDS,
|
|
686
|
+
description:
|
|
687
|
+
"Put an existing task or handoff beside the folder documents and assets it uses.",
|
|
688
|
+
inputSchema: { folder: z.string(), guide: z.string() },
|
|
689
|
+
},
|
|
690
|
+
async ({ folder, guide }) => {
|
|
691
|
+
try {
|
|
692
|
+
return json(await api.linkFolderGuide(folder, guide));
|
|
693
|
+
} catch (err) {
|
|
694
|
+
return fail(err);
|
|
695
|
+
}
|
|
696
|
+
},
|
|
697
|
+
);
|
|
698
|
+
|
|
510
699
|
server.registerTool(
|
|
511
700
|
"search_guides",
|
|
512
701
|
{
|
|
@@ -1160,7 +1349,9 @@ export function buildServer() {
|
|
|
1160
1349
|
to: z
|
|
1161
1350
|
.string()
|
|
1162
1351
|
.optional()
|
|
1163
|
-
.describe(
|
|
1352
|
+
.describe(
|
|
1353
|
+
"team slug, team/@handle for one teammate, team/@ada,@bola for several named teammates, or team/#group for a set of them",
|
|
1354
|
+
),
|
|
1164
1355
|
cwd: z
|
|
1165
1356
|
.string()
|
|
1166
1357
|
.optional()
|
|
@@ -1233,7 +1424,9 @@ export function buildServer() {
|
|
|
1233
1424
|
to: z
|
|
1234
1425
|
.string()
|
|
1235
1426
|
.optional()
|
|
1236
|
-
.describe(
|
|
1427
|
+
.describe(
|
|
1428
|
+
"team slug, team/@handle for one teammate, team/@ada,@bola for several named teammates, or team/#group for a set of them",
|
|
1429
|
+
),
|
|
1237
1430
|
issues: z
|
|
1238
1431
|
.array(
|
|
1239
1432
|
z.object({
|
package/src/passalong.js
CHANGED
|
@@ -523,6 +523,19 @@ export async function assign(ref, to) {
|
|
|
523
523
|
return r;
|
|
524
524
|
}
|
|
525
525
|
|
|
526
|
+
/**
|
|
527
|
+
* Take back something you sent: it is private again and gone from every inbox it was in. Refused
|
|
528
|
+
* while somebody is on it. Whoever already opened it keeps what they pulled. The local copy is
|
|
529
|
+
* dropped so the next read shows it without the address.
|
|
530
|
+
*/
|
|
531
|
+
export async function recall(ref) {
|
|
532
|
+
needsSync("taking a guide back");
|
|
533
|
+
const id = await resolveId(ref);
|
|
534
|
+
const r = await api.recall(id);
|
|
535
|
+
store.remove(id);
|
|
536
|
+
return r;
|
|
537
|
+
}
|
|
538
|
+
|
|
526
539
|
/** Not this agent's to do: what it held is open again, and `why` goes to whoever is next. */
|
|
527
540
|
export async function pass(id, why, { cwd = process.cwd() } = {}) {
|
|
528
541
|
needsSync("passing work");
|