editmamei 1.5.0 → 1.6.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 +17 -1
- package/dist/backends/detect-editors.js +79 -0
- package/dist/backends/gimp/backend.js +96 -0
- package/dist/backends/gimp/bridge/lib.py +1229 -0
- package/dist/backends/gimp/bridge/ops.py +1501 -0
- package/dist/backends/gimp/detect.js +86 -0
- package/dist/backends/gimp/errors.js +21 -0
- package/dist/backends/gimp/launcher.js +6 -0
- package/dist/backends/gimp/session.js +629 -0
- package/dist/bin/editmamei-core-darwin-arm64 +0 -0
- package/dist/bin/editmamei-core-darwin-x64 +0 -0
- package/dist/bin/editmamei-core-win-x64.exe +0 -0
- package/dist/cli/config.js +28 -0
- package/dist/core/server.js +142 -56
- package/dist/core/settings.js +25 -0
- package/dist/core/tool-activity.js +114 -0
- package/dist/core/tool-groups.js +16 -0
- package/dist/core/tool-tiers.js +16 -0
- package/dist/diagnostics/collect.js +4 -0
- package/dist/index.js +5 -1
- package/dist/kernel/kernel.js +3 -0
- package/dist/kernel/module-lifecycle.js +69 -9
- package/dist/license/advisory.js +109 -0
- package/dist/license/entitlement.js +34 -6
- package/dist/license/polar-client.js +50 -7
- package/dist/license/store.js +59 -0
- package/dist/modules/gimp/index.js +35 -0
- package/dist/skills/editmamei-skill.zip +0 -0
- package/dist/telemetry/activity.js +1 -93
- package/dist/telemetry/client.js +55 -17
- package/dist/telemetry/events.js +5 -0
- package/dist/telemetry/outbox.js +43 -18
- package/dist/tools/adjustment-tools.js +2 -1
- package/dist/tools/gimp-adjustment-tools.js +382 -0
- package/dist/tools/gimp-core-tools.js +288 -0
- package/dist/tools/gimp-document-tools.js +321 -0
- package/dist/tools/gimp-filter-tools.js +93 -0
- package/dist/tools/gimp-geometry-tools.js +191 -0
- package/dist/tools/gimp-inspect-tools.js +70 -0
- package/dist/tools/gimp-mask-tools.js +102 -0
- package/dist/tools/gimp-shared.js +51 -0
- package/dist/tools/gimp-verify-tools.js +374 -0
- package/dist/utils/gimp-path.js +27 -0
- package/dist/utils/operation-timeouts.js +16 -0
- package/dist/utils/session-log.js +19 -1
- package/dist/utils/tool-helpers.js +14 -0
- package/dist/version.js +1 -1
- package/package.json +1 -1
package/dist/telemetry/client.js
CHANGED
|
@@ -7,7 +7,7 @@ import { READ_ONLY_TOOLS, KEPT_WORK_TOOLS, nodeMajor, archToken, osMajor, boundM
|
|
|
7
7
|
import { buildClientConnected, buildDiagnosticEvent, buildModuleStatus, buildSessionStart, buildSessionSummary, buildUsageEvent, dayBucket, normalizeDayBucket, isContentSafe, PS_VERSION_UNKNOWN, } from './events.js';
|
|
8
8
|
import { sanitizeMessage, sanitizeSnippet, sanitizeStderrTail } from './sanitize.js';
|
|
9
9
|
import { httpTransport, resolveEndpoint } from './transport.js';
|
|
10
|
-
import { appendOutboxSync, clearOutbox, clearSessionState,
|
|
10
|
+
import { appendOutboxSync, clearOutbox, clearSessionState, readOutboxWithDiscards, readSessionState, rewriteOutbox, writeSessionStateSync, } from './outbox.js';
|
|
11
11
|
const MAX_BATCH_SIZE = 100;
|
|
12
12
|
const MAX_QUEUE_SIZE = 500;
|
|
13
13
|
const DEFAULT_FLUSH_INTERVAL_MS = 5 * 60_000;
|
|
@@ -43,6 +43,9 @@ export class TelemetryClient {
|
|
|
43
43
|
editsOk = 0;
|
|
44
44
|
keptWork = 0;
|
|
45
45
|
droppedEvents = 0;
|
|
46
|
+
droppedOutbox = 0;
|
|
47
|
+
droppedUnsafe = 0;
|
|
48
|
+
usageCallsSent = 0;
|
|
46
49
|
behindLatest = null;
|
|
47
50
|
moduleUpdate = 'none';
|
|
48
51
|
installAssets = {};
|
|
@@ -156,6 +159,9 @@ export class TelemetryClient {
|
|
|
156
159
|
kept_work: this.keptWork,
|
|
157
160
|
...(this.behindLatest !== null ? { behind_latest: this.behindLatest } : {}),
|
|
158
161
|
dropped_events: this.droppedEvents,
|
|
162
|
+
dropped_outbox: this.droppedOutbox,
|
|
163
|
+
dropped_unsafe: this.droppedUnsafe,
|
|
164
|
+
usage_calls_sent: this.usageCallsSent,
|
|
159
165
|
...(moduleStatus !== null ? { module_update: this.moduleUpdate } : {}),
|
|
160
166
|
...(this.installAssets.templates_saved !== undefined
|
|
161
167
|
? { templates_saved: this.installAssets.templates_saved }
|
|
@@ -241,15 +247,18 @@ export class TelemetryClient {
|
|
|
241
247
|
async flush() {
|
|
242
248
|
if (!this.active || this.queue.length === 0)
|
|
243
249
|
return;
|
|
244
|
-
const
|
|
250
|
+
const picked = this.restampPsVersion(this.queue.splice(0, this.maxBatchSize));
|
|
251
|
+
const batch = picked.filter(isContentSafe);
|
|
252
|
+
this.droppedUnsafe += picked.length - batch.length;
|
|
245
253
|
if (batch.length === 0)
|
|
246
254
|
return;
|
|
247
255
|
try {
|
|
248
256
|
await this.transport(this.endpoint, JSON.stringify({ events: batch }));
|
|
257
|
+
this.usageCallsSent += countUsageCalls(batch);
|
|
249
258
|
}
|
|
250
259
|
catch (err) {
|
|
251
260
|
this.logger.debug(`telemetry flush failed, persisting ${batch.length} event(s) to outbox: ${errMsg(err)}`);
|
|
252
|
-
appendOutboxSync(batch, this.outboxOpts);
|
|
261
|
+
this.droppedOutbox += appendOutboxSync(batch, this.outboxOpts);
|
|
253
262
|
}
|
|
254
263
|
}
|
|
255
264
|
shutdown() {
|
|
@@ -264,16 +273,26 @@ export class TelemetryClient {
|
|
|
264
273
|
}
|
|
265
274
|
if (!this.active)
|
|
266
275
|
return;
|
|
267
|
-
if (this.
|
|
268
|
-
this.
|
|
276
|
+
if (this.queue.length > 0) {
|
|
277
|
+
const picked = this.restampPsVersion(this.queue.splice(0));
|
|
278
|
+
const batch = picked.filter(isContentSafe);
|
|
279
|
+
this.droppedUnsafe += picked.length - batch.length;
|
|
280
|
+
this.droppedOutbox += appendOutboxSync(batch, this.outboxOpts);
|
|
281
|
+
}
|
|
282
|
+
const hasLossToReport = this.droppedEvents > 0 || this.droppedOutbox > 0 || this.droppedUnsafe > 0;
|
|
283
|
+
if (this.settings.telemetry.usage && (this.toolCallCount > 0 || hasLossToReport)) {
|
|
284
|
+
const summary = buildSessionSummary(this.dims, {
|
|
269
285
|
tool_call_count: this.toolCallCount,
|
|
270
286
|
distinct_tools: this.distinctTools.size,
|
|
271
287
|
any_failures: this.anyFailures,
|
|
272
288
|
...this.summaryFields(),
|
|
273
|
-
}, this.ensureStartDayBucket(), this.now())
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
289
|
+
}, this.ensureStartDayBucket(), this.now());
|
|
290
|
+
if (isContentSafe(summary)) {
|
|
291
|
+
const lost = appendOutboxSync([summary], this.outboxOpts);
|
|
292
|
+
if (lost > 0) {
|
|
293
|
+
this.logger.debug(`outbox discarded ${lost} event(s) while writing the session summary`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
277
296
|
}
|
|
278
297
|
clearSessionState(this.outboxOpts);
|
|
279
298
|
}
|
|
@@ -286,7 +305,8 @@ export class TelemetryClient {
|
|
|
286
305
|
appendOutboxSync([summaryFromState(stale)], this.outboxOpts);
|
|
287
306
|
}
|
|
288
307
|
clearSessionState(this.outboxOpts);
|
|
289
|
-
const pending =
|
|
308
|
+
const { events: pending, discarded } = readOutboxWithDiscards(this.outboxOpts);
|
|
309
|
+
this.droppedOutbox += discarded;
|
|
290
310
|
if (pending.length === 0) {
|
|
291
311
|
clearOutbox(this.outboxOpts);
|
|
292
312
|
return;
|
|
@@ -295,22 +315,27 @@ export class TelemetryClient {
|
|
|
295
315
|
clearOutbox(this.outboxOpts);
|
|
296
316
|
return;
|
|
297
317
|
}
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
318
|
+
const sendable = pending.filter(isContentSafe);
|
|
319
|
+
this.droppedUnsafe += pending.length - sendable.length;
|
|
320
|
+
let settled = 0;
|
|
321
|
+
for (let i = 0; i < sendable.length; i += this.maxBatchSize) {
|
|
322
|
+
const batch = sendable.slice(i, i + this.maxBatchSize);
|
|
303
323
|
try {
|
|
304
324
|
await this.transport(this.endpoint, JSON.stringify({ events: batch }));
|
|
325
|
+
this.usageCallsSent += countUsageCalls(batch);
|
|
305
326
|
}
|
|
306
327
|
catch (err) {
|
|
307
328
|
this.logger.debug(`startup outbox flush failed: ${errMsg(err)}`);
|
|
308
|
-
allAccepted = false;
|
|
309
329
|
break;
|
|
310
330
|
}
|
|
331
|
+
settled = i + batch.length;
|
|
311
332
|
}
|
|
312
|
-
if (
|
|
333
|
+
if (settled >= sendable.length) {
|
|
313
334
|
clearOutbox(this.outboxOpts);
|
|
335
|
+
}
|
|
336
|
+
else {
|
|
337
|
+
rewriteOutbox(sendable.slice(settled), this.outboxOpts);
|
|
338
|
+
}
|
|
314
339
|
}
|
|
315
340
|
catch (err) {
|
|
316
341
|
this.logger.debug(`startup outbox flush error: ${errMsg(err)}`);
|
|
@@ -342,6 +367,9 @@ function summaryFromState(s) {
|
|
|
342
367
|
...(s.kept_work !== undefined ? { kept_work: s.kept_work } : {}),
|
|
343
368
|
...(s.behind_latest !== undefined ? { behind_latest: s.behind_latest } : {}),
|
|
344
369
|
...(s.dropped_events !== undefined ? { dropped_events: s.dropped_events } : {}),
|
|
370
|
+
...(s.dropped_outbox !== undefined ? { dropped_outbox: s.dropped_outbox } : {}),
|
|
371
|
+
...(s.dropped_unsafe !== undefined ? { dropped_unsafe: s.dropped_unsafe } : {}),
|
|
372
|
+
...(s.usage_calls_sent !== undefined ? { usage_calls_sent: s.usage_calls_sent } : {}),
|
|
345
373
|
...(s.module_update !== undefined ? { module_update: s.module_update } : {}),
|
|
346
374
|
...(templatesSaved !== null ? { templates_saved: templatesSaved } : {}),
|
|
347
375
|
...(actionSets !== null ? { action_sets: actionSets } : {}),
|
|
@@ -350,6 +378,16 @@ function summaryFromState(s) {
|
|
|
350
378
|
function errMsg(err) {
|
|
351
379
|
return err instanceof Error ? err.message : String(err);
|
|
352
380
|
}
|
|
381
|
+
function countUsageCalls(events) {
|
|
382
|
+
let n = 0;
|
|
383
|
+
for (const event of events) {
|
|
384
|
+
if (event.type !== 'usage')
|
|
385
|
+
continue;
|
|
386
|
+
const count = event.count;
|
|
387
|
+
n += typeof count === 'number' && Number.isInteger(count) && count > 0 ? count : 1;
|
|
388
|
+
}
|
|
389
|
+
return n;
|
|
390
|
+
}
|
|
353
391
|
function clampCount(n) {
|
|
354
392
|
if (!Number.isFinite(n))
|
|
355
393
|
return null;
|
package/dist/telemetry/events.js
CHANGED
|
@@ -61,6 +61,11 @@ export function buildSessionSummary(dims, summary, tsBucket, now) {
|
|
|
61
61
|
...(summary.kept_work !== undefined ? { kept_work: summary.kept_work } : {}),
|
|
62
62
|
...(summary.behind_latest !== undefined ? { behind_latest: summary.behind_latest } : {}),
|
|
63
63
|
...(summary.dropped_events !== undefined ? { dropped_events: summary.dropped_events } : {}),
|
|
64
|
+
...(summary.dropped_outbox !== undefined ? { dropped_outbox: summary.dropped_outbox } : {}),
|
|
65
|
+
...(summary.dropped_unsafe !== undefined ? { dropped_unsafe: summary.dropped_unsafe } : {}),
|
|
66
|
+
...(summary.usage_calls_sent !== undefined
|
|
67
|
+
? { usage_calls_sent: summary.usage_calls_sent }
|
|
68
|
+
: {}),
|
|
64
69
|
...(summary.module_update !== undefined ? { module_update: summary.module_update } : {}),
|
|
65
70
|
...(summary.templates_saved !== undefined ? { templates_saved: summary.templates_saved } : {}),
|
|
66
71
|
...(summary.action_sets !== undefined ? { action_sets: summary.action_sets } : {}),
|
package/dist/telemetry/outbox.js
CHANGED
|
@@ -6,8 +6,8 @@ const logger = new Logger('TelemetryOutbox');
|
|
|
6
6
|
const DIRNAME = '.editmamei';
|
|
7
7
|
const OUTBOX_FILENAME = 'telemetry-outbox.ndjson';
|
|
8
8
|
const SESSION_STATE_FILENAME = 'telemetry-session.json';
|
|
9
|
-
export const MAX_OUTBOX_EVENTS =
|
|
10
|
-
const MAX_OUTBOX_BYTES = 2_000_000;
|
|
9
|
+
export const MAX_OUTBOX_EVENTS = 5_000;
|
|
10
|
+
export const MAX_OUTBOX_BYTES = 2_000_000;
|
|
11
11
|
function baseDir(opts) {
|
|
12
12
|
return opts.dir ?? join(homedir(), DIRNAME);
|
|
13
13
|
}
|
|
@@ -22,27 +22,31 @@ function ensureDir(path) {
|
|
|
22
22
|
}
|
|
23
23
|
export function appendOutboxSync(events, opts = {}) {
|
|
24
24
|
if (events.length === 0)
|
|
25
|
-
return;
|
|
25
|
+
return 0;
|
|
26
26
|
const path = outboxPath(opts);
|
|
27
|
+
let discarded = 0;
|
|
27
28
|
try {
|
|
28
29
|
ensureDir(path);
|
|
29
30
|
if (existsSync(path) && statSync(path).size > MAX_OUTBOX_BYTES) {
|
|
30
|
-
compactOutbox(opts);
|
|
31
|
+
discarded += compactOutbox(opts);
|
|
31
32
|
}
|
|
32
33
|
const lines = events.map((e) => JSON.stringify(e)).join('\n') + '\n';
|
|
33
34
|
appendFileSync(path, lines, { encoding: 'utf8', mode: 0o600 });
|
|
34
35
|
}
|
|
35
36
|
catch (err) {
|
|
36
37
|
logger.debug(`outbox append dropped ${events.length} event(s): ${errMsg(err)}`);
|
|
38
|
+
discarded += events.length;
|
|
37
39
|
}
|
|
40
|
+
return discarded;
|
|
38
41
|
}
|
|
39
|
-
export function
|
|
42
|
+
export function readOutboxWithDiscards(opts = {}) {
|
|
40
43
|
const path = outboxPath(opts);
|
|
41
44
|
if (!existsSync(path))
|
|
42
|
-
return [];
|
|
45
|
+
return { events: [], discarded: 0 };
|
|
43
46
|
try {
|
|
44
47
|
const raw = readFileSync(path, 'utf8');
|
|
45
48
|
const events = [];
|
|
49
|
+
let discarded = 0;
|
|
46
50
|
for (const line of raw.split('\n')) {
|
|
47
51
|
const trimmed = line.trim();
|
|
48
52
|
if (trimmed.length === 0)
|
|
@@ -51,17 +55,23 @@ export function readOutbox(opts = {}) {
|
|
|
51
55
|
events.push(JSON.parse(trimmed));
|
|
52
56
|
}
|
|
53
57
|
catch {
|
|
58
|
+
discarded += 1;
|
|
54
59
|
}
|
|
55
60
|
}
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
: events;
|
|
61
|
+
if (events.length > MAX_OUTBOX_EVENTS) {
|
|
62
|
+
const over = events.length - MAX_OUTBOX_EVENTS;
|
|
63
|
+
return { events: events.slice(over), discarded: discarded + over };
|
|
64
|
+
}
|
|
65
|
+
return { events, discarded };
|
|
59
66
|
}
|
|
60
67
|
catch (err) {
|
|
61
68
|
logger.debug(`outbox read failed: ${errMsg(err)}`);
|
|
62
|
-
return [];
|
|
69
|
+
return { events: [], discarded: 0 };
|
|
63
70
|
}
|
|
64
71
|
}
|
|
72
|
+
export function readOutbox(opts = {}) {
|
|
73
|
+
return readOutboxWithDiscards(opts).events;
|
|
74
|
+
}
|
|
65
75
|
export function clearOutbox(opts = {}) {
|
|
66
76
|
try {
|
|
67
77
|
rmSync(outboxPath(opts), { force: true });
|
|
@@ -70,24 +80,39 @@ export function clearOutbox(opts = {}) {
|
|
|
70
80
|
logger.debug(`outbox clear failed: ${errMsg(err)}`);
|
|
71
81
|
}
|
|
72
82
|
}
|
|
73
|
-
function
|
|
74
|
-
|
|
83
|
+
export function rewriteOutbox(events, opts = {}) {
|
|
84
|
+
if (events.length === 0) {
|
|
85
|
+
clearOutbox(opts);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
75
88
|
const path = outboxPath(opts);
|
|
76
89
|
try {
|
|
77
|
-
|
|
78
|
-
clearOutbox(opts);
|
|
79
|
-
return;
|
|
80
|
-
}
|
|
90
|
+
ensureDir(path);
|
|
81
91
|
const tmp = join(dirname(path), `.outbox.${process.pid}.tmp`);
|
|
82
|
-
writeFileSync(tmp,
|
|
92
|
+
writeFileSync(tmp, events.map((e) => JSON.stringify(e)).join('\n') + '\n', {
|
|
83
93
|
encoding: 'utf8',
|
|
84
94
|
mode: 0o600,
|
|
85
95
|
});
|
|
86
96
|
renameSync(tmp, path);
|
|
87
97
|
}
|
|
88
98
|
catch (err) {
|
|
89
|
-
logger.debug(`outbox
|
|
99
|
+
logger.debug(`outbox rewrite failed: ${errMsg(err)}`);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
function compactOutbox(opts = {}) {
|
|
103
|
+
const { events, discarded } = readOutboxWithDiscards(opts);
|
|
104
|
+
const target = MAX_OUTBOX_BYTES / 2;
|
|
105
|
+
let bytes = 0;
|
|
106
|
+
let firstKept = Math.max(events.length - 1, 0);
|
|
107
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
108
|
+
bytes += Buffer.byteLength(JSON.stringify(events[i]), 'utf8') + 1;
|
|
109
|
+
if (bytes > target)
|
|
110
|
+
break;
|
|
111
|
+
firstKept = i;
|
|
90
112
|
}
|
|
113
|
+
const kept = events.slice(firstKept);
|
|
114
|
+
rewriteOutbox(kept, opts);
|
|
115
|
+
return discarded + (events.length - kept.length);
|
|
91
116
|
}
|
|
92
117
|
export function writeSessionStateSync(state, opts = {}) {
|
|
93
118
|
const path = sessionStatePath(opts);
|
|
@@ -2,6 +2,7 @@ import { runScript } from '../utils/run-script.js';
|
|
|
2
2
|
import { validateArgs } from '../utils/validate.js';
|
|
3
3
|
import { toolErrorResult, runSnippetTool, applyToActiveLayerProp } from '../utils/tool-helpers.js';
|
|
4
4
|
import { getPendingRawDevelop } from '../core/raw-develop-state.js';
|
|
5
|
+
import { CAMERA_RAW_TOOL } from '../core/tool-activity.js';
|
|
5
6
|
const addAdjustmentLayerSchema = {
|
|
6
7
|
type: 'object',
|
|
7
8
|
properties: {
|
|
@@ -844,7 +845,7 @@ async function addAdjustmentLayer(connection, snippetClient, rawArgs) {
|
|
|
844
845
|
if (pendingRaw) {
|
|
845
846
|
const source = pendingRaw.documentName || pendingRaw.filePath || 'a raw source';
|
|
846
847
|
const note = `This document was opened from raw source ${source} with no Camera Raw develop pass yet — ` +
|
|
847
|
-
|
|
848
|
+
`consider ${CAMERA_RAW_TOOL} first for global tone/color. Nothing failed; ignore if the ` +
|
|
848
849
|
'user prescribed this exact layer or the file was already developed elsewhere.';
|
|
849
850
|
if (typeof result === 'object' && result !== null) {
|
|
850
851
|
structured.raw_develop_pending = note;
|