slopwaresystems 0.1.1 → 0.1.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +1 -1
  2. package/dist/index.js +32 -25
  3. package/package.json +1 -1
package/README.md CHANGED
@@ -4,7 +4,7 @@ The [Slopware Tasks](https://tasks.slopware.systems) board from the terminal. Bu
4
4
 
5
5
  ```bash
6
6
  npm i -g slopwaresystems
7
- slop login # approve the machine in your browser
7
+ slop login # approve the machine in your browser (on a VM or over SSH: open the printed link on any device)
8
8
  cd your-repo && slop link WEB
9
9
  slop task create "Retry storms on 429" -s high -d -
10
10
  slop skills install # puts the agent skills into .claude/skills/
package/dist/index.js CHANGED
@@ -3379,7 +3379,7 @@ import { homedir } from "os";
3379
3379
  import { join } from "path";
3380
3380
 
3381
3381
  // skills/slop-cli.md
3382
- var slop_cli_default = '---\nname: slop-cli\ndescription: How to use the slop CLI to read and update the task board this repository is linked to. Load when asked to create, update, plan, comment on or finish tasks.\n---\n\n# slop: the board from the terminal\n\nThe repository you are in is linked to a project on Slopware Tasks. Everything a person can do on the board, you can do with `slop`. Add `--json` to any command for machine-readable output.\n\n## Orientation\n\n```bash\nslop status # which project this checkout is linked to\nslop statuses # the board\'s columns (key, label, category)\nslop members # who tasks can be assigned to\nslop task list [--all] [--status <column>] [--assignee me]\nslop task show <KEY> # title, status, severity, assignee, links, plan, description\nslop task history <KEY> # everything that happened to it, in order\n```\n\nTask keys look like `WEB-12`; the bare number works inside the linked project.\n\n## Creating and changing tasks\n\n```bash\nslop task create "Short imperative title" -d "What, where, why" -s <low|medium|high|critical> [--status <column>] [-a <email|me|none>]\nslop task update <KEY> [--title \u2026] [-d \u2026] [-s \u2026] [--status \u2026] [-a \u2026]\nslop task move <KEY> "<column label or key>"\nslop task start <KEY> # first "in progress" column\nslop task done <KEY> # first "done" column\nslop task assign <KEY> <email|me|none>\nslop task link <KEY> <url> # a PR, a design, a log; GitHub PRs are recognised\nslop task notes <KEY> [text|-] # the scratchpad under the links: read it, or replace it whole\n\n## Sessions (see the `slop-sessions` skill)\n\n```bash\nslop session start <KEY> # claim: assign me, move to in progress\nslop session wait <KEY> # be live until someone says something; run in the background\nslop session end <KEY>\nslop sessions # who is live on what\nslop task ask <KEY> "\u2026" # say something to whoever is live on it (also on the timeline)\n```\nslop task archive <KEY> # off the board, history kept\n```\n\nDescriptions are Markdown and are shown in full. Long text: `-d -` reads stdin.\n\n## Plans (see the `slop-planning` skill)\n\n```bash\nslop task items <KEY> # the plan, numbered, with notes\nslop task plan <KEY> - <<\'EOF\' # one item per line, appended in order\nInventory the Intercom data model\nWrite the export script\nEOF\nslop task item add <KEY> "title" [-n "note"]\nslop task item done <KEY> <n> # by number from `items`\nslop task item reopen <KEY> <n>\nslop task item note <KEY> <n> "what is true now"\nslop task item rename <KEY> <n> "new title"\nslop task item remove <KEY> <n>\n```\n\n## Projects\n\n```bash\nslop projects\nslop project create "Name" [--key KEY]\nslop project update [--name \u2026] [--description \u2026] [--theme <theme>]\nslop project status add "In review" -c in_progress # columns: add | rename | remove | order\n```\n\n## Rules of the road\n\n- Do not delete tasks you did not create. Archive finished work instead.\n- Keep titles short and imperative; put the reasoning in the description or the plan\'s notes. There is no chat: the plan\'s notes are the running state, the description holds decisions.\n- When you open a pull request for a task, link it.\n';
3382
+ var slop_cli_default = '---\nname: slop-cli\ndescription: How to use the slop CLI to read and update the task board this repository is linked to. Load when asked to create, update, plan, comment on or finish tasks.\n---\n\n# slop: the board from the terminal\n\nThe repository you are in is linked to a project on Slopware Tasks. Everything a person can do on the board, you can do with `slop`. Add `--json` to any command for machine-readable output.\n\n## Orientation\n\n```bash\nslop status # which project this checkout is linked to\nslop statuses # the board\'s columns (key, label, category)\nslop members # who tasks can be assigned to\nslop task list [--all] [--status <column>] [--assignee me]\nslop task show <KEY> # title, status, severity, assignee, links, plan, description\nslop task history <KEY> # everything that happened to it, in order\n```\n\nTask keys look like `WEB-12`; the bare number works inside the linked project.\n\nA link belongs to the clone: every git worktree of it (including agent sandboxes made with `git worktree add`) is linked without doing anything. On a machine with no browser, `slop login` prints a link and a code; open it on any device.\n\n## Creating and changing tasks\n\n```bash\nslop task create "Short imperative title" -d "What, where, why" -s <low|medium|high|critical> [--status <column>] [-a <email|me|none>]\nslop task update <KEY> [--title \u2026] [-d \u2026] [-s \u2026] [--status \u2026] [-a \u2026]\nslop task move <KEY> "<column label or key>"\nslop task start <KEY> # first "in progress" column\nslop task done <KEY> # first "done" column\nslop task assign <KEY> <email|me|none>\nslop task link <KEY> <url> # a PR, a design, a log; GitHub PRs are recognised\nslop task notes <KEY> [text|-] # the scratchpad under the links: read it, or replace it whole\n\n## Sessions (see the `slop-sessions` skill)\n\n```bash\nslop session start <KEY> # claim: assign me, move to in progress\nslop session wait <KEY> # be live until someone says something; run in the background\nslop session end <KEY>\nslop sessions # who is live on what\nslop task ask <KEY> "\u2026" # say something to whoever is live on it (also on the timeline)\n```\nslop task archive <KEY> # off the board, history kept\n```\n\nDescriptions are Markdown and are shown in full. Long text: `-d -` reads stdin.\n\n## Plans (see the `slop-planning` skill)\n\n```bash\nslop task items <KEY> # the plan, numbered, with notes\nslop task plan <KEY> - <<\'EOF\' # one item per line, appended in order\nInventory the Intercom data model\nWrite the export script\nEOF\nslop task item add <KEY> "title" [-n "note"]\nslop task item done <KEY> <n> # by number from `items`\nslop task item reopen <KEY> <n>\nslop task item note <KEY> <n> "what is true now"\nslop task item rename <KEY> <n> "new title"\nslop task item remove <KEY> <n>\n```\n\n## Projects\n\n```bash\nslop projects\nslop project create "Name" [--key KEY]\nslop project update [--name \u2026] [--description \u2026]\nslop project status add "In review" -c in_progress # columns: add | rename | remove | order\n```\n\n## Rules of the road\n\n- Do not delete tasks you did not create. Archive finished work instead.\n- Keep titles short and imperative; put the reasoning in the description or the plan\'s notes. There is no chat: the plan\'s notes are the running state, the description holds decisions.\n- When you open a pull request for a task, link it.\n';
3383
3383
 
3384
3384
  // skills/slop-planning.md
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';
@@ -23207,9 +23207,21 @@ var Invitation = external_exports.object({
23207
23207
  role: OrgRole,
23208
23208
  createdAt: Timestamp
23209
23209
  });
23210
+ var InviteLink = external_exports.object({
23211
+ id: Id,
23212
+ role: OrgRole,
23213
+ createdAt: Timestamp,
23214
+ expiresAt: Timestamp.nullable(),
23215
+ uses: external_exports.number()
23216
+ });
23217
+ var CreateInviteLinkInput = external_exports.object({ role: OrgRole.default("member"), expiresInHours: external_exports.number().int().min(1).max(24 * 30).nullable().default(24) });
23218
+ var CreatedInviteLink = InviteLink.extend({ url: external_exports.string() });
23219
+ var JoinPreview = external_exports.object({ org: external_exports.object({ id: Id, name: external_exports.string() }), role: OrgRole, alreadyMember: external_exports.boolean() });
23210
23220
  var MembersResult = external_exports.object({
23211
23221
  members: external_exports.array(Member),
23212
- invitations: external_exports.array(Invitation)
23222
+ invitations: external_exports.array(Invitation),
23223
+ /** Active links only. Admins see them; members get an empty list. */
23224
+ links: external_exports.array(InviteLink)
23213
23225
  });
23214
23226
  var InviteMemberInput = external_exports.object({ email: Email, role: OrgRole.default("member") });
23215
23227
  var UpdateMemberInput = external_exports.object({ role: OrgRole });
@@ -23241,8 +23253,6 @@ function findStatus(statuses, ref) {
23241
23253
 
23242
23254
  // ../contract/src/work/projects.ts
23243
23255
  var ProjectKey = external_exports.string().trim().toUpperCase().pipe(external_exports.string().regex(/^[A-Z][A-Z0-9]{1,7}$/, "2\u20138 letters or digits, starting with a letter"));
23244
- var PROJECT_THEMES = ["slate", "stone", "zinc", "mist", "sand", "rose", "violet", "emerald"];
23245
- var ProjectTheme = external_exports.enum(PROJECT_THEMES);
23246
23256
  var TaskCounts = external_exports.record(external_exports.string(), external_exports.number());
23247
23257
  var Project = external_exports.object({
23248
23258
  id: Id,
@@ -23250,8 +23260,6 @@ var Project = external_exports.object({
23250
23260
  key: external_exports.string(),
23251
23261
  name: external_exports.string(),
23252
23262
  description: external_exports.string(),
23253
- /** Null: no theme of its own; the viewer's personal theme applies. */
23254
- theme: ProjectTheme.nullable(),
23255
23263
  /** The board's columns, in order. */
23256
23264
  statuses: external_exports.array(ProjectStatus),
23257
23265
  createdAt: Timestamp,
@@ -23266,8 +23274,7 @@ var CreateProjectInput = external_exports.object({
23266
23274
  });
23267
23275
  var UpdateProjectInput = external_exports.object({
23268
23276
  name: external_exports.string().trim().min(1).max(80),
23269
- description: external_exports.string().trim().max(2e3),
23270
- theme: ProjectTheme.nullable()
23277
+ description: external_exports.string().trim().max(2e3)
23271
23278
  }).partial();
23272
23279
  function suggestProjectKey(name) {
23273
23280
  const words = name.toUpperCase().replace(/[^A-Z0-9 ]+/g, " ").split(/\s+/).filter(Boolean);
@@ -23640,19 +23647,21 @@ function openBrowser(url2) {
23640
23647
  }
23641
23648
  }
23642
23649
  var sleep = (ms) => new Promise((done) => setTimeout(done, ms));
23650
+ var headless = () => Boolean(process.env.SSH_CONNECTION || process.env.SSH_TTY) || platform2() === "linux" && !process.env.DISPLAY && !process.env.WAYLAND_DISPLAY;
23643
23651
  function registerAuthCommands(program3) {
23644
- program3.command("login").description("Sign this machine in. Opens the web app, where you approve the login.").option("--url <url>", "the Slopware Tasks instance to sign in to").option("--no-browser", "print the link instead of opening it").action(async (options) => {
23652
+ program3.command("login").description("Sign this machine in. Opens the web app, where you approve the login. On a VM or over SSH, open the printed link on any device.").option("--url <url>", "the Slopware Tasks instance to sign in to").option("--no-browser", "print the link instead of opening it").action(async (options) => {
23645
23653
  const baseUrl = (options.url || process.env.SLOP_URL || readConfig()?.baseUrl || DEFAULT_URL).replace(/\/+$/, "");
23646
23654
  const api = createApi({ baseUrl, token: null });
23647
23655
  const started = await api.post("/cli/auth/start", { machine: machineInfo() });
23656
+ const remote = headless();
23648
23657
  process.stderr.write(`
23649
- Approve this login in your browser:
23658
+ Approve this login in a browser${remote ? " on any device (this machine has no browser; your laptop or phone is fine)" : ""}:
23650
23659
  ${started.verifyUrl}
23651
23660
 
23652
23661
  Confirm the code matches: ${started.userCode}
23653
23662
 
23654
23663
  `);
23655
- if (options.browser) openBrowser(started.verifyUrl);
23664
+ if (options.browser && !remote) openBrowser(started.verifyUrl);
23656
23665
  const deadline = Date.now() + started.expiresIn * 1e3;
23657
23666
  while (Date.now() < deadline) {
23658
23667
  await sleep(started.interval * 1e3);
@@ -23721,8 +23730,9 @@ function git(args, cwd = process.cwd()) {
23721
23730
  function findRepo() {
23722
23731
  const commonDir = git(["rev-parse", "--path-format=absolute", "--git-common-dir"]);
23723
23732
  if (!commonDir) return null;
23724
- const root = basename(commonDir) === ".git" ? resolve(commonDir, "..") : git(["rev-parse", "--show-toplevel"]) ?? commonDir;
23725
- return { root, linkPath: join4(commonDir, "slop.json") };
23733
+ const worktree = git(["rev-parse", "--show-toplevel"]);
23734
+ const root = basename(commonDir) === ".git" ? resolve(commonDir, "..") : worktree ?? commonDir;
23735
+ return { root, worktree: worktree ?? root, linkPath: join4(commonDir, "slop.json") };
23726
23736
  }
23727
23737
  function requireRepo() {
23728
23738
  const repo = findRepo();
@@ -23845,17 +23855,13 @@ function registerProjectCommands(program3) {
23845
23855
  const statuses = await api.put(`/projects/${target.id}/statuses/order`, { ids: refs.map((ref) => columnOf(target, ref).id) });
23846
23856
  emit(statuses, () => list(statuses));
23847
23857
  });
23848
- project.command("update").description("Change the linked project's name, description or theme.").option("-p, --project <key>", "project key (default: the linked project)").option("-n, --name <name>").option("-d, --description <text>").option("--theme <theme>", `${PROJECT_THEMES.join(" | ")} | none`).action(async (options) => {
23858
+ project.command("update").description("Change the linked project's name or description.").option("-p, --project <key>", "project key (default: the linked project)").option("-n, --name <name>").option("-d, --description <text>").action(async (options) => {
23849
23859
  const api = authedApi();
23850
23860
  const target = await resolveProject(api, options.project);
23851
- const theme = options.theme === void 0 ? void 0 : options.theme === "none" ? null : options.theme;
23852
- if (theme && !PROJECT_THEMES.includes(theme)) {
23853
- throw new CliError(`Unknown theme "${theme}"`, `use one of: ${PROJECT_THEMES.join(", ")}, none`);
23854
- }
23855
- if (options.name === void 0 && options.description === void 0 && theme === void 0) {
23856
- throw new CliError("Nothing to change", "pass --name, --description or --theme");
23861
+ if (options.name === void 0 && options.description === void 0) {
23862
+ throw new CliError("Nothing to change", "pass --name or --description");
23857
23863
  }
23858
- const updated = await api.patch(`/projects/${target.id}`, { name: options.name, description: options.description, theme });
23864
+ const updated = await api.patch(`/projects/${target.id}`, { name: options.name, description: options.description });
23859
23865
  emit(updated, () => `Updated ${updated.key} ${updated.name}`);
23860
23866
  });
23861
23867
  project.command("create <name>").description("Create a project.").option("-k, --key <key>", "task key prefix, e.g. WEB (default: derived from the name)").option("-d, --description <text>", "what the project is").option("--org <slug-or-id>", "which org, if you belong to several").action(async (name, options) => {
@@ -23891,10 +23897,11 @@ Try: slop task create "My first task"`);
23891
23897
  program3.command("status").description("Show what this repository is linked to.").action(async () => {
23892
23898
  const repo = findRepo();
23893
23899
  const link = repo ? readLink(repo) : null;
23900
+ const where = !repo ? "" : repo.worktree === repo.root ? repo.root : `${repo.worktree} (worktree of ${repo.root})`;
23894
23901
  emit(
23895
- { repo: repo?.root ?? null, link },
23896
- () => !repo ? "Not inside a git repository." : !link ? `${repo.root}
23897
- not linked. Run: slop link <PROJECT_KEY>` : `${repo.root}
23902
+ { repo: repo?.root ?? null, worktree: repo?.worktree ?? null, link },
23903
+ () => !repo ? "Not inside a git repository." : !link ? `${where}
23904
+ not linked. Run: slop link <PROJECT_KEY>` : `${where}
23898
23905
  linked to ${link.projectKey} ${link.projectName} (${link.baseUrl})`
23899
23906
  );
23900
23907
  });
@@ -24253,7 +24260,7 @@ ${written.map((path3) => ` ${path3}`).join("\n")}`);
24253
24260
  }
24254
24261
 
24255
24262
  // src/index.ts
24256
- 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.1.1").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)");
24263
+ 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.1.2").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)");
24257
24264
  registerAuthCommands(program2);
24258
24265
  registerProjectCommands(program2);
24259
24266
  registerSessionCommands(program2, registerTaskCommands(program2));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "slopwaresystems",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "description": "slop: the Slopware Tasks board from the terminal, for people and coding agents.",
6
6
  "type": "module",