@remotedraw/cli 0.1.3 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +56 -1
- package/dist/cli.d.ts +15 -39
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1020 -804
- package/dist/cloud.d.ts +13 -0
- package/dist/cloud.d.ts.map +1 -1
- package/dist/cloud.js +61 -14
- package/dist/errors.d.ts +16 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +13 -0
- package/dist/generated/agent-skill.d.ts +2 -0
- package/dist/generated/agent-skill.d.ts.map +1 -0
- package/dist/generated/agent-skill.js +4 -0
- package/dist/i18n.d.ts +74 -0
- package/dist/i18n.d.ts.map +1 -0
- package/dist/i18n.js +178 -0
- package/dist/index.js +17 -1
- package/dist/locales/catalog.d.ts +21 -0
- package/dist/locales/catalog.d.ts.map +1 -0
- package/dist/locales/catalog.js +1 -0
- package/dist/locales/en.d.ts +414 -0
- package/dist/locales/en.d.ts.map +1 -0
- package/dist/locales/en.js +440 -0
- package/dist/locales/index.d.ts +430 -0
- package/dist/locales/index.d.ts.map +1 -0
- package/dist/locales/index.js +27 -0
- package/dist/locales/nl.d.ts +10 -0
- package/dist/locales/nl.d.ts.map +1 -0
- package/dist/locales/nl.js +432 -0
- package/dist/setup-wizard.d.ts +1 -1
- package/dist/setup-wizard.d.ts.map +1 -1
- package/dist/setup-wizard.js +50 -36
- package/dist/telemetry.d.ts +73 -0
- package/dist/telemetry.d.ts.map +1 -0
- package/dist/telemetry.js +145 -0
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +8 -3
- package/package.json +11 -4
package/dist/cli.js
CHANGED
|
@@ -7,23 +7,39 @@ import { fileURLToPath } from "node:url";
|
|
|
7
7
|
import { emitKeypressEvents } from "node:readline";
|
|
8
8
|
import { createInterface } from "node:readline/promises";
|
|
9
9
|
import { createWizardTerminal, runSetupWizard, } from "./setup-wizard.js";
|
|
10
|
-
import { assertEnvCanAcceptProvisioning, currentCliAccount, DEFAULT_REMOTEDRAW_API_BASE_URL, globalConfigPath, loginToRemoteDraw, logoutFromRemoteDraw, provisionRemoteDrawProject, remoteDrawApiBaseUrl, writeProvisionedProjectSetup, } from "./cloud.js";
|
|
10
|
+
import { assertEnvCanAcceptProvisioning, currentCliAccount, DEFAULT_REMOTEDRAW_API_BASE_URL, globalConfigPath, loginToRemoteDraw, logoutFromRemoteDraw, provisionRemoteDrawProject, readStoredLanguage, remoteDrawApiBaseUrl, writeStoredLanguage, writeProvisionedProjectSetup, } from "./cloud.js";
|
|
11
|
+
import { LANGUAGE_ENV_VARS, SOURCE_LOCALE, extractLanguageFlag, languageChoices, languagePromptMessage, localeEndonym, normalizeLocale, resolveLocale, setActiveLocale, supportedLocaleList, SUPPORTED_LOCALES, systemLocaleHint, t, } from "./i18n.js";
|
|
12
|
+
import { CliError, cliError } from "./errors.js";
|
|
13
|
+
import { AGENT_SKILL_MARKDOWN } from "./generated/agent-skill.js";
|
|
14
|
+
import { reportCliCrash } from "./telemetry.js";
|
|
11
15
|
import { checkForUpdate, CLI_PACKAGE_NAME, detectInstallMethod, installMethodDisplay, runInstall, } from "./update.js";
|
|
12
16
|
const packageMetadata = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
|
|
13
17
|
export const CLI_VERSION = typeof packageMetadata.version === "string"
|
|
14
18
|
? packageMetadata.version
|
|
15
19
|
: "unknown";
|
|
16
|
-
|
|
17
|
-
|
|
20
|
+
// Generated manifests may only name packages this repository actually
|
|
21
|
+
// publishes, at a range those published versions satisfy. "latest" used to be
|
|
22
|
+
// written here, which resolved to nothing at all while the SDK packages were
|
|
23
|
+
// private, and would silently accept a future breaking major once they were
|
|
24
|
+
// not. tests/generated-manifest.test.ts cross-checks every range below against
|
|
25
|
+
// the workspace manifests.
|
|
26
|
+
export const REMOTEDRAW_SDK_RANGE = "^0.2.0";
|
|
27
|
+
export const REMOTEDRAW_CLI_RANGE = "^0.2.0";
|
|
28
|
+
export const targetChoices = ["web", "desktop", "ios", "headless"];
|
|
29
|
+
export const senderChoices = [
|
|
18
30
|
"remotedraw-ios",
|
|
19
31
|
"embedded-web",
|
|
20
32
|
"own-ios",
|
|
21
33
|
"headless",
|
|
22
34
|
];
|
|
23
|
-
const sdkChoices = [
|
|
35
|
+
export const sdkChoices = [
|
|
36
|
+
"react",
|
|
37
|
+
"svelte",
|
|
38
|
+
"js",
|
|
39
|
+
"swift",
|
|
40
|
+
"headless",
|
|
41
|
+
];
|
|
24
42
|
const presetChoices = [
|
|
25
|
-
"signature",
|
|
26
|
-
"initials",
|
|
27
43
|
"approval",
|
|
28
44
|
"sketch",
|
|
29
45
|
"photoMarkup",
|
|
@@ -40,7 +56,6 @@ const exampleChoices = [
|
|
|
40
56
|
"ios-owned-sender",
|
|
41
57
|
];
|
|
42
58
|
const packageManagerChoices = ["npm", "pnpm", "yarn", "bun"];
|
|
43
|
-
const apiEndpointChoices = ["deployment", "local"];
|
|
44
59
|
export function createNodeRuntime() {
|
|
45
60
|
const cwd = process.cwd();
|
|
46
61
|
const env = cliEnvironmentForWorkspace(cwd, process.env);
|
|
@@ -97,7 +112,7 @@ async function runChildCommand(command, args) {
|
|
|
97
112
|
async function openExternalUrl(url) {
|
|
98
113
|
const parsed = new URL(url);
|
|
99
114
|
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
100
|
-
throw new Error("
|
|
115
|
+
throw new Error(t("error.httpUrlsOnly"));
|
|
101
116
|
}
|
|
102
117
|
const [command, args] = process.platform === "darwin"
|
|
103
118
|
? ["open", [url]]
|
|
@@ -148,10 +163,10 @@ function createTerminalPrompter(input, output) {
|
|
|
148
163
|
}
|
|
149
164
|
function terminalSelect(input, output, options) {
|
|
150
165
|
if (options.choices.length === 0) {
|
|
151
|
-
throw new Error(
|
|
166
|
+
throw new Error(t("error.noChoices", { message: options.message }));
|
|
152
167
|
}
|
|
153
168
|
if (!input.isTTY || !output.isTTY || !input.setRawMode) {
|
|
154
|
-
throw new Error("
|
|
169
|
+
throw new Error(t("error.interactiveNeedsTty"));
|
|
155
170
|
}
|
|
156
171
|
const defaultIndex = Math.max(0, options.choices.findIndex((choiceOption) => choiceOption.value === options.defaultValue));
|
|
157
172
|
let selectedIndex = defaultIndex;
|
|
@@ -225,7 +240,13 @@ function selectPromptLines(options, selectedIndex) {
|
|
|
225
240
|
const hint = choiceOption.hint ? ` ${style("dim", choiceOption.hint)}` : "";
|
|
226
241
|
lines.push(`${marker} ${label}${hint}`);
|
|
227
242
|
}
|
|
228
|
-
|
|
243
|
+
// Rendered in the active locale, which for the language question itself is
|
|
244
|
+
// English — nothing has been chosen yet at that point.
|
|
245
|
+
lines.push([
|
|
246
|
+
`${style("dim", "↑/↓")} ${t("prompt.control.move")}`,
|
|
247
|
+
`${style("dim", "Enter")} ${t("prompt.control.select")}`,
|
|
248
|
+
`${style("dim", "Ctrl+C")} ${t("prompt.control.quit")}`,
|
|
249
|
+
].join(" · "));
|
|
229
250
|
return lines;
|
|
230
251
|
}
|
|
231
252
|
function clearRendered(output, lineCount) {
|
|
@@ -241,13 +262,61 @@ function style(kind, value) {
|
|
|
241
262
|
};
|
|
242
263
|
return `${codes[kind]}${value}\x1b[0m`;
|
|
243
264
|
}
|
|
265
|
+
/**
|
|
266
|
+
* How the locale for this run was decided. Only `language` reports it, but it
|
|
267
|
+
* has to be recorded where the decision is actually made.
|
|
268
|
+
*/
|
|
269
|
+
let resolvedLocaleSource = "default";
|
|
270
|
+
/**
|
|
271
|
+
* The language the CLI's *chrome* resolved to. It can differ from the active
|
|
272
|
+
* locale on a machine-readable run, which is forced to English — `language`
|
|
273
|
+
* must still report what the reader actually chose.
|
|
274
|
+
*/
|
|
275
|
+
let resolvedChromeLocale = SOURCE_LOCALE;
|
|
244
276
|
export async function runCli(args, runtime = createNodeRuntime()) {
|
|
277
|
+
// The language flag is global, so it is taken out before per-command parsing
|
|
278
|
+
// — which rejects any option the command has not declared.
|
|
279
|
+
const { value: languageFlag, rest: cliArgs } = extractLanguageFlag(args);
|
|
245
280
|
try {
|
|
246
|
-
const
|
|
247
|
-
const
|
|
281
|
+
const stored = await readStoredLanguage(runtime);
|
|
282
|
+
const resolution = resolveLocale({
|
|
283
|
+
flag: languageFlag,
|
|
284
|
+
env: runtime.env,
|
|
285
|
+
stored,
|
|
286
|
+
});
|
|
287
|
+
if (!resolution.ok) {
|
|
288
|
+
// Report the bad value in whatever language the reader already chose,
|
|
289
|
+
// falling back to English when they have not chosen one.
|
|
290
|
+
setActiveLocale(normalizeLocale(stored) ?? SOURCE_LOCALE);
|
|
291
|
+
const message = t("language.unknown", {
|
|
292
|
+
value: resolution.value,
|
|
293
|
+
supported: supportedLocaleList(),
|
|
294
|
+
variable: LANGUAGE_ENV_VARS[0],
|
|
295
|
+
});
|
|
296
|
+
return requestedJsonOutput(args)
|
|
297
|
+
? jsonFailure("language", new Error(message))
|
|
298
|
+
: fail(message);
|
|
299
|
+
}
|
|
300
|
+
// Machine-readable output is a data interchange format, not chrome: it
|
|
301
|
+
// stays English so an agent quoting a label back to its user does not
|
|
302
|
+
// suddenly speak the operator's language. `--language` on the same
|
|
303
|
+
// invocation is a deliberate request and does localize it; an ambient
|
|
304
|
+
// stored choice or environment variable does not.
|
|
305
|
+
const machineReadable = machineReadableOutput(cliArgs);
|
|
306
|
+
const effectiveLocale = machineReadable && resolution.source !== "flag"
|
|
307
|
+
? SOURCE_LOCALE
|
|
308
|
+
: resolution.locale;
|
|
309
|
+
setActiveLocale(effectiveLocale);
|
|
310
|
+
resolvedChromeLocale = resolution.locale;
|
|
311
|
+
resolvedLocaleSource = resolution.source;
|
|
312
|
+
// Unresolved means "never asked". Interactive entry points ask; every
|
|
313
|
+
// other invocation — CI, a pipe, an agent shelling out — stays English.
|
|
314
|
+
const askLanguage = resolution.needsPrompt;
|
|
315
|
+
const command = cliArgs[0];
|
|
316
|
+
const commandArgs = cliArgs.slice(1);
|
|
248
317
|
if (command == null) {
|
|
249
318
|
if (runtime.wizardTerminal) {
|
|
250
|
-
return await setupWizardCommand(runtime);
|
|
319
|
+
return await setupWizardCommand(runtime, askLanguage);
|
|
251
320
|
}
|
|
252
321
|
return ok(mainHelpText());
|
|
253
322
|
}
|
|
@@ -262,6 +331,8 @@ export async function runCli(args, runtime = createNodeRuntime()) {
|
|
|
262
331
|
return ok(guideText());
|
|
263
332
|
case "options":
|
|
264
333
|
return optionsCommand(commandArgs);
|
|
334
|
+
case "language":
|
|
335
|
+
return await languageCommand(commandArgs, runtime);
|
|
265
336
|
case "login":
|
|
266
337
|
return await loginCommand(commandArgs, runtime);
|
|
267
338
|
case "logout":
|
|
@@ -269,7 +340,7 @@ export async function runCli(args, runtime = createNodeRuntime()) {
|
|
|
269
340
|
case "whoami":
|
|
270
341
|
return await whoamiCommand(commandArgs, runtime);
|
|
271
342
|
case "new":
|
|
272
|
-
return await newCommand(commandArgs, runtime);
|
|
343
|
+
return await newCommand(commandArgs, runtime, askLanguage);
|
|
273
344
|
case "init":
|
|
274
345
|
return await initCommand(commandArgs, runtime);
|
|
275
346
|
case "update":
|
|
@@ -286,20 +357,36 @@ export async function runCli(args, runtime = createNodeRuntime()) {
|
|
|
286
357
|
return await agentCommand(commandArgs, runtime);
|
|
287
358
|
default:
|
|
288
359
|
return requestedJsonOutput(args)
|
|
289
|
-
? jsonFailure(command, new Error(
|
|
290
|
-
: fail(
|
|
360
|
+
? jsonFailure(command, new Error(t("error.unknownCommand", { command })))
|
|
361
|
+
: fail(t("error.unknownCommand", { command }));
|
|
291
362
|
}
|
|
292
363
|
}
|
|
293
364
|
catch (error) {
|
|
365
|
+
// Only genuine faults land here: a bad flag or an unknown command returns a
|
|
366
|
+
// `fail(...)` result instead of throwing, so this path is not user error.
|
|
367
|
+
const command = cliArgs[0] ?? "help";
|
|
368
|
+
await reportCliCrash(error, command, cliTelemetryEnvironment(runtime));
|
|
294
369
|
return requestedJsonOutput(args)
|
|
295
|
-
? jsonFailure(
|
|
370
|
+
? jsonFailure(command, error)
|
|
296
371
|
: fail(errorMessage(error));
|
|
297
372
|
}
|
|
298
373
|
}
|
|
299
|
-
|
|
374
|
+
function cliTelemetryEnvironment(runtime) {
|
|
375
|
+
return {
|
|
376
|
+
env: runtime.env,
|
|
377
|
+
version: CLI_VERSION,
|
|
378
|
+
platform: process.platform,
|
|
379
|
+
nodeVersion: process.versions.node,
|
|
380
|
+
fetch: runtime.fetch,
|
|
381
|
+
};
|
|
382
|
+
}
|
|
383
|
+
async function setupWizardCommand(runtime, askLanguage) {
|
|
300
384
|
const terminal = runtime.wizardTerminal;
|
|
301
385
|
if (!terminal)
|
|
302
386
|
return ok(mainHelpText());
|
|
387
|
+
// Before login, before the first field: nothing the reader has to understand
|
|
388
|
+
// is printed until the language is settled.
|
|
389
|
+
await ensureLanguageChosen(runtime, askLanguage);
|
|
303
390
|
if (!cloudSetupDisabled(runtime)) {
|
|
304
391
|
const apiBaseUrl = remoteDrawApiBaseUrl(runtime.env);
|
|
305
392
|
await loginToRemoteDraw(runtime, apiBaseUrl);
|
|
@@ -324,6 +411,121 @@ async function setupWizardCommand(runtime) {
|
|
|
324
411
|
});
|
|
325
412
|
return { exitCode: result.exitCode, stdout: "" };
|
|
326
413
|
}
|
|
414
|
+
/**
|
|
415
|
+
* The language question, asked once and only where a human is already being
|
|
416
|
+
* prompted. The question itself is built from every locale's own word for
|
|
417
|
+
* "Language" and lists endonyms, so it reads before any language is chosen.
|
|
418
|
+
*/
|
|
419
|
+
async function ensureLanguageChosen(runtime, askLanguage) {
|
|
420
|
+
if (!askLanguage)
|
|
421
|
+
return;
|
|
422
|
+
const prompts = runtime.prompts;
|
|
423
|
+
// No TTY, no question: piped and CI runs must never block on this.
|
|
424
|
+
if (!prompts || runtime.isInteractive !== true)
|
|
425
|
+
return;
|
|
426
|
+
let chosen;
|
|
427
|
+
try {
|
|
428
|
+
chosen = await prompts.select({
|
|
429
|
+
message: languagePromptMessage(),
|
|
430
|
+
defaultValue: systemLocaleHint(runtime.env) ?? SOURCE_LOCALE,
|
|
431
|
+
choices: languageChoices(),
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
catch {
|
|
435
|
+
// A prompter that cannot run (stdin is not a real terminal after all)
|
|
436
|
+
// means English, never a failed command over a preference question.
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
const locale = normalizeLocale(chosen) ?? SOURCE_LOCALE;
|
|
440
|
+
await applyLanguage(runtime, locale);
|
|
441
|
+
}
|
|
442
|
+
/** Sets the locale for the rest of this run and remembers it for later ones. */
|
|
443
|
+
async function applyLanguage(runtime, locale) {
|
|
444
|
+
setActiveLocale(locale);
|
|
445
|
+
resolvedChromeLocale = locale;
|
|
446
|
+
resolvedLocaleSource = "prompt";
|
|
447
|
+
try {
|
|
448
|
+
await writeStoredLanguage(runtime, locale);
|
|
449
|
+
}
|
|
450
|
+
catch (error) {
|
|
451
|
+
// An unwritable config is worth saying out loud, but it must not abort the
|
|
452
|
+
// command the reader actually asked for.
|
|
453
|
+
runtime.notify?.(t("language.saveFailed", {
|
|
454
|
+
reason: errorMessage(error),
|
|
455
|
+
language: localeEndonym(locale),
|
|
456
|
+
}));
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
async function languageCommand(args, runtime) {
|
|
460
|
+
const parsed = parseArgs(args, { values: ["format"], booleans: ["help"] });
|
|
461
|
+
if (parsed.booleans.has("help"))
|
|
462
|
+
return ok(languageHelpText());
|
|
463
|
+
const format = outputFormat(parsed);
|
|
464
|
+
const requested = parsed.positionals[0];
|
|
465
|
+
if (requested != null) {
|
|
466
|
+
const locale = normalizeLocale(requested);
|
|
467
|
+
if (!locale) {
|
|
468
|
+
const message = t("language.unknown", {
|
|
469
|
+
value: requested,
|
|
470
|
+
supported: supportedLocaleList(),
|
|
471
|
+
variable: LANGUAGE_ENV_VARS[0],
|
|
472
|
+
});
|
|
473
|
+
return format === "json"
|
|
474
|
+
? jsonFailure("language", new Error(message))
|
|
475
|
+
: fail(message);
|
|
476
|
+
}
|
|
477
|
+
await applyLanguage(runtime, locale);
|
|
478
|
+
return format === "json"
|
|
479
|
+
? jsonResult({ ok: true, command: "language", language: locale })
|
|
480
|
+
: ok(t("language.saved", { language: localeEndonym(locale) }));
|
|
481
|
+
}
|
|
482
|
+
if (format !== "json" && runtime.prompts && runtime.isInteractive === true) {
|
|
483
|
+
const chosen = await runtime.prompts.select({
|
|
484
|
+
message: languagePromptMessage(),
|
|
485
|
+
defaultValue: resolvedChromeLocale,
|
|
486
|
+
choices: languageChoices(),
|
|
487
|
+
});
|
|
488
|
+
const locale = normalizeLocale(chosen) ?? SOURCE_LOCALE;
|
|
489
|
+
await applyLanguage(runtime, locale);
|
|
490
|
+
return ok(t("language.saved", { language: localeEndonym(locale) }));
|
|
491
|
+
}
|
|
492
|
+
const current = resolvedChromeLocale;
|
|
493
|
+
if (format === "json") {
|
|
494
|
+
return jsonResult({
|
|
495
|
+
ok: true,
|
|
496
|
+
command: "language",
|
|
497
|
+
language: current,
|
|
498
|
+
source: resolvedLocaleSource,
|
|
499
|
+
available: SUPPORTED_LOCALES.map((locale) => ({
|
|
500
|
+
code: locale,
|
|
501
|
+
endonym: localeEndonym(locale),
|
|
502
|
+
})),
|
|
503
|
+
});
|
|
504
|
+
}
|
|
505
|
+
return ok([
|
|
506
|
+
t("language.current", {
|
|
507
|
+
language: localeEndonym(current),
|
|
508
|
+
locale: current,
|
|
509
|
+
}),
|
|
510
|
+
languageSourceLine(runtime),
|
|
511
|
+
"",
|
|
512
|
+
t("language.available"),
|
|
513
|
+
...SUPPORTED_LOCALES.map((locale) => ` ${locale.padEnd(6)} ${localeEndonym(locale)}`),
|
|
514
|
+
].join("\n"));
|
|
515
|
+
}
|
|
516
|
+
function languageSourceLine(runtime) {
|
|
517
|
+
if (resolvedLocaleSource === "flag")
|
|
518
|
+
return t("language.source.flag");
|
|
519
|
+
if (resolvedLocaleSource === "env") {
|
|
520
|
+
const variable = LANGUAGE_ENV_VARS.find((name) => runtime.env[name]?.trim()) ??
|
|
521
|
+
LANGUAGE_ENV_VARS[0];
|
|
522
|
+
return t("language.source.env", { variable });
|
|
523
|
+
}
|
|
524
|
+
if (resolvedLocaleSource === "config" || resolvedLocaleSource === "prompt") {
|
|
525
|
+
return t("language.source.config", { path: globalConfigPath(runtime) });
|
|
526
|
+
}
|
|
527
|
+
return t("language.source.default");
|
|
528
|
+
}
|
|
327
529
|
function ok(stdout) {
|
|
328
530
|
return { exitCode: 0, stdout: ensureTrailingNewline(stdout) };
|
|
329
531
|
}
|
|
@@ -335,6 +537,12 @@ function jsonResult(value, exitCode = 0) {
|
|
|
335
537
|
}
|
|
336
538
|
function jsonFailure(command, error) {
|
|
337
539
|
const message = errorMessage(error);
|
|
540
|
+
// A declared code survives translation; the substring heuristic below is
|
|
541
|
+
// only a fallback for errors raised outside the catalog (a failed fetch, a
|
|
542
|
+
// filesystem fault) and is matched against English internals.
|
|
543
|
+
if (error instanceof CliError) {
|
|
544
|
+
return jsonResult({ ok: false, command, error: { code: error.code, message } }, 1);
|
|
545
|
+
}
|
|
338
546
|
const code = message.includes("required")
|
|
339
547
|
? "MISSING_ARGUMENT"
|
|
340
548
|
: message.includes(" requires ") || message.includes("only supported")
|
|
@@ -348,6 +556,14 @@ function jsonFailure(command, error) {
|
|
|
348
556
|
: "COMMAND_FAILED";
|
|
349
557
|
return jsonResult({ ok: false, command, error: { code, message } }, 1);
|
|
350
558
|
}
|
|
559
|
+
/**
|
|
560
|
+
* True when this invocation asks for output meant to be parsed rather than
|
|
561
|
+
* read: `--format json` on any command, or `create-input --json`.
|
|
562
|
+
*/
|
|
563
|
+
function machineReadableOutput(args) {
|
|
564
|
+
return (requestedJsonOutput(args) ||
|
|
565
|
+
args.some((argument) => argument === "--json"));
|
|
566
|
+
}
|
|
351
567
|
function requestedJsonOutput(args) {
|
|
352
568
|
return args.some((argument, index) => argument === "--format=json" ||
|
|
353
569
|
(argument === "--format" && args[index + 1] === "json"));
|
|
@@ -365,32 +581,34 @@ function errorMessage(error) {
|
|
|
365
581
|
return error instanceof Error ? error.message : String(error);
|
|
366
582
|
}
|
|
367
583
|
function mainHelpText() {
|
|
584
|
+
const command = (name, description) => ` ${name.padEnd(14)} ${description}`;
|
|
368
585
|
return [
|
|
369
|
-
"
|
|
370
|
-
"",
|
|
371
|
-
"
|
|
372
|
-
"
|
|
373
|
-
"",
|
|
374
|
-
"
|
|
375
|
-
"
|
|
376
|
-
"
|
|
377
|
-
"
|
|
378
|
-
"
|
|
379
|
-
"
|
|
380
|
-
"
|
|
381
|
-
"
|
|
382
|
-
"
|
|
383
|
-
"
|
|
384
|
-
"
|
|
385
|
-
"
|
|
386
|
-
"
|
|
387
|
-
"
|
|
388
|
-
"",
|
|
389
|
-
"
|
|
586
|
+
t("help.title"),
|
|
587
|
+
"",
|
|
588
|
+
t("word.usage"),
|
|
589
|
+
t("help.usage.main"),
|
|
590
|
+
"",
|
|
591
|
+
t("word.commands"),
|
|
592
|
+
command("guide", t("help.command.guide")),
|
|
593
|
+
command("options", t("help.command.options")),
|
|
594
|
+
command("language", t("help.command.language")),
|
|
595
|
+
command("login", t("help.command.login")),
|
|
596
|
+
command("logout", t("help.command.logout")),
|
|
597
|
+
command("whoami", t("help.command.whoami")),
|
|
598
|
+
command("new", t("help.command.new")),
|
|
599
|
+
command("init", t("help.command.init")),
|
|
600
|
+
command("dev", t("help.command.dev")),
|
|
601
|
+
command("doctor", t("help.command.doctor")),
|
|
602
|
+
command("update", t("help.command.update")),
|
|
603
|
+
command("create-input", t("help.command.createInput")),
|
|
604
|
+
command("examples", t("help.command.examples")),
|
|
605
|
+
command("agent", t("help.command.agent")),
|
|
606
|
+
"",
|
|
607
|
+
t("help.startHere"),
|
|
390
608
|
" remotedraw guide",
|
|
391
609
|
" remotedraw login",
|
|
392
610
|
" remotedraw new",
|
|
393
|
-
" remotedraw new --app-name
|
|
611
|
+
" remotedraw new --app-name MyApp --target web --sender remotedraw-ios --sdk react",
|
|
394
612
|
" remotedraw init --target web --sender embedded-web --sdk react",
|
|
395
613
|
].join("\n");
|
|
396
614
|
}
|
|
@@ -405,39 +623,39 @@ function optionsCommand(args) {
|
|
|
405
623
|
if (outputFormat(parsed) === "json")
|
|
406
624
|
return jsonResult(catalog);
|
|
407
625
|
return ok([
|
|
408
|
-
"
|
|
626
|
+
t("options.title"),
|
|
409
627
|
"",
|
|
410
628
|
...catalog.fields.flatMap((field) => [
|
|
411
629
|
`${field.label} (${field.flag})`,
|
|
412
630
|
...field.options.map((option) => ` ${option.value}: ${option.details.summary}`),
|
|
413
631
|
"",
|
|
414
632
|
]),
|
|
415
|
-
"
|
|
633
|
+
t("options.machineReadable"),
|
|
416
634
|
].join("\n"));
|
|
417
635
|
}
|
|
418
636
|
function guideText() {
|
|
419
637
|
return [
|
|
420
|
-
"
|
|
638
|
+
t("guide.title"),
|
|
421
639
|
"",
|
|
422
|
-
"
|
|
423
|
-
"
|
|
424
|
-
" remotedraw init --target web --sender remotedraw-ios --sdk react --preset
|
|
640
|
+
t("guide.intro"),
|
|
641
|
+
t("guide.path.webIos"),
|
|
642
|
+
" remotedraw init --target web --sender remotedraw-ios --sdk react --preset sketch",
|
|
425
643
|
"",
|
|
426
|
-
"
|
|
644
|
+
t("guide.path.webOwned"),
|
|
427
645
|
" remotedraw init --target web --sender embedded-web --sdk react --preset sketch",
|
|
428
646
|
"",
|
|
429
|
-
"
|
|
430
|
-
" remotedraw init --target web --sender remotedraw-ios --sdk svelte --preset
|
|
647
|
+
t("guide.path.svelteIos"),
|
|
648
|
+
" remotedraw init --target web --sender remotedraw-ios --sdk svelte --preset sketch",
|
|
431
649
|
"",
|
|
432
|
-
"
|
|
650
|
+
t("guide.path.desktop"),
|
|
433
651
|
" remotedraw init --target desktop --sender remotedraw-ios --sdk js --preset sketch",
|
|
434
652
|
"",
|
|
435
|
-
"
|
|
653
|
+
t("guide.path.ownIos"),
|
|
436
654
|
" remotedraw init --target ios --sender own-ios --sdk swift --preset sketch",
|
|
437
655
|
"",
|
|
438
|
-
"
|
|
439
|
-
"
|
|
440
|
-
"
|
|
656
|
+
t("guide.note.keys"),
|
|
657
|
+
t("guide.note.provisioning"),
|
|
658
|
+
t("guide.note.offline"),
|
|
441
659
|
].join("\n");
|
|
442
660
|
}
|
|
443
661
|
async function loginCommand(args, runtime) {
|
|
@@ -455,11 +673,9 @@ async function loginCommand(args, runtime) {
|
|
|
455
673
|
...(deviceName == null ? {} : { deviceName }),
|
|
456
674
|
});
|
|
457
675
|
return ok([
|
|
458
|
-
result.alreadyLoggedIn
|
|
459
|
-
? "Already logged in."
|
|
460
|
-
: "RemoteDraw login complete.",
|
|
676
|
+
result.alreadyLoggedIn ? t("login.already") : t("login.complete"),
|
|
461
677
|
accountSummary(result.account),
|
|
462
|
-
|
|
678
|
+
t("login.credential", { path: globalConfigPath(runtime) }),
|
|
463
679
|
].join("\n"));
|
|
464
680
|
}
|
|
465
681
|
async function logoutCommand(args, runtime) {
|
|
@@ -472,8 +688,8 @@ async function logoutCommand(args, runtime) {
|
|
|
472
688
|
const apiBaseUrl = remoteDrawApiBaseUrl(runtime.env, readString(parsed, "api-base-url"));
|
|
473
689
|
const result = await logoutFromRemoteDraw(runtime, apiBaseUrl);
|
|
474
690
|
return ok(result.hadCredential
|
|
475
|
-
?
|
|
476
|
-
:
|
|
691
|
+
? t("logout.done", { url: apiBaseUrl })
|
|
692
|
+
: t("logout.none", { url: apiBaseUrl }));
|
|
477
693
|
}
|
|
478
694
|
async function whoamiCommand(args, runtime) {
|
|
479
695
|
const parsed = parseArgs(args, {
|
|
@@ -485,15 +701,15 @@ async function whoamiCommand(args, runtime) {
|
|
|
485
701
|
const apiBaseUrl = remoteDrawApiBaseUrl(runtime.env, readString(parsed, "api-base-url"));
|
|
486
702
|
const account = await currentCliAccount(runtime, apiBaseUrl);
|
|
487
703
|
if (!account) {
|
|
488
|
-
return fail(
|
|
704
|
+
return fail(t("whoami.notLoggedIn", { url: apiBaseUrl }));
|
|
489
705
|
}
|
|
490
|
-
return ok([accountSummary(account),
|
|
706
|
+
return ok([accountSummary(account), t("whoami.api", { url: apiBaseUrl })].join("\n"));
|
|
491
707
|
}
|
|
492
708
|
function accountSummary(account) {
|
|
493
|
-
const identity = account.email || account.name || "
|
|
709
|
+
const identity = account.email || account.name || t("whoami.anonymous");
|
|
494
710
|
return `${identity} (${account.tenantSlug})`;
|
|
495
711
|
}
|
|
496
|
-
async function newCommand(args, runtime) {
|
|
712
|
+
async function newCommand(args, runtime, askLanguage = false) {
|
|
497
713
|
const parsed = parseArgs(args, {
|
|
498
714
|
values: [
|
|
499
715
|
"app-name",
|
|
@@ -511,8 +727,13 @@ async function newCommand(args, runtime) {
|
|
|
511
727
|
if (parsed.booleans.has("help"))
|
|
512
728
|
return ok(newHelpText());
|
|
513
729
|
const format = outputFormat(parsed);
|
|
514
|
-
const
|
|
515
|
-
shouldPromptForNewProject(parsed, runtime)
|
|
730
|
+
const interactive = !parsed.booleans.has("non-interactive") &&
|
|
731
|
+
shouldPromptForNewProject(parsed, runtime);
|
|
732
|
+
// Only ask when this run was going to prompt anyway; a fully flagged
|
|
733
|
+
// `new` stays as non-interactive as the caller wrote it.
|
|
734
|
+
if (interactive)
|
|
735
|
+
await ensureLanguageChosen(runtime, askLanguage);
|
|
736
|
+
const plan = interactive
|
|
516
737
|
? await promptForNewProject(parsed, runtime)
|
|
517
738
|
: planFromArgs(parsed, {
|
|
518
739
|
appNameRequired: true,
|
|
@@ -537,7 +758,7 @@ async function newCommand(args, runtime) {
|
|
|
537
758
|
}
|
|
538
759
|
return format === "json"
|
|
539
760
|
? jsonResult(scaffoldJsonResult("new", plan, outputDir, written, parsed, provisioning, offline, dryRun))
|
|
540
|
-
: ok(scaffoldSummary("
|
|
761
|
+
: ok(scaffoldSummary(t("scaffold.verb.created"), plan, outputDir, written, dryRun) +
|
|
541
762
|
cloudSetupSummary(provisioning, offline, dryRun));
|
|
542
763
|
}
|
|
543
764
|
export async function createRemoteDrawProject(plan, outputDir, runtime, options = {}) {
|
|
@@ -594,7 +815,7 @@ async function initCommand(args, runtime) {
|
|
|
594
815
|
}
|
|
595
816
|
return format === "json"
|
|
596
817
|
? jsonResult(scaffoldJsonResult("init", plan, outputDir, written, parsed, provisioning, offline, dryRun))
|
|
597
|
-
: ok(scaffoldSummary("
|
|
818
|
+
: ok(scaffoldSummary(t("scaffold.verb.initialized"), plan, outputDir, written, dryRun) +
|
|
598
819
|
cloudSetupSummary(provisioning, offline, dryRun));
|
|
599
820
|
}
|
|
600
821
|
function cloudSetupDisabled(runtime) {
|
|
@@ -616,16 +837,16 @@ function cloudSetupSummary(provisioning, offline, dryRun) {
|
|
|
616
837
|
if (dryRun)
|
|
617
838
|
return "";
|
|
618
839
|
if (offline) {
|
|
619
|
-
return
|
|
840
|
+
return `\n\n${t("cloud.skipped")}`;
|
|
620
841
|
}
|
|
621
842
|
if (!provisioning)
|
|
622
843
|
return "";
|
|
623
844
|
return [
|
|
624
845
|
"",
|
|
625
846
|
"",
|
|
626
|
-
|
|
627
|
-
"
|
|
628
|
-
|
|
847
|
+
t("cloud.dashboardProject", { url: provisioning.project.dashboardUrl }),
|
|
848
|
+
t("cloud.devKey"),
|
|
849
|
+
t("cloud.projectId", { id: provisioning.project.id }),
|
|
629
850
|
].join("\n");
|
|
630
851
|
}
|
|
631
852
|
const DOCTOR_PROBE_TIMEOUT_MS = 8_000;
|
|
@@ -634,7 +855,7 @@ function probeFailureMessage(error) {
|
|
|
634
855
|
}
|
|
635
856
|
async function probeApiHealth(runtime, apiBaseUrl) {
|
|
636
857
|
if (!runtime.fetch) {
|
|
637
|
-
return { ok: false, message: "
|
|
858
|
+
return { ok: false, message: t("doctor.probe.noNetwork") };
|
|
638
859
|
}
|
|
639
860
|
try {
|
|
640
861
|
const response = await runtime.fetch(`${apiBaseUrl}/health`, {
|
|
@@ -657,7 +878,7 @@ async function probeApiHealth(runtime, apiBaseUrl) {
|
|
|
657
878
|
*/
|
|
658
879
|
async function probeApiKey(runtime, apiBaseUrl, apiKey) {
|
|
659
880
|
if (!runtime.fetch) {
|
|
660
|
-
return { ok: false, message: "
|
|
881
|
+
return { ok: false, message: t("doctor.probe.noNetworkKey") };
|
|
661
882
|
}
|
|
662
883
|
try {
|
|
663
884
|
const response = await runtime.fetch(`${apiBaseUrl}/v1/sessions/list`, {
|
|
@@ -677,7 +898,7 @@ async function probeApiKey(runtime, apiBaseUrl, apiKey) {
|
|
|
677
898
|
catch {
|
|
678
899
|
return {
|
|
679
900
|
ok: false,
|
|
680
|
-
message:
|
|
901
|
+
message: t("doctor.probe.nonJson", { status: response.status }),
|
|
681
902
|
};
|
|
682
903
|
}
|
|
683
904
|
if (!response.ok) {
|
|
@@ -687,7 +908,7 @@ async function probeApiKey(runtime, apiBaseUrl, apiKey) {
|
|
|
687
908
|
typeof payload.error?.message ===
|
|
688
909
|
"string"
|
|
689
910
|
? payload.error.message
|
|
690
|
-
:
|
|
911
|
+
: t("doctor.probe.rejected", { status: response.status });
|
|
691
912
|
return { ok: false, message };
|
|
692
913
|
}
|
|
693
914
|
const items = typeof payload === "object" && payload !== null && "items" in payload
|
|
@@ -718,13 +939,19 @@ async function updateCommand(args, runtime) {
|
|
|
718
939
|
// ambient opt-outs that only silence the passive notice.
|
|
719
940
|
const check = await checkForUpdate(runtime, CLI_VERSION, { force: true });
|
|
720
941
|
if (!check) {
|
|
721
|
-
const message =
|
|
942
|
+
const message = t("update.unreachable", {
|
|
943
|
+
package: CLI_PACKAGE_NAME,
|
|
944
|
+
command,
|
|
945
|
+
});
|
|
722
946
|
return format === "json"
|
|
723
947
|
? jsonFailure("update", new Error(message))
|
|
724
948
|
: fail(message);
|
|
725
949
|
}
|
|
726
950
|
if (!check.updateAvailable) {
|
|
727
|
-
const message =
|
|
951
|
+
const message = t("update.alreadyLatest", {
|
|
952
|
+
package: CLI_PACKAGE_NAME,
|
|
953
|
+
version: check.current,
|
|
954
|
+
});
|
|
728
955
|
return format === "json"
|
|
729
956
|
? jsonResult({ ok: true, command: "update", ...check, updated: false })
|
|
730
957
|
: ok(message);
|
|
@@ -732,12 +959,18 @@ async function updateCommand(args, runtime) {
|
|
|
732
959
|
if (checkOnly || method.ephemeral) {
|
|
733
960
|
const message = method.ephemeral
|
|
734
961
|
? [
|
|
735
|
-
|
|
736
|
-
|
|
962
|
+
t("update.available", {
|
|
963
|
+
current: check.current,
|
|
964
|
+
latest: check.latest,
|
|
965
|
+
}),
|
|
966
|
+
t("update.ephemeral", { package: CLI_PACKAGE_NAME }),
|
|
737
967
|
].join("\n")
|
|
738
968
|
: [
|
|
739
|
-
|
|
740
|
-
|
|
969
|
+
t("update.available", {
|
|
970
|
+
current: check.current,
|
|
971
|
+
latest: check.latest,
|
|
972
|
+
}),
|
|
973
|
+
t("update.runToInstall", { command }),
|
|
741
974
|
].join("\n");
|
|
742
975
|
return format === "json"
|
|
743
976
|
? jsonResult({
|
|
@@ -752,7 +985,11 @@ async function updateCommand(args, runtime) {
|
|
|
752
985
|
}
|
|
753
986
|
const install = await runInstall(runtime, method);
|
|
754
987
|
if (!install.ran) {
|
|
755
|
-
const message =
|
|
988
|
+
const message = t("update.availableRun", {
|
|
989
|
+
current: check.current,
|
|
990
|
+
latest: check.latest,
|
|
991
|
+
command,
|
|
992
|
+
});
|
|
756
993
|
return format === "json"
|
|
757
994
|
? jsonResult({
|
|
758
995
|
ok: true,
|
|
@@ -764,7 +1001,10 @@ async function updateCommand(args, runtime) {
|
|
|
764
1001
|
: ok(message);
|
|
765
1002
|
}
|
|
766
1003
|
if (install.exitCode !== 0) {
|
|
767
|
-
const message =
|
|
1004
|
+
const message = t("update.failed", {
|
|
1005
|
+
command,
|
|
1006
|
+
code: install.exitCode,
|
|
1007
|
+
});
|
|
768
1008
|
return format === "json"
|
|
769
1009
|
? jsonFailure("update", new Error(message))
|
|
770
1010
|
: fail(message);
|
|
@@ -777,7 +1017,10 @@ async function updateCommand(args, runtime) {
|
|
|
777
1017
|
updated: true,
|
|
778
1018
|
installCommand: command,
|
|
779
1019
|
})
|
|
780
|
-
: ok(
|
|
1020
|
+
: ok(t("update.done", {
|
|
1021
|
+
package: CLI_PACKAGE_NAME,
|
|
1022
|
+
version: check.latest,
|
|
1023
|
+
}));
|
|
781
1024
|
}
|
|
782
1025
|
async function doctorCommand(args, runtime) {
|
|
783
1026
|
const parsed = parseArgs(args, {
|
|
@@ -796,28 +1039,29 @@ async function doctorCommand(args, runtime) {
|
|
|
796
1039
|
const apiBaseUrl = resolveDoctorApiBaseUrl(projectEnv, readString(parsed, "api-base-url"));
|
|
797
1040
|
const normalizedEnvApiBaseUrl = apiBaseUrl.state === "ok" ? apiBaseUrl.url : undefined;
|
|
798
1041
|
const envApiKey = projectEnv.REMOTEDRAW_API_KEY;
|
|
799
|
-
const lines = ["
|
|
1042
|
+
const lines = [t("doctor.title"), ""];
|
|
800
1043
|
const checks = [];
|
|
801
1044
|
const addCheck = (state, label, message) => {
|
|
802
1045
|
checks.push({ state, label, message });
|
|
803
1046
|
lines.push(statusLine(state, label, message));
|
|
804
1047
|
};
|
|
805
1048
|
const hasConfig = await runtime.exists(configPath);
|
|
806
|
-
addCheck(hasConfig ? "ok" : "warn", "remotedraw.config.json", hasConfig
|
|
807
|
-
? "Project is linked to a local integration profile."
|
|
808
|
-
: "Run remotedraw init to create one.");
|
|
1049
|
+
addCheck(hasConfig ? "ok" : "warn", "remotedraw.config.json", hasConfig ? t("doctor.config.ok") : t("doctor.config.missing"));
|
|
809
1050
|
const packageJson = (await runtime.exists(packagePath))
|
|
810
1051
|
? await readJsonFile(packagePath, runtime)
|
|
811
1052
|
: null;
|
|
812
|
-
addCheck(packageJson ? "ok" : "warn", "package.json", packageJson
|
|
813
|
-
? "Package metadata found."
|
|
814
|
-
: "No package.json found in this directory.");
|
|
1053
|
+
addCheck(packageJson ? "ok" : "warn", "package.json", packageJson ? t("doctor.package.ok") : t("doctor.package.missing"));
|
|
815
1054
|
const config = hasConfig ? await readJsonFile(configPath, runtime) : null;
|
|
816
1055
|
const sdk = stringFromRecord(config, "sdk");
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
1056
|
+
const sdkPackage = sdkPackageName(sdk);
|
|
1057
|
+
if (sdkPackage) {
|
|
1058
|
+
const installed = hasDependency(packageJson, sdkPackage);
|
|
1059
|
+
addCheck(installed ? "ok" : "warn", sdkPackage, installed
|
|
1060
|
+
? t("doctor.sdk.ok", { package: sdkPackage })
|
|
1061
|
+
: t("doctor.sdk.missing", {
|
|
1062
|
+
package: sdkPackage,
|
|
1063
|
+
range: REMOTEDRAW_SDK_RANGE,
|
|
1064
|
+
}));
|
|
821
1065
|
}
|
|
822
1066
|
addCheck(apiBaseUrl.state === "ok"
|
|
823
1067
|
? "ok"
|
|
@@ -825,47 +1069,55 @@ async function doctorCommand(args, runtime) {
|
|
|
825
1069
|
? "warn"
|
|
826
1070
|
: "error", "REMOTEDRAW_API_BASE_URL", doctorApiBaseUrlMessage(apiBaseUrl));
|
|
827
1071
|
addCheck(envApiKey?.startsWith("rd_sk_") ? "ok" : "warn", "REMOTEDRAW_API_KEY", envApiKey?.startsWith("rd_sk_")
|
|
828
|
-
? "
|
|
829
|
-
: "
|
|
1072
|
+
? t("doctor.apiKey.ok")
|
|
1073
|
+
: t("doctor.apiKey.missing"));
|
|
830
1074
|
// The checks above only prove the strings look right. A revoked key, a URL
|
|
831
1075
|
// pointing at the wrong deployment, and an exhausted credit balance all pass
|
|
832
1076
|
// them, so doctor also asks the deployment itself.
|
|
833
1077
|
if (offline) {
|
|
834
|
-
addCheck("warn", "deployment", "
|
|
1078
|
+
addCheck("warn", t("doctor.label.deployment"), t("doctor.network.offline"));
|
|
835
1079
|
}
|
|
836
1080
|
else if (!normalizedEnvApiBaseUrl) {
|
|
837
|
-
addCheck("warn", "deployment", "
|
|
1081
|
+
addCheck("warn", t("doctor.label.deployment"), t("doctor.network.noOrigin"));
|
|
838
1082
|
}
|
|
839
1083
|
else {
|
|
840
1084
|
const health = await probeApiHealth(runtime, normalizedEnvApiBaseUrl);
|
|
841
|
-
addCheck(health.ok ? "ok" : "error", "
|
|
842
|
-
?
|
|
843
|
-
:
|
|
1085
|
+
addCheck(health.ok ? "ok" : "error", t("doctor.label.deploymentReachable"), health.ok
|
|
1086
|
+
? t("doctor.health.ok", { url: normalizedEnvApiBaseUrl })
|
|
1087
|
+
: t("doctor.health.failed", {
|
|
1088
|
+
url: normalizedEnvApiBaseUrl,
|
|
1089
|
+
reason: health.message,
|
|
1090
|
+
}));
|
|
844
1091
|
if (!envApiKey?.startsWith("rd_sk_")) {
|
|
845
|
-
addCheck("warn", "
|
|
1092
|
+
addCheck("warn", t("doctor.label.apiKeyAccepted"), t("doctor.key.skippedNoKey"));
|
|
846
1093
|
}
|
|
847
1094
|
else if (!health.ok) {
|
|
848
|
-
addCheck("warn", "
|
|
1095
|
+
addCheck("warn", t("doctor.label.apiKeyAccepted"), t("doctor.key.skippedNoHealth"));
|
|
849
1096
|
}
|
|
850
1097
|
else {
|
|
851
1098
|
const probe = await probeApiKey(runtime, normalizedEnvApiBaseUrl, envApiKey);
|
|
852
|
-
addCheck(probe.ok ? "ok" : "error", "
|
|
853
|
-
?
|
|
1099
|
+
addCheck(probe.ok ? "ok" : "error", t("doctor.label.apiKeyAccepted"), probe.ok
|
|
1100
|
+
? t(probe.sessionCount === 1
|
|
1101
|
+
? "doctor.key.liveOne"
|
|
1102
|
+
: "doctor.key.liveMany", { count: probe.sessionCount })
|
|
854
1103
|
: probe.message);
|
|
855
1104
|
}
|
|
856
1105
|
}
|
|
857
1106
|
// A stale global install is the quietest failure mode there is: npm pins the
|
|
858
1107
|
// binary at install time and never revisits it, so doctor has to say so.
|
|
859
1108
|
if (offline) {
|
|
860
|
-
addCheck("warn", "
|
|
1109
|
+
addCheck("warn", t("doctor.label.cliVersion"), t("doctor.version.offline", { version: CLI_VERSION }));
|
|
861
1110
|
}
|
|
862
1111
|
else {
|
|
863
1112
|
const update = await checkForUpdate(runtime, CLI_VERSION, { force: true });
|
|
864
|
-
addCheck(!update || !update.updateAvailable ? "ok" : "warn", "
|
|
865
|
-
?
|
|
1113
|
+
addCheck(!update || !update.updateAvailable ? "ok" : "warn", t("doctor.label.cliVersion"), !update
|
|
1114
|
+
? t("doctor.version.unknown", { version: CLI_VERSION })
|
|
866
1115
|
: update.updateAvailable
|
|
867
|
-
?
|
|
868
|
-
|
|
1116
|
+
? t("doctor.version.stale", {
|
|
1117
|
+
current: update.current,
|
|
1118
|
+
latest: update.latest,
|
|
1119
|
+
})
|
|
1120
|
+
: t("doctor.version.latest", { current: update.current }));
|
|
869
1121
|
}
|
|
870
1122
|
return format === "json"
|
|
871
1123
|
? jsonResult({
|
|
@@ -888,23 +1140,22 @@ async function devCommand(args, runtime) {
|
|
|
888
1140
|
const projectEnv = await environmentForProject(dir, runtime);
|
|
889
1141
|
const apiBaseUrl = readString(parsed, "api-base-url") ??
|
|
890
1142
|
projectEnv.REMOTEDRAW_API_BASE_URL ??
|
|
891
|
-
|
|
1143
|
+
DEFAULT_REMOTEDRAW_API_BASE_URL;
|
|
892
1144
|
return ok([
|
|
893
|
-
"
|
|
1145
|
+
t("dev.title"),
|
|
894
1146
|
"",
|
|
895
|
-
|
|
1147
|
+
t("dev.apiBaseUrl", { url: apiBaseUrl }),
|
|
896
1148
|
"",
|
|
897
|
-
"
|
|
898
|
-
" Inside this repository: bun run dev:codex",
|
|
1149
|
+
t("dev.hosted"),
|
|
899
1150
|
"",
|
|
900
|
-
"
|
|
901
|
-
"
|
|
1151
|
+
t("dev.step1"),
|
|
1152
|
+
t("dev.step1.detail"),
|
|
902
1153
|
"",
|
|
903
|
-
"
|
|
904
|
-
` remotedraw create-input --preset
|
|
1154
|
+
t("dev.step2"),
|
|
1155
|
+
` remotedraw create-input --preset sketch --api-base-url ${apiBaseUrl}`,
|
|
905
1156
|
"",
|
|
906
|
-
"
|
|
907
|
-
"
|
|
1157
|
+
t("dev.step3"),
|
|
1158
|
+
t("dev.step3.detail"),
|
|
908
1159
|
].join("\n"));
|
|
909
1160
|
}
|
|
910
1161
|
async function createInputCommand(args, runtime) {
|
|
@@ -922,10 +1173,10 @@ async function createInputCommand(args, runtime) {
|
|
|
922
1173
|
if (parsed.booleans.has("help"))
|
|
923
1174
|
return ok(createInputHelpText());
|
|
924
1175
|
const projectEnv = await environmentForProject(runtime.cwd, runtime);
|
|
925
|
-
const preset = choice(readString(parsed, "preset") ?? "
|
|
1176
|
+
const preset = choice(readString(parsed, "preset") ?? "sketch", presetChoices, "preset");
|
|
926
1177
|
const apiBaseUrl = readString(parsed, "api-base-url") ??
|
|
927
1178
|
projectEnv.REMOTEDRAW_API_BASE_URL ??
|
|
928
|
-
|
|
1179
|
+
DEFAULT_REMOTEDRAW_API_BASE_URL;
|
|
929
1180
|
const payload = createSessionPayload({
|
|
930
1181
|
preset,
|
|
931
1182
|
label: readString(parsed, "label") ?? labelForPreset(preset),
|
|
@@ -935,9 +1186,9 @@ async function createInputCommand(args, runtime) {
|
|
|
935
1186
|
if (parsed.booleans.has("execute")) {
|
|
936
1187
|
const apiKey = readString(parsed, "api-key") ?? projectEnv.REMOTEDRAW_API_KEY;
|
|
937
1188
|
if (!runtime.fetch)
|
|
938
|
-
throw new Error("
|
|
1189
|
+
throw new Error(t("createInput.error.noFetch"));
|
|
939
1190
|
if (!apiKey?.startsWith("rd_sk_")) {
|
|
940
|
-
throw new Error("
|
|
1191
|
+
throw new Error(t("createInput.error.noKey"));
|
|
941
1192
|
}
|
|
942
1193
|
const response = await runtime.fetch(`${normalizedOrigin(apiBaseUrl)}/v1/sessions`, {
|
|
943
1194
|
method: "POST",
|
|
@@ -949,20 +1200,20 @@ async function createInputCommand(args, runtime) {
|
|
|
949
1200
|
});
|
|
950
1201
|
const text = await response.text();
|
|
951
1202
|
if (!response.ok)
|
|
952
|
-
throw new Error(text ||
|
|
1203
|
+
throw new Error(text || t("createInput.error.apiStatus", { status: response.status }));
|
|
953
1204
|
return ok(text);
|
|
954
1205
|
}
|
|
955
1206
|
if (parsed.booleans.has("json")) {
|
|
956
1207
|
return ok(JSON.stringify(payload, null, 2));
|
|
957
1208
|
}
|
|
958
1209
|
return ok([
|
|
959
|
-
"
|
|
1210
|
+
t("createInput.title"),
|
|
960
1211
|
"",
|
|
961
|
-
"
|
|
1212
|
+
t("createInput.warning"),
|
|
962
1213
|
"",
|
|
963
1214
|
curlForCreateInput(apiBaseUrl, payload),
|
|
964
1215
|
"",
|
|
965
|
-
"
|
|
1216
|
+
t("createInput.footer"),
|
|
966
1217
|
].join("\n"));
|
|
967
1218
|
}
|
|
968
1219
|
async function examplesCommand(args, runtime) {
|
|
@@ -975,14 +1226,14 @@ async function examplesCommand(args, runtime) {
|
|
|
975
1226
|
const example = readString(parsed, "install");
|
|
976
1227
|
if (example == null || parsed.booleans.has("list")) {
|
|
977
1228
|
return ok([
|
|
978
|
-
"
|
|
1229
|
+
t("examples.title"),
|
|
979
1230
|
"",
|
|
980
|
-
"
|
|
981
|
-
"
|
|
982
|
-
"
|
|
983
|
-
"
|
|
1231
|
+
` ${"react-ios".padEnd(19)} ${t("examples.reactIos")}`,
|
|
1232
|
+
` ${"react-owned-sender".padEnd(19)} ${t("examples.reactOwnedSender")}`,
|
|
1233
|
+
` ${"raw-http".padEnd(19)} ${t("examples.rawHttp")}`,
|
|
1234
|
+
` ${"ios-owned-sender".padEnd(19)} ${t("examples.iosOwnedSender")}`,
|
|
984
1235
|
"",
|
|
985
|
-
"
|
|
1236
|
+
t("examples.installOne"),
|
|
986
1237
|
" remotedraw examples --install react-ios --path ./remotedraw-react-ios",
|
|
987
1238
|
].join("\n"));
|
|
988
1239
|
}
|
|
@@ -1000,7 +1251,7 @@ async function examplesCommand(args, runtime) {
|
|
|
1000
1251
|
dryRun,
|
|
1001
1252
|
force: parsed.booleans.has("force"),
|
|
1002
1253
|
});
|
|
1003
|
-
return ok(scaffoldSummary("
|
|
1254
|
+
return ok(scaffoldSummary(t("scaffold.verb.installedExample"), plan, outputDir, written, dryRun));
|
|
1004
1255
|
}
|
|
1005
1256
|
async function agentCommand(args, runtime) {
|
|
1006
1257
|
const parsed = parseArgs(args, {
|
|
@@ -1014,15 +1265,18 @@ async function agentCommand(args, runtime) {
|
|
|
1014
1265
|
const targetDir = readString(parsed, "path");
|
|
1015
1266
|
if (targetDir == null) {
|
|
1016
1267
|
return ok([
|
|
1017
|
-
"
|
|
1268
|
+
t("agent.title"),
|
|
1018
1269
|
"",
|
|
1019
|
-
"
|
|
1270
|
+
t("agent.intro"),
|
|
1020
1271
|
"",
|
|
1021
|
-
"
|
|
1272
|
+
t("agent.printSkill"),
|
|
1022
1273
|
" remotedraw agent --print-skill",
|
|
1023
1274
|
"",
|
|
1024
|
-
"
|
|
1025
|
-
|
|
1275
|
+
t("agent.installSkill"),
|
|
1276
|
+
// Agent runtimes read skills from a per-project directory. `~/.codex`
|
|
1277
|
+
// is the retired location and installing there reaches nothing.
|
|
1278
|
+
" remotedraw agent --path .agents/skills/remotedraw",
|
|
1279
|
+
" remotedraw agent --path .claude/skills/remotedraw",
|
|
1026
1280
|
].join("\n"));
|
|
1027
1281
|
}
|
|
1028
1282
|
const outputDir = resolveAgentPath(targetDir, runtime);
|
|
@@ -1030,7 +1284,7 @@ async function agentCommand(args, runtime) {
|
|
|
1030
1284
|
if (!parsed.booleans.has("dry-run") &&
|
|
1031
1285
|
!parsed.booleans.has("force") &&
|
|
1032
1286
|
(await runtime.exists(outputFile))) {
|
|
1033
|
-
throw new Error("
|
|
1287
|
+
throw new Error(t("agent.error.exists"));
|
|
1034
1288
|
}
|
|
1035
1289
|
if (!parsed.booleans.has("dry-run")) {
|
|
1036
1290
|
await runtime.mkdir(outputDir);
|
|
@@ -1038,141 +1292,150 @@ async function agentCommand(args, runtime) {
|
|
|
1038
1292
|
}
|
|
1039
1293
|
return ok([
|
|
1040
1294
|
parsed.booleans.has("dry-run")
|
|
1041
|
-
?
|
|
1042
|
-
:
|
|
1295
|
+
? t("agent.wouldWrite", { path: outputFile })
|
|
1296
|
+
: t("agent.installed", { path: outputFile }),
|
|
1043
1297
|
"",
|
|
1044
|
-
"
|
|
1045
|
-
"
|
|
1298
|
+
t("word.next"),
|
|
1299
|
+
t("agent.next"),
|
|
1046
1300
|
].join("\n"));
|
|
1047
1301
|
}
|
|
1048
1302
|
function newHelpText() {
|
|
1303
|
+
const option = (flag, description) => ` ${flag.padEnd(25)} ${description}`;
|
|
1049
1304
|
return [
|
|
1050
|
-
"
|
|
1051
|
-
"
|
|
1052
|
-
"
|
|
1053
|
-
"",
|
|
1054
|
-
"
|
|
1055
|
-
"",
|
|
1056
|
-
"
|
|
1057
|
-
"
|
|
1058
|
-
"
|
|
1059
|
-
"
|
|
1060
|
-
"
|
|
1061
|
-
"
|
|
1062
|
-
"
|
|
1063
|
-
"
|
|
1064
|
-
"
|
|
1065
|
-
"
|
|
1066
|
-
"
|
|
1067
|
-
"
|
|
1068
|
-
"
|
|
1305
|
+
t("word.usage"),
|
|
1306
|
+
t("new.help.usage.bare"),
|
|
1307
|
+
t("new.help.usage.named"),
|
|
1308
|
+
"",
|
|
1309
|
+
t("new.help.interactive"),
|
|
1310
|
+
"",
|
|
1311
|
+
t("word.options"),
|
|
1312
|
+
option("--path <dir>", t("new.help.option.path")),
|
|
1313
|
+
option("--target <kind>", t("new.help.option.target")),
|
|
1314
|
+
option("--sender <kind>", t("new.help.option.sender")),
|
|
1315
|
+
option("--sdk <kind>", t("new.help.option.sdk")),
|
|
1316
|
+
option("--preset <kind>", t("new.help.option.preset")),
|
|
1317
|
+
option("--package-manager <name>", t("new.help.option.packageManager")),
|
|
1318
|
+
option("--api-base-url <url>", t("new.help.option.apiBaseUrl")),
|
|
1319
|
+
option("--force", t("new.help.option.force")),
|
|
1320
|
+
option("--offline", t("new.help.option.offline")),
|
|
1321
|
+
option("--non-interactive", t("new.help.option.nonInteractive")),
|
|
1322
|
+
option("--format <text|json>", t("new.help.option.format")),
|
|
1323
|
+
option("--dry-run", t("new.help.option.dryRun")),
|
|
1324
|
+
option("--language <code>", t("new.help.option.language")),
|
|
1069
1325
|
].join("\n");
|
|
1070
1326
|
}
|
|
1071
1327
|
function initHelpText() {
|
|
1072
1328
|
return [
|
|
1073
|
-
"
|
|
1074
|
-
"
|
|
1329
|
+
t("word.usage"),
|
|
1330
|
+
t("init.help.usage"),
|
|
1075
1331
|
"",
|
|
1076
|
-
"
|
|
1332
|
+
t("init.help.body"),
|
|
1077
1333
|
].join("\n");
|
|
1078
1334
|
}
|
|
1079
|
-
function
|
|
1335
|
+
function languageHelpText() {
|
|
1080
1336
|
return [
|
|
1081
|
-
"
|
|
1337
|
+
t("language.help.title"),
|
|
1338
|
+
"",
|
|
1339
|
+
t("word.usage"),
|
|
1340
|
+
t("language.help.usage.list"),
|
|
1341
|
+
t("language.help.usage.set"),
|
|
1082
1342
|
"",
|
|
1083
|
-
"
|
|
1084
|
-
"
|
|
1343
|
+
t("language.help.body"),
|
|
1344
|
+
t("language.help.override"),
|
|
1085
1345
|
"",
|
|
1086
|
-
"
|
|
1346
|
+
t("language.available"),
|
|
1347
|
+
...SUPPORTED_LOCALES.map((locale) => ` ${locale.padEnd(6)} ${localeEndonym(locale)}`),
|
|
1087
1348
|
].join("\n");
|
|
1088
1349
|
}
|
|
1089
|
-
function
|
|
1350
|
+
function loginHelpText() {
|
|
1090
1351
|
return [
|
|
1091
|
-
"
|
|
1352
|
+
t("login.help.title"),
|
|
1353
|
+
"",
|
|
1354
|
+
t("word.usage"),
|
|
1355
|
+
t("login.help.usage"),
|
|
1092
1356
|
"",
|
|
1093
|
-
"
|
|
1094
|
-
" remotedraw logout [--api-base-url <origin>]",
|
|
1357
|
+
t("login.help.body"),
|
|
1095
1358
|
].join("\n");
|
|
1096
1359
|
}
|
|
1360
|
+
function logoutHelpText() {
|
|
1361
|
+
return [t("logout.help.title"), "", t("word.usage"), t("logout.help.usage")].join("\n");
|
|
1362
|
+
}
|
|
1097
1363
|
function whoamiHelpText() {
|
|
1098
|
-
return [
|
|
1099
|
-
"Show the active RemoteDraw CLI account",
|
|
1100
|
-
"",
|
|
1101
|
-
"Usage:",
|
|
1102
|
-
" remotedraw whoami [--api-base-url <origin>]",
|
|
1103
|
-
].join("\n");
|
|
1364
|
+
return [t("whoami.help.title"), "", t("word.usage"), t("whoami.help.usage")].join("\n");
|
|
1104
1365
|
}
|
|
1105
1366
|
function doctorHelpText() {
|
|
1106
1367
|
return [
|
|
1107
|
-
"
|
|
1108
|
-
"
|
|
1109
|
-
"",
|
|
1110
|
-
"
|
|
1111
|
-
|
|
1112
|
-
"
|
|
1113
|
-
"
|
|
1114
|
-
"
|
|
1368
|
+
t("word.usage"),
|
|
1369
|
+
t("doctor.help.usage"),
|
|
1370
|
+
"",
|
|
1371
|
+
t("doctor.help.checks"),
|
|
1372
|
+
t("doctor.help.baseUrl", { url: DEFAULT_REMOTEDRAW_API_BASE_URL }),
|
|
1373
|
+
t("doctor.help.network"),
|
|
1374
|
+
t("doctor.help.version"),
|
|
1375
|
+
t("doctor.help.offline"),
|
|
1115
1376
|
].join("\n");
|
|
1116
1377
|
}
|
|
1117
1378
|
function updateHelpText() {
|
|
1118
1379
|
return [
|
|
1119
|
-
"
|
|
1120
|
-
"
|
|
1380
|
+
t("word.usage"),
|
|
1381
|
+
t("update.help.usage"),
|
|
1121
1382
|
"",
|
|
1122
|
-
|
|
1123
|
-
"
|
|
1124
|
-
"
|
|
1383
|
+
t("update.help.body", { package: CLI_PACKAGE_NAME }),
|
|
1384
|
+
t("update.help.global"),
|
|
1385
|
+
t("update.help.check"),
|
|
1125
1386
|
"",
|
|
1126
|
-
"
|
|
1387
|
+
t("update.help.silence"),
|
|
1127
1388
|
].join("\n");
|
|
1128
1389
|
}
|
|
1129
1390
|
function optionsHelpText() {
|
|
1130
1391
|
return [
|
|
1131
|
-
"
|
|
1392
|
+
t("word.usage"),
|
|
1132
1393
|
" remotedraw options [--format text|json]",
|
|
1133
1394
|
"",
|
|
1134
|
-
"
|
|
1395
|
+
t("options.help.body"),
|
|
1135
1396
|
].join("\n");
|
|
1136
1397
|
}
|
|
1137
1398
|
function devHelpText() {
|
|
1138
1399
|
return [
|
|
1139
|
-
"
|
|
1140
|
-
"
|
|
1400
|
+
t("word.usage"),
|
|
1401
|
+
t("dev.help.usage"),
|
|
1141
1402
|
"",
|
|
1142
|
-
"
|
|
1403
|
+
t("dev.help.body"),
|
|
1143
1404
|
].join("\n");
|
|
1144
1405
|
}
|
|
1145
1406
|
function createInputHelpText() {
|
|
1407
|
+
const option = (flag, description) => ` ${flag.padEnd(21)} ${description}`;
|
|
1146
1408
|
return [
|
|
1147
|
-
"
|
|
1148
|
-
"
|
|
1149
|
-
"",
|
|
1150
|
-
"
|
|
1151
|
-
"
|
|
1152
|
-
"
|
|
1153
|
-
"
|
|
1154
|
-
"
|
|
1155
|
-
"
|
|
1156
|
-
"
|
|
1409
|
+
t("word.usage"),
|
|
1410
|
+
t("createInput.help.usage"),
|
|
1411
|
+
"",
|
|
1412
|
+
t("word.options"),
|
|
1413
|
+
option("--preset <kind>", t("createInput.help.option.preset")),
|
|
1414
|
+
option("--label <text>", t("createInput.help.option.label")),
|
|
1415
|
+
option("--external-id <id>", t("createInput.help.option.externalId")),
|
|
1416
|
+
option("--api-base-url <url>", t("createInput.help.option.apiBaseUrl")),
|
|
1417
|
+
option("--api-key <key>", t("createInput.help.option.apiKey")),
|
|
1418
|
+
option("--expires-in-ms <n>", t("createInput.help.option.expiresInMs")),
|
|
1157
1419
|
].join("\n");
|
|
1158
1420
|
}
|
|
1159
1421
|
function examplesHelpText() {
|
|
1160
1422
|
return [
|
|
1161
|
-
"
|
|
1162
|
-
"
|
|
1163
|
-
"
|
|
1423
|
+
t("word.usage"),
|
|
1424
|
+
t("examples.help.usage.list"),
|
|
1425
|
+
t("examples.help.usage.install"),
|
|
1164
1426
|
"",
|
|
1165
|
-
|
|
1427
|
+
t("word.examples", { list: exampleChoices.join(", ") }),
|
|
1166
1428
|
].join("\n");
|
|
1167
1429
|
}
|
|
1168
1430
|
function agentHelpText() {
|
|
1169
1431
|
return [
|
|
1170
|
-
"
|
|
1171
|
-
"
|
|
1172
|
-
"
|
|
1173
|
-
"
|
|
1432
|
+
t("word.usage"),
|
|
1433
|
+
t("agent.help.usage.bare"),
|
|
1434
|
+
t("agent.help.usage.print"),
|
|
1435
|
+
t("agent.help.usage.install"),
|
|
1174
1436
|
"",
|
|
1175
|
-
"
|
|
1437
|
+
t("agent.help.body"),
|
|
1438
|
+
t("agent.help.paths"),
|
|
1176
1439
|
].join("\n");
|
|
1177
1440
|
}
|
|
1178
1441
|
function parseArgs(args, allowed) {
|
|
@@ -1195,17 +1458,17 @@ function parseArgs(args, allowed) {
|
|
|
1195
1458
|
const inlineValue = equalsIndex === -1 ? undefined : withoutPrefix.slice(equalsIndex + 1);
|
|
1196
1459
|
if (booleanFlags.has(name)) {
|
|
1197
1460
|
if (inlineValue != null) {
|
|
1198
|
-
throw
|
|
1461
|
+
throw cliError("INVALID_ARGUMENT", "error.optionNoValue", { name });
|
|
1199
1462
|
}
|
|
1200
1463
|
booleans.add(name);
|
|
1201
1464
|
continue;
|
|
1202
1465
|
}
|
|
1203
1466
|
if (!valueFlags.has(name)) {
|
|
1204
|
-
throw
|
|
1467
|
+
throw cliError("INVALID_ARGUMENT", "error.unsupportedOption", { name });
|
|
1205
1468
|
}
|
|
1206
1469
|
const value = inlineValue ?? args[index + 1];
|
|
1207
1470
|
if (value == null || value.startsWith("--")) {
|
|
1208
|
-
throw
|
|
1471
|
+
throw cliError("MISSING_ARGUMENT", "error.optionNeedsValue", { name });
|
|
1209
1472
|
}
|
|
1210
1473
|
values.set(name, value);
|
|
1211
1474
|
if (inlineValue == null)
|
|
@@ -1222,7 +1485,7 @@ function optionalInteger(value) {
|
|
|
1222
1485
|
return undefined;
|
|
1223
1486
|
const number = Number(value);
|
|
1224
1487
|
if (!Number.isInteger(number) || number <= 0) {
|
|
1225
|
-
throw
|
|
1488
|
+
throw cliError("INVALID_ARGUMENT", "error.expiresInMs");
|
|
1226
1489
|
}
|
|
1227
1490
|
return number;
|
|
1228
1491
|
}
|
|
@@ -1239,16 +1502,16 @@ async function promptForNewProject(parsed, runtime) {
|
|
|
1239
1502
|
defaultAppName: "RemoteDraw app",
|
|
1240
1503
|
});
|
|
1241
1504
|
}
|
|
1242
|
-
await prompts.intro?.("
|
|
1505
|
+
await prompts.intro?.(t("prompt.intro.setup"));
|
|
1243
1506
|
const appName = readString(parsed, "app-name") ??
|
|
1244
1507
|
(await prompts.text({
|
|
1245
|
-
message: "
|
|
1508
|
+
message: t("field.appName"),
|
|
1246
1509
|
defaultValue: "RemoteDraw App",
|
|
1247
1510
|
validate: validateProjectName,
|
|
1248
1511
|
}));
|
|
1249
1512
|
const target = readString(parsed, "target") == null
|
|
1250
1513
|
? await prompts.select({
|
|
1251
|
-
message: "
|
|
1514
|
+
message: t("field.target"),
|
|
1252
1515
|
defaultValue: "web",
|
|
1253
1516
|
choices: targetPromptChoices(),
|
|
1254
1517
|
})
|
|
@@ -1261,25 +1524,19 @@ async function promptForNewProject(parsed, runtime) {
|
|
|
1261
1524
|
: choice(readString(parsed, "sdk"), sdkChoices, "sdk");
|
|
1262
1525
|
const preset = readString(parsed, "preset") == null
|
|
1263
1526
|
? await prompts.select({
|
|
1264
|
-
message: "
|
|
1265
|
-
defaultValue: "
|
|
1527
|
+
message: t("field.preset"),
|
|
1528
|
+
defaultValue: "sketch",
|
|
1266
1529
|
choices: presetPromptChoices(),
|
|
1267
1530
|
})
|
|
1268
1531
|
: choice(readString(parsed, "preset"), presetChoices, "preset");
|
|
1269
1532
|
const packageManager = readString(parsed, "package-manager") == null
|
|
1270
1533
|
? await prompts.select({
|
|
1271
|
-
message: "
|
|
1534
|
+
message: t("field.packageManager"),
|
|
1272
1535
|
defaultValue: "npm",
|
|
1273
1536
|
choices: packageManagerPromptChoices(),
|
|
1274
1537
|
})
|
|
1275
1538
|
: choice(readString(parsed, "package-manager"), packageManagerChoices, "package-manager");
|
|
1276
|
-
const apiBaseUrl = readString(parsed, "api-base-url") ??
|
|
1277
|
-
apiBaseUrlForEndpoint(await prompts.select({
|
|
1278
|
-
message: "API endpoint",
|
|
1279
|
-
helperText: "Use the placeholder unless you are wiring local dev now.",
|
|
1280
|
-
defaultValue: "deployment",
|
|
1281
|
-
choices: apiEndpointPromptChoices(),
|
|
1282
|
-
}));
|
|
1539
|
+
const apiBaseUrl = readString(parsed, "api-base-url") ?? DEFAULT_REMOTEDRAW_API_BASE_URL;
|
|
1283
1540
|
validatePlan({ target, sender, sdk });
|
|
1284
1541
|
return {
|
|
1285
1542
|
appName,
|
|
@@ -1293,13 +1550,13 @@ async function promptForNewProject(parsed, runtime) {
|
|
|
1293
1550
|
};
|
|
1294
1551
|
}
|
|
1295
1552
|
function validateProjectName(value) {
|
|
1296
|
-
return value.trim() === "" ? "
|
|
1553
|
+
return value.trim() === "" ? t("field.appName.error") : undefined;
|
|
1297
1554
|
}
|
|
1298
1555
|
async function promptForSender(prompts, target) {
|
|
1299
1556
|
const choices = senderPromptChoices(target);
|
|
1300
1557
|
return await prompts.select({
|
|
1301
|
-
message: "
|
|
1302
|
-
helperText: "
|
|
1558
|
+
message: t("field.sender"),
|
|
1559
|
+
helperText: t("field.sender.helper"),
|
|
1303
1560
|
defaultValue: defaultPromptValue(defaultSenderForTarget(target), choices),
|
|
1304
1561
|
choices,
|
|
1305
1562
|
});
|
|
@@ -1307,8 +1564,8 @@ async function promptForSender(prompts, target) {
|
|
|
1307
1564
|
async function promptForSdk(prompts, target, sender) {
|
|
1308
1565
|
const choices = sdkPromptChoices(target, sender);
|
|
1309
1566
|
return await prompts.select({
|
|
1310
|
-
message: "
|
|
1311
|
-
helperText: "
|
|
1567
|
+
message: t("field.sdk"),
|
|
1568
|
+
helperText: t("field.sdk.helper"),
|
|
1312
1569
|
defaultValue: defaultPromptValue(defaultSdkForChoices(target, sender), choices),
|
|
1313
1570
|
choices,
|
|
1314
1571
|
});
|
|
@@ -1320,10 +1577,10 @@ function defaultPromptValue(preferred, choices) {
|
|
|
1320
1577
|
}
|
|
1321
1578
|
function targetPromptChoices() {
|
|
1322
1579
|
return [
|
|
1323
|
-
{ value: "web", label: "
|
|
1324
|
-
{ value: "desktop", label: "
|
|
1325
|
-
{ value: "ios", label: "
|
|
1326
|
-
{ value: "headless", label: "
|
|
1580
|
+
{ value: "web", label: t("choice.target.web.label") },
|
|
1581
|
+
{ value: "desktop", label: t("choice.target.desktop.label") },
|
|
1582
|
+
{ value: "ios", label: t("choice.target.ios.label") },
|
|
1583
|
+
{ value: "headless", label: t("choice.target.headless.label") },
|
|
1327
1584
|
];
|
|
1328
1585
|
}
|
|
1329
1586
|
const DOCS_INTEGRATION_PATHS = "https://docs.remotedraw.com/docs#paths";
|
|
@@ -1331,103 +1588,112 @@ const DOCS_SDKS = "https://docs.remotedraw.com/docs/sdks#components";
|
|
|
1331
1588
|
const DOCS_PAYLOADS = "https://docs.remotedraw.com/docs/api#payloads";
|
|
1332
1589
|
const DOCS_ENDPOINTS = "https://docs.remotedraw.com/docs/api#endpoints";
|
|
1333
1590
|
const DOCS_AUTH = "https://docs.remotedraw.com/docs/api#auth";
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1591
|
+
// Detail bundles are built per call, not frozen at module load: the active
|
|
1592
|
+
// locale is only known once `runCli` has parsed the flag, the environment, and
|
|
1593
|
+
// the stored choice.
|
|
1594
|
+
function targetWizardDetails() {
|
|
1595
|
+
return {
|
|
1596
|
+
web: {
|
|
1597
|
+
summary: t("choice.target.web.summary"),
|
|
1598
|
+
explanation: t("choice.target.web.explanation"),
|
|
1599
|
+
docsUrl: DOCS_INTEGRATION_PATHS,
|
|
1600
|
+
docsLabel: t("choice.target.web.docs"),
|
|
1601
|
+
},
|
|
1602
|
+
desktop: {
|
|
1603
|
+
summary: t("choice.target.desktop.summary"),
|
|
1604
|
+
explanation: t("choice.target.desktop.explanation"),
|
|
1605
|
+
docsUrl: DOCS_INTEGRATION_PATHS,
|
|
1606
|
+
docsLabel: t("choice.target.desktop.docs"),
|
|
1607
|
+
},
|
|
1608
|
+
ios: {
|
|
1609
|
+
summary: t("choice.target.ios.summary"),
|
|
1610
|
+
explanation: t("choice.target.ios.explanation"),
|
|
1611
|
+
docsUrl: DOCS_INTEGRATION_PATHS,
|
|
1612
|
+
docsLabel: t("choice.target.ios.docs"),
|
|
1613
|
+
},
|
|
1614
|
+
headless: {
|
|
1615
|
+
summary: t("choice.target.headless.summary"),
|
|
1616
|
+
explanation: t("choice.target.headless.explanation"),
|
|
1617
|
+
docsUrl: DOCS_ENDPOINTS,
|
|
1618
|
+
docsLabel: t("choice.target.headless.docs"),
|
|
1619
|
+
},
|
|
1620
|
+
};
|
|
1621
|
+
}
|
|
1360
1622
|
function wizardChoices(choices, details) {
|
|
1361
|
-
return
|
|
1623
|
+
return choices.map((choiceOption) => ({
|
|
1362
1624
|
...choiceOption,
|
|
1363
1625
|
details: details[choiceOption.value],
|
|
1364
1626
|
}));
|
|
1365
1627
|
}
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1628
|
+
function senderWizardDetails() {
|
|
1629
|
+
return {
|
|
1630
|
+
"remotedraw-ios": {
|
|
1631
|
+
summary: t("choice.sender.remotedrawIos.summary"),
|
|
1632
|
+
explanation: t("choice.sender.remotedrawIos.explanation"),
|
|
1633
|
+
docsUrl: DOCS_INTEGRATION_PATHS,
|
|
1634
|
+
docsLabel: t("choice.sender.remotedrawIos.docs"),
|
|
1635
|
+
},
|
|
1636
|
+
"embedded-web": {
|
|
1637
|
+
summary: t("choice.sender.embeddedWeb.summary"),
|
|
1638
|
+
explanation: t("choice.sender.embeddedWeb.explanation"),
|
|
1639
|
+
docsUrl: DOCS_INTEGRATION_PATHS,
|
|
1640
|
+
docsLabel: t("choice.sender.embeddedWeb.docs"),
|
|
1641
|
+
},
|
|
1642
|
+
"own-ios": {
|
|
1643
|
+
summary: t("choice.sender.ownIos.summary"),
|
|
1644
|
+
explanation: t("choice.sender.ownIos.explanation"),
|
|
1645
|
+
docsUrl: DOCS_INTEGRATION_PATHS,
|
|
1646
|
+
docsLabel: t("choice.sender.ownIos.docs"),
|
|
1647
|
+
},
|
|
1648
|
+
headless: {
|
|
1649
|
+
summary: t("choice.sender.headless.summary"),
|
|
1650
|
+
explanation: t("choice.sender.headless.explanation"),
|
|
1651
|
+
docsUrl: DOCS_ENDPOINTS,
|
|
1652
|
+
docsLabel: t("choice.sender.headless.docs"),
|
|
1653
|
+
},
|
|
1654
|
+
};
|
|
1655
|
+
}
|
|
1656
|
+
function sdkWizardDetails() {
|
|
1657
|
+
return {
|
|
1658
|
+
react: {
|
|
1659
|
+
summary: t("choice.sdk.react.summary"),
|
|
1660
|
+
explanation: t("choice.sdk.react.explanation"),
|
|
1661
|
+
docsUrl: DOCS_SDKS,
|
|
1662
|
+
docsLabel: t("choice.sdk.react.docs"),
|
|
1663
|
+
},
|
|
1664
|
+
svelte: {
|
|
1665
|
+
summary: t("choice.sdk.svelte.summary"),
|
|
1666
|
+
explanation: t("choice.sdk.svelte.explanation"),
|
|
1667
|
+
docsUrl: DOCS_SDKS,
|
|
1668
|
+
docsLabel: t("choice.sdk.svelte.docs"),
|
|
1669
|
+
},
|
|
1670
|
+
js: {
|
|
1671
|
+
summary: t("choice.sdk.js.summary"),
|
|
1672
|
+
explanation: t("choice.sdk.js.explanation"),
|
|
1673
|
+
docsUrl: DOCS_ENDPOINTS,
|
|
1674
|
+
docsLabel: t("choice.sdk.js.docs"),
|
|
1675
|
+
},
|
|
1676
|
+
swift: {
|
|
1677
|
+
summary: t("choice.sdk.swift.summary"),
|
|
1678
|
+
explanation: t("choice.sdk.swift.explanation"),
|
|
1679
|
+
docsUrl: DOCS_SDKS,
|
|
1680
|
+
docsLabel: t("choice.sdk.swift.docs"),
|
|
1681
|
+
},
|
|
1682
|
+
headless: {
|
|
1683
|
+
summary: t("choice.sdk.headless.summary"),
|
|
1684
|
+
explanation: t("choice.sdk.headless.explanation"),
|
|
1685
|
+
docsUrl: DOCS_ENDPOINTS,
|
|
1686
|
+
docsLabel: t("choice.sdk.headless.docs"),
|
|
1687
|
+
},
|
|
1688
|
+
};
|
|
1689
|
+
}
|
|
1424
1690
|
function senderPromptChoices(target) {
|
|
1425
1691
|
if (target === "ios") {
|
|
1426
1692
|
return [
|
|
1427
1693
|
{
|
|
1428
1694
|
value: "own-ios",
|
|
1429
|
-
label: "
|
|
1430
|
-
hint: "
|
|
1695
|
+
label: t("choice.sender.ownIos.label"),
|
|
1696
|
+
hint: t("choice.sender.ownIos.hint"),
|
|
1431
1697
|
},
|
|
1432
1698
|
];
|
|
1433
1699
|
}
|
|
@@ -1435,8 +1701,8 @@ function senderPromptChoices(target) {
|
|
|
1435
1701
|
return [
|
|
1436
1702
|
{
|
|
1437
1703
|
value: "headless",
|
|
1438
|
-
label: "
|
|
1439
|
-
hint: "
|
|
1704
|
+
label: t("choice.sender.headless.label"),
|
|
1705
|
+
hint: t("choice.sender.headless.hint.noUi"),
|
|
1440
1706
|
},
|
|
1441
1707
|
];
|
|
1442
1708
|
}
|
|
@@ -1444,26 +1710,26 @@ function senderPromptChoices(target) {
|
|
|
1444
1710
|
return [
|
|
1445
1711
|
{
|
|
1446
1712
|
value: "remotedraw-ios",
|
|
1447
|
-
label: "
|
|
1448
|
-
hint: "
|
|
1713
|
+
label: t("choice.sender.remotedrawIos.label"),
|
|
1714
|
+
hint: t("choice.sender.remotedrawIos.hint.joinUrl"),
|
|
1449
1715
|
},
|
|
1450
1716
|
{
|
|
1451
1717
|
value: "headless",
|
|
1452
|
-
label: "
|
|
1453
|
-
hint: "
|
|
1718
|
+
label: t("choice.sender.headless.label"),
|
|
1719
|
+
hint: t("choice.sender.headless.hint.httpRoutes"),
|
|
1454
1720
|
},
|
|
1455
1721
|
];
|
|
1456
1722
|
}
|
|
1457
1723
|
return [
|
|
1458
1724
|
{
|
|
1459
1725
|
value: "remotedraw-ios",
|
|
1460
|
-
label: "
|
|
1461
|
-
hint: "
|
|
1726
|
+
label: t("choice.sender.remotedrawIos.label"),
|
|
1727
|
+
hint: t("choice.sender.remotedrawIos.hint.scan"),
|
|
1462
1728
|
},
|
|
1463
1729
|
{
|
|
1464
1730
|
value: "embedded-web",
|
|
1465
|
-
label: "
|
|
1466
|
-
hint: "
|
|
1731
|
+
label: t("choice.sender.embeddedWeb.label"),
|
|
1732
|
+
hint: t("choice.sender.embeddedWeb.hint"),
|
|
1467
1733
|
},
|
|
1468
1734
|
];
|
|
1469
1735
|
}
|
|
@@ -1472,8 +1738,8 @@ function sdkPromptChoices(target, sender) {
|
|
|
1472
1738
|
return [
|
|
1473
1739
|
{
|
|
1474
1740
|
value: "swift",
|
|
1475
|
-
label: "
|
|
1476
|
-
hint: "
|
|
1741
|
+
label: t("choice.sdk.swift.label"),
|
|
1742
|
+
hint: t("choice.sdk.swift.hint"),
|
|
1477
1743
|
},
|
|
1478
1744
|
];
|
|
1479
1745
|
}
|
|
@@ -1481,13 +1747,13 @@ function sdkPromptChoices(target, sender) {
|
|
|
1481
1747
|
return [
|
|
1482
1748
|
{
|
|
1483
1749
|
value: "js",
|
|
1484
|
-
label: "
|
|
1485
|
-
hint: "
|
|
1750
|
+
label: t("choice.sdk.js.label.client"),
|
|
1751
|
+
hint: t("choice.sdk.js.hint.client"),
|
|
1486
1752
|
},
|
|
1487
1753
|
{
|
|
1488
1754
|
value: "headless",
|
|
1489
|
-
label: "
|
|
1490
|
-
hint: "
|
|
1755
|
+
label: t("choice.sdk.headless.label"),
|
|
1756
|
+
hint: t("choice.sdk.headless.hint"),
|
|
1491
1757
|
},
|
|
1492
1758
|
];
|
|
1493
1759
|
}
|
|
@@ -1495,40 +1761,40 @@ function sdkPromptChoices(target, sender) {
|
|
|
1495
1761
|
return [
|
|
1496
1762
|
{
|
|
1497
1763
|
value: "js",
|
|
1498
|
-
label: "
|
|
1499
|
-
hint: "
|
|
1764
|
+
label: t("choice.sdk.js.label.client"),
|
|
1765
|
+
hint: t("choice.sdk.js.hint.client"),
|
|
1500
1766
|
},
|
|
1501
1767
|
];
|
|
1502
1768
|
}
|
|
1503
1769
|
return [
|
|
1504
1770
|
{
|
|
1505
1771
|
value: "react",
|
|
1506
|
-
label: "
|
|
1507
|
-
hint: "
|
|
1772
|
+
label: t("choice.sdk.react.label"),
|
|
1773
|
+
hint: t("choice.sdk.react.hint"),
|
|
1508
1774
|
},
|
|
1509
1775
|
{
|
|
1510
1776
|
value: "svelte",
|
|
1511
|
-
label: "
|
|
1512
|
-
hint: "
|
|
1777
|
+
label: t("choice.sdk.svelte.label"),
|
|
1778
|
+
hint: t("choice.sdk.svelte.hint"),
|
|
1513
1779
|
},
|
|
1514
1780
|
{
|
|
1515
1781
|
value: "js",
|
|
1516
|
-
label: "
|
|
1517
|
-
hint: "
|
|
1782
|
+
label: t("choice.sdk.js.label.noFramework"),
|
|
1783
|
+
hint: t("choice.sdk.js.hint.noFramework"),
|
|
1518
1784
|
},
|
|
1519
1785
|
];
|
|
1520
1786
|
}
|
|
1521
1787
|
function presetPromptChoices() {
|
|
1522
1788
|
return [
|
|
1523
1789
|
{
|
|
1524
|
-
value: "
|
|
1525
|
-
label: "
|
|
1526
|
-
hint: "
|
|
1790
|
+
value: "sketch",
|
|
1791
|
+
label: t("choice.preset.sketch.label"),
|
|
1792
|
+
hint: t("choice.preset.sketch.hint"),
|
|
1527
1793
|
},
|
|
1528
1794
|
{
|
|
1529
|
-
value: "
|
|
1530
|
-
label: "
|
|
1531
|
-
hint: "
|
|
1795
|
+
value: "screenMarkup",
|
|
1796
|
+
label: t("choice.preset.screenMarkup.label"),
|
|
1797
|
+
hint: t("choice.preset.screenMarkup.hint"),
|
|
1532
1798
|
},
|
|
1533
1799
|
];
|
|
1534
1800
|
}
|
|
@@ -1538,122 +1804,86 @@ function packageManagerPromptChoices() {
|
|
|
1538
1804
|
label: packageManager,
|
|
1539
1805
|
}));
|
|
1540
1806
|
}
|
|
1541
|
-
function
|
|
1542
|
-
return
|
|
1543
|
-
{
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1807
|
+
function presetWizardDetails() {
|
|
1808
|
+
return {
|
|
1809
|
+
approval: {
|
|
1810
|
+
summary: t("choice.preset.approval.summary"),
|
|
1811
|
+
explanation: t("choice.preset.approval.explanation"),
|
|
1812
|
+
docsUrl: DOCS_PAYLOADS,
|
|
1813
|
+
docsLabel: t("choice.preset.approval.docs"),
|
|
1547
1814
|
},
|
|
1548
|
-
{
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1815
|
+
sketch: {
|
|
1816
|
+
summary: t("choice.preset.sketch.summary"),
|
|
1817
|
+
explanation: t("choice.preset.sketch.explanation"),
|
|
1818
|
+
docsUrl: DOCS_PAYLOADS,
|
|
1819
|
+
docsLabel: t("choice.preset.sketch.docs"),
|
|
1552
1820
|
},
|
|
1553
|
-
|
|
1821
|
+
photoMarkup: {
|
|
1822
|
+
summary: t("choice.preset.photoMarkup.summary"),
|
|
1823
|
+
explanation: t("choice.preset.photoMarkup.explanation"),
|
|
1824
|
+
docsUrl: DOCS_PAYLOADS,
|
|
1825
|
+
docsLabel: t("choice.preset.photoMarkup.docs"),
|
|
1826
|
+
},
|
|
1827
|
+
pdfMarkup: {
|
|
1828
|
+
summary: t("choice.preset.pdfMarkup.summary"),
|
|
1829
|
+
explanation: t("choice.preset.pdfMarkup.explanation"),
|
|
1830
|
+
docsUrl: DOCS_PAYLOADS,
|
|
1831
|
+
docsLabel: t("choice.preset.pdfMarkup.docs"),
|
|
1832
|
+
},
|
|
1833
|
+
mapMarkup: {
|
|
1834
|
+
summary: t("choice.preset.mapMarkup.summary"),
|
|
1835
|
+
explanation: t("choice.preset.mapMarkup.explanation"),
|
|
1836
|
+
docsUrl: DOCS_PAYLOADS,
|
|
1837
|
+
docsLabel: t("choice.preset.mapMarkup.docs"),
|
|
1838
|
+
},
|
|
1839
|
+
screenMarkup: {
|
|
1840
|
+
summary: t("choice.preset.screenMarkup.summary"),
|
|
1841
|
+
explanation: t("choice.preset.screenMarkup.explanation"),
|
|
1842
|
+
docsUrl: DOCS_PAYLOADS,
|
|
1843
|
+
docsLabel: t("choice.preset.screenMarkup.docs"),
|
|
1844
|
+
},
|
|
1845
|
+
designReview: {
|
|
1846
|
+
summary: t("choice.preset.designReview.summary"),
|
|
1847
|
+
explanation: t("choice.preset.designReview.explanation"),
|
|
1848
|
+
docsUrl: DOCS_PAYLOADS,
|
|
1849
|
+
docsLabel: t("choice.preset.designReview.docs"),
|
|
1850
|
+
},
|
|
1851
|
+
pointer: {
|
|
1852
|
+
summary: t("choice.preset.pointer.summary"),
|
|
1853
|
+
explanation: t("choice.preset.pointer.explanation"),
|
|
1854
|
+
docsUrl: DOCS_PAYLOADS,
|
|
1855
|
+
docsLabel: t("choice.preset.pointer.docs"),
|
|
1856
|
+
},
|
|
1857
|
+
};
|
|
1858
|
+
}
|
|
1859
|
+
function packageManagerWizardDetails() {
|
|
1860
|
+
return {
|
|
1861
|
+
npm: {
|
|
1862
|
+
summary: t("choice.packageManager.npm.summary"),
|
|
1863
|
+
explanation: t("choice.packageManager.npm.explanation"),
|
|
1864
|
+
docsUrl: "https://docs.npmjs.com/",
|
|
1865
|
+
docsLabel: t("choice.packageManager.npm.docs"),
|
|
1866
|
+
},
|
|
1867
|
+
pnpm: {
|
|
1868
|
+
summary: t("choice.packageManager.pnpm.summary"),
|
|
1869
|
+
explanation: t("choice.packageManager.pnpm.explanation"),
|
|
1870
|
+
docsUrl: "https://pnpm.io/",
|
|
1871
|
+
docsLabel: t("choice.packageManager.pnpm.docs"),
|
|
1872
|
+
},
|
|
1873
|
+
yarn: {
|
|
1874
|
+
summary: t("choice.packageManager.yarn.summary"),
|
|
1875
|
+
explanation: t("choice.packageManager.yarn.explanation"),
|
|
1876
|
+
docsUrl: "https://yarnpkg.com/getting-started/usage",
|
|
1877
|
+
docsLabel: t("choice.packageManager.yarn.docs"),
|
|
1878
|
+
},
|
|
1879
|
+
bun: {
|
|
1880
|
+
summary: t("choice.packageManager.bun.summary"),
|
|
1881
|
+
explanation: t("choice.packageManager.bun.explanation"),
|
|
1882
|
+
docsUrl: "https://bun.com/docs/pm/cli/install",
|
|
1883
|
+
docsLabel: t("choice.packageManager.bun.docs"),
|
|
1884
|
+
},
|
|
1885
|
+
};
|
|
1554
1886
|
}
|
|
1555
|
-
const presetWizardDetails = {
|
|
1556
|
-
signature: {
|
|
1557
|
-
summary: "De productklare RemoteDraw-handtekeningstrook.",
|
|
1558
|
-
explanation: "Kies dit voor de complete, merkbare SignatureStrip met een exact passend telefoonoppervlak. Je kunt logo, tekst, kleur en omliggende formulier-UI aanpassen.",
|
|
1559
|
-
docsUrl: DOCS_PAYLOADS,
|
|
1560
|
-
docsLabel: "Handtekeningdocs openen",
|
|
1561
|
-
},
|
|
1562
|
-
initials: {
|
|
1563
|
-
summary: "Een klein tekenveld voor initialen.",
|
|
1564
|
-
explanation: "Gebruik dit wanneer iemand op één of meerdere plekken korte initialen moet plaatsen, bijvoorbeeld bij documentcontrole.",
|
|
1565
|
-
docsUrl: DOCS_PAYLOADS,
|
|
1566
|
-
docsLabel: "Initialendocs openen",
|
|
1567
|
-
},
|
|
1568
|
-
approval: {
|
|
1569
|
-
summary: "Vrije tekeninvoer voor een visueel akkoord.",
|
|
1570
|
-
explanation: "Gebruik dit voor goedkeuringen waarbij een vink, paraaf of korte markering voldoende is en je workflow de status beheert.",
|
|
1571
|
-
docsUrl: DOCS_PAYLOADS,
|
|
1572
|
-
docsLabel: "Goedkeuringsdocs openen",
|
|
1573
|
-
},
|
|
1574
|
-
sketch: {
|
|
1575
|
-
summary: "Genormaliseerde invoer voor een eigen ontvangeroppervlak.",
|
|
1576
|
-
explanation: "Kies dit wanneer jouw product de foto, kaart, PDF, canvas of andere receiver-UI beheert. RemoteDraw levert de invoerprimitieven zonder een voorgeschreven componentontwerp.",
|
|
1577
|
-
docsUrl: DOCS_PAYLOADS,
|
|
1578
|
-
docsLabel: "Schetsdocs openen",
|
|
1579
|
-
},
|
|
1580
|
-
photoMarkup: {
|
|
1581
|
-
summary: "Aantekeningen bovenop een foto.",
|
|
1582
|
-
explanation: "Gebruik dit voor inspecties, feedback of aanwijzingen op beeldmateriaal. Je app levert de foto en bewaart de relatie met de tekeningen.",
|
|
1583
|
-
docsUrl: DOCS_PAYLOADS,
|
|
1584
|
-
docsLabel: "Fotoannotatiedocs openen",
|
|
1585
|
-
},
|
|
1586
|
-
pdfMarkup: {
|
|
1587
|
-
summary: "Aantekeningen op een PDF-pagina.",
|
|
1588
|
-
explanation: "Gebruik dit voor documentreview. Je app rendert de juiste pagina en koppelt RemoteDraw-coördinaten aan die vaste weergave.",
|
|
1589
|
-
docsUrl: DOCS_PAYLOADS,
|
|
1590
|
-
docsLabel: "PDF-annotatiedocs openen",
|
|
1591
|
-
},
|
|
1592
|
-
mapMarkup: {
|
|
1593
|
-
summary: "Tekeningen en aanwijzingen op een kaart.",
|
|
1594
|
-
explanation: "Gebruik dit voor routes, locaties of ruimtelijke feedback. Je app beheert de kaart en viewport; RemoteDraw synchroniseert de invoer.",
|
|
1595
|
-
docsUrl: DOCS_PAYLOADS,
|
|
1596
|
-
docsLabel: "Kaartannotatiedocs openen",
|
|
1597
|
-
},
|
|
1598
|
-
screenMarkup: {
|
|
1599
|
-
summary: "Aantekeningen op een scherm of applicatieweergave.",
|
|
1600
|
-
explanation: "Gebruik dit voor support, demo's en UI-feedback. Je ontvanger levert het schermbeeld waarop de telefoonmarkeringen worden geprojecteerd.",
|
|
1601
|
-
docsUrl: DOCS_PAYLOADS,
|
|
1602
|
-
docsLabel: "Schermaantekeningdocs openen",
|
|
1603
|
-
},
|
|
1604
|
-
designReview: {
|
|
1605
|
-
summary: "Gerichte visuele feedback op een ontwerp.",
|
|
1606
|
-
explanation: "Gebruik dit voor ontwerpbeoordelingen met pijlen, vormen en vrije lijnen. Je product beheert opmerkingen, versies en besluitvorming.",
|
|
1607
|
-
docsUrl: DOCS_PAYLOADS,
|
|
1608
|
-
docsLabel: "Ontwerpbeoordelingsdocs openen",
|
|
1609
|
-
},
|
|
1610
|
-
pointer: {
|
|
1611
|
-
summary: "Live aanwijzen zonder blijvende tekening.",
|
|
1612
|
-
explanation: "Gebruik dit voor presentaties en begeleiding waarbij de telefoon als aanwijzer dient en beweging belangrijker is dan opgeslagen inkt.",
|
|
1613
|
-
docsUrl: DOCS_PAYLOADS,
|
|
1614
|
-
docsLabel: "Aanwijzerdocs openen",
|
|
1615
|
-
},
|
|
1616
|
-
};
|
|
1617
|
-
const packageManagerWizardDetails = {
|
|
1618
|
-
npm: {
|
|
1619
|
-
summary: "De standaard package manager die met Node.js wordt geleverd.",
|
|
1620
|
-
explanation: "Kies npm als je project package-lock.json gebruikt of geen voorkeur heeft. De wizard gebruikt npm voor installatie- en vervolgopdrachten.",
|
|
1621
|
-
docsUrl: "https://docs.npmjs.com/",
|
|
1622
|
-
docsLabel: "npm-docs openen",
|
|
1623
|
-
},
|
|
1624
|
-
pnpm: {
|
|
1625
|
-
summary: "Een snelle package manager met een gedeelde pakketopslag.",
|
|
1626
|
-
explanation: "Kies pnpm als je project pnpm-lock.yaml gebruikt, strikte dependency-isolatie wenst of al deel is van een pnpm-workspace.",
|
|
1627
|
-
docsUrl: "https://pnpm.io/",
|
|
1628
|
-
docsLabel: "pnpm-docs openen",
|
|
1629
|
-
},
|
|
1630
|
-
yarn: {
|
|
1631
|
-
summary: "Een package manager met workspace- en Plug'n'Play-ondersteuning.",
|
|
1632
|
-
explanation: "Kies Yarn als je project yarn.lock gebruikt. De wizard sluit aan op de bestaande Yarn-versie en projectconfiguratie.",
|
|
1633
|
-
docsUrl: "https://yarnpkg.com/getting-started/usage",
|
|
1634
|
-
docsLabel: "Yarn-docs openen",
|
|
1635
|
-
},
|
|
1636
|
-
bun: {
|
|
1637
|
-
summary: "De snelle package manager die onderdeel is van de Bun-runtime.",
|
|
1638
|
-
explanation: "Kies Bun als je project bun.lock gebruikt of Bun al inzet voor scripts, tests en installatie van dependencies.",
|
|
1639
|
-
docsUrl: "https://bun.com/docs/pm/cli/install",
|
|
1640
|
-
docsLabel: "Bun-docs openen",
|
|
1641
|
-
},
|
|
1642
|
-
};
|
|
1643
|
-
const apiEndpointWizardDetails = {
|
|
1644
|
-
deployment: {
|
|
1645
|
-
summary: "Een tijdelijke URL die je later door je echte API-origin vervangt.",
|
|
1646
|
-
explanation: "Kies dit voor een nieuw project dat nog niet aan een lokale server is gekoppeld. De gegenereerde configuratie bevat een duidelijke deployment-placeholder.",
|
|
1647
|
-
docsUrl: DOCS_AUTH,
|
|
1648
|
-
docsLabel: "Deploymentdocs openen",
|
|
1649
|
-
},
|
|
1650
|
-
local: {
|
|
1651
|
-
summary: "Verbind met de lokale API op http://localhost:3210.",
|
|
1652
|
-
explanation: "Kies dit wanneer je RemoteDraw lokaal draait en de ontvanger op dezelfde computer de ontwikkelserver kan bereiken.",
|
|
1653
|
-
docsUrl: DOCS_AUTH,
|
|
1654
|
-
docsLabel: "Lokale API-docs openen",
|
|
1655
|
-
},
|
|
1656
|
-
};
|
|
1657
1887
|
function agentOptionCatalog() {
|
|
1658
1888
|
const catalogOption = (choiceOption, compatibility) => ({
|
|
1659
1889
|
value: choiceOption.value,
|
|
@@ -1662,13 +1892,13 @@ function agentOptionCatalog() {
|
|
|
1662
1892
|
details: choiceOption.details,
|
|
1663
1893
|
...(compatibility ?? {}),
|
|
1664
1894
|
});
|
|
1665
|
-
const targets = wizardChoices(targetPromptChoices(), targetWizardDetails);
|
|
1895
|
+
const targets = wizardChoices(targetPromptChoices(), targetWizardDetails());
|
|
1666
1896
|
const senders = senderChoices.map((value) => {
|
|
1667
1897
|
const compatibleTargets = targetChoices.filter((target) => senderPromptChoices(target).some((option) => option.value === value));
|
|
1668
1898
|
const prompt = compatibleTargets
|
|
1669
1899
|
.flatMap((target) => senderPromptChoices(target))
|
|
1670
1900
|
.find((option) => option.value === value);
|
|
1671
|
-
return catalogOption(wizardChoices([prompt], senderWizardDetails)[0], {
|
|
1901
|
+
return catalogOption(wizardChoices([prompt], senderWizardDetails())[0], {
|
|
1672
1902
|
compatibleTargets,
|
|
1673
1903
|
});
|
|
1674
1904
|
});
|
|
@@ -1678,7 +1908,7 @@ function agentOptionCatalog() {
|
|
|
1678
1908
|
: []));
|
|
1679
1909
|
const firstPlan = compatiblePlans[0];
|
|
1680
1910
|
const prompt = sdkPromptChoices(firstPlan.target, firstPlan.sender).find((option) => option.value === value);
|
|
1681
|
-
return catalogOption(wizardChoices([prompt], sdkWizardDetails)[0], {
|
|
1911
|
+
return catalogOption(wizardChoices([prompt], sdkWizardDetails())[0], {
|
|
1682
1912
|
compatiblePlans,
|
|
1683
1913
|
});
|
|
1684
1914
|
});
|
|
@@ -1690,7 +1920,7 @@ function agentOptionCatalog() {
|
|
|
1690
1920
|
{
|
|
1691
1921
|
key: "appName",
|
|
1692
1922
|
flag: "--app-name",
|
|
1693
|
-
label: "
|
|
1923
|
+
label: t("field.appName"),
|
|
1694
1924
|
kind: "text",
|
|
1695
1925
|
requiredFor: ["new"],
|
|
1696
1926
|
options: [],
|
|
@@ -1698,48 +1928,44 @@ function agentOptionCatalog() {
|
|
|
1698
1928
|
{
|
|
1699
1929
|
key: "target",
|
|
1700
1930
|
flag: "--target",
|
|
1701
|
-
label: "
|
|
1931
|
+
label: t("field.target"),
|
|
1702
1932
|
kind: "select",
|
|
1703
1933
|
options: targets.map((option) => catalogOption(option)),
|
|
1704
1934
|
},
|
|
1705
1935
|
{
|
|
1706
1936
|
key: "sender",
|
|
1707
1937
|
flag: "--sender",
|
|
1708
|
-
label: "
|
|
1938
|
+
label: t("field.sender"),
|
|
1709
1939
|
kind: "select",
|
|
1710
1940
|
options: senders,
|
|
1711
1941
|
},
|
|
1712
1942
|
{
|
|
1713
1943
|
key: "sdk",
|
|
1714
1944
|
flag: "--sdk",
|
|
1715
|
-
label: "
|
|
1945
|
+
label: t("field.sdk"),
|
|
1716
1946
|
kind: "select",
|
|
1717
1947
|
options: sdks,
|
|
1718
1948
|
},
|
|
1719
1949
|
{
|
|
1720
1950
|
key: "preset",
|
|
1721
1951
|
flag: "--preset",
|
|
1722
|
-
label: "
|
|
1952
|
+
label: t("field.preset"),
|
|
1723
1953
|
kind: "select",
|
|
1724
|
-
options: wizardChoices(presetPromptChoices(), presetWizardDetails).map((option) => catalogOption(option)),
|
|
1954
|
+
options: wizardChoices(presetPromptChoices(), presetWizardDetails()).map((option) => catalogOption(option)),
|
|
1725
1955
|
},
|
|
1726
1956
|
{
|
|
1727
1957
|
key: "packageManager",
|
|
1728
1958
|
flag: "--package-manager",
|
|
1729
|
-
label: "
|
|
1959
|
+
label: t("field.packageManager"),
|
|
1730
1960
|
kind: "select",
|
|
1731
|
-
options: wizardChoices(packageManagerPromptChoices(), packageManagerWizardDetails).map((option) => catalogOption(option)),
|
|
1961
|
+
options: wizardChoices(packageManagerPromptChoices(), packageManagerWizardDetails()).map((option) => catalogOption(option)),
|
|
1732
1962
|
},
|
|
1733
1963
|
{
|
|
1734
1964
|
key: "apiBaseUrl",
|
|
1735
1965
|
flag: "--api-base-url",
|
|
1736
|
-
label: "
|
|
1737
|
-
kind: "
|
|
1738
|
-
options:
|
|
1739
|
-
...catalogOption(option),
|
|
1740
|
-
endpoint: option.value,
|
|
1741
|
-
value: apiBaseUrlForEndpoint(option.value),
|
|
1742
|
-
})),
|
|
1966
|
+
label: t("field.apiBaseUrl"),
|
|
1967
|
+
kind: "text",
|
|
1968
|
+
options: [],
|
|
1743
1969
|
},
|
|
1744
1970
|
],
|
|
1745
1971
|
defaults: {
|
|
@@ -1749,9 +1975,9 @@ function agentOptionCatalog() {
|
|
|
1749
1975
|
`${target}:${sender.value}`,
|
|
1750
1976
|
defaultSdkForChoices(target, sender.value),
|
|
1751
1977
|
]))),
|
|
1752
|
-
preset: "
|
|
1978
|
+
preset: "sketch",
|
|
1753
1979
|
packageManager: "npm",
|
|
1754
|
-
apiBaseUrl:
|
|
1980
|
+
apiBaseUrl: DEFAULT_REMOTEDRAW_API_BASE_URL,
|
|
1755
1981
|
},
|
|
1756
1982
|
safety: {
|
|
1757
1983
|
dryRunFlag: "--dry-run",
|
|
@@ -1761,11 +1987,6 @@ function agentOptionCatalog() {
|
|
|
1761
1987
|
},
|
|
1762
1988
|
};
|
|
1763
1989
|
}
|
|
1764
|
-
function apiBaseUrlForEndpoint(endpoint) {
|
|
1765
|
-
if (endpoint === "local")
|
|
1766
|
-
return "http://localhost:3210";
|
|
1767
|
-
return "https://<deployment>.convex.site";
|
|
1768
|
-
}
|
|
1769
1990
|
export function projectSetupDefinition() {
|
|
1770
1991
|
return {
|
|
1771
1992
|
defaults: {
|
|
@@ -1773,9 +1994,8 @@ export function projectSetupDefinition() {
|
|
|
1773
1994
|
target: "web",
|
|
1774
1995
|
sender: "remotedraw-ios",
|
|
1775
1996
|
sdk: "react",
|
|
1776
|
-
preset: "
|
|
1997
|
+
preset: "sketch",
|
|
1777
1998
|
packageManager: "npm",
|
|
1778
|
-
apiEndpoint: "deployment",
|
|
1779
1999
|
},
|
|
1780
2000
|
fields: projectSetupFields(),
|
|
1781
2001
|
normalize(values, changed) {
|
|
@@ -1799,13 +2019,13 @@ export function projectSetupDefinition() {
|
|
|
1799
2019
|
const plan = planFromWizardValues(values);
|
|
1800
2020
|
const label = (key, fallback) => wizardChoiceLabel(key, values) ?? fallback;
|
|
1801
2021
|
return [
|
|
1802
|
-
["
|
|
1803
|
-
["
|
|
1804
|
-
["
|
|
1805
|
-
["
|
|
1806
|
-
["
|
|
1807
|
-
["
|
|
1808
|
-
["
|
|
2022
|
+
[t("field.project"), plan.appName],
|
|
2023
|
+
[t("field.folder"), `./${plan.slug}`],
|
|
2024
|
+
[t("field.target"), label("target", plan.target)],
|
|
2025
|
+
[t("field.sender"), label("sender", plan.sender)],
|
|
2026
|
+
[t("field.sdkShort"), label("sdk", plan.sdk)],
|
|
2027
|
+
[t("field.packageManager"), label("packageManager", plan.packageManager)],
|
|
2028
|
+
[t("field.apiBaseUrl"), plan.apiBaseUrl],
|
|
1809
2029
|
];
|
|
1810
2030
|
},
|
|
1811
2031
|
};
|
|
@@ -1814,42 +2034,36 @@ function projectSetupFields() {
|
|
|
1814
2034
|
return [
|
|
1815
2035
|
{
|
|
1816
2036
|
key: "appName",
|
|
1817
|
-
label: "
|
|
2037
|
+
label: t("field.appName"),
|
|
1818
2038
|
kind: "text",
|
|
1819
2039
|
validate: validateWizardProjectName,
|
|
1820
2040
|
preview: (values) => values.appName.trim() === ""
|
|
1821
2041
|
? undefined
|
|
1822
|
-
:
|
|
2042
|
+
: t("field.appName.preview", { slug: slugify(values.appName) }),
|
|
1823
2043
|
},
|
|
1824
2044
|
{
|
|
1825
2045
|
key: "target",
|
|
1826
|
-
label: "
|
|
2046
|
+
label: t("field.target"),
|
|
1827
2047
|
kind: "select",
|
|
1828
|
-
choices: () => wizardChoices(targetPromptChoices(), targetWizardDetails),
|
|
2048
|
+
choices: () => wizardChoices(targetPromptChoices(), targetWizardDetails()),
|
|
1829
2049
|
},
|
|
1830
2050
|
{
|
|
1831
2051
|
key: "sender",
|
|
1832
|
-
label: "
|
|
2052
|
+
label: t("field.sender"),
|
|
1833
2053
|
kind: "select",
|
|
1834
|
-
choices: (values) => wizardChoices(senderPromptChoices(choice(values.target, targetChoices, "target")), senderWizardDetails),
|
|
2054
|
+
choices: (values) => wizardChoices(senderPromptChoices(choice(values.target, targetChoices, "target")), senderWizardDetails()),
|
|
1835
2055
|
},
|
|
1836
2056
|
{
|
|
1837
2057
|
key: "sdk",
|
|
1838
|
-
label: "
|
|
2058
|
+
label: t("field.sdk"),
|
|
1839
2059
|
kind: "select",
|
|
1840
|
-
choices: (values) => wizardChoices(sdkPromptChoices(choice(values.target, targetChoices, "target"), choice(values.sender, senderChoices, "sender")), sdkWizardDetails),
|
|
2060
|
+
choices: (values) => wizardChoices(sdkPromptChoices(choice(values.target, targetChoices, "target"), choice(values.sender, senderChoices, "sender")), sdkWizardDetails()),
|
|
1841
2061
|
},
|
|
1842
2062
|
{
|
|
1843
2063
|
key: "packageManager",
|
|
1844
|
-
label: "
|
|
1845
|
-
kind: "select",
|
|
1846
|
-
choices: () => wizardChoices(packageManagerPromptChoices(), packageManagerWizardDetails),
|
|
1847
|
-
},
|
|
1848
|
-
{
|
|
1849
|
-
key: "apiEndpoint",
|
|
1850
|
-
label: "API-eindpunt",
|
|
2064
|
+
label: t("field.packageManager"),
|
|
1851
2065
|
kind: "select",
|
|
1852
|
-
choices: () => wizardChoices(
|
|
2066
|
+
choices: () => wizardChoices(packageManagerPromptChoices(), packageManagerWizardDetails()),
|
|
1853
2067
|
},
|
|
1854
2068
|
];
|
|
1855
2069
|
}
|
|
@@ -1859,62 +2073,7 @@ function wizardChoiceLabel(key, values) {
|
|
|
1859
2073
|
?.label;
|
|
1860
2074
|
}
|
|
1861
2075
|
function validateWizardProjectName(value) {
|
|
1862
|
-
return value.trim() === "" ? "
|
|
1863
|
-
}
|
|
1864
|
-
const dutchWizardText = {
|
|
1865
|
-
"Web app": "Webapp",
|
|
1866
|
-
"Desktop or custom app": "Desktop- of maatwerkapp",
|
|
1867
|
-
"iOS sender app": "iOS-zenderapp",
|
|
1868
|
-
"Headless service": "Headless-service",
|
|
1869
|
-
"Own iOS sender": "Eigen iOS-zender",
|
|
1870
|
-
"Use Swift join-link and sender request helpers.": "Gebruik Swift-helpers voor deelnamelinks en zenderverzoeken.",
|
|
1871
|
-
"Headless/custom sender": "Headless- of maatwerkzender",
|
|
1872
|
-
"No hosted phone UI.": "Geen gehoste telefooninterface.",
|
|
1873
|
-
"RemoteDraw iOS app": "RemoteDraw-iOS-app",
|
|
1874
|
-
"Show a join URL or QR code from your receiver.": "Toon vanuit je ontvanger een deelname-URL of QR-code.",
|
|
1875
|
-
"Build directly on the HTTP sender routes.": "Bouw rechtstreeks op de HTTP-zenderroutes.",
|
|
1876
|
-
"Fastest path: scan the receiver QR code.": "Snelste route: scan de QR-code van de ontvanger.",
|
|
1877
|
-
"Own web sender": "Eigen webzender",
|
|
1878
|
-
"Use a web sender component or raw sender helpers.": "Gebruik een webzendercomponent of losse zenderhelpers.",
|
|
1879
|
-
"Swift helpers": "Swift-helpers",
|
|
1880
|
-
"For customer-owned iOS sender apps.": "Voor iOS-zenderapps in eigen beheer.",
|
|
1881
|
-
"JavaScript client": "JavaScript-client",
|
|
1882
|
-
"Framework-free HTTP clients and protocol types.": "Frameworkvrije HTTP-clients en protocoltypen.",
|
|
1883
|
-
"Config only": "Alleen configuratie",
|
|
1884
|
-
"No UI package dependency.": "Geen afhankelijkheid van een UI-pakket.",
|
|
1885
|
-
"Receiver, pairing, and optional embedded sender components.": "Componenten voor ontvanger, koppeling en optionele ingebouwde zender.",
|
|
1886
|
-
"Svelte receiver store plus shared HTTP clients.": "Svelte-store voor de ontvanger met gedeelde HTTP-clients.",
|
|
1887
|
-
"No framework": "Geen framework",
|
|
1888
|
-
"Framework-free clients and raw HTTP starter files.": "Frameworkvrije clients en kale HTTP-startbestanden.",
|
|
1889
|
-
"Signature strip": "Handtekeningstrook",
|
|
1890
|
-
"Product-ready branded field with exact surface mapping.": "Productklaar merkveld met exacte oppervlaktekoppeling.",
|
|
1891
|
-
"Custom drawing surface": "Eigen tekenoppervlak",
|
|
1892
|
-
"Flexible normalized input for your own receiver UI.": "Flexibele genormaliseerde invoer voor je eigen ontvanger-UI.",
|
|
1893
|
-
Signature: "Handtekening",
|
|
1894
|
-
Initials: "Initialen",
|
|
1895
|
-
Approval: "Goedkeuring",
|
|
1896
|
-
Sketch: "Schets",
|
|
1897
|
-
"Photo markup": "Fotoannotatie",
|
|
1898
|
-
"PDF markup": "PDF-annotatie",
|
|
1899
|
-
"Map markup": "Kaartannotatie",
|
|
1900
|
-
"Screen markup": "Schermaantekening",
|
|
1901
|
-
"Design review": "Ontwerpbeoordeling",
|
|
1902
|
-
Pointer: "Aanwijzer",
|
|
1903
|
-
"Deployment URL placeholder": "Tijdelijke deployment-URL",
|
|
1904
|
-
"Fill in your Convex site origin later.": "Vul later de oorsprong van je Convex-site in.",
|
|
1905
|
-
"Local dev server": "Lokale ontwikkelserver",
|
|
1906
|
-
"Use http://localhost:3210.": "Gebruik http://localhost:3210.",
|
|
1907
|
-
};
|
|
1908
|
-
function dutchWizardChoices(choices) {
|
|
1909
|
-
return choices.map((choiceOption) => ({
|
|
1910
|
-
...choiceOption,
|
|
1911
|
-
label: dutchWizardText[choiceOption.label] ?? choiceOption.label,
|
|
1912
|
-
...(choiceOption.hint == null
|
|
1913
|
-
? {}
|
|
1914
|
-
: {
|
|
1915
|
-
hint: dutchWizardText[choiceOption.hint] ?? choiceOption.hint,
|
|
1916
|
-
}),
|
|
1917
|
-
}));
|
|
2076
|
+
return value.trim() === "" ? t("field.appName.error") : undefined;
|
|
1918
2077
|
}
|
|
1919
2078
|
function planFromWizardValues(values) {
|
|
1920
2079
|
const appName = values.appName.trim();
|
|
@@ -1929,11 +2088,11 @@ function planFromWizardValues(values) {
|
|
|
1929
2088
|
sdk,
|
|
1930
2089
|
preset: choice(values.preset, presetChoices, "preset"),
|
|
1931
2090
|
packageManager: choice(values.packageManager, packageManagerChoices, "package-manager"),
|
|
1932
|
-
apiBaseUrl:
|
|
2091
|
+
apiBaseUrl: DEFAULT_REMOTEDRAW_API_BASE_URL,
|
|
1933
2092
|
};
|
|
1934
2093
|
const nameError = validateProjectName(plan.appName);
|
|
1935
2094
|
if (nameError)
|
|
1936
|
-
throw
|
|
2095
|
+
throw cliError("MISSING_ARGUMENT", "field.appName.error");
|
|
1937
2096
|
validatePlan(plan);
|
|
1938
2097
|
return plan;
|
|
1939
2098
|
}
|
|
@@ -1942,7 +2101,7 @@ function planFromArgs(parsed, options) {
|
|
|
1942
2101
|
options.defaults?.appName ??
|
|
1943
2102
|
options.defaultAppName;
|
|
1944
2103
|
if (options.appNameRequired && !readString(parsed, "app-name")) {
|
|
1945
|
-
throw
|
|
2104
|
+
throw cliError("MISSING_ARGUMENT", "error.appNameRequired");
|
|
1946
2105
|
}
|
|
1947
2106
|
const target = choice(readString(parsed, "target") ?? options.defaults?.target ?? "web", targetChoices, "target");
|
|
1948
2107
|
const sender = choice(readString(parsed, "sender") ??
|
|
@@ -1951,13 +2110,13 @@ function planFromArgs(parsed, options) {
|
|
|
1951
2110
|
const sdk = choice(readString(parsed, "sdk") ??
|
|
1952
2111
|
options.defaults?.sdk ??
|
|
1953
2112
|
defaultSdkForChoices(target, sender), sdkChoices, "sdk");
|
|
1954
|
-
const preset = choice(readString(parsed, "preset") ?? options.defaults?.preset ?? "
|
|
2113
|
+
const preset = choice(readString(parsed, "preset") ?? options.defaults?.preset ?? "sketch", presetChoices, "preset");
|
|
1955
2114
|
const packageManager = choice(readString(parsed, "package-manager") ??
|
|
1956
2115
|
options.defaults?.packageManager ??
|
|
1957
2116
|
"npm", packageManagerChoices, "package-manager");
|
|
1958
2117
|
const apiBaseUrl = readString(parsed, "api-base-url") ??
|
|
1959
2118
|
options.defaults?.apiBaseUrl ??
|
|
1960
|
-
|
|
2119
|
+
DEFAULT_REMOTEDRAW_API_BASE_URL;
|
|
1961
2120
|
validatePlan({ target, sender, sdk });
|
|
1962
2121
|
return {
|
|
1963
2122
|
appName,
|
|
@@ -1986,22 +2145,26 @@ function defaultSdkForChoices(target, sender) {
|
|
|
1986
2145
|
}
|
|
1987
2146
|
function validatePlan(plan) {
|
|
1988
2147
|
if (plan.sender === "own-ios" && plan.sdk !== "swift") {
|
|
1989
|
-
throw
|
|
2148
|
+
throw cliError("INVALID_COMBINATION", "error.plan.ownIosNeedsSwift");
|
|
1990
2149
|
}
|
|
1991
2150
|
if (plan.sdk === "swift" && plan.sender !== "own-ios") {
|
|
1992
|
-
throw
|
|
2151
|
+
throw cliError("INVALID_COMBINATION", "error.plan.swiftNeedsOwnIos");
|
|
1993
2152
|
}
|
|
1994
2153
|
if (plan.target === "ios" && plan.sender !== "own-ios") {
|
|
1995
|
-
throw
|
|
2154
|
+
throw cliError("INVALID_COMBINATION", "error.plan.iosNeedsOwnIos");
|
|
1996
2155
|
}
|
|
1997
2156
|
if (plan.sender === "headless" && plan.sdk === "react") {
|
|
1998
|
-
throw
|
|
2157
|
+
throw cliError("INVALID_COMBINATION", "error.plan.headlessSenderSdk");
|
|
1999
2158
|
}
|
|
2000
2159
|
}
|
|
2001
2160
|
function choice(value, choices, label) {
|
|
2002
2161
|
if (choices.includes(value))
|
|
2003
2162
|
return value;
|
|
2004
|
-
throw
|
|
2163
|
+
throw cliError("INVALID_ARGUMENT", "error.unknownChoice", {
|
|
2164
|
+
label,
|
|
2165
|
+
value,
|
|
2166
|
+
expected: choices.join(", "),
|
|
2167
|
+
});
|
|
2005
2168
|
}
|
|
2006
2169
|
function resolveOutputDir(cwd, parsed, fallback) {
|
|
2007
2170
|
const requestedPath = readString(parsed, "path") ?? parsed.positionals[0] ?? fallback ?? ".";
|
|
@@ -2023,6 +2186,11 @@ function newProjectFiles(plan) {
|
|
|
2023
2186
|
files.set("tsconfig.json", reactTsconfig());
|
|
2024
2187
|
files.set("vite.config.ts", reactViteConfig());
|
|
2025
2188
|
}
|
|
2189
|
+
else if (plan.sdk !== "swift") {
|
|
2190
|
+
// Without this the starter shipped a TypeScript toolchain and nothing to
|
|
2191
|
+
// point it at, so `tsc` had no config and no way to resolve node globals.
|
|
2192
|
+
files.set("tsconfig.json", nodeTsconfig());
|
|
2193
|
+
}
|
|
2026
2194
|
return files;
|
|
2027
2195
|
}
|
|
2028
2196
|
function initProjectFiles(plan, packageJsonAlreadyExists) {
|
|
@@ -2037,7 +2205,7 @@ function initProjectFiles(plan, packageJsonAlreadyExists) {
|
|
|
2037
2205
|
files.set("src/remotedraw/createRemoteDrawSession.ts", reactBackendSessionTs(plan));
|
|
2038
2206
|
files.set("src/remotedraw/RemoteDrawReceiver.tsx", reactReceiverTsx());
|
|
2039
2207
|
if (plan.sender === "embedded-web") {
|
|
2040
|
-
files.set("src/remotedraw/
|
|
2208
|
+
files.set("src/remotedraw/useRemoteDrawSender.ts", reactSenderTsx());
|
|
2041
2209
|
}
|
|
2042
2210
|
}
|
|
2043
2211
|
else if (plan.sdk === "svelte") {
|
|
@@ -2077,7 +2245,9 @@ async function assertProjectFilesWritable(files, outputDir, runtime, dryRun, for
|
|
|
2077
2245
|
return;
|
|
2078
2246
|
for (const relativePath of files.keys()) {
|
|
2079
2247
|
if (await runtime.exists(path.join(outputDir, relativePath))) {
|
|
2080
|
-
throw
|
|
2248
|
+
throw cliError("FILE_CONFLICT", "error.refuseOverwrite", {
|
|
2249
|
+
file: relativePath,
|
|
2250
|
+
});
|
|
2081
2251
|
}
|
|
2082
2252
|
}
|
|
2083
2253
|
}
|
|
@@ -2146,25 +2316,30 @@ function hasDependency(packageJson, dependencyName) {
|
|
|
2146
2316
|
return (isRecord(packageJson.dependencies) &&
|
|
2147
2317
|
typeof packageJson.dependencies[dependencyName] === "string");
|
|
2148
2318
|
}
|
|
2319
|
+
/**
|
|
2320
|
+
* The RemoteDraw package an sdk choice installs, if any. The js starter calls
|
|
2321
|
+
* the HTTP API with plain fetch and takes only the request/response contract
|
|
2322
|
+
* from `@remotedraw/protocol`; the headless and swift starters take nothing.
|
|
2323
|
+
*/
|
|
2324
|
+
function sdkPackageName(sdk) {
|
|
2325
|
+
if (sdk === "react")
|
|
2326
|
+
return "@remotedraw/react";
|
|
2327
|
+
if (sdk === "svelte")
|
|
2328
|
+
return "@remotedraw/svelte";
|
|
2329
|
+
if (sdk === "js")
|
|
2330
|
+
return "@remotedraw/protocol";
|
|
2331
|
+
return undefined;
|
|
2332
|
+
}
|
|
2149
2333
|
function dependenciesForPlan(plan) {
|
|
2334
|
+
const sdkPackage = sdkPackageName(plan.sdk);
|
|
2335
|
+
const dependencies = sdkPackage
|
|
2336
|
+
? { [sdkPackage]: REMOTEDRAW_SDK_RANGE }
|
|
2337
|
+
: {};
|
|
2150
2338
|
if (plan.sdk === "react") {
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
react: "^19.0.0",
|
|
2154
|
-
"react-dom": "^19.0.0",
|
|
2155
|
-
};
|
|
2156
|
-
}
|
|
2157
|
-
if (plan.sdk === "svelte") {
|
|
2158
|
-
return {
|
|
2159
|
-
"@remotedraw/svelte": "latest",
|
|
2160
|
-
};
|
|
2161
|
-
}
|
|
2162
|
-
if (plan.sdk === "js") {
|
|
2163
|
-
return {
|
|
2164
|
-
"@remotedraw/protocol": "latest",
|
|
2165
|
-
};
|
|
2339
|
+
dependencies.react = "^19.0.0";
|
|
2340
|
+
dependencies["react-dom"] = "^19.0.0";
|
|
2166
2341
|
}
|
|
2167
|
-
return
|
|
2342
|
+
return dependencies;
|
|
2168
2343
|
}
|
|
2169
2344
|
function newPackageJson(plan) {
|
|
2170
2345
|
const dependencies = dependenciesForPlan(plan);
|
|
@@ -2180,15 +2355,24 @@ function newPackageJson(plan) {
|
|
|
2180
2355
|
},
|
|
2181
2356
|
dependencies,
|
|
2182
2357
|
devDependencies: {
|
|
2358
|
+
// src/remotedraw/createRemoteDrawSession.ts reads process.env, so the
|
|
2359
|
+
// starter cannot typecheck without node types.
|
|
2360
|
+
"@types/node": "^25.0.0",
|
|
2183
2361
|
"@types/react": "^19.0.0",
|
|
2184
2362
|
"@types/react-dom": "^19.0.0",
|
|
2185
|
-
"@vitejs/plugin-react": "
|
|
2363
|
+
"@vitejs/plugin-react": "^6.0.0",
|
|
2186
2364
|
typescript: "^6.0.0",
|
|
2187
|
-
vite: "
|
|
2365
|
+
vite: "^8.0.0",
|
|
2188
2366
|
},
|
|
2189
2367
|
};
|
|
2190
2368
|
}
|
|
2191
|
-
|
|
2369
|
+
const manifest = initPackageJson(plan);
|
|
2370
|
+
if (plan.sdk === "swift")
|
|
2371
|
+
return manifest;
|
|
2372
|
+
return {
|
|
2373
|
+
...manifest,
|
|
2374
|
+
scripts: { ...manifest.scripts, typecheck: "tsc --noEmit" },
|
|
2375
|
+
};
|
|
2192
2376
|
}
|
|
2193
2377
|
function initPackageJson(plan) {
|
|
2194
2378
|
return {
|
|
@@ -2201,14 +2385,21 @@ function initPackageJson(plan) {
|
|
|
2201
2385
|
"remotedraw:create-input": "remotedraw create-input --preset " + plan.preset,
|
|
2202
2386
|
},
|
|
2203
2387
|
dependencies: dependenciesForPlan(plan),
|
|
2204
|
-
|
|
2205
|
-
|
|
2206
|
-
|
|
2388
|
+
// The scripts above shell out to `remotedraw`, so the CLI has to be a real
|
|
2389
|
+
// dependency of the project instead of an assumed global install. The swift
|
|
2390
|
+
// starter writes no TypeScript, so it gets no TypeScript toolchain.
|
|
2391
|
+
devDependencies: plan.sdk === "swift"
|
|
2392
|
+
? { "@remotedraw/cli": REMOTEDRAW_CLI_RANGE }
|
|
2393
|
+
: {
|
|
2394
|
+
"@remotedraw/cli": REMOTEDRAW_CLI_RANGE,
|
|
2395
|
+
"@types/node": "^25.0.0",
|
|
2396
|
+
typescript: "^6.0.0",
|
|
2397
|
+
},
|
|
2207
2398
|
};
|
|
2208
2399
|
}
|
|
2209
2400
|
function configJson(plan) {
|
|
2210
2401
|
return {
|
|
2211
|
-
$schema: "https://remotedraw.
|
|
2402
|
+
$schema: "https://www.remotedraw.com/schemas/remotedraw.config.json",
|
|
2212
2403
|
appName: plan.appName,
|
|
2213
2404
|
environment: "dev",
|
|
2214
2405
|
target: plan.target,
|
|
@@ -2220,7 +2411,7 @@ function configJson(plan) {
|
|
|
2220
2411
|
}
|
|
2221
2412
|
function envExample(plan) {
|
|
2222
2413
|
return [
|
|
2223
|
-
"# RemoteDraw API origin.
|
|
2414
|
+
"# RemoteDraw API origin. The hosted RemoteDraw API; leave as-is unless RemoteDraw support tells you otherwise.",
|
|
2224
2415
|
`REMOTEDRAW_API_BASE_URL=${plan.apiBaseUrl}`,
|
|
2225
2416
|
"",
|
|
2226
2417
|
"# Non-secret dashboard project identifier. remotedraw new/init fills this in.",
|
|
@@ -2308,7 +2499,7 @@ function reactReceiverTsx() {
|
|
|
2308
2499
|
"import {",
|
|
2309
2500
|
" RemoteDrawProvider,",
|
|
2310
2501
|
" RemoteDrawReceiver,",
|
|
2311
|
-
"
|
|
2502
|
+
" RemoteDrawSessionControls,",
|
|
2312
2503
|
" createHttpReceiverClient,",
|
|
2313
2504
|
'} from "@remotedraw/react";',
|
|
2314
2505
|
'import type { CreateSessionResponse } from "@remotedraw/react";',
|
|
@@ -2333,36 +2524,130 @@ function reactReceiverTsx() {
|
|
|
2333
2524
|
" receiver={receiver}",
|
|
2334
2525
|
" >",
|
|
2335
2526
|
' <RemoteDrawReceiver aria-label="RemoteDraw receiver surface" />',
|
|
2336
|
-
|
|
2527
|
+
" <RemoteDrawSessionControls />",
|
|
2337
2528
|
" </RemoteDrawProvider>",
|
|
2338
2529
|
" );",
|
|
2339
2530
|
"}",
|
|
2340
2531
|
"",
|
|
2341
2532
|
].join("\n");
|
|
2342
2533
|
}
|
|
2343
|
-
function
|
|
2534
|
+
function reactSenderTsx() {
|
|
2535
|
+
// Headless on purpose: the ready-made web sender is the hosted /join page
|
|
2536
|
+
// (the QR / Universal Link target). This starter is for products that must
|
|
2537
|
+
// host the pad themselves — RemoteDraw transports normalized points while
|
|
2538
|
+
// the canvas, tools, and styling stay app-owned.
|
|
2344
2539
|
return [
|
|
2345
|
-
'import {
|
|
2346
|
-
"",
|
|
2347
|
-
"
|
|
2348
|
-
"
|
|
2349
|
-
"
|
|
2350
|
-
"
|
|
2540
|
+
'import { useEffect, useMemo, useRef, useState } from "react";',
|
|
2541
|
+
"import {",
|
|
2542
|
+
" createHttpSenderClient,",
|
|
2543
|
+
" createPacedDraftQueue,",
|
|
2544
|
+
" draftPointsForTransport,",
|
|
2545
|
+
" joinTokenFromInput,",
|
|
2546
|
+
'} from "@remotedraw/react";',
|
|
2547
|
+
"import type {",
|
|
2548
|
+
" DraftInputUpdate,",
|
|
2549
|
+
" JoinSessionResponse,",
|
|
2550
|
+
" NormalizedPoint,",
|
|
2551
|
+
'} from "@remotedraw/react";',
|
|
2351
2552
|
"",
|
|
2352
|
-
"
|
|
2353
|
-
"
|
|
2354
|
-
"
|
|
2355
|
-
"
|
|
2356
|
-
"
|
|
2357
|
-
"
|
|
2358
|
-
"
|
|
2359
|
-
"
|
|
2360
|
-
"
|
|
2361
|
-
|
|
2362
|
-
' submitMetadata={{ senderLabel: "Embedded sender" }}',
|
|
2363
|
-
' submitLabel="Done"',
|
|
2364
|
-
" />",
|
|
2553
|
+
"// Headless RemoteDraw sender. The hosted /join page is the ready-made",
|
|
2554
|
+
"// pad; use this hook when the drawing surface must live inside your own",
|
|
2555
|
+
"// page. Points are normalized 0..1; the canvas and tools stay yours.",
|
|
2556
|
+
"export function useRemoteDrawSender(",
|
|
2557
|
+
" apiBaseUrl: string,",
|
|
2558
|
+
" joinTokenOrUrl: string,",
|
|
2559
|
+
") {",
|
|
2560
|
+
" const client = useMemo(",
|
|
2561
|
+
" () => createHttpSenderClient(apiBaseUrl),",
|
|
2562
|
+
" [apiBaseUrl],",
|
|
2365
2563
|
" );",
|
|
2564
|
+
" const [joined, setJoined] = useState<JoinSessionResponse | null>(null);",
|
|
2565
|
+
" const [error, setError] = useState<string | null>(null);",
|
|
2566
|
+
" const joinedRef = useRef<JoinSessionResponse | null>(null);",
|
|
2567
|
+
" const sequenceRef = useRef(0);",
|
|
2568
|
+
" // Drafts are throttled and latest-only: never POST every pointer event.",
|
|
2569
|
+
" const draftQueue = useMemo(",
|
|
2570
|
+
" () =>",
|
|
2571
|
+
" createPacedDraftQueue<DraftInputUpdate>({",
|
|
2572
|
+
" intervalMs: 32,",
|
|
2573
|
+
" run: async (request) => {",
|
|
2574
|
+
" await client.updateDraft?.(request);",
|
|
2575
|
+
" },",
|
|
2576
|
+
" }),",
|
|
2577
|
+
" [client],",
|
|
2578
|
+
" );",
|
|
2579
|
+
"",
|
|
2580
|
+
" useEffect(() => {",
|
|
2581
|
+
" const joinToken = joinTokenFromInput(joinTokenOrUrl);",
|
|
2582
|
+
" if (!joinToken) {",
|
|
2583
|
+
' setError("Join token is required.");',
|
|
2584
|
+
" return;",
|
|
2585
|
+
" }",
|
|
2586
|
+
" let cancelled = false;",
|
|
2587
|
+
" client",
|
|
2588
|
+
" .join?.({",
|
|
2589
|
+
" joinToken,",
|
|
2590
|
+
' device: { platform: "web", displayName: "Embedded sender" },',
|
|
2591
|
+
" })",
|
|
2592
|
+
" .then((result) => {",
|
|
2593
|
+
" if (cancelled || !result) return;",
|
|
2594
|
+
" // Resuming a token must continue its sequence: the server drops",
|
|
2595
|
+
" // anything at or below the last sequence it has seen.",
|
|
2596
|
+
" sequenceRef.current = result.lastSequence ?? 0;",
|
|
2597
|
+
" joinedRef.current = result;",
|
|
2598
|
+
" setJoined(result);",
|
|
2599
|
+
" })",
|
|
2600
|
+
" .catch((caught: unknown) => {",
|
|
2601
|
+
" if (!cancelled) setError(String(caught));",
|
|
2602
|
+
" });",
|
|
2603
|
+
" return () => {",
|
|
2604
|
+
" cancelled = true;",
|
|
2605
|
+
" };",
|
|
2606
|
+
" }, [client, joinTokenOrUrl]);",
|
|
2607
|
+
"",
|
|
2608
|
+
" return {",
|
|
2609
|
+
" joined,",
|
|
2610
|
+
" error,",
|
|
2611
|
+
" /** Call on pointermove with the whole in-progress stroke. */",
|
|
2612
|
+
" previewDraft(points: NormalizedPoint[]) {",
|
|
2613
|
+
" const senderToken = joinedRef.current?.senderToken;",
|
|
2614
|
+
" if (!senderToken || points.length === 0) return;",
|
|
2615
|
+
" draftQueue.offer(() => ({",
|
|
2616
|
+
" senderToken,",
|
|
2617
|
+
" sequence: ++sequenceRef.current,",
|
|
2618
|
+
' tool: "freehand" as const,',
|
|
2619
|
+
' pointerType: "touch" as const,',
|
|
2620
|
+
" points: draftPointsForTransport(points),",
|
|
2621
|
+
" occurredAt: Date.now(),",
|
|
2622
|
+
" }));",
|
|
2623
|
+
" },",
|
|
2624
|
+
" /** Call once on pointer-up with a stable id per stroke. */",
|
|
2625
|
+
" async commitStroke(points: NormalizedPoint[], clientStrokeId: string) {",
|
|
2626
|
+
" const senderToken = joinedRef.current?.senderToken;",
|
|
2627
|
+
" if (!senderToken || points.length < 2) return;",
|
|
2628
|
+
" draftQueue.clear();",
|
|
2629
|
+
" await client.commitStroke?.({",
|
|
2630
|
+
" senderToken,",
|
|
2631
|
+
" clientStrokeId,",
|
|
2632
|
+
" sequence: ++sequenceRef.current,",
|
|
2633
|
+
' tool: "freehand",',
|
|
2634
|
+
' pointerType: "touch",',
|
|
2635
|
+
" points,",
|
|
2636
|
+
" occurredAt: Date.now(),",
|
|
2637
|
+
" });",
|
|
2638
|
+
" },",
|
|
2639
|
+
" /** Call when the sender-side workflow is complete. */",
|
|
2640
|
+
" async submit(metadata?: Record<string, string>) {",
|
|
2641
|
+
" const senderToken = joinedRef.current?.senderToken;",
|
|
2642
|
+
" if (!senderToken) return;",
|
|
2643
|
+
" await client.submit?.({",
|
|
2644
|
+
" senderToken,",
|
|
2645
|
+
" clientSubmissionId: crypto.randomUUID(),",
|
|
2646
|
+
" occurredAt: Date.now(),",
|
|
2647
|
+
" ...(metadata ? { metadata } : {}),",
|
|
2648
|
+
" });",
|
|
2649
|
+
" },",
|
|
2650
|
+
" };",
|
|
2366
2651
|
"}",
|
|
2367
2652
|
"",
|
|
2368
2653
|
].join("\n");
|
|
@@ -2440,11 +2725,24 @@ function svelteReceiverSvelte() {
|
|
|
2440
2725
|
].join("\n");
|
|
2441
2726
|
}
|
|
2442
2727
|
function rawHttpSessionTs(plan) {
|
|
2728
|
+
// The js starter declares @remotedraw/protocol, so it uses it: the contract
|
|
2729
|
+
// types are the whole reason that dependency is there. The headless starter
|
|
2730
|
+
// declares nothing and stays import-free.
|
|
2731
|
+
const typed = plan.sdk === "js";
|
|
2443
2732
|
return [
|
|
2733
|
+
...(typed
|
|
2734
|
+
? [
|
|
2735
|
+
"import type {",
|
|
2736
|
+
" CreateSessionRequest,",
|
|
2737
|
+
" CreateSessionResponse,",
|
|
2738
|
+
'} from "@remotedraw/protocol";',
|
|
2739
|
+
"",
|
|
2740
|
+
]
|
|
2741
|
+
: []),
|
|
2444
2742
|
"const apiBaseUrl = process.env.REMOTEDRAW_API_BASE_URL;",
|
|
2445
2743
|
"const apiKey = process.env.REMOTEDRAW_API_KEY;",
|
|
2446
2744
|
"",
|
|
2447
|
-
|
|
2745
|
+
`export function createRemoteDrawSessionRequest()${typed ? ": CreateSessionRequest" : ""} {`,
|
|
2448
2746
|
" return " +
|
|
2449
2747
|
JSON.stringify(createSessionPayload({
|
|
2450
2748
|
preset: plan.preset,
|
|
@@ -2454,7 +2752,7 @@ function rawHttpSessionTs(plan) {
|
|
|
2454
2752
|
";",
|
|
2455
2753
|
"}",
|
|
2456
2754
|
"",
|
|
2457
|
-
|
|
2755
|
+
`export async function createRemoteDrawSession()${typed ? ": Promise<CreateSessionResponse>" : ""} {`,
|
|
2458
2756
|
" if (!apiBaseUrl || !apiKey) {",
|
|
2459
2757
|
' throw new Error("Set REMOTEDRAW_API_BASE_URL and REMOTEDRAW_API_KEY in backend secrets.");',
|
|
2460
2758
|
" }",
|
|
@@ -2468,7 +2766,9 @@ function rawHttpSessionTs(plan) {
|
|
|
2468
2766
|
" body: JSON.stringify(createRemoteDrawSessionRequest()),",
|
|
2469
2767
|
" });",
|
|
2470
2768
|
" if (!response.ok) throw new Error(await response.text());",
|
|
2471
|
-
|
|
2769
|
+
typed
|
|
2770
|
+
? " return (await response.json()) as CreateSessionResponse;"
|
|
2771
|
+
: " return await response.json();",
|
|
2472
2772
|
"}",
|
|
2473
2773
|
"",
|
|
2474
2774
|
].join("\n");
|
|
@@ -2678,6 +2978,9 @@ function reactTsconfig() {
|
|
|
2678
2978
|
' "target": "ES2022",',
|
|
2679
2979
|
' "useDefineForClassFields": true,',
|
|
2680
2980
|
' "lib": ["ES2022", "DOM", "DOM.Iterable"],',
|
|
2981
|
+
// vite/client declares the CSS side-effect import in src/main.tsx; node
|
|
2982
|
+
// covers the process.env reads in the backend session helper.
|
|
2983
|
+
' "types": ["node", "vite/client"],',
|
|
2681
2984
|
' "allowJs": false,',
|
|
2682
2985
|
' "skipLibCheck": true,',
|
|
2683
2986
|
' "esModuleInterop": true,',
|
|
@@ -2695,6 +2998,26 @@ function reactTsconfig() {
|
|
|
2695
2998
|
"",
|
|
2696
2999
|
].join("\n");
|
|
2697
3000
|
}
|
|
3001
|
+
function nodeTsconfig() {
|
|
3002
|
+
return [
|
|
3003
|
+
"{",
|
|
3004
|
+
' "compilerOptions": {',
|
|
3005
|
+
' "target": "ES2022",',
|
|
3006
|
+
' "lib": ["ES2022", "DOM"],',
|
|
3007
|
+
' "types": ["node"],',
|
|
3008
|
+
' "module": "ESNext",',
|
|
3009
|
+
' "moduleResolution": "Bundler",',
|
|
3010
|
+
' "strict": true,',
|
|
3011
|
+
' "skipLibCheck": true,',
|
|
3012
|
+
' "resolveJsonModule": true,',
|
|
3013
|
+
' "isolatedModules": true,',
|
|
3014
|
+
' "noEmit": true',
|
|
3015
|
+
" },",
|
|
3016
|
+
' "include": ["src"]',
|
|
3017
|
+
"}",
|
|
3018
|
+
"",
|
|
3019
|
+
].join("\n");
|
|
3020
|
+
}
|
|
2698
3021
|
function reactViteConfig() {
|
|
2699
3022
|
return [
|
|
2700
3023
|
'import react from "@vitejs/plugin-react";',
|
|
@@ -2714,17 +3037,6 @@ export function createSessionPayload(options) {
|
|
|
2714
3037
|
target: {
|
|
2715
3038
|
kind: targetKindForPreset(options.preset),
|
|
2716
3039
|
label: options.label,
|
|
2717
|
-
...(options.preset === "signature"
|
|
2718
|
-
? {
|
|
2719
|
-
inputMapping: "surface",
|
|
2720
|
-
coordinateSpace: { width: 1600, height: 500 },
|
|
2721
|
-
}
|
|
2722
|
-
: options.preset === "initials"
|
|
2723
|
-
? {
|
|
2724
|
-
inputMapping: "surface",
|
|
2725
|
-
coordinateSpace: { width: 800, height: 500 },
|
|
2726
|
-
}
|
|
2727
|
-
: {}),
|
|
2728
3040
|
metadata: {
|
|
2729
3041
|
descriptor,
|
|
2730
3042
|
},
|
|
@@ -2735,31 +3047,6 @@ export function createSessionPayload(options) {
|
|
|
2735
3047
|
};
|
|
2736
3048
|
}
|
|
2737
3049
|
function descriptorForPreset(preset, label) {
|
|
2738
|
-
if (preset === "signature" || preset === "initials") {
|
|
2739
|
-
return {
|
|
2740
|
-
version: 1,
|
|
2741
|
-
privacy: "metadata-only",
|
|
2742
|
-
surfaceRole: "field",
|
|
2743
|
-
inputIntent: preset,
|
|
2744
|
-
label,
|
|
2745
|
-
components: [
|
|
2746
|
-
{
|
|
2747
|
-
id: preset,
|
|
2748
|
-
role: "field",
|
|
2749
|
-
inputIntent: preset,
|
|
2750
|
-
required: true,
|
|
2751
|
-
bounds: { minX: 0.1, minY: 0.68, maxX: 0.9, maxY: 0.86 },
|
|
2752
|
-
},
|
|
2753
|
-
],
|
|
2754
|
-
phoneHints: {
|
|
2755
|
-
preferredLayout: "signature-pad",
|
|
2756
|
-
preferredTool: "freehand",
|
|
2757
|
-
primaryActionLabel: "Submit",
|
|
2758
|
-
showUndo: true,
|
|
2759
|
-
showClear: true,
|
|
2760
|
-
},
|
|
2761
|
-
};
|
|
2762
|
-
}
|
|
2763
3050
|
if (preset === "approval") {
|
|
2764
3051
|
return {
|
|
2765
3052
|
version: 1,
|
|
@@ -2790,11 +3077,8 @@ function descriptorForPreset(preset, label) {
|
|
|
2790
3077
|
};
|
|
2791
3078
|
}
|
|
2792
3079
|
function targetKindForPreset(preset) {
|
|
2793
|
-
if (preset === "
|
|
2794
|
-
preset === "initials" ||
|
|
2795
|
-
preset === "approval") {
|
|
3080
|
+
if (preset === "approval")
|
|
2796
3081
|
return "field";
|
|
2797
|
-
}
|
|
2798
3082
|
if (preset === "photoMarkup" || preset === "designReview")
|
|
2799
3083
|
return "image";
|
|
2800
3084
|
if (preset === "pdfMarkup")
|
|
@@ -2818,8 +3102,6 @@ function surfaceRoleForPreset(preset) {
|
|
|
2818
3102
|
}
|
|
2819
3103
|
function labelForPreset(preset) {
|
|
2820
3104
|
const labels = {
|
|
2821
|
-
signature: "Customer signature",
|
|
2822
|
-
initials: "Customer initials",
|
|
2823
3105
|
approval: "Quick approval",
|
|
2824
3106
|
sketch: "Sketch input",
|
|
2825
3107
|
photoMarkup: "Photo annotation",
|
|
@@ -2848,10 +3130,10 @@ function shellQuote(value) {
|
|
|
2848
3130
|
function normalizedOrigin(value) {
|
|
2849
3131
|
const parsed = new URL(value);
|
|
2850
3132
|
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
2851
|
-
throw
|
|
3133
|
+
throw cliError("INVALID_ARGUMENT", "error.expectedHttpOrigin");
|
|
2852
3134
|
}
|
|
2853
3135
|
if (parsed.pathname !== "/" || parsed.search || parsed.hash) {
|
|
2854
|
-
throw
|
|
3136
|
+
throw cliError("INVALID_ARGUMENT", "error.expectedBareOrigin");
|
|
2855
3137
|
}
|
|
2856
3138
|
return parsed.origin;
|
|
2857
3139
|
}
|
|
@@ -2866,95 +3148,20 @@ function resolveAgentPath(value, runtime) {
|
|
|
2866
3148
|
if (value.startsWith("~/")) {
|
|
2867
3149
|
const home = runtime.env.HOME;
|
|
2868
3150
|
if (home == null) {
|
|
2869
|
-
throw
|
|
3151
|
+
throw cliError("INVALID_ARGUMENT", "error.noHome");
|
|
2870
3152
|
}
|
|
2871
3153
|
return path.join(home, value.slice(2));
|
|
2872
3154
|
}
|
|
2873
3155
|
return path.resolve(runtime.cwd, value);
|
|
2874
3156
|
}
|
|
3157
|
+
/**
|
|
3158
|
+
* The skill a coding agent installs is the same file this repository
|
|
3159
|
+
* documents. `docs/agents/remotedraw/SKILL.md` is the single source; the
|
|
3160
|
+
* module below is generated from it by `bun run generate:skill`, and
|
|
3161
|
+
* tests/agent-skill.test.ts fails if the two drift apart again.
|
|
3162
|
+
*/
|
|
2875
3163
|
export function agentSkillMarkdown() {
|
|
2876
|
-
return
|
|
2877
|
-
"---",
|
|
2878
|
-
"name: remotedraw",
|
|
2879
|
-
"description: Add RemoteDraw phone input to customer apps with the RemoteDraw CLI, public API, React SDK, raw HTTP, or customer-owned iOS sender flow.",
|
|
2880
|
-
"---",
|
|
2881
|
-
"",
|
|
2882
|
-
"# RemoteDraw Agent Skill",
|
|
2883
|
-
"",
|
|
2884
|
-
"Use this skill when a user asks to create, initialize, debug, or review a RemoteDraw integration.",
|
|
2885
|
-
"",
|
|
2886
|
-
"## Decision Flow",
|
|
2887
|
-
"",
|
|
2888
|
-
"1. Identify the receiver surface: web app, desktop app, iOS app, or headless/backend workflow.",
|
|
2889
|
-
"2. Identify the sender surface: RemoteDraw iOS app, embedded web sender, customer-owned iOS sender, or raw/headless sender.",
|
|
2890
|
-
"3. Pick the SDK path:",
|
|
2891
|
-
" - React SDK: web receiver and optional embedded web sender.",
|
|
2892
|
-
" - Plain JavaScript/raw HTTP: non-React web, desktop, backend, or custom clients.",
|
|
2893
|
-
" - Swift: customer-owned iOS sender apps.",
|
|
2894
|
-
"4. Pick a supported starter: signature for the product-ready SignatureStrip, or sketch for a receiver UI owned by the integrating app. Configure target kind and descriptors directly for photos, PDFs, maps, screens, and other custom surfaces.",
|
|
2895
|
-
"",
|
|
2896
|
-
"## CLI First",
|
|
2897
|
-
"",
|
|
2898
|
-
"Read the machine-readable option catalog before choosing a plan:",
|
|
2899
|
-
"",
|
|
2900
|
-
"```sh",
|
|
2901
|
-
"remotedraw options --format json",
|
|
2902
|
-
"```",
|
|
2903
|
-
"",
|
|
2904
|
-
"Initialize a project with the closest supported path:",
|
|
2905
|
-
"",
|
|
2906
|
-
"```sh",
|
|
2907
|
-
"remotedraw init --target web --sender remotedraw-ios --sdk react --preset signature",
|
|
2908
|
-
"remotedraw init --target web --sender embedded-web --sdk react --preset sketch",
|
|
2909
|
-
"remotedraw init --target desktop --sender remotedraw-ios --sdk js --preset sketch",
|
|
2910
|
-
"remotedraw init --target ios --sender own-ios --sdk swift --preset sketch",
|
|
2911
|
-
"```",
|
|
2912
|
-
"",
|
|
2913
|
-
"By default, new/init create the dashboard project and a project-scoped development API key, then write REMOTEDRAW_API_BASE_URL, REMOTEDRAW_PROJECT_ID, and REMOTEDRAW_API_KEY to a gitignored .env.local. Use --offline only when cloud setup is intentionally out of scope.",
|
|
2914
|
-
"",
|
|
2915
|
-
"Agents must use explicit non-interactive dry runs before changing a project:",
|
|
2916
|
-
"",
|
|
2917
|
-
"```sh",
|
|
2918
|
-
"remotedraw init --non-interactive --offline --dry-run --format json --target web --sender remotedraw-ios --sdk react --preset signature --package-manager npm",
|
|
2919
|
-
"# Inspect plan, files, defaultsApplied, and warnings before applying.",
|
|
2920
|
-
"remotedraw init --non-interactive --offline --format json --target web --sender remotedraw-ios --sdk react --preset signature --package-manager npm",
|
|
2921
|
-
"remotedraw doctor --format json",
|
|
2922
|
-
"remotedraw create-input --preset signature --json",
|
|
2923
|
-
"```",
|
|
2924
|
-
"",
|
|
2925
|
-
"Do not use the interactive wizard, synthesize arrow-key input, or scrape human-formatted output. If receiver, sender, or preset intent is ambiguous, ask the user instead of guessing. Never pass --force unless overwrite scope was explicitly approved.",
|
|
2926
|
-
"",
|
|
2927
|
-
"## Security Rules",
|
|
2928
|
-
"",
|
|
2929
|
-
"- Keep `rd_sk_...` API keys in trusted backend secrets only.",
|
|
2930
|
-
"- 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.",
|
|
2931
|
-
"- Never place API keys in browser bundles, mobile clients, screenshots, logs, or generated examples.",
|
|
2932
|
-
"- Public clients should receive only `joinUrl`, `joinToken`, `receiverToken`, or `senderToken` values scoped to the session.",
|
|
2933
|
-
"- Production QR codes should use HTTPS `joinUrl` values. Do not make the custom scheme the primary QR target.",
|
|
2934
|
-
"",
|
|
2935
|
-
"## API Contract",
|
|
2936
|
-
"",
|
|
2937
|
-
"- Backend creates sessions with `POST /v1/sessions`.",
|
|
2938
|
-
"- Receiver clients read `POST /v1/receiver/session`, `/drawings`, `/drafts`, and `/senders` with a receiver token.",
|
|
2939
|
-
"- 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`.",
|
|
2940
|
-
"- Custom senders should throttle draft updates, coalesce to the latest pending preview, and commit one durable stroke on pointer-up with a stable `clientStrokeId`.",
|
|
2941
|
-
"",
|
|
2942
|
-
"## Verification",
|
|
2943
|
-
"",
|
|
2944
|
-
"After changes, run the narrowest relevant checks first:",
|
|
2945
|
-
"",
|
|
2946
|
-
"```sh",
|
|
2947
|
-
"remotedraw doctor",
|
|
2948
|
-
"bun run test:api",
|
|
2949
|
-
"bun run typecheck",
|
|
2950
|
-
"```",
|
|
2951
|
-
"",
|
|
2952
|
-
"For customer-owned iOS sender helpers in this repo, also run:",
|
|
2953
|
-
"",
|
|
2954
|
-
"```sh",
|
|
2955
|
-
"bun run ios:kit:test",
|
|
2956
|
-
"```",
|
|
2957
|
-
].join("\n");
|
|
3164
|
+
return AGENT_SKILL_MARKDOWN;
|
|
2958
3165
|
}
|
|
2959
3166
|
/**
|
|
2960
3167
|
* Doctor used to read REMOTEDRAW_API_BASE_URL raw and call an unset value a
|
|
@@ -2991,20 +3198,30 @@ function resolveDoctorApiBaseUrl(projectEnv, requested) {
|
|
|
2991
3198
|
}
|
|
2992
3199
|
function doctorApiBaseUrlMessage(resolved) {
|
|
2993
3200
|
if (resolved.state === "placeholder") {
|
|
2994
|
-
return
|
|
3201
|
+
return t("doctor.baseUrl.placeholder", {
|
|
3202
|
+
value: resolved.value,
|
|
3203
|
+
defaultUrl: DEFAULT_REMOTEDRAW_API_BASE_URL,
|
|
3204
|
+
});
|
|
2995
3205
|
}
|
|
2996
3206
|
if (resolved.state === "invalid") {
|
|
2997
|
-
return
|
|
3207
|
+
return t("doctor.baseUrl.invalid", {
|
|
3208
|
+
value: resolved.value,
|
|
3209
|
+
defaultUrl: DEFAULT_REMOTEDRAW_API_BASE_URL,
|
|
3210
|
+
});
|
|
2998
3211
|
}
|
|
2999
3212
|
if (resolved.source === "default") {
|
|
3000
|
-
return
|
|
3213
|
+
return t("doctor.baseUrl.default", { url: resolved.url });
|
|
3001
3214
|
}
|
|
3002
3215
|
const origin = resolved.source === "flag"
|
|
3003
|
-
? "
|
|
3004
|
-
: "
|
|
3216
|
+
? t("doctor.baseUrl.origin.flag")
|
|
3217
|
+
: t("doctor.baseUrl.origin.project");
|
|
3005
3218
|
return resolved.isProductionDefault
|
|
3006
|
-
?
|
|
3007
|
-
:
|
|
3219
|
+
? t("doctor.baseUrl.production", { url: resolved.url, origin })
|
|
3220
|
+
: t("doctor.baseUrl.override", {
|
|
3221
|
+
url: resolved.url,
|
|
3222
|
+
origin,
|
|
3223
|
+
defaultUrl: DEFAULT_REMOTEDRAW_API_BASE_URL,
|
|
3224
|
+
});
|
|
3008
3225
|
}
|
|
3009
3226
|
function isHttpOrigin(value) {
|
|
3010
3227
|
if (value == null || value.includes("<"))
|
|
@@ -3041,28 +3258,31 @@ function planDefaultsForExample(example) {
|
|
|
3041
3258
|
target: "web",
|
|
3042
3259
|
sender: "remotedraw-ios",
|
|
3043
3260
|
sdk: "react",
|
|
3044
|
-
preset: "
|
|
3261
|
+
preset: "sketch",
|
|
3045
3262
|
};
|
|
3046
3263
|
}
|
|
3047
3264
|
function scaffoldSummary(verb, plan, outputDir, written, dryRun) {
|
|
3048
|
-
const nextCommandPrefix = dryRun ? "Would write" : "Wrote";
|
|
3049
3265
|
return [
|
|
3050
|
-
|
|
3051
|
-
|
|
3052
|
-
|
|
3053
|
-
|
|
3054
|
-
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
"",
|
|
3058
|
-
|
|
3266
|
+
t("scaffold.headline", {
|
|
3267
|
+
verb,
|
|
3268
|
+
target: plan.target,
|
|
3269
|
+
dir: outputDir,
|
|
3270
|
+
}),
|
|
3271
|
+
"",
|
|
3272
|
+
t("scaffold.choices"),
|
|
3273
|
+
t("scaffold.choice.app", { value: plan.appName }),
|
|
3274
|
+
t("scaffold.choice.sdk", { value: plan.sdk }),
|
|
3275
|
+
t("scaffold.choice.sender", { value: plan.sender }),
|
|
3276
|
+
t("scaffold.choice.starter", { value: plan.preset }),
|
|
3277
|
+
"",
|
|
3278
|
+
dryRun ? t("scaffold.wouldWrite") : t("scaffold.wrote"),
|
|
3059
3279
|
...written.map((file) => ` ${file}`),
|
|
3060
3280
|
"",
|
|
3061
|
-
"
|
|
3062
|
-
|
|
3063
|
-
"
|
|
3064
|
-
"
|
|
3065
|
-
"
|
|
3281
|
+
t("word.next"),
|
|
3282
|
+
t("scaffold.next.install", { command: installCommand(plan.packageManager) }),
|
|
3283
|
+
t("scaffold.next.secrets"),
|
|
3284
|
+
t("scaffold.next.createInput"),
|
|
3285
|
+
t("scaffold.next.render"),
|
|
3066
3286
|
].join("\n");
|
|
3067
3287
|
}
|
|
3068
3288
|
function scaffoldJsonResult(command, plan, outputDir, files, parsed, provisioning, offline, dryRun) {
|
|
@@ -3080,16 +3300,12 @@ function scaffoldJsonResult(command, plan, outputDir, files, parsed, provisionin
|
|
|
3080
3300
|
.map(([field, , value]) => ({ field, value }));
|
|
3081
3301
|
const warnings = [
|
|
3082
3302
|
...(plan.apiBaseUrl.includes("<deployment>")
|
|
3083
|
-
? ["
|
|
3303
|
+
? [t("cloud.warning.placeholder")]
|
|
3084
3304
|
: []),
|
|
3085
3305
|
...(offline
|
|
3086
|
-
? [
|
|
3087
|
-
"Cloud provisioning was skipped; no dashboard project or API key was created.",
|
|
3088
|
-
]
|
|
3306
|
+
? [t("cloud.warning.offline")]
|
|
3089
3307
|
: dryRun
|
|
3090
|
-
? [
|
|
3091
|
-
"Cloud authentication and provisioning were not executed during dry-run.",
|
|
3092
|
-
]
|
|
3308
|
+
? [t("cloud.warning.dryRun")]
|
|
3093
3309
|
: []),
|
|
3094
3310
|
];
|
|
3095
3311
|
return {
|