@augurworks/augur 0.15.1 → 0.15.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/INSTALL.md CHANGED
@@ -108,8 +108,9 @@ git clone https://github.com/andratwiro/augur.git augur
108
108
  The engine has no runtime dependencies — `login`, `publish`, `build` and `status` run on
109
109
  a bare clone with plain `node`. `npm install` is only needed for the Playwright-backed
110
110
  screenshot scripts. The commands below call the scripts by path
111
- (`node <parent>/augur/scripts/<name>.mjs`); `npm link` inside `augur/` gives you the
112
- shorter `augur <cmd>` form if you want it.
111
+ (`node <parent>/augur/scripts/<name>.mjs`), which works on a bare clone;
112
+ `npm i -g @augurworks/augur` gives you the shorter `augur <cmd>` form from the
113
+ published package instead.
113
114
 
114
115
  Never nest one instance's parent inside another's — offline mode would serve both
115
116
  instances' spaces at once.
package/README.md CHANGED
@@ -96,12 +96,14 @@ clones, so a slower connection moves the number and nothing else does — the
96
96
  engine itself is about two seconds from cold to serving.
97
97
 
98
98
  ```bash
99
- git clone https://github.com/andratwiro/augur.git
100
99
  git clone https://github.com/andratwiro/augur-space-fulla.git
101
100
  cd augur-space-fulla
102
- node ../augur/scripts/dev.mjs
101
+ npx @augurworks/augur dev
103
102
  ```
104
103
 
104
+ Or, with an engine clone beside the workspace instead of the package:
105
+ `git clone https://github.com/andratwiro/augur.git` and `node ../augur/scripts/dev.mjs`.
106
+
105
107
  There is no install step and no build step in that list, and that is not an
106
108
  omission: **the engine has no runtime dependencies**, so plain `node` is enough.
107
109
  What you get is the full shell — the gallery, the library, review mode, boards —
package/agents/README.md CHANGED
@@ -21,7 +21,7 @@ prototypes needs a **publish token**, and an agent gets one by device pairing, n
21
21
  asking anyone for a password:
22
22
 
23
23
  ```
24
- npx augur connect --origin https://<the workspace>
24
+ npx @augurworks/augur connect --origin https://<the workspace>
25
25
  ```
26
26
 
27
27
  It prints one line to relay — *ask the owner of this workspace to open `<link>` and
@@ -31,13 +31,13 @@ signed in to and types the code; the token lands in `~/.config/augur/tokens.json
31
31
  it finds no token, so inside a workspace tree nothing has to be done first. On a workspace
32
32
  that serves drafts (`/.well-known/augur.json` says `drafts.enabled`), the everyday verbs
33
33
  are `augur open` and `augur land` instead — see [drafts.md](./drafts.md). To get the
34
- tree from a hostname, pair first, then `npx augur clone --space <id>` — it reads the
34
+ tree from a hostname, pair first, then `npx @augurworks/augur clone --space <id>` — it reads the
35
35
  origin from the pairing.
36
36
 
37
- Not on npm yet? The engine clone sits next to every workspace that publishes:
38
- `node <engine>/scripts/cli.mjs connect --origin <origin>`. The instance says all of this
39
- itself at `GET /llms.txt` (and as data at `/.well-known/augur.json`); a signed-out
40
- request for an engine path answers `401` with the same facts.
37
+ The instance says all of this itself at `GET /llms.txt` (and as data at
38
+ `/.well-known/augur.json`); a signed-out request for an engine path answers `401` with
39
+ the same facts. `npm i -g @augurworks/augur` puts `augur` on the PATH for good; an engine
40
+ clone beside the workspace still works too (`node <engine>/scripts/cli.mjs <verb>`).
41
41
 
42
42
  Two things an agent never does: type a password into a terminal (`augur login` exists
43
43
  for CI, and says so), and try the `pass` field from a deploy shell's `identity.json` —
@@ -49,7 +49,6 @@ Read them by trigger, not up front:
49
49
  | When you are… | Read |
50
50
  |---|---|
51
51
  | asking what is current here, or what changed lately | [currency.md](./currency.md) |
52
- | starting work on a prototype somebody else might also be in | [working-marks.md](./working-marks.md) — read it BEFORE the first edit |
53
52
  | changing a prototype on a workspace that serves drafts | [drafts.md](./drafts.md) — open, edit, land |
54
53
  | shipping / going live on a workspace without drafts | [publishing.md](./publishing.md) |
55
54
  | acting on review comments | [review-feedback.md](./review-feedback.md) |
package/agents/drafts.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # Drafts — how a prototype is changed on a workspace that serves them
2
2
 
3
3
  **Does this apply here?** Ask the instance: `GET /.well-known/augur.json` carries
4
- `drafts.enabled`. `true` → this document. `false` [publishing.md](./publishing.md), and
5
- `augur ship` as before. `augur ship` on a drafts workspace refuses and points here.
4
+ `drafts.enabled`. `true` → this document; `augur publish` refuses there and points here.
5
+ `false` [publishing.md](./publishing.md), which publishes a whole tree.
6
6
 
7
7
  ## The whole day, in four lines
8
8
 
@@ -31,7 +31,7 @@ you land. Two agents opening the same prototype get two drafts, are both told ab
31
31
  other, and both work — nothing locks, nobody waits.
32
32
 
33
33
  `augur open` prints who else has it open. That is the whole coordination step; there is
34
- nothing to leave and nothing to clear (working marks are for workspaces without drafts).
34
+ nothing to leave and nothing to clear.
35
35
 
36
36
  ## Editing
37
37
 
@@ -80,13 +80,12 @@ you do not want (its saves stay on the instance for a while; nothing else is tou
80
80
  | `stale-draft` | another process saved to this same draft | `augur sync`, then `augur save` |
81
81
  | `would-unpublish` | the draft has no files (the folder is empty) | check the folder; a deletion is its own verb |
82
82
  | `not-a-prototype-folder` / `reserved-folder` | the path is not `<opportunity>/<prototype>` | name the prototype folder |
83
- | `units-not-configured` | this instance does not serve drafts | use `augur ship` — see publishing.md |
83
+ | `units-not-configured` | this instance does not serve drafts | `augur publish` — see publishing.md |
84
84
  | no publish token | this machine is not paired | `augur connect` (never a password) |
85
85
  | unreachable | the instance could not be reached | nothing is lost; the next save carries every change since |
86
86
 
87
87
  ## What you never do
88
88
 
89
- - Ship a whole tree to a drafts workspace. `ship` refuses; `--legacy` exists for one
90
- release and is not for you.
89
+ - Publish a whole tree to a drafts workspace. `publish` refuses there, and `ship` is gone.
91
90
  - Wait for, or refuse over, somebody else's draft. Both work; the second landing syncs.
92
91
  - Hand over a path on disk as "done". Done is the URL `land` printed.
@@ -3,7 +3,7 @@
3
3
  ## Getting in, if you are an agent
4
4
 
5
5
  You do not need a login. Publishing needs a **publish token**, and the way to get one
6
- is device pairing: `npx augur connect --origin <the workspace>` prints a link and a
6
+ is device pairing: `npx @augurworks/augur connect --origin <the workspace>` prints a link and a
7
7
  code, the owner enters the code in a browser they are already signed in to, and the
8
8
  token lands on this machine. Nobody's password is asked for, typed or stored. The
9
9
  instance says the same at `GET /llms.txt`. The full shape is in [README.md](./README.md)
@@ -12,7 +12,7 @@ That `file://`-ability is a property of the *files* — it keeps them portable a
12
12
  buildless, nothing more. It is not a hand-off channel: publish the change and
13
13
  share the live URL (see `agents/publishing.md`). The only acceptable local
14
14
  stand-in, and only when publishing is genuinely impossible, is the real shell
15
- (`node ../augur/scripts/dev.mjs`), said out loud to be exactly that — local
15
+ (`augur dev`), said out loud to be exactly that — local
16
16
  only, not shipped, nobody else can see it.
17
17
 
18
18
  Neither is ever offered as a choice. An agent doesn't ask "should I publish
@@ -1,20 +1,22 @@
1
1
  # Publishing — how work in a workspace goes live
2
2
 
3
- **Where the workspace serves drafts, this is the legacy path** — `augur ship` refuses
4
- there and points at `augur open` / `augur land`; read [drafts.md](./drafts.md). The
3
+ **Where the workspace serves drafts, this document does not apply** — `augur publish`
4
+ refuses there and points at `augur open` / `augur land`; read [drafts.md](./drafts.md). The
5
5
  instance says which it is at `/.well-known/augur.json` (`drafts.enabled`). Everything
6
- below is for a workspace that does not serve drafts yet.
6
+ below is for a self-hosted instance without a unit store, which still publishes a tree.
7
7
 
8
- **One command, every time:**
8
+ **The command, every time your work is ready:**
9
9
 
10
10
  ```
11
- node ../augur/scripts/ship.mjs # commit + publish + push, then print the live URL
12
- node ../augur/scripts/ship.mjs -m "…" # with your own commit message
11
+ augur publish # the working tree goes live; the last line is the URL
12
+ git add -A && git commit -m "…" && git push # then keep the repository true, yourself
13
13
  ```
14
14
 
15
- `augur ship` is the same thing, and only works if someone ran `npm link` in the
16
- engine clone on this machine. The `node …` form always works prefer it in
17
- instructions, or an agent following them hits "command not found" and improvises.
15
+ `augur` is the npm package `@augurworks/augur` (`npm i -g @augurworks/augur`, or
16
+ `npx @augurworks/augur publish` for a one-off). On a machine that only has an engine
17
+ clone beside the workspace, `node ../augur/scripts/publish.mjs` is the same command.
18
+ (`augur ship`, which did the commit and the push for you, is retired: where drafts are
19
+ served there is nothing to commit, and elsewhere the three steps are the two lines above.)
18
20
 
19
21
  Agents: this is the default, and it runs every time you finish a piece of work —
20
22
  not once at the end of a session. Hand the human the URL it prints: the live
@@ -29,7 +31,9 @@ checkout, and telling them to run `git pull` is a support ticket you created.
29
31
  Assume **none of them know what git is** — that is the design target, not a
30
32
  worst case.
31
33
 
32
- So: keep the engine clone current yourself. `publish` fast-forwards it
34
+ So: keep the engine current yourself. A package install updates with
35
+ `npm i -g @augurworks/augur@latest`, and `publish` prints exactly that line when the
36
+ instance speaks a newer protocol than the package. A clone `publish` fast-forwards
33
37
  automatically when the instance speaks a newer publish protocol (clean tree,
34
38
  `--ff-only`, once per run), and re-runs itself — you will see
35
39
  `engine updated <a> → <b>`. When it cannot (uncommitted changes in the engine
@@ -173,10 +177,10 @@ site doesn't show your change, the answer is always the same: it wasn't publishe
173
177
 
174
178
  ## The command
175
179
 
176
- From the workspace repo's root (the engine clone must sit next to it):
180
+ From the workspace repo's root:
177
181
 
178
182
  ```
179
- node ../augur/scripts/publish.mjs # or: augur publish
183
+ augur publish # or, clone-only: node ../augur/scripts/publish.mjs
180
184
  ```
181
185
 
182
186
  - Infers the workspace from the working directory; `--dry-run` and `--engine`
@@ -225,7 +229,11 @@ them.
225
229
  purpose), say so:
226
230
 
227
231
  ```
228
- node ../augur/scripts/ship.mjs --allow-unpublish
232
+ <<<<<<< HEAD
233
+ node ../augur/scripts/publish.mjs --allow-unpublish
234
+ =======
235
+ augur publish --allow-unpublish
236
+ >>>>>>> 01da41b5 (npm: the engine speaks as a package — hooks, self-update and every contract)
229
237
  ```
230
238
 
231
239
  Adding pages is never blocked; only losing them is.
@@ -244,8 +252,7 @@ The first publish on a machine pairs it with your browser, on its own:
244
252
 
245
253
  Open the link in a browser where you are already signed in to the site, type the
246
254
  code, and the publish carries on. No password goes near the terminal, the shell
247
- history, or an agent transcript. `node ../augur/scripts/connect.mjs` (`augur connect`)
248
- runs that step by itself when you want it ahead of time. Agents: relay the link and
255
+ history, or an agent transcript. `augur connect` runs that step by itself when you want it ahead of time. Agents: relay the link and
249
256
  the code to your human; that is the whole hand-off.
250
257
 
251
258
  `augur login` (web email + password) still exists for two cases: CI and scripts,
@@ -299,7 +306,7 @@ responses agree before declaring a chrome bug.
299
306
 
300
307
  ## Local preview (no publish involved)
301
308
 
302
- `node ../augur/scripts/dev.mjs` from the workspace root runs the full site shell
309
+ `augur dev` from the workspace root runs the full site shell
303
310
  locally — login, rail, overlays, canvas, the same experience the live site
304
311
  gives (login `dev@local` / `dev` when no identity file is around). This is the
305
312
  **only** acceptable stand-in when you genuinely cannot publish, and always say
package/build.js CHANGED
@@ -1875,28 +1875,6 @@ const PAGE_CSS = `
1875
1875
  .card-opp.is-stale .preview :is(iframe, .preview-img, .preview-ph) { transition: none; }
1876
1876
  }
1877
1877
 
1878
- /* ---- Working mark ("somebody is in here right now"; MARKS_JS stamps it) ----
1879
- The card-side face of an AGENT protocol: a tool leaves a mark before it starts
1880
- editing so the next tool reads it. This badge is the byproduct, and it is drawn
1881
- to be exactly that — it refuses nothing, covers nothing clickable, and takes
1882
- itself off the card the second the mark's TTL passes.
1883
- Top-RIGHT, because the status glyph owns bottom-left and the two must never
1884
- cover each other on a small card. */
1885
- .mark-badge {
1886
- position: absolute; right: 8px; top: 8px; z-index: 3;
1887
- display: inline-flex; align-items: center; gap: 6px; max-width: calc(100% - 16px);
1888
- padding: 3px 9px 3px 4px; border-radius: 999px;
1889
- background: #fff; box-shadow: 0 2px 8px -1px rgba(16,24,40,0.32);
1890
- font-size: 11.5px; font-weight: 600; letter-spacing: -0.005em; color: #101828;
1891
- pointer-events: none; /* a note is not a control */
1892
- }
1893
- .mark-badge__who {
1894
- flex: none; width: 16px; height: 16px; border-radius: 50%;
1895
- display: inline-grid; place-items: center;
1896
- font-size: 8px; font-weight: 700; letter-spacing: 0; color: #fff;
1897
- background: var(--faint);
1898
- }
1899
- .mark-badge__text { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
1900
1878
  /* Draft chips — one per open draft on a prototype card, a count on a folder card.
1901
1879
  Bottom-left so they never fight the mark badge (top-right) or the status chip. */
1902
1880
  .draft-chips {
@@ -3095,80 +3073,6 @@ const FACE_JS = `
3095
3073
  wire();
3096
3074
  })();`;
3097
3075
 
3098
- // Working marks, the card-side face of an AGENT protocol (`F-presence-marks`).
3099
- //
3100
- // A tool leaves a mark on a path before it starts editing so the next tool reads it; this
3101
- // badge is the BYPRODUCT of that, not the point, and it is written to stay one. It reads
3102
- // (`GET /__marks`) and never writes: a person with a tab open is not running a work-start
3103
- // step, and inventing a mark for them would put a claim in the store nobody made.
3104
- //
3105
- // ⚠️ IT REFUSES NOTHING. It adds a note to a card. It does not disable the link, dim the
3106
- // card, gate the menu or set anything another script could branch on to block — a mark
3107
- // that stopped a click would be a lock, which is the one thing this feature is not.
3108
- //
3109
- // EXACT PATHS ONLY, deliberately. The CLI matches a mark against a path by containment,
3110
- // because before you start work a mark on the folder above is the thing worth knowing. A
3111
- // CARD is a published unit, so stamping a folder's mark onto each prototype inside it
3112
- // would put "someone is working on this" on five cards nobody has touched. A folder has
3113
- // its own card on the landing grid and gets the badge there.
3114
- //
3115
- // One fetch per page load, only on pages that have cards, and each badge takes itself off
3116
- // at its own expiry — so a lapsed mark cannot outlive its meaning on an open tab, which
3117
- // is the same guarantee the instance makes to every other reader.
3118
- const MARKS_JS = `
3119
- (function(){
3120
- function norm(p){
3121
- var s = String(p == null ? '' : p).trim();
3122
- if(!s) return '';
3123
- try { s = decodeURIComponent(s); } catch(e){}
3124
- s = s.replace(/^\\.\\//, '').replace(/\\/{2,}/g, '/');
3125
- if(!s || s === '/') return '/';
3126
- return '/' + s.replace(/^\\/+/, '').replace(/\\/+$/, '') + '/';
3127
- }
3128
- function pathOf(card){
3129
- var a = card.querySelector('a.preview-link[href], a[href]');
3130
- if(!a) return '';
3131
- try { return norm(new URL(a.getAttribute('href'), location.href).pathname); } catch(e){ return ''; }
3132
- }
3133
- function badge(card, m){
3134
- if(card.dataset.markDone) return;
3135
- card.dataset.markDone = '1';
3136
- var host = card.querySelector('.preview') || card;
3137
- var b = document.createElement('span');
3138
- b.className = 'mark-badge';
3139
- var who = document.createElement('span');
3140
- who.className = 'mark-badge__who';
3141
- who.textContent = m.initials || '?';
3142
- if(m.color) who.style.background = m.color;
3143
- var t = document.createElement('span');
3144
- t.className = 'mark-badge__text';
3145
- // "Someone" when the id behind the mark answers to nobody on the roster — a token
3146
- // labelled by hand, or a person who has since left. Never a guess at a name.
3147
- t.textContent = (m.by || 'Someone') + ' is working on this';
3148
- b.appendChild(who); b.appendChild(t);
3149
- host.appendChild(b);
3150
- var left = Math.max(0, +m.expiresIn || 0);
3151
- setTimeout(function(){ b.remove(); delete card.dataset.markDone; }, left);
3152
- }
3153
- function wire(){
3154
- var cards = [].slice.call(document.querySelectorAll('.card-proto, .card-opp'));
3155
- if(!cards.length) return;
3156
- fetch('/__marks', { credentials: 'same-origin', headers: { Accept: 'application/json' } })
3157
- .then(function(r){ return r.ok ? r.json() : null; })
3158
- .then(function(d){
3159
- if(!d || !d.marks || !d.marks.length) return;
3160
- var by = {};
3161
- d.marks.forEach(function(m){ if(m && m.path) by[norm(m.path)] = m; });
3162
- cards.forEach(function(card){
3163
- var m = by[pathOf(card)];
3164
- if(m) badge(card, m);
3165
- });
3166
- })
3167
- .catch(function(){ /* a note may never be the reason a gallery looks broken */ });
3168
- }
3169
- window.__gvMarksWire = wire;
3170
- wire();
3171
- })();`;
3172
3076
 
3173
3077
  // Draft chips — the gallery's face of drafts that land (docs/drafts-that-land.md §5).
3174
3078
  //
@@ -6178,7 +6082,7 @@ const CHROME_CSS_BODY = `${FONT_CSS}${PAGE_CSS}${NAV_CSS}${TABBAR_CSS}`;
6178
6082
  const CHROME_JS_BODY = [
6179
6083
  CAROUSEL_JS, chromeScript(), STATUS_JS, CURRENCY_JS, COMP_STATUS_JS, CARD_MENU_JS,
6180
6084
  PINS_JS, PROFILE_JS, SETTINGS_JS, NEWCANVAS_JS, SPACE_JS, WORKSPACES_JS, TABBAR_JS(),
6181
- RESEARCH_JS, FACE_JS, MARKS_JS, DRAFTS_JS,
6085
+ RESEARCH_JS, FACE_JS, DRAFTS_JS,
6182
6086
  ].join("\n;\n") + "\n;\n" +
6183
6087
  // Register the service worker (P0), shipped inside the already-cached bundle so
6184
6088
  // it costs pages zero inline bytes.
package/changelog.md CHANGED
@@ -20,6 +20,11 @@ The date is the day the update went live. The page works out "Today / 3 days ago
20
20
 
21
21
  ---
22
22
 
23
+ ## 2026-09-06 — Augur installs from npm
24
+ Your agent can now fetch the Augur tool with one line, `npx @augurworks/augur`, on any
25
+ machine with Node. Nothing to clone first: the workspace's own front door prints that
26
+ line, and the rest is the code you approve in your browser.
27
+
23
28
  ## 2026-09-03 — Your agent finds the door on its own
24
29
  Point an agent at a workspace address and it now learns how to get in without
25
30
  asking you for a password: the site tells it to run `augur connect`, you approve a
package/package.json CHANGED
@@ -1,20 +1,6 @@
1
1
  {
2
2
  "name": "@augurworks/augur",
3
- "version": "0.15.1",
4
- "description": "A prototype and research repository for product teams. Real, clickable prototypes and the design system they are built from, on one site with login, comments and live boards on top. Underneath it is all git and static HTML.",
5
- "license": "MIT",
6
- "repository": {
7
- "type": "git",
8
- "url": "git+https://github.com/andratwiro/augur.git"
9
- },
10
- "homepage": "https://github.com/andratwiro/augur#readme",
11
- "publishConfig": {
12
- "access": "public"
13
- },
14
- "type": "module",
15
- "bin": {
16
- "augur": "scripts/cli.mjs"
17
- },
3
+ "version": "0.15.3",
18
4
  "files": [
19
5
  "scripts/",
20
6
  "build.js",
@@ -31,6 +17,12 @@
31
17
  "TRADEMARKS.md",
32
18
  "changelog.md"
33
19
  ],
20
+ "license": "MIT",
21
+ "description": "A prototype and research repository for product teams. Real, clickable prototypes and the design system they are built from, on one site with login, comments and live boards on top. Underneath it is all git and static HTML.",
22
+ "type": "module",
23
+ "bin": {
24
+ "augur": "scripts/cli.mjs"
25
+ },
34
26
  "scripts": {
35
27
  "test": "node --test \"test/*.test.mjs\"",
36
28
  "smoke": "node scripts/smoke-invite.mjs",
@@ -53,5 +45,13 @@
53
45
  },
54
46
  "devDependencies": {
55
47
  "playwright": "^1.60.0"
56
- }
48
+ },
49
+ "publishConfig": {
50
+ "access": "public"
51
+ },
52
+ "repository": {
53
+ "type": "git",
54
+ "url": "git+https://github.com/andratwiro/augur.git"
55
+ },
56
+ "homepage": "https://github.com/andratwiro/augur#readme"
57
57
  }
package/scripts/cli.mjs CHANGED
@@ -4,13 +4,11 @@
4
4
  // augur dev full local shell in the current space folder (or workspace root)
5
5
  // augur build compose dist/ once
6
6
  // augur deploy build + direct-upload the whole site (see deploy.mjs)
7
- // augur ship commit + publish + push — the default way a change goes out
8
7
  // augur publish publish only, without committing or pushing (see publish.mjs)
9
8
  // augur fork copy a published artifact to a new URL, moving no bytes (see fork.mjs)
10
9
  // augur status what is live vs what your clones hold (see status.mjs)
11
10
  // augur refine render every component, photograph it, measure it against the
12
11
  // original, and report a pass-rate nobody can assert (see refine.mjs)
13
- // augur mark say what you are about to work on; read what everyone else is
14
12
  // augur open open one prototype into a folder of its own, live at once
15
13
  // augur save push every changed file in this draft folder
16
14
  // augur land replace the prototype's main with this draft
@@ -40,13 +38,11 @@ const map = {
40
38
  dev: "dev.mjs",
41
39
  offline: "offline.mjs",
42
40
  deploy: "deploy.mjs",
43
- ship: "ship.mjs",
44
41
  publish: "publish.mjs",
45
42
  // The one publishing verb that needs no tree: two paths and a token (see fork.mjs).
46
43
  fork: "fork.mjs",
47
44
  status: "status.mjs",
48
45
  refine: "refine.mjs",
49
- mark: "mark.mjs",
50
46
  // Drafts that land (docs/drafts-that-land.md): one prototype, one folder, live at once.
51
47
  open: "open.mjs",
52
48
  save: "save.mjs",
@@ -83,8 +79,18 @@ const map = {
83
79
  canon: "canon.mjs",
84
80
  build: path.join("..", "build.js"),
85
81
  };
82
+ // Two verbs that are gone, answered rather than dropped: a person or an agent following an
83
+ // older note lands on one sentence saying where the work went, not on "unknown command".
84
+ if (sub === "ship") {
85
+ console.error("augur ship is retired. A prototype is changed by opening it: `augur open <opportunity>/<prototype>`, edit, `augur land` (engine agents/drafts.md). A workspace without a unit store still publishes a tree with `augur publish`.");
86
+ process.exit(1);
87
+ }
88
+ if (sub === "mark") {
89
+ console.error("augur mark is retired: a draft IS the mark. `augur open <prototype>` tells you who else has it open, and `augur status` lists what is open on this machine.");
90
+ process.exit(1);
91
+ }
86
92
  if (!map[sub]) {
87
- console.error("usage: augur <init|ship|dev|offline|build|deploy|publish|fork|status|canon|refine|mark|open|save|land|sync|close|read|watch|hook|clone|pull|export|restore|migrate|bundle-rekey|identity-rekey|adopt|freeze|thaw|connect|login> [options]");
93
+ console.error("usage: augur <init|dev|offline|build|deploy|publish|fork|status|canon|refine|open|save|land|sync|close|read|watch|hook|clone|pull|export|restore|migrate|bundle-rekey|identity-rekey|adopt|freeze|thaw|connect|login> [options]");
88
94
  process.exit(sub ? 1 : 0);
89
95
  }
90
96
  const child = spawn(process.execPath, [path.join(SCRIPTS, map[sub]), ...rest], {
package/scripts/clone.mjs CHANGED
@@ -68,7 +68,6 @@ import {
68
68
  resolveUnit, unitFilesFromManifest, skillDirsReferenced, skillFilesFromManifest,
69
69
  rerootHtml, residualFindings, isText, isSkillInternal,
70
70
  } from "./lib/graduate.mjs";
71
- import { fetchMarks, marksOverlap, markLine } from "./lib/marks.mjs";
72
71
 
73
72
  const C = { dim: "\x1b[2m", warn: "\x1b[33m", bad: "\x1b[31m", ok: "\x1b[32m", off: "\x1b[0m" };
74
73
  const log = (m) => console.log(`\x1b[35m[clone]\x1b[0m ${m}`);
@@ -332,25 +331,6 @@ async function main() {
332
331
  console.log(` ${C.warn}conflict${C.off} ${c.path} ${C.dim}${c.why}${C.off}`);
333
332
  }
334
333
 
335
- // ── WHO IS ALREADY IN HERE (`F-presence-marks`) ───────────────────────────
336
- //
337
- // A pull is a work-start: files are about to be written into a tree somebody is about to
338
- // edit. So the marks on the paths ABOUT TO BE TOUCHED are printed before the first byte
339
- // lands, which is the only moment the information can still change what happens next.
340
- //
341
- // ⚠️ IT NEVER REFUSES AND NEVER CHANGES THE EXIT CODE. A mark is a note, not a lock, and
342
- // a pull that stopped for one would be a lock with extra steps. Read on a URL rather than
343
- // a source path: a mark names the published unit, which is what both sides can agree on.
344
- const touching = [...plan.write, ...plan.conflict];
345
- if (touching.length) {
346
- const marks = (await fetchMarks(req))
347
- .filter((m) => touching.some((f) => marksOverlap(f.url, m.path)));
348
- if (marks.length) {
349
- console.log(`\n ${C.warn}somebody is working on paths this pull touches${C.off}`);
350
- for (const m of marks) console.log(` ${markLine(m)}`);
351
- console.log(` ${C.dim}Nothing here is blocked. It is worth knowing before you edit the same folder.${C.off}\n`);
352
- }
353
- }
354
334
  if (DRY) {
355
335
  log(`${C.dim}dry run — would write ${plan.write.length}, leave ${plan.skip.length} identical, ${plan.conflict.length} conflict(s)${C.off}`);
356
336
  process.exit(plan.conflict.length ? 2 : 0);
@@ -7,7 +7,7 @@
7
7
  * transcript. This asks for neither: it prints a code, you type that code into a browser
8
8
  * that already has your session, and the token approval mints comes back here.
9
9
  *
10
- * npx augur connect [--origin https://your.site]
10
+ * npx @augurworks/augur connect [--origin https://your.site]
11
11
  *
12
12
  * `augur login` stays for CI and scripts, where there is no browser to type into.
13
13
  *
package/scripts/init.mjs CHANGED
@@ -96,7 +96,7 @@ const html = `<!doctype html>
96
96
  and ships exactly as written.</p>
97
97
  <p>When it works, ship it: on a workspace that serves drafts,
98
98
  <code>augur open ${project}/${proto}</code> then <code>augur land</code>; otherwise
99
- <code>augur ship</code>. Either way you get the live URL.</p>
99
+ <code>augur publish</code>. Either way you get the live URL.</p>
100
100
  </main>
101
101
  </body>
102
102
  </html>
@@ -119,4 +119,4 @@ log(` space.json`);
119
119
  log(` ${rel}`);
120
120
  if (wroteCanon) log(` CANON.md`);
121
121
  if (!origin) log(`no siteOrigin set — add one to space.json (or pass --origin) so connect/login/publish work from this clone.`);
122
- log(`next: edit the prototype, then \`augur land\` (a workspace that serves drafts) or \`augur ship\`.`);
122
+ log(`next: edit the prototype, then \`augur land\` (a workspace that serves drafts) or \`augur publish\`.`);
@@ -16,12 +16,16 @@ import fs from "node:fs";
16
16
  import os from "node:os";
17
17
  import path from "node:path";
18
18
  import { fileURLToPath } from "node:url";
19
+ import { execFileSync } from "node:child_process";
19
20
 
20
21
  // What identifies OUR entries in a settings file: the quoted script name and the event,
21
22
  // exactly as `hookCommand` spells them — so an entry survives the engine moving on disk
22
23
  // (the path changes, the tail does not) and nobody else's hook is ever mistaken for ours.
23
24
  export const HOOK_TAG = 'hook.mjs"';
24
- const OURS_RE = /hook\.mjs" (?:pre|post)$/;
25
+ // Two spellings are ours: the clone-era `node "<path>/hook.mjs" pre` and the package-era
26
+ // `augur hook pre`. Both are matched so an entry written by an older engine is replaced
27
+ // on the next `augur open`, never duplicated.
28
+ const OURS_RE = /(?:hook\.mjs" |(?:^|\s)augur hook )(?:pre|post)$/;
25
29
  export const HOOK_SCRIPT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "hook.mjs");
26
30
  export const UNIT_HOME_RE = /^(?:([^/]+)\/prototypes\/([^/]+)|(playground)\/([^/]+))(?:\/|$)/;
27
31
 
@@ -90,7 +94,22 @@ export const ADAPTERS = Object.freeze([
90
94
  }),
91
95
  ]);
92
96
 
93
- export const hookCommand = (event) => `node "${HOOK_SCRIPT}" ${event}`;
97
+ /**
98
+ * The command a tool runs for one of our hooks. With the package installed globally
99
+ * (`npm i -g @augurworks/augur`) `augur` resolves on PATH and the hook says so, because
100
+ * the absolute path of a global install moves on every upgrade. Otherwise — a clone, or
101
+ * a one-off `npx` run — the absolute path to this engine's hook.mjs is the one thing
102
+ * guaranteed to exist.
103
+ */
104
+ export const hookCommand = (event, { onPath = false } = {}) =>
105
+ onPath ? `augur hook ${event}` : `node "${HOOK_SCRIPT}" ${event}`;
106
+ /** Whether `augur` resolves on this machine's PATH. Asked once, at install time. */
107
+ export function augurOnPath() {
108
+ try {
109
+ const out = execFileSync(process.platform === "win32" ? "where" : "which", ["augur"], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] });
110
+ return out.trim().length > 0;
111
+ } catch (e) { return false; }
112
+ }
94
113
  const ours = (h) => !!(h && Array.isArray(h.hooks) && h.hooks.some((x) => x && typeof x.command === "string" && OURS_RE.test(x.command.trim())));
95
114
 
96
115
  export function mergeHooks(settings, matcher, commands) {
@@ -146,7 +165,8 @@ function applyToAdapters({ home = os.homedir(), dryRun = false } = {}, decide) {
146
165
  /** Install the two hooks for every tool present on this machine. Idempotent; keeps every other hook. */
147
166
  export function installAdapters(opts = {}) {
148
167
  return applyToAdapters(opts, (a, cur) => {
149
- const r = mergeHooks(cur, a.matcher, { pre: hookCommand("pre"), post: hookCommand("post") });
168
+ const onPath = typeof opts.onPath === "boolean" ? opts.onPath : augurOnPath();
169
+ const r = mergeHooks(cur, a.matcher, { pre: hookCommand("pre", { onPath }), post: hookCommand("post", { onPath }) });
150
170
  const had = !!(cur && cur.hooks && Object.values(cur.hooks).some((l) => Array.isArray(l) && l.some(ours)));
151
171
  return { ...r, did: had ? "updated" : "installed" };
152
172
  });
@@ -28,6 +28,18 @@ const MIME = {
28
28
  export const mimeOf = (name) => MIME[path.extname(name).slice(1).toLowerCase()] || "application/octet-stream";
29
29
  export const hashBytes = (buf) => createHash("sha256").update(buf).digest("hex");
30
30
  export const relOf = (unit, urlPath) => urlPath.slice(unit.length);
31
+ /**
32
+ * A REPO folder or a URL, as the unit path it publishes to: `<project>/prototypes/<name>`
33
+ * is the nesting a space clone keeps, served at `/<project>/<name>/`. An agent has just been
34
+ * looking at the folder, so it is the spelling it will type.
35
+ */
36
+ export function unitPathFor(input) {
37
+ const s = String(input == null ? "" : input).trim().slice(0, 300).replace(/\/prototypes\//g, "/");
38
+ if (!s) return "";
39
+ const t = s.replace(/^\.\//, "").replace(/\/{2,}/g, "/");
40
+ if (!t || t === "/") return "/";
41
+ return `/${t.replace(/^\/+/, "").replace(/\/+$/, "")}/`;
42
+ }
31
43
  export const urlOf = (unit, rel) => unit + rel;
32
44
 
33
45
  export function scanFolder(dir) {
package/scripts/open.mjs CHANGED
@@ -8,8 +8,7 @@
8
8
  import fs from "node:fs";
9
9
  import path from "node:path";
10
10
  import { target, buildStamp } from "./lib/store.mjs";
11
- import { markPathFor } from "./lib/marks.mjs";
12
- import { unitClient, doOpen } from "./lib/draft.mjs";
11
+ import { unitClient, doOpen, unitPathFor } from "./lib/draft.mjs";
13
12
  import { installAdapters } from "./lib/adapters.mjs";
14
13
  import { normUnit } from "../src/unit-core.mjs";
15
14
 
@@ -21,7 +20,7 @@ const positional = argv.filter((a, i) => !a.startsWith("--") && !(i > 0 && argv[
21
20
 
22
21
  const raw = positional[0];
23
22
  if (!raw) die("name a prototype: `augur open <opportunity>/<prototype>` (a folder path works too).");
24
- const unit = normUnit(markPathFor(raw) || raw);
23
+ const unit = normUnit(unitPathFor(raw) || raw);
25
24
  if (!unit) die(`"${raw}" is not a prototype path.`);
26
25
  let origin, token;
27
26
  try { ({ origin, token } = target({ needToken: true })); } catch (e) { die(e.message); }
@@ -25,6 +25,7 @@ import { readFile } from "node:fs/promises";
25
25
  import path from "node:path";
26
26
  import { fileURLToPath } from "node:url";
27
27
  import { findShellDir, deployConfig, originHost } from "./lib/instance.mjs";
28
+ import { draftsServed } from "./lib/draft.mjs";
28
29
  import { composePublish, filterLitter, unitPaths } from "./lib/publish-compose.mjs";
29
30
  import { collectEvidence } from "./lib/publish-evidence.mjs";
30
31
  import { stripVolatileHead } from "./lib/publish-conflict.mjs";
@@ -115,6 +116,17 @@ let ORIGIN = (process.env.AUGUR_ORIGIN || DEPLOY_ENV.AUGUR_ORIGIN ||
115
116
  deployConfig(ROOT, originHost(cwdSpaceOrigin)).siteOrigin || cwdSpaceOrigin || "")
116
117
  .replace(/\/+$/, "");
117
118
  if (!ORIGIN) die("no target origin — set AUGUR_ORIGIN, or add \"siteOrigin\" to space.json.");
119
+ // Where the instance serves drafts, a TREE is not what goes live any more: a prototype is
120
+ // changed by opening it and landing it (docs/drafts-that-land.md), and publishing a whole
121
+ // checkout there would put every session's half-done work on the site at once. The engine
122
+ // chrome (`--engine`) is not content and still goes this way.
123
+ if (!ENGINE_ONLY && await draftsServed(ORIGIN)) {
124
+ die(`${ORIGIN} serves drafts, so a prototype is changed by opening it, not by publishing a tree:\n\n` +
125
+ ` augur open <opportunity>/<prototype> # a folder of its own, live at once at its draft address\n` +
126
+ ` …edit; every save is live there…\n` +
127
+ ` augur land # the real URL moves; the last line printed is the live URL\n\n` +
128
+ `A prototype that does not exist yet: augur open --new <opportunity>/<name>. Read agents/drafts.md in the engine clone.`);
129
+ }
118
130
  // A WORKSPACE THAT MOVED answers its old address with a redirect. A checkout that still
119
131
  // names the old address (space.json not yet pulled, a stale AUGUR_ORIGIN) would otherwise
120
132
  // POST through that redirect and read the answer as a bad token. One GET with redirects
@@ -642,6 +654,13 @@ function selfUpdate(why) {
642
654
  if (NO_SELF_UPDATE || selfUpdateTried || process.env.AUGUR_SELF_UPDATED === "1") return false;
643
655
  selfUpdateTried = true;
644
656
  const git = (...a) => execFileSync("git", ["-C", ROOT, ...a], { encoding: "utf8", stdio: ["ignore", "pipe", "pipe"] }).trim();
657
+ // A package install (`npm i -g @augurworks/augur`, or an `npx` run) has no clone to
658
+ // fast-forward. Say the one line that updates it, in the same place the clone would
659
+ // have updated itself, and let the caller report the skew.
660
+ if (!existsSync(path.join(ROOT, ".git"))) {
661
+ log(`this engine is a package install and is behind what the instance speaks (${why}) — update it: npm i -g @augurworks/augur@latest`);
662
+ return false;
663
+ }
645
664
  try {
646
665
  if (git("rev-parse", "--is-inside-work-tree") !== "true") return false;
647
666
  if (git("status", "--porcelain")) {