@feralfile/cli 1.1.1

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.
Files changed (58) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +96 -0
  3. package/config.json.example +96 -0
  4. package/dist/index.js +54 -0
  5. package/dist/src/ai-orchestrator/index.js +1019 -0
  6. package/dist/src/ai-orchestrator/registry.js +96 -0
  7. package/dist/src/commands/build.js +69 -0
  8. package/dist/src/commands/chat.js +189 -0
  9. package/dist/src/commands/config.js +68 -0
  10. package/dist/src/commands/device.js +278 -0
  11. package/dist/src/commands/helpers/config-files.js +62 -0
  12. package/dist/src/commands/helpers/device-discovery.js +111 -0
  13. package/dist/src/commands/helpers/playlist-display.js +161 -0
  14. package/dist/src/commands/helpers/prompt.js +65 -0
  15. package/dist/src/commands/helpers/ssh-helpers.js +44 -0
  16. package/dist/src/commands/play.js +110 -0
  17. package/dist/src/commands/publish.js +115 -0
  18. package/dist/src/commands/setup.js +225 -0
  19. package/dist/src/commands/sign.js +41 -0
  20. package/dist/src/commands/ssh.js +108 -0
  21. package/dist/src/commands/status.js +126 -0
  22. package/dist/src/commands/validate.js +18 -0
  23. package/dist/src/config.js +441 -0
  24. package/dist/src/intent-parser/index.js +1382 -0
  25. package/dist/src/intent-parser/utils.js +108 -0
  26. package/dist/src/logger.js +82 -0
  27. package/dist/src/main.js +459 -0
  28. package/dist/src/types.js +5 -0
  29. package/dist/src/utilities/address-validator.js +242 -0
  30. package/dist/src/utilities/device-default.js +36 -0
  31. package/dist/src/utilities/device-lookup.js +107 -0
  32. package/dist/src/utilities/device-normalize.js +62 -0
  33. package/dist/src/utilities/device-upsert.js +91 -0
  34. package/dist/src/utilities/domain-resolver.js +291 -0
  35. package/dist/src/utilities/ed25519-key-derive.js +155 -0
  36. package/dist/src/utilities/feed-fetcher.js +471 -0
  37. package/dist/src/utilities/ff1-compatibility.js +269 -0
  38. package/dist/src/utilities/ff1-device.js +250 -0
  39. package/dist/src/utilities/ff1-discovery.js +330 -0
  40. package/dist/src/utilities/functions.js +308 -0
  41. package/dist/src/utilities/index.js +469 -0
  42. package/dist/src/utilities/nft-indexer.js +1024 -0
  43. package/dist/src/utilities/playlist-builder.js +523 -0
  44. package/dist/src/utilities/playlist-publisher.js +131 -0
  45. package/dist/src/utilities/playlist-send.js +260 -0
  46. package/dist/src/utilities/playlist-signer.js +204 -0
  47. package/dist/src/utilities/playlist-signing-role.js +41 -0
  48. package/dist/src/utilities/playlist-source.js +128 -0
  49. package/dist/src/utilities/playlist-verifier.js +274 -0
  50. package/dist/src/utilities/ssh-access.js +145 -0
  51. package/dist/src/utils.js +48 -0
  52. package/docs/CONFIGURATION.md +206 -0
  53. package/docs/EXAMPLES.md +390 -0
  54. package/docs/FUNCTION_CALLING.md +96 -0
  55. package/docs/PROJECT_SPEC.md +228 -0
  56. package/docs/README.md +348 -0
  57. package/docs/RELEASING.md +73 -0
  58. package/package.json +76 -0
@@ -0,0 +1,291 @@
1
+ "use strict";
2
+ /**
3
+ * Domain Resolution Utilities
4
+ * Resolves blockchain domain names (ENS, TNS) to their corresponding addresses
5
+ */
6
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
7
+ if (k2 === undefined) k2 = k;
8
+ var desc = Object.getOwnPropertyDescriptor(m, k);
9
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
10
+ desc = { enumerable: true, get: function() { return m[k]; } };
11
+ }
12
+ Object.defineProperty(o, k2, desc);
13
+ }) : (function(o, m, k, k2) {
14
+ if (k2 === undefined) k2 = k;
15
+ o[k2] = m[k];
16
+ }));
17
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
18
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
19
+ }) : function(o, v) {
20
+ o["default"] = v;
21
+ });
22
+ var __importStar = (this && this.__importStar) || (function () {
23
+ var ownKeys = function(o) {
24
+ ownKeys = Object.getOwnPropertyNames || function (o) {
25
+ var ar = [];
26
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
27
+ return ar;
28
+ };
29
+ return ownKeys(o);
30
+ };
31
+ return function (mod) {
32
+ if (mod && mod.__esModule) return mod;
33
+ var result = {};
34
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
35
+ __setModuleDefault(result, mod);
36
+ return result;
37
+ };
38
+ })();
39
+ var __importDefault = (this && this.__importDefault) || function (mod) {
40
+ return (mod && mod.__esModule) ? mod : { "default": mod };
41
+ };
42
+ Object.defineProperty(exports, "__esModule", { value: true });
43
+ exports.resolveDomain = resolveDomain;
44
+ exports.resolveDomainsBatch = resolveDomainsBatch;
45
+ exports.displayResolutionResults = displayResolutionResults;
46
+ const viem_1 = require("viem");
47
+ const chains_1 = require("viem/chains");
48
+ const ens_1 = require("viem/ens");
49
+ const axios_1 = __importDefault(require("axios"));
50
+ const chalk_1 = __importDefault(require("chalk"));
51
+ const logger = __importStar(require("../logger"));
52
+ /**
53
+ * ENS resolver using viem
54
+ */
55
+ class ENSResolver {
56
+ client;
57
+ constructor() {
58
+ this.client = (0, viem_1.createPublicClient)({
59
+ chain: chains_1.mainnet,
60
+ transport: (0, viem_1.http)(),
61
+ });
62
+ }
63
+ /**
64
+ * Resolve an ENS domain to its Ethereum address
65
+ *
66
+ * @param {string} domain - ENS domain (e.g., 'vitalik.eth')
67
+ * @returns {Promise<string|null>} Resolved address or null
68
+ */
69
+ async resolve(domain) {
70
+ try {
71
+ const address = await this.client.getEnsAddress({
72
+ name: (0, ens_1.normalize)(domain),
73
+ });
74
+ return address;
75
+ }
76
+ catch (error) {
77
+ logger.debug(`ENS resolution failed for ${domain}: ${error}`);
78
+ return null;
79
+ }
80
+ }
81
+ }
82
+ /**
83
+ * TNS (Tezos Name Service) resolver using Tezos Domains GraphQL API
84
+ *
85
+ * Uses the official Tezos Domains API for reliable resolution
86
+ * API: https://api.tezos.domains/graphql
87
+ */
88
+ class TNSResolver {
89
+ apiUrl;
90
+ constructor() {
91
+ this.apiUrl = 'https://api.tezos.domains/graphql';
92
+ }
93
+ /**
94
+ * Resolve a TNS domain to its Tezos address using GraphQL API
95
+ *
96
+ * @param {string} domain - TNS domain (e.g., 'alice.tez', 'einstein-rosen.tez')
97
+ * @returns {Promise<string|null>} Resolved address or null
98
+ */
99
+ async resolve(domain) {
100
+ try {
101
+ // GraphQL query to resolve domain (using GET request)
102
+ // Note: API only supports 'address' field, not 'expiry'
103
+ const query = `{ domain(name: "${domain}") { address } }`;
104
+ const response = await axios_1.default.get(this.apiUrl, {
105
+ params: { query },
106
+ timeout: 10000,
107
+ });
108
+ if (response.data?.errors) {
109
+ logger.debug(`TNS API returned errors for ${domain}: ${JSON.stringify(response.data.errors)}`);
110
+ return null;
111
+ }
112
+ const domainData = response.data?.data?.domain;
113
+ if (!domainData || !domainData.address) {
114
+ logger.debug(`TNS domain ${domain} not found - domainData: ${JSON.stringify(domainData)}`);
115
+ return null;
116
+ }
117
+ logger.debug(`TNS resolved ${domain} → ${domainData.address}`);
118
+ return domainData.address;
119
+ }
120
+ catch (error) {
121
+ const errorMessage = error instanceof Error ? error.message : String(error);
122
+ logger.debug(`TNS resolution failed for ${domain}: ${errorMessage}`);
123
+ return null;
124
+ }
125
+ }
126
+ }
127
+ /**
128
+ * Determine domain type based on TLD
129
+ *
130
+ * @param {string} domain - Domain name
131
+ * @returns {string|null} Domain type ('ens', 'tns') or null
132
+ */
133
+ function getDomainType(domain) {
134
+ const normalizedDomain = domain.toLowerCase();
135
+ if (normalizedDomain.endsWith('.eth')) {
136
+ return 'ens';
137
+ }
138
+ else if (normalizedDomain.endsWith('.tez')) {
139
+ return 'tns';
140
+ }
141
+ return null;
142
+ }
143
+ /**
144
+ * Validate domain name format
145
+ *
146
+ * @param {string} domain - Domain to validate
147
+ * @returns {boolean} Whether domain is valid
148
+ */
149
+ function isValidDomain(domain) {
150
+ if (!domain || typeof domain !== 'string') {
151
+ return false;
152
+ }
153
+ const trimmedDomain = domain.trim();
154
+ if (trimmedDomain.length === 0) {
155
+ return false;
156
+ }
157
+ const domainType = getDomainType(trimmedDomain);
158
+ return domainType !== null;
159
+ }
160
+ /**
161
+ * Resolve a single domain to its blockchain address
162
+ *
163
+ * @param {string} domain - Domain name to resolve (e.g., 'vitalik.eth', 'alice.tez')
164
+ * @returns {Promise<DomainResolution>} Resolution result
165
+ * @example
166
+ * const result = await resolveDomain('vitalik.eth');
167
+ * if (result.resolved) {
168
+ * console.log(`${result.domain} -> ${result.address}`);
169
+ * }
170
+ */
171
+ async function resolveDomain(domain) {
172
+ const trimmedDomain = domain.trim();
173
+ // Validate domain
174
+ if (!isValidDomain(trimmedDomain)) {
175
+ return {
176
+ domain: trimmedDomain,
177
+ address: null,
178
+ resolved: false,
179
+ error: `Invalid or unsupported domain: ${trimmedDomain}`,
180
+ };
181
+ }
182
+ const domainType = getDomainType(trimmedDomain);
183
+ try {
184
+ let address = null;
185
+ if (domainType === 'ens') {
186
+ const ensResolver = new ENSResolver();
187
+ address = await ensResolver.resolve(trimmedDomain);
188
+ }
189
+ else if (domainType === 'tns') {
190
+ const tnsResolver = new TNSResolver();
191
+ address = await tnsResolver.resolve(trimmedDomain);
192
+ }
193
+ if (!address) {
194
+ return {
195
+ domain: trimmedDomain,
196
+ address: null,
197
+ resolved: false,
198
+ error: `Could not resolve ${trimmedDomain}`,
199
+ };
200
+ }
201
+ return {
202
+ domain: trimmedDomain,
203
+ address,
204
+ resolved: true,
205
+ };
206
+ }
207
+ catch (error) {
208
+ const errorMessage = error instanceof Error ? error.message : 'Unknown error during resolution';
209
+ logger.debug(`Domain resolution error for ${trimmedDomain}: ${errorMessage}`);
210
+ return {
211
+ domain: trimmedDomain,
212
+ address: null,
213
+ resolved: false,
214
+ error: errorMessage,
215
+ };
216
+ }
217
+ }
218
+ /**
219
+ * Resolve multiple domains in batch (concurrent processing)
220
+ *
221
+ * Supports ENS (.eth) and TNS (.tez) domains.
222
+ * Processes all domains concurrently for optimal performance.
223
+ *
224
+ * @param {string[]} domains - Array of domain names to resolve
225
+ * @returns {Promise<BatchResolutionResult>} Batch resolution result with domain->address map
226
+ * @example
227
+ * const result = await resolveDomainsBatch(['vitalik.eth', 'alice.tez']);
228
+ * if (result.success) {
229
+ * console.log(result.domainMap); // { 'vitalik.eth': '0x...', 'alice.tez': 'tz...' }
230
+ * }
231
+ */
232
+ async function resolveDomainsBatch(domains) {
233
+ // Validate input
234
+ if (!Array.isArray(domains) || domains.length === 0) {
235
+ return {
236
+ success: false,
237
+ resolutions: [],
238
+ domainMap: {},
239
+ errors: ['No domains provided for resolution'],
240
+ };
241
+ }
242
+ logger.debug(`Resolving ${domains.length} domains in batch...`);
243
+ // Resolve all domains concurrently
244
+ const resolutionPromises = domains.map((domain) => resolveDomain(domain));
245
+ const resolutions = await Promise.all(resolutionPromises);
246
+ // Build domain map and collect errors
247
+ const domainMap = {};
248
+ const errors = [];
249
+ for (const resolution of resolutions) {
250
+ if (resolution.resolved && resolution.address) {
251
+ domainMap[resolution.domain] = resolution.address;
252
+ }
253
+ else if (resolution.error) {
254
+ errors.push(`${resolution.domain}: ${resolution.error}`);
255
+ }
256
+ }
257
+ const successfulResolutions = resolutions.filter((r) => r.resolved).length;
258
+ logger.debug(`Batch resolution complete: ${successfulResolutions}/${domains.length} successful`);
259
+ return {
260
+ success: successfulResolutions > 0,
261
+ resolutions,
262
+ domainMap,
263
+ errors,
264
+ };
265
+ }
266
+ /**
267
+ * Display batch resolution results in a user-friendly format
268
+ *
269
+ * @param {BatchResolutionResult} result - Batch resolution result
270
+ */
271
+ function displayResolutionResults(result) {
272
+ if (result.resolutions.length === 0) {
273
+ console.log(chalk_1.default.yellow('No names to resolve'));
274
+ return;
275
+ }
276
+ // Display successful resolutions
277
+ const successful = result.resolutions.filter((r) => r.resolved);
278
+ if (successful.length > 0) {
279
+ successful.forEach((resolution) => {
280
+ console.log(chalk_1.default.dim(` ${resolution.domain} → ${resolution.address}`));
281
+ });
282
+ }
283
+ // Display failures (but don't make them too prominent)
284
+ const failed = result.resolutions.filter((r) => !r.resolved);
285
+ if (failed.length > 0) {
286
+ failed.forEach((resolution) => {
287
+ console.log(chalk_1.default.yellow(` ${resolution.domain}: ${resolution.error || 'Could not resolve'}`));
288
+ });
289
+ }
290
+ console.log();
291
+ }
@@ -0,0 +1,155 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.normalizeVerifyPublicKeyToPem = normalizeVerifyPublicKeyToPem;
4
+ exports.parsePlaylistPrivateKeyToKeyObject = parsePlaylistPrivateKeyToKeyObject;
5
+ exports.deriveEd25519PublicKeyForVerify = deriveEd25519PublicKeyForVerify;
6
+ const node_crypto_1 = require("node:crypto");
7
+ /**
8
+ * RFC 5480-style SubjectPublicKeyInfo prefix for Ed25519 public keys (raw 32 octets in BIT STRING).
9
+ */
10
+ const ED25519_SPKI_RAW_PREFIX = Buffer.from('302a300506032b6570032100', 'hex');
11
+ function rawEd25519PublicBytesToPem(raw32) {
12
+ if (raw32.length !== 32) {
13
+ throw new Error('Ed25519 raw public key must be 32 bytes');
14
+ }
15
+ const der = Buffer.concat([ED25519_SPKI_RAW_PREFIX, raw32]);
16
+ const pub = (0, node_crypto_1.createPublicKey)({ key: der, format: 'der', type: 'spki' });
17
+ assertEd25519Public(pub);
18
+ return pub.export({ format: 'pem', type: 'spki' }).toString();
19
+ }
20
+ /**
21
+ * normalizeVerifyPublicKeyToPem interprets `--public-key` values documented for `verify`:
22
+ * PEM SPKI, 64-character hex (optional `0x`), standard base64 of exactly 32 raw bytes,
23
+ * or DER SPKI as base64 (typical single-line export).
24
+ *
25
+ * dp1-js expects PEM-friendly material in practice; this keeps CLI input aligned with docs.
26
+ *
27
+ * @param material - Non-empty key string from the CLI or config-derived PEM
28
+ * @returns PEM-encoded SPKI Ed25519 public key
29
+ * @throws Error if the material cannot be decoded as Ed25519 public key material
30
+ */
31
+ function normalizeVerifyPublicKeyToPem(material) {
32
+ const trimmed = material.trim();
33
+ if (!trimmed) {
34
+ throw new Error('Public key material is empty');
35
+ }
36
+ if (trimmed.includes('BEGIN')) {
37
+ const pub = (0, node_crypto_1.createPublicKey)({ key: trimmed, format: 'pem' });
38
+ assertEd25519Public(pub);
39
+ return pub.export({ format: 'pem', type: 'spki' }).toString();
40
+ }
41
+ const hexCompact = trimmed.replace(/^0x/i, '').replace(/\s/g, '');
42
+ const hexRegex = /^[0-9a-fA-F]{64}$/;
43
+ if (hexRegex.test(hexCompact)) {
44
+ return rawEd25519PublicBytesToPem(Buffer.from(hexCompact, 'hex'));
45
+ }
46
+ const base64Regex = /^[A-Za-z0-9+/]+=*$/;
47
+ if (base64Regex.test(trimmed)) {
48
+ const buf = Buffer.from(trimmed, 'base64');
49
+ if (buf.length === 32) {
50
+ return rawEd25519PublicBytesToPem(buf);
51
+ }
52
+ try {
53
+ const pub = (0, node_crypto_1.createPublicKey)({ key: buf, format: 'der', type: 'spki' });
54
+ assertEd25519Public(pub);
55
+ return pub.export({ format: 'pem', type: 'spki' }).toString();
56
+ }
57
+ catch {
58
+ // fall through
59
+ }
60
+ }
61
+ throw new Error('Unrecognized Ed25519 public key format for verify (expected PEM, 32-byte hex, 32-byte base64, or SPKI DER base64)');
62
+ }
63
+ /**
64
+ * RFC 8410 PKCS#8 prefix for Ed25519: nested OCTET STRING holds the 32-byte seed.
65
+ * Used so 32-byte hex seeds work on Node versions that reject `type: 'raw'` imports.
66
+ */
67
+ const ED25519_PKCS8_SEED_PREFIX = Buffer.from('302e020100300506032b657004220420', 'hex');
68
+ function ed25519SeedBytesToPkcs8(seed32) {
69
+ if (seed32.length !== 32) {
70
+ throw new Error('Ed25519 seed must be 32 bytes');
71
+ }
72
+ return Buffer.concat([ED25519_PKCS8_SEED_PREFIX, seed32]);
73
+ }
74
+ /**
75
+ * parsePlaylistPrivateKeyToKeyObject interprets playlist signing material the same way
76
+ * operators configure it: PKCS#8 DER as base64 (setup default), optional 32-byte raw
77
+ * seed as hex, PKCS#8 as hex, or PEM.
78
+ *
79
+ * @param material - Trimmed private key string from config or env
80
+ * @returns Node.js KeyObject for the Ed25519 private key
81
+ * @throws Error if the material cannot be parsed or is not Ed25519
82
+ */
83
+ function parsePlaylistPrivateKeyToKeyObject(material) {
84
+ const trimmed = material.trim();
85
+ if (!trimmed) {
86
+ throw new Error('Private key material is empty');
87
+ }
88
+ if (trimmed.includes('BEGIN')) {
89
+ const key = (0, node_crypto_1.createPrivateKey)({ key: trimmed, format: 'pem' });
90
+ assertEd25519(key);
91
+ return key;
92
+ }
93
+ const base64Regex = /^[A-Za-z0-9+/]+=*$/;
94
+ const hexRegex = /^(0x)?[0-9a-fA-F]+$/;
95
+ if (base64Regex.test(trimmed)) {
96
+ const buf = Buffer.from(trimmed, 'base64');
97
+ if (buf.length === 0) {
98
+ throw new Error('Invalid base64 private key');
99
+ }
100
+ try {
101
+ const key = (0, node_crypto_1.createPrivateKey)({ key: buf, format: 'der', type: 'pkcs8' });
102
+ assertEd25519(key);
103
+ return key;
104
+ }
105
+ catch {
106
+ // Continue to other strategies (e.g. hex path may apply for unusual configs)
107
+ }
108
+ }
109
+ if (hexRegex.test(trimmed)) {
110
+ const raw = Buffer.from(trimmed.replace(/^0x/i, ''), 'hex');
111
+ if (raw.length === 32) {
112
+ try {
113
+ const der = ed25519SeedBytesToPkcs8(raw);
114
+ const key = (0, node_crypto_1.createPrivateKey)({ key: der, format: 'der', type: 'pkcs8' });
115
+ assertEd25519(key);
116
+ return key;
117
+ }
118
+ catch {
119
+ // fall through to full PKCS#8-in-hex
120
+ }
121
+ }
122
+ try {
123
+ const key = (0, node_crypto_1.createPrivateKey)({ key: raw, format: 'der', type: 'pkcs8' });
124
+ assertEd25519(key);
125
+ return key;
126
+ }
127
+ catch {
128
+ // fall through
129
+ }
130
+ }
131
+ throw new Error('Unrecognized Ed25519 private key format (expected PKCS#8 base64, 32-byte hex seed, PKCS#8 hex, or PEM)');
132
+ }
133
+ /**
134
+ * deriveEd25519PublicKeyForVerify exports the public half as PEM so legacy
135
+ * verification can hand Node a decoder-friendly public key string directly.
136
+ *
137
+ * @param privateKeyMaterial - Same encoding rules as `playlist.privateKey` / `PLAYLIST_PRIVATE_KEY`
138
+ * @returns PEM-encoded SPKI public key
139
+ */
140
+ function deriveEd25519PublicKeyForVerify(privateKeyMaterial) {
141
+ const privateKey = parsePlaylistPrivateKeyToKeyObject(privateKeyMaterial);
142
+ const publicKey = (0, node_crypto_1.createPublicKey)(privateKey);
143
+ assertEd25519Public(publicKey);
144
+ return publicKey.export({ format: 'pem', type: 'spki' }).toString();
145
+ }
146
+ function assertEd25519(key) {
147
+ if (key.asymmetricKeyType !== 'ed25519') {
148
+ throw new Error('Configured private key must be an Ed25519 key');
149
+ }
150
+ }
151
+ function assertEd25519Public(key) {
152
+ if (key.asymmetricKeyType !== 'ed25519') {
153
+ throw new Error('Public key must be Ed25519');
154
+ }
155
+ }