@agtnames/cli 0.0.0-stage → 0.1.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.
@@ -0,0 +1,294 @@
1
+ /**
2
+ * Prompt widgets. The placeholder rules (RATIONALE R-5, R-6):
3
+ * prefill a prediction from real data, dim after the cursor; Tab fills it, Enter submits. Typing over it never
4
+ * loses it: it stays on the hint line, ↑ puts it back, ^Y copies it.
5
+ * instruction says what to enter; in the field when there is no prefill, otherwise on the label line. Always primary.
6
+ * examples only on an empty field after ~3 s idle, fading in italic below it; never fillable.
7
+ *
8
+ * Step navigation: `j` goes back a step and `;` skips it. In lists they are plain keys; in a typing field they act
9
+ * only while the field is empty, so a value containing j or ; types normally.
10
+ */
11
+ import { BACK, copyToClipboard, SKIP, style } from "./tui.js";
12
+ const PROMPT = "› ";
13
+ const IDLE_MS = 3000;
14
+ const FADE = [236, 238, 240, 242, 244];
15
+ const printable = (str, key) => !!str && !key.ctrl && !key.meta && str.length >= 1 && !/[\x00-\x1f\x7f]/.test(str);
16
+ export const NAV_HINT = "j back · ; skip";
17
+ export class TextInput {
18
+ o;
19
+ value;
20
+ pos;
21
+ error = null;
22
+ flash = null;
23
+ lastKey = Date.now();
24
+ timer;
25
+ get label() { return this.o.label; }
26
+ constructor(o) {
27
+ this.o = o;
28
+ this.value = o.initial ?? "";
29
+ this.pos = this.value.length;
30
+ }
31
+ get prefill() { return this.o.prefill?.trim() || undefined; }
32
+ attach(redraw) {
33
+ // Repaint on a clock only for what changes by itself: the examples fade-in and flash messages.
34
+ this.timer = setInterval(() => {
35
+ const idle = Date.now() - this.lastKey;
36
+ const fading = !this.value && this.o.examples?.length && idle >= IDLE_MS && idle <= IDLE_MS + FADE.length * 150 + 300;
37
+ const cycling = !this.value && (this.o.examples?.length ?? 0) > 1 && idle > IDLE_MS;
38
+ const flashing = this.flash && Date.now() <= this.flash.until + 300;
39
+ if (fading || cycling || flashing)
40
+ redraw();
41
+ }, 150);
42
+ }
43
+ detach() { clearInterval(this.timer); }
44
+ say(text) { this.flash = { text, until: Date.now() + 1500 }; }
45
+ render() {
46
+ const p = this.prefill;
47
+ const width = Math.max(20, (process.stdout.columns ?? 80) - 1);
48
+ const lines = [];
49
+ const instructionInField = !this.value && !p;
50
+ lines.push(`${style.cyan("?")} ${style.bold(this.o.label)}${instructionInField ? "" : ` ${this.o.instruction}`}${this.o.optional ? style.dim(" (optional)") : ""}`);
51
+ // Field, windowed so the cursor stays visible on long values.
52
+ const room = width - PROMPT.length - 1;
53
+ let start = 0;
54
+ if (this.value.length > room)
55
+ start = Math.max(0, Math.min(this.pos - Math.floor(room * 0.7), this.value.length - room));
56
+ const shown = this.value.slice(start, start + room);
57
+ const clipped = start > 0 ? "…" + shown.slice(1) : shown;
58
+ let field;
59
+ if (instructionInField)
60
+ field = style.dim(this.o.instruction);
61
+ else if (!this.value && p)
62
+ field = style.dim(p);
63
+ else if (p && this.pos === this.value.length && p.length > this.value.length && p.toLowerCase().startsWith(this.value.toLowerCase()))
64
+ field = clipped + style.dim(p.slice(this.value.length));
65
+ else
66
+ field = clipped;
67
+ lines.push(PROMPT + field);
68
+ const cursorCol = PROMPT.length + (this.pos - start);
69
+ // Hint line: the suggestion is never lost.
70
+ if (p) {
71
+ const src = this.o.prefillSource ? style.dim(` (${this.o.prefillSource})`) : "";
72
+ if (!this.value)
73
+ lines.push(style.dim(` Tab fills the suggestion · Enter submits · ^Y copies it${this.o.nav === false ? "" : " · " + NAV_HINT}`) + src);
74
+ else if (this.value !== p)
75
+ lines.push(style.dim(" suggested: ") + p + src + style.dim(" · ↑ restore · ^Y copy"));
76
+ else
77
+ lines.push(style.dim(" suggestion filled") + src + style.dim(" · ^K copy · Esc clear"));
78
+ }
79
+ const notes = [];
80
+ if (this.o.maxLength && this.value) {
81
+ const n = this.value.length;
82
+ const t = `${n}/${this.o.maxLength}`;
83
+ notes.push(n > this.o.maxLength ? style.red(t) : style.dim(t));
84
+ }
85
+ const note = this.o.note?.();
86
+ if (note)
87
+ notes.push(note);
88
+ if (this.flash && Date.now() <= this.flash.until)
89
+ notes.push(style.green(this.flash.text));
90
+ if (this.error)
91
+ notes.push(style.red(this.error));
92
+ if (!p && !this.value && this.o.nav !== false && !notes.length)
93
+ notes.push(style.dim(NAV_HINT));
94
+ if (notes.length)
95
+ lines.push(" " + notes.join(style.dim(" · ")));
96
+ // Examples: empty field, idle, fading in, italic, secondary.
97
+ const idle = Date.now() - this.lastKey;
98
+ if (!this.value && this.o.examples?.length && idle >= IDLE_MS) {
99
+ const step = Math.min(FADE.length - 1, Math.floor((idle - IDLE_MS) / 150));
100
+ const ex = this.o.examples[Math.floor((idle - IDLE_MS) / 5000) % this.o.examples.length];
101
+ lines.push(style.grey(FADE[step])(style.italic(` e.g. ${ex}`)));
102
+ }
103
+ return { lines, cursor: { row: 1, col: cursorCol } };
104
+ }
105
+ onKey(str, key) {
106
+ this.lastKey = Date.now();
107
+ this.error = null;
108
+ const p = this.prefill;
109
+ if (!this.value && this.o.nav !== false && !key.ctrl && !key.meta) {
110
+ if (str === "j")
111
+ return { done: BACK };
112
+ if (str === ";")
113
+ return { done: SKIP };
114
+ }
115
+ const set = (v, pos = v.length) => { this.value = v; this.pos = pos; };
116
+ switch (key.name) {
117
+ case "return":
118
+ case "enter": {
119
+ const v = this.value.trim();
120
+ if (!v && !this.o.optional) {
121
+ this.error = p ? "Tab fills the suggestion, or type your own" : "required";
122
+ return;
123
+ }
124
+ const err = v ? this.o.validate?.(v) ?? null : null;
125
+ if (err) {
126
+ this.error = err;
127
+ return;
128
+ }
129
+ return { done: v };
130
+ }
131
+ case "tab":
132
+ if (p && this.value !== p)
133
+ set(p);
134
+ return;
135
+ case "up":
136
+ if (p)
137
+ set(p);
138
+ return;
139
+ case "escape":
140
+ set("");
141
+ return;
142
+ case "left":
143
+ this.pos = Math.max(0, this.pos - 1);
144
+ return;
145
+ case "right": {
146
+ if (this.pos === this.value.length && p && p.toLowerCase().startsWith(this.value.toLowerCase()) && p.length > this.value.length)
147
+ set(this.value + p[this.value.length]);
148
+ else
149
+ this.pos = Math.min(this.value.length, this.pos + 1);
150
+ return;
151
+ }
152
+ case "home":
153
+ this.pos = 0;
154
+ return;
155
+ case "end":
156
+ this.pos = this.value.length;
157
+ return;
158
+ case "backspace":
159
+ if (this.pos > 0)
160
+ set(this.value.slice(0, this.pos - 1) + this.value.slice(this.pos), this.pos - 1);
161
+ return;
162
+ case "delete":
163
+ set(this.value.slice(0, this.pos) + this.value.slice(this.pos + 1), this.pos);
164
+ return;
165
+ }
166
+ if (key.ctrl) {
167
+ if (key.name === "a")
168
+ this.pos = 0;
169
+ else if (key.name === "e")
170
+ this.pos = this.value.length;
171
+ else if (key.name === "u")
172
+ set(this.value.slice(this.pos), 0);
173
+ else if (key.name === "w") {
174
+ const left = this.value.slice(0, this.pos).replace(/\S+\s*$/, "");
175
+ set(left + this.value.slice(this.pos), left.length);
176
+ }
177
+ else if (key.name === "y") {
178
+ const t = p ?? this.value;
179
+ if (t) {
180
+ copyToClipboard(t);
181
+ this.say(`copied: ${t.length > 40 ? t.slice(0, 40) + "…" : t}`);
182
+ }
183
+ }
184
+ else if (key.name === "k") {
185
+ if (this.value) {
186
+ copyToClipboard(this.value);
187
+ this.say("copied what you typed");
188
+ }
189
+ }
190
+ return;
191
+ }
192
+ if (printable(str, key))
193
+ set(this.value.slice(0, this.pos) + str.replace(/[\r\n]+/g, " ") + this.value.slice(this.pos), this.pos + str.length);
194
+ }
195
+ summary(v) {
196
+ return `${style.green("✔")} ${style.bold(this.o.label)} ${typeof v === "string" && v ? style.cyan(v) : style.dim("(empty)")}`;
197
+ }
198
+ }
199
+ export class Select {
200
+ o;
201
+ i;
202
+ get label() { return this.o.label; }
203
+ constructor(o) {
204
+ this.o = o;
205
+ const at = o.choices.findIndex((c) => c.value === o.initial);
206
+ this.i = at >= 0 ? at : 0;
207
+ }
208
+ render() {
209
+ const w = Math.max(...this.o.choices.map((c) => c.label.length));
210
+ const lines = [`${style.cyan("?")} ${style.bold(this.o.label)} ${this.o.instruction}`];
211
+ this.o.choices.forEach((c, i) => {
212
+ const on = i === this.i;
213
+ const pre = on && this.o.initial === c.value && this.o.prefillSource ? style.dim(` (${this.o.prefillSource})`) : "";
214
+ lines.push(`${on ? style.cyan("❯ ◉") : " ○"} ${on ? style.bold(c.label.padEnd(w)) : c.label.padEnd(w)} ${style.dim(c.description ?? "")}${pre}`);
215
+ });
216
+ lines.push(style.dim(` ↑/↓ or 1–9 to choose · Enter confirms · ${NAV_HINT}`));
217
+ return { lines, cursor: { row: 1 + this.i, col: 2 } };
218
+ }
219
+ onKey(str, key) {
220
+ const n = this.o.choices.length;
221
+ if (str === "j")
222
+ return { done: BACK };
223
+ if (str === ";")
224
+ return { done: SKIP };
225
+ if (key.name === "up")
226
+ this.i = (this.i - 1 + n) % n;
227
+ else if (key.name === "down")
228
+ this.i = (this.i + 1) % n;
229
+ else if (key.name === "return" || key.name === "enter")
230
+ return { done: this.o.choices[this.i].value };
231
+ else if (str && /^[1-9]$/.test(str) && Number(str) <= n)
232
+ this.i = Number(str) - 1;
233
+ }
234
+ summary(v) { return `${style.green("✔")} ${style.bold(this.o.label)} ${style.cyan(this.o.choices.find((c) => c.value === v)?.label ?? String(v))}`; }
235
+ }
236
+ // ── multiple choice ───────────────────────────────────────────────────────────────────────────────────────────────
237
+ export class Checkbox {
238
+ o;
239
+ i = 0;
240
+ get label() { return this.o.label; }
241
+ on;
242
+ error = null;
243
+ constructor(o) {
244
+ this.o = o;
245
+ this.on = new Set(o.initial ?? []);
246
+ }
247
+ render() {
248
+ const w = Math.max(...this.o.choices.map((c) => c.label.length));
249
+ const lines = [`${style.cyan("?")} ${style.bold(this.o.label)} ${this.o.instruction}`];
250
+ this.o.choices.forEach((c, i) => {
251
+ const box = this.on.has(c.value) ? style.green("◼") : "◻";
252
+ lines.push(`${i === this.i ? style.cyan("❯") : " "} ${box} ${i === this.i ? style.bold(c.label.padEnd(w)) : c.label.padEnd(w)} ${style.dim(c.description ?? "")}`);
253
+ });
254
+ lines.push(this.error ? " " + style.red(this.error) : style.dim(` Space toggles · a all/none · Enter confirms · ${NAV_HINT}`));
255
+ return { lines, cursor: { row: 1 + this.i, col: 2 } };
256
+ }
257
+ onKey(str, key) {
258
+ this.error = null;
259
+ if (str === "j")
260
+ return { done: BACK };
261
+ if (str === ";")
262
+ return { done: SKIP };
263
+ const n = this.o.choices.length;
264
+ const cur = this.o.choices[this.i].value;
265
+ if (key.name === "up")
266
+ this.i = (this.i - 1 + n) % n;
267
+ else if (key.name === "down")
268
+ this.i = (this.i + 1) % n;
269
+ else if (key.name === "space" || str === " ") {
270
+ if (this.on.has(cur))
271
+ this.on.delete(cur);
272
+ else
273
+ this.on.add(cur);
274
+ }
275
+ else if (str === "a") {
276
+ if (this.on.size === n)
277
+ this.on.clear();
278
+ else
279
+ this.o.choices.forEach((c) => this.on.add(c.value));
280
+ }
281
+ else if (key.name === "return" || key.name === "enter") {
282
+ if (this.on.size < (this.o.min ?? 0)) {
283
+ this.error = `choose at least ${this.o.min}`;
284
+ return;
285
+ }
286
+ return { done: this.o.choices.filter((c) => this.on.has(c.value)).map((c) => c.value) };
287
+ }
288
+ }
289
+ summary(v) {
290
+ if (!Array.isArray(v))
291
+ return "";
292
+ return `${style.green("✔")} ${style.bold(this.o.label)} ${v.length ? style.cyan(this.o.choices.filter((c) => v.includes(c.value)).map((c) => c.label).join(", ")) : style.dim("(none)")}`;
293
+ }
294
+ }
package/package.json CHANGED
@@ -1,6 +1,66 @@
1
1
  {
2
2
  "name": "@agtnames/cli",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "0.1.0",
4
+ "description": "Register a .agt name, publish a signed manifest, set records and verify the result from a terminal. Signs in MetaMask through a local page, or with a key from the environment or an encrypted keyfile.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "bin": {
15
+ "agt": "dist/cli.js"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "!dist/*.test.js",
20
+ "!dist/*.test.d.ts",
21
+ "README.md",
22
+ "LICENSE"
23
+ ],
24
+ "engines": {
25
+ "node": ">=20"
26
+ },
27
+ "publishConfig": {
28
+ "access": "public"
29
+ },
30
+ "repository": {
31
+ "type": "git",
32
+ "url": "git+https://github.com/agtnames/agt.git",
33
+ "directory": "packages/cli"
34
+ },
35
+ "scripts": {
36
+ "build": "tsc -p tsconfig.json",
37
+ "test": "node --test dist/*.test.js",
38
+ "prepublishOnly": "node scripts/check-publish-deps.mjs && npm run build && npm test"
39
+ },
40
+ "dependencies": {
41
+ "@agtnames/resolver": "^1.5.0",
42
+ "viem": "^2.56.0"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^22.0.0",
46
+ "typescript": "^5.6.0"
47
+ },
48
+ "keywords": [
49
+ "agt",
50
+ "agent-identity",
51
+ "cli",
52
+ "metamask",
53
+ "manifest",
54
+ "agent",
55
+ "ai-agents",
56
+ "mcp",
57
+ "a2a",
58
+ "polygon",
59
+ "naming"
60
+ ],
61
+ "license": "MIT",
62
+ "homepage": "https://agtnames.com/docs/guides/command-line",
63
+ "bugs": {
64
+ "url": "https://github.com/agtnames/agt/issues"
65
+ }
66
+ }