@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.
Files changed (65) hide show
  1. package/README.md +28 -0
  2. package/dist/commands/login.d.ts.map +1 -1
  3. package/dist/commands/login.js +15 -1
  4. package/dist/commands/login.js.map +1 -1
  5. package/dist/commands/proof.d.ts +5 -0
  6. package/dist/commands/proof.d.ts.map +1 -0
  7. package/dist/commands/proof.js +440 -0
  8. package/dist/commands/proof.js.map +1 -0
  9. package/dist/commands/prospect.d.ts +27 -0
  10. package/dist/commands/prospect.d.ts.map +1 -0
  11. package/dist/commands/prospect.js +222 -0
  12. package/dist/commands/prospect.js.map +1 -0
  13. package/dist/commands/request.d.ts +38 -0
  14. package/dist/commands/request.d.ts.map +1 -0
  15. package/dist/commands/request.js +214 -0
  16. package/dist/commands/request.js.map +1 -0
  17. package/dist/commands/site.d.ts +11 -0
  18. package/dist/commands/site.d.ts.map +1 -1
  19. package/dist/commands/site.js +41 -3
  20. package/dist/commands/site.js.map +1 -1
  21. package/dist/commands/update.d.ts +10 -0
  22. package/dist/commands/update.d.ts.map +1 -1
  23. package/dist/commands/update.js +128 -31
  24. package/dist/commands/update.js.map +1 -1
  25. package/dist/index.d.ts.map +1 -1
  26. package/dist/index.js +15 -1
  27. package/dist/index.js.map +1 -1
  28. package/dist/lib/claude-hook.d.ts +74 -0
  29. package/dist/lib/claude-hook.d.ts.map +1 -0
  30. package/dist/lib/claude-hook.js +108 -0
  31. package/dist/lib/claude-hook.js.map +1 -0
  32. package/dist/lib/client.d.ts +351 -0
  33. package/dist/lib/client.d.ts.map +1 -1
  34. package/dist/lib/client.js +159 -0
  35. package/dist/lib/client.js.map +1 -1
  36. package/dist/lib/proof-folder.d.ts +86 -0
  37. package/dist/lib/proof-folder.d.ts.map +1 -0
  38. package/dist/lib/proof-folder.js +226 -0
  39. package/dist/lib/proof-folder.js.map +1 -0
  40. package/dist/lib/proof-preview.d.ts +39 -0
  41. package/dist/lib/proof-preview.d.ts.map +1 -0
  42. package/dist/lib/proof-preview.js +318 -0
  43. package/dist/lib/proof-preview.js.map +1 -0
  44. package/dist/lib/proof-push.d.ts +42 -0
  45. package/dist/lib/proof-push.d.ts.map +1 -0
  46. package/dist/lib/proof-push.js +91 -0
  47. package/dist/lib/proof-push.js.map +1 -0
  48. package/dist/lib/skill.d.ts +13 -0
  49. package/dist/lib/skill.d.ts.map +1 -1
  50. package/dist/lib/skill.js +33 -2
  51. package/dist/lib/skill.js.map +1 -1
  52. package/dist/lib/update-check.d.ts +46 -0
  53. package/dist/lib/update-check.d.ts.map +1 -0
  54. package/dist/lib/update-check.js +103 -0
  55. package/dist/lib/update-check.js.map +1 -0
  56. package/package.json +5 -3
  57. package/runtime/proof.js +139 -0
  58. package/skills/scribe-site-builder/SKILL.md +175 -33
  59. package/skills/scribe-site-builder/recipes/README.md +11 -3
  60. package/skills/scribe-site-builder/recipes/domains.md +151 -0
  61. package/skills/scribe-site-builder/recipes/forms.md +5 -2
  62. package/skills/scribe-site-builder/recipes/proofs.md +311 -0
  63. package/skills/scribe-site-builder/recipes/prospects.md +192 -0
  64. package/skills/scribe-site-builder/recipes/requests.md +145 -0
  65. 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) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;', '"': '&quot;', "'": '&#39;' })[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":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AACzC,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC1F,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,MAAM,WAAW,CAAC;AAIxD;;;;;;;;GAQG;AAEH,uGAAuG;AACvG,MAAM,kBAAkB,GAAG,CAAC,CAAC;AAI7B;;;;;;GAMG;AACH,MAAM,CAAC,KAAK,UAAU,UAAU,CAAC,MAAyB,EAAE,KAAiB,EAAE,YAA0B,KAAK;IAC5G,MAAM,OAAO,GAAG,MAAM,CAAC,WAAW,CAAC,MAAM,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC,WAAW,EAAE,KAAK,gBAAgB,CAAC,CAAC,CAAC;IAC/H,4EAA4E;IAC5E,sFAAsF;IACtF,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,MAAM,CAAC,GAAG,EAAE,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,UAAU,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC;IACjG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC;QACZ,MAAM,MAAM,GAAG,CAAC,MAAM,GAAG,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;QAChE,MAAM,IAAI,KAAK,CAAC,aAAa,MAAM,CAAC,IAAI,YAAY,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,UAAU,GAAG,MAAM,CAAC,CAAC,CAAC,MAAM,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACrH,CAAC;AACH,CAAC;AAED,KAAK,UAAU,SAAS,CAAI,KAAU,EAAE,IAAY,EAAE,EAA8B;IAClF,IAAI,IAAI,GAAG,CAAC,CAAC;IACb,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,IAAI,CAAC,GAAG,CAAC,IAAI,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,EAAE,KAAK,IAAI,EAAE;QAC9E,OAAO,IAAI,GAAG,KAAK,CAAC,MAAM;YAAE,MAAM,EAAE,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC;IACtD,CAAC,CAAC,CAAC;IACH,MAAM,OAAO,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC;AAC7B,CAAC;AASD,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAO/B;IACC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,KAAK,EAAE,GAAG,IAAI,CAAC;IACjC,MAAM,MAAM,GAAG,IAAI,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC;IAEnE,MAAM,MAAM,GAAG,MAAM,GAAG,CAAC,iBAAiB,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,EAAE;QAChE,QAAQ,EAAE,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,CAAC;QAC3E,IAAI,EAAE,IAAI,CAAC,IAAI,IAAI,IAAI;KACxB,CAAC,CAAC;IAEH,MAAM,MAAM,GAAG,IAAI,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;IACxD,MAAM,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,kBAAkB,EAAE,KAAK,EAAE,CAAC,EAAE,EAAE;QAC9D,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC;QAClC,IAAI,CAAC,IAAI;YAAE,MAAM,IAAI,KAAK,CAAC,yDAAyD,CAAC,CAAC,IAAI,GAAG,CAAC,CAAC;QAC/F,MAAM,MAAM,CAAC,CAAC,EAAE,YAAY,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC;QAC7C,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEH,MAAM,SAAS,GAAG,MAAM,GAAG,CAAC,oBAAoB,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC;IACxE,OAAO,EAAE,OAAO,EAAE,SAAS,CAAC,OAAO,EAAE,UAAU,EAAE,SAAS,CAAC,UAAU,EAAE,QAAQ,EAAE,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,CAAC;AAClI,CAAC;AAED;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAAC,IAM/B;IACC,MAAM,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;IAC1B,MAAM,GAAG,GAAG,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;IAC9B,IAAI,UAAU,CAAC,GAAG,CAAC,IAAI,WAAW,CAAC,GAAG,CAAC,CAAC,MAAM,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,KAAK,EAAE,CAAC;QAClE,MAAM,IAAI,WAAW,CAAC,GAAG,GAAG,qEAAqE,CAAC,CAAC;IACrG,CAAC;IAED,MAAM,OAAO,GAAG,MAAM,GAAG,CAAC,eAAe,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,EAAE,IAAI,CAAC,OAAO,CAAC,CAAC;IAC/E,KAAK,MAAM,KAAK,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrC,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,EAAE,GAAG,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC;QACtD,0EAA0E;QAC1E,uEAAuE;QACvE,IAAI,CAAC,MAAM,CAAC,UAAU,CAAC,GAAG,GAAG,GAAG,CAAC;YAAE,MAAM,IAAI,KAAK,CAAC,qBAAqB,KAAK,CAAC,IAAI,YAAY,GAAG,EAAE,CAAC,CAAC;QAErG,MAAM,KAAK,GAAG,MAAM,GAAG,CAAC,iBAAiB,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,CAAC,KAAK,EAAE,OAAO,CAAC,MAAM,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAC7F,MAAM,GAAG,GAAG,UAAU,CAAC,QAAQ,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QAC7D,IAAI,GAAG,KAAK,KAAK,CAAC,MAAM;YAAE,MAAM,IAAI,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,yDAAyD,CAAC,CAAC;QAElH,SAAS,CAAC,OAAO,CAAC,MAAM,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAChD,aAAa,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;IAC/B,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,8DAA8D;AAC9D,MAAM,OAAO,WAAY,SAAQ,KAAK;CAAG;AAEzC,MAAM,UAAU,cAAc,CAAC,GAAW,EAAE,GAAa,EAAE,OAAyB;IAClF,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,GAAG,CAAC,KAAK,IAAI,OAAO,KAAK,OAAO,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,OAAO,EAAE,EAAE,CAAC,CAAC;AACpF,CAAC"}
@@ -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
@@ -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,CAaxG;AAED,2EAA2E;AAC3E,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG5C"}
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
- cpSync(source, dest, { recursive: true });
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
@@ -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;AACvF,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;IAC9C,MAAM,CAAC,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,CAAC;IAC/C,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC1C,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"}
1
+ {"version":3,"file":"skill.js","sourceRoot":"","sources":["../../src/lib/skill.ts"],"names":[],"mappings":"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"}
@@ -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"}