@singi-labs/sifa-sdk 0.19.65 → 0.19.66

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.cjs CHANGED
@@ -7197,7 +7197,7 @@ function describeSifaRecord(collection, value) {
7197
7197
  }
7198
7198
 
7199
7199
  // src/index.ts
7200
- var SIFA_SDK_VERSION = "0.19.65";
7200
+ var SIFA_SDK_VERSION = "0.19.66";
7201
7201
 
7202
7202
  exports.ACTIVITY_TIERS = ACTIVITY_TIERS;
7203
7203
  exports.ACTIVITY_VERBS = ACTIVITY_VERBS;
package/dist/index.js CHANGED
@@ -353,7 +353,7 @@ import {
353
353
  roadmapIssueUrl
354
354
  } from "./roadmap/index.js";
355
355
  import { SIFA_REPO_GROUPS, describeSifaRecord, repoGroupForCollection } from "./repo/index.js";
356
- var SIFA_SDK_VERSION = "0.19.65";
356
+ var SIFA_SDK_VERSION = "0.19.66";
357
357
  export {
358
358
  ACTIVITY_TIERS,
359
359
  ACTIVITY_VERBS,
@@ -1,23 +1,42 @@
1
1
  // src/rpg/catalog-drift.ts
2
2
  var SIFA_RPG_PROVIDER_DID = "did:plc:2f2ahswozqy4v5lvu676375y";
3
- var COMPARED_FIELDS = ["title", "kind", "category", "assetCid", "iconCid"];
3
+ var RPG_ITEM_DATA_FIELDS = [
4
+ "title",
5
+ "description",
6
+ "kind",
7
+ "category",
8
+ "channels",
9
+ "assetCid",
10
+ "iconCid"
11
+ ];
12
+ function sameRpgFieldValue(a, b) {
13
+ return JSON.stringify(a ?? null) === JSON.stringify(b ?? null);
14
+ }
15
+ function display(value) {
16
+ if (value === void 0) return "(absent)";
17
+ return Array.isArray(value) ? value.join(", ") : value;
18
+ }
4
19
  function compareRpgCatalog(ours, theirs, expectedProvider = SIFA_RPG_PROVIDER_DID) {
5
20
  const remote = new Map(theirs.items.map((i) => [i.item, i]));
6
21
  const mismatches = [];
22
+ const missingDisabled = [];
7
23
  for (const item of ours) {
8
- if (!item.enabled) continue;
9
24
  const r = remote.get(item.id);
10
25
  if (!r) {
11
- mismatches.push({ item: item.id, field: "listed", ours: "yes", theirs: "missing" });
26
+ if (item.enabled) {
27
+ mismatches.push({ item: item.id, field: "listed", ours: "yes", theirs: "missing" });
28
+ } else {
29
+ missingDisabled.push(item.id);
30
+ }
12
31
  continue;
13
32
  }
14
- for (const field of COMPARED_FIELDS) {
15
- if (item[field] !== r[field]) {
33
+ for (const field of RPG_ITEM_DATA_FIELDS) {
34
+ if (!sameRpgFieldValue(item[field], r[field])) {
16
35
  mismatches.push({
17
36
  item: item.id,
18
37
  field,
19
- ours: item[field],
20
- theirs: r[field] ?? "(absent)"
38
+ ours: display(item[field]),
39
+ theirs: display(r[field])
21
40
  });
22
41
  }
23
42
  }
@@ -25,11 +44,20 @@ function compareRpgCatalog(ours, theirs, expectedProvider = SIFA_RPG_PROVIDER_DI
25
44
  const ourIds = new Set(ours.map((i) => i.id));
26
45
  const extra = theirs.items.map((i) => i.item).filter((id) => !ourIds.has(id));
27
46
  const providerMismatch = theirs.provider === expectedProvider ? null : { expected: expectedProvider, actual: String(theirs.provider) };
28
- return { ok: !providerMismatch && mismatches.length === 0, providerMismatch, mismatches, extra };
47
+ return {
48
+ ok: !providerMismatch && mismatches.length === 0,
49
+ providerMismatch,
50
+ mismatches,
51
+ extra,
52
+ missingDisabled
53
+ };
54
+ }
55
+ function cell(value) {
56
+ return value.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\r?\n/g, "<br>");
29
57
  }
30
58
  function renderRpgCatalogDriftMarkdown(result) {
31
59
  const lines = ["## rpg.actor item catalog drift", ""];
32
- if (result.ok) lines.push("No drift: every enabled Sifa item matches rpg.actor.", "");
60
+ if (result.ok) lines.push("No drift: every Sifa item matches rpg.actor.", "");
33
61
  if (result.providerMismatch) {
34
62
  lines.push(
35
63
  `**Provider mismatch:** expected \`${result.providerMismatch.expected}\`, got \`${result.providerMismatch.actual}\`.`,
@@ -37,23 +65,42 @@ function renderRpgCatalogDriftMarkdown(result) {
37
65
  );
38
66
  }
39
67
  if (result.mismatches.length > 0) {
40
- lines.push("| Item | Field | Ours | rpg.actor |", "| --- | --- | --- | --- |");
41
- for (const m of result.mismatches) {
42
- lines.push(`| ${m.item} | ${m.field} | ${m.ours} | ${m.theirs} |`);
68
+ const byItem = /* @__PURE__ */ new Map();
69
+ for (const m of result.mismatches) byItem.set(m.item, [...byItem.get(m.item) ?? [], m]);
70
+ for (const [item, ms] of byItem) {
71
+ lines.push(
72
+ `### \`${item}\``,
73
+ "",
74
+ "| Field | Ours (catalog.ts) | rpg.actor |",
75
+ "| --- | --- | --- |"
76
+ );
77
+ for (const m of ms) lines.push(`| ${m.field} | ${cell(m.ours)} | ${cell(m.theirs)} |`);
78
+ lines.push("");
43
79
  }
44
- lines.push("");
80
+ lines.push(
81
+ "To adopt rpg.actor's values, run `pnpm rpg:sync-catalog` with `RPG_ACTOR_API_KEY` set, review the diff to `src/rpg/catalog.ts`, and commit.",
82
+ ""
83
+ );
45
84
  }
46
85
  if (result.extra.length > 0) {
47
86
  lines.push(
48
- `Notice: rpg.actor lists items not in our catalog: ${result.extra.map((id) => `\`${id}\``).join(", ")}.`,
87
+ `Notice: rpg.actor lists items not in our catalog: ${result.extra.map((id) => `\`${id}\``).join(", ")}. \`pnpm rpg:sync-catalog\` adds them disabled.`,
88
+ ""
89
+ );
90
+ }
91
+ if (result.missingDisabled.length > 0) {
92
+ lines.push(
93
+ `Notice: rpg.actor does not list these disabled items: ${result.missingDisabled.map((id) => `\`${id}\``).join(", ")}.`,
49
94
  ""
50
95
  );
51
96
  }
52
97
  return lines.join("\n");
53
98
  }
54
99
  export {
100
+ RPG_ITEM_DATA_FIELDS,
55
101
  SIFA_RPG_PROVIDER_DID,
56
102
  compareRpgCatalog,
57
- renderRpgCatalogDriftMarkdown
103
+ renderRpgCatalogDriftMarkdown,
104
+ sameRpgFieldValue
58
105
  };
59
106
  //# sourceMappingURL=catalog-drift.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/rpg/catalog-drift.ts"],"sourcesContent":["/**\n * Pure comparison between our rpg.actor item catalog (`RPG_ITEMS`) and the\n * catalog rpg.actor serves at `GET /api/creator/items`. Used by\n * scripts/check-rpg-catalog.mjs (weekly drift check). Internal: deliberately\n * not re-exported from the `/rpg` barrel.\n */\nimport type { RpgItem } from './catalog.js';\n\n/** The sifa.id DID that owns the Sifa items on rpg.actor. */\nexport const SIFA_RPG_PROVIDER_DID = 'did:plc:2f2ahswozqy4v5lvu676375y';\n\nexport interface RpgActorItem {\n item: string;\n title?: string;\n description?: string;\n kind?: string;\n category?: string;\n channels?: string[];\n assetCid?: string;\n iconCid?: string;\n}\n\nexport interface RpgActorCatalog {\n provider: string;\n count?: number;\n items: RpgActorItem[];\n}\n\n/**\n * Fields that must match. `description` is left out on purpose: it is flavor\n * text that may be reworded on either side without affecting gifts.\n */\nconst COMPARED_FIELDS = ['title', 'kind', 'category', 'assetCid', 'iconCid'] as const;\n\nexport interface RpgCatalogMismatch {\n item: string;\n field: (typeof COMPARED_FIELDS)[number] | 'listed';\n ours: string;\n theirs: string;\n}\n\nexport interface RpgCatalogDriftResult {\n ok: boolean;\n providerMismatch: { expected: string; actual: string } | null;\n mismatches: RpgCatalogMismatch[];\n /** Items rpg.actor lists that we do not have. Informational only. */\n extra: string[];\n}\n\nexport function compareRpgCatalog(\n ours: readonly RpgItem[],\n theirs: RpgActorCatalog,\n expectedProvider: string = SIFA_RPG_PROVIDER_DID,\n): RpgCatalogDriftResult {\n const remote = new Map(theirs.items.map((i) => [i.item, i]));\n const mismatches: RpgCatalogMismatch[] = [];\n\n for (const item of ours) {\n if (!item.enabled) continue;\n const r = remote.get(item.id);\n if (!r) {\n mismatches.push({ item: item.id, field: 'listed', ours: 'yes', theirs: 'missing' });\n continue;\n }\n for (const field of COMPARED_FIELDS) {\n if (item[field] !== r[field]) {\n mismatches.push({\n item: item.id,\n field,\n ours: item[field],\n theirs: r[field] ?? '(absent)',\n });\n }\n }\n }\n\n const ourIds = new Set(ours.map((i) => i.id));\n const extra = theirs.items.map((i) => i.item).filter((id) => !ourIds.has(id));\n\n const providerMismatch =\n theirs.provider === expectedProvider\n ? null\n : { expected: expectedProvider, actual: String(theirs.provider) };\n\n return { ok: !providerMismatch && mismatches.length === 0, providerMismatch, mismatches, extra };\n}\n\nexport function renderRpgCatalogDriftMarkdown(result: RpgCatalogDriftResult): string {\n const lines = ['## rpg.actor item catalog drift', ''];\n if (result.ok) lines.push('No drift: every enabled Sifa item matches rpg.actor.', '');\n if (result.providerMismatch) {\n lines.push(\n `**Provider mismatch:** expected \\`${result.providerMismatch.expected}\\`, got \\`${result.providerMismatch.actual}\\`.`,\n '',\n );\n }\n if (result.mismatches.length > 0) {\n lines.push('| Item | Field | Ours | rpg.actor |', '| --- | --- | --- | --- |');\n for (const m of result.mismatches) {\n lines.push(`| ${m.item} | ${m.field} | ${m.ours} | ${m.theirs} |`);\n }\n lines.push('');\n }\n if (result.extra.length > 0) {\n lines.push(\n `Notice: rpg.actor lists items not in our catalog: ${result.extra.map((id) => `\\`${id}\\``).join(', ')}.`,\n '',\n );\n }\n return lines.join('\\n');\n}\n"],"mappings":";AASO,IAAM,wBAAwB;AAuBrC,IAAM,kBAAkB,CAAC,SAAS,QAAQ,YAAY,YAAY,SAAS;AAiBpE,SAAS,kBACd,MACA,QACA,mBAA2B,uBACJ;AACvB,QAAM,SAAS,IAAI,IAAI,OAAO,MAAM,IAAI,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC;AAC3D,QAAM,aAAmC,CAAC;AAE1C,aAAW,QAAQ,MAAM;AACvB,QAAI,CAAC,KAAK,QAAS;AACnB,UAAM,IAAI,OAAO,IAAI,KAAK,EAAE;AAC5B,QAAI,CAAC,GAAG;AACN,iBAAW,KAAK,EAAE,MAAM,KAAK,IAAI,OAAO,UAAU,MAAM,OAAO,QAAQ,UAAU,CAAC;AAClF;AAAA,IACF;AACA,eAAW,SAAS,iBAAiB;AACnC,UAAI,KAAK,KAAK,MAAM,EAAE,KAAK,GAAG;AAC5B,mBAAW,KAAK;AAAA,UACd,MAAM,KAAK;AAAA,UACX;AAAA,UACA,MAAM,KAAK,KAAK;AAAA,UAChB,QAAQ,EAAE,KAAK,KAAK;AAAA,QACtB,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAEA,QAAM,SAAS,IAAI,IAAI,KAAK,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC;AAC5C,QAAM,QAAQ,OAAO,MAAM,IAAI,CAAC,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,OAAO,CAAC,OAAO,IAAI,EAAE,CAAC;AAE5E,QAAM,mBACJ,OAAO,aAAa,mBAChB,OACA,EAAE,UAAU,kBAAkB,QAAQ,OAAO,OAAO,QAAQ,EAAE;AAEpE,SAAO,EAAE,IAAI,CAAC,oBAAoB,WAAW,WAAW,GAAG,kBAAkB,YAAY,MAAM;AACjG;AAEO,SAAS,8BAA8B,QAAuC;AACnF,QAAM,QAAQ,CAAC,mCAAmC,EAAE;AACpD,MAAI,OAAO,GAAI,OAAM,KAAK,wDAAwD,EAAE;AACpF,MAAI,OAAO,kBAAkB;AAC3B,UAAM;AAAA,MACJ,qCAAqC,OAAO,iBAAiB,QAAQ,aAAa,OAAO,iBAAiB,MAAM;AAAA,MAChH;AAAA,IACF;AAAA,EACF;AACA,MAAI,OAAO,WAAW,SAAS,GAAG;AAChC,UAAM,KAAK,uCAAuC,2BAA2B;AAC7E,eAAW,KAAK,OAAO,YAAY;AACjC,YAAM,KAAK,KAAK,EAAE,IAAI,MAAM,EAAE,KAAK,MAAM,EAAE,IAAI,MAAM,EAAE,MAAM,IAAI;AAAA,IACnE;AACA,UAAM,KAAK,EAAE;AAAA,EACf;AACA,MAAI,OAAO,MAAM,SAAS,GAAG;AAC3B,UAAM;AAAA,MACJ,qDAAqD,OAAO,MAAM,IAAI,CAAC,OAAO,KAAK,EAAE,IAAI,EAAE,KAAK,IAAI,CAAC;AAAA,MACrG;AAAA,IACF;AAAA,EACF;AACA,SAAO,MAAM,KAAK,IAAI;AACxB;","names":[]}
1
+ {"version":3,"sources":["../../src/rpg/catalog-drift.ts"],"sourcesContent":["/**\n * Pure comparison between our rpg.actor item catalog (`RPG_ITEMS`) and the\n * catalog rpg.actor serves at `GET /api/creator/items`. Used by\n * scripts/check-rpg-catalog.mjs (weekly drift check). Internal: deliberately\n * not re-exported from the `/rpg` barrel.\n */\nimport type { RpgItem } from './catalog.js';\n\n/** The sifa.id DID that owns the Sifa items on rpg.actor. */\nexport const SIFA_RPG_PROVIDER_DID = 'did:plc:2f2ahswozqy4v5lvu676375y';\n\nexport interface RpgActorItem {\n item: string;\n title?: string;\n description?: string;\n kind?: string;\n category?: string;\n channels?: string[];\n assetCid?: string;\n iconCid?: string;\n}\n\nexport interface RpgActorCatalog {\n provider: string;\n count?: number;\n items: RpgActorItem[];\n}\n\n/**\n * The item data fields our catalog copies from rpg.actor. All of them must\n * match. Our own fields (`id` aside, `unlock` and `enabled`) are not rpg.actor\n * data, and rpg.actor's `context` is not stored in the catalog at all.\n */\nexport const RPG_ITEM_DATA_FIELDS = [\n 'title',\n 'description',\n 'kind',\n 'category',\n 'channels',\n 'assetCid',\n 'iconCid',\n] as const;\nexport type RpgItemDataField = (typeof RPG_ITEM_DATA_FIELDS)[number];\n\nexport interface RpgCatalogMismatch {\n item: string;\n field: RpgItemDataField | 'listed';\n ours: string;\n theirs: string;\n}\n\nexport interface RpgCatalogDriftResult {\n ok: boolean;\n providerMismatch: { expected: string; actual: string } | null;\n mismatches: RpgCatalogMismatch[];\n /** Items rpg.actor lists that we do not have. Informational only. */\n extra: string[];\n /** Disabled items of ours that rpg.actor does not list. Informational only. */\n missingDisabled: string[];\n}\n\n/** Same value for the catalog's purposes (strings, or the channels array). */\nexport function sameRpgFieldValue(a: unknown, b: unknown): boolean {\n return JSON.stringify(a ?? null) === JSON.stringify(b ?? null);\n}\n\nfunction display(value: string | string[] | undefined): string {\n if (value === undefined) return '(absent)';\n return Array.isArray(value) ? value.join(', ') : value;\n}\n\nexport function compareRpgCatalog(\n ours: readonly RpgItem[],\n theirs: RpgActorCatalog,\n expectedProvider: string = SIFA_RPG_PROVIDER_DID,\n): RpgCatalogDriftResult {\n const remote = new Map(theirs.items.map((i) => [i.item, i]));\n const mismatches: RpgCatalogMismatch[] = [];\n const missingDisabled: string[] = [];\n\n // Disabled items are compared too: their data is reviewed copy that ships\n // the moment they are enabled.\n for (const item of ours) {\n const r = remote.get(item.id);\n if (!r) {\n if (item.enabled) {\n mismatches.push({ item: item.id, field: 'listed', ours: 'yes', theirs: 'missing' });\n } else {\n missingDisabled.push(item.id);\n }\n continue;\n }\n for (const field of RPG_ITEM_DATA_FIELDS) {\n if (!sameRpgFieldValue(item[field], r[field])) {\n mismatches.push({\n item: item.id,\n field,\n ours: display(item[field]),\n theirs: display(r[field]),\n });\n }\n }\n }\n\n const ourIds = new Set(ours.map((i) => i.id));\n const extra = theirs.items.map((i) => i.item).filter((id) => !ourIds.has(id));\n\n const providerMismatch =\n theirs.provider === expectedProvider\n ? null\n : { expected: expectedProvider, actual: String(theirs.provider) };\n\n return {\n ok: !providerMismatch && mismatches.length === 0,\n providerMismatch,\n mismatches,\n extra,\n missingDisabled,\n };\n}\n\n/** Escape a value for a Markdown table cell. */\nfunction cell(value: string): string {\n return value.replace(/\\\\/g, '\\\\\\\\').replace(/\\|/g, '\\\\|').replace(/\\r?\\n/g, '<br>');\n}\n\nexport function renderRpgCatalogDriftMarkdown(result: RpgCatalogDriftResult): string {\n const lines = ['## rpg.actor item catalog drift', ''];\n if (result.ok) lines.push('No drift: every Sifa item matches rpg.actor.', '');\n if (result.providerMismatch) {\n lines.push(\n `**Provider mismatch:** expected \\`${result.providerMismatch.expected}\\`, got \\`${result.providerMismatch.actual}\\`.`,\n '',\n );\n }\n if (result.mismatches.length > 0) {\n const byItem = new Map<string, RpgCatalogMismatch[]>();\n for (const m of result.mismatches) byItem.set(m.item, [...(byItem.get(m.item) ?? []), m]);\n for (const [item, ms] of byItem) {\n lines.push(\n `### \\`${item}\\``,\n '',\n '| Field | Ours (catalog.ts) | rpg.actor |',\n '| --- | --- | --- |',\n );\n for (const m of ms) lines.push(`| ${m.field} | ${cell(m.ours)} | ${cell(m.theirs)} |`);\n lines.push('');\n }\n lines.push(\n \"To adopt rpg.actor's values, run `pnpm rpg:sync-catalog` with `RPG_ACTOR_API_KEY` set, review the diff to `src/rpg/catalog.ts`, and commit.\",\n '',\n );\n }\n if (result.extra.length > 0) {\n lines.push(\n `Notice: rpg.actor lists items not in our catalog: ${result.extra.map((id) => `\\`${id}\\``).join(', ')}. \\`pnpm rpg:sync-catalog\\` adds them disabled.`,\n '',\n );\n }\n if (result.missingDisabled.length > 0) {\n lines.push(\n `Notice: rpg.actor does not list these disabled items: ${result.missingDisabled.map((id) => `\\`${id}\\``).join(', ')}.`,\n '',\n );\n }\n return lines.join('\\n');\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,83 @@
1
+ // src/rpg/catalog-sync.ts
2
+ import { RpgItemSchema } from "./catalog.js";
3
+ import {
4
+ RPG_ITEM_DATA_FIELDS,
5
+ sameRpgFieldValue
6
+ } from "./catalog-drift.js";
7
+ var RPG_UNLOCK_PLACEHOLDER = { kind: "usesApp", appId: "todo-unlock-rule" };
8
+ function validateItem(candidate) {
9
+ const parsed = RpgItemSchema.safeParse(candidate);
10
+ if (parsed.success) return { data: parsed.data };
11
+ return {
12
+ reason: parsed.error.issues.map((i) => `${i.path.join(".") || "(item)"}: ${i.message}`).join("; ")
13
+ };
14
+ }
15
+ function syncRpgCatalog(ours, theirs) {
16
+ const remote = new Map(theirs.map((i) => [i.item, i]));
17
+ const items = [];
18
+ const changes = [];
19
+ const added = [];
20
+ const skipped = [];
21
+ const iconUpdates = [];
22
+ for (const item of ours) {
23
+ const r = remote.get(item.id);
24
+ if (!r) {
25
+ items.push(item);
26
+ continue;
27
+ }
28
+ const candidate = { ...item };
29
+ const itemChanges = [];
30
+ for (const field of RPG_ITEM_DATA_FIELDS) {
31
+ const after = r[field];
32
+ if (after === void 0 && field !== "channels") continue;
33
+ if (sameRpgFieldValue(item[field], after)) continue;
34
+ if (after === void 0) delete candidate[field];
35
+ else candidate[field] = Array.isArray(after) ? [...after] : after;
36
+ itemChanges.push({ item: item.id, field, before: item[field], after });
37
+ }
38
+ if (itemChanges.length === 0) {
39
+ items.push(item);
40
+ continue;
41
+ }
42
+ const checked = validateItem(candidate);
43
+ if ("reason" in checked) {
44
+ skipped.push({ item: item.id, reason: checked.reason });
45
+ items.push(item);
46
+ continue;
47
+ }
48
+ items.push(checked.data);
49
+ changes.push(...itemChanges);
50
+ if (itemChanges.some((c) => c.field === "iconCid")) {
51
+ iconUpdates.push({ item: item.id, iconCid: checked.data.iconCid });
52
+ }
53
+ }
54
+ const ourIds = new Set(ours.map((i) => i.id));
55
+ for (const r of theirs) {
56
+ if (ourIds.has(r.item)) continue;
57
+ const checked = validateItem({
58
+ id: r.item,
59
+ title: r.title,
60
+ description: r.description,
61
+ kind: r.kind,
62
+ category: r.category,
63
+ ...r.channels === void 0 ? {} : { channels: [...r.channels] },
64
+ assetCid: r.assetCid,
65
+ iconCid: r.iconCid,
66
+ enabled: false,
67
+ unlock: [RPG_UNLOCK_PLACEHOLDER]
68
+ });
69
+ if ("reason" in checked) {
70
+ skipped.push({ item: r.item, reason: checked.reason });
71
+ continue;
72
+ }
73
+ items.push(checked.data);
74
+ added.push(r.item);
75
+ iconUpdates.push({ item: r.item, iconCid: checked.data.iconCid });
76
+ }
77
+ return { items, changes, added, skipped, iconUpdates };
78
+ }
79
+ export {
80
+ RPG_UNLOCK_PLACEHOLDER,
81
+ syncRpgCatalog
82
+ };
83
+ //# sourceMappingURL=catalog-sync.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/rpg/catalog-sync.ts"],"sourcesContent":["/**\n * Pure transform behind scripts/sync-rpg-catalog.mjs: given our catalog data\n * and the items rpg.actor serves, work out the catalog we would have after\n * adopting rpg.actor's item data. No network, no file access. Internal:\n * deliberately not re-exported from the `/rpg` barrel.\n *\n * Only the item data fields (`RPG_ITEM_DATA_FIELDS`) are taken from rpg.actor.\n * Our own fields (`id`, `unlock`, `enabled`) are kept. Items rpg.actor lists\n * that we do not have are appended disabled, with a placeholder unlock rule.\n */\nimport { RpgItemSchema, type RpgItem, type RpgUnlock } from './catalog.js';\nimport {\n RPG_ITEM_DATA_FIELDS,\n sameRpgFieldValue,\n type RpgActorItem,\n type RpgItemDataField,\n} from './catalog-drift.js';\n\n/**\n * Unlock rule for items added by the sync. The schema needs at least one\n * rule, so this is a `usesApp` rule for an app id that no registry app has:\n * it can never match, so an item enabled by mistake before its real rule is\n * written stays unearned instead of being handed out.\n */\nexport const RPG_UNLOCK_PLACEHOLDER: RpgUnlock = { kind: 'usesApp', appId: 'todo-unlock-rule' };\n\nexport interface RpgCatalogSyncChange {\n item: string;\n field: RpgItemDataField;\n before: string | string[] | undefined;\n after: string | string[] | undefined;\n}\n\nexport interface RpgCatalogSyncResult {\n /** The catalog after the sync: our items in our order, then added items. */\n items: RpgItem[];\n /** Field changes to existing items. */\n changes: RpgCatalogSyncChange[];\n /** Ids of items appended (disabled). */\n added: string[];\n /** Items left as they are because rpg.actor's data would not pass the schema. */\n skipped: { item: string; reason: string }[];\n /** Items whose icon is new or changed; the icon has to be copied to sifa-web. */\n iconUpdates: { item: string; iconCid: string }[];\n}\n\nfunction validateItem(candidate: unknown): { data: RpgItem } | { reason: string } {\n const parsed = RpgItemSchema.safeParse(candidate);\n if (parsed.success) return { data: parsed.data };\n return {\n reason: parsed.error.issues\n .map((i) => `${i.path.join('.') || '(item)'}: ${i.message}`)\n .join('; '),\n };\n}\n\nexport function syncRpgCatalog(\n ours: readonly RpgItem[],\n theirs: readonly RpgActorItem[],\n): RpgCatalogSyncResult {\n const remote = new Map(theirs.map((i) => [i.item, i]));\n const items: RpgItem[] = [];\n const changes: RpgCatalogSyncChange[] = [];\n const added: string[] = [];\n const skipped: { item: string; reason: string }[] = [];\n const iconUpdates: { item: string; iconCid: string }[] = [];\n\n for (const item of ours) {\n const r = remote.get(item.id);\n if (!r) {\n items.push(item);\n continue;\n }\n const candidate: Record<string, unknown> = { ...item };\n const itemChanges: RpgCatalogSyncChange[] = [];\n for (const field of RPG_ITEM_DATA_FIELDS) {\n const after = r[field];\n // A required field rpg.actor leaves out is kept as ours; only the\n // optional `channels` can be dropped.\n if (after === undefined && field !== 'channels') continue;\n if (sameRpgFieldValue(item[field], after)) continue;\n if (after === undefined) delete candidate[field];\n else candidate[field] = Array.isArray(after) ? [...after] : after;\n itemChanges.push({ item: item.id, field, before: item[field], after });\n }\n if (itemChanges.length === 0) {\n items.push(item);\n continue;\n }\n const checked = validateItem(candidate);\n if ('reason' in checked) {\n skipped.push({ item: item.id, reason: checked.reason });\n items.push(item);\n continue;\n }\n items.push(checked.data);\n changes.push(...itemChanges);\n if (itemChanges.some((c) => c.field === 'iconCid')) {\n iconUpdates.push({ item: item.id, iconCid: checked.data.iconCid });\n }\n }\n\n const ourIds = new Set(ours.map((i) => i.id));\n for (const r of theirs) {\n if (ourIds.has(r.item)) continue;\n const checked = validateItem({\n id: r.item,\n title: r.title,\n description: r.description,\n kind: r.kind,\n category: r.category,\n ...(r.channels === undefined ? {} : { channels: [...r.channels] }),\n assetCid: r.assetCid,\n iconCid: r.iconCid,\n enabled: false,\n unlock: [RPG_UNLOCK_PLACEHOLDER],\n });\n if ('reason' in checked) {\n skipped.push({ item: r.item, reason: checked.reason });\n continue;\n }\n items.push(checked.data);\n added.push(r.item);\n iconUpdates.push({ item: r.item, iconCid: checked.data.iconCid });\n }\n\n return { items, changes, added, skipped, iconUpdates };\n}\n"],"mappings":"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","names":[]}
@@ -114,7 +114,7 @@ var RPG_ITEMS = [
114
114
  {
115
115
  id: "weekend_shirt",
116
116
  title: "Weekend Shirt",
117
- description: "Fun shirt to wear when work is all finished",
117
+ description: "Fun shirt to wear when work is all finished.... but you still have a side-project...",
118
118
  kind: "layer",
119
119
  category: "tops",
120
120
  channels: ["main", "sub1", "sub2"],
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/rpg/catalog.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport const RpgUnlockSchema = z.discriminatedUnion('kind', [\n z.object({\n kind: z.literal('hasRecord'),\n collection: z.enum([\n 'id.sifa.profile.position',\n 'id.sifa.profile.volunteering',\n 'id.sifa.profile.presentation',\n 'id.sifa.profile.education',\n ]),\n }),\n z.object({ kind: z.literal('hasExternalAccount'), platforms: z.array(z.string()).min(1) }),\n z.object({ kind: z.literal('usesApp'), appId: z.string().min(1) }),\n z.object({ kind: z.literal('hasDoctorate') }),\n]);\n\n/** CIDv1 (raw codec, sha2-256, base32) as used for rpg.actor image blobs. */\nexport const RPG_CID_REGEX = /^bafkrei[a-z2-7]{52}$/;\n\nexport const RpgItemSchema = z.object({\n /** Lowercase `[a-z0-9_]` only: the id is embedded in an AT Protocol record key. */\n id: z\n .string()\n .min(1)\n .max(50)\n .regex(/^[a-z0-9_]+$/),\n title: z.string().max(100),\n description: z.string().max(500),\n kind: z.enum(['layer', 'held', 'overlay']),\n category: z.string().max(30),\n /** rpg.actor channels the item is offered in (optional). */\n channels: z.array(z.string().min(1)).optional(),\n /** Full-size sprite asset (CIDv1, raw codec, base32). */\n assetCid: z.string().regex(RPG_CID_REGEX),\n /** Inventory icon (CIDv1, raw codec, base32). */\n iconCid: z.string().regex(RPG_CID_REGEX),\n unlock: z.array(RpgUnlockSchema).min(1), // ANY of these unlocks the item\n enabled: z.boolean(),\n});\nexport type RpgItem = z.infer<typeof RpgItemSchema>;\nexport type RpgUnlock = z.infer<typeof RpgUnlockSchema>;\n\n/**\n * The Sifa item catalog for rpg.actor. Ids, titles, descriptions, kinds,\n * categories, channels and image CIDs come from rpg.actor's item cores.\n */\nexport const RPG_ITEMS: readonly RpgItem[] = [\n {\n id: 'leather_briefcase',\n title: 'Leather Briefcase',\n description: 'A trusty briefcase, for your daily commute',\n kind: 'held',\n category: 'righthand',\n channels: ['main'],\n assetCid: 'bafkreibjtgvetkpzwkliq4o4qnf7atbui6nxgyas3fyk6sjgd5zrl52swa',\n iconCid: 'bafkreifmyglopjtbkiuocmcjcbjhtr34vjumkzbnspbnlsezbdphbkfvu4',\n enabled: true,\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.position' }],\n },\n {\n id: 'sifa_suit',\n title: 'Sifa Suit',\n description: 'Sharp power suit for confirmed professionals',\n kind: 'overlay',\n category: 'costume',\n channels: ['main'],\n assetCid: 'bafkreibortlffvfzkdcuqdpoubczuno7ohnvfwqhefe4h4s36eooe4njgu',\n iconCid: 'bafkreifo7vojbr4i6uvsthj2hbczoqwmrn3ovysrskvxlxw6fvkbswm2ae',\n enabled: false,\n // Reserved for the confirmed tier (a colleague confirms the job and both\n // have a confirmed work email). The unlock kind for that is added once work\n // email confirmation exists; until then the item stays disabled.\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.position' }],\n },\n {\n id: 'speaker_mic',\n title: 'Speaker Mic',\n description: \"A presentation mic, so you can drop it on 'em\",\n kind: 'held',\n category: 'righthand',\n channels: ['main'],\n assetCid: 'bafkreibr4kgeijc5naecf7l4awfv3omph4ngjmjb36qkfnk2sdo6l45ck4',\n iconCid: 'bafkreidnkirbw4nwiro3ocrn5nk66uxf3dhcoy5pojunisvs7so55u3j3q',\n enabled: true,\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.presentation' }],\n },\n {\n id: 'strapped_books',\n title: 'Strapped Books',\n description: 'Knowledge is power, better come strapped',\n kind: 'held',\n category: 'righthand',\n channels: ['main'],\n assetCid: 'bafkreieofltl6427jy3n6dfv63phublalv6nq2hcs36qnyxz33ta6u6yqi',\n iconCid: 'bafkreiavywpv4sgw7gnkzq5gw3nq6yihaucdexpsvgko26q6jq6u6grjpi',\n enabled: true,\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.education' }],\n },\n {\n id: 'dev_hoodie',\n title: 'Dev Hoodie',\n description: 'Snug hoodie for when you enter total code mode',\n kind: 'layer',\n category: 'tops',\n channels: ['main', 'sub1'],\n assetCid: 'bafkreigmrqp5kd6lytqcen2mypuv6dqonrdfbi2ngxhq3uqozk63yi7hoy',\n iconCid: 'bafkreigjwti3w43ta6gy2c75qtdnuvovfohjtv2febbd6hdhpri4tupbem',\n enabled: true,\n unlock: [\n { kind: 'hasExternalAccount', platforms: ['github'] },\n { kind: 'usesApp', appId: 'tangled' },\n ],\n },\n {\n id: 'doctoral_cap',\n title: 'Doctoral Cap',\n description: 'Took so many long years to earn this hat',\n kind: 'layer',\n category: 'headwear',\n channels: ['main'],\n assetCid: 'bafkreic7pvfpb7fjgooiaianrc27mh7fwj5vkicre63l6x5hrzb4w36tcq',\n iconCid: 'bafkreigqt6lmx5crj3w7vc3bvuewarzdreg2doxyeerbxmhljhpeshjewm',\n enabled: true,\n unlock: [{ kind: 'hasDoctorate' }],\n },\n {\n id: 'weekend_shirt',\n title: 'Weekend Shirt',\n description: 'Fun shirt to wear when work is all finished',\n kind: 'layer',\n category: 'tops',\n channels: ['main', 'sub1', 'sub2'],\n assetCid: 'bafkreigifgpwkxvftiuzbjc6c2xz4xf2iv4bznbhu364e7rhvfmhosbak4',\n iconCid: 'bafkreieguco74wkabuyhs4sz6ec35nyywr7ikaw6et4coeezphnsnqqjui',\n enabled: true,\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.volunteering' }],\n },\n {\n id: 'lab_coat',\n title: 'Lab Coat',\n description: 'Scientifically proven attire',\n kind: 'layer',\n category: 'tops',\n channels: ['main'],\n assetCid: 'bafkreiby4g6fgkmi65myv7tdblgb4jvqa3kgnrxvtkxlrtme55unuie4he',\n iconCid: 'bafkreifoddxk5tobgwadib5rxwjn67iind6me3swpvw4t5rbcauc5r4syy',\n enabled: true,\n unlock: [{ kind: 'hasExternalAccount', platforms: ['orcid'] }],\n },\n];\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../../src/rpg/catalog.ts"],"sourcesContent":["import { z } from 'zod';\n\nexport const RpgUnlockSchema = z.discriminatedUnion('kind', [\n z.object({\n kind: z.literal('hasRecord'),\n collection: z.enum([\n 'id.sifa.profile.position',\n 'id.sifa.profile.volunteering',\n 'id.sifa.profile.presentation',\n 'id.sifa.profile.education',\n ]),\n }),\n z.object({ kind: z.literal('hasExternalAccount'), platforms: z.array(z.string()).min(1) }),\n z.object({ kind: z.literal('usesApp'), appId: z.string().min(1) }),\n z.object({ kind: z.literal('hasDoctorate') }),\n]);\n\n/** CIDv1 (raw codec, sha2-256, base32) as used for rpg.actor image blobs. */\nexport const RPG_CID_REGEX = /^bafkrei[a-z2-7]{52}$/;\n\nexport const RpgItemSchema = z.object({\n /** Lowercase `[a-z0-9_]` only: the id is embedded in an AT Protocol record key. */\n id: z\n .string()\n .min(1)\n .max(50)\n .regex(/^[a-z0-9_]+$/),\n title: z.string().max(100),\n description: z.string().max(500),\n kind: z.enum(['layer', 'held', 'overlay']),\n category: z.string().max(30),\n /** rpg.actor channels the item is offered in (optional). */\n channels: z.array(z.string().min(1)).optional(),\n /** Full-size sprite asset (CIDv1, raw codec, base32). */\n assetCid: z.string().regex(RPG_CID_REGEX),\n /** Inventory icon (CIDv1, raw codec, base32). */\n iconCid: z.string().regex(RPG_CID_REGEX),\n unlock: z.array(RpgUnlockSchema).min(1), // ANY of these unlocks the item\n enabled: z.boolean(),\n});\nexport type RpgItem = z.infer<typeof RpgItemSchema>;\nexport type RpgUnlock = z.infer<typeof RpgUnlockSchema>;\n\n/**\n * The Sifa item catalog for rpg.actor. Ids, titles, descriptions, kinds,\n * categories, channels and image CIDs come from rpg.actor's item cores; the\n * weekly drift check compares them and `pnpm rpg:sync-catalog` adopts\n * rpg.actor's changes. `unlock` and `enabled` are ours.\n */\nexport const RPG_ITEMS: readonly RpgItem[] = [\n {\n id: 'leather_briefcase',\n title: 'Leather Briefcase',\n description: 'A trusty briefcase, for your daily commute',\n kind: 'held',\n category: 'righthand',\n channels: ['main'],\n assetCid: 'bafkreibjtgvetkpzwkliq4o4qnf7atbui6nxgyas3fyk6sjgd5zrl52swa',\n iconCid: 'bafkreifmyglopjtbkiuocmcjcbjhtr34vjumkzbnspbnlsezbdphbkfvu4',\n enabled: true,\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.position' }],\n },\n {\n id: 'sifa_suit',\n title: 'Sifa Suit',\n description: 'Sharp power suit for confirmed professionals',\n kind: 'overlay',\n category: 'costume',\n channels: ['main'],\n assetCid: 'bafkreibortlffvfzkdcuqdpoubczuno7ohnvfwqhefe4h4s36eooe4njgu',\n iconCid: 'bafkreifo7vojbr4i6uvsthj2hbczoqwmrn3ovysrskvxlxw6fvkbswm2ae',\n enabled: false,\n // Reserved for the confirmed tier (a colleague confirms the job and both\n // have a confirmed work email). The unlock kind for that is added once work\n // email confirmation exists; until then the item stays disabled.\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.position' }],\n },\n {\n id: 'speaker_mic',\n title: 'Speaker Mic',\n description: \"A presentation mic, so you can drop it on 'em\",\n kind: 'held',\n category: 'righthand',\n channels: ['main'],\n assetCid: 'bafkreibr4kgeijc5naecf7l4awfv3omph4ngjmjb36qkfnk2sdo6l45ck4',\n iconCid: 'bafkreidnkirbw4nwiro3ocrn5nk66uxf3dhcoy5pojunisvs7so55u3j3q',\n enabled: true,\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.presentation' }],\n },\n {\n id: 'strapped_books',\n title: 'Strapped Books',\n description: 'Knowledge is power, better come strapped',\n kind: 'held',\n category: 'righthand',\n channels: ['main'],\n assetCid: 'bafkreieofltl6427jy3n6dfv63phublalv6nq2hcs36qnyxz33ta6u6yqi',\n iconCid: 'bafkreiavywpv4sgw7gnkzq5gw3nq6yihaucdexpsvgko26q6jq6u6grjpi',\n enabled: true,\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.education' }],\n },\n {\n id: 'dev_hoodie',\n title: 'Dev Hoodie',\n description: 'Snug hoodie for when you enter total code mode',\n kind: 'layer',\n category: 'tops',\n channels: ['main', 'sub1'],\n assetCid: 'bafkreigmrqp5kd6lytqcen2mypuv6dqonrdfbi2ngxhq3uqozk63yi7hoy',\n iconCid: 'bafkreigjwti3w43ta6gy2c75qtdnuvovfohjtv2febbd6hdhpri4tupbem',\n enabled: true,\n unlock: [\n { kind: 'hasExternalAccount', platforms: ['github'] },\n { kind: 'usesApp', appId: 'tangled' },\n ],\n },\n {\n id: 'doctoral_cap',\n title: 'Doctoral Cap',\n description: 'Took so many long years to earn this hat',\n kind: 'layer',\n category: 'headwear',\n channels: ['main'],\n assetCid: 'bafkreic7pvfpb7fjgooiaianrc27mh7fwj5vkicre63l6x5hrzb4w36tcq',\n iconCid: 'bafkreigqt6lmx5crj3w7vc3bvuewarzdreg2doxyeerbxmhljhpeshjewm',\n enabled: true,\n unlock: [{ kind: 'hasDoctorate' }],\n },\n {\n id: 'weekend_shirt',\n title: 'Weekend Shirt',\n description:\n 'Fun shirt to wear when work is all finished.... but you still have a side-project...',\n kind: 'layer',\n category: 'tops',\n channels: ['main', 'sub1', 'sub2'],\n assetCid: 'bafkreigifgpwkxvftiuzbjc6c2xz4xf2iv4bznbhu364e7rhvfmhosbak4',\n iconCid: 'bafkreieguco74wkabuyhs4sz6ec35nyywr7ikaw6et4coeezphnsnqqjui',\n enabled: true,\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.volunteering' }],\n },\n {\n id: 'lab_coat',\n title: 'Lab Coat',\n description: 'Scientifically proven attire',\n kind: 'layer',\n category: 'tops',\n channels: ['main'],\n assetCid: 'bafkreiby4g6fgkmi65myv7tdblgb4jvqa3kgnrxvtkxlrtme55unuie4he',\n iconCid: 'bafkreifoddxk5tobgwadib5rxwjn67iind6me3swpvw4t5rbcauc5r4syy',\n enabled: true,\n unlock: [{ kind: 'hasExternalAccount', platforms: ['orcid'] }],\n },\n];\n"],"mappings":"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","names":[]}
@@ -117,7 +117,7 @@ var RPG_ITEMS = [
117
117
  {
118
118
  id: "weekend_shirt",
119
119
  title: "Weekend Shirt",
120
- description: "Fun shirt to wear when work is all finished",
120
+ description: "Fun shirt to wear when work is all finished.... but you still have a side-project...",
121
121
  kind: "layer",
122
122
  category: "tops",
123
123
  channels: ["main", "sub1", "sub2"],
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/rpg/catalog.ts","../../src/rpg/unlocks.ts","../../src/rpg/links.ts"],"names":["z"],"mappings":"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","file":"index.cjs","sourcesContent":["import { z } from 'zod';\n\nexport const RpgUnlockSchema = z.discriminatedUnion('kind', [\n z.object({\n kind: z.literal('hasRecord'),\n collection: z.enum([\n 'id.sifa.profile.position',\n 'id.sifa.profile.volunteering',\n 'id.sifa.profile.presentation',\n 'id.sifa.profile.education',\n ]),\n }),\n z.object({ kind: z.literal('hasExternalAccount'), platforms: z.array(z.string()).min(1) }),\n z.object({ kind: z.literal('usesApp'), appId: z.string().min(1) }),\n z.object({ kind: z.literal('hasDoctorate') }),\n]);\n\n/** CIDv1 (raw codec, sha2-256, base32) as used for rpg.actor image blobs. */\nexport const RPG_CID_REGEX = /^bafkrei[a-z2-7]{52}$/;\n\nexport const RpgItemSchema = z.object({\n /** Lowercase `[a-z0-9_]` only: the id is embedded in an AT Protocol record key. */\n id: z\n .string()\n .min(1)\n .max(50)\n .regex(/^[a-z0-9_]+$/),\n title: z.string().max(100),\n description: z.string().max(500),\n kind: z.enum(['layer', 'held', 'overlay']),\n category: z.string().max(30),\n /** rpg.actor channels the item is offered in (optional). */\n channels: z.array(z.string().min(1)).optional(),\n /** Full-size sprite asset (CIDv1, raw codec, base32). */\n assetCid: z.string().regex(RPG_CID_REGEX),\n /** Inventory icon (CIDv1, raw codec, base32). */\n iconCid: z.string().regex(RPG_CID_REGEX),\n unlock: z.array(RpgUnlockSchema).min(1), // ANY of these unlocks the item\n enabled: z.boolean(),\n});\nexport type RpgItem = z.infer<typeof RpgItemSchema>;\nexport type RpgUnlock = z.infer<typeof RpgUnlockSchema>;\n\n/**\n * The Sifa item catalog for rpg.actor. Ids, titles, descriptions, kinds,\n * categories, channels and image CIDs come from rpg.actor's item cores.\n */\nexport const RPG_ITEMS: readonly RpgItem[] = [\n {\n id: 'leather_briefcase',\n title: 'Leather Briefcase',\n description: 'A trusty briefcase, for your daily commute',\n kind: 'held',\n category: 'righthand',\n channels: ['main'],\n assetCid: 'bafkreibjtgvetkpzwkliq4o4qnf7atbui6nxgyas3fyk6sjgd5zrl52swa',\n iconCid: 'bafkreifmyglopjtbkiuocmcjcbjhtr34vjumkzbnspbnlsezbdphbkfvu4',\n enabled: true,\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.position' }],\n },\n {\n id: 'sifa_suit',\n title: 'Sifa Suit',\n description: 'Sharp power suit for confirmed professionals',\n kind: 'overlay',\n category: 'costume',\n channels: ['main'],\n assetCid: 'bafkreibortlffvfzkdcuqdpoubczuno7ohnvfwqhefe4h4s36eooe4njgu',\n iconCid: 'bafkreifo7vojbr4i6uvsthj2hbczoqwmrn3ovysrskvxlxw6fvkbswm2ae',\n enabled: false,\n // Reserved for the confirmed tier (a colleague confirms the job and both\n // have a confirmed work email). The unlock kind for that is added once work\n // email confirmation exists; until then the item stays disabled.\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.position' }],\n },\n {\n id: 'speaker_mic',\n title: 'Speaker Mic',\n description: \"A presentation mic, so you can drop it on 'em\",\n kind: 'held',\n category: 'righthand',\n channels: ['main'],\n assetCid: 'bafkreibr4kgeijc5naecf7l4awfv3omph4ngjmjb36qkfnk2sdo6l45ck4',\n iconCid: 'bafkreidnkirbw4nwiro3ocrn5nk66uxf3dhcoy5pojunisvs7so55u3j3q',\n enabled: true,\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.presentation' }],\n },\n {\n id: 'strapped_books',\n title: 'Strapped Books',\n description: 'Knowledge is power, better come strapped',\n kind: 'held',\n category: 'righthand',\n channels: ['main'],\n assetCid: 'bafkreieofltl6427jy3n6dfv63phublalv6nq2hcs36qnyxz33ta6u6yqi',\n iconCid: 'bafkreiavywpv4sgw7gnkzq5gw3nq6yihaucdexpsvgko26q6jq6u6grjpi',\n enabled: true,\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.education' }],\n },\n {\n id: 'dev_hoodie',\n title: 'Dev Hoodie',\n description: 'Snug hoodie for when you enter total code mode',\n kind: 'layer',\n category: 'tops',\n channels: ['main', 'sub1'],\n assetCid: 'bafkreigmrqp5kd6lytqcen2mypuv6dqonrdfbi2ngxhq3uqozk63yi7hoy',\n iconCid: 'bafkreigjwti3w43ta6gy2c75qtdnuvovfohjtv2febbd6hdhpri4tupbem',\n enabled: true,\n unlock: [\n { kind: 'hasExternalAccount', platforms: ['github'] },\n { kind: 'usesApp', appId: 'tangled' },\n ],\n },\n {\n id: 'doctoral_cap',\n title: 'Doctoral Cap',\n description: 'Took so many long years to earn this hat',\n kind: 'layer',\n category: 'headwear',\n channels: ['main'],\n assetCid: 'bafkreic7pvfpb7fjgooiaianrc27mh7fwj5vkicre63l6x5hrzb4w36tcq',\n iconCid: 'bafkreigqt6lmx5crj3w7vc3bvuewarzdreg2doxyeerbxmhljhpeshjewm',\n enabled: true,\n unlock: [{ kind: 'hasDoctorate' }],\n },\n {\n id: 'weekend_shirt',\n title: 'Weekend Shirt',\n description: 'Fun shirt to wear when work is all finished',\n kind: 'layer',\n category: 'tops',\n channels: ['main', 'sub1', 'sub2'],\n assetCid: 'bafkreigifgpwkxvftiuzbjc6c2xz4xf2iv4bznbhu364e7rhvfmhosbak4',\n iconCid: 'bafkreieguco74wkabuyhs4sz6ec35nyywr7ikaw6et4coeezphnsnqqjui',\n enabled: true,\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.volunteering' }],\n },\n {\n id: 'lab_coat',\n title: 'Lab Coat',\n description: 'Scientifically proven attire',\n kind: 'layer',\n category: 'tops',\n channels: ['main'],\n assetCid: 'bafkreiby4g6fgkmi65myv7tdblgb4jvqa3kgnrxvtkxlrtme55unuie4he',\n iconCid: 'bafkreifoddxk5tobgwadib5rxwjn67iind6me3swpvw4t5rbcauc5r4syy',\n enabled: true,\n unlock: [{ kind: 'hasExternalAccount', platforms: ['orcid'] }],\n },\n];\n","import { RPG_ITEMS, type RpgItem, type RpgUnlock } from './catalog.js';\n\n/**\n * Profile signals the unlock rules are checked against. Callers must pass\n * lowercase platform codes (e.g. 'github', 'orcid') and registry app ids (e.g. 'tangled').\n */\nexport interface RpgUnlockSignals {\n collections: readonly string[];\n externalPlatforms: readonly string[];\n activeAppIds: readonly string[];\n hasDoctorate: boolean;\n}\n\n/** One enabled catalog item and whether the signals earn it. */\nexport interface RpgUnlockResult {\n item: RpgItem;\n earned: boolean;\n}\n\nfunction matches(u: RpgUnlock, s: RpgUnlockSignals): boolean {\n switch (u.kind) {\n case 'hasRecord':\n return s.collections.includes(u.collection);\n case 'hasExternalAccount':\n return u.platforms.some((p) => s.externalPlatforms.includes(p));\n case 'usesApp':\n return s.activeAppIds.includes(u.appId);\n case 'hasDoctorate':\n return s.hasDoctorate;\n }\n}\n\n/** Evaluate every enabled item (ANY matching unlock earns it); disabled items are omitted. */\nexport function evaluateRpgUnlocks(\n signals: RpgUnlockSignals,\n items: readonly RpgItem[] = RPG_ITEMS,\n): RpgUnlockResult[] {\n return items\n .filter((item) => item.enabled)\n .map((item) => ({ item, earned: item.unlock.some((u) => matches(u, signals)) }));\n}\n","/** Base URL of rpg.actor. */\nexport const RPG_ACTOR_URL = 'https://rpg.actor';\n\n/**\n * Record key of the `equipment.rpg.give` Sifa writes for an item. rpg.actor\n * stores the accepted `equipment.rpg.item` under the same key, so this also\n * identifies the item in the player's own account.\n *\n * The DID is used as-is: AT Protocol record keys allow `A-Za-z0-9 . - _ : ~`\n * (https://atproto.com/specs/record-key), so the colons in `did:plc:` are\n * valid. The format is already live in written gift records; do not change it.\n */\nexport function rpgGiveRkey(itemId: string, recipientDid: string): string {\n return `sifa-${itemId}-${recipientDid}`;\n}\n\n/** The player's character page on rpg.actor, where pending gifts can be accepted. */\nexport function rpgActorCharacterUrl(handleOrDid: string): string {\n return `${RPG_ACTOR_URL}/${encodeURIComponent(handleOrDid.replace(/^@+/, ''))}`;\n}\n\n/** Deep link that opens the Wear action for an accepted Sifa item on the character page. */\nexport function rpgActorWearUrl(handleOrDid: string, itemId: string, recipientDid: string): string {\n return `${rpgActorCharacterUrl(handleOrDid)}?wear=${encodeURIComponent(rpgGiveRkey(itemId, recipientDid))}`;\n}\n"]}
1
+ {"version":3,"sources":["../../src/rpg/catalog.ts","../../src/rpg/unlocks.ts","../../src/rpg/links.ts"],"names":["z"],"mappings":"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","file":"index.cjs","sourcesContent":["import { z } from 'zod';\n\nexport const RpgUnlockSchema = z.discriminatedUnion('kind', [\n z.object({\n kind: z.literal('hasRecord'),\n collection: z.enum([\n 'id.sifa.profile.position',\n 'id.sifa.profile.volunteering',\n 'id.sifa.profile.presentation',\n 'id.sifa.profile.education',\n ]),\n }),\n z.object({ kind: z.literal('hasExternalAccount'), platforms: z.array(z.string()).min(1) }),\n z.object({ kind: z.literal('usesApp'), appId: z.string().min(1) }),\n z.object({ kind: z.literal('hasDoctorate') }),\n]);\n\n/** CIDv1 (raw codec, sha2-256, base32) as used for rpg.actor image blobs. */\nexport const RPG_CID_REGEX = /^bafkrei[a-z2-7]{52}$/;\n\nexport const RpgItemSchema = z.object({\n /** Lowercase `[a-z0-9_]` only: the id is embedded in an AT Protocol record key. */\n id: z\n .string()\n .min(1)\n .max(50)\n .regex(/^[a-z0-9_]+$/),\n title: z.string().max(100),\n description: z.string().max(500),\n kind: z.enum(['layer', 'held', 'overlay']),\n category: z.string().max(30),\n /** rpg.actor channels the item is offered in (optional). */\n channels: z.array(z.string().min(1)).optional(),\n /** Full-size sprite asset (CIDv1, raw codec, base32). */\n assetCid: z.string().regex(RPG_CID_REGEX),\n /** Inventory icon (CIDv1, raw codec, base32). */\n iconCid: z.string().regex(RPG_CID_REGEX),\n unlock: z.array(RpgUnlockSchema).min(1), // ANY of these unlocks the item\n enabled: z.boolean(),\n});\nexport type RpgItem = z.infer<typeof RpgItemSchema>;\nexport type RpgUnlock = z.infer<typeof RpgUnlockSchema>;\n\n/**\n * The Sifa item catalog for rpg.actor. Ids, titles, descriptions, kinds,\n * categories, channels and image CIDs come from rpg.actor's item cores; the\n * weekly drift check compares them and `pnpm rpg:sync-catalog` adopts\n * rpg.actor's changes. `unlock` and `enabled` are ours.\n */\nexport const RPG_ITEMS: readonly RpgItem[] = [\n {\n id: 'leather_briefcase',\n title: 'Leather Briefcase',\n description: 'A trusty briefcase, for your daily commute',\n kind: 'held',\n category: 'righthand',\n channels: ['main'],\n assetCid: 'bafkreibjtgvetkpzwkliq4o4qnf7atbui6nxgyas3fyk6sjgd5zrl52swa',\n iconCid: 'bafkreifmyglopjtbkiuocmcjcbjhtr34vjumkzbnspbnlsezbdphbkfvu4',\n enabled: true,\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.position' }],\n },\n {\n id: 'sifa_suit',\n title: 'Sifa Suit',\n description: 'Sharp power suit for confirmed professionals',\n kind: 'overlay',\n category: 'costume',\n channels: ['main'],\n assetCid: 'bafkreibortlffvfzkdcuqdpoubczuno7ohnvfwqhefe4h4s36eooe4njgu',\n iconCid: 'bafkreifo7vojbr4i6uvsthj2hbczoqwmrn3ovysrskvxlxw6fvkbswm2ae',\n enabled: false,\n // Reserved for the confirmed tier (a colleague confirms the job and both\n // have a confirmed work email). The unlock kind for that is added once work\n // email confirmation exists; until then the item stays disabled.\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.position' }],\n },\n {\n id: 'speaker_mic',\n title: 'Speaker Mic',\n description: \"A presentation mic, so you can drop it on 'em\",\n kind: 'held',\n category: 'righthand',\n channels: ['main'],\n assetCid: 'bafkreibr4kgeijc5naecf7l4awfv3omph4ngjmjb36qkfnk2sdo6l45ck4',\n iconCid: 'bafkreidnkirbw4nwiro3ocrn5nk66uxf3dhcoy5pojunisvs7so55u3j3q',\n enabled: true,\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.presentation' }],\n },\n {\n id: 'strapped_books',\n title: 'Strapped Books',\n description: 'Knowledge is power, better come strapped',\n kind: 'held',\n category: 'righthand',\n channels: ['main'],\n assetCid: 'bafkreieofltl6427jy3n6dfv63phublalv6nq2hcs36qnyxz33ta6u6yqi',\n iconCid: 'bafkreiavywpv4sgw7gnkzq5gw3nq6yihaucdexpsvgko26q6jq6u6grjpi',\n enabled: true,\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.education' }],\n },\n {\n id: 'dev_hoodie',\n title: 'Dev Hoodie',\n description: 'Snug hoodie for when you enter total code mode',\n kind: 'layer',\n category: 'tops',\n channels: ['main', 'sub1'],\n assetCid: 'bafkreigmrqp5kd6lytqcen2mypuv6dqonrdfbi2ngxhq3uqozk63yi7hoy',\n iconCid: 'bafkreigjwti3w43ta6gy2c75qtdnuvovfohjtv2febbd6hdhpri4tupbem',\n enabled: true,\n unlock: [\n { kind: 'hasExternalAccount', platforms: ['github'] },\n { kind: 'usesApp', appId: 'tangled' },\n ],\n },\n {\n id: 'doctoral_cap',\n title: 'Doctoral Cap',\n description: 'Took so many long years to earn this hat',\n kind: 'layer',\n category: 'headwear',\n channels: ['main'],\n assetCid: 'bafkreic7pvfpb7fjgooiaianrc27mh7fwj5vkicre63l6x5hrzb4w36tcq',\n iconCid: 'bafkreigqt6lmx5crj3w7vc3bvuewarzdreg2doxyeerbxmhljhpeshjewm',\n enabled: true,\n unlock: [{ kind: 'hasDoctorate' }],\n },\n {\n id: 'weekend_shirt',\n title: 'Weekend Shirt',\n description:\n 'Fun shirt to wear when work is all finished.... but you still have a side-project...',\n kind: 'layer',\n category: 'tops',\n channels: ['main', 'sub1', 'sub2'],\n assetCid: 'bafkreigifgpwkxvftiuzbjc6c2xz4xf2iv4bznbhu364e7rhvfmhosbak4',\n iconCid: 'bafkreieguco74wkabuyhs4sz6ec35nyywr7ikaw6et4coeezphnsnqqjui',\n enabled: true,\n unlock: [{ kind: 'hasRecord', collection: 'id.sifa.profile.volunteering' }],\n },\n {\n id: 'lab_coat',\n title: 'Lab Coat',\n description: 'Scientifically proven attire',\n kind: 'layer',\n category: 'tops',\n channels: ['main'],\n assetCid: 'bafkreiby4g6fgkmi65myv7tdblgb4jvqa3kgnrxvtkxlrtme55unuie4he',\n iconCid: 'bafkreifoddxk5tobgwadib5rxwjn67iind6me3swpvw4t5rbcauc5r4syy',\n enabled: true,\n unlock: [{ kind: 'hasExternalAccount', platforms: ['orcid'] }],\n },\n];\n","import { RPG_ITEMS, type RpgItem, type RpgUnlock } from './catalog.js';\n\n/**\n * Profile signals the unlock rules are checked against. Callers must pass\n * lowercase platform codes (e.g. 'github', 'orcid') and registry app ids (e.g. 'tangled').\n */\nexport interface RpgUnlockSignals {\n collections: readonly string[];\n externalPlatforms: readonly string[];\n activeAppIds: readonly string[];\n hasDoctorate: boolean;\n}\n\n/** One enabled catalog item and whether the signals earn it. */\nexport interface RpgUnlockResult {\n item: RpgItem;\n earned: boolean;\n}\n\nfunction matches(u: RpgUnlock, s: RpgUnlockSignals): boolean {\n switch (u.kind) {\n case 'hasRecord':\n return s.collections.includes(u.collection);\n case 'hasExternalAccount':\n return u.platforms.some((p) => s.externalPlatforms.includes(p));\n case 'usesApp':\n return s.activeAppIds.includes(u.appId);\n case 'hasDoctorate':\n return s.hasDoctorate;\n }\n}\n\n/** Evaluate every enabled item (ANY matching unlock earns it); disabled items are omitted. */\nexport function evaluateRpgUnlocks(\n signals: RpgUnlockSignals,\n items: readonly RpgItem[] = RPG_ITEMS,\n): RpgUnlockResult[] {\n return items\n .filter((item) => item.enabled)\n .map((item) => ({ item, earned: item.unlock.some((u) => matches(u, signals)) }));\n}\n","/** Base URL of rpg.actor. */\nexport const RPG_ACTOR_URL = 'https://rpg.actor';\n\n/**\n * Record key of the `equipment.rpg.give` Sifa writes for an item. rpg.actor\n * stores the accepted `equipment.rpg.item` under the same key, so this also\n * identifies the item in the player's own account.\n *\n * The DID is used as-is: AT Protocol record keys allow `A-Za-z0-9 . - _ : ~`\n * (https://atproto.com/specs/record-key), so the colons in `did:plc:` are\n * valid. The format is already live in written gift records; do not change it.\n */\nexport function rpgGiveRkey(itemId: string, recipientDid: string): string {\n return `sifa-${itemId}-${recipientDid}`;\n}\n\n/** The player's character page on rpg.actor, where pending gifts can be accepted. */\nexport function rpgActorCharacterUrl(handleOrDid: string): string {\n return `${RPG_ACTOR_URL}/${encodeURIComponent(handleOrDid.replace(/^@+/, ''))}`;\n}\n\n/** Deep link that opens the Wear action for an accepted Sifa item on the character page. */\nexport function rpgActorWearUrl(handleOrDid: string, itemId: string, recipientDid: string): string {\n return `${rpgActorCharacterUrl(handleOrDid)}?wear=${encodeURIComponent(rpgGiveRkey(itemId, recipientDid))}`;\n}\n"]}
@@ -53,7 +53,9 @@ type RpgItem = z.infer<typeof RpgItemSchema>;
53
53
  type RpgUnlock = z.infer<typeof RpgUnlockSchema>;
54
54
  /**
55
55
  * The Sifa item catalog for rpg.actor. Ids, titles, descriptions, kinds,
56
- * categories, channels and image CIDs come from rpg.actor's item cores.
56
+ * categories, channels and image CIDs come from rpg.actor's item cores; the
57
+ * weekly drift check compares them and `pnpm rpg:sync-catalog` adopts
58
+ * rpg.actor's changes. `unlock` and `enabled` are ours.
57
59
  */
58
60
  declare const RPG_ITEMS: readonly RpgItem[];
59
61
 
@@ -53,7 +53,9 @@ type RpgItem = z.infer<typeof RpgItemSchema>;
53
53
  type RpgUnlock = z.infer<typeof RpgUnlockSchema>;
54
54
  /**
55
55
  * The Sifa item catalog for rpg.actor. Ids, titles, descriptions, kinds,
56
- * categories, channels and image CIDs come from rpg.actor's item cores.
56
+ * categories, channels and image CIDs come from rpg.actor's item cores; the
57
+ * weekly drift check compares them and `pnpm rpg:sync-catalog` adopts
58
+ * rpg.actor's changes. `unlock` and `enabled` are ours.
57
59
  */
58
60
  declare const RPG_ITEMS: readonly RpgItem[];
59
61
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@singi-labs/sifa-sdk",
3
- "version": "0.19.65",
3
+ "version": "0.19.66",
4
4
  "description": "Sifa SDK — public client library for the Sifa AppView on AT Protocol. Shared by sifa-web and sifa-app.",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -208,7 +208,8 @@
208
208
  "release": "pnpm run build && changeset publish",
209
209
  "build:flags": "node scripts/build-flags.mjs",
210
210
  "sync:term-mappings": "node scripts/sync-term-mappings.mjs",
211
- "check:dist": "node scripts/check-dist.mjs"
211
+ "check:dist": "node scripts/check-dist.mjs",
212
+ "rpg:sync-catalog": "pnpm build && node scripts/sync-rpg-catalog.mjs"
212
213
  },
213
214
  "engines": {
214
215
  "node": ">=22"