@remotedraw/cli 0.1.3 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +56 -1
- package/dist/cli.d.ts +11 -3
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +1020 -749
- 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 +420 -0
- package/dist/locales/en.d.ts.map +1 -0
- package/dist/locales/en.js +446 -0
- package/dist/locales/index.d.ts +436 -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 +438 -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/cloud.d.ts
CHANGED
|
@@ -40,6 +40,19 @@ export type ProvisionedProject = {
|
|
|
40
40
|
};
|
|
41
41
|
export declare function remoteDrawApiBaseUrl(env: Record<string, string | undefined>, requested?: string): string;
|
|
42
42
|
export declare function globalConfigPath(runtime: CloudRuntime): string;
|
|
43
|
+
/**
|
|
44
|
+
* The stored CLI language, or undefined when the reader has never chosen one.
|
|
45
|
+
* A damaged or unreadable config never blocks a command here — the caller
|
|
46
|
+
* simply falls back to English — because the language is a preference, not a
|
|
47
|
+
* credential.
|
|
48
|
+
*/
|
|
49
|
+
export declare function readStoredLanguage(runtime: CloudRuntime): Promise<string | undefined>;
|
|
50
|
+
/**
|
|
51
|
+
* Persists the language into the same config.json (and same 0600 directory)
|
|
52
|
+
* that already holds the CLI credential, so there is one user-config file to
|
|
53
|
+
* find, back up, or delete.
|
|
54
|
+
*/
|
|
55
|
+
export declare function writeStoredLanguage(runtime: CloudRuntime, language: string): Promise<void>;
|
|
43
56
|
export declare function currentCliAccount(runtime: CloudRuntime, apiBaseUrl: string): Promise<CliAccount | undefined>;
|
|
44
57
|
export declare function loginToRemoteDraw(runtime: CloudRuntime, apiBaseUrl: string, options?: {
|
|
45
58
|
openBrowser?: boolean;
|
package/dist/cloud.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../src/cloud.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"cloud.d.ts","sourceRoot":"","sources":["../src/cloud.ts"],"names":[],"mappings":"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"}
|
package/dist/cloud.js
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { homedir } from "node:os";
|
|
2
2
|
import path from "node:path";
|
|
3
|
+
import { cliError } from "./errors.js";
|
|
4
|
+
import { t } from "./i18n.js";
|
|
3
5
|
export const DEFAULT_REMOTEDRAW_API_BASE_URL = "https://api.remotedraw.com";
|
|
4
6
|
function normalizedOrigin(value) {
|
|
5
7
|
const url = new URL(value.trim());
|
|
@@ -9,7 +11,7 @@ function normalizedOrigin(value) {
|
|
|
9
11
|
url.pathname !== "/" ||
|
|
10
12
|
url.search ||
|
|
11
13
|
url.hash) {
|
|
12
|
-
throw new Error(
|
|
14
|
+
throw new Error(t("cloud.error.badOrigin", { value }));
|
|
13
15
|
}
|
|
14
16
|
return url.origin;
|
|
15
17
|
}
|
|
@@ -59,12 +61,47 @@ async function readStoredConfig(runtime) {
|
|
|
59
61
|
? parsed.activeApiBaseUrl
|
|
60
62
|
: "",
|
|
61
63
|
profiles,
|
|
64
|
+
...(typeof parsed.language === "string"
|
|
65
|
+
? { language: parsed.language }
|
|
66
|
+
: {}),
|
|
62
67
|
};
|
|
63
68
|
}
|
|
64
69
|
catch {
|
|
65
|
-
throw new Error(
|
|
70
|
+
throw new Error(t("cloud.error.configUnreadable", { path: configPath }));
|
|
66
71
|
}
|
|
67
72
|
}
|
|
73
|
+
/**
|
|
74
|
+
* The stored CLI language, or undefined when the reader has never chosen one.
|
|
75
|
+
* A damaged or unreadable config never blocks a command here — the caller
|
|
76
|
+
* simply falls back to English — because the language is a preference, not a
|
|
77
|
+
* credential.
|
|
78
|
+
*/
|
|
79
|
+
export async function readStoredLanguage(runtime) {
|
|
80
|
+
try {
|
|
81
|
+
return (await readStoredConfig(runtime)).language;
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
return undefined;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Persists the language into the same config.json (and same 0600 directory)
|
|
89
|
+
* that already holds the CLI credential, so there is one user-config file to
|
|
90
|
+
* find, back up, or delete.
|
|
91
|
+
*/
|
|
92
|
+
export async function writeStoredLanguage(runtime, language) {
|
|
93
|
+
let config;
|
|
94
|
+
try {
|
|
95
|
+
config = await readStoredConfig(runtime);
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
// A corrupt credential file must not make the language unsettable; the
|
|
99
|
+
// login flow reports that failure on its own terms.
|
|
100
|
+
config = { version: 1, activeApiBaseUrl: "", profiles: {} };
|
|
101
|
+
}
|
|
102
|
+
config.language = language;
|
|
103
|
+
await writeStoredConfig(runtime, config);
|
|
104
|
+
}
|
|
68
105
|
async function writeStoredConfig(runtime, config) {
|
|
69
106
|
const configPath = globalConfigPath(runtime);
|
|
70
107
|
await runtime.mkdir(path.dirname(configPath));
|
|
@@ -89,7 +126,7 @@ async function storedToken(runtime, apiBaseUrl) {
|
|
|
89
126
|
}
|
|
90
127
|
async function fetchJson(runtime, apiBaseUrl, pathname, options = {}) {
|
|
91
128
|
if (!runtime.fetch)
|
|
92
|
-
throw new Error("
|
|
129
|
+
throw new Error(t("cloud.error.noFetch"));
|
|
93
130
|
let response;
|
|
94
131
|
try {
|
|
95
132
|
response = await runtime.fetch(`${apiBaseUrl}${pathname}`, {
|
|
@@ -102,7 +139,10 @@ async function fetchJson(runtime, apiBaseUrl, pathname, options = {}) {
|
|
|
102
139
|
});
|
|
103
140
|
}
|
|
104
141
|
catch (error) {
|
|
105
|
-
throw new Error(
|
|
142
|
+
throw new Error(t("cloud.error.unreachable", {
|
|
143
|
+
url: apiBaseUrl,
|
|
144
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
145
|
+
}));
|
|
106
146
|
}
|
|
107
147
|
const raw = await response.text();
|
|
108
148
|
let payload;
|
|
@@ -111,16 +151,20 @@ async function fetchJson(runtime, apiBaseUrl, pathname, options = {}) {
|
|
|
111
151
|
}
|
|
112
152
|
catch {
|
|
113
153
|
if (response.status === 404) {
|
|
114
|
-
throw new Error(
|
|
154
|
+
throw new Error(t("cloud.error.endpointMissing", {
|
|
155
|
+
path: pathname,
|
|
156
|
+
url: apiBaseUrl,
|
|
157
|
+
defaultUrl: DEFAULT_REMOTEDRAW_API_BASE_URL,
|
|
158
|
+
}));
|
|
115
159
|
}
|
|
116
|
-
throw new Error(
|
|
160
|
+
throw new Error(t("cloud.error.invalidResponse", { status: response.status }));
|
|
117
161
|
}
|
|
118
162
|
if (!response.ok) {
|
|
119
163
|
const message = isRecord(payload) &&
|
|
120
164
|
isRecord(payload.error) &&
|
|
121
165
|
typeof payload.error.message === "string"
|
|
122
166
|
? payload.error.message
|
|
123
|
-
:
|
|
167
|
+
: t("cloud.error.requestFailed", { status: response.status });
|
|
124
168
|
throw new Error(message);
|
|
125
169
|
}
|
|
126
170
|
return payload;
|
|
@@ -146,14 +190,17 @@ export async function loginToRemoteDraw(runtime, apiBaseUrl, options = {}) {
|
|
|
146
190
|
const request = await fetchJson(runtime, normalized, "/v1/cli/auth/start", {
|
|
147
191
|
body: { deviceName: options.deviceName ?? "RemoteDraw CLI" },
|
|
148
192
|
});
|
|
149
|
-
runtime.notify?.(
|
|
193
|
+
runtime.notify?.(t("cloud.authorize", {
|
|
194
|
+
url: request.verificationUrl,
|
|
195
|
+
code: request.userCode,
|
|
196
|
+
}));
|
|
150
197
|
if (options.openBrowser !== false && runtime.openUrl) {
|
|
151
198
|
try {
|
|
152
199
|
await runtime.openUrl(request.verificationUrl);
|
|
153
|
-
runtime.notify?.("
|
|
200
|
+
runtime.notify?.(t("cloud.browserOpened"));
|
|
154
201
|
}
|
|
155
202
|
catch {
|
|
156
|
-
runtime.notify?.("
|
|
203
|
+
runtime.notify?.(t("cloud.browserFailed"));
|
|
157
204
|
}
|
|
158
205
|
}
|
|
159
206
|
const sleep = runtime.sleep ??
|
|
@@ -166,7 +213,7 @@ export async function loginToRemoteDraw(runtime, apiBaseUrl, options = {}) {
|
|
|
166
213
|
if (result.status === "pending")
|
|
167
214
|
continue;
|
|
168
215
|
if (result.status !== "authorized") {
|
|
169
|
-
throw new Error(
|
|
216
|
+
throw new Error(t("cloud.error.authStatus", { status: result.status }));
|
|
170
217
|
}
|
|
171
218
|
const config = await readStoredConfig(runtime);
|
|
172
219
|
config.activeApiBaseUrl = normalized;
|
|
@@ -179,7 +226,7 @@ export async function loginToRemoteDraw(runtime, apiBaseUrl, options = {}) {
|
|
|
179
226
|
await writeStoredConfig(runtime, config);
|
|
180
227
|
return { account: result.account, alreadyLoggedIn: false };
|
|
181
228
|
}
|
|
182
|
-
throw new Error("
|
|
229
|
+
throw new Error(t("cloud.error.authExpired"));
|
|
183
230
|
}
|
|
184
231
|
export async function logoutFromRemoteDraw(runtime, apiBaseUrl) {
|
|
185
232
|
const normalized = normalizedOrigin(apiBaseUrl);
|
|
@@ -214,7 +261,7 @@ export async function provisionRemoteDrawProject(runtime, apiBaseUrl, input) {
|
|
|
214
261
|
}
|
|
215
262
|
}
|
|
216
263
|
if (!token) {
|
|
217
|
-
throw
|
|
264
|
+
throw cliError("AUTH_REQUIRED", "cloud.error.notLoggedIn");
|
|
218
265
|
}
|
|
219
266
|
return await fetchJson(runtime, normalized, "/v1/cli/projects", { body: input, token });
|
|
220
267
|
}
|
|
@@ -246,7 +293,7 @@ export function updateEnvContents(contents, values, force = false) {
|
|
|
246
293
|
existing !== value &&
|
|
247
294
|
!isReplaceableEnvValue(name, existing) &&
|
|
248
295
|
!force) {
|
|
249
|
-
throw new Error(
|
|
296
|
+
throw new Error(t("cloud.error.envConflict", { name }));
|
|
250
297
|
}
|
|
251
298
|
const pattern = new RegExp(`^\\s*(?:export\\s+)?${name}\\s*=.*$`, "m");
|
|
252
299
|
if (pattern.test(next)) {
|
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type MessageId, type MessageParams } from "./i18n.js";
|
|
2
|
+
/**
|
|
3
|
+
* The machine-readable classification carried by `--format json` failures.
|
|
4
|
+
* Codes used to be inferred from English substrings in the message, which
|
|
5
|
+
* quietly stopped working the moment a message could be Dutch. They are now
|
|
6
|
+
* declared where the error is raised, so the JSON contract is independent of
|
|
7
|
+
* the reader's language.
|
|
8
|
+
*/
|
|
9
|
+
export type CliErrorCode = "MISSING_ARGUMENT" | "INVALID_ARGUMENT" | "INVALID_COMBINATION" | "FILE_CONFLICT" | "AUTH_REQUIRED" | "COMMAND_FAILED";
|
|
10
|
+
export declare class CliError extends Error {
|
|
11
|
+
readonly code: CliErrorCode;
|
|
12
|
+
constructor(message: string, code: CliErrorCode);
|
|
13
|
+
}
|
|
14
|
+
/** Raises a localized message with a stable, language-independent code. */
|
|
15
|
+
export declare function cliError(code: CliErrorCode, id: MessageId, params?: MessageParams): CliError;
|
|
16
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,EAAK,KAAK,SAAS,EAAE,KAAK,aAAa,EAAE,MAAM,WAAW,CAAC;AAElE;;;;;;GAMG;AACH,MAAM,MAAM,YAAY,GACpB,kBAAkB,GAClB,kBAAkB,GAClB,qBAAqB,GACrB,eAAe,GACf,eAAe,GACf,gBAAgB,CAAC;AAErB,qBAAa,QAAS,SAAQ,KAAK;IACjC,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAC;gBAEhB,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,YAAY;CAKhD;AAED,2EAA2E;AAC3E,wBAAgB,QAAQ,CACtB,IAAI,EAAE,YAAY,EAClB,EAAE,EAAE,SAAS,EACb,MAAM,CAAC,EAAE,aAAa,YAGvB"}
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { t } from "./i18n.js";
|
|
2
|
+
export class CliError extends Error {
|
|
3
|
+
code;
|
|
4
|
+
constructor(message, code) {
|
|
5
|
+
super(message);
|
|
6
|
+
this.name = "CliError";
|
|
7
|
+
this.code = code;
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
/** Raises a localized message with a stable, language-independent code. */
|
|
11
|
+
export function cliError(code, id, params) {
|
|
12
|
+
return new CliError(t(id, params), code);
|
|
13
|
+
}
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
export declare const AGENT_SKILL_MARKDOWN = "---\nname: remotedraw\ndescription: Add RemoteDraw phone input to customer apps with the RemoteDraw CLI, public API, React SDK, raw HTTP, or customer-owned iOS sender flow.\n---\n\n# RemoteDraw Agent Skill\n\nUse this skill when a user asks to create, initialize, debug, or review a RemoteDraw integration.\n\n## Decision Flow\n\n1. Identify the receiver surface: web app, desktop app, iOS app, or headless/backend workflow.\n2. Identify the sender surface: RemoteDraw iOS app, embedded web sender, customer-owned iOS sender, or raw/headless sender.\n3. Pick the SDK path:\n - React SDK: web receiver and optional embedded web sender.\n - Plain JavaScript/raw HTTP: non-React web, desktop, backend, or custom clients.\n - Swift: customer-owned iOS sender apps.\n4. Pick a supported starter: `signature` for a bounded signature-field target,\n or `sketch` for a free-form receiver surface \u2014 both render through the\n `RemoteDrawReceiver` foundation, with the surrounding UI owned by the\n integrating app. Configure the target kind and descriptor directly for\n photos, PDFs, maps, screens, and other custom surfaces.\n\n## Endpoint, Key, and Packages\n\nRemoteDraw is hosted. There is nothing for the customer to run or self-host.\n\n- API base URL: `https://api.remotedraw.com`\n- Public docs: `https://docs.remotedraw.com/docs`\n- API keys are created in the console at\n `https://dashboard.remotedraw.com/api/keys` and belong in `.env.local` as\n `REMOTEDRAW_API_KEY`, alongside\n `REMOTEDRAW_API_BASE_URL=https://api.remotedraw.com`.\n\nPublished npm packages \u2014 install only what the chosen path needs:\n\n| Package | Install | Use it for |\n| --- | --- | --- |\n| `@remotedraw/cli` | `npm install -g @remotedraw/cli` (or `npx @remotedraw/cli@latest`) | Scaffolding, doctor, and test sessions. |\n| `@remotedraw/react` | `npm install @remotedraw/react` | React receiver, pairing, and headless sender. Peers on `react`/`react-dom` >= 18. |\n| `@remotedraw/svelte` | `npm install @remotedraw/svelte` | Svelte receiver store over the framework-free client. |\n| `@remotedraw/client` | `npm install @remotedraw/client` | Framework-free receiver/sender/API clients for any JS runtime. |\n| `@remotedraw/protocol` | `npm install @remotedraw/protocol` | Shared schemas, types, and limits for raw-HTTP integrations. |\n| `@remotedraw/geometry` | `npm install @remotedraw/geometry` | Normalized stroke geometry, shape assist, hit testing, export. |\n\n`@remotedraw/react` already depends on `client`, `protocol`, and `geometry`, so\ndo not add those separately for a React app. There is no published Swift\npackage: `remotedraw init --sdk swift` writes one self-contained\n`RemoteDrawIntegration.swift` against the public HTTP routes.\n\n## CLI First\n\nRead the machine-readable option catalog before choosing a plan:\n\n```sh\nremotedraw options --format json\n```\n\nInitialize a project with the closest supported path:\n\n```sh\nremotedraw init --target web --sender remotedraw-ios --sdk react --preset signature\nremotedraw init --target web --sender embedded-web --sdk react --preset sketch\nremotedraw init --target desktop --sender remotedraw-ios --sdk js --preset sketch\nremotedraw init --target ios --sender own-ios --sdk swift --preset sketch\n```\n\nBy default, `new` and `init` create the dashboard project and a project-scoped\ndevelopment API key, then write `REMOTEDRAW_API_BASE_URL`,\n`REMOTEDRAW_PROJECT_ID`, and `REMOTEDRAW_API_KEY` to a gitignored `.env.local`.\nUse `--offline` only when cloud setup is intentionally out of scope.\n\n`--sender own-ios` requires `--sdk swift`; every other combination of\n`--target`, `--sender`, and `--sdk` is accepted. Invalid combinations fail with\n`INVALID_COMBINATION` rather than guessing.\n\nAgents must use explicit non-interactive dry runs before changing a project:\n\n```sh\nremotedraw init --non-interactive --offline --dry-run --format json --target web --sender remotedraw-ios --sdk react --preset signature --package-manager npm\n# Inspect plan, files, defaultsApplied, and warnings before applying.\nremotedraw init --non-interactive --offline --format json --target web --sender remotedraw-ios --sdk react --preset signature --package-manager npm\nremotedraw doctor --format json\nremotedraw create-input --preset signature --json\n```\n\nDo not use the interactive wizard, synthesize arrow-key input, or scrape\nhuman-formatted output. If receiver, sender, or preset intent is ambiguous, ask\nthe user instead of guessing. Never pass `--force` unless overwrite scope was\nexplicitly approved.\n\n## Security Rules\n\n- Keep `rd_sk_...` API keys in trusted backend secrets only.\n- Keep the account-level `rd_cli_...` credential in the user config directory; never copy it into a project. Use `REMOTEDRAW_CLI_TOKEN` only as an explicitly managed CI secret.\n- Never place API keys in browser bundles, mobile clients, screenshots, logs, or generated examples.\n- Public clients should receive only `joinUrl`, `joinToken`, `receiverToken`, or `senderToken` values scoped to the session.\n- Production QR codes should use HTTPS `joinUrl` values. Do not make the custom scheme the primary QR target.\n\n## API Contract\n\n- Backend creates sessions with `POST /v1/sessions`.\n- Receiver clients read `POST /v1/receiver/session`, `/drawings`, `/drafts`, and `/senders` with a receiver token.\n- Sender clients join with `POST /v1/join`, stream mutable drafts to `/v1/sender/draft`, commit durable strokes to `/v1/sender/commit`, and finish with `/v1/sender/submit`.\n- Custom senders should throttle draft updates, coalesce to the latest pending preview, and commit one durable stroke on pointer-up with a stable `clientStrokeId`.\n\n## AI Actions\n\nReach for AI when the product needs something _from_ the finished drawing:\na generated image, a description, or structured data to branch on. Backend only\n(`aiActions:*` scopes on an `rd_sk_...` key). Never wire it to a commit,\nsubmit, or presence event \u2014 AI runs only on an explicit `POST /v1/ai-actions`\ncall the user asked for. Run it after the user is done; the route accepts\n`active` and `ended` sessions.\n\nMinimal request per outcome (`POST /v1/ai-actions`, plus optional\n`quality: \"fast\" | \"balanced\" | \"max\"`, default `balanced`):\n\n```jsonc\n{ \"sessionId\": \"...\", \"request\": \"image\", \"prompt\": \"...\" } // image back in the response\n{ \"sessionId\": \"...\", \"request\": \"image\", \"prompt\": \"...\", \"deliver\": [\"result\", \"board\"] } // and onto the board\n{ \"sessionId\": \"...\", \"request\": \"text\", \"prompt\": \"Describe this drawing.\" } // text back\n{ \"sessionId\": \"...\", \"request\": \"text\", \"prompt\": \"...\", \"text\": { \"schema\": { /* JSON Schema */ } } } // typed JSON\n```\n\nThe response is asynchronous: `create` returns `status: \"queued\"`. Poll\n`POST /v1/ai-actions/get` until `status` is `succeeded`, `failed`, or\n`canceled`, or use the helper on the API client\n(`createHttpRemoteDrawApiClient` from `@remotedraw/client`, re-exported by\n`@remotedraw/react`). This client holds the `rd_sk_` key, so it only ever runs\non the backend:\n\n```ts\nconst action = await client.createAiAction({\n sessionId,\n request: \"image\",\n prompt,\n});\nconst finished = await client.waitForAiAction({ aiActionId: action.id });\n// finished.result: generatedImageUrl | generatedText | generatedData | boardDrawingIds\n```\n\nThere is no completion webhook. Results are RemoteDraw-hosted asset URLs, not\nprovider URLs. See https://docs.remotedraw.com/docs/api#ai for the full\nrequest, credit, and legacy-mapping tables.\n\n## Verification\n\nAfter changes, verify against the customer's project \u2014 never assume RemoteDraw's\nown repo scripts exist here.\n\n```sh\nremotedraw doctor # config, SDK deps, REMOTEDRAW_* env\nremotedraw create-input --execute # open a real session, print the join URL\n```\n\nThen run whatever type check and test command the project already defines (for\nexample `npm run typecheck` and `npm test`). Do not invent script names, and do\nnot run `bun run test:api`, `bun run typecheck`, or `bun run ios:kit:test` \u2014\nthose are RemoteDraw's internal monorepo scripts and will not exist in a\ncustomer project.";
|
|
2
|
+
//# sourceMappingURL=agent-skill.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"agent-skill.d.ts","sourceRoot":"","sources":["../../src/generated/agent-skill.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,oBAAoB,6nQAAomQ,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
// GENERATED FILE — do not edit.
|
|
2
|
+
// Source: docs/agents/remotedraw/SKILL.md
|
|
3
|
+
// Regenerate: bun run --cwd packages/cli generate:skill
|
|
4
|
+
export const AGENT_SKILL_MARKDOWN = "---\nname: remotedraw\ndescription: Add RemoteDraw phone input to customer apps with the RemoteDraw CLI, public API, React SDK, raw HTTP, or customer-owned iOS sender flow.\n---\n\n# RemoteDraw Agent Skill\n\nUse this skill when a user asks to create, initialize, debug, or review a RemoteDraw integration.\n\n## Decision Flow\n\n1. Identify the receiver surface: web app, desktop app, iOS app, or headless/backend workflow.\n2. Identify the sender surface: RemoteDraw iOS app, embedded web sender, customer-owned iOS sender, or raw/headless sender.\n3. Pick the SDK path:\n - React SDK: web receiver and optional embedded web sender.\n - Plain JavaScript/raw HTTP: non-React web, desktop, backend, or custom clients.\n - Swift: customer-owned iOS sender apps.\n4. Pick a supported starter: `signature` for a bounded signature-field target,\n or `sketch` for a free-form receiver surface — both render through the\n `RemoteDrawReceiver` foundation, with the surrounding UI owned by the\n integrating app. Configure the target kind and descriptor directly for\n photos, PDFs, maps, screens, and other custom surfaces.\n\n## Endpoint, Key, and Packages\n\nRemoteDraw is hosted. There is nothing for the customer to run or self-host.\n\n- API base URL: `https://api.remotedraw.com`\n- Public docs: `https://docs.remotedraw.com/docs`\n- API keys are created in the console at\n `https://dashboard.remotedraw.com/api/keys` and belong in `.env.local` as\n `REMOTEDRAW_API_KEY`, alongside\n `REMOTEDRAW_API_BASE_URL=https://api.remotedraw.com`.\n\nPublished npm packages — install only what the chosen path needs:\n\n| Package | Install | Use it for |\n| --- | --- | --- |\n| `@remotedraw/cli` | `npm install -g @remotedraw/cli` (or `npx @remotedraw/cli@latest`) | Scaffolding, doctor, and test sessions. |\n| `@remotedraw/react` | `npm install @remotedraw/react` | React receiver, pairing, and headless sender. Peers on `react`/`react-dom` >= 18. |\n| `@remotedraw/svelte` | `npm install @remotedraw/svelte` | Svelte receiver store over the framework-free client. |\n| `@remotedraw/client` | `npm install @remotedraw/client` | Framework-free receiver/sender/API clients for any JS runtime. |\n| `@remotedraw/protocol` | `npm install @remotedraw/protocol` | Shared schemas, types, and limits for raw-HTTP integrations. |\n| `@remotedraw/geometry` | `npm install @remotedraw/geometry` | Normalized stroke geometry, shape assist, hit testing, export. |\n\n`@remotedraw/react` already depends on `client`, `protocol`, and `geometry`, so\ndo not add those separately for a React app. There is no published Swift\npackage: `remotedraw init --sdk swift` writes one self-contained\n`RemoteDrawIntegration.swift` against the public HTTP routes.\n\n## CLI First\n\nRead the machine-readable option catalog before choosing a plan:\n\n```sh\nremotedraw options --format json\n```\n\nInitialize a project with the closest supported path:\n\n```sh\nremotedraw init --target web --sender remotedraw-ios --sdk react --preset signature\nremotedraw init --target web --sender embedded-web --sdk react --preset sketch\nremotedraw init --target desktop --sender remotedraw-ios --sdk js --preset sketch\nremotedraw init --target ios --sender own-ios --sdk swift --preset sketch\n```\n\nBy default, `new` and `init` create the dashboard project and a project-scoped\ndevelopment API key, then write `REMOTEDRAW_API_BASE_URL`,\n`REMOTEDRAW_PROJECT_ID`, and `REMOTEDRAW_API_KEY` to a gitignored `.env.local`.\nUse `--offline` only when cloud setup is intentionally out of scope.\n\n`--sender own-ios` requires `--sdk swift`; every other combination of\n`--target`, `--sender`, and `--sdk` is accepted. Invalid combinations fail with\n`INVALID_COMBINATION` rather than guessing.\n\nAgents must use explicit non-interactive dry runs before changing a project:\n\n```sh\nremotedraw init --non-interactive --offline --dry-run --format json --target web --sender remotedraw-ios --sdk react --preset signature --package-manager npm\n# Inspect plan, files, defaultsApplied, and warnings before applying.\nremotedraw init --non-interactive --offline --format json --target web --sender remotedraw-ios --sdk react --preset signature --package-manager npm\nremotedraw doctor --format json\nremotedraw create-input --preset signature --json\n```\n\nDo not use the interactive wizard, synthesize arrow-key input, or scrape\nhuman-formatted output. If receiver, sender, or preset intent is ambiguous, ask\nthe user instead of guessing. Never pass `--force` unless overwrite scope was\nexplicitly approved.\n\n## Security Rules\n\n- Keep `rd_sk_...` API keys in trusted backend secrets only.\n- Keep the account-level `rd_cli_...` credential in the user config directory; never copy it into a project. Use `REMOTEDRAW_CLI_TOKEN` only as an explicitly managed CI secret.\n- Never place API keys in browser bundles, mobile clients, screenshots, logs, or generated examples.\n- Public clients should receive only `joinUrl`, `joinToken`, `receiverToken`, or `senderToken` values scoped to the session.\n- Production QR codes should use HTTPS `joinUrl` values. Do not make the custom scheme the primary QR target.\n\n## API Contract\n\n- Backend creates sessions with `POST /v1/sessions`.\n- Receiver clients read `POST /v1/receiver/session`, `/drawings`, `/drafts`, and `/senders` with a receiver token.\n- Sender clients join with `POST /v1/join`, stream mutable drafts to `/v1/sender/draft`, commit durable strokes to `/v1/sender/commit`, and finish with `/v1/sender/submit`.\n- Custom senders should throttle draft updates, coalesce to the latest pending preview, and commit one durable stroke on pointer-up with a stable `clientStrokeId`.\n\n## AI Actions\n\nReach for AI when the product needs something _from_ the finished drawing:\na generated image, a description, or structured data to branch on. Backend only\n(`aiActions:*` scopes on an `rd_sk_...` key). Never wire it to a commit,\nsubmit, or presence event — AI runs only on an explicit `POST /v1/ai-actions`\ncall the user asked for. Run it after the user is done; the route accepts\n`active` and `ended` sessions.\n\nMinimal request per outcome (`POST /v1/ai-actions`, plus optional\n`quality: \"fast\" | \"balanced\" | \"max\"`, default `balanced`):\n\n```jsonc\n{ \"sessionId\": \"...\", \"request\": \"image\", \"prompt\": \"...\" } // image back in the response\n{ \"sessionId\": \"...\", \"request\": \"image\", \"prompt\": \"...\", \"deliver\": [\"result\", \"board\"] } // and onto the board\n{ \"sessionId\": \"...\", \"request\": \"text\", \"prompt\": \"Describe this drawing.\" } // text back\n{ \"sessionId\": \"...\", \"request\": \"text\", \"prompt\": \"...\", \"text\": { \"schema\": { /* JSON Schema */ } } } // typed JSON\n```\n\nThe response is asynchronous: `create` returns `status: \"queued\"`. Poll\n`POST /v1/ai-actions/get` until `status` is `succeeded`, `failed`, or\n`canceled`, or use the helper on the API client\n(`createHttpRemoteDrawApiClient` from `@remotedraw/client`, re-exported by\n`@remotedraw/react`). This client holds the `rd_sk_` key, so it only ever runs\non the backend:\n\n```ts\nconst action = await client.createAiAction({\n sessionId,\n request: \"image\",\n prompt,\n});\nconst finished = await client.waitForAiAction({ aiActionId: action.id });\n// finished.result: generatedImageUrl | generatedText | generatedData | boardDrawingIds\n```\n\nThere is no completion webhook. Results are RemoteDraw-hosted asset URLs, not\nprovider URLs. See https://docs.remotedraw.com/docs/api#ai for the full\nrequest, credit, and legacy-mapping tables.\n\n## Verification\n\nAfter changes, verify against the customer's project — never assume RemoteDraw's\nown repo scripts exist here.\n\n```sh\nremotedraw doctor # config, SDK deps, REMOTEDRAW_* env\nremotedraw create-input --execute # open a real session, print the join URL\n```\n\nThen run whatever type check and test command the project already defines (for\nexample `npm run typecheck` and `npm test`). Do not invent script names, and do\nnot run `bun run test:api`, `bun run typecheck`, or `bun run ios:kit:test` —\nthose are RemoteDraw's internal monorepo scripts and will not exist in a\ncustomer project.";
|
package/dist/i18n.d.ts
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { LOCALES, SOURCE_LOCALE, SUPPORTED_LOCALES, type Locale, type MessageId } from "./locales/index.js";
|
|
2
|
+
export type { Locale, MessageId };
|
|
3
|
+
export { LOCALES, SOURCE_LOCALE, SUPPORTED_LOCALES };
|
|
4
|
+
export type MessageParams = Record<string, string | number>;
|
|
5
|
+
export type Translate = (id: MessageId, params?: MessageParams) => string;
|
|
6
|
+
/** Env vars that pick the CLI language without a flag or a prompt. */
|
|
7
|
+
export declare const LANGUAGE_ENV_VARS: readonly ["REMOTEDRAW_LANGUAGE", "REMOTEDRAW_LANG"];
|
|
8
|
+
/** Where the resolved locale came from — doctor and `language` report it. */
|
|
9
|
+
export type LocaleSource = "flag" | "env" | "config" | "prompt" | "default";
|
|
10
|
+
export declare function isLocale(value: string): value is Locale;
|
|
11
|
+
/**
|
|
12
|
+
* Accepts what a human or a CI job would actually type: a code (`nl`), a
|
|
13
|
+
* BCP-47 tag (`nl-BE`, `nl_BE.UTF-8`), an endonym (`Nederlands`), or an
|
|
14
|
+
* English name (`Dutch`). Returns undefined for anything unrecognized so the
|
|
15
|
+
* caller can fail loudly instead of silently falling back.
|
|
16
|
+
*/
|
|
17
|
+
export declare function normalizeLocale(value: string | undefined): Locale | undefined;
|
|
18
|
+
export declare function localeEndonym(locale: Locale): string;
|
|
19
|
+
/** `en, nl` — for "supported languages" error text. */
|
|
20
|
+
export declare function supportedLocaleList(): string;
|
|
21
|
+
/**
|
|
22
|
+
* A missing key renders the English string; a key missing from English too
|
|
23
|
+
* renders the id, which only happens for a typo and is loud in review.
|
|
24
|
+
*/
|
|
25
|
+
export declare function createTranslator(locale: Locale): Translate;
|
|
26
|
+
export declare function setActiveLocale(locale: Locale): void;
|
|
27
|
+
export declare function activeLocale(): Locale;
|
|
28
|
+
export declare const t: Translate;
|
|
29
|
+
/**
|
|
30
|
+
* The picker question is built from every installed locale's own word for
|
|
31
|
+
* "Language", so it reads for a Dutch speaker and an English speaker alike
|
|
32
|
+
* and grows automatically when a locale is added.
|
|
33
|
+
*/
|
|
34
|
+
export declare function languagePromptMessage(): string;
|
|
35
|
+
export declare function languageChoices(): {
|
|
36
|
+
value: "en" | "nl";
|
|
37
|
+
label: string;
|
|
38
|
+
hint: "en" | "nl";
|
|
39
|
+
}[];
|
|
40
|
+
/**
|
|
41
|
+
* Only used to preselect a row in the picker. The user still confirms, so a
|
|
42
|
+
* wrong guess costs an arrow key rather than an unreadable CLI.
|
|
43
|
+
*/
|
|
44
|
+
export declare function systemLocaleHint(env: Record<string, string | undefined>): Locale | undefined;
|
|
45
|
+
export type LocaleResolution = {
|
|
46
|
+
ok: true;
|
|
47
|
+
locale: Locale;
|
|
48
|
+
source: LocaleSource;
|
|
49
|
+
needsPrompt: boolean;
|
|
50
|
+
} | {
|
|
51
|
+
ok: false;
|
|
52
|
+
value: string;
|
|
53
|
+
variable: string;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Precedence: explicit flag, environment, stored choice, then unresolved.
|
|
57
|
+
* "Unresolved" is not an error — it means the CLI may ask, and must default
|
|
58
|
+
* to English when it cannot (no TTY, piped stdin, CI, an agent shelling out).
|
|
59
|
+
*/
|
|
60
|
+
export declare function resolveLocale(inputs: {
|
|
61
|
+
flag?: string | undefined;
|
|
62
|
+
env: Record<string, string | undefined>;
|
|
63
|
+
stored?: string | undefined;
|
|
64
|
+
}): LocaleResolution;
|
|
65
|
+
/**
|
|
66
|
+
* Pulls `--language <code>` / `--lang=<code>` out of argv before per-command
|
|
67
|
+
* flag parsing, which rejects options it does not declare. Returns the value
|
|
68
|
+
* and the remaining arguments.
|
|
69
|
+
*/
|
|
70
|
+
export declare function extractLanguageFlag(args: readonly string[]): {
|
|
71
|
+
value?: string;
|
|
72
|
+
rest: string[];
|
|
73
|
+
};
|
|
74
|
+
//# sourceMappingURL=i18n.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../src/i18n.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,aAAa,EACb,iBAAiB,EACjB,KAAK,MAAM,EACX,KAAK,SAAS,EACf,MAAM,oBAAoB,CAAC;AAE5B,YAAY,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,aAAa,EAAE,iBAAiB,EAAE,CAAC;AAErD,MAAM,MAAM,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAC;AAC5D,MAAM,MAAM,SAAS,GAAG,CAAC,EAAE,EAAE,SAAS,EAAE,MAAM,CAAC,EAAE,aAAa,KAAK,MAAM,CAAC;AAE1E,sEAAsE;AACtE,eAAO,MAAM,iBAAiB,qDAGpB,CAAC;AAEX,6EAA6E;AAC7E,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,KAAK,GAAG,QAAQ,GAAG,QAAQ,GAAG,SAAS,CAAC;AAE5E,wBAAgB,QAAQ,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,MAAM,CAEvD;AAED;;;;;GAKG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAkB7E;AAED,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,UAE3C;AAED,uDAAuD;AACvD,wBAAgB,mBAAmB,WAIlC;AAUD;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAK1D;AAYD,wBAAgB,eAAe,CAAC,MAAM,EAAE,MAAM,QAG7C;AAED,wBAAgB,YAAY,IAAI,MAAM,CAErC;AAED,eAAO,MAAM,CAAC,EAAE,SAAwD,CAAC;AAEzE;;;;GAIG;AACH,wBAAgB,qBAAqB,WAOpC;AAED,wBAAgB,eAAe;;;;IAM9B;AAED;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,GACtC,MAAM,GAAG,SAAS,CAMpB;AAED,MAAM,MAAM,gBAAgB,GACxB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,YAAY,CAAC;IAAC,WAAW,EAAE,OAAO,CAAA;CAAE,GACxE;IAAE,EAAE,EAAE,KAAK,CAAC;IAAC,KAAK,EAAE,MAAM,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,CAAC;AAEnD;;;;GAIG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE;IACpC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IAC1B,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC;IACxC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B,GAAG,gBAAgB,CA4BnB;AAED;;;;GAIG;AACH,wBAAgB,mBAAmB,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,GAAG;IAC5D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CA0BA"}
|
package/dist/i18n.js
ADDED
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
import { LOCALES, SOURCE_LOCALE, SUPPORTED_LOCALES, } from "./locales/index.js";
|
|
2
|
+
export { LOCALES, SOURCE_LOCALE, SUPPORTED_LOCALES };
|
|
3
|
+
/** Env vars that pick the CLI language without a flag or a prompt. */
|
|
4
|
+
export const LANGUAGE_ENV_VARS = [
|
|
5
|
+
"REMOTEDRAW_LANGUAGE",
|
|
6
|
+
"REMOTEDRAW_LANG",
|
|
7
|
+
];
|
|
8
|
+
export function isLocale(value) {
|
|
9
|
+
return SUPPORTED_LOCALES.includes(value);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Accepts what a human or a CI job would actually type: a code (`nl`), a
|
|
13
|
+
* BCP-47 tag (`nl-BE`, `nl_BE.UTF-8`), an endonym (`Nederlands`), or an
|
|
14
|
+
* English name (`Dutch`). Returns undefined for anything unrecognized so the
|
|
15
|
+
* caller can fail loudly instead of silently falling back.
|
|
16
|
+
*/
|
|
17
|
+
export function normalizeLocale(value) {
|
|
18
|
+
if (value == null)
|
|
19
|
+
return undefined;
|
|
20
|
+
const cleaned = value.trim().toLowerCase().split(".")[0]?.replace("_", "-");
|
|
21
|
+
if (!cleaned)
|
|
22
|
+
return undefined;
|
|
23
|
+
const base = cleaned.split("-")[0] ?? cleaned;
|
|
24
|
+
for (const locale of SUPPORTED_LOCALES) {
|
|
25
|
+
const definition = LOCALES[locale];
|
|
26
|
+
if (locale === cleaned ||
|
|
27
|
+
locale === base ||
|
|
28
|
+
definition.tags.includes(base) ||
|
|
29
|
+
definition.aliases.includes(cleaned) ||
|
|
30
|
+
definition.endonym.toLowerCase() === cleaned) {
|
|
31
|
+
return locale;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
export function localeEndonym(locale) {
|
|
37
|
+
return LOCALES[locale].endonym;
|
|
38
|
+
}
|
|
39
|
+
/** `en, nl` — for "supported languages" error text. */
|
|
40
|
+
export function supportedLocaleList() {
|
|
41
|
+
return SUPPORTED_LOCALES.map((locale) => `${locale} (${localeEndonym(locale)})`).join(", ");
|
|
42
|
+
}
|
|
43
|
+
function interpolate(template, params) {
|
|
44
|
+
if (!params)
|
|
45
|
+
return template;
|
|
46
|
+
return template.replaceAll(/\{(\w+)\}/g, (match, key) => {
|
|
47
|
+
const value = params[key];
|
|
48
|
+
return value == null ? match : String(value);
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* A missing key renders the English string; a key missing from English too
|
|
53
|
+
* renders the id, which only happens for a typo and is loud in review.
|
|
54
|
+
*/
|
|
55
|
+
export function createTranslator(locale) {
|
|
56
|
+
const catalog = LOCALES[locale].catalog;
|
|
57
|
+
const fallback = LOCALES[SOURCE_LOCALE].catalog;
|
|
58
|
+
return (id, params) => interpolate(catalog[id] ?? fallback[id] ?? id, params);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* The CLI is one process running one command for one reader, so the active
|
|
62
|
+
* locale is process state rather than an argument threaded through ~120 text
|
|
63
|
+
* builders. `runCli` sets it deterministically on entry from flag/env/config,
|
|
64
|
+
* which is what keeps repeated in-process runs (the test suite) from leaking
|
|
65
|
+
* a locale into each other.
|
|
66
|
+
*/
|
|
67
|
+
let activeLocaleValue = SOURCE_LOCALE;
|
|
68
|
+
let activeTranslator = createTranslator(SOURCE_LOCALE);
|
|
69
|
+
export function setActiveLocale(locale) {
|
|
70
|
+
activeLocaleValue = locale;
|
|
71
|
+
activeTranslator = createTranslator(locale);
|
|
72
|
+
}
|
|
73
|
+
export function activeLocale() {
|
|
74
|
+
return activeLocaleValue;
|
|
75
|
+
}
|
|
76
|
+
export const t = (id, params) => activeTranslator(id, params);
|
|
77
|
+
/**
|
|
78
|
+
* The picker question is built from every installed locale's own word for
|
|
79
|
+
* "Language", so it reads for a Dutch speaker and an English speaker alike
|
|
80
|
+
* and grows automatically when a locale is added.
|
|
81
|
+
*/
|
|
82
|
+
export function languagePromptMessage() {
|
|
83
|
+
const words = [];
|
|
84
|
+
for (const locale of SUPPORTED_LOCALES) {
|
|
85
|
+
const word = createTranslator(locale)("language.word");
|
|
86
|
+
if (!words.includes(word))
|
|
87
|
+
words.push(word);
|
|
88
|
+
}
|
|
89
|
+
return words.join(" / ");
|
|
90
|
+
}
|
|
91
|
+
export function languageChoices() {
|
|
92
|
+
return SUPPORTED_LOCALES.map((locale) => ({
|
|
93
|
+
value: locale,
|
|
94
|
+
label: localeEndonym(locale),
|
|
95
|
+
hint: locale,
|
|
96
|
+
}));
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Only used to preselect a row in the picker. The user still confirms, so a
|
|
100
|
+
* wrong guess costs an arrow key rather than an unreadable CLI.
|
|
101
|
+
*/
|
|
102
|
+
export function systemLocaleHint(env) {
|
|
103
|
+
for (const name of ["LC_ALL", "LC_MESSAGES", "LANG", "LANGUAGE"]) {
|
|
104
|
+
const guess = normalizeLocale(env[name]?.split(":")[0]);
|
|
105
|
+
if (guess)
|
|
106
|
+
return guess;
|
|
107
|
+
}
|
|
108
|
+
return undefined;
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Precedence: explicit flag, environment, stored choice, then unresolved.
|
|
112
|
+
* "Unresolved" is not an error — it means the CLI may ask, and must default
|
|
113
|
+
* to English when it cannot (no TTY, piped stdin, CI, an agent shelling out).
|
|
114
|
+
*/
|
|
115
|
+
export function resolveLocale(inputs) {
|
|
116
|
+
if (inputs.flag != null) {
|
|
117
|
+
// A present-but-empty flag (`--language` with nothing after it) is a
|
|
118
|
+
// mistake worth reporting, not a silent fall-through to the environment.
|
|
119
|
+
const locale = normalizeLocale(inputs.flag);
|
|
120
|
+
return locale
|
|
121
|
+
? { ok: true, locale, source: "flag", needsPrompt: false }
|
|
122
|
+
: { ok: false, value: inputs.flag.trim(), variable: "--language" };
|
|
123
|
+
}
|
|
124
|
+
for (const name of LANGUAGE_ENV_VARS) {
|
|
125
|
+
const raw = inputs.env[name]?.trim();
|
|
126
|
+
if (!raw)
|
|
127
|
+
continue;
|
|
128
|
+
const locale = normalizeLocale(raw);
|
|
129
|
+
return locale
|
|
130
|
+
? { ok: true, locale, source: "env", needsPrompt: false }
|
|
131
|
+
: { ok: false, value: raw, variable: name };
|
|
132
|
+
}
|
|
133
|
+
const stored = normalizeLocale(inputs.stored);
|
|
134
|
+
if (stored) {
|
|
135
|
+
return { ok: true, locale: stored, source: "config", needsPrompt: false };
|
|
136
|
+
}
|
|
137
|
+
// Nothing chosen yet: English until the reader says otherwise.
|
|
138
|
+
return {
|
|
139
|
+
ok: true,
|
|
140
|
+
locale: SOURCE_LOCALE,
|
|
141
|
+
source: "default",
|
|
142
|
+
needsPrompt: true,
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* Pulls `--language <code>` / `--lang=<code>` out of argv before per-command
|
|
147
|
+
* flag parsing, which rejects options it does not declare. Returns the value
|
|
148
|
+
* and the remaining arguments.
|
|
149
|
+
*/
|
|
150
|
+
export function extractLanguageFlag(args) {
|
|
151
|
+
const rest = [];
|
|
152
|
+
let value;
|
|
153
|
+
for (let index = 0; index < args.length; index += 1) {
|
|
154
|
+
const arg = args[index];
|
|
155
|
+
if (arg == null)
|
|
156
|
+
continue;
|
|
157
|
+
const match = /^--(language|lang)(?:=(.*))?$/.exec(arg);
|
|
158
|
+
if (!match) {
|
|
159
|
+
rest.push(arg);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
if (match[2] != null) {
|
|
163
|
+
value = match[2];
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
const next = args[index + 1];
|
|
167
|
+
if (next != null && !next.startsWith("--")) {
|
|
168
|
+
value = next;
|
|
169
|
+
index += 1;
|
|
170
|
+
}
|
|
171
|
+
else {
|
|
172
|
+
// `--language` with nothing after it: treat as an empty request so the
|
|
173
|
+
// caller reports an unknown language instead of eating the next flag.
|
|
174
|
+
value = "";
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return value == null ? { rest } : { value, rest };
|
|
178
|
+
}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import process from "node:process";
|
|
3
3
|
import { CLI_VERSION, createNodeRuntime, runCli } from "./cli.js";
|
|
4
|
-
import {
|
|
4
|
+
import { nodeTelemetryEnvironment, reportCliCrash } from "./telemetry.js";
|
|
5
|
+
import { checkForUpdate, detectInstallMethod, updateNotice } from "./update.js";
|
|
6
|
+
// `runCli` reports whatever it catches; the handlers below cover the thin shell
|
|
7
|
+
// around it — building the runtime, writing to a closed stdout, a rejection that
|
|
8
|
+
// escapes a detached promise — which would otherwise crash with a bare Node
|
|
9
|
+
// stack trace we never hear about.
|
|
10
|
+
//
|
|
11
|
+
// Installing them replaces Node's default crash behavior, so this has to restore
|
|
12
|
+
// it: print the error, then exit non-zero. The report is awaited first because
|
|
13
|
+
// `process.exit` would drop the in-flight request.
|
|
14
|
+
async function reportAndCrash(error) {
|
|
15
|
+
await reportCliCrash(error, process.argv[2] ?? "help", nodeTelemetryEnvironment(CLI_VERSION));
|
|
16
|
+
process.stderr.write(`${error instanceof Error ? (error.stack ?? error.message) : String(error)}\n`);
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
process.on("uncaughtException", (error) => void reportAndCrash(error));
|
|
20
|
+
process.on("unhandledRejection", (reason) => void reportAndCrash(reason));
|
|
5
21
|
const runtime = createNodeRuntime();
|
|
6
22
|
const result = await runCli(process.argv.slice(2), runtime);
|
|
7
23
|
if (result.stdout)
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { en } from "./en.js";
|
|
2
|
+
/** Every translatable string in the CLI is addressed by one of these ids. */
|
|
3
|
+
export type MessageId = keyof typeof en;
|
|
4
|
+
/**
|
|
5
|
+
* A translation is a partial overlay on the English source catalog: anything
|
|
6
|
+
* a locale does not define renders in English instead of leaking a raw id.
|
|
7
|
+
*/
|
|
8
|
+
export type LocaleCatalog = Partial<Record<MessageId, string>>;
|
|
9
|
+
export type LocaleDefinition = {
|
|
10
|
+
/**
|
|
11
|
+
* The language's own name for itself. The language picker lists endonyms so
|
|
12
|
+
* it is readable no matter which language the reader speaks.
|
|
13
|
+
*/
|
|
14
|
+
endonym: string;
|
|
15
|
+
/** BCP-47 prefixes that map to this locale (`nl-BE` -> `nl`). */
|
|
16
|
+
tags: readonly string[];
|
|
17
|
+
/** Extra spellings accepted from --language / the environment. */
|
|
18
|
+
aliases: readonly string[];
|
|
19
|
+
catalog: LocaleCatalog;
|
|
20
|
+
};
|
|
21
|
+
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../../src/locales/catalog.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,EAAE,MAAM,SAAS,CAAC;AAElC,6EAA6E;AAC7E,MAAM,MAAM,SAAS,GAAG,MAAM,OAAO,EAAE,CAAC;AAExC;;;GAGG;AACH,MAAM,MAAM,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC;AAE/D,MAAM,MAAM,gBAAgB,GAAG;IAC7B;;;OAGG;IACH,OAAO,EAAE,MAAM,CAAC;IAChB,iEAAiE;IACjE,IAAI,EAAE,SAAS,MAAM,EAAE,CAAC;IACxB,kEAAkE;IAClE,OAAO,EAAE,SAAS,MAAM,EAAE,CAAC;IAC3B,OAAO,EAAE,aAAa,CAAC;CACxB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|