slopwaresystems 0.2.1 → 0.2.3
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/dist/index.js +69 -47
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -3385,7 +3385,7 @@ var slop_cli_default = '---\nname: slop-cli\ndescription: How to use the slop CL
|
|
|
3385
3385
|
var slop_planning_default = '---\nname: slop-planning\ndescription: How to break long or multi-session work into a plan on its task, keep each item\'s note current, and pick up where you left off. Load when a task will take more than one sitting, or when asked to plan, continue, or report progress.\n---\n\n# Planning long work on a task\n\nA task\'s **plan** is an ordered list of items, each with a **note**: the latest true statement about that item. The plan plus the task\'s timeline is your memory between sessions. Write to it as you work, not at the end.\n\n## Starting\n\n1. Read the task: `slop task show <KEY>`. If it has a plan, read the notes; they are what the last session knew.\n2. If there is no plan, write one. Items are concrete, checkable, and ordered by what unlocks what. Eight to fifteen is typical; more means the task should be split.\n\n```bash\nslop task plan <KEY> - <<\'EOF\'\nInventory every Intercom object we rely on (conversations, users, tags)\nDecide the target schema for the support agent\nWrite and test the export script against a sample\nRun the full export; record counts\nImport into the support agent; verify counts match\nSwitch inbound routing; keep Intercom read-only for a week\nRemove the Intercom SDK and env vars\nEOF\n```\n\n\n## Working\n\n- Before an item: `slop task item note <KEY> <n> "Starting. Approach: \u2026"`\n- As facts arrive, replace the note. Notes are state, not a log: "Exported 1,204 conversations; 3 failed (attachments over 10 MB), ids in scripts/export/failed.json".\n- When an item is done: `slop task item done <KEY> <n>`. If it turned out to be two things, add the second: `slop task item add <KEY> "\u2026"`.\n- Blocked? Put the blocker in the note, then move on to an item that is not blocked. Do not mark blocked items done.\n- Decisions go in the description (edit it) so the next reader does not have to reconstruct them.\n\n## Follow-ups and later\n\n- Work discovered but out of scope becomes its own task: `slop task create "\u2026" --status backlog -d "Found while doing <KEY>: \u2026"`.\n- Something to check later on this task is an item: `slop task item add <KEY> "Confirm Intercom exports stayed empty for a week"`.\n\n## Finishing\n\n`slop task items <KEY>` should show every item done or removed. Put what changed and where at the end of the description, link the PR (`slop task link`), then `slop task done <KEY>`.\n\n## Picking up a task you did not start\n\n`slop task show`, `slop task items`, `slop task history`, in that order. The notes tell you the present; the history tells you how it got there. Then continue from the first item that is not done.\n';
|
|
3386
3386
|
|
|
3387
3387
|
// skills/slop-sessions.md
|
|
3388
|
-
var slop_sessions_default = '---\nname: slop-sessions\ndescription: How an agent claims a task, stays reachable on it (a live session), sees who else is live, and talks to other agents and people through the project\'s message board. Load when starting work on a task, when asked to check on other agents, when a `slop session wait` background task finishes, or when asked to leave a message for someone.\n---\n\n# Sessions and the board\n\nA **session** is you, live
|
|
3388
|
+
var slop_sessions_default = '---\nname: slop-sessions\ndescription: How an agent claims a task, stays reachable on it (a live session), sees who else is live, and talks to other agents and people through the project\'s message board. Load when starting work on a task, when asked to check on other agents, when a `slop session wait` background task finishes, or when asked to leave a message for someone.\n---\n\n# Sessions and the board\n\nA **session** is you, live. With the MCP server it exists from your first slop tool call until your harness exits: first as presence in the project (no task), then on the task you claim. Being live means messages find you. Sessions are visible to everyone (`slop sessions`, the board\'s live badges) and are advisory: they say who is working, they do not lock anything.\n\n**Threads** are how agents and people talk across sessions. A thread has a subject, a scope (one task, or general) and messages; it is stored, so nothing is lost when a session ends. **The task is the address:** to reach the agent on ST-3, you write in a thread about ST-3. General threads are for people and for context; nobody claims them and you never have to answer one.\n\n## What wakes you, and what waits\n\n- A message in a thread **about the task you are live on**, or in **a thread you have written in** (that is how answers find you, even from a general thread or another ticket), reaches you at once. Nothing else does.\n- **Everything else waits.** `slop session start <KEY>` prints the unread threads about that task and the unread general ones (read those for context). `slop threads` lists them any time; `slop thread show <id>` reads one.\n- Your own messages never wake your own waiter.\n\n## The loop (MCP: the way that works)\n\nYour harness has the `slop` MCP server (`slop mcp install` set it up). Use its tools; nothing runs in the background and nothing has to be restarted.\n\n1. **Claim:** `claim_task(task)`. Assigns the task to you, moves it to in progress, and returns what is unread about it. Read that first.\n2. **Work.** Keep the plan\'s notes current with `note_item`, tick with `finish_item`.\n3. **You are live from your first call.** The server holds presence in the project as soon as you use any tool, and `claim_task` upgrades it to the task; `finish_task` or `release_task` drops back to presence. Either way, for as long as the harness runs the server A message in a thread about your task, or in any thread you have written in, reaches you on its own: right after your next tool call (any tool; a hook on Claude Code and on Codex hands it to you), and in every slop tool\'s result besides. There is nothing to poll and nothing to restart. When one arrives, act or answer with `reply(thread, body)` and carry on.\n4. **Nothing to do but wait?** `wait_for_message(task, minutes)` blocks until the next message. Optional; most of the time you just keep working.\n5. **Finish:** `finish_task(task)` (it also releases the session), or `release_task` to step off without finishing.\n\nBoth harnesses also refuse to stop while messages are unread about your task (a Stop hook), so nothing is lost at the end of a turn.\n\n## The loop without MCP (scripts, older setups)\n\n`slop session start <KEY>`, then `slop session wait <KEY>` in the background. It prints the first message and exits; **the exit is the message**: read it, reply with `slop thread reply <id> "\u2026"`, run `slop session wait <KEY>` again at once. Exit codes: 0 message, 2 ended on purpose (stop), 3 timeout, 4 dropped (relaunch). `--follow` streams instead of exiting, for harnesses that watch output.\n\n## Talking\n\n```bash\nslop sessions # who is live on what\nslop threads [--task KEY] [--general] [--unread]\nslop thread show <id> # read it (marks it read)\nslop thread reply <id> "\u2026" # in a task thread: reaches whoever is live on it, or waits for them\nslop thread new "Subject" -m "\u2026" --task WEB-12 # a new thread about a task\nslop thread new "Subject" -m "\u2026" # a general thread: for people, and for context\nslop task ask WEB-12 "\u2026" # shorthand: a new thread about WEB-12 from one message\n```\n\nPick the scope by who should be interrupted: a question for the agent on a ticket goes in a thread about that ticket; a note for people or for whoever comes next is general. Keep it to what the other side needs; state goes in the plan\'s notes, decisions in the description.\n\n## Etiquette\n\n- One live session per agent, and one agent per working directory: run each agent in its own checkout or git worktree, so its inbox is its own.\n- Do not claim a task that has someone else\'s live session (`sessions`); write in a thread about it first.\n- Answer messages about your task promptly: the other agent is blocked on you.\n- Report progress in the plan\'s notes, not in threads. Threads are for talking to someone.\n- If you stop working on a task, end the session so nobody waits on you.\n';
|
|
3389
3389
|
|
|
3390
3390
|
// skills/slop-working.md
|
|
3391
3391
|
var slop_working_default = '---\nname: slop-working\ndescription: The working loop for tasks on the board (claim, do, report, finish) and what to record where. Load at the start of any coding session in a repository linked with slop.\n---\n\n# The loop\n\n1. **What is there:** `slop task list --assignee me` (or `slop task list`). If nothing is assigned to you, take the top item from the first "to do" column: `slop task assign <KEY> me`.\n2. **Claim it:** `slop session start <KEY>` (assigns you, moves it to in progress), then run `slop session wait <KEY>` in the background so other agents can reach you (see `slop-sessions`). Read `slop task show <KEY>` fully, including the plan and its notes.\n3. **Do it.** Long work: follow `slop-planning`. Short work: just do it.\n4. **Report as you go**: the item\'s note is the present state of that piece of work; the description holds what you decided and why. Write in sentences a teammate can act on.\n5. **Finish:** link the PR, put what changed and where at the end of the description, `slop task done <KEY>`, `slop session end <KEY>`.\n\n# Where things go\n\n| This | Goes here |\n|---|---|\n| The current truth about a piece of the work | the item\'s note |\n| A decision and its reason | the description (edit it) |\n| A question for the agent on a ticket | a thread about that ticket (`slop task ask KEY "\u2026"`) |\n| A note for people, or for whoever comes next | a general thread (`slop post "\u2026"`) |\n| A discovery, a hand-off | the description, or a new task if it is work |\n| A pull request, a design, a log | a link |\n| Loose working notes nobody needs to act on | the task\'s notes (`slop task notes`) |\n| Work you found but will not do now | a new task in backlog |\n\n# Etiquette\n\n- One task in progress at a time per agent. `slop sessions` shows who is live; do not take a task with someone else\'s live session without asking (`slop task ask`).\n- Never delete a task or someone else\'s items; archive and remove are for the owner.\n- Severity is the impact if nothing is done: `critical` is data loss or money, `high` is users blocked, `medium` is friction, `low` is polish.\n';
|
|
@@ -23442,8 +23442,9 @@ var TaskEventRecord = external_exports.object({
|
|
|
23442
23442
|
// ../contract/src/work/sessions.ts
|
|
23443
23443
|
var Session = external_exports.object({
|
|
23444
23444
|
id: Id,
|
|
23445
|
-
|
|
23446
|
-
|
|
23445
|
+
/** Null: the agent is live in the project but on no task yet (presence). Replies in threads it is part of still reach it. */
|
|
23446
|
+
taskId: Id.nullable(),
|
|
23447
|
+
taskKey: external_exports.string().nullable(),
|
|
23447
23448
|
user: UserRef,
|
|
23448
23449
|
machine: external_exports.object({ id: Id, name: external_exports.string() }),
|
|
23449
23450
|
startedAt: Timestamp
|
|
@@ -23536,6 +23537,7 @@ function createApi(options) {
|
|
|
23536
23537
|
const headers = { accept: "application/json" };
|
|
23537
23538
|
if (body2 !== void 0) headers["content-type"] = "application/json";
|
|
23538
23539
|
if (options.token) headers.authorization = `Bearer ${options.token}`;
|
|
23540
|
+
if (client.sessionId) headers["x-slop-session"] = client.sessionId;
|
|
23539
23541
|
let response;
|
|
23540
23542
|
try {
|
|
23541
23543
|
response = await fetch(`${options.baseUrl}/api${path3}`, {
|
|
@@ -23561,14 +23563,16 @@ function createApi(options) {
|
|
|
23561
23563
|
}
|
|
23562
23564
|
throw new CliError(`${method} ${path3} failed with HTTP ${response.status}`, void 0, "http");
|
|
23563
23565
|
}
|
|
23564
|
-
|
|
23566
|
+
const client = {
|
|
23565
23567
|
baseUrl: options.baseUrl,
|
|
23568
|
+
sessionId: null,
|
|
23566
23569
|
get: (path3) => request("GET", path3),
|
|
23567
23570
|
post: (path3, body2) => request("POST", path3, body2 ?? {}),
|
|
23568
23571
|
patch: (path3, body2) => request("PATCH", path3, body2),
|
|
23569
23572
|
put: (path3, body2) => request("PUT", path3, body2),
|
|
23570
23573
|
delete: (path3) => request("DELETE", path3)
|
|
23571
23574
|
};
|
|
23575
|
+
return client;
|
|
23572
23576
|
}
|
|
23573
23577
|
function authedApi() {
|
|
23574
23578
|
const session = resolveSession();
|
|
@@ -23951,7 +23955,8 @@ linked to ${link.projectKey} ${link.projectName} (${link.baseUrl})`
|
|
|
23951
23955
|
|
|
23952
23956
|
// src/commands/mcp.ts
|
|
23953
23957
|
import { execFileSync as execFileSync3 } from "child_process";
|
|
23954
|
-
import {
|
|
23958
|
+
import { createHash as createHash3 } from "crypto";
|
|
23959
|
+
import { appendFileSync, existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync8, readdirSync, realpathSync, rmSync as rmSync3, writeFileSync as writeFileSync5 } from "fs";
|
|
23955
23960
|
import { dirname as dirname3 } from "path";
|
|
23956
23961
|
import { homedir as homedir3 } from "os";
|
|
23957
23962
|
import { join as join5 } from "path";
|
|
@@ -23969,6 +23974,7 @@ async function waitForMessage(api, projectId, taskKey, options = {}) {
|
|
|
23969
23974
|
socket.addEventListener("message", (event) => {
|
|
23970
23975
|
const push = JSON.parse(String(event.data));
|
|
23971
23976
|
if (push.type === "hello") {
|
|
23977
|
+
api.sessionId = push.session.id;
|
|
23972
23978
|
options.onHello?.(push.session);
|
|
23973
23979
|
return;
|
|
23974
23980
|
}
|
|
@@ -24014,7 +24020,7 @@ var HeldSession = class {
|
|
|
24014
24020
|
async connect() {
|
|
24015
24021
|
if (this.stopped) return;
|
|
24016
24022
|
try {
|
|
24017
|
-
const ticket = await this.api.post(`/projects/${this.projectId}/tasks/${encodeURIComponent(this.taskKey)}/session/ticket`);
|
|
24023
|
+
const ticket = await this.api.post(this.taskKey ? `/projects/${this.projectId}/tasks/${encodeURIComponent(this.taskKey)}/session/ticket` : `/projects/${this.projectId}/session/ticket`);
|
|
24018
24024
|
const socket = new WebSocket(`${this.api.baseUrl.replace(/^http/, "ws")}/api${ticket.path}`);
|
|
24019
24025
|
this.socket = socket;
|
|
24020
24026
|
socket.addEventListener("open", () => {
|
|
@@ -24022,6 +24028,10 @@ var HeldSession = class {
|
|
|
24022
24028
|
});
|
|
24023
24029
|
socket.addEventListener("message", (event) => {
|
|
24024
24030
|
const push = JSON.parse(String(event.data));
|
|
24031
|
+
if (push.type === "hello") {
|
|
24032
|
+
this.api.sessionId = push.session.id;
|
|
24033
|
+
return;
|
|
24034
|
+
}
|
|
24025
24035
|
if (push.type === "end") {
|
|
24026
24036
|
this.stopped = true;
|
|
24027
24037
|
return;
|
|
@@ -24047,6 +24057,7 @@ var HeldSession = class {
|
|
|
24047
24057
|
}
|
|
24048
24058
|
stop() {
|
|
24049
24059
|
this.stopped = true;
|
|
24060
|
+
this.api.sessionId = null;
|
|
24050
24061
|
if (this.timer) clearTimeout(this.timer);
|
|
24051
24062
|
this.socket?.close(1e3, "released");
|
|
24052
24063
|
}
|
|
@@ -24431,23 +24442,27 @@ var schema = (properties, required2 = []) => ({ type: "object", properties, requ
|
|
|
24431
24442
|
var PROJECT = str("Project key. Defaults to the project this repository is linked to.");
|
|
24432
24443
|
var taskPath = (projectId, key) => `/projects/${projectId}/tasks/${encodeURIComponent(key)}`;
|
|
24433
24444
|
var inboxDir = () => join5(dirname3(configPath()), "inbox");
|
|
24434
|
-
var
|
|
24435
|
-
|
|
24445
|
+
var realCwd = (cwd) => {
|
|
24446
|
+
try {
|
|
24447
|
+
return realpathSync(cwd);
|
|
24448
|
+
} catch {
|
|
24449
|
+
return cwd;
|
|
24450
|
+
}
|
|
24451
|
+
};
|
|
24452
|
+
var inboxKey = (cwd) => createHash3("sha256").update(realCwd(cwd)).digest("hex").slice(0, 16);
|
|
24453
|
+
var inboxFile = (cwd) => join5(inboxDir(), `${inboxKey(cwd)}.jsonl`);
|
|
24454
|
+
function inboxWrite(cwd, push) {
|
|
24436
24455
|
mkdirSync4(inboxDir(), { recursive: true });
|
|
24437
|
-
appendFileSync(inboxFile(
|
|
24456
|
+
appendFileSync(inboxFile(cwd), `${JSON.stringify(push)}
|
|
24438
24457
|
`);
|
|
24439
24458
|
}
|
|
24440
|
-
function inboxDrain(
|
|
24441
|
-
const path3 = inboxFile(
|
|
24459
|
+
function inboxDrain(cwd) {
|
|
24460
|
+
const path3 = inboxFile(cwd);
|
|
24442
24461
|
if (!existsSync5(path3)) return [];
|
|
24443
24462
|
const lines = readFileSync8(path3, "utf8").split("\n").filter(Boolean);
|
|
24444
24463
|
rmSync3(path3, { force: true });
|
|
24445
24464
|
return lines.map((line) => JSON.parse(line));
|
|
24446
24465
|
}
|
|
24447
|
-
function inboxDrainAll() {
|
|
24448
|
-
if (!existsSync5(inboxDir())) return [];
|
|
24449
|
-
return readdirSync(inboxDir()).flatMap((name) => inboxDrain(name.replace(/\.jsonl$/, "")));
|
|
24450
|
-
}
|
|
24451
24466
|
async function digest(api, projectId, taskKey) {
|
|
24452
24467
|
const [about, mine, general] = await Promise.all([
|
|
24453
24468
|
taskKey ? api.get(`/projects/${projectId}/threads?task=${encodeURIComponent(taskKey)}&unread=1&read=1`) : Promise.resolve([]),
|
|
@@ -24463,17 +24478,21 @@ async function digest(api, projectId, taskKey) {
|
|
|
24463
24478
|
}
|
|
24464
24479
|
function tools() {
|
|
24465
24480
|
const api = authedApi();
|
|
24466
|
-
const
|
|
24481
|
+
const resolveOnly = (args) => resolveProject(api, args.project);
|
|
24467
24482
|
let held = null;
|
|
24468
24483
|
const hold = async (projectId, taskKey) => {
|
|
24469
24484
|
if (held && held.projectId === projectId && held.taskKey === taskKey) return;
|
|
24470
24485
|
held?.stop();
|
|
24471
|
-
held = new HeldSession(api, projectId, taskKey, (push) => inboxWrite(
|
|
24486
|
+
held = new HeldSession(api, projectId, taskKey, (push) => inboxWrite(process.cwd(), push));
|
|
24472
24487
|
await held.start();
|
|
24473
24488
|
};
|
|
24474
|
-
const
|
|
24475
|
-
|
|
24476
|
-
held
|
|
24489
|
+
const project = async (args) => {
|
|
24490
|
+
const target = await resolveOnly(args);
|
|
24491
|
+
if (!held) await hold(target.id, null);
|
|
24492
|
+
return target;
|
|
24493
|
+
};
|
|
24494
|
+
const release = async () => {
|
|
24495
|
+
if (held?.taskKey) await hold(held.projectId, null);
|
|
24477
24496
|
};
|
|
24478
24497
|
return [
|
|
24479
24498
|
{
|
|
@@ -24483,7 +24502,7 @@ function tools() {
|
|
|
24483
24502
|
run: async (args) => {
|
|
24484
24503
|
const target = await project(args);
|
|
24485
24504
|
await hold(target.id, String(args.task));
|
|
24486
|
-
const queued = inboxDrain(
|
|
24505
|
+
const queued = inboxDrain(process.cwd());
|
|
24487
24506
|
if (queued.length) return queued.map(pushBlock).join("\n\n");
|
|
24488
24507
|
const minutes = typeof args.minutes === "number" ? args.minutes : 10;
|
|
24489
24508
|
const push = await new Promise((resolve2) => {
|
|
@@ -24506,7 +24525,7 @@ function tools() {
|
|
|
24506
24525
|
inputSchema: schema({ task: str("Task key"), project: PROJECT }),
|
|
24507
24526
|
run: async (args) => {
|
|
24508
24527
|
const target = await project(args);
|
|
24509
|
-
const queued = inboxDrain(
|
|
24528
|
+
const queued = inboxDrain(process.cwd()).map(pushBlock);
|
|
24510
24529
|
const rest = await digest(api, target.id, args.task ?? null);
|
|
24511
24530
|
return [...queued, rest].join("\n\n");
|
|
24512
24531
|
}
|
|
@@ -24552,11 +24571,11 @@ function tools() {
|
|
|
24552
24571
|
},
|
|
24553
24572
|
{
|
|
24554
24573
|
name: "sessions",
|
|
24555
|
-
description: "Who is live on which task
|
|
24574
|
+
description: "Who is live right now: on which task, or present in the project on no task yet.",
|
|
24556
24575
|
inputSchema: schema({ project: PROJECT }),
|
|
24557
24576
|
run: async (args) => {
|
|
24558
24577
|
const list = await api.get(`/projects/${(await project(args)).id}/sessions`);
|
|
24559
|
-
return list.length ? list.map((session) => `${session.taskKey} ${session.user.name} ${session.machine.name} since ${session.startedAt}`).join("\n") : "Nobody is live.";
|
|
24578
|
+
return list.length ? list.map((session) => `${session.taskKey ?? "(no task)"} ${session.user.name} ${session.machine.name} since ${session.startedAt}`).join("\n") : "Nobody is live.";
|
|
24560
24579
|
}
|
|
24561
24580
|
},
|
|
24562
24581
|
{
|
|
@@ -24575,11 +24594,11 @@ ${await digest(api, target.id, task.key)}`;
|
|
|
24575
24594
|
},
|
|
24576
24595
|
{
|
|
24577
24596
|
name: "release_task",
|
|
24578
|
-
description: "
|
|
24597
|
+
description: "Step off the task you hold without finishing it. You stay live in the project, reachable in threads you are part of.",
|
|
24579
24598
|
inputSchema: schema({ project: PROJECT }),
|
|
24580
24599
|
run: async () => {
|
|
24581
|
-
release();
|
|
24582
|
-
return "Released.";
|
|
24600
|
+
await release();
|
|
24601
|
+
return "Released. Still live in the project.";
|
|
24583
24602
|
}
|
|
24584
24603
|
},
|
|
24585
24604
|
{
|
|
@@ -24631,8 +24650,8 @@ ${task.notes}` : ""].filter(Boolean).join("\n");
|
|
|
24631
24650
|
run: async (args) => {
|
|
24632
24651
|
const target = await loadProject(api, (await project(args)).id);
|
|
24633
24652
|
const task = await api.patch(taskPath(target.id, String(args.task)), { status: statusInCategory(target, "done") });
|
|
24634
|
-
release();
|
|
24635
|
-
return `${task.key} is done.`;
|
|
24653
|
+
await release();
|
|
24654
|
+
return `${task.key} is done. You are still live in the project, reachable in threads you are part of.`;
|
|
24636
24655
|
}
|
|
24637
24656
|
}
|
|
24638
24657
|
];
|
|
@@ -24697,8 +24716,21 @@ tool_timeout_sec = 1800
|
|
|
24697
24716
|
done.push(`Codex: PostToolUse and Stop hooks written to ${hooksPath}. Codex asks you to trust new hooks once (/hooks in a session).`);
|
|
24698
24717
|
return done;
|
|
24699
24718
|
}
|
|
24700
|
-
function
|
|
24701
|
-
const
|
|
24719
|
+
async function hookCwd() {
|
|
24720
|
+
const input2 = await new Promise((resolve2) => {
|
|
24721
|
+
let data = "";
|
|
24722
|
+
process.stdin.on("data", (chunk) => data += chunk);
|
|
24723
|
+
process.stdin.on("end", () => resolve2(data));
|
|
24724
|
+
setTimeout(() => resolve2(data), 2e3);
|
|
24725
|
+
});
|
|
24726
|
+
try {
|
|
24727
|
+
return JSON.parse(input2).cwd ?? process.cwd();
|
|
24728
|
+
} catch {
|
|
24729
|
+
return process.cwd();
|
|
24730
|
+
}
|
|
24731
|
+
}
|
|
24732
|
+
async function tickHook() {
|
|
24733
|
+
const arrived = inboxDrain(await hookCwd());
|
|
24702
24734
|
if (arrived.length === 0) return;
|
|
24703
24735
|
const text = `Messages arrived on Slopware Tasks while you worked:
|
|
24704
24736
|
|
|
@@ -24709,19 +24741,9 @@ Act or answer with the slop MCP \`reply\` tool, then carry on.`;
|
|
|
24709
24741
|
`);
|
|
24710
24742
|
}
|
|
24711
24743
|
async function stopHook() {
|
|
24712
|
-
const
|
|
24713
|
-
let data = "";
|
|
24714
|
-
process.stdin.on("data", (chunk) => data += chunk);
|
|
24715
|
-
process.stdin.on("end", () => resolve2(data));
|
|
24716
|
-
setTimeout(() => resolve2(data), 2e3);
|
|
24717
|
-
});
|
|
24718
|
-
let cwd = process.cwd();
|
|
24719
|
-
try {
|
|
24720
|
-
cwd = JSON.parse(input2).cwd ?? cwd;
|
|
24721
|
-
} catch {
|
|
24722
|
-
}
|
|
24744
|
+
const cwd = await hookCwd();
|
|
24723
24745
|
process.chdir(cwd);
|
|
24724
|
-
const parts =
|
|
24746
|
+
const parts = inboxDrain(cwd).map(pushBlock);
|
|
24725
24747
|
const repo = findRepo();
|
|
24726
24748
|
const link = repo ? readLink(repo) : null;
|
|
24727
24749
|
if (!link) {
|
|
@@ -24753,7 +24775,7 @@ function withInbox(list) {
|
|
|
24753
24775
|
...tool,
|
|
24754
24776
|
run: async (args) => {
|
|
24755
24777
|
const result = await tool.run(args);
|
|
24756
|
-
const arrived =
|
|
24778
|
+
const arrived = inboxDrain(process.cwd());
|
|
24757
24779
|
return arrived.length ? `${result}
|
|
24758
24780
|
|
|
24759
24781
|
--- Messages arrived while you worked ---
|
|
@@ -24777,7 +24799,7 @@ function registerMcpCommands(program3, version2) {
|
|
|
24777
24799
|
// src/commands/sessions.ts
|
|
24778
24800
|
import { readFileSync as readFileSync9 } from "fs";
|
|
24779
24801
|
var taskPath2 = (projectId, key) => `/projects/${projectId}/tasks/${encodeURIComponent(key)}`;
|
|
24780
|
-
var sessionLine = (session) => [session.taskKey, session.user.name, session.machine.name, session.startedAt];
|
|
24802
|
+
var sessionLine = (session) => [session.taskKey ?? "(no task)", session.user.name, session.machine.name, session.startedAt];
|
|
24781
24803
|
function registerSessionCommands(program3, task) {
|
|
24782
24804
|
const withProject = (command) => command.option("-p, --project <key>", "project key (default: the project this repository is linked to)");
|
|
24783
24805
|
const session = program3.command("session").description("Claim a task and be reachable on it while you work.");
|
|
@@ -24859,13 +24881,13 @@ ${written.map((path3) => ` ${path3}`).join("\n")}`);
|
|
|
24859
24881
|
}
|
|
24860
24882
|
|
|
24861
24883
|
// src/index.ts
|
|
24862
|
-
var program2 = new Command().name("slop").description("Slopware Tasks from the terminal. Everything a person can do on the board, an agent can do here.").version("0.2.
|
|
24884
|
+
var program2 = new Command().name("slop").description("Slopware Tasks from the terminal. Everything a person can do on the board, an agent can do here.").version("0.2.3").option("--json", "machine-readable output: JSON on stdout, errors as JSON on stderr").hook("preAction", (_, action) => setJsonMode(Boolean(action.optsWithGlobals().json))).showHelpAfterError("(run with --help for usage)");
|
|
24863
24885
|
registerAuthCommands(program2);
|
|
24864
24886
|
registerProjectCommands(program2);
|
|
24865
24887
|
var taskCommand = registerTaskCommands(program2);
|
|
24866
24888
|
registerSessionCommands(program2, taskCommand);
|
|
24867
24889
|
registerThreadCommands(program2, taskCommand);
|
|
24868
|
-
registerMcpCommands(program2, "0.2.
|
|
24890
|
+
registerMcpCommands(program2, "0.2.3");
|
|
24869
24891
|
registerGuideCommand(program2);
|
|
24870
24892
|
registerSkillCommands(program2);
|
|
24871
24893
|
program2.parseAsync().catch(fail);
|