scribeclock 0.1.4 → 0.1.6
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 +1 -0
- package/dist/commands/ingest.js +41 -4
- package/dist/commands/ingestAgentPrompt.js +155 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -71,6 +71,7 @@ the app become tasks on the project; docs: https://scribeclock.com/help/bug-repo
|
|
|
71
71
|
|
|
72
72
|
```bash
|
|
73
73
|
scribeclock connect --env # links this repo to its project, writes .scribeclock.json + SCRIBECLOCK_INGEST_KEY to .env
|
|
74
|
+
scribeclock ingest prompt --platform ios | pbcopy # the prompt to paste into your AI agent: key, event shape, stack-specific steps
|
|
74
75
|
scribeclock ingest test # sends a test error straight to https://scribeclock.com/ingest → issue + task
|
|
75
76
|
scribeclock issues # what users hit: level, title, where, count, users, last seen, task
|
|
76
77
|
scribeclock issues show 3f2a9c1b # stack trace, breadcrumbs, trace logs, context
|
package/dist/commands/ingest.js
CHANGED
|
@@ -6,6 +6,7 @@ import { EXIT } from "../types.js";
|
|
|
6
6
|
import { fmtDate, relTime } from "../util/format.js";
|
|
7
7
|
import { PROJECT_FLAG, cmd, emit, guardedEmit, list, num, personName, projectArg, requirePositional, str, taskKey } from "./_shared.js";
|
|
8
8
|
import { parseDotenv } from "./ops.js";
|
|
9
|
+
import { AGENT_PROMPT_PLATFORMS, buildAgentPrompt } from "./ingestAgentPrompt.js";
|
|
9
10
|
export const INGEST_URL = "https://scribeclock.com/ingest";
|
|
10
11
|
export const INGEST_DOCS_URL = "https://scribeclock.com/help/bug-reports";
|
|
11
12
|
const KEY_ENV = "SCRIBECLOCK_INGEST_KEY";
|
|
@@ -757,6 +758,35 @@ export const ingestSnippetsCommand = cmd({
|
|
|
757
758
|
return EXIT.OK;
|
|
758
759
|
},
|
|
759
760
|
});
|
|
761
|
+
export const ingestPromptCommand = cmd({
|
|
762
|
+
path: "ingest prompt",
|
|
763
|
+
group: "Bug reports",
|
|
764
|
+
summary: "Print the prompt to paste into an AI agent so it connects this repository: key, event shape and the exact steps for the stack.",
|
|
765
|
+
description: "The same prompt the project's Bug reports page offers. It embeds the ingest key (from --key, SCRIBECLOCK_INGEST_KEY or .env), the project and repository from .scribeclock.json, and stack-specific steps: the reporter file to write, crash hooks, bug reports, breadcrumbs, trace logs, sessions, screens, clicks, custom events, then a verified test event. Pipe it to the clipboard (pbcopy / xclip) or straight into the agent.",
|
|
766
|
+
tool: ["list_ingest_keys"],
|
|
767
|
+
flags: [
|
|
768
|
+
{ name: "platform", alias: "p", type: "string", choices: AGENT_PROMPT_PLATFORMS.map((o) => o.id), description: "Stack to write the steps for (default: auto — the agent detects it)." },
|
|
769
|
+
{ name: "key", alias: "k", type: "string", description: "Ingest key to embed (default: from the environment / .env; omitted → the agent fetches one itself)." },
|
|
770
|
+
{ name: "no-key", type: "boolean", description: "Never embed a key (the agent runs connect_repo / scribeclock connect)." },
|
|
771
|
+
],
|
|
772
|
+
examples: ["scribeclock ingest prompt --platform ios | pbcopy", "scribeclock ingest prompt --no-key", "scribeclock connect --env && scribeclock ingest prompt"],
|
|
773
|
+
async run(ctx, inv) {
|
|
774
|
+
const key = inv.flags["no-key"] ? null : ingestKeyFor(ctx, str(inv.flags.key));
|
|
775
|
+
const text = buildAgentPrompt({
|
|
776
|
+
key,
|
|
777
|
+
repo: ctx.repo.config.repo ?? remoteUrl(ctx.cwd) ?? null,
|
|
778
|
+
platform: (str(inv.flags.platform) ?? "auto"),
|
|
779
|
+
endpoint: INGEST_URL,
|
|
780
|
+
docsUrl: INGEST_DOCS_URL,
|
|
781
|
+
});
|
|
782
|
+
if (ctx.flags.json) {
|
|
783
|
+
ctx.out.jsonOut({ ok: true, platform: str(inv.flags.platform) ?? "auto", has_key: Boolean(key), prompt: text });
|
|
784
|
+
return EXIT.OK;
|
|
785
|
+
}
|
|
786
|
+
ctx.out.log(text);
|
|
787
|
+
return EXIT.OK;
|
|
788
|
+
},
|
|
789
|
+
});
|
|
760
790
|
// ───────────────────────────── health ─────────────────────────────
|
|
761
791
|
export const healthCommand = cmd({
|
|
762
792
|
path: "health",
|
|
@@ -919,7 +949,7 @@ export const analyticsCommand = cmd({
|
|
|
919
949
|
REPO_FLAG,
|
|
920
950
|
{ name: "days", alias: "d", type: "number", description: "Window in days (default 30, max 180)." },
|
|
921
951
|
{ name: "environment", alias: "e", type: "string", description: "Only this environment." },
|
|
922
|
-
{ name: "report", type: "string", choices: ["overview", "daily", "retention", "screens", "clicks", "events", "breakdowns", "releases", "issues", "hourly"], description: "One section only." },
|
|
952
|
+
{ name: "report", type: "string", choices: ["overview", "daily", "retention", "screens", "clicks", "events", "breakdowns", "releases", "issues", "hourly", "weekday"], description: "One section only." },
|
|
923
953
|
{ name: "csv", type: "boolean", description: "Print the daily series as CSV." },
|
|
924
954
|
],
|
|
925
955
|
examples: ["scribeclock analytics", "scribeclock analytics --days 7 --report releases", "scribeclock analytics --days 90 --csv > usage.csv", "scribeclock analytics --json | jq .totals"],
|
|
@@ -945,11 +975,11 @@ export const analyticsCommand = cmd({
|
|
|
945
975
|
out.log("");
|
|
946
976
|
out.log(out.bold("Overview") + out.dim(" (vs previous window)"));
|
|
947
977
|
out.kv([
|
|
948
|
-
["active users", `${String(t.users ?? 0)} (${delta(t.users, prev.users)}) · ${String(t.new_users ?? 0)} new · stickiness ${pct(t.stickiness_pct)} · MAU ${String(t.mau ?? 0)}`],
|
|
978
|
+
["active users", `${String(t.users ?? 0)} (${delta(t.users, prev.users)}) · ${String(t.new_users ?? 0)} new · ${String(t.returning_users ?? 0)} returning · stickiness ${pct(t.stickiness_pct)} · MAU ${String(t.mau ?? 0)}`],
|
|
949
979
|
["sessions", `${String(t.sessions ?? 0)} (${delta(t.sessions, prev.sessions)}) · ${String(t.avg_session_minutes ?? 0)} min avg · ${String(t.avg_screens_per_session ?? 0)} screens/session`],
|
|
950
980
|
["crash-free", `${pct(t.crash_free_sessions_pct)} sessions · ${pct(t.crash_free_users_pct)} users`],
|
|
951
981
|
["page views / clicks", `${String(t.pageviews ?? 0)} / ${String(t.clicks ?? 0)} · ${String(t.events ?? 0)} events (${delta(t.events, prev.events)})`],
|
|
952
|
-
["errors / crashes", `${String(t.errors ?? 0)} (${delta(t.errors, prev.errors)}) / ${String(t.crashes ?? 0)} (${delta(t.crashes, prev.crashes)}) · ${String(t.users_affected ?? 0)} users affected`],
|
|
982
|
+
["errors / crashes", `${String(t.errors ?? 0)} (${delta(t.errors, prev.errors)}) / ${String(t.crashes ?? 0)} (${delta(t.crashes, prev.crashes)}) · ${String(t.users_affected ?? 0)} users affected · ${String(t.errors_per_1k_sessions ?? "–")} per 1K sessions`],
|
|
953
983
|
["bug reports", `${String(t.bug_reports ?? 0)} (${delta(t.bug_reports, prev.bug_reports)})`],
|
|
954
984
|
["issues", `${String(t.issues_opened ?? 0)} opened · ${String(t.issues_resolved ?? 0)} resolved · ${String(t.issues_regressed ?? 0)} regressed · ${String(t.open_issues ?? 0)} open · MTTR ${String(t.mttr_hours ?? 0)} h`],
|
|
955
985
|
["emails", `${String(t.emails_immediate ?? 0)} immediate · ${String(t.emails_digest ?? 0)} digests`],
|
|
@@ -1022,9 +1052,16 @@ export const analyticsCommand = cmd({
|
|
|
1022
1052
|
out.log(` ${hourly.map((h) => String(h.hour).padStart(2, "0")).join(" ")}`);
|
|
1023
1053
|
out.log(` ${hourly.map((h) => { const n = Number(h.events); const lvl = n === 0 ? " " : n < max / 4 ? "▁" : n < max / 2 ? "▃" : n < (3 * max) / 4 ? "▅" : "█"; return ` ${lvl}`; }).join(" ")}`);
|
|
1024
1054
|
}
|
|
1055
|
+
const weekday = (data.weekday ?? []);
|
|
1056
|
+
if (show("weekday") && weekday.some((w) => Number(w.sessions) || Number(w.events) || Number(w.errors))) {
|
|
1057
|
+
out.log("");
|
|
1058
|
+
out.log(out.bold("Day of week (UTC)"));
|
|
1059
|
+
const names = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat", "Sun"];
|
|
1060
|
+
out.table(["day", "sessions", "events", "errors"], weekday.map((w) => [names[Number(w.weekday)] ?? String(w.weekday), w.sessions, w.events, w.errors]), { indent: 2 });
|
|
1061
|
+
}
|
|
1025
1062
|
if (data.hint && !Number(t.events) && !Number(t.sessions))
|
|
1026
1063
|
out.info(out.dim(String(data.hint)));
|
|
1027
1064
|
});
|
|
1028
1065
|
},
|
|
1029
1066
|
});
|
|
1030
|
-
export const INGEST_COMMANDS = [connectCommand, issuesList, issuesShow, issuesResolve, issuesIgnore, issuesReopen, issuesTask, bugCommand, ingestKeys, ingestRevoke, ingestTest, ingestSnippetsCommand, ingestForward, ingestSettings, issuesUrgency, analyticsCommand, healthCommand];
|
|
1067
|
+
export const INGEST_COMMANDS = [connectCommand, issuesList, issuesShow, issuesResolve, issuesIgnore, issuesReopen, issuesTask, bugCommand, ingestKeys, ingestRevoke, ingestTest, ingestSnippetsCommand, ingestPromptCommand, ingestForward, ingestSettings, issuesUrgency, analyticsCommand, healthCommand];
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The prompt a developer hands to an AI agent (Claude Code, Cursor, Codex, …) to connect a repository to
|
|
3
|
+
* Scribe Clock: get an ingest key, store it, add a reporter for the app's stack, verify, commit.
|
|
4
|
+
*
|
|
5
|
+
* One source of truth, copied verbatim to apps/web/src/lib/ingestAgentPrompt.ts and
|
|
6
|
+
* scribeclock-cli/src/commands/ingestAgentPrompt.ts (no imports, so the same file runs in Deno, Vite and
|
|
7
|
+
* Node). It is shown on the Bug reports page (with the project's key), in the help guide (without a key),
|
|
8
|
+
* printed by `scribeclock ingest prompt`, and returned by the connect_repo MCP tool as `agent_prompt`.
|
|
9
|
+
*/
|
|
10
|
+
export const AGENT_PROMPT_PLATFORMS = [
|
|
11
|
+
{ id: "auto", label: "Detect from the code" },
|
|
12
|
+
{ id: "web", label: "Web (browser)" },
|
|
13
|
+
{ id: "ios", label: "iOS / macOS (Swift)" },
|
|
14
|
+
{ id: "android", label: "Android (Kotlin / Java)" },
|
|
15
|
+
{ id: "react-native", label: "React Native / Expo" },
|
|
16
|
+
{ id: "flutter", label: "Flutter" },
|
|
17
|
+
{ id: "node", label: "Node / backend (JS, TS)" },
|
|
18
|
+
{ id: "python", label: "Python" },
|
|
19
|
+
{ id: "other", label: "Other (any language)" },
|
|
20
|
+
];
|
|
21
|
+
const EVENT_SHAPE = `{
|
|
22
|
+
"type": "error | crash | bug_report | message | event",
|
|
23
|
+
"level": "fatal | error | warning | info",
|
|
24
|
+
"message": "Error message, bug report title, or analytics event name (for type event: $pageview, $click, or your own like checkout_started)",
|
|
25
|
+
"exception": { "type": "TypeError", "value": "x is undefined", "stacktrace": "text stack trace", "frames": [{ "file": "", "function": "", "line": 0, "col": 0, "in_app": true }] },
|
|
26
|
+
"description": "Bug report body (Markdown)",
|
|
27
|
+
"environment": "production | staging | development",
|
|
28
|
+
"release": "1.4.2 (app version + build)",
|
|
29
|
+
"platform": "web | ios | android | react-native | flutter | node | python | …",
|
|
30
|
+
"url": "https://…", "route": "/screen/or/route", "screen": "ScreenName", "element": "button:Save",
|
|
31
|
+
"session_id": "uuid per app session (new after 30 min idle)",
|
|
32
|
+
"user": { "id": "…", "email": "…", "name": "…" },
|
|
33
|
+
"tags": { "feature": "checkout" },
|
|
34
|
+
"context": { "device": { "model": "" }, "os": { "name": "", "version": "" }, "app": { "version": "", "build": "" }, "locale": "" },
|
|
35
|
+
"breadcrumbs": [{ "timestamp": "ISO-8601", "category": "ui.click | navigation | http | log", "message": "", "data": {} }],
|
|
36
|
+
"logs": [{ "timestamp": "ISO-8601", "level": "info", "message": "trace log line" }],
|
|
37
|
+
"attachments": [{ "url": "https://…/screenshot.png", "name": "screenshot.png", "type": "image/png" }],
|
|
38
|
+
"reporter": { "name": "", "email": "" },
|
|
39
|
+
"properties": { "any": "analytics event properties" },
|
|
40
|
+
"fingerprint": "optional grouping key", "event_id": "optional idempotency id", "timestamp": "ISO-8601"
|
|
41
|
+
}`;
|
|
42
|
+
function platformBlock(p, key, environment) {
|
|
43
|
+
switch (p) {
|
|
44
|
+
case "web":
|
|
45
|
+
return `WEB (browser): use the hosted SDK, it already does errors, sessions, page views and click autocapture.
|
|
46
|
+
- In the HTML document, before the app bundle:
|
|
47
|
+
<script src="https://scribeclock.com/sdk/scribeclock.js" data-key="${key}" data-environment="${environment ?? "production"}" data-release="<app version>" async></script>
|
|
48
|
+
(or import it and call ScribeClock.init({ key, environment, release }) in the app entry point; the key may be inlined — it is publishable and write-only).
|
|
49
|
+
- After sign-in: ScribeClock.setUser({ id, email, name }). On sign-out: ScribeClock.setUser(null).
|
|
50
|
+
- Call ScribeClock.track("event_name", { ...properties }) for the actions that matter (signup, checkout_started, export, …).
|
|
51
|
+
- Wire the app's feedback form to ScribeClock.reportBug({ title, description, email, screenshotUrl }).
|
|
52
|
+
- Mark private form areas with data-sc-private so autocapture never records them.
|
|
53
|
+
- SPA: the SDK sends $pageview on history changes; for a custom router call ScribeClock.pageview(screenName).`;
|
|
54
|
+
case "ios":
|
|
55
|
+
return `iOS / macOS (Swift): write ONE dependency-free file, Sources/ScribeClock/ScribeClock.swift (or in the app target), using URLSession:
|
|
56
|
+
- ScribeClock.configure(key:environment:release:) — read the key from Info.plist (SCRIBECLOCK_INGEST_KEY filled from an .xcconfig / CI secret) or from the environment in Debug. release = CFBundleShortVersionString + " (" + CFBundleVersion + ")".
|
|
57
|
+
- Crashes: install NSSetUncaughtExceptionHandler and signal handlers (SIGABRT, SIGSEGV, SIGBUS, SIGILL, SIGFPE, SIGTRAP) that write a small JSON crash file (type "crash", exception type/value, Thread.callStackSymbols, breadcrumbs, last logs, context) to Application Support using only async-signal-safe work where possible; on the next launch read it, POST it, delete it.
|
|
58
|
+
- Errors: capture(_ error: Error, level:), captureMessage(_:level:). Never throw from the reporter.
|
|
59
|
+
- Breadcrumbs (ring buffer of 100): screen changes, taps, HTTP requests (method, path, status), app lifecycle. Logs (ring buffer of 300 lines) via log(_:level:).
|
|
60
|
+
- Analytics: a session id (UUID) created at launch and renewed after 30 minutes in the background; screen(_ name: String) sends {"type":"event","name":"$pageview","screen":name}; tap(_ element: String, screen:) sends {"name":"$click","element":element}; track(_ name:, properties:) for custom events; setUser(id:email:name:). Provide a SwiftUI View modifier .scribeClockScreen("Name") (onAppear) and, for UIKit, an optional viewDidAppear swizzle.
|
|
61
|
+
- Bug reports: reportBug(title:description:email:screenshot:) — upload the screenshot wherever the app already stores files and pass its URL in attachments, or omit it.
|
|
62
|
+
- Transport: queue events in memory and on disk, batch up to 50, flush every 10 s and when the app enters the background (use a background URLSession configuration so the request completes), retry with exponential backoff, drop after 3 attempts. Headers: Authorization: Bearer <key>, Content-Type: application/json. Every event carries environment, release, platform "ios" (or "macos"), context { device.model, os.name/version, app.version/build, locale }, session_id and user.
|
|
63
|
+
- Add XCTest coverage for batching, the 30-minute session rule, and the crash file round trip.`;
|
|
64
|
+
case "android":
|
|
65
|
+
return `ANDROID (Kotlin): write a small ScribeClock object (OkHttp or HttpURLConnection, no other dependencies):
|
|
66
|
+
- configure(context, key, environment, release = BuildConfig.VERSION_NAME + " (" + VERSION_CODE + ")"); the key comes from BuildConfig / local.properties / CI secret, never from source control.
|
|
67
|
+
- Crashes: Thread.setDefaultUncaughtExceptionHandler → write a crash JSON file (type "crash", exception class, message, stack trace, breadcrumbs, logs, context) then chain to the previous handler; on next start POST and delete it.
|
|
68
|
+
- captureException(Throwable, level), captureMessage, breadcrumb(category, message, data) ring buffer of 100, log(level, message) ring buffer of 300.
|
|
69
|
+
- Analytics: session id renewed after 30 min in background (ProcessLifecycleOwner), screen(name) → $pageview (hook Activity/Fragment lifecycle or Navigation's addOnDestinationChangedListener), click(element, screen) → $click, track(name, properties), setUser(id, email, name).
|
|
70
|
+
- reportBug(title, description, email, screenshotUrl) for the in-app feedback screen.
|
|
71
|
+
- Transport: in-memory + file queue, batches of 50, flush every 10 s and onStop/WorkManager for background delivery, exponential backoff, drop after 3 attempts. Headers: Authorization: Bearer <key>. Every event carries environment, release, platform "android", context { device.model, os.version, app.version/build, locale }, session_id, user.`;
|
|
72
|
+
case "react-native":
|
|
73
|
+
return `REACT NATIVE / EXPO (TypeScript): write src/scribeclock.ts (fetch only):
|
|
74
|
+
- init({ key, environment, release: Application.nativeApplicationVersion + build }); key from app config / EAS secret (EXPO_PUBLIC_SCRIBECLOCK_INGEST_KEY) — publishable, write-only.
|
|
75
|
+
- Crashes/errors: ErrorUtils.setGlobalHandler((error, isFatal) => send({ type: isFatal ? "crash" : "error", exception: { type: error.name, value: error.message, stacktrace: error.stack } })) and chain the previous handler; also hook unhandled promise rejections.
|
|
76
|
+
- Breadcrumbs (100) from navigation state changes, presses and fetch; logs (300) from console.* in development builds only.
|
|
77
|
+
- Analytics: session id renewed after 30 min via AppState; screen(name) → $pageview on React Navigation's onStateChange; press(element, screen) → $click; track(name, props); setUser(user).
|
|
78
|
+
- reportBug({ title, description, email, screenshotUrl }).
|
|
79
|
+
- Transport: queue, batches of 50, flush every 10 s and on AppState "background", retry with backoff, drop after 3 attempts. platform = Platform.OS; context from expo-device / react-native-device-info if present.`;
|
|
80
|
+
case "flutter":
|
|
81
|
+
return `FLUTTER (Dart): write lib/scribeclock.dart (package:http only):
|
|
82
|
+
- ScribeClock.configure(key:, environment:, release:) from --dart-define=SCRIBECLOCK_INGEST_KEY=…; release from package_info_plus.
|
|
83
|
+
- Crashes: FlutterError.onError and PlatformDispatcher.instance.onError → type "crash"/"error" with exception type, toString(), stack trace; chain the previous handlers.
|
|
84
|
+
- captureException, captureMessage, breadcrumb (100), log (300), screen(name) via a NavigatorObserver → $pageview, tap(element, screen) → $click, track(name, properties), setUser, reportBug(title, description, email, screenshotUrl).
|
|
85
|
+
- Session id renewed after 30 min in background (WidgetsBindingObserver lifecycle); batches of 50, flush every 10 s and on paused, backoff, drop after 3 attempts; context from device_info_plus.`;
|
|
86
|
+
case "node":
|
|
87
|
+
return `NODE / BACKEND (JavaScript or TypeScript): write src/scribeclock.ts (global fetch only):
|
|
88
|
+
- init({ key: process.env.SCRIBECLOCK_INGEST_KEY, environment: process.env.NODE_ENV, release: package.json version or the git SHA, platform: "node" }).
|
|
89
|
+
- process.on("uncaughtException") and process.on("unhandledRejection") → send type "crash" (then exit as before); an Express/Fastify/Koa error middleware → type "error" with { url, route, user, request id }; a logger transport (pino/winston) that keeps the last 300 lines as logs and attaches them to the next error.
|
|
90
|
+
- captureException(err, { route, user, tags }), captureMessage, track(name, properties, { user }) for product analytics from the backend (signup_completed, order_paid …).
|
|
91
|
+
- Transport: batch up to 50, flush every 5 s and on SIGTERM/beforeExit (await the last flush), retry with backoff, drop after 3 attempts, never throw.`;
|
|
92
|
+
case "python":
|
|
93
|
+
return `PYTHON: write scribeclock.py (requests or urllib only):
|
|
94
|
+
- init(key=os.environ["SCRIBECLOCK_INGEST_KEY"], environment=os.environ.get("APP_ENV"), release=<version or git SHA>, platform="python").
|
|
95
|
+
- sys.excepthook (and threading.excepthook) → type "crash" with exception type, str(exc), traceback.format_exc(); a logging.Handler that keeps the last 300 records and attaches them; framework hooks: Django middleware / Flask errorhandler / FastAPI exception handler → type "error" with url, route, user.
|
|
96
|
+
- capture_exception(exc, route=None, user=None, tags=None), capture_message, track(name, properties, user=None).
|
|
97
|
+
- Transport: a background thread with a queue, batches of 50, flush every 5 s and at exit (atexit), retry with backoff, drop after 3 attempts, never raise into the app.`;
|
|
98
|
+
default:
|
|
99
|
+
return `ANY LANGUAGE: write a small reporter module (HTTP client only) with: configure(key, environment, release); capture_exception / capture_message; a global unhandled-exception / crash hook that sends type "crash"; breadcrumbs (last 100) and logs (last 300) attached to each report; analytics: session id (new after 30 min idle), screen → {"type":"event","name":"$pageview","screen":…}, click → {"name":"$click","element":…}, track(name, properties), set_user; report_bug(title, description, email, screenshot_url); batched transport (≤ 50 events, flush every 10 s and on exit / background, backoff, drop after 3 attempts, never throw into the app).`;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
/** Markdown prompt for an agent; `key` present → it is embedded (the key is publishable and write-only). */
|
|
103
|
+
export function buildAgentPrompt(input = {}) {
|
|
104
|
+
const endpoint = input.endpoint ?? "https://scribeclock.com/ingest";
|
|
105
|
+
const docs = input.docsUrl ?? "https://scribeclock.com/help/bug-reports";
|
|
106
|
+
const platform = (AGENT_PROMPT_PLATFORMS.some((p) => p.id === input.platform) ? input.platform : "auto");
|
|
107
|
+
const key = input.key?.trim() || "";
|
|
108
|
+
const keyRef = key || "$SCRIBECLOCK_INGEST_KEY";
|
|
109
|
+
const env = input.environment?.trim() || null;
|
|
110
|
+
const project = input.projectName?.trim() ? `Project: ${input.projectName.trim()}${input.repo ? ` (${input.repo})` : ""}` : input.repo ? `Repository: ${input.repo}` : null;
|
|
111
|
+
const getKey = key
|
|
112
|
+
? `1. The ingest key is above. Store it as SCRIBECLOCK_INGEST_KEY in the app's configuration (environment variable, secrets manager, .xcconfig / BuildConfig / --dart-define / app config). It is publishable and write-only, but keep it out of git anyway (add any file that holds it to .gitignore).`
|
|
113
|
+
: `1. Get the project's ingest key (sc_ing_…), in this order:
|
|
114
|
+
a. If you have the Scribe Clock MCP server: call connect_repo with repo = "owner/name" from \`git remote get-url origin\` (add create_project=true if no project lists this repository). The reply contains SCRIBECLOCK_INGEST_KEY and an agent_prompt with stack-specific steps.
|
|
115
|
+
b. Else if Node is available: run \`npx scribeclock connect --env\` inside the repository (sign in when it asks); it writes .scribeclock.json and puts SCRIBECLOCK_INGEST_KEY into .env.
|
|
116
|
+
c. Else ask the developer to copy the key from the project's Bug reports page in Scribe Clock (sidebar → App analytics → Bug reports).
|
|
117
|
+
Store it as SCRIBECLOCK_INGEST_KEY in the app's configuration (environment variable, secrets manager, .xcconfig / BuildConfig / --dart-define / app config). It is publishable and write-only, but keep it out of git anyway.`;
|
|
118
|
+
const stack = platform === "auto"
|
|
119
|
+
? `3. Detect the stack from the repository (package.json, Package.swift / *.xcodeproj, build.gradle, pubspec.yaml, pyproject / requirements) and follow the matching block below. A monorepo gets one reporter per app, all with the same key.
|
|
120
|
+
|
|
121
|
+
${["web", "ios", "android", "react-native", "flutter", "node", "python"].map((p) => ` ${platformBlock(p, keyRef, env).replace(/\n/g, "\n ")}`).join("\n\n")}`
|
|
122
|
+
: `3. ${platformBlock(platform, keyRef, env)}`;
|
|
123
|
+
return [
|
|
124
|
+
`Connect this repository to Scribe Clock (${docs.replace(/\/help.*$/, "")}) so that errors, crashes, user bug reports, trace logs and product analytics from the app flow into Scribe Clock. There, every new problem becomes a task for the team (bundled with its duplicates, carrying an urgency), and the project's Analytics page shows active users, sessions, retention, screens, clicks, releases and crash-free rates.`,
|
|
125
|
+
``,
|
|
126
|
+
...(project ? [project] : []),
|
|
127
|
+
`Ingest endpoint: ${endpoint} (POST JSON, header \`Authorization: Bearer <key>\`; one event, or {"events":[…]} with up to 50)`,
|
|
128
|
+
...(key ? [`Ingest key: ${key}`] : []),
|
|
129
|
+
`Docs (read first): ${docs}`,
|
|
130
|
+
...(env ? [`Default environment: ${env}`] : []),
|
|
131
|
+
``,
|
|
132
|
+
`Do this, in order, and do not stop until step 5 passes:`,
|
|
133
|
+
``,
|
|
134
|
+
getKey,
|
|
135
|
+
``,
|
|
136
|
+
`2. Install nothing heavy: the reporter is one small file using the platform's own HTTP client. Do not add Sentry, Firebase or other SDKs for this.`,
|
|
137
|
+
``,
|
|
138
|
+
stack,
|
|
139
|
+
``,
|
|
140
|
+
`4. Whatever the stack, the reporter must:
|
|
141
|
+
- send uncaught exceptions / crashes (type "crash" when the app dies, "error" otherwise) with exception type, message and stack trace;
|
|
142
|
+
- offer captureException / captureMessage / reportBug(title, description, email, screenshot) for the app to call;
|
|
143
|
+
- attach the last 100 breadcrumbs (navigation, taps/clicks, HTTP calls, lifecycle) and the last 300 log lines to every report;
|
|
144
|
+
- send analytics: a session_id that renews after 30 minutes idle, setUser({ id, email, name }), a $pageview event with "screen" on every screen change, a $click event with "element" on taps of buttons/links, and track(name, properties) for the actions that matter;
|
|
145
|
+
- put environment, release (version + build), platform and context { device, os, app, locale } on every event;
|
|
146
|
+
- batch (≤ 50), flush periodically and on background/exit, retry with backoff, drop after 3 attempts, never block or crash the app, and strip passwords, tokens and card numbers from anything it sends.`,
|
|
147
|
+
``,
|
|
148
|
+
`5. Verify end to end: send one event {"type":"message","level":"info","message":"scribeclock connect test"} with the key and confirm the response is HTTP 200 with "ok": true; then send one type "error" with a fake stack trace and confirm it shows up — with the MCP server call project_health (or list_issues), with the CLI run \`scribeclock health\`, otherwise ask the developer to check the project's Bug reports page. Resolve that test issue afterwards (update_issue status=resolved / \`scribeclock issues resolve <id>\`).`,
|
|
149
|
+
``,
|
|
150
|
+
`6. Commit the reporter and a short README section ("Crash reports and analytics go to Scribe Clock; set SCRIBECLOCK_INGEST_KEY"), without the key. Report back with: where the key is read from, what is captured automatically, and the helpers the app can call.`,
|
|
151
|
+
``,
|
|
152
|
+
`Event shape (every field optional except type and message / exception):`,
|
|
153
|
+
EVENT_SHAPE,
|
|
154
|
+
].join("\n");
|
|
155
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "scribeclock",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.6",
|
|
4
4
|
"description": "ScribeClock from the terminal: tasks, time clock, projects, PRDs, meetings, files, vault and Cloudflare — a thin client over the ScribeClock MCP server.",
|
|
5
5
|
"license": "UNLICENSED",
|
|
6
6
|
"author": "Haydar Al-Karar LLC, dba Karar Tech",
|