pointer-feedback 0.1.2 → 0.1.3
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/dist/cli.js +192 -61
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -15,7 +15,7 @@ var init_build_constants = __esm({
|
|
|
15
15
|
"src/build-constants.ts"() {
|
|
16
16
|
"use strict";
|
|
17
17
|
BUILD_DEFAULT_SERVER = true ? "https://api.pointer.moamen.work" : "https://api.pointer.moamen.work";
|
|
18
|
-
BUILD_CLI_VERSION = true ? "0.1.
|
|
18
|
+
BUILD_CLI_VERSION = true ? "0.1.3" : "0.0.0-dev";
|
|
19
19
|
}
|
|
20
20
|
});
|
|
21
21
|
|
|
@@ -866,27 +866,62 @@ var init_deployed = __esm({
|
|
|
866
866
|
|
|
867
867
|
// src/prompt.ts
|
|
868
868
|
import * as readline from "node:readline/promises";
|
|
869
|
+
import { emitKeypressEvents } from "node:readline";
|
|
869
870
|
import { Writable } from "node:stream";
|
|
871
|
+
function assertInteractive() {
|
|
872
|
+
if (process.stdin.isTTY)
|
|
873
|
+
return;
|
|
874
|
+
console.error(
|
|
875
|
+
"\x1B[31mThis command is interactive, but stdin is not a terminal.\x1B[0m\nPiped input, CI, and some editor-embedded shells have no TTY, so there is no way to ask you anything.\n\nEither run it in a real terminal, or pass every answer as a flag:\n npx -y pointer-feedback init --server <url> --key ptr_... --project <key> --environment local --yes\n\nRun 'npx -y pointer-feedback init --help' for the full list of flags."
|
|
876
|
+
);
|
|
877
|
+
process.exit(2);
|
|
878
|
+
}
|
|
879
|
+
var muted = false;
|
|
880
|
+
var gatedStdout = new Writable({
|
|
881
|
+
write(chunk, encoding, callback) {
|
|
882
|
+
if (!muted)
|
|
883
|
+
process.stdout.write(chunk, encoding);
|
|
884
|
+
callback();
|
|
885
|
+
}
|
|
886
|
+
});
|
|
887
|
+
var shared = null;
|
|
888
|
+
function iface() {
|
|
889
|
+
if (!shared) {
|
|
890
|
+
shared = readline.createInterface({
|
|
891
|
+
input: process.stdin,
|
|
892
|
+
output: gatedStdout,
|
|
893
|
+
terminal: true
|
|
894
|
+
});
|
|
895
|
+
shared.on("SIGINT", () => {
|
|
896
|
+
process.stdout.write("\n");
|
|
897
|
+
process.exit(130);
|
|
898
|
+
});
|
|
899
|
+
}
|
|
900
|
+
return shared;
|
|
901
|
+
}
|
|
902
|
+
function closePrompts() {
|
|
903
|
+
shared?.close();
|
|
904
|
+
shared = null;
|
|
905
|
+
}
|
|
870
906
|
async function ask(question, options = {}) {
|
|
871
|
-
|
|
872
|
-
const
|
|
873
|
-
write: function(chunk, encoding, callback) {
|
|
874
|
-
if (!muted)
|
|
875
|
-
process.stdout.write(chunk, encoding);
|
|
876
|
-
callback();
|
|
877
|
-
}
|
|
878
|
-
});
|
|
879
|
-
const rl = readline.createInterface({
|
|
880
|
-
input: process.stdin,
|
|
881
|
-
output: mutableStdout,
|
|
882
|
-
terminal: true
|
|
883
|
-
});
|
|
907
|
+
assertInteractive();
|
|
908
|
+
const rl = iface();
|
|
884
909
|
const displayQuestion = options.default ? `${question} [${options.default}]: ` : `${question}: `;
|
|
885
910
|
while (true) {
|
|
886
|
-
|
|
911
|
+
const pending = rl.question(displayQuestion);
|
|
887
912
|
if (options.secret)
|
|
888
913
|
muted = true;
|
|
889
|
-
|
|
914
|
+
let answer;
|
|
915
|
+
try {
|
|
916
|
+
answer = await pending;
|
|
917
|
+
} catch (err) {
|
|
918
|
+
muted = false;
|
|
919
|
+
if (err?.code === "ABORT_ERR") {
|
|
920
|
+
process.stdout.write("\nCancelled \u2014 nothing was written.\n");
|
|
921
|
+
process.exit(130);
|
|
922
|
+
}
|
|
923
|
+
throw err;
|
|
924
|
+
}
|
|
890
925
|
muted = false;
|
|
891
926
|
if (options.secret)
|
|
892
927
|
process.stdout.write("\n");
|
|
@@ -898,35 +933,96 @@ async function ask(question, options = {}) {
|
|
|
898
933
|
continue;
|
|
899
934
|
}
|
|
900
935
|
}
|
|
901
|
-
rl.close();
|
|
902
936
|
return finalAnswer;
|
|
903
937
|
}
|
|
904
938
|
}
|
|
939
|
+
async function menu(question, items, cursorStart, opts) {
|
|
940
|
+
assertInteractive();
|
|
941
|
+
const rl = iface();
|
|
942
|
+
const selected = opts.selected ?? /* @__PURE__ */ new Set();
|
|
943
|
+
let cursor = Math.max(0, Math.min(cursorStart, items.length - 1));
|
|
944
|
+
const hint = opts.hint ?? (opts.multi ? "\x1B[2m \u2191/\u2193 move \xB7 space toggle \xB7 a all \xB7 enter confirm\x1B[0m" : "\x1B[2m \u2191/\u2193 move \xB7 enter select\x1B[0m");
|
|
945
|
+
const render = (first) => {
|
|
946
|
+
if (!first)
|
|
947
|
+
process.stdout.write(`\x1B[${items.length + 1}A`);
|
|
948
|
+
process.stdout.write("\x1B[0J");
|
|
949
|
+
process.stdout.write(`${hint}
|
|
950
|
+
`);
|
|
951
|
+
items.forEach((item, i) => {
|
|
952
|
+
const pointer = i === cursor ? "\x1B[36m\u276F\x1B[0m" : " ";
|
|
953
|
+
const box = opts.multi ? selected.has(i) ? "\x1B[36m[x]\x1B[0m " : "[ ] " : "";
|
|
954
|
+
const label = i === cursor ? `\x1B[36m${item}\x1B[0m` : item;
|
|
955
|
+
process.stdout.write(`${pointer} ${box}${label}
|
|
956
|
+
`);
|
|
957
|
+
});
|
|
958
|
+
};
|
|
959
|
+
console.log(question);
|
|
960
|
+
rl.pause();
|
|
961
|
+
emitKeypressEvents(process.stdin);
|
|
962
|
+
const wasRaw = process.stdin.isRaw ?? false;
|
|
963
|
+
if (process.stdin.setRawMode)
|
|
964
|
+
process.stdin.setRawMode(true);
|
|
965
|
+
process.stdin.resume();
|
|
966
|
+
render(true);
|
|
967
|
+
try {
|
|
968
|
+
return await new Promise((resolve4) => {
|
|
969
|
+
const onKey = (_str, key) => {
|
|
970
|
+
if (key.ctrl && key.name === "c") {
|
|
971
|
+
cleanup();
|
|
972
|
+
process.stdout.write("\n");
|
|
973
|
+
process.exit(130);
|
|
974
|
+
}
|
|
975
|
+
if (key.name === "up" || key.name === "k") {
|
|
976
|
+
cursor = (cursor - 1 + items.length) % items.length;
|
|
977
|
+
render(false);
|
|
978
|
+
} else if (key.name === "down" || key.name === "j") {
|
|
979
|
+
cursor = (cursor + 1) % items.length;
|
|
980
|
+
render(false);
|
|
981
|
+
} else if (opts.multi && (key.name === "space" || key.sequence === " ")) {
|
|
982
|
+
selected.has(cursor) ? selected.delete(cursor) : selected.add(cursor);
|
|
983
|
+
render(false);
|
|
984
|
+
} else if (opts.multi && key.name === "a") {
|
|
985
|
+
if (selected.size === items.length)
|
|
986
|
+
selected.clear();
|
|
987
|
+
else
|
|
988
|
+
items.forEach((_, i) => selected.add(i));
|
|
989
|
+
render(false);
|
|
990
|
+
} else if (key.name === "return" || key.name === "enter") {
|
|
991
|
+
if (opts.multi && selected.size === 0) {
|
|
992
|
+
selected.add(cursor);
|
|
993
|
+
}
|
|
994
|
+
cleanup();
|
|
995
|
+
resolve4(opts.multi ? [...selected].sort((a, b) => a - b) : [cursor]);
|
|
996
|
+
}
|
|
997
|
+
};
|
|
998
|
+
const cleanup = () => {
|
|
999
|
+
process.stdin.off("keypress", onKey);
|
|
1000
|
+
if (process.stdin.setRawMode)
|
|
1001
|
+
process.stdin.setRawMode(wasRaw);
|
|
1002
|
+
};
|
|
1003
|
+
process.stdin.on("keypress", onKey);
|
|
1004
|
+
});
|
|
1005
|
+
} finally {
|
|
1006
|
+
if (process.stdin.setRawMode)
|
|
1007
|
+
process.stdin.setRawMode(wasRaw);
|
|
1008
|
+
rl.resume();
|
|
1009
|
+
}
|
|
1010
|
+
}
|
|
905
1011
|
async function select(question, items, defaultItem) {
|
|
906
|
-
const
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
const
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
1012
|
+
const start = defaultItem ? Math.max(0, items.indexOf(defaultItem)) : 0;
|
|
1013
|
+
const [chosen] = await menu(question, items, start, {});
|
|
1014
|
+
return items[chosen];
|
|
1015
|
+
}
|
|
1016
|
+
async function multiSelect(question, items, defaults = []) {
|
|
1017
|
+
const selected = /* @__PURE__ */ new Set();
|
|
1018
|
+
defaults.forEach((d) => {
|
|
1019
|
+
const i = items.indexOf(d);
|
|
1020
|
+
if (i >= 0)
|
|
1021
|
+
selected.add(i);
|
|
915
1022
|
});
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
const asNum = parseInt(finalAnswer, 10);
|
|
920
|
-
if (!isNaN(asNum) && asNum >= 1 && asNum <= items.length) {
|
|
921
|
-
rl.close();
|
|
922
|
-
return items[asNum - 1];
|
|
923
|
-
}
|
|
924
|
-
if (items.includes(finalAnswer)) {
|
|
925
|
-
rl.close();
|
|
926
|
-
return finalAnswer;
|
|
927
|
-
}
|
|
928
|
-
console.log("\x1B[31mInvalid selection\x1B[0m");
|
|
929
|
-
}
|
|
1023
|
+
const start = selected.size ? Math.min(...selected) : 0;
|
|
1024
|
+
const chosen = await menu(question, items, start, { selected, multi: true });
|
|
1025
|
+
return chosen.map((i) => items[i]);
|
|
930
1026
|
}
|
|
931
1027
|
|
|
932
1028
|
// src/commands/init.ts
|
|
@@ -1522,7 +1618,7 @@ async function runInitChecks(cwd2, overrides = {}, cliVersion = "0.0.0") {
|
|
|
1522
1618
|
checks.push({ id: "project", status: "warn", message: `Could not list projects: ${err?.message ?? err}` });
|
|
1523
1619
|
}
|
|
1524
1620
|
}
|
|
1525
|
-
checks.push(await widgetCheck(cwd2));
|
|
1621
|
+
checks.push(await widgetCheck(cwd2, config));
|
|
1526
1622
|
if (serverReachable) {
|
|
1527
1623
|
try {
|
|
1528
1624
|
const res = await fetchWithTimeout(`${server}/pointer.js`, 3e3);
|
|
@@ -1565,9 +1661,9 @@ async function runInitChecks(cwd2, overrides = {}, cliVersion = "0.0.0") {
|
|
|
1565
1661
|
}
|
|
1566
1662
|
return checks;
|
|
1567
1663
|
}
|
|
1568
|
-
async function widgetCheck(cwd2) {
|
|
1664
|
+
async function widgetCheck(cwd2, config = {}) {
|
|
1569
1665
|
const detection = await detectStack(cwd2).catch(() => null);
|
|
1570
|
-
const candidates = [detection?.htmlPath, "index.html", "public/index.html", "src/index.html"].filter(Boolean);
|
|
1666
|
+
const candidates = [config.htmlPath, detection?.htmlPath, "index.html", "public/index.html", "src/index.html"].filter(Boolean);
|
|
1571
1667
|
for (const rel of candidates) {
|
|
1572
1668
|
try {
|
|
1573
1669
|
const html = await fs7.readFile(join7(cwd2, rel), "utf8");
|
|
@@ -2415,6 +2511,20 @@ async function initCommand(cwd2, options = {}) {
|
|
|
2415
2511
|
}
|
|
2416
2512
|
await writeCredentials(cwd2, key);
|
|
2417
2513
|
await upsertGitignore(cwd2, product);
|
|
2514
|
+
const appInfo = await detectStack(cwd2);
|
|
2515
|
+
const canInject = appInfo.kind === "vite" || appInfo.kind === "static" || !!options["html"];
|
|
2516
|
+
if (!isJson && !isYes) {
|
|
2517
|
+
console.log(`
|
|
2518
|
+
Stack: ${appInfo.kind}${appInfo.evidence.length ? ` (${appInfo.evidence.join(", ")})` : ""}`);
|
|
2519
|
+
if (!canInject && !options["no-inject"]) {
|
|
2520
|
+
console.log(
|
|
2521
|
+
`\x1B[33mHeads up:\x1B[0m automatic widget injection isn't supported for ${appInfo.kind} yet.
|
|
2522
|
+
Everything else still applies \u2014 the questions below set up your project, key and skills,
|
|
2523
|
+
and the pointer-init skill uses them to mount the widget for you afterwards.
|
|
2524
|
+
`
|
|
2525
|
+
);
|
|
2526
|
+
}
|
|
2527
|
+
}
|
|
2418
2528
|
let project = options["project"];
|
|
2419
2529
|
let create = options["create"];
|
|
2420
2530
|
let finalProjectKey = project || "";
|
|
@@ -2474,24 +2584,16 @@ async function initCommand(cwd2, options = {}) {
|
|
|
2474
2584
|
if (!isYes && !options["environment"]) {
|
|
2475
2585
|
env = await select("Environment", ["local", "staging", "production"], env);
|
|
2476
2586
|
}
|
|
2477
|
-
const appInfo = await detectStack(cwd2);
|
|
2478
2587
|
let appUrl = options["app-url"];
|
|
2479
2588
|
let noAppUrl = options["no-app-url"];
|
|
2480
2589
|
let source = "";
|
|
2481
2590
|
if (!noAppUrl && !appUrl) {
|
|
2482
2591
|
const detected = await detectAppUrl(cwd2, appInfo.kind, env);
|
|
2483
2592
|
source = detected.source;
|
|
2484
|
-
|
|
2485
|
-
const displayDefault = detected.url ? detected.url : "";
|
|
2486
|
-
const ans = await ask(`Where does this app run in ${env}?`, { default: displayDefault });
|
|
2487
|
-
appUrl = ans || void 0;
|
|
2488
|
-
} else {
|
|
2489
|
-
appUrl = detected.url || void 0;
|
|
2490
|
-
}
|
|
2491
|
-
}
|
|
2492
|
-
if (appUrl && env !== "local") {
|
|
2593
|
+
appUrl = detected.url || void 0;
|
|
2493
2594
|
}
|
|
2494
2595
|
let tool = options["tool"];
|
|
2596
|
+
let tools = tool ? [tool] : [];
|
|
2495
2597
|
if (!tool) {
|
|
2496
2598
|
if (process.env.CLAUDECODE || process.env.CLAUDE_CODE_ENTRYPOINT)
|
|
2497
2599
|
tool = "claude-code";
|
|
@@ -2506,9 +2608,20 @@ async function initCommand(cwd2, options = {}) {
|
|
|
2506
2608
|
else
|
|
2507
2609
|
tool = isYes ? "other" : "claude-code";
|
|
2508
2610
|
if (!isYes && !options["tool"]) {
|
|
2509
|
-
|
|
2611
|
+
const ALL = "all of them";
|
|
2612
|
+
const catalogue = ["claude-code", "cursor", "windsurf", "opencode", "antigravity", "other"];
|
|
2613
|
+
const picked = await multiSelect(
|
|
2614
|
+
"Which AI tools work in this repo?",
|
|
2615
|
+
[ALL, ...catalogue],
|
|
2616
|
+
[tool]
|
|
2617
|
+
);
|
|
2618
|
+
tools = picked.includes(ALL) ? catalogue : picked;
|
|
2619
|
+
tool = tools[0] ?? tool;
|
|
2510
2620
|
}
|
|
2511
2621
|
}
|
|
2622
|
+
if (tools.length === 0)
|
|
2623
|
+
tools = [tool];
|
|
2624
|
+
closePrompts();
|
|
2512
2625
|
if (!isJson)
|
|
2513
2626
|
console.log(`Detecting your stack... -> ${appInfo.kind} (${appInfo.evidence.join(", ")})`);
|
|
2514
2627
|
let injected = false;
|
|
@@ -2533,7 +2646,19 @@ async function initCommand(cwd2, options = {}) {
|
|
|
2533
2646
|
}
|
|
2534
2647
|
}
|
|
2535
2648
|
if (!options["no-inject"]) {
|
|
2536
|
-
|
|
2649
|
+
const explicitHtml = options["html"];
|
|
2650
|
+
if (explicitHtml && appInfo.kind !== "vite") {
|
|
2651
|
+
const htmlPath = await injectStatic(cwd2, explicitHtml, {
|
|
2652
|
+
server,
|
|
2653
|
+
key: finalProjectKey,
|
|
2654
|
+
environment: env,
|
|
2655
|
+
pin
|
|
2656
|
+
});
|
|
2657
|
+
filesMod = [htmlPath];
|
|
2658
|
+
injected = true;
|
|
2659
|
+
if (!isJson)
|
|
2660
|
+
console.log(`Injected widget into ${htmlPath}`);
|
|
2661
|
+
} else if (appInfo.kind === "vite") {
|
|
2537
2662
|
filesMod = await injectVite(cwd2, { server, key: finalProjectKey, environment: env, pin }, options["html"]);
|
|
2538
2663
|
injected = true;
|
|
2539
2664
|
if (!isJson)
|
|
@@ -2547,18 +2672,23 @@ async function initCommand(cwd2, options = {}) {
|
|
|
2547
2672
|
} else if (appInfo.kind !== "unknown") {
|
|
2548
2673
|
routedToSkill = true;
|
|
2549
2674
|
if (!isJson) {
|
|
2550
|
-
console.log(`\u2139 ${appInfo.kind} detected \u2014
|
|
2551
|
-
|
|
2675
|
+
console.log(`\u2139 ${appInfo.kind} detected \u2014 there's no single entry point to inject into automatically.
|
|
2676
|
+
If you know the file, name it and re-run \u2014 that always wins over detection:
|
|
2677
|
+
npx -y pointer-feedback init --html path/to/index.html
|
|
2678
|
+
Otherwise the pointer-init skill was installed for ${tool}; run it and it will mount the widget:
|
|
2552
2679
|
claude -> /pointer-init (or @pointer-init for cursor)
|
|
2553
|
-
Config is already saved in .pointer/config.json, so
|
|
2680
|
+
Config is already saved in .pointer/config.json, so neither will ask for the key or project again.`);
|
|
2554
2681
|
}
|
|
2555
2682
|
}
|
|
2556
2683
|
}
|
|
2557
2684
|
if (!options["no-skills"]) {
|
|
2558
2685
|
if (!isJson)
|
|
2559
|
-
console.log(
|
|
2686
|
+
console.log(`Installing AI skills for ${tools.join(", ")}`);
|
|
2560
2687
|
const skillsDir = options["skills-dir"];
|
|
2561
|
-
const installed =
|
|
2688
|
+
const installed = [];
|
|
2689
|
+
for (const t of tools) {
|
|
2690
|
+
installed.push(...await installSkills(server, t, cwd2, t === tool ? skillsDir : void 0));
|
|
2691
|
+
}
|
|
2562
2692
|
filesMod.push(...installed);
|
|
2563
2693
|
skillFiles = installed.filter((f) => f.includes("SKILL.md") || f.endsWith(".md"));
|
|
2564
2694
|
}
|
|
@@ -2584,7 +2714,8 @@ async function initCommand(cwd2, options = {}) {
|
|
|
2584
2714
|
}
|
|
2585
2715
|
const mergedStack = mergeStack(stackMeta, serverStackResponse?.data ?? serverStackResponse, noDesign ? null : designBlock);
|
|
2586
2716
|
await writeStackFile(cwd2, mergedStack);
|
|
2587
|
-
|
|
2717
|
+
const injectedHtml = injected ? filesMod.find((f) => f.toLowerCase().endsWith(".html"))?.replace(`${cwd2}/`, "") : void 0;
|
|
2718
|
+
await writeConfig(cwd2, { server, project: finalProjectKey, environment: env, aiTool: tool, skillsDir: options["skills-dir"], cliVersion: BUILD_CLI_VERSION, htmlPath: injectedHtml });
|
|
2588
2719
|
filesMod.push(".pointer/config.json");
|
|
2589
2720
|
filesMod.push(".pointer/credentials.env");
|
|
2590
2721
|
filesMod.push(".gitignore");
|
package/package.json
CHANGED