@remotedraw/cli 0.2.1 → 0.2.2
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 +20 -0
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +182 -41
- package/dist/cloud.d.ts +1 -1
- package/dist/cloud.d.ts.map +1 -1
- package/dist/generated/agent-skill.d.ts +1 -1
- package/dist/generated/agent-skill.d.ts.map +1 -1
- package/dist/generated/agent-skill.js +1 -1
- package/dist/locales/en.d.ts +19 -4
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/en.js +19 -4
- package/dist/locales/index.d.ts +20 -5
- package/dist/locales/index.d.ts.map +1 -1
- package/dist/locales/nl.d.ts.map +1 -1
- package/dist/locales/nl.js +19 -4
- package/dist/scan.d.ts +58 -0
- package/dist/scan.d.ts.map +1 -0
- package/dist/scan.js +544 -0
- package/dist/telemetry.d.ts +1 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -235,6 +235,24 @@ remotedraw agent --path .agents/skills/remotedraw --force
|
|
|
235
235
|
remotedraw agent --path .claude/skills/remotedraw --force
|
|
236
236
|
```
|
|
237
237
|
|
|
238
|
+
Before scaffolding into an existing app, read it first. `scan` names the web,
|
|
239
|
+
server, and iOS projects in a tree, where an `rd_sk_` key may live, any
|
|
240
|
+
RemoteDraw wiring already present, the integration options that fit, and the
|
|
241
|
+
product questions to settle with the user before building:
|
|
242
|
+
|
|
243
|
+
```sh
|
|
244
|
+
remotedraw scan --format json
|
|
245
|
+
remotedraw scan --path apps/customer-web
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
When the integration already has its files and only needs credentials,
|
|
249
|
+
provision without scaffolding (and without `--force`):
|
|
250
|
+
|
|
251
|
+
```sh
|
|
252
|
+
remotedraw project create --name "Field Notes" --env server # writes server/.env.local
|
|
253
|
+
remotedraw project create --name "Field Notes" --format json # prints the key once
|
|
254
|
+
```
|
|
255
|
+
|
|
238
256
|
## Command reference
|
|
239
257
|
|
|
240
258
|
```text
|
|
@@ -251,6 +269,8 @@ doctor Check project config, packages, and environment variables.
|
|
|
251
269
|
create-input Create or print a test input request payload.
|
|
252
270
|
examples List or install example projects.
|
|
253
271
|
agent Print or install the RemoteDraw agent skill.
|
|
272
|
+
scan Read a codebase and propose where RemoteDraw fits.
|
|
273
|
+
project Create a dashboard project and API key without scaffolding.
|
|
254
274
|
```
|
|
255
275
|
|
|
256
276
|
Run `remotedraw <command> --help` for command-specific options.
|
package/dist/cli.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
|
package/dist/cli.js
CHANGED
|
@@ -5,9 +5,10 @@ import path from "node:path";
|
|
|
5
5
|
import process from "node:process";
|
|
6
6
|
import { fileURLToPath } from "node:url";
|
|
7
7
|
import { emitKeypressEvents } from "node:readline";
|
|
8
|
+
import { renderScanText, scanProject } from "./scan.js";
|
|
8
9
|
import { createInterface } from "node:readline/promises";
|
|
9
10
|
import { createWizardTerminal, runSetupWizard, } from "./setup-wizard.js";
|
|
10
|
-
import { assertEnvCanAcceptProvisioning, currentCliAccount, DEFAULT_REMOTEDRAW_API_BASE_URL, globalConfigPath, loginToRemoteDraw, logoutFromRemoteDraw, provisionRemoteDrawProject, readStoredLanguage, remoteDrawApiBaseUrl, writeStoredLanguage, writeProvisionedProjectSetup, } from "./cloud.js";
|
|
11
|
+
import { assertEnvCanAcceptProvisioning, currentCliAccount, DEFAULT_REMOTEDRAW_API_BASE_URL, globalConfigPath, loginToRemoteDraw, logoutFromRemoteDraw, provisionRemoteDrawProject, updateEnvContents, readStoredLanguage, remoteDrawApiBaseUrl, writeStoredLanguage, writeProvisionedProjectSetup, } from "./cloud.js";
|
|
11
12
|
import { LANGUAGE_ENV_VARS, SOURCE_LOCALE, extractLanguageFlag, languageChoices, languagePromptMessage, localeEndonym, normalizeLocale, resolveLocale, setActiveLocale, supportedLocaleList, SUPPORTED_LOCALES, systemLocaleHint, t, } from "./i18n.js";
|
|
12
13
|
import { CliError, cliError } from "./errors.js";
|
|
13
14
|
import { AGENT_SKILL_MARKDOWN } from "./generated/agent-skill.js";
|
|
@@ -355,6 +356,10 @@ export async function runCli(args, runtime = createNodeRuntime()) {
|
|
|
355
356
|
return await examplesCommand(commandArgs, runtime);
|
|
356
357
|
case "agent":
|
|
357
358
|
return await agentCommand(commandArgs, runtime);
|
|
359
|
+
case "scan":
|
|
360
|
+
return await scanCommand(commandArgs, runtime);
|
|
361
|
+
case "project":
|
|
362
|
+
return await projectCommand(commandArgs, runtime);
|
|
358
363
|
default:
|
|
359
364
|
return requestedJsonOutput(args)
|
|
360
365
|
? jsonFailure(command, new Error(t("error.unknownCommand", { command })))
|
|
@@ -603,6 +608,8 @@ function mainHelpText() {
|
|
|
603
608
|
command("create-input", t("help.command.createInput")),
|
|
604
609
|
command("examples", t("help.command.examples")),
|
|
605
610
|
command("agent", t("help.command.agent")),
|
|
611
|
+
command("scan", t("help.command.scan")),
|
|
612
|
+
command("project", t("help.command.project")),
|
|
606
613
|
"",
|
|
607
614
|
t("help.startHere"),
|
|
608
615
|
" remotedraw guide",
|
|
@@ -693,15 +700,33 @@ async function logoutCommand(args, runtime) {
|
|
|
693
700
|
}
|
|
694
701
|
async function whoamiCommand(args, runtime) {
|
|
695
702
|
const parsed = parseArgs(args, {
|
|
696
|
-
values: ["api-base-url"],
|
|
703
|
+
values: ["api-base-url", "format"],
|
|
697
704
|
booleans: ["help"],
|
|
698
705
|
});
|
|
699
706
|
if (parsed.booleans.has("help"))
|
|
700
707
|
return ok(whoamiHelpText());
|
|
708
|
+
const format = outputFormat(parsed);
|
|
701
709
|
const apiBaseUrl = remoteDrawApiBaseUrl(runtime.env, readString(parsed, "api-base-url"));
|
|
702
710
|
const account = await currentCliAccount(runtime, apiBaseUrl);
|
|
703
711
|
if (!account) {
|
|
704
|
-
|
|
712
|
+
const message = t("whoami.notLoggedIn", { url: apiBaseUrl });
|
|
713
|
+
return format === "json"
|
|
714
|
+
? jsonFailure("whoami", cliError("AUTH_REQUIRED", "whoami.notLoggedIn", { url: apiBaseUrl }))
|
|
715
|
+
: fail(message);
|
|
716
|
+
}
|
|
717
|
+
if (format === "json") {
|
|
718
|
+
return jsonResult({
|
|
719
|
+
ok: true,
|
|
720
|
+
command: "whoami",
|
|
721
|
+
apiBaseUrl,
|
|
722
|
+
account: {
|
|
723
|
+
userId: account.userId,
|
|
724
|
+
tenantId: account.tenantId,
|
|
725
|
+
tenantSlug: account.tenantSlug,
|
|
726
|
+
...(account.email ? { email: account.email } : {}),
|
|
727
|
+
...(account.name ? { name: account.name } : {}),
|
|
728
|
+
},
|
|
729
|
+
});
|
|
705
730
|
}
|
|
706
731
|
return ok([accountSummary(account), t("whoami.api", { url: apiBaseUrl })].join("\n"));
|
|
707
732
|
}
|
|
@@ -1253,6 +1278,125 @@ async function examplesCommand(args, runtime) {
|
|
|
1253
1278
|
});
|
|
1254
1279
|
return ok(scaffoldSummary(t("scaffold.verb.installedExample"), plan, outputDir, written, dryRun));
|
|
1255
1280
|
}
|
|
1281
|
+
async function projectCommand(args, runtime) {
|
|
1282
|
+
const parsed = parseArgs(args, {
|
|
1283
|
+
values: ["name", "key-name", "api-base-url", "env", "format"],
|
|
1284
|
+
booleans: ["help", "force"],
|
|
1285
|
+
});
|
|
1286
|
+
const subcommand = parsed.positionals[0];
|
|
1287
|
+
if (parsed.booleans.has("help") || subcommand == null) {
|
|
1288
|
+
return ok(projectHelpText());
|
|
1289
|
+
}
|
|
1290
|
+
if (subcommand !== "create") {
|
|
1291
|
+
throw cliError("INVALID_ARGUMENT", "project.error.unknownSubcommand", {
|
|
1292
|
+
subcommand,
|
|
1293
|
+
});
|
|
1294
|
+
}
|
|
1295
|
+
const format = outputFormat(parsed);
|
|
1296
|
+
const name = readString(parsed, "name");
|
|
1297
|
+
if (name == null) {
|
|
1298
|
+
throw cliError("MISSING_ARGUMENT", "project.error.nameRequired");
|
|
1299
|
+
}
|
|
1300
|
+
const apiBaseUrl = readString(parsed, "api-base-url") ??
|
|
1301
|
+
runtime.env.REMOTEDRAW_API_BASE_URL ??
|
|
1302
|
+
DEFAULT_REMOTEDRAW_API_BASE_URL;
|
|
1303
|
+
const keyName = readString(parsed, "key-name");
|
|
1304
|
+
// Provisioning is separate from scaffolding on purpose: an integration that
|
|
1305
|
+
// already has its files must not have to --force-overwrite them to get a key.
|
|
1306
|
+
const provisioning = await provisionRemoteDrawProject(runtime, apiBaseUrl, {
|
|
1307
|
+
name,
|
|
1308
|
+
...(keyName ? { keyName } : {}),
|
|
1309
|
+
});
|
|
1310
|
+
const envDir = readString(parsed, "env");
|
|
1311
|
+
let envPath;
|
|
1312
|
+
if (envDir != null) {
|
|
1313
|
+
const outputDir = path.resolve(runtime.cwd, envDir);
|
|
1314
|
+
const force = parsed.booleans.has("force");
|
|
1315
|
+
await assertEnvCanAcceptProvisioning(runtime, outputDir, apiBaseUrl, force);
|
|
1316
|
+
await runtime.mkdir(outputDir);
|
|
1317
|
+
envPath = path.join(outputDir, ".env.local");
|
|
1318
|
+
const existing = (await runtime.exists(envPath))
|
|
1319
|
+
? await runtime.readFile(envPath)
|
|
1320
|
+
: "# Generated by RemoteDraw CLI. Server-side secrets only.\n";
|
|
1321
|
+
const next = updateEnvContents(existing, {
|
|
1322
|
+
REMOTEDRAW_API_BASE_URL: provisioning.apiBaseUrl,
|
|
1323
|
+
REMOTEDRAW_PROJECT_ID: provisioning.project.id,
|
|
1324
|
+
REMOTEDRAW_API_KEY: provisioning.apiKey.value,
|
|
1325
|
+
}, force);
|
|
1326
|
+
if (runtime.writePrivateFile) {
|
|
1327
|
+
await runtime.writePrivateFile(envPath, next);
|
|
1328
|
+
}
|
|
1329
|
+
else {
|
|
1330
|
+
await runtime.writeFile(envPath, next);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
if (format === "json") {
|
|
1334
|
+
return jsonResult({
|
|
1335
|
+
ok: true,
|
|
1336
|
+
command: "project",
|
|
1337
|
+
subcommand: "create",
|
|
1338
|
+
project: provisioning.project,
|
|
1339
|
+
apiKey: {
|
|
1340
|
+
id: provisioning.apiKey.id,
|
|
1341
|
+
name: provisioning.apiKey.name,
|
|
1342
|
+
// Shown exactly once, here. Written to .env.local only with --env.
|
|
1343
|
+
...(envPath ? {} : { value: provisioning.apiKey.value }),
|
|
1344
|
+
},
|
|
1345
|
+
apiBaseUrl: provisioning.apiBaseUrl,
|
|
1346
|
+
...(envPath ? { envPath } : {}),
|
|
1347
|
+
});
|
|
1348
|
+
}
|
|
1349
|
+
return ok([
|
|
1350
|
+
t("project.created", {
|
|
1351
|
+
name: provisioning.project.name,
|
|
1352
|
+
id: provisioning.project.id,
|
|
1353
|
+
}),
|
|
1354
|
+
` ${provisioning.project.dashboardUrl}`,
|
|
1355
|
+
"",
|
|
1356
|
+
envPath
|
|
1357
|
+
? t("project.keyWritten", { path: envPath })
|
|
1358
|
+
: t("project.keyOnce", { key: provisioning.apiKey.value }),
|
|
1359
|
+
"",
|
|
1360
|
+
t("project.keyRule"),
|
|
1361
|
+
].join("\n"));
|
|
1362
|
+
}
|
|
1363
|
+
function projectHelpText() {
|
|
1364
|
+
return [
|
|
1365
|
+
t("word.usage"),
|
|
1366
|
+
t("project.help.usage"),
|
|
1367
|
+
"",
|
|
1368
|
+
t("project.help.body"),
|
|
1369
|
+
t("project.help.env"),
|
|
1370
|
+
t("project.help.auth"),
|
|
1371
|
+
].join("\n");
|
|
1372
|
+
}
|
|
1373
|
+
async function scanCommand(args, runtime) {
|
|
1374
|
+
const parsed = parseArgs(args, {
|
|
1375
|
+
values: ["path", "format"],
|
|
1376
|
+
booleans: ["help"],
|
|
1377
|
+
});
|
|
1378
|
+
if (parsed.booleans.has("help"))
|
|
1379
|
+
return ok(scanHelpText());
|
|
1380
|
+
const format = outputFormat(parsed);
|
|
1381
|
+
const root = resolveOutputDir(runtime.cwd, parsed);
|
|
1382
|
+
if (!(await runtime.exists(root))) {
|
|
1383
|
+
throw cliError("INVALID_ARGUMENT", "scan.error.missingPath", { path: root });
|
|
1384
|
+
}
|
|
1385
|
+
const report = await scanProject(root, runtime);
|
|
1386
|
+
if (format === "json")
|
|
1387
|
+
return jsonResult(report);
|
|
1388
|
+
return ok(renderScanText(report));
|
|
1389
|
+
}
|
|
1390
|
+
function scanHelpText() {
|
|
1391
|
+
return [
|
|
1392
|
+
t("word.usage"),
|
|
1393
|
+
t("scan.help.usage"),
|
|
1394
|
+
"",
|
|
1395
|
+
t("scan.help.body"),
|
|
1396
|
+
t("scan.help.privacy"),
|
|
1397
|
+
t("scan.help.json"),
|
|
1398
|
+
].join("\n");
|
|
1399
|
+
}
|
|
1256
1400
|
async function agentCommand(args, runtime) {
|
|
1257
1401
|
const parsed = parseArgs(args, {
|
|
1258
1402
|
values: ["path"],
|
|
@@ -2399,7 +2543,7 @@ function initPackageJson(plan) {
|
|
|
2399
2543
|
}
|
|
2400
2544
|
function configJson(plan) {
|
|
2401
2545
|
return {
|
|
2402
|
-
$schema: "https://
|
|
2546
|
+
$schema: "https://remotedraw.com/schemas/remotedraw.config.json",
|
|
2403
2547
|
appName: plan.appName,
|
|
2404
2548
|
environment: "dev",
|
|
2405
2549
|
target: plan.target,
|
|
@@ -2467,6 +2611,11 @@ function integrationReadme(plan) {
|
|
|
2467
2611
|
}
|
|
2468
2612
|
function reactBackendSessionTs(plan) {
|
|
2469
2613
|
return [
|
|
2614
|
+
"// SERVER-SIDE ONLY. This file reads REMOTEDRAW_API_KEY (an rd_sk_ secret).",
|
|
2615
|
+
"// It must run where secrets live — an API route, a server action, a Convex",
|
|
2616
|
+
"// action, a serverless function — never in browser or app code. If this",
|
|
2617
|
+
"// landed under a Vite/Next client tree, move it to your server before use.",
|
|
2618
|
+
"",
|
|
2470
2619
|
'import { createHttpRemoteDrawApiClient } from "@remotedraw/react";',
|
|
2471
2620
|
'import type { CreateSessionRequest } from "@remotedraw/react";',
|
|
2472
2621
|
"",
|
|
@@ -2654,6 +2803,11 @@ function reactSenderTsx() {
|
|
|
2654
2803
|
}
|
|
2655
2804
|
function svelteBackendSessionTs(plan) {
|
|
2656
2805
|
return [
|
|
2806
|
+
"// SERVER-SIDE ONLY. This file reads REMOTEDRAW_API_KEY (an rd_sk_ secret).",
|
|
2807
|
+
"// It must run where secrets live — an API route, a server action, a Convex",
|
|
2808
|
+
"// action, a serverless function — never in browser or app code. If this",
|
|
2809
|
+
"// landed under a Vite/Next client tree, move it to your server before use.",
|
|
2810
|
+
"",
|
|
2657
2811
|
'import { createHttpRemoteDrawApiClient } from "@remotedraw/svelte";',
|
|
2658
2812
|
'import type { CreateSessionRequest } from "@remotedraw/svelte";',
|
|
2659
2813
|
"",
|
|
@@ -2730,6 +2884,11 @@ function rawHttpSessionTs(plan) {
|
|
|
2730
2884
|
// declares nothing and stays import-free.
|
|
2731
2885
|
const typed = plan.sdk === "js";
|
|
2732
2886
|
return [
|
|
2887
|
+
"// SERVER-SIDE ONLY. This file reads REMOTEDRAW_API_KEY (an rd_sk_ secret).",
|
|
2888
|
+
"// It must run where secrets live — an API route, a server action, a Convex",
|
|
2889
|
+
"// action, a serverless function — never in browser or app code. If this",
|
|
2890
|
+
"// landed under a Vite/Next client tree, move it to your server before use.",
|
|
2891
|
+
"",
|
|
2733
2892
|
...(typed
|
|
2734
2893
|
? [
|
|
2735
2894
|
"import type {",
|
|
@@ -3035,7 +3194,7 @@ export function createSessionPayload(options) {
|
|
|
3035
3194
|
externalId: options.externalId,
|
|
3036
3195
|
markupPreset: options.preset,
|
|
3037
3196
|
target: {
|
|
3038
|
-
kind:
|
|
3197
|
+
kind: PRESETS[options.preset].kind,
|
|
3039
3198
|
label: options.label,
|
|
3040
3199
|
metadata: {
|
|
3041
3200
|
descriptor,
|
|
@@ -3064,7 +3223,7 @@ function descriptorForPreset(preset, label) {
|
|
|
3064
3223
|
return {
|
|
3065
3224
|
version: 1,
|
|
3066
3225
|
privacy: "metadata-only",
|
|
3067
|
-
surfaceRole:
|
|
3226
|
+
surfaceRole: PRESETS[preset].role,
|
|
3068
3227
|
inputIntent: preset === "pointer" ? "point" : "annotate",
|
|
3069
3228
|
label,
|
|
3070
3229
|
phoneHints: {
|
|
@@ -3076,42 +3235,18 @@ function descriptorForPreset(preset, label) {
|
|
|
3076
3235
|
},
|
|
3077
3236
|
};
|
|
3078
3237
|
}
|
|
3079
|
-
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
3085
|
-
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
return "screen";
|
|
3090
|
-
return "svg";
|
|
3091
|
-
}
|
|
3092
|
-
function surfaceRoleForPreset(preset) {
|
|
3093
|
-
if (preset === "photoMarkup" || preset === "designReview")
|
|
3094
|
-
return "image";
|
|
3095
|
-
if (preset === "mapMarkup")
|
|
3096
|
-
return "map";
|
|
3097
|
-
if (preset === "screenMarkup" || preset === "pointer")
|
|
3098
|
-
return "screen";
|
|
3099
|
-
if (preset === "sketch")
|
|
3100
|
-
return "canvas";
|
|
3101
|
-
return "document";
|
|
3102
|
-
}
|
|
3238
|
+
const PRESETS = {
|
|
3239
|
+
approval: { kind: "field", role: "document", label: "Quick approval" },
|
|
3240
|
+
sketch: { kind: "svg", role: "canvas", label: "Sketch input" },
|
|
3241
|
+
photoMarkup: { kind: "image", role: "image", label: "Photo annotation" },
|
|
3242
|
+
pdfMarkup: { kind: "pdf", role: "document", label: "PDF annotation" },
|
|
3243
|
+
mapMarkup: { kind: "map", role: "map", label: "Map annotation" },
|
|
3244
|
+
screenMarkup: { kind: "screen", role: "screen", label: "Screen annotation" },
|
|
3245
|
+
designReview: { kind: "image", role: "image", label: "Design review" },
|
|
3246
|
+
pointer: { kind: "screen", role: "screen", label: "Pointer input" },
|
|
3247
|
+
};
|
|
3103
3248
|
function labelForPreset(preset) {
|
|
3104
|
-
|
|
3105
|
-
approval: "Quick approval",
|
|
3106
|
-
sketch: "Sketch input",
|
|
3107
|
-
photoMarkup: "Photo annotation",
|
|
3108
|
-
pdfMarkup: "PDF annotation",
|
|
3109
|
-
mapMarkup: "Map annotation",
|
|
3110
|
-
screenMarkup: "Screen annotation",
|
|
3111
|
-
designReview: "Design review",
|
|
3112
|
-
pointer: "Pointer input",
|
|
3113
|
-
};
|
|
3114
|
-
return labels[preset];
|
|
3249
|
+
return PRESETS[preset].label;
|
|
3115
3250
|
}
|
|
3116
3251
|
function curlForCreateInput(apiBaseUrl, payload) {
|
|
3117
3252
|
return [
|
|
@@ -3302,6 +3437,12 @@ function scaffoldJsonResult(command, plan, outputDir, files, parsed, provisionin
|
|
|
3302
3437
|
...(plan.apiBaseUrl.includes("<deployment>")
|
|
3303
3438
|
? [t("cloud.warning.placeholder")]
|
|
3304
3439
|
: []),
|
|
3440
|
+
// The backend helper is written under src/ because the scaffold has no
|
|
3441
|
+
// idea where this project's server lives. Say so, every time, or a Vite
|
|
3442
|
+
// app ships its key.
|
|
3443
|
+
...(plan.target === "web" && plan.sdk !== "swift"
|
|
3444
|
+
? [t("cloud.warning.clientTree")]
|
|
3445
|
+
: []),
|
|
3305
3446
|
...(offline
|
|
3306
3447
|
? [t("cloud.warning.offline")]
|
|
3307
3448
|
: dryRun
|
package/dist/cloud.d.ts
CHANGED
|
@@ -2,7 +2,7 @@ export declare const DEFAULT_REMOTEDRAW_API_BASE_URL = "https://api.remotedraw.c
|
|
|
2
2
|
export type CloudRuntime = {
|
|
3
3
|
env: Record<string, string | undefined>;
|
|
4
4
|
isInteractive?: boolean;
|
|
5
|
-
fetch?:
|
|
5
|
+
fetch?: (input: string | URL | Request, init?: RequestInit) => Promise<Response>;
|
|
6
6
|
exists: (filePath: string) => boolean | Promise<boolean>;
|
|
7
7
|
readFile: (filePath: string) => Promise<string>;
|
|
8
8
|
writeFile: (filePath: string, contents: string) => Promise<void>;
|
package/dist/cloud.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../src/cloud.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,+BAA+B,+BAA+B,CAAC;AAE5E,MAAM,MAAM,YAAY,GAAG;IACzB,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACxC,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;
|
|
1
|
+
{"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../src/cloud.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export declare const AGENT_SKILL_MARKDOWN = "---\nname: remotedraw\ndescription: Add RemoteDraw phone input to customer apps with the RemoteDraw CLI, public API, React SDK, raw HTTP, or customer-owned iOS sender flow.\n---\n\n# RemoteDraw Agent Skill\n\nUse this skill when a user asks to create, initialize, debug, or review a RemoteDraw integration.\n\n## Decision Flow\n\n1. Identify the receiver surface: web app, desktop app, iOS app, or headless/backend workflow.\n2. Identify the sender surface: RemoteDraw iOS app, embedded web sender, customer-owned iOS sender, or raw/headless sender.\n3. Pick the SDK path:\n - React SDK: web receiver and optional embedded web sender.\n - Plain JavaScript/raw HTTP: non-React web, desktop, backend, or custom clients.\n - Swift: customer-owned iOS sender apps.\n4. Pick a supported starter: `sketch` for a free-form receiver surface, or\n `screenMarkup` for annotations over a shared screen \u2014 both render through the\n `RemoteDrawReceiver` foundation, with the surrounding UI owned by the\n integrating app. Configure the target kind and descriptor directly for\n photos, PDFs, maps, bounded fields, and other custom surfaces. A bounded\n field is a target you describe yourself \u2014 `inputMapping: \"surface\"` plus a\n `coordinateSpace` \u2014 not a preset RemoteDraw ships.\n\n## Endpoint, Key, and Packages\n\nRemoteDraw is hosted. There is nothing for the customer to run or self-host.\n\n- API base URL: `https://api.remotedraw.com`\n- Public docs: `https://docs.remotedraw.com/docs`\n- API keys are created in the console at\n `https://dashboard.remotedraw.com/api/keys` and belong in `.env.local` as\n `REMOTEDRAW_API_KEY`, alongside\n `REMOTEDRAW_API_BASE_URL=https://api.remotedraw.com`.\n\nPublished npm packages \u2014 install only what the chosen path needs:\n\n| Package | Install | Use it for |\n| --- | --- | --- |\n| `@remotedraw/cli` | `npm install -g @remotedraw/cli` (or `npx @remotedraw/cli@latest`) | Scaffolding, doctor, and test sessions. |\n| `@remotedraw/react` | `npm install @remotedraw/react` | React receiver, pairing, and headless sender. Peers on `react`/`react-dom` >= 18. |\n| `@remotedraw/svelte` | `npm install @remotedraw/svelte` | Svelte receiver store over the framework-free client. |\n| `@remotedraw/client` | `npm install @remotedraw/client` | Framework-free receiver/sender/API clients for any JS runtime. |\n| `@remotedraw/protocol` | `npm install @remotedraw/protocol` | Shared schemas, types, and limits for raw-HTTP integrations. |\n| `@remotedraw/geometry` | `npm install @remotedraw/geometry` | Normalized stroke geometry, shape assist, hit testing, export. |\n\n`@remotedraw/react` already depends on `client`, `protocol`, and `geometry`, so\ndo not add those separately for a React app.\n\nFor customer-owned iOS senders, prefer the published SwiftPM package \u2014 it is the\nsame implementation the first-party RemoteDraw app runs:\n\n```swift\n.package(url: \"https://github.com/AxioSOzo/remotedraw-swift.git\", from: \"0.1.0\")\n.product(name: \"RemoteDrawSenderKit\", package: \"remotedraw-swift\")\n```\n\n`package:` is the repository basename, not the module name. `remotedraw init\n--sdk swift` remains available and writes one self-contained\n`RemoteDrawIntegration.swift` against the public HTTP routes; use it only when\nadding a SwiftPM dependency is not an option.\n\n## CLI First\n\nRead the machine-readable option catalog before choosing a plan:\n\n```sh\nremotedraw options --format json\n```\n\nInitialize a project with the closest supported path:\n\n```sh\nremotedraw init --target web --sender remotedraw-ios --sdk react --preset sketch\nremotedraw init --target web --sender embedded-web --sdk react --preset sketch\nremotedraw init --target desktop --sender remotedraw-ios --sdk js --preset sketch\nremotedraw init --target ios --sender own-ios --sdk swift --preset sketch\n```\n\nBy default, `new` and `init` create the dashboard project and a project-scoped\ndevelopment API key, then write `REMOTEDRAW_API_BASE_URL`,\n`REMOTEDRAW_PROJECT_ID`, and `REMOTEDRAW_API_KEY` to a gitignored `.env.local`.\nUse `--offline` only when cloud setup is intentionally out of scope.\n\n`--sender own-ios` requires `--sdk swift`; every other combination of\n`--target`, `--sender`, and `--sdk` is accepted. Invalid combinations fail with\n`INVALID_COMBINATION` rather than guessing.\n\nAgents must use explicit non-interactive dry runs before changing a project:\n\n```sh\nremotedraw init --non-interactive --offline --dry-run --format json --target web --sender remotedraw-ios --sdk react --preset sketch --package-manager npm\n# Inspect plan, files, defaultsApplied, and warnings before applying.\nremotedraw init --non-interactive --offline --format json --target web --sender remotedraw-ios --sdk react --preset sketch --package-manager npm\nremotedraw doctor --format json\nremotedraw create-input --preset sketch --json\n```\n\nDo not use the interactive wizard, synthesize arrow-key input, or scrape\nhuman-formatted output. If receiver, sender, or preset intent is ambiguous, ask\nthe user instead of guessing. Never pass `--force` unless overwrite scope was\nexplicitly approved.\n\n## Security Rules\n\n- Keep `rd_sk_...` API keys in trusted backend secrets only.\n- Keep the account-level `rd_cli_...` credential in the user config directory; never copy it into a project. Use `REMOTEDRAW_CLI_TOKEN` only as an explicitly managed CI secret.\n- Never place API keys in browser bundles, mobile clients, screenshots, logs, or generated examples.\n- Public clients should receive only `joinUrl`, `joinToken`, `receiverToken`, or `senderToken` values scoped to the session.\n- Production QR codes should use HTTPS `joinUrl` values. Do not make the custom scheme the primary QR target.\n\n## API Contract\n\n- Backend creates sessions with `POST /v1/sessions`.\n- Receiver clients read `POST /v1/receiver/session`, `/drawings`, `/drafts`, and `/senders` with a receiver token.\n- Sender clients join with `POST /v1/join`, stream mutable drafts to `/v1/sender/draft`, commit durable strokes to `/v1/sender/commit`, and finish with `/v1/sender/submit`.\n- Custom senders should throttle draft updates, coalesce to the latest pending preview, and commit one durable stroke on pointer-up with a stable `clientStrokeId`.\n\n## AI Actions\n\nReach for AI when the product needs something _from_ the finished drawing:\na generated image, a description, or structured data to branch on. Backend only\n(`aiActions:*` scopes on an `rd_sk_...` key). Never wire it to a commit,\nsubmit, or presence event \u2014 AI runs only on an explicit `POST /v1/ai-actions`\ncall the user asked for. Run it after the user is done; the route accepts\n`active` and `ended` sessions.\n\nMinimal request per outcome (`POST /v1/ai-actions`, plus optional\n`quality: \"fast\" | \"balanced\" | \"max\"`, default `balanced`):\n\n```jsonc\n{ \"sessionId\": \"...\", \"request\": \"image\", \"prompt\": \"...\" } // image back in the response\n{ \"sessionId\": \"...\", \"request\": \"image\", \"prompt\": \"...\", \"deliver\": [\"result\", \"board\"] } // and onto the board\n{ \"sessionId\": \"...\", \"request\": \"text\", \"prompt\": \"Describe this drawing.\" } // text back\n{ \"sessionId\": \"...\", \"request\": \"text\", \"prompt\": \"...\", \"text\": { \"schema\": { /* JSON Schema */ } } } // typed JSON\n```\n\nThe response is asynchronous: `create` returns `status: \"queued\"`. Poll\n`POST /v1/ai-actions/get` until `status` is `succeeded`, `failed`, or\n`canceled`, or use the helper on the API client\n(`createHttpRemoteDrawApiClient` from `@remotedraw/client`, re-exported by\n`@remotedraw/react`). This client holds the `rd_sk_` key, so it only ever runs\non the backend:\n\n```ts\nconst action = await client.createAiAction({\n sessionId,\n request: \"image\",\n prompt,\n});\nconst finished = await client.waitForAiAction({ aiActionId: action.id });\n// finished.result: generatedImageUrl | generatedText | generatedData | boardDrawingIds\n```\n\nThere is no completion webhook. Results are RemoteDraw-hosted asset URLs, not\nprovider URLs. See https://docs.remotedraw.com/docs/api#ai for the full\nrequest, credit, and legacy-mapping tables.\n\n## Verification\n\nAfter changes, verify against the customer's project \u2014 never assume RemoteDraw's\nown repo scripts exist here.\n\n```sh\nremotedraw doctor # config, SDK deps, REMOTEDRAW_* env\nremotedraw create-input --execute # open a real session, print the join URL\n```\n\nThen run whatever type check and test command the project already defines (for\nexample `npm run typecheck` and `npm test`). Do not invent script names, and do\nnot run `bun run test:api`, `bun run typecheck`, or `bun run ios:kit:test` \u2014\nthose are RemoteDraw's internal monorepo scripts and will not exist in a\ncustomer project.";
|
|
1
|
+
export declare const AGENT_SKILL_MARKDOWN = "---\nname: remotedraw\ndescription: Integrate RemoteDraw phone drawing into a customer's product \u2014 scan the codebase, ask the product questions, get approval before installing, then wire the receiver and the sender with the SDK's own components (React/Svelte/JS receiver, hosted web sender, RemoteDrawSenderKit full-screen iOS sender).\n---\n\n# RemoteDraw Agent Skill\n\nUse this skill when a user asks what RemoteDraw is, whether it fits their app,\nor to create, debug, or review a RemoteDraw integration.\n\n## What RemoteDraw is, in four sentences\n\nA phone becomes a pen for a screen. Your **backend** creates a **session** with\nan `rd_sk_` API key; your **receiver** (web, desktop, or any client) shows the\nsession's ink using a `rd_recv_` token; a **sender** (the hosted `/join` page\nthat any QR opens, the RemoteDraw iOS app, or your own iOS app through\n`RemoteDrawSenderKit`) draws with a `rd_send_` token. Strokes stream as drafts\nwhile the finger is down and commit on release; nothing runs on the customer's\ninfrastructure except the session-creation call.\n\n- API: `https://api.remotedraw.com` \u00B7 Docs: `https://docs.remotedraw.com/docs`\n (agent summary: `https://docs.remotedraw.com/llms.txt`) \u00B7 Keys:\n `https://dashboard.remotedraw.com/api/keys`\n- Packages: `@remotedraw/cli`, `@remotedraw/react`, `@remotedraw/svelte`,\n `@remotedraw/client`, `@remotedraw/protocol`, `@remotedraw/geometry`,\n and the SwiftPM package `https://github.com/AxioSOzo/remotedraw-swift.git`\n (product `RemoteDrawSenderKit`).\n\n## The flow \u2014 in this order\n\n1. **Explain before touching anything.** If the user is asking what RemoteDraw\n can do, answer from this file and the docs. Do not install, scaffold, or\n create sessions to answer a question.\n2. **Ask for approval before installing.** Name exactly what you want to add\n (`npm install -g @remotedraw/cli`, `@remotedraw/react`, a Swift package, a\n dashboard project + key) and why, then wait. This includes `remotedraw init`\n without `--offline`, which provisions a billable project and key.\n3. **Scan the codebase.** `remotedraw scan --format json` (or\n `npx @remotedraw/cli@latest scan --format json` before the CLI is installed)\n reports the web/server/iOS projects, where an `rd_sk_` key may live, any\n RemoteDraw wiring already present, the integration options that fit, and\n the product questions to ask. Read it; verify its `evidence` where it\n matters.\n4. **Ask the product questions.** They come back in the scan's `questions`\n array. The ones that decide the build: what is drawn on, which existing\n screen the receiver goes on, who holds the phone (signed-in user \u2192 direct\n sender, anyone \u2192 QR), what Submit does, and demo vs. real feature. Do not\n invent answers; a one-page \"drawing lab\" is only right when the user says a\n demo is what they want.\n5. **Propose one plan, then build all of it.** Receiver, session creation,\n sender, and the exit/submit path \u2014 an integration is not done when ink\n appears once on a test page. Build beside existing features; never delete\n or replace a host-app feature on your own initiative.\n6. **Verify by using it.** Run `remotedraw doctor --format json`, open a real\n session, draw from a phone (or `create-input --execute` + the hosted join\n URL), and confirm ink lands on the receiver. On iOS, run it on a device or\n simulator and look at the screen; a compiling canvas is not a working one.\n\n## Choosing the pieces\n\nReceiver (the screen that shows ink):\n\n| Host | Use | Not |\n| --- | --- | --- |\n| React / Next / Remix | `@remotedraw/react`: `RemoteDrawProvider`, `RemoteDrawReceiver`, `PairingCode`, `RemoteDrawSessionControls` | A hand-rolled SVG or polling loop |\n| Svelte / SvelteKit | `@remotedraw/svelte` receiver store | \u2014 |\n| Anything else with JS | `@remotedraw/client`: `createHttpReceiverClient`, `createRealtimeReceiverSource` | \u2014 |\n| No JS at all | Raw HTTP `POST /v1/receiver/*` with the receiver token | \u2014 |\n\nSender (the phone):\n\n| Situation | Use | Not |\n| --- | --- | --- |\n| No phone app in the product | **Hosted `/join` page.** Render `joinUrl` as a QR (`PairingCode`). Full-screen, polished, zero sender code; the RemoteDraw iOS app opens the same link. | An in-page web sender (`--sender embedded-web`) unless the user explicitly wants drawing inside their own web page |\n| The product has its own iOS app | **`RemoteDrawSenderKit`** full-screen surface (below). Backend mints `rd_send_` via `POST /v1/sessions/direct-sender` for a signed-in user; QR join stays as the fallback. | A custom canvas, a raw-HTTP Swift client, or `remotedraw init --sdk swift` when adding a SwiftPM dependency is possible |\n| Headless / tests | `POST /v1/join` \u2192 `/v1/sender/draft` \u2192 `/v1/sender/commit` | \u2014 |\n\nSession creation (`POST /v1/sessions`) needs the `rd_sk_` key and therefore\nruns only where the scan found server-side code: a Convex action, a Next route\nhandler, an Express/Hono route, a serverless function. If the scan found none,\nask where the backend is. Never scaffold `createRemoteDrawSession.ts` into a\nVite/Next client tree \u2014 `--target web` in `remotedraw init` still writes it\nunder `src/`; move it, or scaffold into a scratch directory and copy only what\nbelongs.\n\n## iOS: the bar\n\nThe reference is the RemoteDraw app's own drawing screen, and the SDK ships it.\n\n```swift\n// Package.swift / Xcode \u2192 Add Package\n.package(url: \"https://github.com/AxioSOzo/remotedraw-swift.git\", from: \"0.1.0\")\n.product(name: \"RemoteDrawSenderKit\", package: \"remotedraw-swift\")\n\nimport RemoteDrawSenderKit\n\n// Once, at app start.\nRemoteDraw.configure(.init(apiBaseURL: .production))\n\n// Wherever drawing starts. `token` is the rd_send_ string your backend\n// minted with POST /v1/sessions/direct-sender (a QR's rd_join_ works too).\nButton(\"Draw\") { drawing = true }\n .remoteDrawSurface(isPresented: $drawing, senderToken: token) { outcome in\n switch outcome {\n case .submitted(let receipt): record(receipt)\n case .left: break\n case .expired: refreshSession()\n case .failed(let error): report(error)\n }\n }\n```\n\nRules:\n\n- The drawing surface is **full screen**: `.remoteDrawSurface` (a\n `fullScreenCover`) or `RemoteDrawTakeover` inside your own cover or\n `UIHostingController`. A sheet is acceptable only if `RemoteDrawSurface`\n fills it edge to edge; the surface then owns the gestures and the sheet must\n not be draggable while drawing. There is no small-canvas option.\n- The user can always leave \u2014 the surface has its own exit; `RemoteDrawExit`\n only decides whether leaving with unsubmitted ink asks first \u2014 and the host\n gets an outcome. Submit, undo, clear, instruments, and paper are the SDK's.\n- Do not write a `UIViewRepresentable` canvas, a draft loop, or an HTTP client:\n cadence, point budgets, the packed-point codec, sequence healing, token\n refresh, and presence are protocol, and `RemoteDrawSenderSession` already\n implements them. If you must go headless, compose `RemoteDrawInkCanvas` +\n `RemoteDrawStrokeCapture` on that session \u2014 never raw `URLSession`.\n- `RemoteDraw.configure` is the only setup; the SDK needs no `Info.plist` keys.\n- Customers do not ship a separate RemoteDraw app; their app *is* the sender.\n\n## CLI\n\n```sh\nremotedraw options --format json # the option catalog\nremotedraw scan --format json # read the codebase first (step 3)\nremotedraw init --non-interactive --offline --dry-run --format json \\\n --path apps/web --target web --sender remotedraw-ios --sdk react --preset sketch --package-manager npm\nremotedraw doctor --format json\nremotedraw create-input --preset sketch --json\nremotedraw create-input --execute # a real session + join URL, needs a key\n```\n\n`--sender own-ios` requires `--sdk swift`; other invalid combinations fail with\n`INVALID_COMBINATION`. By default `init`/`new` also create a dashboard project\nand a project-scoped development key in `.env.local` \u2014 that is the step that\nneeds approval (step 2); `--offline` writes files only. Never pass `--force`\nunless the user approved overwriting. Do not drive the interactive wizard or\nscrape human-formatted output; every command has `--format json`.\n\n## Security and tenancy\n\n- `rd_sk_\u2026` keys: backend secrets only. Never in browser bundles, Swift, app\n bundles, screenshots, logs, or generated examples.\n- The account-level `rd_cli_\u2026` credential stays in the user config directory;\n never copy it into a project. `REMOTEDRAW_CLI_TOKEN` is for CI secrets only.\n- Public clients receive only `joinUrl`, `joinToken`, `receiverToken`, or\n `senderToken`, each scoped to one session. Production QR codes use the HTTPS\n `joinUrl`, not the custom scheme.\n- One key serves every customer of the product, so a session id is not a\n capability. Create sessions with `externalId: \"<product>:<tenant>\"`, and\n check it (`POST /v1/sessions/get`) before attaching a sender or ending a\n session on a tenant's behalf.\n\n## Gotchas the SDKs hide and hand-written code hits\n\n- `POST /v1/sessions` is billable and not idempotent. React StrictMode runs\n mount effects twice in development: guard with a ref, or create the session\n in a server action / loader. Store the `receiverToken` if the receiver\n outlives a page load \u2014 it is the only credential that reads a session's ink.\n- Timestamps are integer milliseconds. `occurredAt` and point `t` values are\n accepted with a fraction (floored) but a hand-written client should send\n integers.\n- Committed points come back in board space, remapped through\n `device.aspectRatio`; send the aspect ratio of the pad the finger touches.\n- `RemoteDrawReceiver` defaults `phones` and `pointers` to on. A connected\n phone that enters viewport mode is drawn on the board as a phone outline; pass\n `phones={false}` for a plain surface where that is not wanted.\n- `PairingCode` hides the join URL text unless `showLink`; pass it when a human\n needs the link without a camera.\n- `joinTokenExpiresAt` is earlier than the session's `expiresAt`: the QR dies\n first, the board stays live.\n\n## API contract\n\n- Backend: `POST /v1/sessions` (create), `/v1/sessions/get`, `/v1/sessions/end`,\n `/v1/sessions/direct-sender` (mint `rd_send_` for your own app),\n `/v1/sessions/join-token` (a fresh QR).\n- Receiver: `POST /v1/receiver/session`, `/drawings`, `/drafts`, `/senders`\n with the receiver token, or the realtime source in the SDKs.\n- Sender: `POST /v1/join` (spends a join token; revokes other senders), then\n `/v1/sender/draft` (latest-only preview, throttle to ~32 ms),\n `/v1/sender/commit` (one durable stroke per pointer-up with a stable\n `clientStrokeId`), `/v1/sender/submit`.\n\n## AI actions\n\nReach for AI when the product needs something _from_ the finished drawing:\na generated image, a description, or structured data to branch on. Backend only\n(`aiActions:*` scopes on an `rd_sk_...` key). Never wire it to a commit,\nsubmit, or presence event \u2014 AI runs only on an explicit `POST /v1/ai-actions`\ncall the user asked for. Run it after the user is done; the route accepts\n`active` and `ended` sessions.\n\nMinimal request per outcome (`POST /v1/ai-actions`, plus optional\n`quality: \"fast\" | \"balanced\" | \"max\"`, default `balanced`):\n\n```jsonc\n{ \"sessionId\": \"...\", \"request\": \"image\", \"prompt\": \"...\" } // image back in the response\n{ \"sessionId\": \"...\", \"request\": \"image\", \"prompt\": \"...\", \"deliver\": [\"result\", \"board\"] } // and onto the board\n{ \"sessionId\": \"...\", \"request\": \"text\", \"prompt\": \"Describe this drawing.\" } // text back\n{ \"sessionId\": \"...\", \"request\": \"text\", \"prompt\": \"...\", \"text\": { \"schema\": { /* JSON Schema */ } } } // typed JSON\n```\n\nThe response is asynchronous: `create` returns `status: \"queued\"`. Poll\n`POST /v1/ai-actions/get` until `status` is `succeeded`, `failed`, or\n`canceled`, or use `createAiAction` + `waitForAiAction` on\n`createHttpRemoteDrawApiClient` from `@remotedraw/client` (re-exported by\n`@remotedraw/react`) \u2014 backend only, it holds the key. There is no completion\nwebhook. See https://docs.remotedraw.com/docs/api#ai.\n\n## Verification\n\nAfter changes, verify against the customer's project \u2014 never assume RemoteDraw's\nown repo scripts exist here.\n\n```sh\nremotedraw doctor # config, SDK deps, REMOTEDRAW_* env\nremotedraw create-input --execute # open a real session, print the join URL\n```\n\nThen run whatever type check and test command the project already defines (for\nexample `npm run typecheck` and `npm test`). Do not invent script names, and do\nnot run `bun run test:api`, `bun run typecheck`, or `bun run ios:kit:test` \u2014\nthose are RemoteDraw's internal monorepo scripts and will not exist in a\ncustomer project.";
|
|
2
2
|
//# sourceMappingURL=agent-skill.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"agent-skill.d.ts","sourceRoot":"","sources":["../../src/generated/agent-skill.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,oBAAoB,
|
|
1
|
+
{"version":3,"file":"agent-skill.d.ts","sourceRoot":"","sources":["../../src/generated/agent-skill.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,oBAAoB,s1ZAAotZ,CAAC"}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
// GENERATED FILE — do not edit.
|
|
2
2
|
// Source: docs/agents/remotedraw/SKILL.md
|
|
3
3
|
// Regenerate: bun run --cwd packages/cli generate:skill
|
|
4
|
-
export const AGENT_SKILL_MARKDOWN = "---\nname: remotedraw\ndescription: Add RemoteDraw phone input to customer apps with the RemoteDraw CLI, public API, React SDK, raw HTTP, or customer-owned iOS sender flow.\n---\n\n# RemoteDraw Agent Skill\n\nUse this skill when a user asks to create, initialize, debug, or review a RemoteDraw integration.\n\n## Decision Flow\n\n1. Identify the receiver surface: web app, desktop app, iOS app, or headless/backend workflow.\n2. Identify the sender surface: RemoteDraw iOS app, embedded web sender, customer-owned iOS sender, or raw/headless sender.\n3. Pick the SDK path:\n - React SDK: web receiver and optional embedded web sender.\n - Plain JavaScript/raw HTTP: non-React web, desktop, backend, or custom clients.\n - Swift: customer-owned iOS sender apps.\n4. Pick a supported starter: `sketch` for a free-form receiver surface, or\n `screenMarkup` for annotations over a shared screen — both render through the\n `RemoteDrawReceiver` foundation, with the surrounding UI owned by the\n integrating app. Configure the target kind and descriptor directly for\n photos, PDFs, maps, bounded fields, and other custom surfaces. A bounded\n field is a target you describe yourself — `inputMapping: \"surface\"` plus a\n `coordinateSpace` — not a preset RemoteDraw ships.\n\n## Endpoint, Key, and Packages\n\nRemoteDraw is hosted. There is nothing for the customer to run or self-host.\n\n- API base URL: `https://api.remotedraw.com`\n- Public docs: `https://docs.remotedraw.com/docs`\n- API keys are created in the console at\n `https://dashboard.remotedraw.com/api/keys` and belong in `.env.local` as\n `REMOTEDRAW_API_KEY`, alongside\n `REMOTEDRAW_API_BASE_URL=https://api.remotedraw.com`.\n\nPublished npm packages — install only what the chosen path needs:\n\n| Package | Install | Use it for |\n| --- | --- | --- |\n| `@remotedraw/cli` | `npm install -g @remotedraw/cli` (or `npx @remotedraw/cli@latest`) | Scaffolding, doctor, and test sessions. |\n| `@remotedraw/react` | `npm install @remotedraw/react` | React receiver, pairing, and headless sender. Peers on `react`/`react-dom` >= 18. |\n| `@remotedraw/svelte` | `npm install @remotedraw/svelte` | Svelte receiver store over the framework-free client. |\n| `@remotedraw/client` | `npm install @remotedraw/client` | Framework-free receiver/sender/API clients for any JS runtime. |\n| `@remotedraw/protocol` | `npm install @remotedraw/protocol` | Shared schemas, types, and limits for raw-HTTP integrations. |\n| `@remotedraw/geometry` | `npm install @remotedraw/geometry` | Normalized stroke geometry, shape assist, hit testing, export. |\n\n`@remotedraw/react` already depends on `client`, `protocol`, and `geometry`, so\ndo not add those separately for a React app.\n\nFor customer-owned iOS senders, prefer the published SwiftPM package — it is the\nsame implementation the first-party RemoteDraw app runs:\n\n```swift\n.package(url: \"https://github.com/AxioSOzo/remotedraw-swift.git\", from: \"0.1.0\")\n.product(name: \"RemoteDrawSenderKit\", package: \"remotedraw-swift\")\n```\n\n`package:` is the repository basename, not the module name. `remotedraw init\n--sdk swift` remains available and writes one self-contained\n`RemoteDrawIntegration.swift` against the public HTTP routes; use it only when\nadding a SwiftPM dependency is not an option.\n\n## CLI First\n\nRead the machine-readable option catalog before choosing a plan:\n\n```sh\nremotedraw options --format json\n```\n\nInitialize a project with the closest supported path:\n\n```sh\nremotedraw init --target web --sender remotedraw-ios --sdk react --preset sketch\nremotedraw init --target web --sender embedded-web --sdk react --preset sketch\nremotedraw init --target desktop --sender remotedraw-ios --sdk js --preset sketch\nremotedraw init --target ios --sender own-ios --sdk swift --preset sketch\n```\n\nBy default, `new` and `init` create the dashboard project and a project-scoped\ndevelopment API key, then write `REMOTEDRAW_API_BASE_URL`,\n`REMOTEDRAW_PROJECT_ID`, and `REMOTEDRAW_API_KEY` to a gitignored `.env.local`.\nUse `--offline` only when cloud setup is intentionally out of scope.\n\n`--sender own-ios` requires `--sdk swift`; every other combination of\n`--target`, `--sender`, and `--sdk` is accepted. Invalid combinations fail with\n`INVALID_COMBINATION` rather than guessing.\n\nAgents must use explicit non-interactive dry runs before changing a project:\n\n```sh\nremotedraw init --non-interactive --offline --dry-run --format json --target web --sender remotedraw-ios --sdk react --preset sketch --package-manager npm\n# Inspect plan, files, defaultsApplied, and warnings before applying.\nremotedraw init --non-interactive --offline --format json --target web --sender remotedraw-ios --sdk react --preset sketch --package-manager npm\nremotedraw doctor --format json\nremotedraw create-input --preset sketch --json\n```\n\nDo not use the interactive wizard, synthesize arrow-key input, or scrape\nhuman-formatted output. If receiver, sender, or preset intent is ambiguous, ask\nthe user instead of guessing. Never pass `--force` unless overwrite scope was\nexplicitly approved.\n\n## Security Rules\n\n- Keep `rd_sk_...` API keys in trusted backend secrets only.\n- Keep the account-level `rd_cli_...` credential in the user config directory; never copy it into a project. Use `REMOTEDRAW_CLI_TOKEN` only as an explicitly managed CI secret.\n- Never place API keys in browser bundles, mobile clients, screenshots, logs, or generated examples.\n- Public clients should receive only `joinUrl`, `joinToken`, `receiverToken`, or `senderToken` values scoped to the session.\n- Production QR codes should use HTTPS `joinUrl` values. Do not make the custom scheme the primary QR target.\n\n## API Contract\n\n- Backend creates sessions with `POST /v1/sessions`.\n- Receiver clients read `POST /v1/receiver/session`, `/drawings`, `/drafts`, and `/senders` with a receiver token.\n- Sender clients join with `POST /v1/join`, stream mutable drafts to `/v1/sender/draft`, commit durable strokes to `/v1/sender/commit`, and finish with `/v1/sender/submit`.\n- Custom senders should throttle draft updates, coalesce to the latest pending preview, and commit one durable stroke on pointer-up with a stable `clientStrokeId`.\n\n## AI Actions\n\nReach for AI when the product needs something _from_ the finished drawing:\na generated image, a description, or structured data to branch on. Backend only\n(`aiActions:*` scopes on an `rd_sk_...` key). Never wire it to a commit,\nsubmit, or presence event — AI runs only on an explicit `POST /v1/ai-actions`\ncall the user asked for. Run it after the user is done; the route accepts\n`active` and `ended` sessions.\n\nMinimal request per outcome (`POST /v1/ai-actions`, plus optional\n`quality: \"fast\" | \"balanced\" | \"max\"`, default `balanced`):\n\n```jsonc\n{ \"sessionId\": \"...\", \"request\": \"image\", \"prompt\": \"...\" } // image back in the response\n{ \"sessionId\": \"...\", \"request\": \"image\", \"prompt\": \"...\", \"deliver\": [\"result\", \"board\"] } // and onto the board\n{ \"sessionId\": \"...\", \"request\": \"text\", \"prompt\": \"Describe this drawing.\" } // text back\n{ \"sessionId\": \"...\", \"request\": \"text\", \"prompt\": \"...\", \"text\": { \"schema\": { /* JSON Schema */ } } } // typed JSON\n```\n\nThe response is asynchronous: `create` returns `status: \"queued\"`. Poll\n`POST /v1/ai-actions/get` until `status` is `succeeded`, `failed`, or\n`canceled`, or use the helper on the API client\n(`createHttpRemoteDrawApiClient` from `@remotedraw/client`, re-exported by\n`@remotedraw/react`). This client holds the `rd_sk_` key, so it only ever runs\non the backend:\n\n```ts\nconst action = await client.createAiAction({\n sessionId,\n request: \"image\",\n prompt,\n});\nconst finished = await client.waitForAiAction({ aiActionId: action.id });\n// finished.result: generatedImageUrl | generatedText | generatedData | boardDrawingIds\n```\n\nThere is no completion webhook. Results are RemoteDraw-hosted asset URLs, not\nprovider URLs. See https://docs.remotedraw.com/docs/api#ai for the full\nrequest, credit, and legacy-mapping tables.\n\n## Verification\n\nAfter changes, verify against the customer's project — never assume RemoteDraw's\nown repo scripts exist here.\n\n```sh\nremotedraw doctor # config, SDK deps, REMOTEDRAW_* env\nremotedraw create-input --execute # open a real session, print the join URL\n```\n\nThen run whatever type check and test command the project already defines (for\nexample `npm run typecheck` and `npm test`). Do not invent script names, and do\nnot run `bun run test:api`, `bun run typecheck`, or `bun run ios:kit:test` —\nthose are RemoteDraw's internal monorepo scripts and will not exist in a\ncustomer project.";
|
|
4
|
+
export const AGENT_SKILL_MARKDOWN = "---\nname: remotedraw\ndescription: Integrate RemoteDraw phone drawing into a customer's product — scan the codebase, ask the product questions, get approval before installing, then wire the receiver and the sender with the SDK's own components (React/Svelte/JS receiver, hosted web sender, RemoteDrawSenderKit full-screen iOS sender).\n---\n\n# RemoteDraw Agent Skill\n\nUse this skill when a user asks what RemoteDraw is, whether it fits their app,\nor to create, debug, or review a RemoteDraw integration.\n\n## What RemoteDraw is, in four sentences\n\nA phone becomes a pen for a screen. Your **backend** creates a **session** with\nan `rd_sk_` API key; your **receiver** (web, desktop, or any client) shows the\nsession's ink using a `rd_recv_` token; a **sender** (the hosted `/join` page\nthat any QR opens, the RemoteDraw iOS app, or your own iOS app through\n`RemoteDrawSenderKit`) draws with a `rd_send_` token. Strokes stream as drafts\nwhile the finger is down and commit on release; nothing runs on the customer's\ninfrastructure except the session-creation call.\n\n- API: `https://api.remotedraw.com` · Docs: `https://docs.remotedraw.com/docs`\n (agent summary: `https://docs.remotedraw.com/llms.txt`) · Keys:\n `https://dashboard.remotedraw.com/api/keys`\n- Packages: `@remotedraw/cli`, `@remotedraw/react`, `@remotedraw/svelte`,\n `@remotedraw/client`, `@remotedraw/protocol`, `@remotedraw/geometry`,\n and the SwiftPM package `https://github.com/AxioSOzo/remotedraw-swift.git`\n (product `RemoteDrawSenderKit`).\n\n## The flow — in this order\n\n1. **Explain before touching anything.** If the user is asking what RemoteDraw\n can do, answer from this file and the docs. Do not install, scaffold, or\n create sessions to answer a question.\n2. **Ask for approval before installing.** Name exactly what you want to add\n (`npm install -g @remotedraw/cli`, `@remotedraw/react`, a Swift package, a\n dashboard project + key) and why, then wait. This includes `remotedraw init`\n without `--offline`, which provisions a billable project and key.\n3. **Scan the codebase.** `remotedraw scan --format json` (or\n `npx @remotedraw/cli@latest scan --format json` before the CLI is installed)\n reports the web/server/iOS projects, where an `rd_sk_` key may live, any\n RemoteDraw wiring already present, the integration options that fit, and\n the product questions to ask. Read it; verify its `evidence` where it\n matters.\n4. **Ask the product questions.** They come back in the scan's `questions`\n array. The ones that decide the build: what is drawn on, which existing\n screen the receiver goes on, who holds the phone (signed-in user → direct\n sender, anyone → QR), what Submit does, and demo vs. real feature. Do not\n invent answers; a one-page \"drawing lab\" is only right when the user says a\n demo is what they want.\n5. **Propose one plan, then build all of it.** Receiver, session creation,\n sender, and the exit/submit path — an integration is not done when ink\n appears once on a test page. Build beside existing features; never delete\n or replace a host-app feature on your own initiative.\n6. **Verify by using it.** Run `remotedraw doctor --format json`, open a real\n session, draw from a phone (or `create-input --execute` + the hosted join\n URL), and confirm ink lands on the receiver. On iOS, run it on a device or\n simulator and look at the screen; a compiling canvas is not a working one.\n\n## Choosing the pieces\n\nReceiver (the screen that shows ink):\n\n| Host | Use | Not |\n| --- | --- | --- |\n| React / Next / Remix | `@remotedraw/react`: `RemoteDrawProvider`, `RemoteDrawReceiver`, `PairingCode`, `RemoteDrawSessionControls` | A hand-rolled SVG or polling loop |\n| Svelte / SvelteKit | `@remotedraw/svelte` receiver store | — |\n| Anything else with JS | `@remotedraw/client`: `createHttpReceiverClient`, `createRealtimeReceiverSource` | — |\n| No JS at all | Raw HTTP `POST /v1/receiver/*` with the receiver token | — |\n\nSender (the phone):\n\n| Situation | Use | Not |\n| --- | --- | --- |\n| No phone app in the product | **Hosted `/join` page.** Render `joinUrl` as a QR (`PairingCode`). Full-screen, polished, zero sender code; the RemoteDraw iOS app opens the same link. | An in-page web sender (`--sender embedded-web`) unless the user explicitly wants drawing inside their own web page |\n| The product has its own iOS app | **`RemoteDrawSenderKit`** full-screen surface (below). Backend mints `rd_send_` via `POST /v1/sessions/direct-sender` for a signed-in user; QR join stays as the fallback. | A custom canvas, a raw-HTTP Swift client, or `remotedraw init --sdk swift` when adding a SwiftPM dependency is possible |\n| Headless / tests | `POST /v1/join` → `/v1/sender/draft` → `/v1/sender/commit` | — |\n\nSession creation (`POST /v1/sessions`) needs the `rd_sk_` key and therefore\nruns only where the scan found server-side code: a Convex action, a Next route\nhandler, an Express/Hono route, a serverless function. If the scan found none,\nask where the backend is. Never scaffold `createRemoteDrawSession.ts` into a\nVite/Next client tree — `--target web` in `remotedraw init` still writes it\nunder `src/`; move it, or scaffold into a scratch directory and copy only what\nbelongs.\n\n## iOS: the bar\n\nThe reference is the RemoteDraw app's own drawing screen, and the SDK ships it.\n\n```swift\n// Package.swift / Xcode → Add Package\n.package(url: \"https://github.com/AxioSOzo/remotedraw-swift.git\", from: \"0.1.0\")\n.product(name: \"RemoteDrawSenderKit\", package: \"remotedraw-swift\")\n\nimport RemoteDrawSenderKit\n\n// Once, at app start.\nRemoteDraw.configure(.init(apiBaseURL: .production))\n\n// Wherever drawing starts. `token` is the rd_send_ string your backend\n// minted with POST /v1/sessions/direct-sender (a QR's rd_join_ works too).\nButton(\"Draw\") { drawing = true }\n .remoteDrawSurface(isPresented: $drawing, senderToken: token) { outcome in\n switch outcome {\n case .submitted(let receipt): record(receipt)\n case .left: break\n case .expired: refreshSession()\n case .failed(let error): report(error)\n }\n }\n```\n\nRules:\n\n- The drawing surface is **full screen**: `.remoteDrawSurface` (a\n `fullScreenCover`) or `RemoteDrawTakeover` inside your own cover or\n `UIHostingController`. A sheet is acceptable only if `RemoteDrawSurface`\n fills it edge to edge; the surface then owns the gestures and the sheet must\n not be draggable while drawing. There is no small-canvas option.\n- The user can always leave — the surface has its own exit; `RemoteDrawExit`\n only decides whether leaving with unsubmitted ink asks first — and the host\n gets an outcome. Submit, undo, clear, instruments, and paper are the SDK's.\n- Do not write a `UIViewRepresentable` canvas, a draft loop, or an HTTP client:\n cadence, point budgets, the packed-point codec, sequence healing, token\n refresh, and presence are protocol, and `RemoteDrawSenderSession` already\n implements them. If you must go headless, compose `RemoteDrawInkCanvas` +\n `RemoteDrawStrokeCapture` on that session — never raw `URLSession`.\n- `RemoteDraw.configure` is the only setup; the SDK needs no `Info.plist` keys.\n- Customers do not ship a separate RemoteDraw app; their app *is* the sender.\n\n## CLI\n\n```sh\nremotedraw options --format json # the option catalog\nremotedraw scan --format json # read the codebase first (step 3)\nremotedraw init --non-interactive --offline --dry-run --format json \\\n --path apps/web --target web --sender remotedraw-ios --sdk react --preset sketch --package-manager npm\nremotedraw doctor --format json\nremotedraw create-input --preset sketch --json\nremotedraw create-input --execute # a real session + join URL, needs a key\n```\n\n`--sender own-ios` requires `--sdk swift`; other invalid combinations fail with\n`INVALID_COMBINATION`. By default `init`/`new` also create a dashboard project\nand a project-scoped development key in `.env.local` — that is the step that\nneeds approval (step 2); `--offline` writes files only. Never pass `--force`\nunless the user approved overwriting. Do not drive the interactive wizard or\nscrape human-formatted output; every command has `--format json`.\n\n## Security and tenancy\n\n- `rd_sk_…` keys: backend secrets only. Never in browser bundles, Swift, app\n bundles, screenshots, logs, or generated examples.\n- The account-level `rd_cli_…` credential stays in the user config directory;\n never copy it into a project. `REMOTEDRAW_CLI_TOKEN` is for CI secrets only.\n- Public clients receive only `joinUrl`, `joinToken`, `receiverToken`, or\n `senderToken`, each scoped to one session. Production QR codes use the HTTPS\n `joinUrl`, not the custom scheme.\n- One key serves every customer of the product, so a session id is not a\n capability. Create sessions with `externalId: \"<product>:<tenant>\"`, and\n check it (`POST /v1/sessions/get`) before attaching a sender or ending a\n session on a tenant's behalf.\n\n## Gotchas the SDKs hide and hand-written code hits\n\n- `POST /v1/sessions` is billable and not idempotent. React StrictMode runs\n mount effects twice in development: guard with a ref, or create the session\n in a server action / loader. Store the `receiverToken` if the receiver\n outlives a page load — it is the only credential that reads a session's ink.\n- Timestamps are integer milliseconds. `occurredAt` and point `t` values are\n accepted with a fraction (floored) but a hand-written client should send\n integers.\n- Committed points come back in board space, remapped through\n `device.aspectRatio`; send the aspect ratio of the pad the finger touches.\n- `RemoteDrawReceiver` defaults `phones` and `pointers` to on. A connected\n phone that enters viewport mode is drawn on the board as a phone outline; pass\n `phones={false}` for a plain surface where that is not wanted.\n- `PairingCode` hides the join URL text unless `showLink`; pass it when a human\n needs the link without a camera.\n- `joinTokenExpiresAt` is earlier than the session's `expiresAt`: the QR dies\n first, the board stays live.\n\n## API contract\n\n- Backend: `POST /v1/sessions` (create), `/v1/sessions/get`, `/v1/sessions/end`,\n `/v1/sessions/direct-sender` (mint `rd_send_` for your own app),\n `/v1/sessions/join-token` (a fresh QR).\n- Receiver: `POST /v1/receiver/session`, `/drawings`, `/drafts`, `/senders`\n with the receiver token, or the realtime source in the SDKs.\n- Sender: `POST /v1/join` (spends a join token; revokes other senders), then\n `/v1/sender/draft` (latest-only preview, throttle to ~32 ms),\n `/v1/sender/commit` (one durable stroke per pointer-up with a stable\n `clientStrokeId`), `/v1/sender/submit`.\n\n## AI actions\n\nReach for AI when the product needs something _from_ the finished drawing:\na generated image, a description, or structured data to branch on. Backend only\n(`aiActions:*` scopes on an `rd_sk_...` key). Never wire it to a commit,\nsubmit, or presence event — AI runs only on an explicit `POST /v1/ai-actions`\ncall the user asked for. Run it after the user is done; the route accepts\n`active` and `ended` sessions.\n\nMinimal request per outcome (`POST /v1/ai-actions`, plus optional\n`quality: \"fast\" | \"balanced\" | \"max\"`, default `balanced`):\n\n```jsonc\n{ \"sessionId\": \"...\", \"request\": \"image\", \"prompt\": \"...\" } // image back in the response\n{ \"sessionId\": \"...\", \"request\": \"image\", \"prompt\": \"...\", \"deliver\": [\"result\", \"board\"] } // and onto the board\n{ \"sessionId\": \"...\", \"request\": \"text\", \"prompt\": \"Describe this drawing.\" } // text back\n{ \"sessionId\": \"...\", \"request\": \"text\", \"prompt\": \"...\", \"text\": { \"schema\": { /* JSON Schema */ } } } // typed JSON\n```\n\nThe response is asynchronous: `create` returns `status: \"queued\"`. Poll\n`POST /v1/ai-actions/get` until `status` is `succeeded`, `failed`, or\n`canceled`, or use `createAiAction` + `waitForAiAction` on\n`createHttpRemoteDrawApiClient` from `@remotedraw/client` (re-exported by\n`@remotedraw/react`) — backend only, it holds the key. There is no completion\nwebhook. See https://docs.remotedraw.com/docs/api#ai.\n\n## Verification\n\nAfter changes, verify against the customer's project — never assume RemoteDraw's\nown repo scripts exist here.\n\n```sh\nremotedraw doctor # config, SDK deps, REMOTEDRAW_* env\nremotedraw create-input --execute # open a real session, print the join URL\n```\n\nThen run whatever type check and test command the project already defines (for\nexample `npm run typecheck` and `npm test`). Do not invent script names, and do\nnot run `bun run test:api`, `bun run typecheck`, or `bun run ios:kit:test` —\nthose are RemoteDraw's internal monorepo scripts and will not exist in a\ncustomer project.";
|
package/dist/locales/en.d.ts
CHANGED
|
@@ -12,7 +12,6 @@
|
|
|
12
12
|
*/
|
|
13
13
|
export declare const en: {
|
|
14
14
|
readonly "language.word": "Language";
|
|
15
|
-
readonly "language.prompt.helper": "Sets the language of the RemoteDraw CLI. Generated code and files stay in English.";
|
|
16
15
|
readonly "language.saved": "Language set to {language}. Change it any time with: remotedraw language";
|
|
17
16
|
readonly "language.current": "Language: {language} ({locale})";
|
|
18
17
|
readonly "language.source.flag": "Set for this run by --language.";
|
|
@@ -48,6 +47,24 @@ export declare const en: {
|
|
|
48
47
|
readonly "help.command.createInput": "Create or print a test input request payload.";
|
|
49
48
|
readonly "help.command.examples": "List or install example projects.";
|
|
50
49
|
readonly "help.command.agent": "Print or install the RemoteDraw agent skill.";
|
|
50
|
+
readonly "help.command.scan": "Read a codebase and propose where RemoteDraw fits.";
|
|
51
|
+
readonly "help.command.project": "Create a dashboard project and API key without scaffolding.";
|
|
52
|
+
readonly "project.help.usage": " remotedraw project create --name <name> [--key-name <name>] [--env <dir>] [--format json]";
|
|
53
|
+
readonly "project.help.body": "Creates a dashboard project and a project-scoped development API key. Nothing is scaffolded; use it when the integration already has its files.";
|
|
54
|
+
readonly "project.help.env": "Pass --env <dir> to write REMOTEDRAW_API_BASE_URL, REMOTEDRAW_PROJECT_ID, and REMOTEDRAW_API_KEY into <dir>/.env.local instead of printing the key.";
|
|
55
|
+
readonly "project.help.auth": "Needs a signed-in CLI (remotedraw login) or REMOTEDRAW_CLI_TOKEN.";
|
|
56
|
+
readonly "project.error.unknownSubcommand": "Unknown project subcommand: {subcommand}. Expected create.";
|
|
57
|
+
readonly "project.error.nameRequired": "--name is required for project create.";
|
|
58
|
+
readonly "project.created": "Created project {name} ({id}).";
|
|
59
|
+
readonly "project.keyWritten": "Wrote the API key to {path}.";
|
|
60
|
+
readonly "project.keyOnce": "API key (shown once): {key}";
|
|
61
|
+
readonly "project.keyRule": "Keep rd_sk_... keys in backend secrets only; never in browser bundles, mobile apps, or source control.";
|
|
62
|
+
readonly "cloud.warning.clientTree": "src/remotedraw/createRemoteDrawSession.ts reads REMOTEDRAW_API_KEY and must run server-side. Move it to your API route, server action, Convex action, or serverless function before use; a browser bundle must never import it.";
|
|
63
|
+
readonly "scan.help.usage": " remotedraw scan [--path <dir>] [--format json]";
|
|
64
|
+
readonly "scan.help.body": "Detects web, server, and iOS projects, existing RemoteDraw wiring, and where an rd_sk_ key may live; then lists integration options and the product questions to ask the user before building.";
|
|
65
|
+
readonly "scan.help.privacy": "Reads manifests and file names only. Reports env variable names, never values.";
|
|
66
|
+
readonly "scan.help.json": "Pass --format json for the machine-readable report agents should read.";
|
|
67
|
+
readonly "scan.error.missingPath": "Path does not exist: {path}";
|
|
51
68
|
readonly "help.startHere": "Start here:";
|
|
52
69
|
readonly "guide.title": "RemoteDraw integration guide";
|
|
53
70
|
readonly "guide.intro": "Choose the surface your customer already has:";
|
|
@@ -197,7 +214,6 @@ export declare const en: {
|
|
|
197
214
|
readonly "wizard.control.back": "Esc back";
|
|
198
215
|
readonly "wizard.control.quitCtrlC": "Ctrl+C quit";
|
|
199
216
|
readonly "wizard.control.quitQ": "q quit";
|
|
200
|
-
readonly "wizard.control.move": "↑/↓ move";
|
|
201
217
|
readonly "wizard.control.steps": "←/→ steps";
|
|
202
218
|
readonly "wizard.control.choose": "↑/↓ choose";
|
|
203
219
|
readonly "wizard.control.docs": "d open docs";
|
|
@@ -215,7 +231,6 @@ export declare const en: {
|
|
|
215
231
|
readonly "wizard.tagline.setup": "phone → canvas · project setup";
|
|
216
232
|
readonly "wizard.tagline": "phone → canvas";
|
|
217
233
|
readonly "wizard.docs.openFailed": "Could not open the documentation. Use {url}";
|
|
218
|
-
readonly "wizard.docs.button": "[ {label} ↗ ]";
|
|
219
234
|
readonly "new.help.usage.bare": " remotedraw new";
|
|
220
235
|
readonly "new.help.usage.named": " remotedraw new --app-name <name> [options]";
|
|
221
236
|
readonly "new.help.interactive": "Run without options for the interactive setup flow.";
|
|
@@ -283,7 +298,7 @@ export declare const en: {
|
|
|
283
298
|
readonly "doctor.sdk.ok": "{package} is listed in package.json dependencies.";
|
|
284
299
|
readonly "doctor.sdk.missing": "Add {package}@{range} to dependencies or rerun remotedraw init.";
|
|
285
300
|
readonly "doctor.apiKey.ok": "Server-side API key shape looks correct.";
|
|
286
|
-
readonly "doctor.apiKey.missing": "
|
|
301
|
+
readonly "doctor.apiKey.missing": "No rd_sk_... key found in this project's environment (REMOTEDRAW_API_KEY). When you add one, keep it in backend secrets only.";
|
|
287
302
|
readonly "doctor.label.deployment": "deployment";
|
|
288
303
|
readonly "doctor.label.deploymentReachable": "deployment reachable";
|
|
289
304
|
readonly "doctor.label.apiKeyAccepted": "API key accepted";
|
package/dist/locales/en.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/locales/en.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,EAAE
|
|
1
|
+
{"version":3,"file":"en.d.ts","sourceRoot":"","sources":["../../src/locales/en.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AACH,eAAO,MAAM,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAylBL,CAAC"}
|