@augurworks/augur 0.15.2 → 0.15.4
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 +3 -2
- package/README.md +4 -2
- package/agents/README.md +7 -6
- package/agents/drafts.md +14 -6
- package/agents/prototype-contract.md +1 -1
- package/agents/publishing.md +19 -16
- package/build.js +1 -97
- package/package.json +1 -1
- package/scripts/cli.mjs +11 -5
- package/scripts/clone.mjs +0 -20
- package/scripts/connect.mjs +57 -4
- package/scripts/hook.mjs +2 -1
- package/scripts/init.mjs +2 -2
- package/scripts/land.mjs +4 -1
- package/scripts/lib/adapters.mjs +23 -3
- package/scripts/lib/draft.mjs +76 -8
- package/scripts/no-tenant-globals.mjs +16 -0
- package/scripts/open.mjs +10 -7
- package/scripts/publish.mjs +19 -0
- package/scripts/read.mjs +13 -6
- package/scripts/save.mjs +3 -1
- package/scripts/status.mjs +5 -23
- package/scripts/sync.mjs +3 -1
- package/src/_worker.js +161 -314
- package/src/galleries.mjs +400 -0
- package/src/state-inventory.mjs +0 -4
- package/src/unit-object.mjs +22 -4
- package/agents/working-marks.md +0 -86
- package/scripts/lib/marks.mjs +0 -107
- package/scripts/mark.mjs +0 -112
- package/scripts/ship.mjs +0 -460
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`)
|
|
112
|
-
shorter `augur <cmd>` form
|
|
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
|
-
|
|
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
|
@@ -25,7 +25,9 @@ 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
|
|
28
|
-
enter `<code>`* — and waits.
|
|
28
|
+
enter `<code>`* — and waits. If your person reads you through messages rather than a
|
|
29
|
+
terminal, run it with `--no-wait`: it prints the line and exits, and running it again
|
|
30
|
+
after they approve collects the token for that same code. The owner opens the link in a browser they are already
|
|
29
31
|
signed in to and types the code; the token lands in `~/.config/augur/tokens.json`, and
|
|
30
32
|
`augur ship` / `augur publish` use it from then on. `publish` runs the pairing itself when
|
|
31
33
|
it finds no token, so inside a workspace tree nothing has to be done first. On a workspace
|
|
@@ -34,10 +36,10 @@ are `augur open` and `augur land` instead — see [drafts.md](./drafts.md). To g
|
|
|
34
36
|
tree from a hostname, pair first, then `npx @augurworks/augur clone --space <id>` — it reads the
|
|
35
37
|
origin from the pairing.
|
|
36
38
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
39
|
+
The instance says all of this itself at `GET /llms.txt` (and as data at
|
|
40
|
+
`/.well-known/augur.json`); a signed-out request for an engine path answers `401` with
|
|
41
|
+
the same facts. `npm i -g @augurworks/augur` puts `augur` on the PATH for good; an engine
|
|
42
|
+
clone beside the workspace still works too (`node <engine>/scripts/cli.mjs <verb>`).
|
|
41
43
|
|
|
42
44
|
Two things an agent never does: type a password into a terminal (`augur login` exists
|
|
43
45
|
for CI, and says so), and try the `pass` field from a deploy shell's `identity.json` —
|
|
@@ -49,7 +51,6 @@ Read them by trigger, not up front:
|
|
|
49
51
|
| When you are… | Read |
|
|
50
52
|
|---|---|
|
|
51
53
|
| 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
54
|
| changing a prototype on a workspace that serves drafts | [drafts.md](./drafts.md) — open, edit, land |
|
|
54
55
|
| shipping / going live on a workspace without drafts | [publishing.md](./publishing.md) |
|
|
55
56
|
| 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
|
|
5
|
-
`
|
|
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
|
|
|
@@ -16,6 +16,12 @@ augur sync # only when land was refused: fold main
|
|
|
16
16
|
That is it. There is no commit, no push, no build. The URL `land` prints is what you hand
|
|
17
17
|
the person you are working for — never a localhost, never a `file://` path.
|
|
18
18
|
|
|
19
|
+
A prototype that does not exist yet: `augur open --new <opportunity>/<name>` gives you an
|
|
20
|
+
empty folder and an empty draft; write its `index.html` and land. The same works for a
|
|
21
|
+
library demo (`components/<name>`, `base/…`, `patterns/…`, `pages/…`) and for the
|
|
22
|
+
workspace's design system (`skills/<prefix>-ui`), which is one unit like any other.
|
|
23
|
+
Without `--new`, a name that does not exist is refused rather than guessed at.
|
|
24
|
+
|
|
19
25
|
## What a draft is
|
|
20
26
|
|
|
21
27
|
Your own live copy of one prototype, at its own address: the prototype's URL with `@` and
|
|
@@ -25,7 +31,7 @@ you land. Two agents opening the same prototype get two drafts, are both told ab
|
|
|
25
31
|
other, and both work — nothing locks, nobody waits.
|
|
26
32
|
|
|
27
33
|
`augur open` prints who else has it open. That is the whole coordination step; there is
|
|
28
|
-
nothing to leave and nothing to clear
|
|
34
|
+
nothing to leave and nothing to clear.
|
|
29
35
|
|
|
30
36
|
## Editing
|
|
31
37
|
|
|
@@ -72,15 +78,17 @@ you do not want (its saves stay on the instance for a while; nothing else is tou
|
|
|
72
78
|
|---|---|---|
|
|
73
79
|
| `main-moved` / "sync first" | somebody landed since you opened | `augur sync`, then `augur land` |
|
|
74
80
|
| `stale-draft` | another process saved to this same draft | `augur sync`, then `augur save` |
|
|
81
|
+
| `draft-closed` | somebody landed or discarded this draft from the site (it says who and when) | your edits are still in the folder; `augur open` the prototype again and copy them in |
|
|
82
|
+
| `manifest-contended` | many landings hit the workspace in the same second; `land` already tried again | `augur land` once more |
|
|
83
|
+
| `forbidden` with "run `augur connect` again" | this machine's token was revoked (a role change or a removal) or belongs to another workspace | `augur connect` |
|
|
75
84
|
| `would-unpublish` | the draft has no files (the folder is empty) | check the folder; a deletion is its own verb |
|
|
76
85
|
| `not-a-prototype-folder` / `reserved-folder` | the path is not `<opportunity>/<prototype>` | name the prototype folder |
|
|
77
|
-
| `units-not-configured` | this instance does not serve drafts |
|
|
86
|
+
| `units-not-configured` | this instance does not serve drafts | `augur publish` — see publishing.md |
|
|
78
87
|
| no publish token | this machine is not paired | `augur connect` (never a password) |
|
|
79
88
|
| unreachable | the instance could not be reached | nothing is lost; the next save carries every change since |
|
|
80
89
|
|
|
81
90
|
## What you never do
|
|
82
91
|
|
|
83
|
-
-
|
|
84
|
-
release and is not for you.
|
|
92
|
+
- Publish a whole tree to a drafts workspace. `publish` refuses there, and `ship` is gone.
|
|
85
93
|
- Wait for, or refuse over, somebody else's draft. Both work; the second landing syncs.
|
|
86
94
|
- Hand over a path on disk as "done". Done is the URL `land` printed.
|
|
@@ -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
|
-
(`
|
|
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
|
package/agents/publishing.md
CHANGED
|
@@ -1,20 +1,22 @@
|
|
|
1
1
|
# Publishing — how work in a workspace goes live
|
|
2
2
|
|
|
3
|
-
**Where the workspace serves drafts, this
|
|
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
|
|
6
|
+
below is for a self-hosted instance without a unit store, which still publishes a tree.
|
|
7
7
|
|
|
8
|
-
**
|
|
8
|
+
**The command, every time your work is ready:**
|
|
9
9
|
|
|
10
10
|
```
|
|
11
|
-
|
|
12
|
-
|
|
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
|
|
16
|
-
|
|
17
|
-
|
|
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
|
|
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
|
|
180
|
+
From the workspace repo's root:
|
|
177
181
|
|
|
178
182
|
```
|
|
179
|
-
node ../augur/scripts/publish.mjs
|
|
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,7 @@ them.
|
|
|
225
229
|
purpose), say so:
|
|
226
230
|
|
|
227
231
|
```
|
|
228
|
-
|
|
232
|
+
augur publish --allow-unpublish
|
|
229
233
|
```
|
|
230
234
|
|
|
231
235
|
Adding pages is never blocked; only losing them is.
|
|
@@ -244,8 +248,7 @@ The first publish on a machine pairs it with your browser, on its own:
|
|
|
244
248
|
|
|
245
249
|
Open the link in a browser where you are already signed in to the site, type the
|
|
246
250
|
code, and the publish carries on. No password goes near the terminal, the shell
|
|
247
|
-
history, or an agent transcript. `
|
|
248
|
-
runs that step by itself when you want it ahead of time. Agents: relay the link and
|
|
251
|
+
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
252
|
the code to your human; that is the whole hand-off.
|
|
250
253
|
|
|
251
254
|
`augur login` (web email + password) still exists for two cases: CI and scripts,
|
|
@@ -299,7 +302,7 @@ responses agree before declaring a chrome bug.
|
|
|
299
302
|
|
|
300
303
|
## Local preview (no publish involved)
|
|
301
304
|
|
|
302
|
-
`
|
|
305
|
+
`augur dev` from the workspace root runs the full site shell
|
|
303
306
|
locally — login, rail, overlays, canvas, the same experience the live site
|
|
304
307
|
gives (login `dev@local` / `dev` when no identity file is around). This is the
|
|
305
308
|
**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,
|
|
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/package.json
CHANGED
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|
|
|
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);
|
package/scripts/connect.mjs
CHANGED
|
@@ -30,6 +30,28 @@ const ORIGIN = (opt("--origin") || process.env.AUGUR_ORIGIN || resolveOrigin() |
|
|
|
30
30
|
if (!ORIGIN) die("no origin — pass --origin https://your.site, or set AUGUR_ORIGIN.");
|
|
31
31
|
|
|
32
32
|
const POLL_MS = 2000;
|
|
33
|
+
// `--no-wait`: print the line to relay and exit at once. For an agent that talks to its
|
|
34
|
+
// person through messages rather than a terminal — a chat, a ticket, a print-mode run —
|
|
35
|
+
// waiting here is a dead end: the person cannot see the code until the command ends, and
|
|
36
|
+
// the command does not end until the person acts. The pairing is kept on this machine, so
|
|
37
|
+
// `augur connect` run again after the approval collects the token, minting no new code.
|
|
38
|
+
const NO_WAIT = argv.includes("--no-wait");
|
|
39
|
+
const PENDING_FILE = path.join(os.homedir(), ".config", "augur", "pairing.json");
|
|
40
|
+
const host = new URL(ORIGIN).host;
|
|
41
|
+
function readPending() {
|
|
42
|
+
try {
|
|
43
|
+
const all = JSON.parse(readFileSync(PENDING_FILE, "utf8"));
|
|
44
|
+
const p = all[host];
|
|
45
|
+
return p && p.code && p.deviceSecret && Date.parse(p.expiresAt) > Date.now() ? p : null;
|
|
46
|
+
} catch (e) { return null; }
|
|
47
|
+
}
|
|
48
|
+
function writePending(p) {
|
|
49
|
+
mkdirSync(path.dirname(PENDING_FILE), { recursive: true });
|
|
50
|
+
let all = {};
|
|
51
|
+
try { all = JSON.parse(readFileSync(PENDING_FILE, "utf8")); } catch (e) {}
|
|
52
|
+
if (p) all[host] = p; else delete all[host];
|
|
53
|
+
writeFileSync(PENDING_FILE, JSON.stringify(all, null, 2), { mode: 0o600 });
|
|
54
|
+
}
|
|
33
55
|
|
|
34
56
|
async function post(pathPart, body) {
|
|
35
57
|
const r = await fetch(`${ORIGIN}/__publish/_pair/${pathPart}`, {
|
|
@@ -42,7 +64,22 @@ async function post(pathPart, body) {
|
|
|
42
64
|
return { status: r.status, json };
|
|
43
65
|
}
|
|
44
66
|
|
|
45
|
-
|
|
67
|
+
// A pairing this machine already started and nobody has collected: ask once whether it
|
|
68
|
+
// was approved meanwhile, and if not, keep waiting on THAT code rather than minting a
|
|
69
|
+
// second one for the same person to type.
|
|
70
|
+
const pending = readPending();
|
|
71
|
+
if (pending) {
|
|
72
|
+
const res = await post("claim", { code: pending.code, deviceSecret: pending.deviceSecret });
|
|
73
|
+
if (res.status === 200 && res.json && res.json.token) {
|
|
74
|
+
writePending(null);
|
|
75
|
+
finish(res.json);
|
|
76
|
+
} else if (res.status === 202) {
|
|
77
|
+
log(`the pairing started earlier is still waiting to be approved.`);
|
|
78
|
+
} else {
|
|
79
|
+
writePending(null);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
const start = readPending() ? { status: 200, json: { ...readPending(), expiresInMs: Date.parse(readPending().expiresAt) - Date.now() } } : await post("start");
|
|
46
83
|
if (start.status === 429) die("too many attempts from here. Wait a few minutes.");
|
|
47
84
|
if (start.status !== 200 || !start.json || !start.json.code) {
|
|
48
85
|
// The routes answer as though they are not there when the instance has not opted in,
|
|
@@ -53,8 +90,9 @@ if (start.status !== 200 || !start.json || !start.json.code) {
|
|
|
53
90
|
}
|
|
54
91
|
|
|
55
92
|
const { code, deviceSecret, approveUrl, expiresInMs } = start.json;
|
|
56
|
-
const mins = Math.round((expiresInMs || 300000) / 60000);
|
|
93
|
+
const mins = Math.max(1, Math.round((expiresInMs || 300000) / 60000));
|
|
57
94
|
const pretty = code.length === 8 ? `${code.slice(0, 4)}-${code.slice(4)}` : code;
|
|
95
|
+
if (!readPending()) writePending({ code, deviceSecret, approveUrl, startedAt: new Date().toISOString(), expiresAt: new Date(Date.now() + (expiresInMs || 300000)).toISOString() });
|
|
58
96
|
|
|
59
97
|
// Written to be RELAYED, not read: the usual runner of this command is an agent, which
|
|
60
98
|
// pastes the first line to a person. So the first line is the whole instruction, and it
|
|
@@ -68,6 +106,15 @@ console.log(` The code is good for ${mins} minutes and only for this terminal.
|
|
|
68
106
|
console.log("");
|
|
69
107
|
console.log(` ${C.warn}If you did not just run this command, do not approve it.${C.off}`);
|
|
70
108
|
console.log("");
|
|
109
|
+
if (NO_WAIT) {
|
|
110
|
+
console.log(` ${C.dim}Not waiting. Once they have approved, run \`augur connect\` again here: it collects the`);
|
|
111
|
+
console.log(` token for this same code and mints no new one.${C.off}`);
|
|
112
|
+
process.exit(0);
|
|
113
|
+
}
|
|
114
|
+
console.log(` ${C.dim}You can stop waiting (Ctrl-C) and run \`augur connect\` again after they approve —`);
|
|
115
|
+
console.log(` it collects the token for this same code. Talking to your person through messages`);
|
|
116
|
+
console.log(` rather than a terminal? Use \`augur connect --no-wait\`.${C.off}`);
|
|
117
|
+
console.log("");
|
|
71
118
|
|
|
72
119
|
const deadline = Date.now() + (expiresInMs || 300000);
|
|
73
120
|
let saved = null;
|
|
@@ -76,9 +123,13 @@ while (Date.now() < deadline) {
|
|
|
76
123
|
const res = await post("claim", { code, deviceSecret });
|
|
77
124
|
if (res.status === 200 && res.json && res.json.token) { saved = res.json; break; }
|
|
78
125
|
if (res.status === 202) continue; // still waiting for somebody to approve
|
|
79
|
-
if (res.status === 404) die("this pairing is no longer valid. Run `augur connect` again.");
|
|
126
|
+
if (res.status === 404) { writePending(null); die("this pairing is no longer valid. Run `augur connect` again."); }
|
|
80
127
|
}
|
|
81
|
-
if (!saved) die(`nobody approved it within ${mins} minutes. Run \`augur connect\` again.`);
|
|
128
|
+
if (!saved) { writePending(null); die(`nobody approved it within ${mins} minutes. Run \`augur connect\` again.`); }
|
|
129
|
+
writePending(null);
|
|
130
|
+
finish(saved);
|
|
131
|
+
|
|
132
|
+
function finish(saved) {
|
|
82
133
|
|
|
83
134
|
const dir = path.join(os.homedir(), ".config", "augur");
|
|
84
135
|
mkdirSync(dir, { recursive: true });
|
|
@@ -100,4 +151,6 @@ if (saved.expiresAt) {
|
|
|
100
151
|
console.log(`${C.dim}It expires in ${days} days (${saved.expiresAt.slice(0, 10)}). Run \`augur connect\` again then.${C.off}`);
|
|
101
152
|
} else {
|
|
102
153
|
console.log(`${C.dim}It expires on its own. Run \`augur connect\` again when it does.${C.off}`);
|
|
154
|
+
}
|
|
155
|
+
process.exit(0);
|
|
103
156
|
}
|
package/scripts/hook.mjs
CHANGED
|
@@ -63,5 +63,6 @@ const client = unitClient({ origin, token, space: st.space, session: st.session
|
|
|
63
63
|
const r = await doSave({ client, dir: target.dir });
|
|
64
64
|
if (r.ok) process.exit(0);
|
|
65
65
|
if (r.error === "stale-draft" || r.error === "stale-draft-revision") refuse(`draft ${st.draftId} not saved: it moved under you (another process saved to it) — run augur sync, then augur save.`);
|
|
66
|
+
if (r.error === "draft-closed") refuse(`draft ${st.draftId} not saved: it was ${r.landed ? `landed by ${r.name || r.by || "someone"}${r.session ? ` (${r.session})` : ""} at ${r.at}` : "discarded"} — this folder is no longer a draft. Your edits are still here; run augur open on the prototype again and copy them in.`);
|
|
66
67
|
if (r.error === "network") refuse(`draft ${st.draftId} not saved: ${origin} is unreachable (${r.message}). Nothing is lost — the next save carries every change since.`);
|
|
67
|
-
refuse(`draft ${st.draftId} not saved: ${r.error || r.status}${r.reason ? ` (${r.reason})` : ""}. Fix it and run augur save.`);
|
|
68
|
+
refuse(`draft ${st.draftId} not saved: ${r.error || r.status}${r.reason ? ` (${r.reason})` : ""}${r.message ? ` — ${r.message}` : ""}. Fix it and run augur save.`);
|
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
|
|
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
|
|
122
|
+
log(`next: edit the prototype, then \`augur land\` (a workspace that serves drafts) or \`augur publish\`.`);
|
package/scripts/land.mjs
CHANGED
|
@@ -26,7 +26,10 @@ if (!r.ok) {
|
|
|
26
26
|
die("run `augur sync` to fold those in, check the draft address, then `augur land` again.");
|
|
27
27
|
}
|
|
28
28
|
if (r.error === "landing-in-progress") die("somebody is landing this prototype right now — try again in a few seconds.");
|
|
29
|
-
|
|
29
|
+
if (r.error === "manifest-contended") die("the workspace was busy landing other prototypes — nothing changed; run augur land again.");
|
|
30
|
+
if (r.error === "draft-closed") die(`this draft was ${r.landed ? `landed by ${r.name || r.by || "someone"}${r.session ? ` (${r.session})` : ""} at ${r.at}` : "discarded"} — the folder is no longer a draft. Your edits are still here; run augur open on the prototype again and copy them in.`);
|
|
31
|
+
if (r.error === "network") die(`could not reach the instance (${r.message}). Nothing is lost — run augur land again.`);
|
|
32
|
+
die(`land refused: ${r.error || r.status}${r.message ? ` — ${r.message}` : ""}`);
|
|
30
33
|
}
|
|
31
34
|
// The bytes are live; when `recorded` is false only the history entry is missing. Said out
|
|
32
35
|
// loud because the next call adopts that landing as the instance's own, and nobody would
|
package/scripts/lib/adapters.mjs
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
});
|