chat-recall 0.5.31 → 0.5.32
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/dist/bulk-delete.d.ts +131 -0
- package/dist/bulk-delete.d.ts.map +1 -0
- package/dist/bulk-delete.js +211 -0
- package/dist/bulk-delete.js.map +1 -0
- package/dist/cli.js +686 -435
- package/dist/cli.js.map +1 -1
- package/dist/mcp.js +191 -186
- package/dist/scan-worker.js +59 -20
- package/dist/watch.js +194 -189
- package/package.json +1 -1
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bulk delete — purge many sessions from every logged-in server at once.
|
|
3
|
+
*
|
|
4
|
+
* Deletion existed only one session at a time (`DELETE /api/conversations/:id`,
|
|
5
|
+
* and `recall_forget` over MCP). That is fine for "forget that conversation"
|
|
6
|
+
* and useless for the case that actually produces junk: a tool that fabricates
|
|
7
|
+
* sessions. A misfiring model-probe wrote 475 one-line transcripts into one
|
|
8
|
+
* project — 94% of its indexed history — and clearing them meant 475 HTTP
|
|
9
|
+
* round trips driven by hand.
|
|
10
|
+
*
|
|
11
|
+
* ── Why the sync endpoint rather than N deletes ────────────────────────────
|
|
12
|
+
* `POST /api/sync { tombstones: [...] }` already purges AND tombstones a whole
|
|
13
|
+
* array in one request, and it is the same path the collector uses when it
|
|
14
|
+
* notices a transcript disappeared locally. Reusing it means bulk deletion is
|
|
15
|
+
* not a second implementation of "remove a session everywhere" that has to stay
|
|
16
|
+
* in agreement with the first one — it IS the first one, called with more rows.
|
|
17
|
+
*
|
|
18
|
+
* ── Resurrection ───────────────────────────────────────────────────────────
|
|
19
|
+
* A tombstone is why this sticks. The server records every purged id and the
|
|
20
|
+
* ingest path skips anything in that set (`deadSet` in routes/sync.ts), so a
|
|
21
|
+
* later sync — from this machine with a stale ledger, or from a second device
|
|
22
|
+
* that still has the transcript — cannot put it back. The consequence is worth
|
|
23
|
+
* stating plainly to the caller: deleting a session whose local transcript
|
|
24
|
+
* still exists means that transcript can never be indexed again.
|
|
25
|
+
*/
|
|
26
|
+
import { type Credentials } from './sync-client.js';
|
|
27
|
+
/** Rows as `/api/conversations/recent` returns them. */
|
|
28
|
+
export interface SessionRow {
|
|
29
|
+
sessionId: string;
|
|
30
|
+
firstPrompt?: string;
|
|
31
|
+
projectId?: string;
|
|
32
|
+
tool?: string;
|
|
33
|
+
}
|
|
34
|
+
export interface SelectOptions {
|
|
35
|
+
/** Logical project id, e.g. `git:github.com/me/repo`. */
|
|
36
|
+
project?: string;
|
|
37
|
+
/** Keep only sessions whose first prompt is EXACTLY this, trimmed. */
|
|
38
|
+
match?: string;
|
|
39
|
+
/** Keep only sessions from this tool (claude, codex, …). */
|
|
40
|
+
tool?: string;
|
|
41
|
+
/** Stop after this many matches. */
|
|
42
|
+
limit?: number;
|
|
43
|
+
}
|
|
44
|
+
export interface DeleteOutcome {
|
|
45
|
+
requested: number;
|
|
46
|
+
perTarget: Record<string, {
|
|
47
|
+
deleted: number;
|
|
48
|
+
error?: string;
|
|
49
|
+
}>;
|
|
50
|
+
/** Deleted on at least one server. */
|
|
51
|
+
deleted: number;
|
|
52
|
+
}
|
|
53
|
+
/** Ids per tombstone POST. Large enough to be one request for a typical
|
|
54
|
+
* cleanup, small enough that a failure loses a bounded amount of work. */
|
|
55
|
+
export declare const BATCH = 250;
|
|
56
|
+
/**
|
|
57
|
+
* Enumerate the sessions a selector matches, newest first.
|
|
58
|
+
*
|
|
59
|
+
* Paging walks the WHOLE project rather than trusting page 0 — the same trap
|
|
60
|
+
* `/api/data/delete` had to fix: rows come back mtime-ordered, so a filter that
|
|
61
|
+
* matches only old sessions finds nothing on the first page and would report an
|
|
62
|
+
* empty selection on a project with thousands of rows.
|
|
63
|
+
*/
|
|
64
|
+
export declare function selectSessions(target: Credentials, opts: SelectOptions, fetchImpl?: typeof fetch): Promise<SessionRow[]>;
|
|
65
|
+
/**
|
|
66
|
+
* Purge + tombstone `ids` on every logged-in server.
|
|
67
|
+
*
|
|
68
|
+
* Per-target failures are recorded, never thrown: with two servers configured,
|
|
69
|
+
* one being unreachable must not hide that the other succeeded — the caller
|
|
70
|
+
* has to be able to say which copies are actually gone.
|
|
71
|
+
*/
|
|
72
|
+
export declare function bulkDelete(ids: string[], opts?: {
|
|
73
|
+
targets?: Credentials[];
|
|
74
|
+
fetchImpl?: typeof fetch;
|
|
75
|
+
onProgress?: (done: number, total: number) => void;
|
|
76
|
+
}): Promise<DeleteOutcome>;
|
|
77
|
+
/** One recorded deletion. */
|
|
78
|
+
export interface Tombstone {
|
|
79
|
+
session_id: string;
|
|
80
|
+
deleted_at: number;
|
|
81
|
+
}
|
|
82
|
+
/**
|
|
83
|
+
* What has been deleted on a server, newest first.
|
|
84
|
+
*
|
|
85
|
+
* A purged session leaves nothing else behind to find it by, so this list is
|
|
86
|
+
* the only route back from a delete.
|
|
87
|
+
*/
|
|
88
|
+
export declare function listTombstones(target: Credentials, limit?: number, fetchImpl?: typeof fetch): Promise<{
|
|
89
|
+
total: number;
|
|
90
|
+
tombstones: Tombstone[];
|
|
91
|
+
}>;
|
|
92
|
+
/**
|
|
93
|
+
* Lift tombstones so those sessions may be re-uploaded.
|
|
94
|
+
*
|
|
95
|
+
* Restores no content by itself — it removes the server's refusal. The
|
|
96
|
+
* transcript has to still exist on some device, which is why the CLI tells the
|
|
97
|
+
* caller to re-sync afterwards rather than claiming the data is back.
|
|
98
|
+
*/
|
|
99
|
+
export declare function restoreSessions(ids: string[], opts?: {
|
|
100
|
+
targets?: Credentials[];
|
|
101
|
+
fetchImpl?: typeof fetch;
|
|
102
|
+
}): Promise<{
|
|
103
|
+
requested: number;
|
|
104
|
+
restored: number;
|
|
105
|
+
perTarget: Record<string, {
|
|
106
|
+
restored: number;
|
|
107
|
+
notDeleted: number;
|
|
108
|
+
error?: string;
|
|
109
|
+
}>;
|
|
110
|
+
}>;
|
|
111
|
+
/**
|
|
112
|
+
* Does this invocation need an explicit confirmation?
|
|
113
|
+
*
|
|
114
|
+
* Only when the caller DID NOT ENUMERATE the set. `delete <id>` has always
|
|
115
|
+
* deleted that id immediately and scripts depend on it — the repo's own privacy
|
|
116
|
+
* E2E calls it non-interactively, and demanding --yes for ids typed on the
|
|
117
|
+
* command line broke that contract for no safety gain: naming a session IS the
|
|
118
|
+
* confirmation.
|
|
119
|
+
*
|
|
120
|
+
* A selector is different. --project/--match COMPUTE the set, so its size is
|
|
121
|
+
* discovered rather than intended, and that is where a wrong pattern quietly
|
|
122
|
+
* takes hundreds with it. --stdin sits in the same bucket: the ids came from
|
|
123
|
+
* somewhere the person may not have read, and stdin is already a pipe, so there
|
|
124
|
+
* is nothing left to prompt on.
|
|
125
|
+
*/
|
|
126
|
+
export declare function needsConfirmation(opts: {
|
|
127
|
+
argvIds: number;
|
|
128
|
+
project?: string;
|
|
129
|
+
stdin?: boolean;
|
|
130
|
+
}): boolean;
|
|
131
|
+
//# sourceMappingURL=bulk-delete.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulk-delete.d.ts","sourceRoot":"","sources":["../src/bulk-delete.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAGxE,wDAAwD;AACxD,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,aAAa;IAC5B,yDAAyD;IACzD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,sEAAsE;IACtE,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,4DAA4D;IAC5D,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,oCAAoC;IACpC,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,OAAO,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IAC/D,sCAAsC;IACtC,OAAO,EAAE,MAAM,CAAC;CACjB;AAID;2EAC2E;AAC3E,eAAO,MAAM,KAAK,MAAM,CAAC;AAIzB;;;;;;;GAOG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,WAAW,EACnB,IAAI,EAAE,aAAa,EACnB,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC,UAAU,EAAE,CAAC,CA2BvB;AAED;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,GAAG,EAAE,MAAM,EAAE,EACb,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAC;IAAC,UAAU,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAA;CAAO,GACnH,OAAO,CAAC,aAAa,CAAC,CAmDxB;AAED,6BAA6B;AAC7B,MAAM,WAAW,SAAS;IAAG,UAAU,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,MAAM,CAAA;CAAE;AAErE;;;;;GAKG;AACH,wBAAsB,cAAc,CAClC,MAAM,EAAE,WAAW,EACnB,KAAK,SAAM,EACX,SAAS,GAAE,OAAO,KAAa,GAC9B,OAAO,CAAC;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,UAAU,EAAE,SAAS,EAAE,CAAA;CAAE,CAAC,CAMrD;AAED;;;;;;GAMG;AACH,wBAAsB,eAAe,CACnC,GAAG,EAAE,MAAM,EAAE,EACb,IAAI,GAAE;IAAE,OAAO,CAAC,EAAE,WAAW,EAAE,CAAC;IAAC,SAAS,CAAC,EAAE,OAAO,KAAK,CAAA;CAAO,GAC/D,OAAO,CAAC;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,UAAU,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC,CAAA;CAAE,CAAC,CA8BvI;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAGvG"}
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Bulk delete — purge many sessions from every logged-in server at once.
|
|
3
|
+
*
|
|
4
|
+
* Deletion existed only one session at a time (`DELETE /api/conversations/:id`,
|
|
5
|
+
* and `recall_forget` over MCP). That is fine for "forget that conversation"
|
|
6
|
+
* and useless for the case that actually produces junk: a tool that fabricates
|
|
7
|
+
* sessions. A misfiring model-probe wrote 475 one-line transcripts into one
|
|
8
|
+
* project — 94% of its indexed history — and clearing them meant 475 HTTP
|
|
9
|
+
* round trips driven by hand.
|
|
10
|
+
*
|
|
11
|
+
* ── Why the sync endpoint rather than N deletes ────────────────────────────
|
|
12
|
+
* `POST /api/sync { tombstones: [...] }` already purges AND tombstones a whole
|
|
13
|
+
* array in one request, and it is the same path the collector uses when it
|
|
14
|
+
* notices a transcript disappeared locally. Reusing it means bulk deletion is
|
|
15
|
+
* not a second implementation of "remove a session everywhere" that has to stay
|
|
16
|
+
* in agreement with the first one — it IS the first one, called with more rows.
|
|
17
|
+
*
|
|
18
|
+
* ── Resurrection ───────────────────────────────────────────────────────────
|
|
19
|
+
* A tombstone is why this sticks. The server records every purged id and the
|
|
20
|
+
* ingest path skips anything in that set (`deadSet` in routes/sync.ts), so a
|
|
21
|
+
* later sync — from this machine with a stale ledger, or from a second device
|
|
22
|
+
* that still has the transcript — cannot put it back. The consequence is worth
|
|
23
|
+
* stating plainly to the caller: deleting a session whose local transcript
|
|
24
|
+
* still exists means that transcript can never be indexed again.
|
|
25
|
+
*/
|
|
26
|
+
import { loadAllCredentials } from './sync-client.js';
|
|
27
|
+
import { getLedgerData, persistLedgerData } from './sync-ledger.js';
|
|
28
|
+
/** Page size for listing; the server caps `limit` at 200. */
|
|
29
|
+
const PAGE = 200;
|
|
30
|
+
/** Ids per tombstone POST. Large enough to be one request for a typical
|
|
31
|
+
* cleanup, small enough that a failure loses a bounded amount of work. */
|
|
32
|
+
export const BATCH = 250;
|
|
33
|
+
const base = (url) => url.replace(/\/+$/, '');
|
|
34
|
+
/**
|
|
35
|
+
* Enumerate the sessions a selector matches, newest first.
|
|
36
|
+
*
|
|
37
|
+
* Paging walks the WHOLE project rather than trusting page 0 — the same trap
|
|
38
|
+
* `/api/data/delete` had to fix: rows come back mtime-ordered, so a filter that
|
|
39
|
+
* matches only old sessions finds nothing on the first page and would report an
|
|
40
|
+
* empty selection on a project with thousands of rows.
|
|
41
|
+
*/
|
|
42
|
+
export async function selectSessions(target, opts, fetchImpl = fetch) {
|
|
43
|
+
const found = [];
|
|
44
|
+
const want = opts.match?.trim();
|
|
45
|
+
for (let offset = 0;; offset += PAGE) {
|
|
46
|
+
const qs = new URLSearchParams({ limit: String(PAGE), offset: String(offset) });
|
|
47
|
+
if (opts.project)
|
|
48
|
+
qs.set('project', opts.project);
|
|
49
|
+
if (opts.tool)
|
|
50
|
+
qs.set('tool', opts.tool);
|
|
51
|
+
const res = await fetchImpl(`${base(target.serverUrl)}/api/conversations/recent?${qs}`, {
|
|
52
|
+
headers: { authorization: `Bearer ${target.token}` },
|
|
53
|
+
});
|
|
54
|
+
if (!res.ok)
|
|
55
|
+
throw new Error(`list failed: HTTP ${res.status}`);
|
|
56
|
+
const body = await res.json();
|
|
57
|
+
const page = body.sessions ?? [];
|
|
58
|
+
if (page.length === 0)
|
|
59
|
+
break;
|
|
60
|
+
for (const row of page) {
|
|
61
|
+
if (!row.sessionId)
|
|
62
|
+
continue;
|
|
63
|
+
if (want !== undefined && (row.firstPrompt ?? '').trim() !== want)
|
|
64
|
+
continue;
|
|
65
|
+
found.push(row);
|
|
66
|
+
if (opts.limit && found.length >= opts.limit)
|
|
67
|
+
return found;
|
|
68
|
+
}
|
|
69
|
+
if (page.length < PAGE)
|
|
70
|
+
break;
|
|
71
|
+
}
|
|
72
|
+
return found;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Purge + tombstone `ids` on every logged-in server.
|
|
76
|
+
*
|
|
77
|
+
* Per-target failures are recorded, never thrown: with two servers configured,
|
|
78
|
+
* one being unreachable must not hide that the other succeeded — the caller
|
|
79
|
+
* has to be able to say which copies are actually gone.
|
|
80
|
+
*/
|
|
81
|
+
export async function bulkDelete(ids, opts = {}) {
|
|
82
|
+
const unique = [...new Set(ids.filter(Boolean))];
|
|
83
|
+
const targets = opts.targets ?? loadAllCredentials();
|
|
84
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
85
|
+
const out = { requested: unique.length, perTarget: {}, deleted: 0 };
|
|
86
|
+
if (unique.length === 0 || targets.length === 0)
|
|
87
|
+
return out;
|
|
88
|
+
const succeeded = new Set();
|
|
89
|
+
for (const t of targets) {
|
|
90
|
+
let deleted = 0;
|
|
91
|
+
try {
|
|
92
|
+
for (let i = 0; i < unique.length; i += BATCH) {
|
|
93
|
+
const slice = unique.slice(i, i + BATCH);
|
|
94
|
+
const res = await fetchImpl(`${base(t.serverUrl)}/api/sync`, {
|
|
95
|
+
method: 'POST',
|
|
96
|
+
headers: { 'content-type': 'application/json', authorization: `Bearer ${t.token}` },
|
|
97
|
+
body: JSON.stringify({
|
|
98
|
+
tombstones: slice.map((session_id) => ({ session_id, deleted_at: Date.now() })),
|
|
99
|
+
}),
|
|
100
|
+
});
|
|
101
|
+
if (!res.ok)
|
|
102
|
+
throw new Error(`HTTP ${res.status}`);
|
|
103
|
+
deleted += slice.length;
|
|
104
|
+
for (const id of slice)
|
|
105
|
+
succeeded.add(id);
|
|
106
|
+
opts.onProgress?.(deleted, unique.length);
|
|
107
|
+
}
|
|
108
|
+
out.perTarget[t.serverUrl] = { deleted };
|
|
109
|
+
}
|
|
110
|
+
catch (e) {
|
|
111
|
+
out.perTarget[t.serverUrl] = { deleted, error: e instanceof Error ? e.message : 'failed' };
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
out.deleted = succeeded.size;
|
|
115
|
+
// Drop the ids from this device's ledger. Without it the collector keeps a
|
|
116
|
+
// record of having shipped them and re-offers them on the next full walk —
|
|
117
|
+
// harmless (the server's deadSet refuses) but it means every later sync
|
|
118
|
+
// carries traffic for sessions that are deliberately gone.
|
|
119
|
+
for (const t of targets) {
|
|
120
|
+
if (!out.perTarget[t.serverUrl] || out.perTarget[t.serverUrl].error)
|
|
121
|
+
continue;
|
|
122
|
+
try {
|
|
123
|
+
const data = getLedgerData(t.serverUrl);
|
|
124
|
+
let touched = false;
|
|
125
|
+
for (const id of succeeded) {
|
|
126
|
+
if (id in data) {
|
|
127
|
+
delete data[id];
|
|
128
|
+
touched = true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
if (touched)
|
|
132
|
+
persistLedgerData(t.serverUrl, data);
|
|
133
|
+
}
|
|
134
|
+
catch { /* local bookkeeping — the delete itself already landed */ }
|
|
135
|
+
}
|
|
136
|
+
return out;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* What has been deleted on a server, newest first.
|
|
140
|
+
*
|
|
141
|
+
* A purged session leaves nothing else behind to find it by, so this list is
|
|
142
|
+
* the only route back from a delete.
|
|
143
|
+
*/
|
|
144
|
+
export async function listTombstones(target, limit = 500, fetchImpl = fetch) {
|
|
145
|
+
const res = await fetchImpl(`${base(target.serverUrl)}/api/data/tombstones?limit=${limit}`, {
|
|
146
|
+
headers: { authorization: `Bearer ${target.token}` },
|
|
147
|
+
});
|
|
148
|
+
if (!res.ok)
|
|
149
|
+
throw new Error(`HTTP ${res.status}`);
|
|
150
|
+
return await res.json();
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Lift tombstones so those sessions may be re-uploaded.
|
|
154
|
+
*
|
|
155
|
+
* Restores no content by itself — it removes the server's refusal. The
|
|
156
|
+
* transcript has to still exist on some device, which is why the CLI tells the
|
|
157
|
+
* caller to re-sync afterwards rather than claiming the data is back.
|
|
158
|
+
*/
|
|
159
|
+
export async function restoreSessions(ids, opts = {}) {
|
|
160
|
+
const unique = [...new Set(ids.filter(Boolean))];
|
|
161
|
+
const targets = opts.targets ?? loadAllCredentials();
|
|
162
|
+
const fetchImpl = opts.fetchImpl ?? fetch;
|
|
163
|
+
const out = { requested: unique.length, restored: 0, perTarget: {} };
|
|
164
|
+
if (unique.length === 0 || targets.length === 0)
|
|
165
|
+
return out;
|
|
166
|
+
let best = 0;
|
|
167
|
+
for (const t of targets) {
|
|
168
|
+
try {
|
|
169
|
+
let restored = 0, notDeleted = 0;
|
|
170
|
+
for (let i = 0; i < unique.length; i += BATCH) {
|
|
171
|
+
const res = await fetchImpl(`${base(t.serverUrl)}/api/data/restore`, {
|
|
172
|
+
method: 'POST',
|
|
173
|
+
headers: { 'content-type': 'application/json', authorization: `Bearer ${t.token}` },
|
|
174
|
+
body: JSON.stringify({ session_ids: unique.slice(i, i + BATCH) }),
|
|
175
|
+
});
|
|
176
|
+
if (!res.ok)
|
|
177
|
+
throw new Error(`HTTP ${res.status}`);
|
|
178
|
+
const body = await res.json();
|
|
179
|
+
restored += body.restored ?? 0;
|
|
180
|
+
notDeleted += (body.notDeleted ?? []).length;
|
|
181
|
+
}
|
|
182
|
+
out.perTarget[t.serverUrl] = { restored, notDeleted };
|
|
183
|
+
best = Math.max(best, restored);
|
|
184
|
+
}
|
|
185
|
+
catch (e) {
|
|
186
|
+
out.perTarget[t.serverUrl] = { restored: 0, notDeleted: 0, error: e instanceof Error ? e.message : 'failed' };
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
out.restored = best;
|
|
190
|
+
return out;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Does this invocation need an explicit confirmation?
|
|
194
|
+
*
|
|
195
|
+
* Only when the caller DID NOT ENUMERATE the set. `delete <id>` has always
|
|
196
|
+
* deleted that id immediately and scripts depend on it — the repo's own privacy
|
|
197
|
+
* E2E calls it non-interactively, and demanding --yes for ids typed on the
|
|
198
|
+
* command line broke that contract for no safety gain: naming a session IS the
|
|
199
|
+
* confirmation.
|
|
200
|
+
*
|
|
201
|
+
* A selector is different. --project/--match COMPUTE the set, so its size is
|
|
202
|
+
* discovered rather than intended, and that is where a wrong pattern quietly
|
|
203
|
+
* takes hundreds with it. --stdin sits in the same bucket: the ids came from
|
|
204
|
+
* somewhere the person may not have read, and stdin is already a pipe, so there
|
|
205
|
+
* is nothing left to prompt on.
|
|
206
|
+
*/
|
|
207
|
+
export function needsConfirmation(opts) {
|
|
208
|
+
const enumeratedByCaller = opts.argvIds > 0 && !opts.project && !opts.stdin;
|
|
209
|
+
return !enumeratedByCaller;
|
|
210
|
+
}
|
|
211
|
+
//# sourceMappingURL=bulk-delete.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bulk-delete.js","sourceRoot":"","sources":["../src/bulk-delete.ts"],"names":[],"mappings":"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"}
|