@supportpages.io/wtfm 0.0.0-stage → 0.3.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/LICENSE +201 -0
- package/NOTICE +23 -0
- package/README.md +392 -2
- package/dist/actions.d.ts +98 -0
- package/dist/actions.js +86 -0
- package/dist/actions.js.map +1 -0
- package/dist/agent-settings.d.ts +22 -0
- package/dist/agent-settings.js +35 -0
- package/dist/agent-settings.js.map +1 -0
- package/dist/api.d.ts +14 -0
- package/dist/api.js +159 -0
- package/dist/api.js.map +1 -0
- package/dist/article-link.d.ts +16 -0
- package/dist/article-link.js +25 -0
- package/dist/article-link.js.map +1 -0
- package/dist/artifacts.d.ts +45 -0
- package/dist/artifacts.js +144 -0
- package/dist/artifacts.js.map +1 -0
- package/dist/brand.d.ts +4 -0
- package/dist/brand.js +9 -0
- package/dist/brand.js.map +1 -0
- package/dist/bridge.d.ts +2964 -0
- package/dist/bridge.js +1036 -0
- package/dist/bridge.js.map +1 -0
- package/dist/capacity.d.ts +5 -0
- package/dist/capacity.js +11 -0
- package/dist/capacity.js.map +1 -0
- package/dist/credentials.d.ts +13 -0
- package/dist/credentials.js +88 -0
- package/dist/credentials.js.map +1 -0
- package/dist/development-tls.d.ts +8 -0
- package/dist/development-tls.js +38 -0
- package/dist/development-tls.js.map +1 -0
- package/dist/errors.d.ts +15 -0
- package/dist/errors.js +18 -0
- package/dist/errors.js.map +1 -0
- package/dist/export.d.ts +11 -0
- package/dist/export.js +91 -0
- package/dist/export.js.map +1 -0
- package/dist/hosted-operations.d.ts +108 -0
- package/dist/hosted-operations.js +120 -0
- package/dist/hosted-operations.js.map +1 -0
- package/dist/hosting-benefits.d.ts +66 -0
- package/dist/hosting-benefits.js +67 -0
- package/dist/hosting-benefits.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/local-inventory.d.ts +22 -0
- package/dist/local-inventory.js +59 -0
- package/dist/local-inventory.js.map +1 -0
- package/dist/local-setup.d.ts +208 -0
- package/dist/local-setup.js +140 -0
- package/dist/local-setup.js.map +1 -0
- package/dist/pairing.d.ts +71 -0
- package/dist/pairing.js +235 -0
- package/dist/pairing.js.map +1 -0
- package/dist/preferences.d.ts +18 -0
- package/dist/preferences.js +44 -0
- package/dist/preferences.js.map +1 -0
- package/dist/progress.d.ts +205 -0
- package/dist/progress.js +224 -0
- package/dist/progress.js.map +1 -0
- package/dist/reminders.d.ts +31 -0
- package/dist/reminders.js +73 -0
- package/dist/reminders.js.map +1 -0
- package/dist/replace-connection.d.ts +6 -0
- package/dist/replace-connection.js +77 -0
- package/dist/replace-connection.js.map +1 -0
- package/dist/repository-actions.d.ts +18 -0
- package/dist/repository-actions.js +9 -0
- package/dist/repository-actions.js.map +1 -0
- package/dist/repository-benefits.d.ts +96 -0
- package/dist/repository-benefits.js +64 -0
- package/dist/repository-benefits.js.map +1 -0
- package/dist/run-update.d.ts +17 -0
- package/dist/run-update.js +27 -0
- package/dist/run-update.js.map +1 -0
- package/dist/runs.d.ts +541 -0
- package/dist/runs.js +146 -0
- package/dist/runs.js.map +1 -0
- package/dist/runtime.d.ts +12 -0
- package/dist/runtime.js +58 -0
- package/dist/runtime.js.map +1 -0
- package/dist/schema.d.ts +277 -0
- package/dist/schema.js +66 -0
- package/dist/schema.js.map +1 -0
- package/dist/server.d.ts +4 -0
- package/dist/server.js +298 -0
- package/dist/server.js.map +1 -0
- package/dist/session.d.ts +1714 -0
- package/dist/session.js +619 -0
- package/dist/session.js.map +1 -0
- package/dist/settings.d.ts +9 -0
- package/dist/settings.js +21 -0
- package/dist/settings.js.map +1 -0
- package/dist/sync.d.ts +495 -0
- package/dist/sync.js +191 -0
- package/dist/sync.js.map +1 -0
- package/dist/telemetry-scrub.d.ts +13 -0
- package/dist/telemetry-scrub.js +51 -0
- package/dist/telemetry-scrub.js.map +1 -0
- package/dist/telemetry.d.ts +73 -0
- package/dist/telemetry.js +173 -0
- package/dist/telemetry.js.map +1 -0
- package/dist/walkthroughs.d.ts +224 -0
- package/dist/walkthroughs.js +109 -0
- package/dist/walkthroughs.js.map +1 -0
- package/dist/workspace.d.ts +14 -0
- package/dist/workspace.js +127 -0
- package/dist/workspace.js.map +1 -0
- package/dist/writer-agent.d.ts +21 -0
- package/dist/writer-agent.js +27 -0
- package/dist/writer-agent.js.map +1 -0
- package/dist/writer-entry.d.ts +171 -0
- package/dist/writer-entry.js +233 -0
- package/dist/writer-entry.js.map +1 -0
- package/dist/writing-style.d.ts +7 -0
- package/dist/writing-style.js +52 -0
- package/dist/writing-style.js.map +1 -0
- package/engine/SYNC.json +4 -0
- package/engine/VERSION +1 -0
- package/engine/detect-project/README.md +141 -0
- package/engine/detect-project/SKILL.md +1421 -0
- package/engine/detect-project/assets/desktop/desktop-frame.css +428 -0
- package/engine/detect-project/assets/game/game-frame.css +132 -0
- package/engine/detect-project/assets/macosui/LICENSE-puppertino.txt +21 -0
- package/engine/detect-project/assets/macosui/VERSIONS.txt +1 -0
- package/engine/detect-project/assets/macosui/fonts.css +15 -0
- package/engine/detect-project/assets/macosui/macos-frame.css +481 -0
- package/engine/detect-project/assets/macosui/puppertino.css +2153 -0
- package/engine/detect-project/assets/mobileui/LICENSE-fonts.txt +13 -0
- package/engine/detect-project/assets/mobileui/LICENSE-framework7.txt +52 -0
- package/engine/detect-project/assets/mobileui/VERSIONS.txt +6 -0
- package/engine/detect-project/assets/mobileui/device-frame.css +316 -0
- package/engine/detect-project/assets/mobileui/f7-color-theme.mjs +1345 -0
- package/engine/detect-project/assets/mobileui/f7-icons-names.json +1254 -0
- package/engine/detect-project/assets/mobileui/fonts.css +16 -0
- package/engine/detect-project/assets/mobileui/framework7-components.css +39 -0
- package/engine/detect-project/assets/mobileui/framework7-core.css +5245 -0
- package/engine/detect-project/assets/mobileui/icons.css +31 -0
- package/engine/detect-project/assets/mobileui/md3-defaults.css +89 -0
- package/engine/detect-project/assets/mobileui/platforms.json +46 -0
- package/engine/detect-project/assets/tailwind-fallback.css +1729 -0
- package/engine/detect-project/assets/webtui/LICENSE-webtui.txt +28 -0
- package/engine/detect-project/assets/webtui/VERSIONS.txt +7 -0
- package/engine/detect-project/assets/webtui/terminal-frame.css +195 -0
- package/engine/detect-project/assets/webtui/theme-catppuccin.css +1 -0
- package/engine/detect-project/assets/webtui/theme-everforest.css +1 -0
- package/engine/detect-project/assets/webtui/theme-gruvbox.css +1 -0
- package/engine/detect-project/assets/webtui/theme-nord.css +1 -0
- package/engine/detect-project/assets/webtui/theme-vitesse.css +1 -0
- package/engine/detect-project/assets/webtui/themes.json +37 -0
- package/engine/detect-project/assets/webtui/webtui-core.css +1 -0
- package/engine/detect-project/assets/win32ui/7css.css +2 -0
- package/engine/detect-project/assets/win32ui/LICENSE-7css.txt +21 -0
- package/engine/detect-project/assets/win32ui/VERSIONS.txt +1 -0
- package/engine/detect-project/assets/win32ui/win32-frame.css +278 -0
- package/engine/detect-project/package-lock.json +12 -0
- package/engine/detect-project/package.json +10 -0
- package/engine/detect-project/scripts/apply_runtime_profiles.js +313 -0
- package/engine/detect-project/scripts/check_css_health.js +412 -0
- package/engine/detect-project/scripts/check_project_map.js +150 -0
- package/engine/detect-project/scripts/check_runtime_coverage.js +311 -0
- package/engine/detect-project/scripts/check_runtime_recipe_quality.js +184 -0
- package/engine/detect-project/scripts/classify_app_type.sh +246 -0
- package/engine/detect-project/scripts/classify_surface.sh +95 -0
- package/engine/detect-project/scripts/classify_workspace.js +39 -0
- package/engine/detect-project/scripts/compile_css.sh +447 -0
- package/engine/detect-project/scripts/detect_static.js +645 -0
- package/engine/detect-project/scripts/detect_structure.js +187 -0
- package/engine/detect-project/scripts/include_census.js +451 -0
- package/engine/detect-project/scripts/json_get.js +142 -0
- package/engine/detect-project/scripts/merge_json.js +52 -0
- package/engine/detect-project/scripts/recommend_model_tier.js +252 -0
- package/engine/detect-project/scripts/resolve_route_chains.js +135 -0
- package/engine/detect-project/scripts/run_css_build.sh +40 -0
- package/engine/detect-project/scripts/sanitize_css.js +83 -0
- package/engine/detect-project/scripts/test_classify_surface.js +108 -0
- package/engine/detect-project/scripts/test_node_helpers.js +142 -0
- package/engine/detect-project/scripts/test_recommend_model_tier.js +119 -0
- package/engine/detect-project/scripts/test_resolve_route_chains.js +182 -0
- package/engine/detect-project/scripts/test_runtime_coverage.js +323 -0
- package/engine/detect-project/scripts/test_runtime_recipe_quality.js +187 -0
- package/engine/detect-project/scripts/theme_overrides.js +169 -0
- package/engine/detect-project/scripts/write_branding.js +129 -0
- package/engine/generate-illustrated-article/SKILL.md +461 -0
- package/engine/generate-illustrated-article/contracts/desktop.md +90 -0
- package/engine/generate-illustrated-article/contracts/game.md +38 -0
- package/engine/generate-illustrated-article/contracts/label-evidence.md +38 -0
- package/engine/generate-illustrated-article/contracts/macos.md +79 -0
- package/engine/generate-illustrated-article/contracts/mobile.md +40 -0
- package/engine/generate-illustrated-article/contracts/terminal.md +29 -0
- package/engine/generate-illustrated-article/contracts/win32.md +43 -0
- package/engine/generate-illustrated-article/package-lock.json +366 -0
- package/engine/generate-illustrated-article/package.json +15 -0
- package/engine/generate-illustrated-article/scripts/article_blocks.js +34 -0
- package/engine/generate-illustrated-article/scripts/check_article_json.js +105 -0
- package/engine/generate-illustrated-article/scripts/emit_walkthrough_signals.js +100 -0
- package/engine/generate-illustrated-article/scripts/extract_images.js +187 -0
- package/engine/generate-illustrated-article/scripts/generate_content_images.js +376 -0
- package/engine/generate-illustrated-article/scripts/include_census.js +451 -0
- package/engine/generate-illustrated-article/scripts/inject_assets.js +1691 -0
- package/engine/generate-illustrated-article/scripts/jit_mockup_css.js +191 -0
- package/engine/generate-illustrated-article/scripts/label_evidence.js +87 -0
- package/engine/generate-illustrated-article/scripts/lint_article_copy.js +252 -0
- package/engine/generate-illustrated-article/scripts/lint_mockup_fidelity.js +2403 -0
- package/engine/generate-illustrated-article/scripts/polish_tickets.js +1148 -0
- package/engine/generate-illustrated-article/scripts/related_repos.sh +52 -0
- package/engine/generate-illustrated-article/scripts/render_all.js +177 -0
- package/engine/generate-illustrated-article/scripts/render_mockup.js +665 -0
- package/engine/generate-illustrated-article/scripts/render_ready.js +164 -0
- package/engine/generate-illustrated-article/scripts/resolve_workspace.sh +86 -0
- package/engine/generate-illustrated-article/scripts/runtime_region_geometry.js +55 -0
- package/engine/generate-illustrated-article/scripts/source_paths.js +49 -0
- package/engine/generate-illustrated-article/scripts/test_control_visibility.js +30 -0
- package/engine/generate-illustrated-article/scripts/test_emit_walkthrough_signals.js +157 -0
- package/engine/generate-illustrated-article/scripts/test_generate_content_images.js +168 -0
- package/engine/generate-illustrated-article/scripts/test_include_census.js +126 -0
- package/engine/generate-illustrated-article/scripts/test_label_evidence.js +49 -0
- package/engine/generate-illustrated-article/scripts/test_lint_article_copy.js +138 -0
- package/engine/generate-illustrated-article/scripts/test_lint_mockup_fidelity.js +653 -0
- package/engine/generate-illustrated-article/scripts/test_node_ports.js +152 -0
- package/engine/generate-illustrated-article/scripts/test_polish_tickets.js +481 -0
- package/engine/generate-illustrated-article/scripts/test_related_repos.js +64 -0
- package/engine/generate-illustrated-article/scripts/test_render_ready.js +85 -0
- package/engine/generate-illustrated-article/scripts/test_source_paths.js +68 -0
- package/engine/generate-illustrated-article/scripts/trace_hook.js +79 -0
- package/engine/generate-illustrated-article/scripts/trace_hook.sh +4 -0
- package/engine/generate-illustrated-article/scripts/validate_html.js +114 -0
- package/engine/generate-illustrated-article/scripts/watermark.js +69 -0
- package/install.sh +14 -0
- package/package.json +57 -4
- package/scripts/auth.mjs +33 -0
- package/scripts/auto-update.mjs +7 -0
- package/scripts/build-plugin.mjs +58 -0
- package/scripts/build-release.mjs +94 -0
- package/scripts/check-release.mjs +40 -0
- package/scripts/check-runtime.mjs +10 -0
- package/scripts/cli.mjs +8 -0
- package/scripts/install.mjs +64 -0
- package/scripts/lib/agent-runner.mjs +181 -0
- package/scripts/lib/agent-settings.mjs +215 -0
- package/scripts/lib/article-skills.mjs +82 -0
- package/scripts/lib/auto-update.mjs +50 -0
- package/scripts/lib/brand.mjs +9 -0
- package/scripts/lib/browser.mjs +12 -0
- package/scripts/lib/claude-connection.mjs +33 -0
- package/scripts/lib/claude-permissions.mjs +47 -0
- package/scripts/lib/claude-plugin.mjs +15 -0
- package/scripts/lib/claude-writer.mjs +10 -0
- package/scripts/lib/cli-main.mjs +85 -0
- package/scripts/lib/cli.mjs +888 -0
- package/scripts/lib/codex-config.mjs +51 -0
- package/scripts/lib/codex-integration.mjs +61 -0
- package/scripts/lib/codex-skill.mjs +34 -0
- package/scripts/lib/harness-models.mjs +109 -0
- package/scripts/lib/install.mjs +269 -0
- package/scripts/lib/managed-writer.mjs +38 -0
- package/scripts/lib/planning.mjs +263 -0
- package/scripts/lib/prepare-update.mjs +85 -0
- package/scripts/lib/refresh-writers.mjs +12 -0
- package/scripts/lib/remove.mjs +167 -0
- package/scripts/lib/renderer.mjs +34 -0
- package/scripts/lib/terminal.mjs +252 -0
- package/scripts/lib/uninit.mjs +72 -0
- package/scripts/lib/update.mjs +55 -0
- package/scripts/lib/writer-recovery.mjs +35 -0
- package/scripts/lib/yolo.mjs +362 -0
- package/scripts/plugin-session.mjs +65 -0
- package/scripts/prepare-update.mjs +12 -0
- package/scripts/publish-release.mjs +92 -0
- package/scripts/skills/supportpages/SKILL.md +120 -0
- package/scripts/smoke-release.mjs +101 -0
- package/server.json +27 -0
package/dist/sync.js
ADDED
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { stat } from 'node:fs/promises';
|
|
3
|
+
import { remoteArticleSchema, remoteId } from './schema.js';
|
|
4
|
+
import { parse, sha256 } from './artifacts.js';
|
|
5
|
+
import { runSchema } from './runs.js';
|
|
6
|
+
import { fail, publicError } from './errors.js';
|
|
7
|
+
import { Walkthroughs } from './walkthroughs.js';
|
|
8
|
+
import { CLI_NAME } from './brand.js';
|
|
9
|
+
const identity = { id: remoteId, local_article_id: z.uuid().nullable(), title: z.string(), section_id: remoteId.nullable() };
|
|
10
|
+
const generation = z.object({ run_id: z.uuid(), attempt: z.uuid(), state: z.enum(['running', 'failed', 'interrupted', 'cancelled', 'complete', 'kept', 'discarded']) });
|
|
11
|
+
const live = remoteArticleSchema.extend({ ...identity, deleted_at: z.null(), accepted_bundle_hash: z.string().nullable().optional(), generation: generation.nullable().optional() });
|
|
12
|
+
const tombstone = z.object({ ...identity, deleted_at: z.string().min(1) });
|
|
13
|
+
const pageSchema = z.object({ project_id: remoteId, through_id: z.string().regex(/^\d+$/),
|
|
14
|
+
next_cursor: remoteId.nullable(), articles: z.array(z.union([live, tombstone])).max(500) });
|
|
15
|
+
const stateSchema = z.object({ project_id: remoteId.optional(), api_origin: z.url().optional(), local_article_id: z.uuid(), artifact_dir: z.string(), bundle_dir: z.string(),
|
|
16
|
+
bundle_hash: z.string(), uploaded_bundle_hash: z.string().optional(), remote: remoteArticleSchema.optional() });
|
|
17
|
+
/** Observed remote state is separate from the revision an upload was based on. */
|
|
18
|
+
export class ProjectSync {
|
|
19
|
+
bridge;
|
|
20
|
+
constructor(bridge) {
|
|
21
|
+
this.bridge = bridge;
|
|
22
|
+
}
|
|
23
|
+
async localRecords() {
|
|
24
|
+
const { ws, stateRoot } = this.bridge, binding = await this.bridge.binding();
|
|
25
|
+
const archives = [];
|
|
26
|
+
for (const entry of await ws.list(`${stateRoot}/archives`)) {
|
|
27
|
+
if (!entry.isDirectory())
|
|
28
|
+
continue;
|
|
29
|
+
const root = `${stateRoot}/archives/${entry.name}`;
|
|
30
|
+
if (!await ws.exists(`${root}/binding.json`))
|
|
31
|
+
continue;
|
|
32
|
+
const archived = await ws.json(`${root}/binding.json`);
|
|
33
|
+
if (archived.project_id === binding.project_id && archived.api_origin === binding.api_origin)
|
|
34
|
+
archives.push({ root, time: (await stat(await ws.resolve(root))).mtimeMs });
|
|
35
|
+
}
|
|
36
|
+
const roots = [stateRoot, ...archives.sort((a, b) => b.time - a.time).map(entry => entry.root)];
|
|
37
|
+
const result = new Map();
|
|
38
|
+
// Active records win over history; history is never copied to a new project.
|
|
39
|
+
for (const root of roots) {
|
|
40
|
+
const runs = new Map();
|
|
41
|
+
for (const entry of await ws.list(`${root}/runs`)) {
|
|
42
|
+
if (!entry.isDirectory() || !z.uuid().safeParse(entry.name).success)
|
|
43
|
+
continue;
|
|
44
|
+
const parsed = runSchema.safeParse(await ws.json(`${root}/runs/${entry.name}/run.json`));
|
|
45
|
+
if (!parsed.success)
|
|
46
|
+
continue;
|
|
47
|
+
const run = parsed.data;
|
|
48
|
+
if (run.project_id !== binding.project_id || run.api_origin !== binding.api_origin)
|
|
49
|
+
continue;
|
|
50
|
+
const previous = runs.get(run.local_article_id);
|
|
51
|
+
if (!previous || run.started_at > previous.started_at)
|
|
52
|
+
runs.set(run.local_article_id, run);
|
|
53
|
+
}
|
|
54
|
+
const states = new Map();
|
|
55
|
+
for (const entry of await ws.list(`${root}/articles`)) {
|
|
56
|
+
if (!entry.isFile() || !entry.name.endsWith('.json'))
|
|
57
|
+
continue;
|
|
58
|
+
const parsed = stateSchema.safeParse(await ws.json(`${root}/articles/${entry.name}`));
|
|
59
|
+
if (parsed.success && (!parsed.data.project_id || parsed.data.project_id === binding.project_id && parsed.data.api_origin === binding.api_origin))
|
|
60
|
+
states.set(parsed.data.local_article_id, parsed.data);
|
|
61
|
+
}
|
|
62
|
+
for (const id of new Set([...runs.keys(), ...states.keys()])) {
|
|
63
|
+
if (result.has(id))
|
|
64
|
+
continue;
|
|
65
|
+
const run = runs.get(id), state = states.get(id);
|
|
66
|
+
const artifact = run?.artifact_dir ?? state.artifact_dir;
|
|
67
|
+
let changed = run?.status === 'prepared' || !state || state.bundle_hash !== state.uploaded_bundle_hash;
|
|
68
|
+
let contentChanged = !state;
|
|
69
|
+
if (state) {
|
|
70
|
+
const bundle = root !== stateRoot && state.bundle_dir.startsWith(`${stateRoot}/`)
|
|
71
|
+
? root + state.bundle_dir.slice(stateRoot.length) : state.bundle_dir;
|
|
72
|
+
try {
|
|
73
|
+
const manifest = await ws.json(`${bundle}/manifest.json`);
|
|
74
|
+
contentChanged = sha256(await ws.read(`${artifact}/article.json`)) !== manifest.article_sha256;
|
|
75
|
+
for (const image of manifest.images)
|
|
76
|
+
if (sha256(await ws.read(`${artifact}/${image.filename}`, 10 * 1024 * 1024)) !== image.sha256)
|
|
77
|
+
contentChanged = true;
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
contentChanged = true;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
changed ||= contentChanged;
|
|
84
|
+
result.set(id, { local_article_id: id, artifact_dir: artifact, run_id: run?.id, title: run?.title,
|
|
85
|
+
phase: run?.phase, baseline: run?.remote ?? run?.progress?.article ?? state?.remote, changed, content_changed: contentChanged, bundle_hash: state?.bundle_hash, generation_attempt: run?.progress?.attempt });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return [...result.values()];
|
|
89
|
+
}
|
|
90
|
+
// Caller holds the workspace lock. Commit only a complete, validated inventory.
|
|
91
|
+
async refresh(context, includeWalkthroughs = true) {
|
|
92
|
+
const binding = await this.bridge.binding();
|
|
93
|
+
try {
|
|
94
|
+
context ??= await this.bridge.context();
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
if (['not_found', 'permission_denied'].includes(publicError(error).code))
|
|
98
|
+
fail('project_unavailable', `This help centre is unavailable to the current account. Restore access or run ${CLI_NAME} init to choose a project. The existing link and last sync have been kept.`);
|
|
99
|
+
throw error;
|
|
100
|
+
}
|
|
101
|
+
if (context.project.id !== binding.project_id)
|
|
102
|
+
fail('destination_mismatch', 'The project changed during sync. Run sync again.');
|
|
103
|
+
if (!context.article_sync)
|
|
104
|
+
return { status: 'unsupported', project: context.project,
|
|
105
|
+
instructions: 'Update the SupportPages.io server to sync article metadata. Existing upload revision checks still apply.' };
|
|
106
|
+
const remote = [], seen = new Set();
|
|
107
|
+
let cursor = null, through;
|
|
108
|
+
for (;;) {
|
|
109
|
+
const query = cursor ? `?after_id=${cursor}&through_id=${through}` : '';
|
|
110
|
+
const page = parse(pageSchema, await this.bridge.api.request('GET', `/projects/${binding.project_id}/sync${query}`), 'invalid_response');
|
|
111
|
+
if (page.project_id !== binding.project_id || through !== undefined && page.through_id !== through)
|
|
112
|
+
fail('invalid_response', 'The sync response changed project or inventory boundaries.');
|
|
113
|
+
through = page.through_id;
|
|
114
|
+
for (const article of page.articles) {
|
|
115
|
+
if (seen.has(article.id) || BigInt(article.id) <= BigInt(cursor ?? '0') || BigInt(article.id) > BigInt(page.through_id))
|
|
116
|
+
fail('invalid_response', 'Invalid article sync page.');
|
|
117
|
+
seen.add(article.id);
|
|
118
|
+
remote.push(article);
|
|
119
|
+
}
|
|
120
|
+
if (!page.next_cursor)
|
|
121
|
+
break;
|
|
122
|
+
if (page.next_cursor !== page.articles.at(-1)?.id || BigInt(page.next_cursor) <= BigInt(cursor ?? '0'))
|
|
123
|
+
fail('invalid_response', 'Invalid article sync cursor.');
|
|
124
|
+
cursor = page.next_cursor;
|
|
125
|
+
if (remote.length > 100_000)
|
|
126
|
+
fail('inventory_truncated', 'The article inventory is too large to sync. Previous sync records have been kept.');
|
|
127
|
+
}
|
|
128
|
+
const locals = await this.localRecords(), matched = new Set();
|
|
129
|
+
const byId = new Map(remote.map(article => [article.id, article]));
|
|
130
|
+
const byLocalId = new Map();
|
|
131
|
+
for (const article of remote)
|
|
132
|
+
if (article.local_article_id) {
|
|
133
|
+
if (byLocalId.has(article.local_article_id))
|
|
134
|
+
fail('invalid_response', 'Multiple remote articles claim the same local identity.');
|
|
135
|
+
byLocalId.set(article.local_article_id, article);
|
|
136
|
+
}
|
|
137
|
+
const articles = locals.map(local => {
|
|
138
|
+
const article = local.baseline ? byId.get(local.baseline.id) : byLocalId.get(local.local_article_id);
|
|
139
|
+
// A record that already links to a remote article by id keeps that link even
|
|
140
|
+
// when the server remembers a different local identity for it (an earlier
|
|
141
|
+
// attempt, or a record rewritten by a later run). The local record wins; the
|
|
142
|
+
// mismatch is reported instead of aborting the whole sync.
|
|
143
|
+
const identity_mismatch = Boolean(article && article.local_article_id && article.local_article_id !== local.local_article_id);
|
|
144
|
+
if (article)
|
|
145
|
+
matched.add(article.id);
|
|
146
|
+
const available = article && article.deleted_at === null ? article : undefined;
|
|
147
|
+
const accepted = available?.accepted_bundle_hash && available.accepted_bundle_hash === local.bundle_hash;
|
|
148
|
+
// A preview still locked to this exact writer can advance after a lost
|
|
149
|
+
// milestone response. The server forbids editor changes until it is kept.
|
|
150
|
+
const ownPreview = available?.generation?.run_id === local.run_id && available?.generation?.attempt === local.generation_attempt &&
|
|
151
|
+
['running', 'failed', 'interrupted', 'cancelled'].includes(available?.generation?.state ?? '');
|
|
152
|
+
const changed = available && local.baseline && available.revision !== local.baseline.revision && !accepted && !ownPreview;
|
|
153
|
+
const sync_status = article?.deleted_at ? 'deleted_remotely' : !article ? local.baseline ? 'unavailable' : 'local_only'
|
|
154
|
+
: changed ? local.changed ? 'conflict' : 'remote_changed' : local.changed && (!accepted || local.content_changed) ? 'local_changes' : 'synced';
|
|
155
|
+
return { ...local, remote: article, sync_status, ...(identity_mismatch ? { identity_mismatch } : {}) };
|
|
156
|
+
});
|
|
157
|
+
const remoteOnly = remote.filter(article => !matched.has(article.id) && article.deleted_at === null)
|
|
158
|
+
.map(article => ({ remote: article, sync_status: 'remote_only' }));
|
|
159
|
+
const previous = await this.bridge.ws.exists(`${this.bridge.stateRoot}/sync.json`)
|
|
160
|
+
? await this.bridge.ws.json(`${this.bridge.stateRoot}/sync.json`) : undefined;
|
|
161
|
+
const walkthroughs = includeWalkthroughs ? await new Walkthroughs(this.bridge).refresh(context, previous?.walkthroughs) : previous?.walkthroughs;
|
|
162
|
+
const result = { version: 1, status: 'ready', project: context.project, api_origin: binding.api_origin,
|
|
163
|
+
walkthroughs,
|
|
164
|
+
synced_at: new Date().toISOString(), product_context: context.product_context, writing_style: context.writing_style,
|
|
165
|
+
articles: [...articles, ...remoteOnly], counts: { local: locals.length, remote: remote.filter(article => !article.deleted_at).length,
|
|
166
|
+
conflicts: articles.filter(article => article.sync_status === 'conflict').length,
|
|
167
|
+
deleted: articles.filter(article => article.sync_status === 'deleted_remotely').length },
|
|
168
|
+
instructions: 'Remote settings and article metadata are current. Local content is preserved. Review remote edits in the editor before updating an article; never recreate a remotely deleted article automatically.' };
|
|
169
|
+
await this.bridge.ws.writeJson(`${this.bridge.stateRoot}/sync.json`, result);
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
async guard(localId, context) {
|
|
173
|
+
const result = await this.refresh(context, false);
|
|
174
|
+
if (result.status !== 'ready')
|
|
175
|
+
return result;
|
|
176
|
+
const item = result.articles.find(item => 'local_article_id' in item && item.local_article_id === localId);
|
|
177
|
+
if (!item)
|
|
178
|
+
return result;
|
|
179
|
+
const editor_url = item.remote && 'editor_url' in item.remote ? item.remote.editor_url : undefined;
|
|
180
|
+
if (item.remote && 'generation' in item.remote && item.remote.generation?.state === 'kept')
|
|
181
|
+
fail('run_closed', 'This preview was kept as an editable draft. Continue in the editor; its writer cannot resume or replace it.', { editor_url });
|
|
182
|
+
if (item.sync_status === 'deleted_remotely')
|
|
183
|
+
fail('article_deleted', 'This article was deleted in SupportPages.io. Restore it there before continuing; it will not be recreated automatically.', { editor_url });
|
|
184
|
+
if (item.sync_status === 'unavailable')
|
|
185
|
+
fail('article_unavailable', 'The linked article is unavailable. Check its project and access before continuing.');
|
|
186
|
+
if (['conflict', 'remote_changed'].includes(item.sync_status))
|
|
187
|
+
fail('revision_conflict', 'This article changed in SupportPages.io. Review the current version in the editor before uploading local changes.', { editor_url });
|
|
188
|
+
return result;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=sync.js.map
|
package/dist/sync.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync.js","sourceRoot":"","sources":["../src/sync.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export type ErrorReport = {
|
|
2
|
+
error_code: string;
|
|
3
|
+
error_class: string;
|
|
4
|
+
message?: string;
|
|
5
|
+
frames: string[];
|
|
6
|
+
};
|
|
7
|
+
/** Credentials, emails, URLs' paths and queries, and home directories never leave the device. */
|
|
8
|
+
export declare function scrubText(text: string, limit: number): string;
|
|
9
|
+
/** Keeps only the Writer's own frames by location; anything else on the
|
|
10
|
+
* machine (the user's project, other packages) becomes <external>. */
|
|
11
|
+
export declare function scrubFrame(line: string, installRoot?: string): string;
|
|
12
|
+
/** A report for an unexpected error, or undefined for an expected one. */
|
|
13
|
+
export declare function errorReport(error: unknown, installRoot?: string): ErrorReport | undefined;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import os from 'node:os';
|
|
2
|
+
import { pathToFileURL } from 'node:url';
|
|
3
|
+
import { SupportPagesError } from './errors.js';
|
|
4
|
+
/** Codes that point at a Writer or API bug. Every other SupportPagesError is an
|
|
5
|
+
* expected account, plan or run state the user recovers from, not a crash. */
|
|
6
|
+
const unexpectedCodes = new Set(['internal_error', 'invalid_response', 'remote_error']);
|
|
7
|
+
const MAX_FRAMES = 30;
|
|
8
|
+
/** Credentials, emails, URLs' paths and queries, and home directories never leave the device. */
|
|
9
|
+
export function scrubText(text, limit) {
|
|
10
|
+
const home = os.homedir();
|
|
11
|
+
let value = text.slice(0, limit * 2);
|
|
12
|
+
if (home.length > 1)
|
|
13
|
+
value = value.split(home).join('~');
|
|
14
|
+
return value
|
|
15
|
+
.replace(/\bsp_(?:local|live)_[A-Za-z0-9]+/g, '[token]')
|
|
16
|
+
.replace(/\bBearer\s+\S+/gi, 'Bearer [token]')
|
|
17
|
+
.replace(/[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi, '[email]')
|
|
18
|
+
.replace(/(https?:\/\/[^\s/?#]+)[^\s)]*/g, '$1')
|
|
19
|
+
.replace(/\b[A-Fa-f0-9]{32,}\b/g, '[hex]')
|
|
20
|
+
.slice(0, limit);
|
|
21
|
+
}
|
|
22
|
+
/** Keeps only the Writer's own frames by location; anything else on the
|
|
23
|
+
* machine (the user's project, other packages) becomes <external>. */
|
|
24
|
+
export function scrubFrame(line, installRoot) {
|
|
25
|
+
let frame = line.trim().replace(/^at\s+/, '');
|
|
26
|
+
if (installRoot) {
|
|
27
|
+
for (const prefix of [pathToFileURL(installRoot).href, installRoot])
|
|
28
|
+
frame = frame.split(prefix).join('writer');
|
|
29
|
+
}
|
|
30
|
+
frame = frame
|
|
31
|
+
.replace(/(^|[\s(])(?:file:\/\/)?\/[^\s:()]+/g, '$1<external>')
|
|
32
|
+
.replace(/(^|[\s(])(?:file:\/\/\/)?[A-Za-z]:[\\/][^\s:()]+/g, '$1<external>');
|
|
33
|
+
return scrubText(frame, 200);
|
|
34
|
+
}
|
|
35
|
+
/** A report for an unexpected error, or undefined for an expected one. */
|
|
36
|
+
export function errorReport(error, installRoot) {
|
|
37
|
+
if (error instanceof SupportPagesError && !unexpectedCodes.has(error.code))
|
|
38
|
+
return undefined;
|
|
39
|
+
const name = error instanceof Error ? error.constructor.name || error.name : typeof error;
|
|
40
|
+
const stack = error instanceof Error && typeof error.stack === 'string' ? error.stack : '';
|
|
41
|
+
const frames = stack.split('\n').filter(line => /^\s+at\s/.test(line)).slice(0, MAX_FRAMES).map(line => scrubFrame(line, installRoot));
|
|
42
|
+
return {
|
|
43
|
+
error_code: error instanceof SupportPagesError ? error.code : 'internal_error',
|
|
44
|
+
error_class: /^[A-Za-z][A-Za-z0-9_.]{0,63}$/.test(name) ? name : 'Error',
|
|
45
|
+
// Only our own fail() text is sent; a foreign exception's message can quote
|
|
46
|
+
// file contents, paths or server responses.
|
|
47
|
+
...(error instanceof SupportPagesError ? { message: scrubText(error.message, 300) } : {}),
|
|
48
|
+
frames,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//# sourceMappingURL=telemetry-scrub.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry-scrub.js","sourceRoot":"","sources":["../src/telemetry-scrub.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { type DevicePreferences } from './preferences.js';
|
|
2
|
+
/** Anonymous usage counts and crash reports, on by default and disclosed once.
|
|
3
|
+
* Sent without credentials to the Writer's own API origin, so nothing ties
|
|
4
|
+
* them to an account. See docs/writer/client/telemetry.md in the RTFM repo
|
|
5
|
+
* for the exact fields; nothing else is ever sent. */
|
|
6
|
+
export type TelemetryEvent = 'project_init' | 'article_completed' | 'walkthrough_completed' | 'yolo_started' | 'yolo_completed';
|
|
7
|
+
type Properties = Record<string, string | number | undefined>;
|
|
8
|
+
export declare const telemetryNotice = "SupportPages Writer sends anonymous usage counts and crash reports: no code, file paths, article titles or account details. Ask me to turn this off, or set SUPPORTPAGES_TELEMETRY=0.";
|
|
9
|
+
export type TelemetryReason = 'SUPPORTPAGES_TELEMETRY' | 'DO_NOT_TRACK' | 'CI' | 'preference' | 'default';
|
|
10
|
+
export declare function telemetryStatus(env: NodeJS.ProcessEnv, prefs: DevicePreferences): {
|
|
11
|
+
enabled: boolean;
|
|
12
|
+
reason: TelemetryReason;
|
|
13
|
+
};
|
|
14
|
+
export type TelemetryOptions = {
|
|
15
|
+
configDir: string;
|
|
16
|
+
origin: string;
|
|
17
|
+
installRoot?: string;
|
|
18
|
+
env?: NodeJS.ProcessEnv;
|
|
19
|
+
fetcher?: typeof fetch;
|
|
20
|
+
log?: (line: string) => void;
|
|
21
|
+
};
|
|
22
|
+
export declare class Telemetry {
|
|
23
|
+
private options;
|
|
24
|
+
private queue;
|
|
25
|
+
private queued;
|
|
26
|
+
private errors;
|
|
27
|
+
private fingerprints;
|
|
28
|
+
private state?;
|
|
29
|
+
private chain;
|
|
30
|
+
private pending;
|
|
31
|
+
private clientName?;
|
|
32
|
+
constructor(options: TelemetryOptions);
|
|
33
|
+
private get env();
|
|
34
|
+
get endpoint(): string;
|
|
35
|
+
/** The MCP client's self-reported name, e.g. claude-code or codex. */
|
|
36
|
+
setClient(name: () => string | undefined): void;
|
|
37
|
+
/** Creates the install id on first enabled use, which is what counts an install. */
|
|
38
|
+
private ready;
|
|
39
|
+
track(event: TelemetryEvent, properties?: Properties): void;
|
|
40
|
+
/** Reports unexpected errors only; expected account and run states are not bugs. */
|
|
41
|
+
error(error: unknown, tool?: string): void;
|
|
42
|
+
private schedule;
|
|
43
|
+
private flush;
|
|
44
|
+
/** Counts a new install as soon as the Writer first runs, before any other event. */
|
|
45
|
+
start(): void;
|
|
46
|
+
/** Resolves once everything queued so far has been sent or dropped. */
|
|
47
|
+
drain(): Promise<void>;
|
|
48
|
+
/** The one-time disclosure, returned the first time it is due and never again. */
|
|
49
|
+
notice(): Promise<"SupportPages Writer sends anonymous usage counts and crash reports: no code, file paths, article titles or account details. Ask me to turn this off, or set SUPPORTPAGES_TELEMETRY=0." | undefined>;
|
|
50
|
+
status(): Promise<{
|
|
51
|
+
sends: string;
|
|
52
|
+
disable: string;
|
|
53
|
+
install_id?: string | undefined;
|
|
54
|
+
endpoint: string;
|
|
55
|
+
enabled: boolean;
|
|
56
|
+
reason: TelemetryReason;
|
|
57
|
+
}>;
|
|
58
|
+
setEnabled(enabled: boolean): Promise<{
|
|
59
|
+
message: string;
|
|
60
|
+
sends: string;
|
|
61
|
+
disable: string;
|
|
62
|
+
install_id?: string | undefined;
|
|
63
|
+
endpoint: string;
|
|
64
|
+
enabled: boolean;
|
|
65
|
+
reason: TelemetryReason;
|
|
66
|
+
}>;
|
|
67
|
+
}
|
|
68
|
+
export declare function configureTelemetry(options: TelemetryOptions | undefined): Telemetry | undefined;
|
|
69
|
+
export declare function activeTelemetry(): Telemetry | undefined;
|
|
70
|
+
export declare function track(event: TelemetryEvent, properties?: Properties): void;
|
|
71
|
+
export declare function reportError(error: unknown, tool?: string): void;
|
|
72
|
+
export declare function secondsSince(start?: string, end?: number): number | undefined;
|
|
73
|
+
export {};
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { randomUUID } from 'node:crypto';
|
|
2
|
+
import { readFile } from 'node:fs/promises';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import { devicePreferences, saveDevicePreferences } from './preferences.js';
|
|
5
|
+
import { errorReport } from './telemetry-scrub.js';
|
|
6
|
+
import { CLI_NAME } from './brand.js';
|
|
7
|
+
export const telemetryNotice = 'SupportPages Writer sends anonymous usage counts and crash reports: no code, file paths, article titles or account details. Ask me to turn this off, or set SUPPORTPAGES_TELEMETRY=0.';
|
|
8
|
+
const disabledWith = /^(0|false|off|no)$/i;
|
|
9
|
+
const MAX_EVENTS = 50;
|
|
10
|
+
const MAX_ERRORS = 10;
|
|
11
|
+
const BATCH = 20;
|
|
12
|
+
const INSTALL_ID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
13
|
+
export function telemetryStatus(env, prefs) {
|
|
14
|
+
const setting = env.SUPPORTPAGES_TELEMETRY?.trim();
|
|
15
|
+
if (setting && disabledWith.test(setting))
|
|
16
|
+
return { enabled: false, reason: 'SUPPORTPAGES_TELEMETRY' };
|
|
17
|
+
const doNotTrack = env.DO_NOT_TRACK?.trim();
|
|
18
|
+
if (doNotTrack && !/^(0|false)$/i.test(doNotTrack))
|
|
19
|
+
return { enabled: false, reason: 'DO_NOT_TRACK' };
|
|
20
|
+
// An explicit SUPPORTPAGES_TELEMETRY=1 is the only way to report from CI.
|
|
21
|
+
if (env.CI && !setting)
|
|
22
|
+
return { enabled: false, reason: 'CI' };
|
|
23
|
+
if (prefs.telemetry === false)
|
|
24
|
+
return { enabled: false, reason: 'preference' };
|
|
25
|
+
return { enabled: true, reason: prefs.telemetry === true ? 'preference' : 'default' };
|
|
26
|
+
}
|
|
27
|
+
export class Telemetry {
|
|
28
|
+
options;
|
|
29
|
+
queue = [];
|
|
30
|
+
queued = 0;
|
|
31
|
+
errors = 0;
|
|
32
|
+
fingerprints = new Set();
|
|
33
|
+
state;
|
|
34
|
+
chain = Promise.resolve();
|
|
35
|
+
pending = false;
|
|
36
|
+
clientName;
|
|
37
|
+
constructor(options) {
|
|
38
|
+
this.options = options;
|
|
39
|
+
}
|
|
40
|
+
get env() { return this.options.env ?? process.env; }
|
|
41
|
+
get endpoint() { return `${this.options.origin}/api/v1/writer_telemetry`; }
|
|
42
|
+
/** The MCP client's self-reported name, e.g. claude-code or codex. */
|
|
43
|
+
setClient(name) { this.clientName = name; }
|
|
44
|
+
/** Creates the install id on first enabled use, which is what counts an install. */
|
|
45
|
+
ready() {
|
|
46
|
+
return this.state ??= (async () => {
|
|
47
|
+
const prefs = await devicePreferences(this.options.configDir);
|
|
48
|
+
if (!telemetryStatus(this.env, prefs).enabled)
|
|
49
|
+
return { enabled: false };
|
|
50
|
+
let installId = typeof prefs.install_id === 'string' && INSTALL_ID.test(prefs.install_id) ? prefs.install_id : undefined;
|
|
51
|
+
if (!installId) {
|
|
52
|
+
installId = randomUUID();
|
|
53
|
+
await saveDevicePreferences(this.options.configDir, { install_id: installId });
|
|
54
|
+
this.queue.unshift({ event: 'install' });
|
|
55
|
+
}
|
|
56
|
+
let version;
|
|
57
|
+
if (this.options.installRoot) {
|
|
58
|
+
try {
|
|
59
|
+
version = JSON.parse(await readFile(path.join(this.options.installRoot, 'package.json'), 'utf8')).version;
|
|
60
|
+
}
|
|
61
|
+
catch { /* Sent without a version. */ }
|
|
62
|
+
}
|
|
63
|
+
return { enabled: true, installId, version };
|
|
64
|
+
})().catch(() => ({ enabled: false }));
|
|
65
|
+
}
|
|
66
|
+
track(event, properties = {}) {
|
|
67
|
+
if (this.queued >= MAX_EVENTS)
|
|
68
|
+
return;
|
|
69
|
+
this.queued += 1;
|
|
70
|
+
// Property values are codes and counts only; anything else (a server
|
|
71
|
+
// message, a title) is dropped rather than sent.
|
|
72
|
+
const safe = Object.fromEntries(Object.entries(properties).filter(([, value]) => typeof value === 'number' ? Number.isInteger(value) && value >= 0 : typeof value === 'string' && /^[a-z][a-z0-9_]{0,63}$/.test(value)));
|
|
73
|
+
this.queue.push({ event, properties: safe });
|
|
74
|
+
this.schedule();
|
|
75
|
+
}
|
|
76
|
+
/** Reports unexpected errors only; expected account and run states are not bugs. */
|
|
77
|
+
error(error, tool) {
|
|
78
|
+
try {
|
|
79
|
+
const report = errorReport(error, this.options.installRoot);
|
|
80
|
+
if (!report)
|
|
81
|
+
return;
|
|
82
|
+
const fingerprint = `${report.error_class}:${report.error_code}:${report.frames[0] ?? ''}`;
|
|
83
|
+
if (this.errors >= MAX_ERRORS || this.queued >= MAX_EVENTS || this.fingerprints.has(fingerprint))
|
|
84
|
+
return;
|
|
85
|
+
this.fingerprints.add(fingerprint);
|
|
86
|
+
this.errors += 1;
|
|
87
|
+
this.queued += 1;
|
|
88
|
+
const { message, frames, ...properties } = report;
|
|
89
|
+
this.queue.push({ event: 'error', properties: { ...properties, tool: tool && /^[a-z][a-z0-9_]{0,63}$/.test(tool) ? tool : undefined }, error: { message, frames } });
|
|
90
|
+
this.schedule();
|
|
91
|
+
}
|
|
92
|
+
catch { /* Reporting must never add a failure of its own. */ }
|
|
93
|
+
}
|
|
94
|
+
schedule() {
|
|
95
|
+
if (this.pending)
|
|
96
|
+
return;
|
|
97
|
+
this.pending = true;
|
|
98
|
+
this.chain = this.chain.then(async () => {
|
|
99
|
+
await Promise.resolve();
|
|
100
|
+
this.pending = false;
|
|
101
|
+
await this.flush();
|
|
102
|
+
}).catch(() => { });
|
|
103
|
+
}
|
|
104
|
+
async flush() {
|
|
105
|
+
const state = await this.ready();
|
|
106
|
+
if (!state.enabled || !state.installId) {
|
|
107
|
+
this.queue = [];
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
while (this.queue.length) {
|
|
111
|
+
const events = this.queue.splice(0, BATCH).map(({ event, properties, error }) => ({
|
|
112
|
+
event, ...(properties ? { properties: Object.fromEntries(Object.entries(properties).filter(([, value]) => value !== undefined)) } : {}), ...(error ? { error } : {}),
|
|
113
|
+
}));
|
|
114
|
+
const body = JSON.stringify({ install_id: state.installId, context: {
|
|
115
|
+
writer_version: state.version, host_client: this.clientName?.()?.slice(0, 64),
|
|
116
|
+
os: process.platform, arch: process.arch, node_major: process.versions.node.split('.')[0],
|
|
117
|
+
}, events });
|
|
118
|
+
if (/^(1|true)$/i.test(this.env.SUPPORTPAGES_TELEMETRY_DEBUG ?? ''))
|
|
119
|
+
(this.options.log ?? (line => process.stderr.write(line)))(`${CLI_NAME} telemetry → ${this.endpoint} ${body}\n`);
|
|
120
|
+
try {
|
|
121
|
+
await (this.options.fetcher ?? fetch)(this.endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body, redirect: 'error', signal: AbortSignal.timeout(3000) });
|
|
122
|
+
}
|
|
123
|
+
catch { /* Offline, blocked or an older server: counts are best-effort. */ }
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** Counts a new install as soon as the Writer first runs, before any other event. */
|
|
127
|
+
start() { this.schedule(); }
|
|
128
|
+
/** Resolves once everything queued so far has been sent or dropped. */
|
|
129
|
+
drain() { return this.chain; }
|
|
130
|
+
/** The one-time disclosure, returned the first time it is due and never again. */
|
|
131
|
+
async notice() {
|
|
132
|
+
try {
|
|
133
|
+
const prefs = await devicePreferences(this.options.configDir);
|
|
134
|
+
if (!telemetryStatus(this.env, prefs).enabled || prefs.telemetry_notice_shown)
|
|
135
|
+
return undefined;
|
|
136
|
+
await saveDevicePreferences(this.options.configDir, { telemetry_notice_shown: true });
|
|
137
|
+
return telemetryNotice;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
return undefined;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async status() {
|
|
144
|
+
const prefs = await devicePreferences(this.options.configDir);
|
|
145
|
+
const status = telemetryStatus(this.env, prefs);
|
|
146
|
+
return { ...status, endpoint: this.endpoint,
|
|
147
|
+
...(typeof prefs.install_id === 'string' ? { install_id: prefs.install_id } : {}),
|
|
148
|
+
sends: 'Anonymous install id, Writer version, coding client name, OS, CPU architecture, Node.js major version; counts of installs, project setups, finished articles, walkthroughs and whole-help-centre runs (with outcome, location and duration); and error class, code and stack frames for unexpected errors. Never code, file paths, article titles or content, or account details.',
|
|
149
|
+
disable: `Ask the agent to turn telemetry off (supportpages_set_telemetry), run ${CLI_NAME} telemetry off, or set SUPPORTPAGES_TELEMETRY=0 or DO_NOT_TRACK=1.` };
|
|
150
|
+
}
|
|
151
|
+
async setEnabled(enabled) {
|
|
152
|
+
await saveDevicePreferences(this.options.configDir, { telemetry: enabled, telemetry_notice_shown: true });
|
|
153
|
+
this.state = undefined;
|
|
154
|
+
if (!enabled)
|
|
155
|
+
this.queue = [];
|
|
156
|
+
const status = await this.status();
|
|
157
|
+
return { ...status, message: status.enabled === enabled
|
|
158
|
+
? `Anonymous usage counts and crash reports are now ${enabled ? 'on' : 'off'} for SupportPages Writer on this device.`
|
|
159
|
+
: `Saved, but ${status.reason} in the environment keeps telemetry ${status.enabled ? 'on' : 'off'}.` };
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/** One instance per process, configured by the MCP server or CLI entry point.
|
|
163
|
+
* Until then (and in library use or tests) every call is a no-op. */
|
|
164
|
+
let active;
|
|
165
|
+
export function configureTelemetry(options) { active = options ? new Telemetry(options) : undefined; return active; }
|
|
166
|
+
export function activeTelemetry() { return active; }
|
|
167
|
+
export function track(event, properties) { active?.track(event, properties); }
|
|
168
|
+
export function reportError(error, tool) { active?.error(error, tool); }
|
|
169
|
+
export function secondsSince(start, end = Date.now()) {
|
|
170
|
+
const began = start ? Date.parse(start) : NaN;
|
|
171
|
+
return Number.isFinite(began) && end >= began ? Math.round((end - began) / 1000) : undefined;
|
|
172
|
+
}
|
|
173
|
+
//# sourceMappingURL=telemetry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"telemetry.js","sourceRoot":"","sources":["../src/telemetry.ts"],"names":[],"mappings":"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"}
|