@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 +1 -1
- package/dist/index.js +1 -1
- package/dist/rpg/catalog-drift.js +62 -15
- package/dist/rpg/catalog-drift.js.map +1 -1
- package/dist/rpg/catalog-sync.js +83 -0
- package/dist/rpg/catalog-sync.js.map +1 -0
- package/dist/rpg/catalog.js +1 -1
- package/dist/rpg/catalog.js.map +1 -1
- package/dist/rpg/index.cjs +1 -1
- package/dist/rpg/index.cjs.map +1 -1
- package/dist/rpg/index.d.cts +3 -1
- package/dist/rpg/index.d.ts +3 -1
- package/package.json +3 -2
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.
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
|
15
|
-
if (item[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]
|
|
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 {
|
|
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
|
|
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
|
-
|
|
41
|
-
for (const m of result.mismatches)
|
|
42
|
-
|
|
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 *
|
|
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":[]}
|
package/dist/rpg/catalog.js
CHANGED
|
@@ -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"],
|
package/dist/rpg/catalog.js.map
CHANGED
|
@@ -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
|
|
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":[]}
|
package/dist/rpg/index.cjs
CHANGED
|
@@ -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"],
|
package/dist/rpg/index.cjs.map
CHANGED
|
@@ -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"]}
|
package/dist/rpg/index.d.cts
CHANGED
|
@@ -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/dist/rpg/index.d.ts
CHANGED
|
@@ -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.
|
|
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"
|