heyquilt 0.3.121 → 0.3.138
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/RELEASES.md +292 -17
- package/bin/quilt.js +72 -5
- package/desktop/main.js +16 -2
- package/package.json +3 -1
- package/src/agent-task-workflow.js +2 -2
- package/src/agents/common.js +13 -0
- package/src/ai-vm.js +9 -1
- package/src/api/activity.js +5 -0
- package/src/api/memory-store.js +21 -10
- package/src/api/notify.js +134 -0
- package/src/api/routes/browsers.js +29 -0
- package/src/api/routes/github.js +0 -13
- package/src/api/routes/relay.js +26 -3
- package/src/api/routes/seam-connect.js +103 -0
- package/src/api/routes/session-migrate.js +39 -0
- package/src/api/routes/sessions.js +73 -1
- package/src/api/server.js +46 -12
- package/src/api/supabase-store.js +53 -4
- package/src/autowork.js +182 -16
- package/src/connection.js +39 -9
- package/src/control.js +139 -13
- package/src/force-date.js +27 -0
- package/src/fsutil.js +13 -3
- package/src/gitstate.js +38 -2
- package/src/history.js +25 -1
- package/src/mcp.js +93 -7
- package/src/merge3.js +11 -3
- package/src/now-summary.js +113 -0
- package/src/pathrules.js +12 -0
- package/src/protocol.js +10 -2
- package/src/quilt-default.js +84 -0
- package/src/quilt-history.js +106 -0
- package/src/quilt-remote.js +78 -0
- package/src/relay-commit.js +5 -0
- package/src/relay-lease.js +6 -2
- package/src/relay-mcp.js +172 -8
- package/src/relay-outbound.js +1 -1
- package/src/relay-prune.js +4 -1
- package/src/release-words.js +2 -2
- package/src/runner.js +20 -7
- package/src/seam-access.js +97 -0
- package/src/seam-claims.js +53 -0
- package/src/seam-url.js +15 -0
- package/src/server.js +1767 -33
- package/src/session-follow.js +515 -0
- package/src/session-layout3.js +466 -0
- package/src/session-migrate.js +123 -0
- package/src/session-quilt.js +145 -0
- package/src/session-seam.js +284 -0
- package/src/session.js +899 -46
- package/src/source-live/bring-in.js +283 -0
- package/src/source-live/live-block.js +507 -35
- package/src/source-live/live-square.js +17 -6
- package/src/status.js +5 -0
- package/src/ui/agent-guide.js +1 -1
- package/src/ui/app.css +85 -20
- package/src/ui/app.js +4 -1
- package/src/ui/chat.js +9 -3
- package/src/ui/cloud-ai-model.js +13 -0
- package/src/ui/cloud-ai.js +10 -2
- package/src/ui/common.js +6 -2
- package/src/ui/fileview.js +20 -7
- package/src/ui/force-words.js +19 -0
- package/src/ui/git-rule.js +9 -0
- package/src/ui/home.js +0 -147
- package/src/ui/loom-model.js +15 -1
- package/src/ui/quiltview.js +213 -0
- package/src/ui/seam-model.js +251 -0
- package/src/ui/session.js +413 -31
- package/src/ui/voice-call.js +83 -14
- package/src/ui/voice-model.js +39 -0
- package/src/ui/voice.js +167 -17
- package/src/ui-server.js +80 -28
- package/src/update-check.js +25 -1
- package/src/voice.js +13 -6
- package/src/git.js +0 -147
- package/src/quilts/AGENTS.md +0 -1
- package/src/quilts/CLAUDE.md +0 -1
- package/src/quilts/archiver.js +0 -175
- package/src/quilts/autorevert.js +0 -49
- package/src/quilts/bisect-run.js +0 -31
- package/src/quilts/blame.js +0 -103
- package/src/quilts/block.js +0 -347
- package/src/quilts/bucket.js +0 -25
- package/src/quilts/cbor.js +0 -92
- package/src/quilts/claims.js +0 -144
- package/src/quilts/cli.js +0 -449
- package/src/quilts/client.js +0 -128
- package/src/quilts/conflict-format.js +0 -190
- package/src/quilts/describe.js +0 -266
- package/src/quilts/dogfood.js +0 -82
- package/src/quilts/errors.js +0 -8
- package/src/quilts/events.js +0 -160
- package/src/quilts/export-git.js +0 -130
- package/src/quilts/export.js +0 -81
- package/src/quilts/fastmerge.js +0 -41
- package/src/quilts/hash.js +0 -44
- package/src/quilts/history.js +0 -266
- package/src/quilts/ids.js +0 -12
- package/src/quilts/import-git.js +0 -291
- package/src/quilts/import.js +0 -134
- package/src/quilts/index.js +0 -72
- package/src/quilts/limits.js +0 -67
- package/src/quilts/lines.js +0 -49
- package/src/quilts/live.js +0 -50
- package/src/quilts/merge-smart.js +0 -114
- package/src/quilts/merge.js +0 -486
- package/src/quilts/objects.js +0 -144
- package/src/quilts/pack.js +0 -118
- package/src/quilts/records.js +0 -211
- package/src/quilts/resolutions.js +0 -69
- package/src/quilts/reviews.js +0 -195
- package/src/quilts/routes.js +0 -126
- package/src/quilts/service.js +0 -923
- package/src/quilts/setup.js +0 -34
- package/src/quilts/signing.js +0 -149
- package/src/quilts/squares.js +0 -239
- package/src/quilts/store-memory.js +0 -683
- package/src/quilts/store-rpc.js +0 -79
- package/src/quilts/store-supabase.js +0 -13
- package/src/quilts/tags.js +0 -64
- package/src/quilts/textdiff.js +0 -96
- package/src/quilts/tokens.js +0 -66
- package/src/quilts/tools.js +0 -375
- package/src/quilts/tree.js +0 -351
- package/src/quilts/webhook.js +0 -116
- package/src/quilts/window.js +0 -119
- package/src/quilts/xdiff.js +0 -435
- package/src/ui/branches.js +0 -111
package/src/quilts/store-rpc.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
// The quilt store over Postgres functions (supabase/migrations/20261024100000_quilts.sql): each
|
|
2
|
-
// method is one call of one function with one jsonb argument, so each is one transaction, and
|
|
3
|
-
// the commit and merge transactions hold the quilt row lock in the database. `rpc(fn, p)` makes
|
|
4
|
-
// the call: supabase-js in production (store-supabase.js), psql in the local tests.
|
|
5
|
-
import { StoreError } from './store-memory.js'
|
|
6
|
-
|
|
7
|
-
/** A refusal raised by quilt_refuse ('quilt:<code>:<json>') as the store's own error. */
|
|
8
|
-
export function toStoreError (message) {
|
|
9
|
-
const m = String(message || '').match(/quilt:([a-z_]+):(\{.*\})/s)
|
|
10
|
-
if (!m) return null
|
|
11
|
-
let extra = {}
|
|
12
|
-
try { extra = JSON.parse(m[2]) } catch {}
|
|
13
|
-
const { message: msg, ...rest } = extra
|
|
14
|
-
return new StoreError(m[1], msg || m[1], rest)
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function createRpcQuiltStore (rpc) {
|
|
18
|
-
const call = async (fn, p) => {
|
|
19
|
-
try { return await rpc(fn, p) } catch (err) { throw toStoreError(err?.message) || err }
|
|
20
|
-
}
|
|
21
|
-
const writes = {
|
|
22
|
-
createQuilt: 'quilt_create_quilt', setMember: 'quilt_set_member', sealPack: 'quilt_seal_pack', stageFiles: 'quilt_stage_files',
|
|
23
|
-
openSquare: 'quilt_open_square', commit: 'quilt_commit', beginMerge: 'quilt_begin_merge', endMerge: 'quilt_end_merge', merge: 'quilt_merge',
|
|
24
|
-
importCommit: 'quilt_import_commit', setSnapshot: 'quilt_set_snapshot', setSquare: 'quilt_set_square', setGitSha: 'quilt_set_git_sha', remember: 'quilt_remember',
|
|
25
|
-
addEvents: 'quilt_add_events', setWebhook: 'quilt_set_webhook', takeLease: 'quilt_take_lease', openAndCommit: 'quilt_open_and_commit', giveLease: 'quilt_give_lease', addStubs: 'quilt_add_stubs', fillLinePaths: 'quilt_fill_line_paths', setConflicted: 'quilt_set_conflicted', setArchive: 'quilt_set_archive', setLineWindow: 'quilt_set_line_window', createLine: 'quilt_create_line', setLineFrozen: 'quilt_set_line_frozen', createTag: 'quilt_create_tag', setLive: 'quilt_set_live', createClaim: 'quilt_create_claim', deleteClaim: 'quilt_delete_claim', setResolution: 'quilt_set_resolution', deleteResolution: 'quilt_delete_resolution', addReview: 'quilt_add_review', setLineRule: 'quilt_set_line_rule', setSquareCheck: 'quilt_set_square_check', addSigningKey: 'quilt_add_signing_key', removeSigningKey: 'quilt_remove_signing_key', addSignature: 'quilt_add_signature', createToken: 'quilt_create_token', revokeToken: 'quilt_revoke_token'
|
|
26
|
-
}
|
|
27
|
-
const store = {}
|
|
28
|
-
for (const [name, fn] of Object.entries(writes)) store[name] = (args) => call(fn, args)
|
|
29
|
-
return Object.assign(store, {
|
|
30
|
-
getQuilt: (id) => call('quilt_get_quilt', { id }),
|
|
31
|
-
quiltBySlug: (owner, slug) => call('quilt_quilt_by_slug', { owner, slug }),
|
|
32
|
-
quiltsFor: (member) => call('quilt_quilts_for', { member }),
|
|
33
|
-
memberLevel: (q, member) => call('quilt_member_level', { quilt_id: q, member }),
|
|
34
|
-
members: (q) => call('quilt_members_of', { quilt_id: q }),
|
|
35
|
-
getLine: (q, name) => call('quilt_get_line', { quilt_id: q, name }),
|
|
36
|
-
lines: (q) => call('quilt_lines_of', { quilt_id: q }),
|
|
37
|
-
getTag: (q, name) => call('quilt_get_tag', { quilt_id: q, name }),
|
|
38
|
-
getLive: (q, sq) => call('quilt_get_live', { quilt_id: q, square_id: sq }),
|
|
39
|
-
tags: (q) => call('quilt_tags_of', { quilt_id: q }),
|
|
40
|
-
claims: (q) => call('quilt_claims_of', { quilt_id: q }),
|
|
41
|
-
tokens: (q) => call('quilt_tokens_of', { quilt_id: q }),
|
|
42
|
-
tokenByHash: (hash) => call('quilt_token_by_hash', { hash }),
|
|
43
|
-
signingKey: (id) => call('quilt_signing_key', { id }),
|
|
44
|
-
signingKeys: (member) => call('quilt_signing_keys_of', { member }),
|
|
45
|
-
signaturesOf: (q, commits) => call('quilt_signatures_of', { quilt_id: q, commits }),
|
|
46
|
-
reviews: (q, sq) => call('quilt_reviews_of', { quilt_id: q, square_id: sq }),
|
|
47
|
-
squareChecks: (q, sq) => call('quilt_square_checks_of', { quilt_id: q, square_id: sq }),
|
|
48
|
-
lineRules: (q) => call('quilt_line_rules_of', { quilt_id: q }),
|
|
49
|
-
resolution: (q, key) => call('quilt_resolution', { quilt_id: q, key }),
|
|
50
|
-
resolutions: (q) => call('quilt_resolutions_of', { quilt_id: q }),
|
|
51
|
-
getSquare: (id) => call('quilt_get_square', { id }),
|
|
52
|
-
squares: (q, { state, holder, task } = {}) => call('quilt_squares_of', { quilt_id: q, state, holder, task }),
|
|
53
|
-
getCommit: (q, id) => call('quilt_get_commit', { quilt_id: q, id }),
|
|
54
|
-
commitAtSeq: (q, seq) => call('quilt_commit_at_seq', { quilt_id: q, seq }),
|
|
55
|
-
squaresByTail: (q, tail) => call('quilt_squares_by_tail', { quilt_id: q, tail }),
|
|
56
|
-
resolvePrefix: (q, prefix) => call('quilt_resolve_prefix', { quilt_id: q, prefix }),
|
|
57
|
-
squareCommits: (q, squareId) => call('quilt_square_commits', { quilt_id: q, square_id: squareId }),
|
|
58
|
-
getBlob: (q, id) => call('quilt_get_blob', { quilt_id: q, id }),
|
|
59
|
-
hasBlobs: (q, ids) => call('quilt_has_blobs', { quilt_id: q, ids }),
|
|
60
|
-
hasObjects: (q, ids) => call('quilt_has_objects', { quilt_id: q, ids }),
|
|
61
|
-
getObject: (q, id) => call('quilt_get_object', { quilt_id: q, id }),
|
|
62
|
-
patch: (q, line, n) => call('quilt_patch', { quilt_id: q, line, patch_no: n }),
|
|
63
|
-
patches: (q, line, { after = 0, before, limit = 50, desc = true } = {}) => call('quilt_patches', { quilt_id: q, line, after, before: Number.isFinite(before) ? before : undefined, limit, desc }),
|
|
64
|
-
linePatchesView: (q, line, { before, limit = 100 } = {}) => call('quilt_line_patches_view', { quilt_id: q, line, before: Number.isFinite(before) ? before : undefined, limit }),
|
|
65
|
-
eventCursor: async (q) => Number(await call('quilt_event_cursor', { quilt_id: q })) || 0,
|
|
66
|
-
eventsFor: (q, member, { after = 0, kinds, limit = 100 } = {}) => call('quilt_events_for', { quilt_id: q, member, after, kinds, limit }),
|
|
67
|
-
webhook: (q, member) => call('quilt_webhook', { quilt_id: q, member }),
|
|
68
|
-
logRows: (q, { after = 0, limit = 100, kinds } = {}) => call('quilt_log_rows', { quilt_id: q, after, limit, kinds }),
|
|
69
|
-
squarePathCommits: (q, squareId, path, { upTo, limit = 1000 } = {}) => call('quilt_square_path_commits', { quilt_id: q, square_id: squareId, path, up_to: Number.isFinite(upTo) ? upTo : undefined, limit }),
|
|
70
|
-
linePaths: (q, line, path, { upTo, limit = 1000 } = {}) => call('quilt_line_paths_of', { quilt_id: q, line, path, up_to: Number.isFinite(upTo) ? upTo : undefined, limit }),
|
|
71
|
-
recall: (key, maxAgeMs, now) => call('quilt_recall', { key, max_age_ms: maxAgeMs, now }),
|
|
72
|
-
stubMembers: (q) => call('quilt_stub_members', { quilt_id: q }),
|
|
73
|
-
gitSha: (q, sha) => call('quilt_git_sha', { quilt_id: q, sha }),
|
|
74
|
-
gitShas: async (q, shas) => (await call('quilt_git_shas', { quilt_id: q, shas })) || {},
|
|
75
|
-
gitShaPrefix: (q, prefix) => call('quilt_git_sha_prefix', { quilt_id: q, prefix }),
|
|
76
|
-
gitShaOf: (q, id) => call('quilt_git_sha_of', { quilt_id: q, commit_id: id }),
|
|
77
|
-
close () {}
|
|
78
|
-
})
|
|
79
|
-
}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
// The quilt store in the accounts Supabase project: the migration's functions over supabase-js
|
|
2
|
-
// with the service role (store-rpc.js has the mapping; only the API holds this key).
|
|
3
|
-
import { createClient } from '@supabase/supabase-js'
|
|
4
|
-
import { createRpcQuiltStore } from './store-rpc.js'
|
|
5
|
-
|
|
6
|
-
export function createSupabaseQuiltStore ({ url, serviceKey }) {
|
|
7
|
-
const db = createClient(url, serviceKey, { auth: { persistSession: false, autoRefreshToken: false } })
|
|
8
|
-
return createRpcQuiltStore(async (fn, p) => {
|
|
9
|
-
const { data, error } = await db.rpc(fn, { p })
|
|
10
|
-
if (error) throw new Error(error.message)
|
|
11
|
-
return data
|
|
12
|
-
})
|
|
13
|
-
}
|
package/src/quilts/tags.js
DELETED
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
// Tags and releases (plans/quilt-source-control/06): a tag is a name on one commit (a patch or any
|
|
2
|
-
// revision) that never moves and is never deleted; a release is a tag with notes. tag:<name> is a
|
|
3
|
-
// revision everywhere. Anyone who can write tags; the tag records who and when.
|
|
4
|
-
import { QuiltError } from './service.js'
|
|
5
|
-
|
|
6
|
-
const NAME = /^[A-Za-z0-9._-][A-Za-z0-9._/-]{0,99}$/
|
|
7
|
-
|
|
8
|
-
/** A tag name: letters, digits, dots, dashes, underscores and slashes, no `..`, no trailing slash. */
|
|
9
|
-
export function tagName (raw) {
|
|
10
|
-
const s = String(raw || '').trim()
|
|
11
|
-
if (!NAME.test(s) || s.includes('..') || s.includes('//') || s.endsWith('/')) throw new QuiltError(400, 'bad_request', 'a tag name is letters, digits, dots, dashes, underscores and slashes (up to 100)')
|
|
12
|
-
return s
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function tagOps (svc) {
|
|
16
|
-
const { store, now, quiltFor, resolve, fromStore, getCommit } = svc.internals
|
|
17
|
-
|
|
18
|
-
async function view (q, row) {
|
|
19
|
-
const c = row.commit ? await store.getCommit(q, row.commit) : null
|
|
20
|
-
return { name: row.name, commit: row.commit, short: row.commit ? row.commit.slice(0, 12) : null, patch: c?.patch || null, release: !!row.release, notes: row.notes ?? null, by: row.actor, at: row.at }
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
async function make (me, ref, { name, rev, notes } = {}, release) {
|
|
24
|
-
const q = await quiltFor(me, ref, 'write')
|
|
25
|
-
const n = tagName(name)
|
|
26
|
-
if (notes != null && String(notes).length > 20000) throw new QuiltError(400, 'bad_request', 'release notes are up to 20000 characters')
|
|
27
|
-
const commit = await resolve(q.id, rev || 'main')
|
|
28
|
-
if (!commit) throw new QuiltError(400, 'bad_request', `${rev || 'main'} has no commits to tag`)
|
|
29
|
-
await getCommit(q.id, commit)
|
|
30
|
-
try {
|
|
31
|
-
const row = await store.createTag({ quilt_id: q.id, name: n, commit, release, notes: release ? String(notes ?? '') : null, actor: me.member_id, at: now() })
|
|
32
|
-
return view(q.id, row)
|
|
33
|
-
} catch (err) { throw fromStore(err) }
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** GET /tags: every tag, by name. */
|
|
37
|
-
async function tags (me, ref) {
|
|
38
|
-
const q = await quiltFor(me, ref, 'read')
|
|
39
|
-
const rows = (await store.tags(q.id)).sort((a, b) => (a.name < b.name ? -1 : a.name > b.name ? 1 : 0))
|
|
40
|
-
return { tags: await Promise.all(rows.map((r) => view(q.id, r))) }
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/** GET /releases: the tags that are releases, newest first. */
|
|
44
|
-
async function releases (me, ref) {
|
|
45
|
-
const q = await quiltFor(me, ref, 'read')
|
|
46
|
-
const rows = (await store.tags(q.id)).filter((r) => r.release).sort((a, b) => (b.seq || 0) - (a.seq || 0))
|
|
47
|
-
return { releases: await Promise.all(rows.map((r) => view(q.id, r))) }
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
async function getTag (me, ref, name) {
|
|
51
|
-
const q = await quiltFor(me, ref, 'read')
|
|
52
|
-
const row = await store.getTag(q.id, String(name))
|
|
53
|
-
if (!row) throw new QuiltError(404, 'not_found', `no tag ${name}`)
|
|
54
|
-
return view(q.id, row)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return {
|
|
58
|
-
tags,
|
|
59
|
-
releases,
|
|
60
|
-
getTag,
|
|
61
|
-
createTag: (me, ref, body) => make(me, ref, body, false),
|
|
62
|
-
createRelease: (me, ref, body) => make(me, ref, body, true)
|
|
63
|
-
}
|
|
64
|
-
}
|
package/src/quilts/textdiff.js
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
// Line diffs for quilts: unified text for `diff` and `show`, and the line mapping `blame` walks.
|
|
2
|
-
|
|
3
|
-
import { xdiff } from './xdiff.js'
|
|
4
|
-
|
|
5
|
-
const MAX_TEXT = 8 * 1024 * 1024
|
|
6
|
-
|
|
7
|
-
/** Whether bytes read as text: valid UTF-8, no NUL, at most 8 MiB (07's max_merge_text_bytes). */
|
|
8
|
-
export function isText (buf) {
|
|
9
|
-
if (!buf) return true
|
|
10
|
-
if (buf.length > MAX_TEXT || buf.includes(0)) return false
|
|
11
|
-
try { new TextDecoder('utf-8', { fatal: true }).decode(buf); return true } catch { return false }
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
/** A text's lines, split on \n, without the empty string after a final newline. */
|
|
15
|
-
export function splitLines (s) {
|
|
16
|
-
if (!s) return []
|
|
17
|
-
const ls = s.split('\n')
|
|
18
|
-
if (ls[ls.length - 1] === '') ls.pop()
|
|
19
|
-
return ls
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
/** Lines back to bytes, each ended by \n (what `hunks` diffs). */
|
|
23
|
-
function linesBuf (ls) { return Buffer.from(ls.length ? ls.join('\n') + '\n' : '', 'utf8') }
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* The hunks between two line lists: [{ a: [start, len], b: [start, len] }], chosen the way git's
|
|
27
|
-
* xdiff chooses them (its Myers variant, record cleanup, and sliding with the indent heuristic),
|
|
28
|
-
* so `diff`, `show` and `blame` agree with git's.
|
|
29
|
-
*/
|
|
30
|
-
export function hunks (a, b) {
|
|
31
|
-
return xdiff(linesBuf(a), linesBuf(b))
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** +added −removed counts between two texts. */
|
|
35
|
-
export function lineStat (a, b) {
|
|
36
|
-
let add = 0; let del = 0
|
|
37
|
-
for (const h of hunks(splitLines(a), splitLines(b))) { del += h.a[1]; add += h.b[1] }
|
|
38
|
-
return { add, del }
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/**
|
|
42
|
-
* A unified diff of one file (git's format, 3 lines of context). `before`/`after` are strings,
|
|
43
|
-
* or null when the file is added or deleted.
|
|
44
|
-
*/
|
|
45
|
-
export function unified (path, before, after, { context = 3, fromPath = path } = {}) {
|
|
46
|
-
const a = splitLines(before ?? '')
|
|
47
|
-
const b = splitLines(after ?? '')
|
|
48
|
-
const head = [`--- ${before == null ? '/dev/null' : `a/${fromPath}`}`, `+++ ${after == null ? '/dev/null' : `b/${path}`}`]
|
|
49
|
-
const hs = hunks(a, b)
|
|
50
|
-
if (!hs.length) return ''
|
|
51
|
-
const out = [...head]
|
|
52
|
-
// Group hunks whose context overlaps.
|
|
53
|
-
let i = 0
|
|
54
|
-
while (i < hs.length) {
|
|
55
|
-
let j = i
|
|
56
|
-
while (j + 1 < hs.length && hs[j + 1].a[0] - (hs[j].a[0] + hs[j].a[1]) <= context * 2) j++
|
|
57
|
-
const aStart = Math.max(0, hs[i].a[0] - context)
|
|
58
|
-
const aEnd = Math.min(a.length, hs[j].a[0] + hs[j].a[1] + context)
|
|
59
|
-
const bStart = Math.max(0, hs[i].b[0] - context)
|
|
60
|
-
const bEnd = Math.min(b.length, hs[j].b[0] + hs[j].b[1] + context)
|
|
61
|
-
const body = []
|
|
62
|
-
let ap = aStart
|
|
63
|
-
for (let k = i; k <= j; k++) {
|
|
64
|
-
const h = hs[k]
|
|
65
|
-
while (ap < h.a[0]) body.push(' ' + a[ap++])
|
|
66
|
-
for (let x = 0; x < h.a[1]; x++) body.push('-' + a[h.a[0] + x])
|
|
67
|
-
for (let x = 0; x < h.b[1]; x++) body.push('+' + b[h.b[0] + x])
|
|
68
|
-
ap = h.a[0] + h.a[1]
|
|
69
|
-
}
|
|
70
|
-
while (ap < aEnd) body.push(' ' + a[ap++])
|
|
71
|
-
const range = (s, n) => `${n === 0 ? s : s + 1}${n === 1 ? '' : `,${n}`}`
|
|
72
|
-
out.push(`@@ -${range(aStart, aEnd - aStart)} +${range(bStart, bEnd - bStart)} @@`, ...body)
|
|
73
|
-
i = j + 1
|
|
74
|
-
}
|
|
75
|
-
return out.join('\n') + '\n'
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
/**
|
|
79
|
-
* For each line of `b`, its line in `a` when the diff kept it, else -1 (it was added or
|
|
80
|
-
* changed in b). What blame follows from a commit back to its parent. Given Buffers (the files'
|
|
81
|
-
* bytes) it diffs exactly as `git blame` does, final newline and common-tail trim included.
|
|
82
|
-
*/
|
|
83
|
-
export function lineMap (a, b) {
|
|
84
|
-
const bytes = Buffer.isBuffer(a)
|
|
85
|
-
const hs = bytes ? xdiff(a, b, { trimTail: true }) : hunks(a, b)
|
|
86
|
-
const n = bytes ? splitLines(b.toString('utf8')).length : b.length
|
|
87
|
-
const map = new Array(n).fill(-1)
|
|
88
|
-
let ai = 0; let bi = 0
|
|
89
|
-
for (const h of hs) {
|
|
90
|
-
while (bi < h.b[0]) map[bi++] = ai++
|
|
91
|
-
ai = h.a[0] + h.a[1]
|
|
92
|
-
bi = h.b[0] + h.b[1]
|
|
93
|
-
}
|
|
94
|
-
while (bi < n) map[bi++] = ai++
|
|
95
|
-
return map
|
|
96
|
-
}
|
package/src/quilts/tokens.js
DELETED
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
// Quilt tokens: a key for one quilt, read or write, for a CI job or a script. An admin makes one
|
|
2
|
-
// (seam token new ci --level write), the secret (sqt_…) is shown once, and only its sha256 is kept.
|
|
3
|
-
// A token is a member of its one quilt as token:<id> (so every read and write checks it like any
|
|
4
|
-
// other member) and calls as an agent named after the token. Revoking it ends its membership.
|
|
5
|
-
import crypto from 'node:crypto'
|
|
6
|
-
import { QuiltError } from './errors.js'
|
|
7
|
-
import { uuidv7 } from './ids.js'
|
|
8
|
-
|
|
9
|
-
export const TOKEN_PREFIX = 'sqt_'
|
|
10
|
-
export const MAX_TOKENS = 50
|
|
11
|
-
const SECRET = /^sqt_[A-Za-z0-9_-]{43}$/
|
|
12
|
-
|
|
13
|
-
export const hashToken = (secret) => crypto.createHash('sha256').update(String(secret)).digest('hex')
|
|
14
|
-
export const isQuiltToken = (s) => SECRET.test(String(s || ''))
|
|
15
|
-
|
|
16
|
-
export function tokenOps (svc) {
|
|
17
|
-
const { store, now, quiltFor, fromStore } = svc.internals
|
|
18
|
-
const view = (r) => ({ id: r.id, name: r.name, level: r.level, created_by: r.created_by, created_at: r.created_at, ...(r.revoked_at ? { revoked_at: r.revoked_at } : {}) })
|
|
19
|
-
|
|
20
|
-
/** POST /tokens { name, level? }: makes a token (admin). The secret is in this answer only. */
|
|
21
|
-
async function createToken (me, ref, { name, level = 'read' } = {}) {
|
|
22
|
-
const q = await quiltFor(me, ref, 'admin')
|
|
23
|
-
const n = String(name || '').trim()
|
|
24
|
-
if (!n || n.length > 100) throw new QuiltError(400, 'bad_request', 'name: what the token is for, up to 100 characters (e.g. ci)')
|
|
25
|
-
if (!['read', 'write'].includes(level)) throw new QuiltError(400, 'bad_request', 'level is read or write')
|
|
26
|
-
const live = (await store.tokens(q.id)).filter((r) => !r.revoked_at)
|
|
27
|
-
if (live.length >= MAX_TOKENS) throw new QuiltError(409, 'too_many_tokens', `up to ${MAX_TOKENS} tokens per quilt; revoke one first`)
|
|
28
|
-
const secret = TOKEN_PREFIX + crypto.randomBytes(32).toString('base64url')
|
|
29
|
-
try {
|
|
30
|
-
const row = await store.createToken({ id: uuidv7(now()), quilt_id: q.id, name: n, level, hash: hashToken(secret), created_by: me.member_id, created_at: now() })
|
|
31
|
-
return { ...view(row), quilt: q.slug, member_id: `token:${row.id}`, token: secret }
|
|
32
|
-
} catch (err) { throw fromStore(err) }
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/** GET /tokens: the quilt's tokens, revoked ones too (admin). Never their secrets. */
|
|
36
|
-
async function tokens (me, ref) {
|
|
37
|
-
const q = await quiltFor(me, ref, 'admin')
|
|
38
|
-
return { quilt: q.slug, tokens: (await store.tokens(q.id)).map(view) }
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
/** DELETE /tokens/:id: revokes a token (by id, or by name when one live token has it). */
|
|
42
|
-
async function revokeToken (me, ref, which) {
|
|
43
|
-
const q = await quiltFor(me, ref, 'admin')
|
|
44
|
-
const all = await store.tokens(q.id)
|
|
45
|
-
const w = String(which || '')
|
|
46
|
-
let row = all.find((r) => r.id === w)
|
|
47
|
-
if (!row) {
|
|
48
|
-
const named = all.filter((r) => r.name === w && !r.revoked_at)
|
|
49
|
-
if (named.length > 1) throw new QuiltError(400, 'bad_request', `${named.length} tokens are named ${w}; revoke one by its id`)
|
|
50
|
-
row = named[0]
|
|
51
|
-
}
|
|
52
|
-
if (!row) throw new QuiltError(404, 'not_found', `no token ${w}`)
|
|
53
|
-
if (row.revoked_at) return view(row)
|
|
54
|
-
try { return view(await store.revokeToken({ quilt_id: q.id, id: row.id, actor: me.member_id, at: now() })) } catch (err) { throw fromStore(err) }
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
/** The caller a token secret stands for, or null when it is unknown or revoked. */
|
|
58
|
-
async function tokenCaller (secret) {
|
|
59
|
-
if (!isQuiltToken(secret)) return null
|
|
60
|
-
const row = await store.tokenByHash(hashToken(secret))
|
|
61
|
-
if (!row || row.revoked_at) return null
|
|
62
|
-
return { member_id: `token:${row.id}`, kind: 'agent', name: row.name, tool: 'token', token: { id: row.id, quilt_id: row.quilt_id, level: row.level } }
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
return { createToken, tokens, revokeToken, tokenCaller }
|
|
66
|
-
}
|