@pipod/cli 0.1.19 → 0.1.21
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/chunk-7TJWBDVX.js +100 -0
- package/dist/chunk-7TJWBDVX.js.map +1 -0
- package/dist/cli.js +99 -116
- package/dist/cli.js.map +1 -1
- package/dist/key-repeat-guard-7OVKW5KD.js +64 -0
- package/dist/key-repeat-guard-7OVKW5KD.js.map +1 -0
- package/dist/stdin-fragment-guard-SPIJC7GT.js +61 -0
- package/dist/stdin-fragment-guard-SPIJC7GT.js.map +1 -0
- package/docs/classic-runtime-coupling.md +12 -0
- package/docs/keyboard-input.md +91 -0
- package/docs/pod-list-pagination.md +36 -1
- package/man/pipod-config.5 +1 -1
- package/man/pipod.1 +4 -1
- package/man/pipod.1.md +3 -0
- package/npm-shrinkwrap.json +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// src/log.ts
|
|
2
|
+
import { stripVTControlCharacters } from "util";
|
|
3
|
+
|
|
4
|
+
// src/redact.ts
|
|
5
|
+
var MIN_REDACTABLE_LENGTH = 4;
|
|
6
|
+
var secrets = /* @__PURE__ */ new Set();
|
|
7
|
+
function registerSecret(value) {
|
|
8
|
+
if (!value) return;
|
|
9
|
+
const v = String(value);
|
|
10
|
+
if (v.length < MIN_REDACTABLE_LENGTH) return;
|
|
11
|
+
secrets.add(v);
|
|
12
|
+
}
|
|
13
|
+
function registerSecrets(values) {
|
|
14
|
+
for (const v of Object.values(values)) registerSecret(v);
|
|
15
|
+
}
|
|
16
|
+
function redact(input) {
|
|
17
|
+
if (secrets.size === 0) return input;
|
|
18
|
+
let out2 = input;
|
|
19
|
+
for (const secret of [...secrets].sort((a, b) => b.length - a.length)) {
|
|
20
|
+
if (out2.includes(secret)) out2 = out2.split(secret).join("[redacted]");
|
|
21
|
+
}
|
|
22
|
+
return out2;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// src/log.ts
|
|
26
|
+
var verbose = false;
|
|
27
|
+
var colorEnabled = process.stderr.isTTY === true && !process.env.NO_COLOR;
|
|
28
|
+
function setVerbose(v) {
|
|
29
|
+
verbose = v;
|
|
30
|
+
}
|
|
31
|
+
function isVerbose() {
|
|
32
|
+
return verbose;
|
|
33
|
+
}
|
|
34
|
+
var CODES = {
|
|
35
|
+
reset: "\x1B[0m",
|
|
36
|
+
dim: "\x1B[2m",
|
|
37
|
+
bold: "\x1B[1m",
|
|
38
|
+
red: "\x1B[31m",
|
|
39
|
+
yellow: "\x1B[33m",
|
|
40
|
+
cyan: "\x1B[36m",
|
|
41
|
+
green: "\x1B[32m"
|
|
42
|
+
};
|
|
43
|
+
function paint(code, s) {
|
|
44
|
+
return colorEnabled ? `${CODES[code]}${s}${CODES.reset}` : s;
|
|
45
|
+
}
|
|
46
|
+
var color = {
|
|
47
|
+
dim: (s) => paint("dim", s),
|
|
48
|
+
bold: (s) => paint("bold", s),
|
|
49
|
+
red: (s) => paint("red", s),
|
|
50
|
+
yellow: (s) => paint("yellow", s),
|
|
51
|
+
cyan: (s) => paint("cyan", s),
|
|
52
|
+
green: (s) => paint("green", s)
|
|
53
|
+
};
|
|
54
|
+
function write(line) {
|
|
55
|
+
process.stderr.write(redact(line) + "\n");
|
|
56
|
+
}
|
|
57
|
+
function info(msg) {
|
|
58
|
+
write(`${paint("cyan", "pi pod")} ${msg}`);
|
|
59
|
+
}
|
|
60
|
+
function step(phase, msg) {
|
|
61
|
+
write(`${paint("cyan", "pi pod")} ${paint("dim", `[${phase}]`)} ${msg}`);
|
|
62
|
+
}
|
|
63
|
+
function warn(msg) {
|
|
64
|
+
write(`${paint("yellow", "warning")} ${msg}`);
|
|
65
|
+
}
|
|
66
|
+
function error(msg) {
|
|
67
|
+
write(`${paint("red", "error")} ${msg}`);
|
|
68
|
+
}
|
|
69
|
+
function hint(msg) {
|
|
70
|
+
const lines = msg.split("\n");
|
|
71
|
+
write(`${paint("dim", " hint:")} ${lines[0] ?? ""}`);
|
|
72
|
+
for (const line of lines.slice(1)) write(paint("dim", ` ${line}`));
|
|
73
|
+
}
|
|
74
|
+
function debug(msg) {
|
|
75
|
+
if (verbose) write(`${paint("dim", "debug")} ${paint("dim", msg)}`);
|
|
76
|
+
}
|
|
77
|
+
function plain(msg) {
|
|
78
|
+
write(msg);
|
|
79
|
+
}
|
|
80
|
+
function out(msg) {
|
|
81
|
+
const line = redact(msg);
|
|
82
|
+
process.stdout.write((process.stdout.isTTY === true ? line : stripVTControlCharacters(line)) + "\n");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export {
|
|
86
|
+
registerSecret,
|
|
87
|
+
registerSecrets,
|
|
88
|
+
setVerbose,
|
|
89
|
+
isVerbose,
|
|
90
|
+
color,
|
|
91
|
+
info,
|
|
92
|
+
step,
|
|
93
|
+
warn,
|
|
94
|
+
error,
|
|
95
|
+
hint,
|
|
96
|
+
debug,
|
|
97
|
+
plain,
|
|
98
|
+
out
|
|
99
|
+
};
|
|
100
|
+
//# sourceMappingURL=chunk-7TJWBDVX.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/log.ts","../src/redact.ts"],"sourcesContent":["/**\n * src/log.ts — the launcher's logger. Everything user-visible goes through here so that\n * redaction (§7.3) is structural rather than something each call site has to remember.\n */\nimport { stripVTControlCharacters } from \"node:util\";\nimport { redact } from \"./redact.js\";\n\nlet verbose = false;\nlet colorEnabled = process.stderr.isTTY === true && !process.env.NO_COLOR;\n\nexport function setVerbose(v: boolean): void {\n verbose = v;\n}\n\nexport function isVerbose(): boolean {\n return verbose;\n}\n\nexport function setColor(enabled: boolean): void {\n colorEnabled = enabled;\n}\n\nconst CODES = {\n reset: \"\\u001b[0m\",\n dim: \"\\u001b[2m\",\n bold: \"\\u001b[1m\",\n red: \"\\u001b[31m\",\n yellow: \"\\u001b[33m\",\n cyan: \"\\u001b[36m\",\n green: \"\\u001b[32m\",\n} as const;\n\nfunction paint(code: keyof typeof CODES, s: string): string {\n return colorEnabled ? `${CODES[code]}${s}${CODES.reset}` : s;\n}\n\nexport const color = {\n dim: (s: string) => paint(\"dim\", s),\n bold: (s: string) => paint(\"bold\", s),\n red: (s: string) => paint(\"red\", s),\n yellow: (s: string) => paint(\"yellow\", s),\n cyan: (s: string) => paint(\"cyan\", s),\n green: (s: string) => paint(\"green\", s),\n};\n\n/**\n * Diagnostics go to stderr so that stdout stays clean for the PTY stream and for\n * machine-readable output (`--dry-run`, `--version`).\n */\nfunction write(line: string): void {\n process.stderr.write(redact(line) + \"\\n\");\n}\n\nexport function info(msg: string): void {\n write(`${paint(\"cyan\", \"pi pod\")} ${msg}`);\n}\n\nexport function step(phase: string, msg: string): void {\n write(`${paint(\"cyan\", \"pi pod\")} ${paint(\"dim\", `[${phase}]`)} ${msg}`);\n}\n\nexport function warn(msg: string): void {\n write(`${paint(\"yellow\", \"warning\")} ${msg}`);\n}\n\nexport function error(msg: string): void {\n write(`${paint(\"red\", \"error\")} ${msg}`);\n}\n\n/** Multi-line hints are one hint: the prefix appears once, continuation lines align under it. */\nexport function hint(msg: string): void {\n const lines = msg.split(\"\\n\");\n write(`${paint(\"dim\", \" hint:\")} ${lines[0] ?? \"\"}`);\n for (const line of lines.slice(1)) write(paint(\"dim\", ` ${line}`));\n}\n\nexport function debug(msg: string): void {\n if (verbose) write(`${paint(\"dim\", \"debug\")} ${paint(\"dim\", msg)}`);\n}\n\nexport function plain(msg: string): void {\n write(msg);\n}\n\n/**\n * Direct stdout writer for command results and machine-readable output; still redacted.\n * Color is useful at a terminal but must never leak into a pipe or redirected file.\n */\nexport function out(msg: string): void {\n const line = redact(msg);\n process.stdout.write((process.stdout.isTTY === true ? line : stripVTControlCharacters(line)) + \"\\n\");\n}\n\n/**\n * Wall-clock phase timings for one launch/attach, so the summary line can say where the\n * time went. Overlapping (parallelized) phases each record their own duration; the summary\n * total is wall time, not the sum.\n */\nexport interface PhaseTiming {\n phase: string;\n ms: number;\n}\n\nconst phaseTimings: PhaseTiming[] = [];\n\nexport function resetPhaseTimings(): void {\n phaseTimings.length = 0;\n}\n\nexport function recordPhase(phase: string, ms: number): void {\n phaseTimings.push({ phase, ms });\n}\n\nexport async function timePhase<T>(phase: string, work: () => Promise<T>): Promise<T> {\n const startedAt = Date.now();\n try {\n return await work();\n } finally {\n recordPhase(phase, Date.now() - startedAt);\n }\n}\n\nfunction fmtSeconds(ms: number): string {\n return `${(ms / 1000).toFixed(1)}s`;\n}\n\n/** One line naming total wall time and every phase that cost at least 100ms. */\nexport function phaseSummary(label: string, totalMs: number): string {\n const parts = phaseTimings.filter((t) => t.ms >= 100).map((t) => `${t.phase} ${fmtSeconds(t.ms)}`);\n const breakdown = parts.length > 0 ? ` (${parts.join(\" · \")})` : \"\";\n return `${label} in ${fmtSeconds(totalMs)}${breakdown}`;\n}\n\n/**\n * Print a periodic \"still …\" line while a long provider operation runs, so a multi-minute\n * wait is distinguishable from a hang. The line names the elapsed time and, when known,\n * roughly how long the wait usually takes.\n */\nexport const LIVENESS_INTERVAL_MS = 15_000;\n\nexport async function withLiveness<T>(\n label: string,\n work: () => Promise<T>,\n opts: { intervalMs?: number; expectation?: string } = {},\n): Promise<T> {\n const intervalMs = opts.intervalMs ?? LIVENESS_INTERVAL_MS;\n const startedAt = Date.now();\n const timer = setInterval(() => {\n const elapsed = Math.round((Date.now() - startedAt) / 1000);\n const usually = opts.expectation ? `, ${opts.expectation}` : \"\";\n write(`${paint(\"cyan\", \"pi pod\")} ${paint(\"dim\", `still ${label} (${elapsed}s elapsed${usually})`)}`);\n }, intervalMs);\n timer.unref?.();\n try {\n return await work();\n } finally {\n clearInterval(timer);\n }\n}\n\n/**\n * Stream a child stream to the terminal with a prefix (`[init]`, §4.3). Handles partial\n * lines so a chunk boundary never splits a prefix.\n */\nexport function prefixedStreamer(prefix: string): (chunk: Uint8Array) => void {\n let pending = \"\";\n const flushLine = (line: string) => write(`${paint(\"dim\", prefix)} ${line}`);\n return (chunk: Uint8Array) => {\n pending += Buffer.from(chunk).toString(\"utf8\");\n const lines = pending.split(/\\r?\\n/);\n pending = lines.pop() ?? \"\";\n for (const line of lines) flushLine(line);\n // Guard against a runaway line with no newline ever arriving.\n if (pending.length > 8192) {\n flushLine(pending);\n pending = \"\";\n }\n };\n}\n","/**\n * src/redact.ts — secret-aware log redaction (§7.3).\n *\n * Every value originating from the env file is registered here; the logger runs all output\n * through `redact()`. The conformance suite (§14) asserts that no registered value ever\n * appears in captured logs.\n */\n\n/** Values short enough to appear coincidentally are not worth redacting (and would mangle output). */\nconst MIN_REDACTABLE_LENGTH = 4;\n\nconst secrets = new Set<string>();\n\nexport function registerSecret(value: string | undefined | null): void {\n if (!value) return;\n const v = String(value);\n if (v.length < MIN_REDACTABLE_LENGTH) return;\n secrets.add(v);\n}\n\nexport function registerSecrets(values: Record<string, string>): void {\n for (const v of Object.values(values)) registerSecret(v);\n}\n\n/** Test/reset hook. */\nexport function clearSecrets(): void {\n secrets.clear();\n}\n\nexport function registeredSecretCount(): number {\n return secrets.size;\n}\n\n/**\n * Replace every registered secret with `[redacted]`. Longest-first so that a secret which\n * contains another secret as a substring is not partially revealed.\n */\nexport function redact(input: string): string {\n if (secrets.size === 0) return input;\n let out = input;\n for (const secret of [...secrets].sort((a, b) => b.length - a.length)) {\n if (out.includes(secret)) out = out.split(secret).join(\"[redacted]\");\n }\n return out;\n}\n\nexport function redactBytes(input: Uint8Array): Uint8Array {\n if (secrets.size === 0) return input;\n const text = Buffer.from(input).toString(\"utf8\");\n const cleaned = redact(text);\n return cleaned === text ? input : Buffer.from(cleaned, \"utf8\");\n}\n\n/** Redact an arbitrary structure for debug dumps. Keys are preserved, values scrubbed. */\nexport function redactDeep<T>(value: T): T {\n if (typeof value === \"string\") return redact(value) as unknown as T;\n if (Array.isArray(value)) return value.map((v) => redactDeep(v)) as unknown as T;\n if (value && typeof value === \"object\") {\n const out: Record<string, unknown> = {};\n for (const [k, v] of Object.entries(value as Record<string, unknown>)) out[k] = redactDeep(v);\n return out as unknown as T;\n }\n return value;\n}\n"],"mappings":";AAIA,SAAS,gCAAgC;;;ACKzC,IAAM,wBAAwB;AAE9B,IAAM,UAAU,oBAAI,IAAY;AAEzB,SAAS,eAAe,OAAwC;AACrE,MAAI,CAAC,MAAO;AACZ,QAAM,IAAI,OAAO,KAAK;AACtB,MAAI,EAAE,SAAS,sBAAuB;AACtC,UAAQ,IAAI,CAAC;AACf;AAEO,SAAS,gBAAgB,QAAsC;AACpE,aAAW,KAAK,OAAO,OAAO,MAAM,EAAG,gBAAe,CAAC;AACzD;AAeO,SAAS,OAAO,OAAuB;AAC5C,MAAI,QAAQ,SAAS,EAAG,QAAO;AAC/B,MAAIA,OAAM;AACV,aAAW,UAAU,CAAC,GAAG,OAAO,EAAE,KAAK,CAAC,GAAG,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG;AACrE,QAAIA,KAAI,SAAS,MAAM,EAAG,CAAAA,OAAMA,KAAI,MAAM,MAAM,EAAE,KAAK,YAAY;AAAA,EACrE;AACA,SAAOA;AACT;;;ADrCA,IAAI,UAAU;AACd,IAAI,eAAe,QAAQ,OAAO,UAAU,QAAQ,CAAC,QAAQ,IAAI;AAE1D,SAAS,WAAW,GAAkB;AAC3C,YAAU;AACZ;AAEO,SAAS,YAAqB;AACnC,SAAO;AACT;AAMA,IAAM,QAAQ;AAAA,EACZ,OAAO;AAAA,EACP,KAAK;AAAA,EACL,MAAM;AAAA,EACN,KAAK;AAAA,EACL,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,OAAO;AACT;AAEA,SAAS,MAAM,MAA0B,GAAmB;AAC1D,SAAO,eAAe,GAAG,MAAM,IAAI,CAAC,GAAG,CAAC,GAAG,MAAM,KAAK,KAAK;AAC7D;AAEO,IAAM,QAAQ;AAAA,EACnB,KAAK,CAAC,MAAc,MAAM,OAAO,CAAC;AAAA,EAClC,MAAM,CAAC,MAAc,MAAM,QAAQ,CAAC;AAAA,EACpC,KAAK,CAAC,MAAc,MAAM,OAAO,CAAC;AAAA,EAClC,QAAQ,CAAC,MAAc,MAAM,UAAU,CAAC;AAAA,EACxC,MAAM,CAAC,MAAc,MAAM,QAAQ,CAAC;AAAA,EACpC,OAAO,CAAC,MAAc,MAAM,SAAS,CAAC;AACxC;AAMA,SAAS,MAAM,MAAoB;AACjC,UAAQ,OAAO,MAAM,OAAO,IAAI,IAAI,IAAI;AAC1C;AAEO,SAAS,KAAK,KAAmB;AACtC,QAAM,GAAG,MAAM,QAAQ,QAAQ,CAAC,IAAI,GAAG,EAAE;AAC3C;AAEO,SAAS,KAAK,OAAe,KAAmB;AACrD,QAAM,GAAG,MAAM,QAAQ,QAAQ,CAAC,IAAI,MAAM,OAAO,IAAI,KAAK,GAAG,CAAC,IAAI,GAAG,EAAE;AACzE;AAEO,SAAS,KAAK,KAAmB;AACtC,QAAM,GAAG,MAAM,UAAU,SAAS,CAAC,IAAI,GAAG,EAAE;AAC9C;AAEO,SAAS,MAAM,KAAmB;AACvC,QAAM,GAAG,MAAM,OAAO,OAAO,CAAC,IAAI,GAAG,EAAE;AACzC;AAGO,SAAS,KAAK,KAAmB;AACtC,QAAM,QAAQ,IAAI,MAAM,IAAI;AAC5B,QAAM,GAAG,MAAM,OAAO,SAAS,CAAC,IAAI,MAAM,CAAC,KAAK,EAAE,EAAE;AACpD,aAAW,QAAQ,MAAM,MAAM,CAAC,EAAG,OAAM,MAAM,OAAO,WAAW,IAAI,EAAE,CAAC;AAC1E;AAEO,SAAS,MAAM,KAAmB;AACvC,MAAI,QAAS,OAAM,GAAG,MAAM,OAAO,OAAO,CAAC,IAAI,MAAM,OAAO,GAAG,CAAC,EAAE;AACpE;AAEO,SAAS,MAAM,KAAmB;AACvC,QAAM,GAAG;AACX;AAMO,SAAS,IAAI,KAAmB;AACrC,QAAM,OAAO,OAAO,GAAG;AACvB,UAAQ,OAAO,OAAO,QAAQ,OAAO,UAAU,OAAO,OAAO,yBAAyB,IAAI,KAAK,IAAI;AACrG;","names":["out"]}
|
package/dist/cli.js
CHANGED
|
@@ -4,6 +4,21 @@ import {
|
|
|
4
4
|
POD_SUBCOMMANDS,
|
|
5
5
|
PodCommandRouter
|
|
6
6
|
} from "./chunk-GNKF75X7.js";
|
|
7
|
+
import {
|
|
8
|
+
color,
|
|
9
|
+
debug,
|
|
10
|
+
error,
|
|
11
|
+
hint,
|
|
12
|
+
info,
|
|
13
|
+
isVerbose,
|
|
14
|
+
out,
|
|
15
|
+
plain,
|
|
16
|
+
registerSecret,
|
|
17
|
+
registerSecrets,
|
|
18
|
+
setVerbose,
|
|
19
|
+
step,
|
|
20
|
+
warn
|
|
21
|
+
} from "./chunk-7TJWBDVX.js";
|
|
7
22
|
|
|
8
23
|
// src/cli.ts
|
|
9
24
|
import * as fs36 from "fs";
|
|
@@ -96,90 +111,6 @@ var PodAsleepError = class extends PiPodError {
|
|
|
96
111
|
import * as fs7 from "fs";
|
|
97
112
|
import { Readable } from "stream";
|
|
98
113
|
|
|
99
|
-
// src/log.ts
|
|
100
|
-
import { stripVTControlCharacters } from "util";
|
|
101
|
-
|
|
102
|
-
// src/redact.ts
|
|
103
|
-
var MIN_REDACTABLE_LENGTH = 4;
|
|
104
|
-
var secrets = /* @__PURE__ */ new Set();
|
|
105
|
-
function registerSecret(value) {
|
|
106
|
-
if (!value) return;
|
|
107
|
-
const v = String(value);
|
|
108
|
-
if (v.length < MIN_REDACTABLE_LENGTH) return;
|
|
109
|
-
secrets.add(v);
|
|
110
|
-
}
|
|
111
|
-
function registerSecrets(values) {
|
|
112
|
-
for (const v of Object.values(values)) registerSecret(v);
|
|
113
|
-
}
|
|
114
|
-
function redact(input) {
|
|
115
|
-
if (secrets.size === 0) return input;
|
|
116
|
-
let out2 = input;
|
|
117
|
-
for (const secret of [...secrets].sort((a, b) => b.length - a.length)) {
|
|
118
|
-
if (out2.includes(secret)) out2 = out2.split(secret).join("[redacted]");
|
|
119
|
-
}
|
|
120
|
-
return out2;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// src/log.ts
|
|
124
|
-
var verbose = false;
|
|
125
|
-
var colorEnabled = process.stderr.isTTY === true && !process.env.NO_COLOR;
|
|
126
|
-
function setVerbose(v) {
|
|
127
|
-
verbose = v;
|
|
128
|
-
}
|
|
129
|
-
function isVerbose() {
|
|
130
|
-
return verbose;
|
|
131
|
-
}
|
|
132
|
-
var CODES = {
|
|
133
|
-
reset: "\x1B[0m",
|
|
134
|
-
dim: "\x1B[2m",
|
|
135
|
-
bold: "\x1B[1m",
|
|
136
|
-
red: "\x1B[31m",
|
|
137
|
-
yellow: "\x1B[33m",
|
|
138
|
-
cyan: "\x1B[36m",
|
|
139
|
-
green: "\x1B[32m"
|
|
140
|
-
};
|
|
141
|
-
function paint(code, s) {
|
|
142
|
-
return colorEnabled ? `${CODES[code]}${s}${CODES.reset}` : s;
|
|
143
|
-
}
|
|
144
|
-
var color = {
|
|
145
|
-
dim: (s) => paint("dim", s),
|
|
146
|
-
bold: (s) => paint("bold", s),
|
|
147
|
-
red: (s) => paint("red", s),
|
|
148
|
-
yellow: (s) => paint("yellow", s),
|
|
149
|
-
cyan: (s) => paint("cyan", s),
|
|
150
|
-
green: (s) => paint("green", s)
|
|
151
|
-
};
|
|
152
|
-
function write(line) {
|
|
153
|
-
process.stderr.write(redact(line) + "\n");
|
|
154
|
-
}
|
|
155
|
-
function info(msg) {
|
|
156
|
-
write(`${paint("cyan", "pi pod")} ${msg}`);
|
|
157
|
-
}
|
|
158
|
-
function step(phase, msg) {
|
|
159
|
-
write(`${paint("cyan", "pi pod")} ${paint("dim", `[${phase}]`)} ${msg}`);
|
|
160
|
-
}
|
|
161
|
-
function warn(msg) {
|
|
162
|
-
write(`${paint("yellow", "warning")} ${msg}`);
|
|
163
|
-
}
|
|
164
|
-
function error(msg) {
|
|
165
|
-
write(`${paint("red", "error")} ${msg}`);
|
|
166
|
-
}
|
|
167
|
-
function hint(msg) {
|
|
168
|
-
const lines = msg.split("\n");
|
|
169
|
-
write(`${paint("dim", " hint:")} ${lines[0] ?? ""}`);
|
|
170
|
-
for (const line of lines.slice(1)) write(paint("dim", ` ${line}`));
|
|
171
|
-
}
|
|
172
|
-
function debug(msg) {
|
|
173
|
-
if (verbose) write(`${paint("dim", "debug")} ${paint("dim", msg)}`);
|
|
174
|
-
}
|
|
175
|
-
function plain(msg) {
|
|
176
|
-
write(msg);
|
|
177
|
-
}
|
|
178
|
-
function out(msg) {
|
|
179
|
-
const line = redact(msg);
|
|
180
|
-
process.stdout.write((process.stdout.isTTY === true ? line : stripVTControlCharacters(line)) + "\n");
|
|
181
|
-
}
|
|
182
|
-
|
|
183
114
|
// src/account/oidc.ts
|
|
184
115
|
import { createHash, randomBytes, timingSafeEqual } from "crypto";
|
|
185
116
|
import { createRemoteJWKSet, jwtVerify } from "jose";
|
|
@@ -1942,6 +1873,10 @@ var AccountClient = class {
|
|
|
1942
1873
|
listPods(query = {}) {
|
|
1943
1874
|
return this.request("/pods", { query });
|
|
1944
1875
|
}
|
|
1876
|
+
/** Null means an older server lacks lookup, not that the ref had no matches. */
|
|
1877
|
+
lookupPodRef(ref) {
|
|
1878
|
+
return this.request(`/pods/by-ref/${encodeURIComponent(ref)}`, { allow404: true });
|
|
1879
|
+
}
|
|
1945
1880
|
/** `waitMs` long-polls: a supporting server answers on the next pod change; older servers ignore it. */
|
|
1946
1881
|
getPod(id, opts = {}) {
|
|
1947
1882
|
const waitMs = opts.waitMs != null && opts.waitMs > 0 ? Math.floor(opts.waitMs) : void 0;
|
|
@@ -5137,19 +5072,24 @@ function displayRef(id, kind) {
|
|
|
5137
5072
|
const tail = id.replaceAll("-", "").slice(-DISPLAY_TAIL_LENGTH).toLowerCase();
|
|
5138
5073
|
return `${kind === "pod" ? "p" : "j"}-${tail}`;
|
|
5139
5074
|
}
|
|
5140
|
-
function
|
|
5141
|
-
const
|
|
5142
|
-
|
|
5143
|
-
|
|
5144
|
-
const prefixed = /^[pj]-(.*)$/i.exec(normalizedRef);
|
|
5075
|
+
function parseRef(ref) {
|
|
5076
|
+
const normalized = ref.toLowerCase();
|
|
5077
|
+
if (FULL_UUID.test(normalized)) return { kind: "uuid", value: normalized };
|
|
5078
|
+
const prefixed = /^[pj]-(.*)$/i.exec(normalized);
|
|
5145
5079
|
if (prefixed) {
|
|
5146
|
-
|
|
5147
|
-
|
|
5148
|
-
return normalizedId.replaceAll("-", "").endsWith(tail);
|
|
5080
|
+
assertUsableHexRef(ref, prefixed[1]);
|
|
5081
|
+
return { kind: "tail", value: prefixed[1] };
|
|
5149
5082
|
}
|
|
5150
|
-
if (!HEX.test(
|
|
5151
|
-
assertUsableHexRef(ref,
|
|
5152
|
-
return
|
|
5083
|
+
if (!HEX.test(normalized)) return null;
|
|
5084
|
+
assertUsableHexRef(ref, normalized);
|
|
5085
|
+
return { kind: "hex", value: normalized };
|
|
5086
|
+
}
|
|
5087
|
+
function matchesRef(id, ref) {
|
|
5088
|
+
const pattern = parseRef(ref);
|
|
5089
|
+
if (!pattern) return false;
|
|
5090
|
+
const normalizedId = id.toLowerCase();
|
|
5091
|
+
if (pattern.kind === "uuid") return normalizedId === pattern.value;
|
|
5092
|
+
return normalizedId.replaceAll("-", "").endsWith(pattern.value) || pattern.kind === "hex" && normalizedId.startsWith(pattern.value);
|
|
5153
5093
|
}
|
|
5154
5094
|
function assertUsableHexRef(ref, hex) {
|
|
5155
5095
|
if (!HEX.test(hex)) {
|
|
@@ -5284,19 +5224,19 @@ async function archiveIntoPod(opts, plan, flags = {}) {
|
|
|
5284
5224
|
const ignored = gitIgnoredPaths(plan.root);
|
|
5285
5225
|
const tmpDir = opts.tmpDir ?? os3.tmpdir();
|
|
5286
5226
|
const archivePath = path14.join(tmpDir, `pi-pod-seed-${randomBytes3(8).toString("hex")}.tar.gz`);
|
|
5287
|
-
const
|
|
5227
|
+
const write = opts.tarball ?? createTarball;
|
|
5288
5228
|
try {
|
|
5289
5229
|
let summary;
|
|
5290
5230
|
let includeGit = plan.includeGit;
|
|
5291
5231
|
step("seed", `archiving ${plan.root}${includeGit ? " with .git history" : ""} (${plan.reason})`);
|
|
5292
5232
|
try {
|
|
5293
|
-
summary = await
|
|
5233
|
+
summary = await write(tarballOptions(opts, plan.root, archivePath, ignored, includeGit));
|
|
5294
5234
|
} catch (error2) {
|
|
5295
5235
|
throwIfCancelled(opts, error2);
|
|
5296
5236
|
if (!(error2 instanceof TarballLimitError) || !includeGit) throw error2;
|
|
5297
5237
|
warn(`${error2.message} \u2014 retrying without .git history`);
|
|
5298
5238
|
includeGit = false;
|
|
5299
|
-
summary = await
|
|
5239
|
+
summary = await write(tarballOptions(opts, plan.root, archivePath, ignored, includeGit));
|
|
5300
5240
|
}
|
|
5301
5241
|
if (summary.entries === 0) {
|
|
5302
5242
|
info("nothing to seed \u2014 the local directory is empty");
|
|
@@ -6646,6 +6586,7 @@ var RemoteStateCache = class {
|
|
|
6646
6586
|
sessionRefreshRequested = false;
|
|
6647
6587
|
expectedRefreshLeaf = NO_EXPECTED_LEAF;
|
|
6648
6588
|
cacheGeneration = 0;
|
|
6589
|
+
modelsRefresh = null;
|
|
6649
6590
|
replacementInProgress = false;
|
|
6650
6591
|
labelStates = /* @__PURE__ */ new Map();
|
|
6651
6592
|
seedBridge = null;
|
|
@@ -6881,11 +6822,22 @@ var RemoteStateCache = class {
|
|
|
6881
6822
|
}
|
|
6882
6823
|
throw lastError ?? new Error("pod session refresh could not be reconciled");
|
|
6883
6824
|
}
|
|
6884
|
-
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6825
|
+
refreshModels() {
|
|
6826
|
+
const generation = this.cacheGeneration;
|
|
6827
|
+
if (this.modelsRefresh?.generation === generation) return this.modelsRefresh.promise;
|
|
6828
|
+
const promise = (async () => {
|
|
6829
|
+
try {
|
|
6830
|
+
const models = await this.rpc.getAvailableModels();
|
|
6831
|
+
if (generation === this.cacheGeneration) this.availableModels = models;
|
|
6832
|
+
} catch {
|
|
6833
|
+
}
|
|
6834
|
+
})();
|
|
6835
|
+
const entry = { generation, promise };
|
|
6836
|
+
this.modelsRefresh = entry;
|
|
6837
|
+
void promise.then(() => {
|
|
6838
|
+
if (this.modelsRefresh === entry) this.modelsRefresh = null;
|
|
6839
|
+
});
|
|
6840
|
+
return promise;
|
|
6889
6841
|
}
|
|
6890
6842
|
async refreshCommands() {
|
|
6891
6843
|
try {
|
|
@@ -11503,8 +11455,7 @@ function createRemoteRuntime(opts) {
|
|
|
11503
11455
|
treeBridge,
|
|
11504
11456
|
init: async () => {
|
|
11505
11457
|
await cache.init();
|
|
11506
|
-
await refreshThinkingLevels();
|
|
11507
|
-
await authBridge.refresh();
|
|
11458
|
+
await Promise.all([refreshThinkingLevels(), authBridge.refresh(), cache.refreshModels()]);
|
|
11508
11459
|
await seedScopedModels();
|
|
11509
11460
|
if (opts.attestedExtensionPaths?.length) {
|
|
11510
11461
|
try {
|
|
@@ -12472,6 +12423,7 @@ var GatewayRpcClient = class _GatewayRpcClient extends RpcClientBase {
|
|
|
12472
12423
|
/** False between adopting a skewed hello and readiness replacing it; ordinary RPC is gated. */
|
|
12473
12424
|
piVersionReady = false;
|
|
12474
12425
|
idPrefix = `cli-${randomBytes5(4).toString("hex")}-`;
|
|
12426
|
+
availableModelsRequest = null;
|
|
12475
12427
|
modelsRequest = null;
|
|
12476
12428
|
pendingSemantic = /* @__PURE__ */ new Map();
|
|
12477
12429
|
/** Auxiliary completions multiplex by caller id — unlike single-slot semantic queries. */
|
|
@@ -12625,6 +12577,20 @@ var GatewayRpcClient = class _GatewayRpcClient extends RpcClientBase {
|
|
|
12625
12577
|
}
|
|
12626
12578
|
return promise;
|
|
12627
12579
|
}
|
|
12580
|
+
/** Model and thinking consumers share one semantic catalog, not two large replies. */
|
|
12581
|
+
getAvailableModels() {
|
|
12582
|
+
if (this.availableModelsRequest) return this.availableModelsRequest;
|
|
12583
|
+
const task = this.getModels().then((snapshot) => snapshot.models).catch((error2) => {
|
|
12584
|
+
if (error2 instanceof Error && error2.message === "timed out waiting for the gateway model catalog" && !this.userClosed && this.piVersionReady) return super.getAvailableModels();
|
|
12585
|
+
throw error2;
|
|
12586
|
+
});
|
|
12587
|
+
this.availableModelsRequest = task;
|
|
12588
|
+
const clear = () => {
|
|
12589
|
+
if (this.availableModelsRequest === task) this.availableModelsRequest = null;
|
|
12590
|
+
};
|
|
12591
|
+
void task.then(clear, clear);
|
|
12592
|
+
return task;
|
|
12593
|
+
}
|
|
12628
12594
|
/** List pod-side sessions. A timeout or unsupported marker is an absent capability. */
|
|
12629
12595
|
getSessions() {
|
|
12630
12596
|
const absent = { sessions: [], workdir: "", complete: false, unsupported: true };
|
|
@@ -12982,6 +12948,7 @@ var GatewayRpcClient = class _GatewayRpcClient extends RpcClientBase {
|
|
|
12982
12948
|
}
|
|
12983
12949
|
}
|
|
12984
12950
|
resetHandshakeState(podId) {
|
|
12951
|
+
this.availableModelsRequest = null;
|
|
12985
12952
|
this.currentPodId = podId;
|
|
12986
12953
|
this.hello = null;
|
|
12987
12954
|
this.piExitCode = void 0;
|
|
@@ -14346,9 +14313,9 @@ async function runAccountInteractive(opts) {
|
|
|
14346
14313
|
latestConnectionState = state;
|
|
14347
14314
|
noteAsleep(state);
|
|
14348
14315
|
if (runtimeRef) {
|
|
14349
|
-
const
|
|
14350
|
-
if (runtimeRef.bridge.ui)
|
|
14351
|
-
else runtimeRef.bridge.onUiBound(
|
|
14316
|
+
const paint = () => applyConnectionStatus(runtimeRef?.bridge.ui, state, rpc?.podId ?? opts.pod.id);
|
|
14317
|
+
if (runtimeRef.bridge.ui) paint();
|
|
14318
|
+
else runtimeRef.bridge.onUiBound(paint);
|
|
14352
14319
|
if (state.kind === "lost") {
|
|
14353
14320
|
runtimeRef.bridge.notify(
|
|
14354
14321
|
state.reason ? `could not reconnect: ${state.reason}` : "connection lost",
|
|
@@ -14694,20 +14661,27 @@ ${choices.join("\n")}`,
|
|
|
14694
14661
|
};
|
|
14695
14662
|
let unwireDisconnectedQuit = () => {
|
|
14696
14663
|
};
|
|
14664
|
+
let unwireKeyRepeat = () => {
|
|
14665
|
+
};
|
|
14697
14666
|
let mode;
|
|
14698
14667
|
try {
|
|
14699
14668
|
await runtime.init();
|
|
14700
14669
|
const { InteractiveMode } = await import("@earendil-works/pi-coding-agent");
|
|
14701
14670
|
const { wirePodChords } = await import("./chords-SOJBLC6R.js");
|
|
14671
|
+
const { wireKeyRepeatGuard } = await import("./key-repeat-guard-7OVKW5KD.js");
|
|
14672
|
+
const { guardStdinFragments } = await import("./stdin-fragment-guard-SPIJC7GT.js");
|
|
14673
|
+
guardStdinFragments();
|
|
14702
14674
|
const chords = context.config.pi.chords;
|
|
14703
14675
|
runtime.bridge.setOnUiBound(() => {
|
|
14704
14676
|
unwireChords();
|
|
14705
14677
|
unwireDisconnectedQuit();
|
|
14678
|
+
unwireKeyRepeat();
|
|
14706
14679
|
unwireChords = wirePodChords(runtime.bridge, runtime.router, chords);
|
|
14707
14680
|
unwireDisconnectedQuit = wireDisconnectedQuit(
|
|
14708
14681
|
runtime.bridge,
|
|
14709
14682
|
() => connectionAllowsQuit(latestConnectionState)
|
|
14710
14683
|
);
|
|
14684
|
+
unwireKeyRepeat = wireKeyRepeatGuard(runtime.bridge.ui);
|
|
14711
14685
|
});
|
|
14712
14686
|
const tuiMode = opts.tuiMode ?? tuiModeFromArgv(context.config.pi.args);
|
|
14713
14687
|
process.env["PI_SKIP_VERSION_CHECK"] ??= "1";
|
|
@@ -14737,6 +14711,7 @@ ${choices.join("\n")}`,
|
|
|
14737
14711
|
runtime.bridge.setOnUiBound(null);
|
|
14738
14712
|
unwireChords();
|
|
14739
14713
|
unwireDisconnectedQuit();
|
|
14714
|
+
unwireKeyRepeat();
|
|
14740
14715
|
rpc.close();
|
|
14741
14716
|
derived?.dispose();
|
|
14742
14717
|
throw error2;
|
|
@@ -14763,6 +14738,7 @@ ${choices.join("\n")}`,
|
|
|
14763
14738
|
runtime.bridge.setOnUiBound(null);
|
|
14764
14739
|
unwireChords();
|
|
14765
14740
|
unwireDisconnectedQuit();
|
|
14741
|
+
unwireKeyRepeat();
|
|
14766
14742
|
process.off("SIGHUP", onHangUp);
|
|
14767
14743
|
process.off("SIGTERM", onHangUp);
|
|
14768
14744
|
}
|
|
@@ -15056,6 +15032,12 @@ function renderPodLines(pods) {
|
|
|
15056
15032
|
async function selectAccountPod(client, podRef, action, cwd) {
|
|
15057
15033
|
if (podRef) {
|
|
15058
15034
|
if (isFullUuid(podRef)) return client.getPod(podRef.toLowerCase());
|
|
15035
|
+
const found = await client.lookupPodRef(podRef);
|
|
15036
|
+
if (found !== null) {
|
|
15037
|
+
if (found.total > 1) throw ambiguousPodRef(podRef, found.matches, found.total);
|
|
15038
|
+
if (found.pod) return found.pod;
|
|
15039
|
+
throw new PiPodError(`no pod matches "${podRef}"`, { hint: "see `pipod list --all`" });
|
|
15040
|
+
}
|
|
15059
15041
|
const pods2 = (await listAllPods(client, {})).pods;
|
|
15060
15042
|
const named = pods2.filter((pod) => pod.name === podRef);
|
|
15061
15043
|
if (named.length === 1) return named[0];
|
|
@@ -15082,10 +15064,11 @@ ${podChoiceLines(pods)}`
|
|
|
15082
15064
|
}
|
|
15083
15065
|
return pods[0];
|
|
15084
15066
|
}
|
|
15085
|
-
function ambiguousPodRef(ref, pods) {
|
|
15086
|
-
return new PiPodError(`"${ref}" matches ${
|
|
15067
|
+
function ambiguousPodRef(ref, pods, total = pods.length) {
|
|
15068
|
+
return new PiPodError(`"${ref}" matches ${total} pods`, {
|
|
15087
15069
|
hint: `use a longer ref:
|
|
15088
|
-
${podChoiceLines(pods)}`
|
|
15070
|
+
${podChoiceLines(pods)}` + (total > pods.length ? `
|
|
15071
|
+
\u2026 showing the first ${pods.length} matches` : "")
|
|
15089
15072
|
});
|
|
15090
15073
|
}
|
|
15091
15074
|
function podChoiceLines(pods) {
|
|
@@ -18671,10 +18654,10 @@ async function showCmd(client, args) {
|
|
|
18671
18654
|
if (t.piSettings && Object.keys(t.piSettings).length > 0) {
|
|
18672
18655
|
info(`Pi settings ${describePiSettings(flatPiSettings(t.piSettings))}`);
|
|
18673
18656
|
}
|
|
18674
|
-
const
|
|
18675
|
-
if (
|
|
18657
|
+
const secrets = await client.listSecrets("template", t.id).catch(() => null);
|
|
18658
|
+
if (secrets && secrets.secrets.length > 0) {
|
|
18676
18659
|
out("");
|
|
18677
|
-
info(`template secrets: ${
|
|
18660
|
+
info(`template secrets: ${secrets.secrets.map((s) => s.name).join(", ")}`);
|
|
18678
18661
|
}
|
|
18679
18662
|
return 0;
|
|
18680
18663
|
}
|
|
@@ -18684,9 +18667,9 @@ function describePiSettings(settings) {
|
|
|
18684
18667
|
const packageCount = Array.isArray(packages) ? packages.length : 0;
|
|
18685
18668
|
return `${names.join(", ") || "empty"}${packageCount > 0 ? ` (${packageCount} package(s))` : ""}`;
|
|
18686
18669
|
}
|
|
18687
|
-
async function writeTemplateSnapshot(
|
|
18670
|
+
async function writeTemplateSnapshot(write) {
|
|
18688
18671
|
try {
|
|
18689
|
-
return await
|
|
18672
|
+
return await write();
|
|
18690
18673
|
} catch (error2) {
|
|
18691
18674
|
if (error2 instanceof PiPodError && error2.status === 400 && /(?:unrecognized|unknown|unexpected)[^\n]*piSettings|piSettings[^\n]*(?:unrecognized|unknown|unexpected)/i.test(error2.message)) {
|
|
18692
18675
|
throw new PiPodError("the pi pod server does not support template-owned Pi settings", {
|