@scribe-platform/cli 0.7.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/dist/commands/login.d.ts.map +1 -1
- package/dist/commands/login.js +15 -1
- package/dist/commands/login.js.map +1 -1
- package/dist/commands/proof.d.ts +5 -0
- package/dist/commands/proof.d.ts.map +1 -0
- package/dist/commands/proof.js +440 -0
- package/dist/commands/proof.js.map +1 -0
- package/dist/commands/prospect.d.ts +27 -0
- package/dist/commands/prospect.d.ts.map +1 -0
- package/dist/commands/prospect.js +222 -0
- package/dist/commands/prospect.js.map +1 -0
- package/dist/commands/request.d.ts +38 -0
- package/dist/commands/request.d.ts.map +1 -0
- package/dist/commands/request.js +214 -0
- package/dist/commands/request.js.map +1 -0
- package/dist/commands/site.d.ts +11 -0
- package/dist/commands/site.d.ts.map +1 -1
- package/dist/commands/site.js +41 -3
- package/dist/commands/site.js.map +1 -1
- package/dist/commands/update.d.ts +10 -0
- package/dist/commands/update.d.ts.map +1 -1
- package/dist/commands/update.js +128 -31
- package/dist/commands/update.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -1
- package/dist/index.js.map +1 -1
- package/dist/lib/claude-hook.d.ts +74 -0
- package/dist/lib/claude-hook.d.ts.map +1 -0
- package/dist/lib/claude-hook.js +108 -0
- package/dist/lib/claude-hook.js.map +1 -0
- package/dist/lib/client.d.ts +351 -0
- package/dist/lib/client.d.ts.map +1 -1
- package/dist/lib/client.js +159 -0
- package/dist/lib/client.js.map +1 -1
- package/dist/lib/proof-folder.d.ts +86 -0
- package/dist/lib/proof-folder.d.ts.map +1 -0
- package/dist/lib/proof-folder.js +226 -0
- package/dist/lib/proof-folder.js.map +1 -0
- package/dist/lib/proof-preview.d.ts +39 -0
- package/dist/lib/proof-preview.d.ts.map +1 -0
- package/dist/lib/proof-preview.js +318 -0
- package/dist/lib/proof-preview.js.map +1 -0
- package/dist/lib/proof-push.d.ts +42 -0
- package/dist/lib/proof-push.d.ts.map +1 -0
- package/dist/lib/proof-push.js +91 -0
- package/dist/lib/proof-push.js.map +1 -0
- package/dist/lib/skill.d.ts +13 -0
- package/dist/lib/skill.d.ts.map +1 -1
- package/dist/lib/skill.js +33 -2
- package/dist/lib/skill.js.map +1 -1
- package/dist/lib/update-check.d.ts +46 -0
- package/dist/lib/update-check.d.ts.map +1 -0
- package/dist/lib/update-check.js +103 -0
- package/dist/lib/update-check.js.map +1 -0
- package/package.json +5 -3
- package/runtime/proof.js +139 -0
- package/skills/scribe-site-builder/SKILL.md +175 -33
- package/skills/scribe-site-builder/recipes/README.md +11 -3
- package/skills/scribe-site-builder/recipes/domains.md +151 -0
- package/skills/scribe-site-builder/recipes/forms.md +5 -2
- package/skills/scribe-site-builder/recipes/proofs.md +311 -0
- package/skills/scribe-site-builder/recipes/prospects.md +192 -0
- package/skills/scribe-site-builder/recipes/requests.md +145 -0
- package/skills/scribe-site-builder/recipes/schema.md +24 -0
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
import { existsSync, readFileSync, statSync } from 'node:fs';
|
|
2
|
+
import { createServer } from 'node:http';
|
|
3
|
+
import { basename, resolve, sep } from 'node:path';
|
|
4
|
+
import { ENTRY_PATH, extensionOf, PROOF_CONTENT_TYPES } from './proof-folder.js';
|
|
5
|
+
/**
|
|
6
|
+
* RFC 0029 — `scribe proof preview`: a Proof folder served the way a Client
|
|
7
|
+
* will see it, before anything is pushed.
|
|
8
|
+
*
|
|
9
|
+
* Same sandbox CSP, same runtime injected the same way, same iframe sandbox
|
|
10
|
+
* attribute — so a Proof that works here works for the Client, and one that
|
|
11
|
+
* breaks under the sandbox (a script reading `localStorage`, say) breaks here
|
|
12
|
+
* first. The panel around it is a stand-in for the real review page: it keeps
|
|
13
|
+
* hearts, comments and pins in memory so they show up on the page as they
|
|
14
|
+
* would, and prints each one to the terminal. Nothing is saved anywhere.
|
|
15
|
+
*/
|
|
16
|
+
/** Mirrors the admin's `PROOF_CONTENT_CSP`. No `allow-same-origin`, ever. */
|
|
17
|
+
export const PREVIEW_CSP = 'sandbox allow-scripts allow-forms allow-popups allow-modals';
|
|
18
|
+
const PREFIX = '/c/local/';
|
|
19
|
+
const RUNTIME_PATH = '_scribe/proof.js';
|
|
20
|
+
/**
|
|
21
|
+
* Must match `PROTOCOL_VERSION` in `@scribe-cms/proof-runtime`'s protocol
|
|
22
|
+
* module. Not imported from it: the CLI ships to npm and only gets the bundled
|
|
23
|
+
* script copied in at build time, never the package. A mismatch is loud — the
|
|
24
|
+
* runtime drops every message and the preview draws no affordances at all.
|
|
25
|
+
*/
|
|
26
|
+
const PROTOCOL_VERSION = 7;
|
|
27
|
+
/**
|
|
28
|
+
* The runtime bundle, copied in from `@scribe-cms/proof-runtime` at build time
|
|
29
|
+
* by scripts/bundle-runtime.mjs — the CLI is published to npm and can't depend
|
|
30
|
+
* on the private workspace package at install time. `../../runtime/` resolves
|
|
31
|
+
* to the package root from both `src/lib/` (tests) and `dist/lib/` (published).
|
|
32
|
+
*/
|
|
33
|
+
export function loadRuntimeSource() {
|
|
34
|
+
const file = new URL('../../runtime/proof.js', import.meta.url);
|
|
35
|
+
if (!existsSync(file)) {
|
|
36
|
+
throw new Error('The Proof runtime is missing from this install. Reinstall the CLI (or, in the repo, run `npm run build --workspace=@scribe-platform/cli`).');
|
|
37
|
+
}
|
|
38
|
+
return readFileSync(file, 'utf8');
|
|
39
|
+
}
|
|
40
|
+
/** Same placement rule as the admin's `injectRuntime`: first thing in `<head>`, src relative to the file's depth. */
|
|
41
|
+
export function injectRuntime(html, path) {
|
|
42
|
+
const depth = path.split('/').length - 1;
|
|
43
|
+
const tag = `<script src="${'../'.repeat(depth)}${RUNTIME_PATH}"></script>`;
|
|
44
|
+
const at = (index) => html.slice(0, index) + tag + html.slice(index);
|
|
45
|
+
const head = /<head(\s[^>]*)?>/i.exec(html);
|
|
46
|
+
if (head)
|
|
47
|
+
return at(head.index + head[0].length);
|
|
48
|
+
const body = /<body(\s[^>]*)?>/i.exec(html);
|
|
49
|
+
if (body)
|
|
50
|
+
return at(body.index);
|
|
51
|
+
const doctype = /^\s*<!doctype[^>]*>/i.exec(html);
|
|
52
|
+
if (doctype)
|
|
53
|
+
return at(doctype[0].length);
|
|
54
|
+
return tag + html;
|
|
55
|
+
}
|
|
56
|
+
function contentHeaders(contentType) {
|
|
57
|
+
return {
|
|
58
|
+
'Content-Type': contentType,
|
|
59
|
+
'Content-Security-Policy': PREVIEW_CSP,
|
|
60
|
+
'Referrer-Policy': 'no-referrer',
|
|
61
|
+
'X-Content-Type-Options': 'nosniff',
|
|
62
|
+
// Ashley edits and reloads; never serve her a stale copy.
|
|
63
|
+
'Cache-Control': 'no-store',
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* The file a request path names inside the folder, or null. Hidden files and
|
|
68
|
+
* anything outside the folder are never served — the same files a push would
|
|
69
|
+
* leave out.
|
|
70
|
+
*/
|
|
71
|
+
export function resolvePreviewFile(dir, requestPath) {
|
|
72
|
+
let segments;
|
|
73
|
+
try {
|
|
74
|
+
segments = requestPath.split('/').map(decodeURIComponent);
|
|
75
|
+
}
|
|
76
|
+
catch {
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
if (segments.some((s) => s === '' || s === '.' || s === '..' || s.startsWith('.') || s.includes('/') || s.includes('\\')))
|
|
80
|
+
return null;
|
|
81
|
+
const root = resolve(dir);
|
|
82
|
+
const target = resolve(root, ...segments);
|
|
83
|
+
if (!target.startsWith(root + sep))
|
|
84
|
+
return null;
|
|
85
|
+
if (!existsSync(target) || !statSync(target).isFile())
|
|
86
|
+
return null;
|
|
87
|
+
return target;
|
|
88
|
+
}
|
|
89
|
+
export async function startPreview(args) {
|
|
90
|
+
const dir = resolve(args.dir);
|
|
91
|
+
const log = args.log ?? ((line) => console.log(line));
|
|
92
|
+
const runtime = args.runtimeSource ?? loadRuntimeSource();
|
|
93
|
+
const shell = shellPage(basename(dir));
|
|
94
|
+
const server = createServer((req, res) => {
|
|
95
|
+
const url = new URL(req.url ?? '/', 'http://localhost');
|
|
96
|
+
const send = (status, headers, body) => {
|
|
97
|
+
res.writeHead(status, headers);
|
|
98
|
+
res.end(body);
|
|
99
|
+
};
|
|
100
|
+
if (req.method === 'POST' && url.pathname === '/__log') {
|
|
101
|
+
let body = '';
|
|
102
|
+
req.on('data', (chunk) => (body += chunk));
|
|
103
|
+
req.on('end', () => {
|
|
104
|
+
try {
|
|
105
|
+
const { line } = JSON.parse(body);
|
|
106
|
+
if (typeof line === 'string')
|
|
107
|
+
log(line.slice(0, 2000));
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// a malformed log line from our own page; nothing to print
|
|
111
|
+
}
|
|
112
|
+
send(204, {});
|
|
113
|
+
});
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
if (req.method !== 'GET' && req.method !== 'HEAD')
|
|
117
|
+
return send(405, { 'Content-Type': 'text/plain' }, 'Method not allowed\n');
|
|
118
|
+
if (url.pathname === '/')
|
|
119
|
+
return send(200, { 'Content-Type': 'text/html; charset=utf-8', 'Cache-Control': 'no-store' }, shell);
|
|
120
|
+
if (url.pathname === '/c/local' || url.pathname === PREFIX)
|
|
121
|
+
return send(307, { Location: `${PREFIX}${ENTRY_PATH}` });
|
|
122
|
+
if (!url.pathname.startsWith(PREFIX))
|
|
123
|
+
return send(404, contentHeaders('text/plain; charset=utf-8'), 'Not found\n');
|
|
124
|
+
const path = url.pathname.slice(PREFIX.length);
|
|
125
|
+
if (path === RUNTIME_PATH)
|
|
126
|
+
return send(200, contentHeaders('text/javascript; charset=utf-8'), runtime);
|
|
127
|
+
const file = resolvePreviewFile(dir, path);
|
|
128
|
+
const ext = file ? extensionOf(file) : null;
|
|
129
|
+
const contentType = ext ? PROOF_CONTENT_TYPES[ext] : undefined;
|
|
130
|
+
if (!file || !contentType) {
|
|
131
|
+
// Same 404 for a missing file and a type a push would refuse: either
|
|
132
|
+
// way the Client would never see it, so neither should the preview.
|
|
133
|
+
return send(404, contentHeaders('text/plain; charset=utf-8'), `Not in this Proof: ${path}\n`);
|
|
134
|
+
}
|
|
135
|
+
const bytes = readFileSync(file);
|
|
136
|
+
if (contentType.startsWith('text/html')) {
|
|
137
|
+
return send(200, contentHeaders(contentType), injectRuntime(bytes.toString('utf8'), decodeURIComponent(path)));
|
|
138
|
+
}
|
|
139
|
+
return send(200, contentHeaders(contentType), bytes);
|
|
140
|
+
});
|
|
141
|
+
await new Promise((resolveListen, reject) => {
|
|
142
|
+
server.once('error', reject);
|
|
143
|
+
// Loopback only: a preview is for the machine it runs on.
|
|
144
|
+
server.listen(args.port ?? 0, '127.0.0.1', () => resolveListen());
|
|
145
|
+
});
|
|
146
|
+
const { port } = server.address();
|
|
147
|
+
return {
|
|
148
|
+
url: `http://127.0.0.1:${port}/`,
|
|
149
|
+
close: () => new Promise((done) => server.close(() => done())),
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function escapeHtml(value) {
|
|
153
|
+
return value.replace(/[&<>"']/g, (c) => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' })[c]);
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* The stand-in review page. Deliberately plain — the real one (plan PR 6) is
|
|
157
|
+
* branded and built from the admin's design system; this one only has to make
|
|
158
|
+
* the runtime's behaviour visible and print what a Client's feedback would be.
|
|
159
|
+
*/
|
|
160
|
+
function shellPage(folder) {
|
|
161
|
+
return `<!doctype html>
|
|
162
|
+
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1">
|
|
163
|
+
<title>Preview · ${escapeHtml(folder)}</title>
|
|
164
|
+
<style>
|
|
165
|
+
:root { color-scheme: light dark; --bg: #f8fafc; --panel: #fff; --text: #0f172a; --muted: #64748b; --line: #e2e8f0; --accent: #2563eb; }
|
|
166
|
+
@media (prefers-color-scheme: dark) { :root { --bg: #0b1220; --panel: #111827; --text: #e5e7eb; --muted: #94a3b8; --line: #1f2937; } }
|
|
167
|
+
* { box-sizing: border-box; }
|
|
168
|
+
body { margin: 0; height: 100vh; display: grid; grid-template-columns: 1fr 320px; background: var(--bg); color: var(--text);
|
|
169
|
+
font: 14px/1.4 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; }
|
|
170
|
+
main { display: grid; place-items: start center; overflow: auto; padding: 16px; }
|
|
171
|
+
iframe { width: 100%; height: calc(100vh - 32px); border: 1px solid var(--line); border-radius: 8px; background: #fff; transition: width .2s; }
|
|
172
|
+
iframe.phone { width: 390px; }
|
|
173
|
+
aside { border-left: 1px solid var(--line); background: var(--panel); display: flex; flex-direction: column; min-height: 0; }
|
|
174
|
+
header { padding: 16px; border-bottom: 1px solid var(--line); }
|
|
175
|
+
h1 { margin: 0 0 4px; font-size: 15px; } p { margin: 0; color: var(--muted); font-size: 13px; }
|
|
176
|
+
.tools { display: flex; flex-wrap: wrap; gap: 8px; padding: 12px 16px; border-bottom: 1px solid var(--line); }
|
|
177
|
+
button { font: inherit; padding: 6px 12px; border-radius: 6px; border: 1px solid var(--line); background: var(--panel); color: var(--text); cursor: pointer; }
|
|
178
|
+
button[aria-pressed="true"] { background: var(--accent); border-color: var(--accent); color: #fff; }
|
|
179
|
+
ol { list-style: none; margin: 0; padding: 8px 16px; overflow: auto; flex: 1; }
|
|
180
|
+
li { padding: 8px 0; border-bottom: 1px solid var(--line); font-size: 13px; }
|
|
181
|
+
li.focus { background: color-mix(in srgb, var(--accent) 12%, transparent); }
|
|
182
|
+
li .when { color: var(--muted); font-size: 12px; }
|
|
183
|
+
@media (max-width: 800px) { body { grid-template-columns: 1fr; grid-template-rows: 1fr 40vh; } aside { border-left: 0; border-top: 1px solid var(--line); } }
|
|
184
|
+
</style></head>
|
|
185
|
+
<body>
|
|
186
|
+
<main><iframe id="proof" title="The Proof" src="${PREFIX}${ENTRY_PATH}" sandbox="allow-scripts allow-forms allow-popups allow-modals"></iframe></main>
|
|
187
|
+
<aside>
|
|
188
|
+
<header><h1>Preview · ${escapeHtml(folder)}</h1><p>What the Client will see: click anywhere on the work to leave a comment. This is a rehearsal — nothing is saved or sent.</p></header>
|
|
189
|
+
<div class="tools">
|
|
190
|
+
<button id="phone" type="button" aria-pressed="false">Phone width</button>
|
|
191
|
+
<button id="reset" type="button">Clear</button>
|
|
192
|
+
</div>
|
|
193
|
+
<ol id="log" aria-live="polite"></ol>
|
|
194
|
+
</aside>
|
|
195
|
+
<script>
|
|
196
|
+
const env = { scribe: 'proof', protocolVersion: ${PROTOCOL_VERSION} };
|
|
197
|
+
const frame = document.getElementById('proof');
|
|
198
|
+
const list = document.getElementById('log');
|
|
199
|
+
const ME = { initials: 'Y', color: '#2563eb' };
|
|
200
|
+
let state = { options: {}, pins: [] };
|
|
201
|
+
let seq = 0;
|
|
202
|
+
let labels = {};
|
|
203
|
+
let lastUnplaced = '';
|
|
204
|
+
|
|
205
|
+
function post(message) { frame.contentWindow.postMessage({ ...env, ...message }, '*'); }
|
|
206
|
+
function push() { post({ type: 'feedback', feedback: state }); }
|
|
207
|
+
function option(id) { return state.options[id] || (state.options[id] = { up: 0, down: 0, mine: null, comments: 0, reactions: [] }); }
|
|
208
|
+
function name(id) { return labels[id] ? labels[id] + ' (' + id + ')' : id; }
|
|
209
|
+
|
|
210
|
+
function log(line, id) {
|
|
211
|
+
const li = document.createElement('li');
|
|
212
|
+
if (id) li.dataset.pin = id;
|
|
213
|
+
li.innerHTML = '<div></div><div class="when"></div>';
|
|
214
|
+
li.firstChild.textContent = line;
|
|
215
|
+
li.lastChild.textContent = new Date().toLocaleTimeString();
|
|
216
|
+
list.prepend(li);
|
|
217
|
+
fetch('/__log', { method: 'POST', body: JSON.stringify({ line }) }).catch(() => {});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
window.addEventListener('message', (event) => {
|
|
221
|
+
if (event.source !== frame.contentWindow) return;
|
|
222
|
+
const m = event.data;
|
|
223
|
+
if (!m || m.scribe !== 'proof' || m.protocolVersion !== ${PROTOCOL_VERSION}) return;
|
|
224
|
+
switch (m.type) {
|
|
225
|
+
case 'ready':
|
|
226
|
+
labels = Object.fromEntries((m.options || []).map((o) => [o.id, o.label]));
|
|
227
|
+
post({ type: 'init', mode: 'review', reviewer: { name: 'You (preview)' }, me: ME, canReply: true, feedback: state });
|
|
228
|
+
log('Loaded ' + m.path + ' — ' + (m.options.length ? m.options.length + ' option(s): ' + m.options.map((o) => o.id).join(', ') : 'no options marked'));
|
|
229
|
+
break;
|
|
230
|
+
case 'react': {
|
|
231
|
+
// One Reaction per person per Option, so a second one rewrites the
|
|
232
|
+
// first — the same rule the real API enforces.
|
|
233
|
+
const o = option(m.optionId);
|
|
234
|
+
const body = (m.body || '').trim();
|
|
235
|
+
let entry = o.reactions.find((r) => r.mine);
|
|
236
|
+
if (!entry) {
|
|
237
|
+
entry = { id: 'r' + (++seq), reaction: m.reaction, author: 'You', body: '', mine: true, when: 'just now', person: ME, replies: [] };
|
|
238
|
+
o.reactions.push(entry);
|
|
239
|
+
}
|
|
240
|
+
if (entry.reaction !== m.reaction) o[entry.reaction]--;
|
|
241
|
+
entry.reaction = m.reaction;
|
|
242
|
+
if (body) entry.body = body;
|
|
243
|
+
o.up = o.reactions.filter((r) => r.reaction === 'up').length;
|
|
244
|
+
o.down = o.reactions.filter((r) => r.reaction === 'down').length;
|
|
245
|
+
o.mine = m.reaction;
|
|
246
|
+
log((m.reaction === 'up' ? '👍 ' : '👎 ') + name(m.optionId) + (body ? ' — ' + body : ''));
|
|
247
|
+
// The composer in the page is waiting on this whenever words came
|
|
248
|
+
// with the thumb, exactly as it is for a comment.
|
|
249
|
+
if (m.body !== null && m.body !== undefined) post({ type: 'comment-result', ok: true, error: null });
|
|
250
|
+
push();
|
|
251
|
+
break;
|
|
252
|
+
}
|
|
253
|
+
case 'comment': {
|
|
254
|
+
// The composer lives in the page now, so this one case covers what the
|
|
255
|
+
// reviewer typed and what the Proof's own script said.
|
|
256
|
+
const body = (m.body || '').trim();
|
|
257
|
+
if (m.parentId) {
|
|
258
|
+
// A reply, so it belongs under the pin it answers rather than on the
|
|
259
|
+
// page — the same filing the real shell does.
|
|
260
|
+
// Under a pin, or under one of an Option's Reactions — the real
|
|
261
|
+
// shell files a reply by its parent, wherever that parent lives.
|
|
262
|
+
const parent = state.pins.find((p) => p.id === m.parentId) ||
|
|
263
|
+
Object.values(state.options).flatMap((o) => o.reactions).find((r) => r.id === m.parentId);
|
|
264
|
+
if (parent) parent.replies.push({ id: m.parentId + '-r' + (parent.replies.length + 1), author: 'You (preview)', body, mine: true, when: 'just now', person: ME });
|
|
265
|
+
log('↩ Replied: "' + body + '"', m.parentId);
|
|
266
|
+
} else if (m.pin) {
|
|
267
|
+
const n = state.pins.length + 1, id = 'pin-' + n;
|
|
268
|
+
state.pins.push({ id, number: n, anchor: m.pin, mine: true, author: 'You (preview)', body, when: 'just now', person: ME, replies: [] });
|
|
269
|
+
if (m.optionId) option(m.optionId).comments++;
|
|
270
|
+
log('📍 Pin ' + n + ' on ' + m.pin.selector + ' (' + Math.round(m.pin.x * 100) + '%, ' + Math.round(m.pin.y * 100) + '%) at ' + m.pin.viewportWidth + 'px: "' + body + '"', id);
|
|
271
|
+
} else if (m.optionId) {
|
|
272
|
+
option(m.optionId).comments++;
|
|
273
|
+
log('💬 On ' + name(m.optionId) + ': "' + body + '"');
|
|
274
|
+
} else {
|
|
275
|
+
log('💬 "' + body + '"');
|
|
276
|
+
}
|
|
277
|
+
// Nothing is saved in a preview, so it always "saved" — the composer
|
|
278
|
+
// needs an answer either way or it sits there saying Saving….
|
|
279
|
+
post({ type: 'comment-result', ok: true, error: null });
|
|
280
|
+
push();
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
283
|
+
case 'focus-pin':
|
|
284
|
+
for (const li of list.querySelectorAll('li.focus')) li.classList.remove('focus');
|
|
285
|
+
list.querySelector('[data-pin="' + CSS.escape(m.pinId) + '"]')?.classList.add('focus');
|
|
286
|
+
break;
|
|
287
|
+
case 'unplaced-pins': { const key = m.pinIds.join(','); if (key && key !== lastUnplaced) log('⚠ Can’t place ' + m.pinIds.join(', ') + ' on this page — the element moved'); lastUnplaced = key; break; }
|
|
288
|
+
case 'delete-comment': {
|
|
289
|
+
// The whole thread goes, the way it does for real.
|
|
290
|
+
const before = state.pins.length;
|
|
291
|
+
state.pins = state.pins.filter((p) => p.id !== m.commentId);
|
|
292
|
+
for (const p of state.pins) p.replies = p.replies.filter((r) => r.id !== m.commentId);
|
|
293
|
+
let cleared = false;
|
|
294
|
+
for (const o of Object.values(state.options)) {
|
|
295
|
+
const kept = o.reactions.filter((r) => r.id !== m.commentId);
|
|
296
|
+
cleared = cleared || kept.length !== o.reactions.length;
|
|
297
|
+
o.reactions = kept;
|
|
298
|
+
for (const r of o.reactions) r.replies = r.replies.filter((x) => x.id !== m.commentId);
|
|
299
|
+
o.up = o.reactions.filter((r) => r.reaction === 'up').length;
|
|
300
|
+
o.down = o.reactions.filter((r) => r.reaction === 'down').length;
|
|
301
|
+
o.mine = (o.reactions.find((r) => r.mine) || {}).reaction || null;
|
|
302
|
+
}
|
|
303
|
+
log(cleared ? '🗑 Cleared a reaction' : before === state.pins.length ? '🗑 Deleted a reply' : '🗑 Deleted a comment and everything under it');
|
|
304
|
+
push();
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
document.getElementById('phone').addEventListener('click', (e) => {
|
|
311
|
+
const on = frame.classList.toggle('phone');
|
|
312
|
+
e.currentTarget.setAttribute('aria-pressed', String(on));
|
|
313
|
+
});
|
|
314
|
+
document.getElementById('reset').addEventListener('click', () => { state = { options: {}, pins: [] }; list.innerHTML = ''; push(); });
|
|
315
|
+
</script>
|
|
316
|
+
</body></html>`;
|
|
317
|
+
}
|
|
318
|
+
//# sourceMappingURL=proof-preview.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proof-preview.js","sourceRoot":"","sources":["../../src/lib/proof-preview.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { ProofStagedUpload, ProofVersionDetail, ScribeClient } from './client.js';
|
|
2
|
+
import type { ProofFile, ProofRef } from './proof-folder.js';
|
|
3
|
+
export type ProofApi = Pick<ScribeClient, 'stageProofVersion' | 'finalizeProofVersion' | 'getProofVersion' | 'downloadProofFile'>;
|
|
4
|
+
/**
|
|
5
|
+
* PUTs one blob to its presigned URL. Not through `ScribeClient`: the URL is
|
|
6
|
+
* storage's, not Scribe's, and must not carry the API token.
|
|
7
|
+
*
|
|
8
|
+
* `content-length` is left for fetch to set from the body: it's signed into
|
|
9
|
+
* the URL, and the body is exactly the declared bytes, so the two agree.
|
|
10
|
+
*/
|
|
11
|
+
export declare function uploadBlob(upload: ProofStagedUpload, bytes: Uint8Array, fetchImpl?: typeof fetch): Promise<void>;
|
|
12
|
+
export interface PushOutcome {
|
|
13
|
+
version: ProofVersionDetail;
|
|
14
|
+
previewUrl: string;
|
|
15
|
+
uploaded: number;
|
|
16
|
+
reused: number;
|
|
17
|
+
}
|
|
18
|
+
export declare function pushProof(args: {
|
|
19
|
+
api: ProofApi;
|
|
20
|
+
ref: ProofRef;
|
|
21
|
+
files: ProofFile[];
|
|
22
|
+
note?: string;
|
|
23
|
+
onUpload?: (path: string) => void;
|
|
24
|
+
upload?: (upload: ProofStagedUpload, bytes: Uint8Array) => Promise<void>;
|
|
25
|
+
}): Promise<PushOutcome>;
|
|
26
|
+
/**
|
|
27
|
+
* Downloads one version into `dir`, checking each file against its hash.
|
|
28
|
+
* Refuses a folder that already has files in it unless `force`, so a pull can
|
|
29
|
+
* never quietly overwrite the work in Ashley's current Proof folder.
|
|
30
|
+
*/
|
|
31
|
+
export declare function pullProof(args: {
|
|
32
|
+
api: ProofApi;
|
|
33
|
+
ref: ProofRef;
|
|
34
|
+
version: number | 'draft';
|
|
35
|
+
dir: string;
|
|
36
|
+
force?: boolean;
|
|
37
|
+
}): Promise<ProofVersionDetail>;
|
|
38
|
+
/** A pull that was refused before anything was downloaded. */
|
|
39
|
+
export declare class PullRefused extends Error {
|
|
40
|
+
}
|
|
41
|
+
export declare function defaultPullDir(cwd: string, ref: ProofRef, version: number | 'draft'): string;
|
|
42
|
+
//# sourceMappingURL=proof-push.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proof-push.d.ts","sourceRoot":"","sources":["../../src/lib/proof-push.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,iBAAiB,EAAE,kBAAkB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACvF,OAAO,KAAK,EAAE,SAAS,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAe7D,MAAM,MAAM,QAAQ,GAAG,IAAI,CAAC,YAAY,EAAE,mBAAmB,GAAG,sBAAsB,GAAG,iBAAiB,GAAG,mBAAmB,CAAC,CAAC;AAElI;;;;;;GAMG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,UAAU,EAAE,SAAS,GAAE,OAAO,KAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAS7H;AAUD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,kBAAkB,CAAC;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACpC,GAAG,EAAE,QAAQ,CAAC;IACd,GAAG,EAAE,QAAQ,CAAC;IACd,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,QAAQ,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IAClC,MAAM,CAAC,EAAE,CAAC,MAAM,EAAE,iBAAiB,EAAE,KAAK,EAAE,UAAU,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CAC1E,GAAG,OAAO,CAAC,WAAW,CAAC,CAmBvB;AAED;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,IAAI,EAAE;IACpC,GAAG,EAAE,QAAQ,CAAC;IACd,GAAG,EAAE,QAAQ,CAAC;IACd,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,OAAO,CAAC;CACjB,GAAG,OAAO,CAAC,kBAAkB,CAAC,CAsB9B;AAED,8DAA8D;AAC9D,qBAAa,WAAY,SAAQ,KAAK;CAAG;AAEzC,wBAAgB,cAAc,CAAC,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAE5F"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs';
|
|
3
|
+
import { dirname, join, resolve, sep } from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* RFC 0029 — moving a Proof's files between Ashley's folder and Scribe.
|
|
6
|
+
*
|
|
7
|
+
* A push is three calls: declare the files (the server answers with upload
|
|
8
|
+
* URLs for the ones it doesn't have), upload those straight to storage, then
|
|
9
|
+
* finalize (the server checks each upload's size and hash and makes it the
|
|
10
|
+
* draft). Unchanged files are never re-sent, because the server already holds
|
|
11
|
+
* them under their hash.
|
|
12
|
+
*/
|
|
13
|
+
/** How many uploads run at once. Enough to hide latency, few enough not to swamp a home connection. */
|
|
14
|
+
const UPLOAD_CONCURRENCY = 4;
|
|
15
|
+
/**
|
|
16
|
+
* PUTs one blob to its presigned URL. Not through `ScribeClient`: the URL is
|
|
17
|
+
* storage's, not Scribe's, and must not carry the API token.
|
|
18
|
+
*
|
|
19
|
+
* `content-length` is left for fetch to set from the body: it's signed into
|
|
20
|
+
* the URL, and the body is exactly the declared bytes, so the two agree.
|
|
21
|
+
*/
|
|
22
|
+
export async function uploadBlob(upload, bytes, fetchImpl = fetch) {
|
|
23
|
+
const headers = Object.fromEntries(Object.entries(upload.headers).filter(([name]) => name.toLowerCase() !== 'content-length'));
|
|
24
|
+
// Copied into a fresh ArrayBuffer-backed array: fetch's body type rejects a
|
|
25
|
+
// Uint8Array over a possibly-shared buffer, which is what readFileSync's is typed as.
|
|
26
|
+
const res = await fetchImpl(upload.url, { method: 'PUT', headers, body: new Uint8Array(bytes) });
|
|
27
|
+
if (!res.ok) {
|
|
28
|
+
const detail = (await res.text().catch(() => '')).slice(0, 300);
|
|
29
|
+
throw new Error(`Uploading ${upload.path} failed: ${res.status} ${res.statusText}${detail ? ` — ${detail}` : ''}`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
async function inBatches(items, size, fn) {
|
|
33
|
+
let next = 0;
|
|
34
|
+
const workers = Array.from({ length: Math.min(size, items.length) }, async () => {
|
|
35
|
+
while (next < items.length)
|
|
36
|
+
await fn(items[next++]);
|
|
37
|
+
});
|
|
38
|
+
await Promise.all(workers);
|
|
39
|
+
}
|
|
40
|
+
export async function pushProof(args) {
|
|
41
|
+
const { api, ref, files } = args;
|
|
42
|
+
const upload = args.upload ?? ((u, bytes) => uploadBlob(u, bytes));
|
|
43
|
+
const staged = await api.stageProofVersion(ref.client, ref.proof, {
|
|
44
|
+
manifest: files.map(({ path, sha256, bytes }) => ({ path, sha256, bytes })),
|
|
45
|
+
note: args.note ?? null,
|
|
46
|
+
});
|
|
47
|
+
const byHash = new Map(files.map((f) => [f.sha256, f]));
|
|
48
|
+
await inBatches(staged.uploads, UPLOAD_CONCURRENCY, async (u) => {
|
|
49
|
+
const file = byHash.get(u.sha256);
|
|
50
|
+
if (!file)
|
|
51
|
+
throw new Error(`The server asked for a file this folder doesn't have (${u.path})`);
|
|
52
|
+
await upload(u, readFileSync(file.absolute));
|
|
53
|
+
args.onUpload?.(u.path);
|
|
54
|
+
});
|
|
55
|
+
const finalized = await api.finalizeProofVersion(ref.client, ref.proof);
|
|
56
|
+
return { version: finalized.version, previewUrl: finalized.previewUrl, uploaded: staged.uploads.length, reused: staged.reused };
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Downloads one version into `dir`, checking each file against its hash.
|
|
60
|
+
* Refuses a folder that already has files in it unless `force`, so a pull can
|
|
61
|
+
* never quietly overwrite the work in Ashley's current Proof folder.
|
|
62
|
+
*/
|
|
63
|
+
export async function pullProof(args) {
|
|
64
|
+
const { api, ref } = args;
|
|
65
|
+
const dir = resolve(args.dir);
|
|
66
|
+
if (existsSync(dir) && readdirSync(dir).length > 0 && !args.force) {
|
|
67
|
+
throw new PullRefused(`${dir} isn't empty. Pull into a new folder, or pass --force to overwrite.`);
|
|
68
|
+
}
|
|
69
|
+
const version = await api.getProofVersion(ref.client, ref.proof, args.version);
|
|
70
|
+
for (const entry of version.manifest) {
|
|
71
|
+
const target = resolve(dir, ...entry.path.split('/'));
|
|
72
|
+
// The server validates paths at push time; this is the client's own check
|
|
73
|
+
// that a manifest can't write outside the folder it was asked to fill.
|
|
74
|
+
if (!target.startsWith(dir + sep))
|
|
75
|
+
throw new Error(`Refusing to write ${entry.path} outside ${dir}`);
|
|
76
|
+
const bytes = await api.downloadProofFile(ref.client, ref.proof, version.number, entry.path);
|
|
77
|
+
const sha = createHash('sha256').update(bytes).digest('hex');
|
|
78
|
+
if (sha !== entry.sha256)
|
|
79
|
+
throw new Error(`${entry.path} arrived corrupted (hash mismatch) — try the pull again`);
|
|
80
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
81
|
+
writeFileSync(target, bytes);
|
|
82
|
+
}
|
|
83
|
+
return version;
|
|
84
|
+
}
|
|
85
|
+
/** A pull that was refused before anything was downloaded. */
|
|
86
|
+
export class PullRefused extends Error {
|
|
87
|
+
}
|
|
88
|
+
export function defaultPullDir(cwd, ref, version) {
|
|
89
|
+
return join(cwd, `${ref.proof}-${version === 'draft' ? 'draft' : `v${version}`}`);
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=proof-push.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"proof-push.js","sourceRoot":"","sources":["../../src/lib/proof-push.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/skill.d.ts
CHANGED
|
@@ -42,4 +42,17 @@ export declare function installSkill(options?: {
|
|
|
42
42
|
}): SkillInstallResult;
|
|
43
43
|
/** Shortens a path for display, so output reads `~/.claude/skills/...`. */
|
|
44
44
|
export declare function tildify(path: string): string;
|
|
45
|
+
/**
|
|
46
|
+
* Installs the bundled skill and says what happened in one line.
|
|
47
|
+
*
|
|
48
|
+
* Called from `scribe login` as well as `scribe update`, so setting up a
|
|
49
|
+
* machine is install-then-login and nothing else. Before this, the setup guide
|
|
50
|
+
* had to tell someone to run `scribe update` on a fresh install purely to copy
|
|
51
|
+
* the skill across — an `npm install -g` of the version they had just
|
|
52
|
+
* installed, to do a directory copy.
|
|
53
|
+
*
|
|
54
|
+
* Silent on a symlinked destination: that's a maintainer editing the skill in
|
|
55
|
+
* a checkout, and they don't need telling on every login.
|
|
56
|
+
*/
|
|
57
|
+
export declare function reportSkillInstall(): void;
|
|
45
58
|
//# sourceMappingURL=skill.d.ts.map
|
package/dist/lib/skill.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill.d.ts","sourceRoot":"","sources":["../../src/lib/skill.ts"],"names":[],"mappings":"AAKA,sFAAsF;AACtF,eAAO,MAAM,UAAU,wBAAwB,CAAC;AAEhD,6DAA6D;AAC7D,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAOhD;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,MAAM,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAWjC;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,kBAAkB,
|
|
1
|
+
{"version":3,"file":"skill.d.ts","sourceRoot":"","sources":["../../src/lib/skill.ts"],"names":[],"mappings":"AAKA,sFAAsF;AACtF,eAAO,MAAM,UAAU,wBAAwB,CAAC;AAEhD,6DAA6D;AAC7D,wBAAgB,gBAAgB,IAAI,MAAM,CAEzC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAOhD;AAED,MAAM,MAAM,kBAAkB,GAC1B;IAAE,MAAM,EAAE,WAAW,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACrC;IAAE,MAAM,EAAE,SAAS,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,GACnC;IAAE,MAAM,EAAE,gBAAgB,CAAA;CAAE,CAAC;AAWjC;;;;;;GAMG;AACH,wBAAgB,YAAY,CAAC,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,kBAAkB,CA0BxG;AAED,2EAA2E;AAC3E,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG5C;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAOzC"}
|
package/dist/lib/skill.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { cpSync, existsSync, lstatSync, mkdirSync, rmSync } from 'node:fs';
|
|
1
|
+
import { cpSync, existsSync, lstatSync, mkdirSync, renameSync, rmSync } from 'node:fs';
|
|
2
2
|
import { homedir } from 'node:os';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
@@ -58,8 +58,18 @@ export function installSkill(options = {}) {
|
|
|
58
58
|
if (existing?.isSymbolicLink())
|
|
59
59
|
return { status: 'symlink', dest };
|
|
60
60
|
mkdirSync(dirname(dest), { recursive: true });
|
|
61
|
+
// Staged beside the destination and swapped in, rather than removed and
|
|
62
|
+
// re-copied in place. Claude Code reads this directory, and a session that
|
|
63
|
+
// looks during a remove-then-copy finds a skill that has no SKILL.md or no
|
|
64
|
+
// recipes — briefly, but on a machine where the update runs at session start
|
|
65
|
+
// that window overlaps exactly the moment something is reading.
|
|
66
|
+
const staging = `${dest}.scribe-tmp`;
|
|
67
|
+
rmSync(staging, { recursive: true, force: true });
|
|
68
|
+
cpSync(source, staging, { recursive: true });
|
|
69
|
+
// `rename` onto a non-empty directory fails, so the old copy goes first.
|
|
70
|
+
// The gap between these two is a rename, not a recursive file copy.
|
|
61
71
|
rmSync(dest, { recursive: true, force: true });
|
|
62
|
-
|
|
72
|
+
renameSync(staging, dest);
|
|
63
73
|
return { status: 'installed', dest };
|
|
64
74
|
}
|
|
65
75
|
/** Shortens a path for display, so output reads `~/.claude/skills/...`. */
|
|
@@ -67,4 +77,25 @@ export function tildify(path) {
|
|
|
67
77
|
const home = homedir();
|
|
68
78
|
return path.startsWith(home) ? `~${path.slice(home.length)}` : path;
|
|
69
79
|
}
|
|
80
|
+
/**
|
|
81
|
+
* Installs the bundled skill and says what happened in one line.
|
|
82
|
+
*
|
|
83
|
+
* Called from `scribe login` as well as `scribe update`, so setting up a
|
|
84
|
+
* machine is install-then-login and nothing else. Before this, the setup guide
|
|
85
|
+
* had to tell someone to run `scribe update` on a fresh install purely to copy
|
|
86
|
+
* the skill across — an `npm install -g` of the version they had just
|
|
87
|
+
* installed, to do a directory copy.
|
|
88
|
+
*
|
|
89
|
+
* Silent on a symlinked destination: that's a maintainer editing the skill in
|
|
90
|
+
* a checkout, and they don't need telling on every login.
|
|
91
|
+
*/
|
|
92
|
+
export function reportSkillInstall() {
|
|
93
|
+
const skill = installSkill();
|
|
94
|
+
if (skill.status === 'installed') {
|
|
95
|
+
console.log(`Skill: installed scribe-site-builder to ${tildify(skill.dest)}.`);
|
|
96
|
+
}
|
|
97
|
+
else if (skill.status === 'missing-source') {
|
|
98
|
+
console.warn("Skill: this install is missing its bundled copy of scribe-site-builder — tell Dave.");
|
|
99
|
+
}
|
|
100
|
+
}
|
|
70
101
|
//# sourceMappingURL=skill.js.map
|
package/dist/lib/skill.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skill.js","sourceRoot":"","sources":["../../src/lib/skill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,MAAM,EAAc,MAAM,SAAS,CAAC;
|
|
1
|
+
{"version":3,"file":"skill.js","sourceRoot":"","sources":["../../src/lib/skill.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,UAAU,EAAE,SAAS,EAAE,SAAS,EAAE,UAAU,EAAE,MAAM,EAAc,MAAM,SAAS,CAAC;AACnG,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAC1C,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,sFAAsF;AACtF,MAAM,CAAC,MAAM,UAAU,GAAG,qBAAqB,CAAC;AAEhD,6DAA6D;AAC7D,MAAM,UAAU,gBAAgB;IAC9B,OAAO,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,QAAQ,EAAE,UAAU,CAAC,CAAC;AAC1D,CAAC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB;IAC9B,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,MAAM,UAAU,GAAG;QACjB,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,CAAC;QAC5C,IAAI,CAAC,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,CAAC;KACzD,CAAC;IACF,OAAO,UAAU,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC;AACzF,CAAC;AAOD,0FAA0F;AAC1F,SAAS,WAAW,CAAC,IAAY;IAC/B,IAAI,CAAC;QACH,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC;IACzB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED;;;;;;GAMG;AACH,MAAM,UAAU,YAAY,CAAC,UAAqD,EAAE;IAClF,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,gBAAgB,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,MAAM,CAAC;IAClF,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,gBAAgB,EAAE,CAAC;IAEhD,IAAI,CAAC,MAAM;QAAE,OAAO,EAAE,MAAM,EAAE,gBAAgB,EAAE,CAAC;IAEjD,MAAM,QAAQ,GAAG,WAAW,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,QAAQ,EAAE,cAAc,EAAE;QAAE,OAAO,EAAE,MAAM,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC;IAEnE,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE9C,wEAAwE;IACxE,2EAA2E;IAC3E,2EAA2E;IAC3E,6EAA6E;IAC7E,gEAAgE;IAChE,MAAM,OAAO,GAAG,GAAG,IAAI,aAAa,CAAC;IACrC,MAAM,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAClD,MAAM,CAAC,MAAM,EAAE,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAE7C,yEAAyE;IACzE,oEAAoE;IACpE,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,UAAU,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;IAE1B,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC;AACvC,CAAC;AAED,2EAA2E;AAC3E,MAAM,UAAU,OAAO,CAAC,IAAY;IAClC,MAAM,IAAI,GAAG,OAAO,EAAE,CAAC;IACvB,OAAO,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;AACtE,CAAC;AAED;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,kBAAkB;IAChC,MAAM,KAAK,GAAG,YAAY,EAAE,CAAC;IAC7B,IAAI,KAAK,CAAC,MAAM,KAAK,WAAW,EAAE,CAAC;QACjC,OAAO,CAAC,GAAG,CAAC,2CAA2C,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IACjF,CAAC;SAAM,IAAI,KAAK,CAAC,MAAM,KAAK,gBAAgB,EAAE,CAAC;QAC7C,OAAO,CAAC,IAAI,CAAC,qFAAqF,CAAC,CAAC;IACtG,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
export declare const PACKAGE_NAME = "@scribe-platform/cli";
|
|
2
|
+
/**
|
|
3
|
+
* Compares two dotted numeric versions. Returns true when `candidate` is newer
|
|
4
|
+
* than `current`.
|
|
5
|
+
*
|
|
6
|
+
* Deliberately not a semver dependency: this package publishes plain
|
|
7
|
+
* `major.minor.patch` and the one judgement call — what to do with a
|
|
8
|
+
* prerelease suffix — is answered by ignoring it. A `0.8.0-beta.1` compares
|
|
9
|
+
* equal to `0.8.0`, so someone already on the beta is not told to "update" to
|
|
10
|
+
* the release they effectively have, and someone on `0.7.0` is offered it.
|
|
11
|
+
* Shipping a prerelease to `latest` is not something this project does.
|
|
12
|
+
*/
|
|
13
|
+
export declare function isNewer(candidate: string, current: string): boolean;
|
|
14
|
+
export interface UpdateCheck {
|
|
15
|
+
current: string;
|
|
16
|
+
/** Null when the registry couldn't be reached. */
|
|
17
|
+
latest: string | null;
|
|
18
|
+
updateAvailable: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* False when the registry could not be reached.
|
|
21
|
+
*
|
|
22
|
+
* Reported separately from `latest` so a caller can't say "up to date" when
|
|
23
|
+
* it means "couldn't ask" — the two are the same shape and only one of them
|
|
24
|
+
* is a fact.
|
|
25
|
+
*/
|
|
26
|
+
reachable: boolean;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Whether a newer CLI has been published.
|
|
30
|
+
*
|
|
31
|
+
* Runs on **every** session start, with no cache. An earlier version cached
|
|
32
|
+
* the answer for a day to keep a round trip off the front of each
|
|
33
|
+
* conversation; measuring it made that look like the wrong trade — 210ms
|
|
34
|
+
* against being up to a day late on a fix, plus a cache that silently
|
|
35
|
+
* swallowed the environment override this is tested with. Checking every time
|
|
36
|
+
* is both faster to the user and less code.
|
|
37
|
+
*/
|
|
38
|
+
export declare function checkForUpdate(current: string): Promise<UpdateCheck>;
|
|
39
|
+
/**
|
|
40
|
+
* What `npm install -g` is pointed at.
|
|
41
|
+
*
|
|
42
|
+
* Overridable so the whole update path can be exercised against a local
|
|
43
|
+
* `npm pack` tarball before anything is published — see TESTING-UPDATES.md.
|
|
44
|
+
*/
|
|
45
|
+
export declare function installSpec(): string;
|
|
46
|
+
//# sourceMappingURL=update-check.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"update-check.d.ts","sourceRoot":"","sources":["../../src/lib/update-check.ts"],"names":[],"mappings":"AAAA,eAAO,MAAM,YAAY,yBAAyB,CAAC;AAsBnD;;;;;;;;;;GAUG;AACH,wBAAgB,OAAO,CAAC,SAAS,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAiBnE;AAuBD,MAAM,WAAW,WAAW;IAC1B,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,eAAe,EAAE,OAAO,CAAC;IACzB;;;;;;OAMG;IACH,SAAS,EAAE,OAAO,CAAC;CACpB;AAED;;;;;;;;;GASG;AACH,wBAAsB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAe1E;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,IAAI,MAAM,CAEpC"}
|