@remotedraw/cli 0.2.2 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +16 -6
- package/dist/cli.d.ts +17 -5
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +79 -23
- package/dist/generated/agent-skill.d.ts +1 -1
- package/dist/generated/agent-skill.d.ts.map +1 -1
- package/dist/generated/agent-skill.js +1 -1
- package/dist/index.js +1 -1
- package/dist/locales/en.d.ts +8 -8
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/en.js +8 -8
- package/dist/locales/index.d.ts +8 -8
- package/dist/locales/nl.d.ts.map +1 -1
- package/dist/locales/nl.js +7 -7
- package/dist/packageManagers.d.ts +75 -0
- package/dist/packageManagers.d.ts.map +1 -0
- package/dist/packageManagers.js +146 -0
- package/dist/scan.d.ts +19 -0
- package/dist/scan.d.ts.map +1 -1
- package/dist/scan.js +22 -3
- package/dist/update.d.ts +11 -5
- package/dist/update.d.ts.map +1 -1
- package/dist/update.js +55 -28
- package/package.json +1 -1
package/dist/scan.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
16
16
|
import path from "node:path";
|
|
17
|
+
import { detectPackageManager, packageManagerCommands, } from "./packageManagers.js";
|
|
17
18
|
const SKIPPED_DIRECTORIES = new Set([
|
|
18
19
|
"node_modules",
|
|
19
20
|
".git",
|
|
@@ -355,11 +356,23 @@ export async function scanProject(root, runtime) {
|
|
|
355
356
|
keyBoundary.warning =
|
|
356
357
|
"No server-side code was found. POST /v1/sessions needs an rd_sk_ key, which must never ship in a browser or app bundle — ask the user where their backend (or a serverless function) lives before scaffolding anything.";
|
|
357
358
|
}
|
|
359
|
+
const detected = await detectPackageManager(root, { env: runtime.env ?? {}, exists: runtime.exists, readFile: runtime.readFile }, { pathModule: path });
|
|
360
|
+
const managerCommands = packageManagerCommands[detected.manager];
|
|
358
361
|
return {
|
|
359
362
|
ok: true,
|
|
360
363
|
command: "scan",
|
|
361
364
|
schemaVersion: 1,
|
|
362
365
|
root,
|
|
366
|
+
packageManager: {
|
|
367
|
+
id: detected.manager,
|
|
368
|
+
reason: detected.reason,
|
|
369
|
+
commands: {
|
|
370
|
+
install: managerCommands.install,
|
|
371
|
+
add: managerCommands.add,
|
|
372
|
+
dlx: managerCommands.dlx,
|
|
373
|
+
run: managerCommands.run,
|
|
374
|
+
},
|
|
375
|
+
},
|
|
363
376
|
projects,
|
|
364
377
|
remotedraw: {
|
|
365
378
|
configured: configFiles.length > 0 || remotedrawPackages.size > 0 || swiftPackage,
|
|
@@ -461,8 +474,9 @@ function buildRecommendations(input) {
|
|
|
461
474
|
}
|
|
462
475
|
function productQuestions(input) {
|
|
463
476
|
const questions = [
|
|
464
|
-
"
|
|
465
|
-
"
|
|
477
|
+
"Which screen in the product already shows the thing people will draw on, and what device is that screen on? RemoteDraw needs two devices: that screen is the receiver, and a phone is the pen. If the only screen involved is the phone's own, this is not a RemoteDraw flow.",
|
|
478
|
+
"What is on that screen — a blank sketch surface, a photo your app already shows, a PDF page it renders, a map it owns, a form field, or a shared screen? This picks target.kind and the preset. Your app renders it; the phone never supplies it.",
|
|
479
|
+
"Where does the receiver go? Name the existing screen or route; RemoteDraw does not get its own page unless the user asks for a demo.",
|
|
466
480
|
"Who holds the phone — the same signed-in user (direct sender, no QR), or anyone who scans (QR join)?",
|
|
467
481
|
"What happens to the drawing when the phone taps Submit — saved to which record, shown where, exported how?",
|
|
468
482
|
"Is this a minimal demo to evaluate RemoteDraw, or the real feature? A demo is one page; the feature is the answers above.",
|
|
@@ -476,10 +490,14 @@ function productQuestions(input) {
|
|
|
476
490
|
return questions;
|
|
477
491
|
}
|
|
478
492
|
export const INTEGRATION_RULES = [
|
|
493
|
+
"RemoteDraw needs two devices: the receiver is a screen the user is already looking at (desktop, laptop, large display) showing your content, and the sender is a phone. A phone that photographs something and annotates it on its own screen is not a RemoteDraw flow — say so and propose PencilKit or a canvas instead.",
|
|
494
|
+
"The phone supplies a hand, not content. Your app renders the photo, the PDF page, the map, the form; the iOS sender opens no camera and no file picker.",
|
|
479
495
|
"Ask before installing anything (packages, CLI, Swift package) — name what and why, then wait.",
|
|
480
496
|
"rd_sk_ keys live only in server-side code found by this scan; never in Vite/Next client source, Swift, or an app bundle.",
|
|
481
497
|
"iOS senders use RemoteDrawSenderKit's full-screen surface (`.remoteDrawSurface` / `RemoteDrawTakeover`). Do not write a UIViewRepresentable canvas, and do not put a small canvas inside a sheet.",
|
|
482
498
|
"Web senders use the hosted /join page unless the user explicitly wants an in-page sender.",
|
|
499
|
+
"Bluetooth and local-network pairing are not implemented anywhere in the product — the radios only advertise, nothing scans. Do not offer them; QR and direct sender are the real methods. There is no native Android SDK and no desktop sender either.",
|
|
500
|
+
"Use the ready-made components: PairingCode / RemoteDrawConnect / RemoteDrawLaunchButton for pairing, RemoteDrawReceiver (or RemoteDrawMapReceiver over a map) for ink, RemoteDrawSessionControls for state. Do not hand-roll a connect button, an SVG receiver, or a polling loop.",
|
|
483
501
|
"Timestamps (`occurredAt`, point `t`) are integer milliseconds; the SDKs handle this, hand-written clients often do not.",
|
|
484
502
|
"Session creation is billable and not idempotent: guard React StrictMode double effects, and persist the receiverToken if the receiver outlives a page load.",
|
|
485
503
|
"One API key serves every tenant: scope sessions with externalId and verify it before attaching a sender or ending a session.",
|
|
@@ -488,7 +506,8 @@ export const INTEGRATION_RULES = [
|
|
|
488
506
|
export function renderScanText(report) {
|
|
489
507
|
const lines = [];
|
|
490
508
|
lines.push("RemoteDraw scan", "");
|
|
491
|
-
lines.push(`Root: ${report.root}
|
|
509
|
+
lines.push(`Root: ${report.root}`);
|
|
510
|
+
lines.push(`Package manager: ${report.packageManager.id} (${report.packageManager.reason})`, "");
|
|
492
511
|
lines.push("Projects:");
|
|
493
512
|
if (report.projects.length === 0)
|
|
494
513
|
lines.push(" (none detected)");
|
package/dist/update.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type CloudRuntime } from "./cloud.js";
|
|
2
|
+
import { type PackageManagerId } from "./packageManagers.js";
|
|
2
3
|
export declare const CLI_PACKAGE_NAME = "@remotedraw/cli";
|
|
3
4
|
export type UpdateRuntime = CloudRuntime & {
|
|
4
5
|
/**
|
|
@@ -12,8 +13,12 @@ export type UpdateRuntime = CloudRuntime & {
|
|
|
12
13
|
};
|
|
13
14
|
export type InstallMethod = {
|
|
14
15
|
/** Package manager the binary appears to have been installed with. */
|
|
15
|
-
manager:
|
|
16
|
-
/**
|
|
16
|
+
manager: PackageManagerId;
|
|
17
|
+
/**
|
|
18
|
+
* True when the binary was resolved by a throwaway runner (npx, pnpm dlx,
|
|
19
|
+
* yarn dlx, bunx). Nothing survives the command, so there is nothing to
|
|
20
|
+
* update: the next run fetches the latest by itself.
|
|
21
|
+
*/
|
|
17
22
|
ephemeral: boolean;
|
|
18
23
|
command: string;
|
|
19
24
|
args: string[];
|
|
@@ -29,10 +34,11 @@ export declare function parseVersion(value: string): {
|
|
|
29
34
|
};
|
|
30
35
|
export declare function compareVersions(a: string, b: string): 1 | 0 | -1;
|
|
31
36
|
/**
|
|
32
|
-
* Derives the update command from where the running binary lives
|
|
33
|
-
*
|
|
37
|
+
* Derives the update command from where the running binary lives, falling back
|
|
38
|
+
* to the manager that invoked us. Guessing wrong only prints the wrong
|
|
39
|
+
* suggestion, so the last fallback is plain npm.
|
|
34
40
|
*/
|
|
35
|
-
export declare function detectInstallMethod(binPath: string): InstallMethod;
|
|
41
|
+
export declare function detectInstallMethod(binPath: string, env?: Record<string, string | undefined>): InstallMethod;
|
|
36
42
|
export declare function installMethodDisplay(method: InstallMethod): string;
|
|
37
43
|
export declare function updateCachePath(runtime: UpdateRuntime): string;
|
|
38
44
|
export declare function latestPublishedVersion(runtime: UpdateRuntime, timeoutMs?: number): Promise<string | undefined>;
|
package/dist/update.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../src/update.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"update.d.ts","sourceRoot":"","sources":["../src/update.ts"],"names":[],"mappings":"AACA,OAAO,EAAoB,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAEjE,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAE9B,eAAO,MAAM,gBAAgB,oBAAoB,CAAC;AAOlD,MAAM,MAAM,aAAa,GAAG,YAAY,GAAG;IACzC;;;;OAIG;IACH,YAAY,CAAC,EAAE,CACb,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,MAAM,EAAE,KACX,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACpC,CAAC;AAEF,MAAM,MAAM,aAAa,GAAG;IAC1B,sEAAsE;IACtE,OAAO,EAAE,gBAAgB,CAAC;IAC1B;;;;OAIG;IACH,SAAS,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,EAAE,CAAC;CAChB,CAAC;AAEF,MAAM,MAAM,WAAW,GAAG;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,eAAe,EAAE,OAAO,CAAC;CAC1B,CAAC;AAiBF,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM;;;EAczC;AA2BD,wBAAgB,eAAe,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,cASnD;AAuCD;;;;GAIG;AACH,wBAAgB,mBAAmB,CACjC,OAAO,EAAE,MAAM,EACf,GAAG,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAM,GAC3C,aAAa,CAsCf;AAED,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,aAAa,UAEzD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,aAAa,UAErD;AAgDD,wBAAsB,sBAAsB,CAC1C,OAAO,EAAE,aAAa,EACtB,SAAS,SAAmB,+BAoC7B;AAED,+EAA+E;AAC/E,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,WAO3E;AAED,wBAAsB,cAAc,CAClC,OAAO,EAAE,aAAa,EACtB,cAAc,EAAE,MAAM,EACtB,OAAO,GAAE;IAAE,KAAK,CAAC,EAAE,OAAO,CAAC;IAAC,GAAG,CAAC,EAAE,MAAM,CAAA;CAAO,GAC9C,OAAO,CAAC,WAAW,GAAG,SAAS,CAAC,CA2BlC;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,aAAa,UAWrE;AAED,wBAAsB,UAAU,CAAC,OAAO,EAAE,aAAa,EAAE,MAAM,EAAE,aAAa;;;;;;GAM7E"}
|
package/dist/update.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { globalConfigPath } from "./cloud.js";
|
|
3
3
|
import { t } from "./i18n.js";
|
|
4
|
+
import { packageManagerCommands, packageManagerFromUserAgent, } from "./packageManagers.js";
|
|
4
5
|
export const CLI_PACKAGE_NAME = "@remotedraw/cli";
|
|
5
6
|
const DEFAULT_REGISTRY_URL = "https://registry.npmjs.org";
|
|
6
7
|
const CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
@@ -75,45 +76,71 @@ export function compareVersions(a, b) {
|
|
|
75
76
|
}
|
|
76
77
|
return comparePrerelease(left.prerelease, right.prerelease);
|
|
77
78
|
}
|
|
79
|
+
/** Splits a table entry like "pnpm dlx" into something spawnable. */
|
|
80
|
+
function commandLine(text, ...extra) {
|
|
81
|
+
const [command, ...args] = text.split(" ");
|
|
82
|
+
return { command: command, args: [...args, ...extra] };
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Where each manager parks a package it is running once and throwing away.
|
|
86
|
+
* Matching the wrong one is not cosmetic: telling someone who ran `pnpm dlx`
|
|
87
|
+
* to `pnpm add --global` installs a copy they never asked for.
|
|
88
|
+
*/
|
|
89
|
+
const EPHEMERAL_PATHS = [
|
|
90
|
+
["npm", /\/(_npx|\.npm\/_npx)\//],
|
|
91
|
+
["bun", /\/\.bun\/install\/cache\//],
|
|
92
|
+
["pnpm", /\/pnpm\/dlx\//],
|
|
93
|
+
// Berry's `yarn dlx` builds a throwaway project directory named dlx-<pid>.
|
|
94
|
+
["yarn", /\/dlx-\d+\//],
|
|
95
|
+
];
|
|
96
|
+
/** Where each manager parks a package it installed to stay. */
|
|
97
|
+
const INSTALLED_PATHS = [
|
|
98
|
+
["bun", /\/\.bun\//],
|
|
99
|
+
["pnpm", /\/(\.pnpm|pnpm)\//],
|
|
100
|
+
["yarn", /\/(\.yarn|[Yy]arn)\//],
|
|
101
|
+
];
|
|
78
102
|
/**
|
|
79
|
-
*
|
|
80
|
-
*
|
|
103
|
+
* Yarn Berry removed global installs, so `yarn global add` is not a command
|
|
104
|
+
* that exists for it; a Berry user has the CLI as a project dependency, and
|
|
105
|
+
* `yarn up` is what moves it. Classic (v1) still has the global store.
|
|
81
106
|
*/
|
|
82
|
-
|
|
107
|
+
function yarnIsBerry(normalizedBinPath) {
|
|
108
|
+
return /(\.yarn\/(berry|cache|unplugged|__virtual__)|\.pnp)/.test(normalizedBinPath);
|
|
109
|
+
}
|
|
110
|
+
/**
|
|
111
|
+
* Derives the update command from where the running binary lives, falling back
|
|
112
|
+
* to the manager that invoked us. Guessing wrong only prints the wrong
|
|
113
|
+
* suggestion, so the last fallback is plain npm.
|
|
114
|
+
*/
|
|
115
|
+
export function detectInstallMethod(binPath, env = {}) {
|
|
83
116
|
const normalized = binPath.replace(/\\/g, "/");
|
|
84
117
|
const update = `${CLI_PACKAGE_NAME}@latest`;
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
args: ["add", "--global", update],
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
if (/\/(\.pnpm|pnpm)\//.test(normalized)) {
|
|
97
|
-
return {
|
|
98
|
-
manager: "pnpm",
|
|
99
|
-
ephemeral: false,
|
|
100
|
-
command: "pnpm",
|
|
101
|
-
args: ["add", "--global", update],
|
|
102
|
-
};
|
|
118
|
+
for (const [manager, pattern] of EPHEMERAL_PATHS) {
|
|
119
|
+
if (pattern.test(normalized)) {
|
|
120
|
+
return {
|
|
121
|
+
manager,
|
|
122
|
+
ephemeral: true,
|
|
123
|
+
...commandLine(packageManagerCommands[manager].dlx, update),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
103
126
|
}
|
|
104
|
-
|
|
127
|
+
// The path says who owns the binary; the user agent only says who launched
|
|
128
|
+
// this run, which can be a project's npm script over a pnpm-installed CLI.
|
|
129
|
+
const manager = INSTALLED_PATHS.find(([, pattern]) => pattern.test(normalized))?.[0] ??
|
|
130
|
+
packageManagerFromUserAgent(env) ??
|
|
131
|
+
"npm";
|
|
132
|
+
if (manager === "yarn") {
|
|
105
133
|
return {
|
|
106
|
-
manager
|
|
134
|
+
manager,
|
|
107
135
|
ephemeral: false,
|
|
108
|
-
|
|
109
|
-
args: ["global", "add", update],
|
|
136
|
+
...commandLine(yarnIsBerry(normalized) ? "yarn up" : "yarn global add", update),
|
|
110
137
|
};
|
|
111
138
|
}
|
|
139
|
+
const addGlobal = packageManagerCommands[manager].addGlobal;
|
|
112
140
|
return {
|
|
113
|
-
manager
|
|
141
|
+
manager,
|
|
114
142
|
ephemeral: false,
|
|
115
|
-
|
|
116
|
-
args: ["install", "--global", update],
|
|
143
|
+
...commandLine(addGlobal ?? packageManagerCommands[manager].dlx, update),
|
|
117
144
|
};
|
|
118
145
|
}
|
|
119
146
|
export function installMethodDisplay(method) {
|