@augurworks/augur 0.15.2 → 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 +3 -2
- package/README.md +4 -2
- package/agents/README.md +4 -5
- package/agents/drafts.md +11 -6
- package/agents/prototype-contract.md +1 -1
- package/agents/publishing.md +23 -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/init.mjs +2 -2
- package/scripts/lib/adapters.mjs +23 -3
- package/scripts/lib/draft.mjs +22 -2
- package/scripts/no-tenant-globals.mjs +16 -0
- package/scripts/open.mjs +8 -6
- package/scripts/publish.mjs +19 -0
- package/scripts/read.mjs +2 -3
- package/scripts/status.mjs +5 -23
- package/src/_worker.js +139 -309
- package/src/galleries.mjs +400 -0
- package/src/state-inventory.mjs +0 -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
|
@@ -34,10 +34,10 @@ are `augur open` and `augur land` instead — see [drafts.md](./drafts.md). To g
|
|
|
34
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
|
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
|
|
|
@@ -74,13 +80,12 @@ you do not want (its saves stay on the instance for a while; nothing else is tou
|
|
|
74
80
|
| `stale-draft` | another process saved to this same draft | `augur sync`, then `augur save` |
|
|
75
81
|
| `would-unpublish` | the draft has no files (the folder is empty) | check the folder; a deletion is its own verb |
|
|
76
82
|
| `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 |
|
|
83
|
+
| `units-not-configured` | this instance does not serve drafts | `augur publish` — see publishing.md |
|
|
78
84
|
| no publish token | this machine is not paired | `augur connect` (never a password) |
|
|
79
85
|
| unreachable | the instance could not be reached | nothing is lost; the next save carries every change since |
|
|
80
86
|
|
|
81
87
|
## What you never do
|
|
82
88
|
|
|
83
|
-
-
|
|
84
|
-
release and is not for you.
|
|
89
|
+
- Publish a whole tree to a drafts workspace. `publish` refuses there, and `ship` is gone.
|
|
85
90
|
- Wait for, or refuse over, somebody else's draft. Both work; the second landing syncs.
|
|
86
91
|
- 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,11 @@ them.
|
|
|
225
229
|
purpose), say so:
|
|
226
230
|
|
|
227
231
|
```
|
|
228
|
-
|
|
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. `
|
|
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
|
-
`
|
|
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,
|
|
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/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/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
|
});
|
package/scripts/lib/draft.mjs
CHANGED
|
@@ -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) {
|
|
@@ -150,11 +162,19 @@ async function materialise(client, unit, table, dir) {
|
|
|
150
162
|
}
|
|
151
163
|
}
|
|
152
164
|
|
|
153
|
-
async function doOpenImpl({ client, unit, dir, origin, space, session, now }) {
|
|
165
|
+
async function doOpenImpl({ client, unit, dir, origin, space, session, now, isNew = false }) {
|
|
154
166
|
if (fs.existsSync(dir) && fs.readdirSync(dir).length) return { ok: false, error: "folder-not-empty", dir };
|
|
155
167
|
const createdFolder = !fs.existsSync(dir);
|
|
156
168
|
const o = await client.open({ unit });
|
|
157
169
|
if (o.status) return { ok: false, ...o };
|
|
170
|
+
// A unit with no files is one that does not exist yet. Creating one is a decision the
|
|
171
|
+
// caller states with `isNew`; without it, a typo would quietly open an empty draft on a
|
|
172
|
+
// prototype that was never there. Either mismatch hands the draft straight back.
|
|
173
|
+
const exists = Object.keys(o.table || {}).length > 0;
|
|
174
|
+
if (exists !== !isNew) {
|
|
175
|
+
try { await client.discard({ unit, draftId: o.draftId }); } catch (e) { /* best-effort */ }
|
|
176
|
+
return { ok: false, error: exists ? "unit-exists" : "unknown-unit", unit };
|
|
177
|
+
}
|
|
158
178
|
// From here the server-side draft exists, so any failure below must both undo what we
|
|
159
179
|
// wrote to disk and tell the server to drop the orphan — otherwise a retry finds a
|
|
160
180
|
// half-materialised folder (`folder-not-empty`) and the draft it opened is never freed.
|
|
@@ -177,7 +197,7 @@ async function doOpenImpl({ client, unit, dir, origin, space, session, now }) {
|
|
|
177
197
|
throw err;
|
|
178
198
|
}
|
|
179
199
|
const others = (o.presence || []).filter((d) => d.id !== o.draftId);
|
|
180
|
-
return { ok: true, draftId: o.draftId, address: `${origin}${o.address}`, files: Object.keys(o.table).length, others };
|
|
200
|
+
return { ok: true, draftId: o.draftId, address: `${origin}${o.address}`, files: Object.keys(o.table).length, others, isNew: !exists };
|
|
181
201
|
}
|
|
182
202
|
|
|
183
203
|
async function doSaveImpl({ client, dir, baseRevision, baseTable }) {
|
|
@@ -224,6 +224,7 @@ const ALLOWLIST = {
|
|
|
224
224
|
"mcpHostAllowlist",
|
|
225
225
|
"CANVAS_REGISTRY",
|
|
226
226
|
"PITI_REMARKS",
|
|
227
|
+
"DERIVED", // the two store reads a derived gallery needs, per manifest version — the status baseline and the design-system catalog
|
|
227
228
|
"ROSTER_OVERLAY",
|
|
228
229
|
"FREEZE_STATE",
|
|
229
230
|
"SUSPENSION_STATE",
|
|
@@ -281,6 +282,21 @@ const ALLOWLIST = {
|
|
|
281
282
|
},
|
|
282
283
|
},
|
|
283
284
|
|
|
285
|
+
// The derived pages: pure renderers over the live store. Every table here is a fact
|
|
286
|
+
// about the engine's vocabulary — tier names, status words and glyphs, the emoji pool a
|
|
287
|
+
// card's leading glyph is picked from — the same for every workspace.
|
|
288
|
+
"src/galleries.mjs": {
|
|
289
|
+
frozen: [
|
|
290
|
+
"TIERS", // the four library tiers whose demos are units
|
|
291
|
+
"TIER_TITLE", // their titles
|
|
292
|
+
"TIER_COPY", // their hints and empty states
|
|
293
|
+
"STATUS_META", // status word → label + class
|
|
294
|
+
"STATUS_ICONS", // status word → glyph
|
|
295
|
+
"STATUS_RANK", // the card order a status implies
|
|
296
|
+
"EMOJI_POOL", // the leading-emoji pool, picked by slug hash
|
|
297
|
+
],
|
|
298
|
+
},
|
|
299
|
+
|
|
284
300
|
"src/bundle-keys.mjs": {
|
|
285
301
|
frozen: [
|
|
286
302
|
"BUNDLE_TENANCY", // which bundle-store families carry a workspace segment; one word per family is the revert, and a deploy-wide fact — moved here from the worker so the workspace object shares the key shape
|
package/scripts/open.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// augur open <prototype> [--dir <folder>] [--session <label>]
|
|
2
|
+
// augur open <prototype> [--new] [--dir <folder>] [--session <label>]
|
|
3
3
|
//
|
|
4
4
|
// Open one prototype into a folder of its own, as a draft that is live at once at its own
|
|
5
5
|
// address. Prints who else is drafting it. The folder holds only that prototype's files and
|
|
@@ -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 {
|
|
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(
|
|
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); }
|
|
@@ -35,15 +34,18 @@ const session = process.env.AUGUR_SESSION || opt("--session") || `session-${proc
|
|
|
35
34
|
const dir = path.resolve(opt("--dir") || unit.split("/").filter(Boolean).pop());
|
|
36
35
|
|
|
37
36
|
const client = unitClient({ origin, token, space, session });
|
|
38
|
-
const
|
|
37
|
+
const isNew = argv.includes("--new");
|
|
38
|
+
const r = await doOpen({ client, unit, dir, origin, space, session, now: new Date().toISOString(), isNew });
|
|
39
39
|
if (!r.ok) {
|
|
40
40
|
if (r.error === "folder-not-empty") die(`${r.dir} is not empty — pick another folder with --dir.`);
|
|
41
|
+
if (r.error === "unknown-unit") die(`${unit} does not exist here. To create it: \`augur open --new ${unit.replace(/^\/|\/$/g, "")}\`.`);
|
|
42
|
+
if (r.error === "unit-exists") die(`${unit} exists already — open it without --new.`);
|
|
41
43
|
if (r.error === "units-not-configured") die("this instance does not serve drafts yet (no unit store bound).");
|
|
42
44
|
if (r.error === "bad-unit" && r.reason === "reserved-folder") die(`${unit} sits under a folder the engine reserves — a prototype lives at <opportunity>/<prototype>.`);
|
|
43
45
|
if (r.error === "bad-unit" && r.reason === "not-a-prototype-folder") die(`${unit} is not a prototype folder — name one as <opportunity>/<prototype>.`);
|
|
44
46
|
die(`could not open: ${r.error || r.status}${r.reason ? ` (${r.reason})` : ""}`);
|
|
45
47
|
}
|
|
46
|
-
log(`draft ${r.draftId} on ${unit} — ${r.files} file(s) in ${dir}`);
|
|
48
|
+
log(r.isNew ? `draft ${r.draftId} on ${unit} — a NEW prototype; ${dir} is empty, write its index.html there` : `draft ${r.draftId} on ${unit} — ${r.files} file(s) in ${dir}`);
|
|
47
49
|
// The agent tool's hooks, installed for this machine the first time a draft is opened
|
|
48
50
|
// here (idempotent; `AUGUR_NO_ADAPTERS=1` skips it — the suite and CI set it).
|
|
49
51
|
if (!process.env.AUGUR_NO_ADAPTERS) {
|