@augurworks/augur 0.15.8 → 0.15.9

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 CHANGED
@@ -53,6 +53,27 @@ contracts in [agents/](./agents/) does the same job. Screens in the demo space
53
53
  carry the session that made them: [open the repo and read one](https://github.com/andratwiro/augur-space-fulla/tree/main/garden/prompts).
54
54
  The follow-ups read like design direction, because that is what they are.
55
55
 
56
+ ## Let an assistant in from a link
57
+
58
+ Someone on the team gives their assistant the workspace address and asks for a
59
+ change. Nothing else is needed from them, and no password ever changes hands.
60
+ The workspace tells the assistant how to get in (`/llms.txt` on every instance),
61
+ and the flow is device pairing:
62
+
63
+ ```bash
64
+ npx @augurworks/augur connect --origin https://<the workspace>
65
+ ```
66
+
67
+ It prints a link and a code. The person opens the link in the browser they are
68
+ already signed in to and types the code; that pairs the one terminal that printed
69
+ it, to publish as them. From then on the assistant opens a prototype as a draft
70
+ (`augur open <opportunity>/<prototype>`), edits it, live at the draft's own
71
+ address, and lands it (`augur land`), which moves the real URL. Signed-in members
72
+ see the same instructions under Help › Building and at `/__connect`, so a person
73
+ who knows nothing technical can still confirm what their assistant was told. The
74
+ package on npm is this repository, and nothing else: `npm view @augurworks/augur
75
+ repository`.
76
+
56
77
  ## Boards where the prototypes run
57
78
 
58
79
  Drop a live prototype next to the stickies and drive it. Everyone on the board
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@augurworks/augur",
3
- "version": "0.15.8",
3
+ "version": "0.15.9",
4
4
  "files": [
5
5
  "scripts/",
6
6
  "build.js",
@@ -19,6 +19,7 @@ import path from "node:path";
19
19
  import os from "node:os";
20
20
  import { mkdirSync, readFileSync, writeFileSync } from "node:fs";
21
21
  import { resolveOrigin } from "./lib/store.mjs";
22
+ import { augurOnPath } from "./lib/adapters.mjs";
22
23
 
23
24
  const C = { dim: "\x1b[2m", bold: "\x1b[1m", ok: "\x1b[32m", warn: "\x1b[33m", off: "\x1b[0m" };
24
25
  const log = (m) => console.log(`\x1b[35m[connect]\x1b[0m ${m}`);
@@ -70,6 +71,11 @@ async function post(pathPart, body) {
70
71
  // token still answers, say so and stop; `--again` pairs afresh on purpose, and a token the
71
72
  // workspace no longer honours (revoked, another workspace's) falls through to a new pairing.
72
73
  const AGAIN = argv.includes("--again");
74
+ // Whether this workspace serves drafts, from its own door — the success line names the
75
+ // right next verb, not `publish` on a workspace that refuses it. Asked before any path
76
+ // that can finish, including the one that collects a pairing started earlier.
77
+ let draftsHere = false;
78
+ try { const j = await (await fetch(`${ORIGIN}/.well-known/augur.json`, { headers: { Accept: "application/json" } })).json(); draftsHere = !!(j && j.drafts && j.drafts.enabled); } catch (e) { draftsHere = false; }
73
79
  const TOKENS_FILE = path.join(os.homedir(), ".config", "augur", "tokens.json");
74
80
  function savedToken() {
75
81
  try { const t = JSON.parse(readFileSync(TOKENS_FILE, "utf8"))[host]; return t && t.token ? t : null; } catch (e) { return null; }
@@ -178,7 +184,17 @@ all[new URL(ORIGIN).host] = {
178
184
  writeFileSync(file, JSON.stringify(all, null, 2), { mode: 0o600 });
179
185
 
180
186
  log(`${C.ok}paired — publish access: ${saved.space === "*" ? "all spaces" : saved.space}${C.off}`);
181
- console.log(`ready \`augur publish\` will now use this token for ${ORIGIN}`);
187
+ // The next verb, spelled so it runs from THIS machine: `augur` after a global install, the
188
+ // package's own `npx` line otherwise — and the verb this workspace takes, which the door
189
+ // knows (drafts: open and land; a whole-tree instance: publish).
190
+ const verb = augurOnPath() ? "augur" : "npx @augurworks/augur";
191
+ if (draftsHere) {
192
+ console.log(`ready — \`${verb} open <opportunity>/<prototype>\` opens one prototype as a draft (live at once at its own address);`);
193
+ console.log(` edit the folder it makes, then \`${verb} land\` in that folder: the real URL moves.`);
194
+ } else {
195
+ console.log(`ready — \`${verb} publish\` will now use this token for ${ORIGIN}`);
196
+ }
197
+ if (verb !== "augur") console.log(`${C.dim}(\`augur\` is not on this machine's PATH; \`npm i -g @augurworks/augur\` puts it there for good.)${C.off}`);
182
198
  if (saved.expiresAt) {
183
199
  const days = Math.max(0, Math.round((Date.parse(saved.expiresAt) - Date.now()) / 86400000));
184
200
  console.log(`${C.dim}It expires in ${days} days (${saved.expiresAt.slice(0, 10)}). Run \`augur connect\` again then.${C.off}`);
@@ -107,7 +107,12 @@ export const hookCommand = (event, { onPath = false } = {}) =>
107
107
  export function augurOnPath() {
108
108
  try {
109
109
  const out = execFileSync(process.platform === "win32" ? "where" : "which", ["augur"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
110
- return out.trim().length > 0;
110
+ const found = out.trim().split(/\r?\n/)[0] || "";
111
+ // Inside an `npx` run the package's own bin dir is on PATH and `augur` resolves to a shim
112
+ // in the npx cache — gone the moment npx exits. A hook written as `augur hook pre` from
113
+ // there fails on every later edit (one cold agent's drafts never auto-saved, and its tool
114
+ // showed a "not found" on each write). That resolution counts as NOT on PATH.
115
+ return found.length > 0 && !/[\\/]_npx[\\/]/.test(found);
111
116
  } catch (e) { return false; }
112
117
  }
113
118
  const ours = (h) => !!(h && Array.isArray(h.hooks) && h.hooks.some((x) => x && typeof x.command === "string" && OURS_RE.test(x.command.trim())));
package/scripts/open.mjs CHANGED
@@ -9,7 +9,7 @@ import fs from "node:fs";
9
9
  import path from "node:path";
10
10
  import { target, buildStamp } from "./lib/store.mjs";
11
11
  import { unitClient, doOpen, unitPathFor } from "./lib/draft.mjs";
12
- import { installAdapters } from "./lib/adapters.mjs";
12
+ import { installAdapters, augurOnPath } from "./lib/adapters.mjs";
13
13
  import { normUnit } from "../src/unit-core.mjs";
14
14
 
15
15
  const log = (m) => console.error(`\x1b[35m[open]\x1b[0m ${m}`);
@@ -51,7 +51,7 @@ log(r.isNew ? `draft ${r.draftId} on ${unit} — a NEW prototype; ${dir} is empt
51
51
  // here (idempotent; `AUGUR_NO_ADAPTERS=1` skips it — the suite and CI set it).
52
52
  if (!process.env.AUGUR_NO_ADAPTERS) {
53
53
  for (const a of installAdapters()) {
54
- if (a.result === "installed") log(`${a.name}: editor hooks installed (${a.path})edits in a draft folder save on their own; edits to a shared checkout's prototypes are refused.`);
54
+ if (a.result === "installed") log(`${a.name}: a save hook is now in ${a.path} — after each edit inside a draft folder it runs \`augur hook post\`, which saves that draft to ${origin}; before an edit it refuses writes into a shared checkout's prototypes. It does nothing outside draft folders and talks to nothing else; \`augur hook remove\` takes it out.`);
55
55
  else if (a.result === "updated") log(`${a.name}: editor hooks updated (${a.path}).`);
56
56
  }
57
57
  }
@@ -60,4 +60,10 @@ if (r.others.length) {
60
60
  for (const o of r.others) log(` ${o.session || "someone"} (${o.active ? "active" : "idle"})`);
61
61
  log("nothing here stops you; if you both land, the second one syncs first.");
62
62
  }
63
+ // The next two steps, spelled so they run from this machine (`augur` only after a global
64
+ // install; the package's `npx` line otherwise).
65
+ const verb = augurOnPath() ? "augur" : "npx @augurworks/augur";
66
+ log(r.isNew
67
+ ? `next: write ${path.join(dir, "index.html")} (self-contained static HTML); it is live at the address below as soon as it saves. When it is ready: \`cd ${path.basename(dir)} && ${verb} land\` — the last line printed is the live URL.`
68
+ : `next: edit the files in ${dir}; every save is live at the address below. When it is ready: \`cd ${path.basename(dir)} && ${verb} land\` — the last line printed is the live URL.`);
63
69
  console.log(r.address);
package/src/_worker.js CHANGED
@@ -3748,7 +3748,7 @@ async function mintPublishToken(kv, tctx, u, { label = null, env = null } = {})
3748
3748
  * Self-contained, like the login and 404 pages beside it: this must render for somebody
3749
3749
  * whose terminal is already waiting, so it depends on no chrome bundle and no space.
3750
3750
  */
3751
- function connectPage(tctx, me) {
3751
+ function connectPage(tctx, me, origin) {
3752
3752
  const body = roleOf(me) === "viewer"
3753
3753
  ? `<h1>Connect a terminal</h1>
3754
3754
  <p>This account can look around but not publish, so it cannot approve a terminal.</p>
@@ -3761,6 +3761,10 @@ function connectPage(tctx, me) {
3761
3761
  <p class="warn">Only approve a code from a terminal you started or an assistant you
3762
3762
  are talking to right now. A code that arrives by mail, from a stranger, or out of
3763
3763
  the blue is not yours — do not type it.</p>
3764
+ <p>Where the code comes from: the terminal or assistant runs
3765
+ <code>npx @augurworks/augur connect --origin ${escapeHtml(origin || "")}</code>, this
3766
+ workspace's own command-line tool, and it shows the code. An assistant that has not
3767
+ shown you one yet can be told to run that.</p>
3764
3768
  <form id="pairf">
3765
3769
  <input id="pairc" autocomplete="off" autocapitalize="characters" spellcheck="false"
3766
3770
  placeholder="ABCD-EFGH" aria-label="Pairing code" />
@@ -9275,22 +9279,37 @@ function doorText(f) {
9275
9279
  const body = f.pairing.enabled
9276
9280
  ? `Run, on the machine that will publish:\n\n`
9277
9281
  + ` ${f.connect}\n\n`
9278
- + `It prints a link and a code. The owner of this workspace opens the link in a\n`
9282
+ + `It prints a link and a code. A member of this workspace opens the link in a\n`
9279
9283
  + `browser they are already signed in to and enters the code. The token lands on\n`
9280
- + `that machine, and every verb below uses it from then on.\n\n`
9281
- + `The CLI is this engine's own, open source at ${f.source} the same engine that\n`
9282
- + `serves this page; \`npm view @augurworks/augur repository\` names that repository.\n\n`
9283
- + `AGENTS: the approval is the person's act, in their browser — you cannot approve\n`
9284
- + `it from here and must not try; your part is to send them the line and collect\n`
9285
- + `the token afterwards. If your person reads your messages only after you finish\n`
9286
- + `your turn, run it with --no-wait: it prints the line and exits. Send them that\n`
9287
- + `line, end your turn, and run the same command again once they say they approved —\n`
9288
- + `the token is collected then, for that same code, and no second code is minted.\n`
9289
- + `Waiting inside one turn skips nothing; it only hides the code from them for the\n`
9290
- + `five minutes it lives.\n\n`
9284
+ + `that machine and nowhere else, and every verb below uses it from then on.\n`
9285
+ + `Approving a code lets that one terminal publish as the person who approved it;\n`
9286
+ + `everything it lands carries their name. A member whose role is viewer cannot\n`
9287
+ + `approve, and the page says so.\n\n`
9288
+ + `Members see this same instruction when signed in, at ${f.origin}/__connect and\n`
9289
+ + `under Help > Building so the person you are working for can read it there and\n`
9290
+ + `confirm it is this workspace's own, without knowing anything technical.\n\n`
9291
+ + `What the command is, for anyone who checks before running it: the CLI is this\n`
9292
+ + `engine's own, open source at ${f.source} the same engine that serves this page\n`
9293
+ + `(${f.origin}/_build.json names the engine commit). \`npm view @augurworks/augur repository\`\n`
9294
+ + `names that repository; \`npm pack @augurworks/augur\` lets you read it first. It is plain\n`
9295
+ + `JavaScript with no runtime dependencies; the pairing is scripts/connect.mjs and it\n`
9296
+ + `talks only to this origin's /__publish/_pair/ routes. It asks for no password, and\n`
9297
+ + `there is none to give it.\n\n`
9298
+ + `How the pairing goes when the person approving is not at the keyboard (an\n`
9299
+ + `assistant working for them): the approval is theirs, in their browser — nobody\n`
9300
+ + `else can do it, and the command does not try. Send them the line the command\n`
9301
+ + `prints, as it is. When they will read that message only after the current turn\n`
9302
+ + `ends, run \`connect --no-wait\`: it prints the line and exits. Once they say they\n`
9303
+ + `approved, run the same command again — it collects the token for that same code and\n`
9304
+ + `mints no second one. Waiting inside one turn skips nothing; it only hides the code\n`
9305
+ + `from them for the five minutes it lives.\n\n`
9291
9306
  + (f.drafts && f.drafts.enabled
9292
9307
  ? `You need no source tree here: \`augur open <opportunity>/<prototype>\` fetches that one\n`
9293
9308
  + `prototype into a folder of its own (see below). Do not clone the workspace first.\n\n`
9309
+ + `Until \`npm i -g @augurworks/augur\`, every verb runs as \`npx @augurworks/augur <verb>\`.\n`
9310
+ + `The first \`open\` on a machine also installs a save hook in the editor tool's\n`
9311
+ + `settings (it says which file): after each edit inside a draft folder it saves that\n`
9312
+ + `draft; it does nothing elsewhere, and \`augur hook remove\` takes it out.\n\n`
9294
9313
  : `With no source tree yet, \`npx @augurworks/augur clone --space ${f.workspace}\` then fetches\n`
9295
9314
  + `one (it reads the origin from the pairing).\n\n`)
9296
9315
  : `Device pairing is switched off on this workspace. Ask an admin for an invite;\n`
@@ -11922,7 +11941,7 @@ async function handleRequest(request, env, ctx, url, trace) {
11922
11941
  if (url.pathname === "/__connect" && tctx.DEVICE_PAIRING) {
11923
11942
  const who = tctx.USERS.length ? await identify(request, env, tctx.USERS, { sessionKeys: tctx.SESSION_KEYS, tctx }) : null;
11924
11943
  if (!who && tctx.USERS.length) return htmlResponse(loginPage(tctx, "/__connect", false, url.href), 200);
11925
- return htmlResponse(connectPage(tctx, who), 200);
11944
+ return htmlResponse(connectPage(tctx, who, url.origin), 200);
11926
11945
  }
11927
11946
  if (url.pathname.startsWith("/__publish/_pair/")) {
11928
11947
  // Identity is resolved here rather than reusing the gate's `me` below, because this
@@ -386,10 +386,16 @@ function helpDrawer(state) {
386
386
  <li>The design system is read-only from a prototype. Edit the skill's own source rather than copying its classes out.</li>
387
387
  </ul>
388
388
 
389
+ <h4>Working with an assistant</h4>
390
+ <ul>
391
+ <li>Give it this workspace's address and say what you want changed or made. It reads how to get in from this site and hands you a link and a short code.</li>
392
+ <li>Open the link here (<code>/__connect</code>) and type the code. That pairs its terminal to publish as you; everything it lands carries your name.</li>
393
+ <li>From then on it opens a prototype as a draft, edits it live at the draft's own address, and lands it when you say so. Viewers cannot approve a code.</li>
394
+ </ul>
395
+
389
396
  <h4>Comment loop <span class="gvhelp__tag">maintainer</span></h4>
390
397
  <ul>
391
- <li><code>npm run review --open</code> lists open threads.</li>
392
- <li>The agent fixes, replies, resolves in-thread. Put it on <code>/loop</code> to keep watching.</li>
398
+ <li>In a checkout, <code>npm run review --open</code> lists open threads; an agent fixes, replies and resolves in-thread.</li>
393
399
  <li>Not automated. You steer it.</li>
394
400
  </ul>${spaceHelpSections(state)}
395
401
  </section>