@elisym/cli 0.21.2 → 0.22.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/dist/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env -S node --no-deprecation
2
2
  import { ReadableStream } from 'node:stream/web';
3
3
  import { readFileSync, existsSync, readdirSync, statSync, renameSync, chmodSync, mkdirSync, writeFileSync } from 'node:fs';
4
- import { dirname, join, resolve, basename, relative, sep } from 'node:path';
5
- import { SolanaPaymentStrategy, validateAgentName, RELAYS, ElisymIdentity, formatSol, formatAssetAmount, USDC_SOLANA_DEVNET, ElisymClient, MediaService, POLICY_D_TAG_PREFIX, KIND_LONG_FORM_ARTICLE, POLICY_T_TAG, jobRequestKind, DEFAULT_KIND_OFFSET, toDTag, DEFAULTS, makeCensor, DEFAULT_REDACT_PATHS, createSlidingWindowLimiter, getProtocolProgramId, getProtocolConfig, utf8ByteLength, LIMITS, calculateProtocolFee, decodeJobPayload, BoundedSet, KIND_JOB_FEEDBACK, NATIVE_SOL } from '@elisym/sdk';
4
+ import { dirname, join, resolve, basename, relative, sep, extname } from 'node:path';
5
+ import { SolanaPaymentStrategy, validateAgentName, RELAYS, ElisymIdentity, formatSol, formatAssetAmount, USDC_SOLANA_DEVNET, ElisymClient, POLICY_D_TAG_PREFIX, KIND_LONG_FORM_ARTICLE, jobRequestKind, DEFAULT_KIND_OFFSET, toDTag, DEFAULTS, makeCensor, DEFAULT_REDACT_PATHS, POLICY_T_TAG, createSlidingWindowLimiter, getProtocolProgramId, getProtocolConfig, utf8ByteLength, LIMITS, calculateProtocolFee, decodeJobPayload, BoundedSet, KIND_JOB_FEEDBACK, NATIVE_SOL } from '@elisym/sdk';
6
6
  import { ElisymYamlSchema, resolveInHome, resolveInProject, createAgentDir, writeYamlInitial, writeExampleSkillTemplate, writeSecrets, listAgents, loadAgent, writeYaml, agentPaths, readMediaCache, loadPoliciesFromDir, ensureGitignoreHasIrohEntry, lookupCachedUrl, newCacheEntry, writeMediaCache } from '@elisym/sdk/agent-store';
7
7
  import { isAddress, createSolanaRpc, address } from '@solana/kit';
8
8
  import { generateSecretKey, getPublicKey, nip19, verifyEvent } from 'nostr-tools';
@@ -2051,6 +2051,18 @@ function createLogger(options = {}) {
2051
2051
  bannerLog: logWithIndent
2052
2052
  };
2053
2053
  }
2054
+ var MIME_BY_EXT = {
2055
+ ".png": "image/png",
2056
+ ".jpg": "image/jpeg",
2057
+ ".jpeg": "image/jpeg",
2058
+ ".gif": "image/gif",
2059
+ ".webp": "image/webp",
2060
+ ".svg": "image/svg+xml",
2061
+ ".avif": "image/avif"
2062
+ };
2063
+ function mimeFromPath(path) {
2064
+ return MIME_BY_EXT[extname(path).toLowerCase()] ?? "application/octet-stream";
2065
+ }
2054
2066
  var payment = new SolanaPaymentStrategy();
2055
2067
  var LEDGER_GC_INTERVAL_MS = 60 * 60 * 1e3;
2056
2068
  var LEDGER_RETENTION_MS = 30 * 24 * 60 * 60 * 1e3;
@@ -4163,14 +4175,13 @@ async function cmdStart(nameArg, options = {}) {
4163
4175
  }
4164
4176
  }
4165
4177
  }
4166
- const media = new MediaService();
4167
4178
  const mediaCache = await readMediaCache(loaded.dir);
4168
4179
  let mediaCacheDirty = false;
4169
4180
  const pictureUrl = await resolveMediaField(
4170
4181
  loaded.yaml.picture,
4171
4182
  loaded.dir,
4172
4183
  mediaCache,
4173
- media,
4184
+ client.blossom,
4174
4185
  identity,
4175
4186
  (updated) => mediaCacheDirty = mediaCacheDirty || updated
4176
4187
  );
@@ -4178,7 +4189,7 @@ async function cmdStart(nameArg, options = {}) {
4178
4189
  loaded.yaml.banner,
4179
4190
  loaded.dir,
4180
4191
  mediaCache,
4181
- media,
4192
+ client.blossom,
4182
4193
  identity,
4183
4194
  (updated) => mediaCacheDirty = mediaCacheDirty || updated
4184
4195
  );
@@ -4194,7 +4205,7 @@ async function cmdStart(nameArg, options = {}) {
4194
4205
  cacheKey,
4195
4206
  absPath,
4196
4207
  mediaCache,
4197
- media,
4208
+ client.blossom,
4198
4209
  identity,
4199
4210
  () => mediaCacheDirty = true
4200
4211
  );
@@ -4224,7 +4235,36 @@ async function cmdStart(nameArg, options = {}) {
4224
4235
  const localPolicyDTags = new Set(
4225
4236
  policies.map((policy) => `${POLICY_D_TAG_PREFIX}${policy.type}`)
4226
4237
  );
4238
+ async function fetchPublishedPolicies() {
4239
+ try {
4240
+ return await client.pool.querySync({
4241
+ kinds: [KIND_LONG_FORM_ARTICLE],
4242
+ authors: [identity.publicKey],
4243
+ "#t": [POLICY_T_TAG]
4244
+ });
4245
+ } catch {
4246
+ return [];
4247
+ }
4248
+ }
4249
+ const publishedPolicies = await fetchPublishedPolicies();
4250
+ const latestPolicyByDTag = /* @__PURE__ */ new Map();
4251
+ for (const event of publishedPolicies) {
4252
+ const dTag = event.tags.find((tag) => tag[0] === "d")?.[1];
4253
+ if (!dTag) {
4254
+ continue;
4255
+ }
4256
+ const prev = latestPolicyByDTag.get(dTag);
4257
+ if (!prev || event.created_at > prev.created_at) {
4258
+ latestPolicyByDTag.set(dTag, event);
4259
+ }
4260
+ }
4227
4261
  for (const policy of policies) {
4262
+ const onRelay = latestPolicyByDTag.get(`${POLICY_D_TAG_PREFIX}${policy.type}`);
4263
+ const relayVersion = onRelay?.tags.find((tag) => tag[0] === "policy_version")?.[1];
4264
+ if (onRelay && onRelay.content && relayVersion === policy.version && onRelay.content === policy.content) {
4265
+ console.log(` * Policy: ${policy.type}@${policy.version} (unchanged, skipped)`);
4266
+ continue;
4267
+ }
4228
4268
  try {
4229
4269
  const { naddr } = await client.policies.publishPolicy(identity, policy);
4230
4270
  console.log(` * Policy: ${policy.type}@${policy.version} -> ${naddr}`);
@@ -4245,31 +4285,23 @@ async function cmdStart(nameArg, options = {}) {
4245
4285
  );
4246
4286
  }
4247
4287
  }
4248
- try {
4249
- const existingPolicies = await client.pool.querySync({
4250
- kinds: [KIND_LONG_FORM_ARTICLE],
4251
- authors: [identity.publicKey],
4252
- "#t": [POLICY_T_TAG]
4253
- });
4254
- for (const event of existingPolicies) {
4255
- const dTag = event.tags.find((tag) => tag[0] === "d")?.[1];
4256
- if (!dTag || localPolicyDTags.has(dTag)) {
4257
- continue;
4258
- }
4259
- if (!event.content) {
4260
- continue;
4261
- }
4262
- const type = event.tags.find((tag) => tag[0] === "policy_type")?.[1];
4263
- if (!type) {
4264
- continue;
4265
- }
4266
- try {
4267
- await client.policies.deletePolicy(identity, type);
4268
- console.log(` Removed stale policy: ${type}`);
4269
- } catch {
4270
- }
4288
+ for (const event of publishedPolicies) {
4289
+ const dTag = event.tags.find((tag) => tag[0] === "d")?.[1];
4290
+ if (!dTag || localPolicyDTags.has(dTag)) {
4291
+ continue;
4292
+ }
4293
+ if (!event.content) {
4294
+ continue;
4295
+ }
4296
+ const type = event.tags.find((tag) => tag[0] === "policy_type")?.[1];
4297
+ if (!type) {
4298
+ continue;
4299
+ }
4300
+ try {
4301
+ await client.policies.deletePolicy(identity, type);
4302
+ console.log(` Removed stale policy: ${type}`);
4303
+ } catch {
4271
4304
  }
4272
- } catch {
4273
4305
  }
4274
4306
  const kinds = [jobRequestKind(DEFAULT_KIND_OFFSET)];
4275
4307
  function buildCard(skill) {
@@ -4469,7 +4501,7 @@ function stripRpcSecrets(raw) {
4469
4501
  return "[unparseable RPC URL]";
4470
4502
  }
4471
4503
  }
4472
- async function resolveMediaField(value, agentDir, cache, media, identity, onCacheUpdate) {
4504
+ async function resolveMediaField(value, agentDir, cache, blossom, identity, onCacheUpdate) {
4473
4505
  if (!value) {
4474
4506
  return void 0;
4475
4507
  }
@@ -4481,7 +4513,7 @@ async function resolveMediaField(value, agentDir, cache, media, identity, onCach
4481
4513
  console.warn(` ! Skipping media field "${value}": path must stay inside the agent directory.`);
4482
4514
  return void 0;
4483
4515
  }
4484
- return uploadOrReuse(value, absPath, cache, media, identity, () => onCacheUpdate(true));
4516
+ return uploadOrReuse(value, absPath, cache, blossom, identity, () => onCacheUpdate(true));
4485
4517
  }
4486
4518
  function resolveInsideAgentDir(value, agentDir) {
4487
4519
  const agentRoot = resolve(agentDir);
@@ -4492,7 +4524,7 @@ function resolveInsideAgentDir(value, agentDir) {
4492
4524
  }
4493
4525
  return candidate;
4494
4526
  }
4495
- async function uploadOrReuse(cacheKey, absPath, cache, media, identity, onCacheUpdate) {
4527
+ async function uploadOrReuse(cacheKey, absPath, cache, blossom, identity, onCacheUpdate) {
4496
4528
  try {
4497
4529
  const cached = await lookupCachedUrl(cache, cacheKey, absPath);
4498
4530
  if (cached) {
@@ -4501,12 +4533,12 @@ async function uploadOrReuse(cacheKey, absPath, cache, media, identity, onCacheU
4501
4533
  console.log(` Uploading ${basename(absPath)}...`);
4502
4534
  const data = readFileSync(absPath);
4503
4535
  const sha256 = createHash("sha256").update(data).digest("hex");
4504
- const blob = new Blob([data]);
4505
- const url = await media.upload(identity, blob, basename(absPath));
4506
- cache[cacheKey] = newCacheEntry(url, sha256);
4536
+ const blob = new Blob([data], { type: mimeFromPath(absPath) });
4537
+ const descriptor = await blossom.upload(identity, blob);
4538
+ cache[cacheKey] = newCacheEntry(descriptor.url, sha256);
4507
4539
  onCacheUpdate();
4508
- console.log(` Uploaded: ${url}`);
4509
- return url;
4540
+ console.log(` Uploaded: ${descriptor.url}`);
4541
+ return descriptor.url;
4510
4542
  } catch (e) {
4511
4543
  console.warn(` ! Failed to upload ${basename(absPath)}: ${e.message}`);
4512
4544
  return void 0;