@kodelyth/tlon 2026.5.39 → 2026.5.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (91) hide show
  1. package/README.md +5 -0
  2. package/api.ts +16 -0
  3. package/channel-plugin-api.ts +1 -0
  4. package/dist/api.js +4 -0
  5. package/dist/channel-Bvzym9ez.js +236 -0
  6. package/dist/channel-plugin-api.js +2 -0
  7. package/dist/channel.runtime-CDY2BdfM.js +3626 -0
  8. package/dist/doctor-contract-Ip6FcHDH.js +7 -0
  9. package/dist/doctor-contract-api.js +2 -0
  10. package/dist/index.js +18 -0
  11. package/dist/runtime-BmSb9A-q.js +8 -0
  12. package/dist/runtime-api-Dq8wkBC_.js +4 -0
  13. package/dist/runtime-api.js +2 -0
  14. package/dist/setup-api.js +3 -0
  15. package/dist/setup-core-CF3ryHqs.js +387 -0
  16. package/dist/setup-entry.js +11 -0
  17. package/dist/setup-surface-BM5_V_XL.js +74 -0
  18. package/dist/test-api.js +2 -0
  19. package/doctor-contract-api.ts +1 -0
  20. package/index.ts +16 -0
  21. package/klaw.plugin.json +3 -203
  22. package/package.json +4 -4
  23. package/runtime-api.ts +17 -0
  24. package/setup-api.ts +2 -0
  25. package/setup-entry.ts +9 -0
  26. package/src/account-fields.ts +31 -0
  27. package/src/channel.message-adapter.test.ts +145 -0
  28. package/src/channel.runtime.ts +259 -0
  29. package/src/channel.ts +192 -0
  30. package/src/config-schema.ts +54 -0
  31. package/src/core.test.ts +298 -0
  32. package/src/doctor-contract.ts +9 -0
  33. package/src/doctor.test.ts +46 -0
  34. package/src/doctor.ts +10 -0
  35. package/src/logger-runtime.ts +1 -0
  36. package/src/monitor/approval-runtime.ts +363 -0
  37. package/src/monitor/approval.test.ts +33 -0
  38. package/src/monitor/approval.ts +283 -0
  39. package/src/monitor/authorization.ts +30 -0
  40. package/src/monitor/cites.ts +54 -0
  41. package/src/monitor/discovery.ts +68 -0
  42. package/src/monitor/history.ts +226 -0
  43. package/src/monitor/index.ts +1523 -0
  44. package/src/monitor/media.test.ts +80 -0
  45. package/src/monitor/media.ts +156 -0
  46. package/src/monitor/processed-messages.test.ts +58 -0
  47. package/src/monitor/processed-messages.ts +89 -0
  48. package/src/monitor/settings-helpers.test.ts +113 -0
  49. package/src/monitor/settings-helpers.ts +158 -0
  50. package/src/monitor/utils.ts +402 -0
  51. package/src/runtime.ts +9 -0
  52. package/src/security.test.ts +658 -0
  53. package/src/session-route.ts +40 -0
  54. package/src/settings.ts +391 -0
  55. package/src/setup-core.ts +231 -0
  56. package/src/setup-surface.ts +99 -0
  57. package/src/targets.ts +102 -0
  58. package/src/tlon-api.test.ts +572 -0
  59. package/src/tlon-api.ts +389 -0
  60. package/src/types.ts +160 -0
  61. package/src/urbit/auth.ssrf.test.ts +45 -0
  62. package/src/urbit/auth.ts +48 -0
  63. package/src/urbit/base-url.test.ts +48 -0
  64. package/src/urbit/base-url.ts +61 -0
  65. package/src/urbit/channel-ops.test.ts +36 -0
  66. package/src/urbit/channel-ops.ts +149 -0
  67. package/src/urbit/context.ts +50 -0
  68. package/src/urbit/errors.ts +51 -0
  69. package/src/urbit/fetch.ts +38 -0
  70. package/src/urbit/foreigns.ts +49 -0
  71. package/src/urbit/send.test.ts +83 -0
  72. package/src/urbit/send.ts +228 -0
  73. package/src/urbit/sse-client.test.ts +234 -0
  74. package/src/urbit/sse-client.ts +492 -0
  75. package/src/urbit/story.ts +332 -0
  76. package/src/urbit/upload.test.ts +155 -0
  77. package/src/urbit/upload.ts +60 -0
  78. package/test-api.ts +1 -0
  79. package/tsconfig.json +16 -0
  80. package/api.js +0 -7
  81. package/bundled-skills/@tloncorp/tlon-skill/SKILL.md +0 -501
  82. package/bundled-skills/@tloncorp/tlon-skill/bin/tlon.js +0 -7
  83. package/bundled-skills/@tloncorp/tlon-skill/package.json +0 -40
  84. package/bundled-skills/@tloncorp/tlon-skill/scripts/postinstall.js +0 -7
  85. package/channel-plugin-api.js +0 -7
  86. package/doctor-contract-api.js +0 -7
  87. package/index.js +0 -7
  88. package/runtime-api.js +0 -7
  89. package/setup-api.js +0 -7
  90. package/setup-entry.js +0 -7
  91. package/test-api.js +0 -7
@@ -0,0 +1,7 @@
1
+ import { createLegacyPrivateNetworkDoctorContract } from "klaw/plugin-sdk/ssrf-runtime";
2
+ //#region extensions/tlon/src/doctor-contract.ts
3
+ const contract = createLegacyPrivateNetworkDoctorContract({ channelKey: "tlon" });
4
+ const legacyConfigRules = contract.legacyConfigRules;
5
+ const normalizeCompatibilityConfig = contract.normalizeCompatibilityConfig;
6
+ //#endregion
7
+ export { normalizeCompatibilityConfig as n, legacyConfigRules as t };
@@ -0,0 +1,2 @@
1
+ import { n as normalizeCompatibilityConfig, t as legacyConfigRules } from "./doctor-contract-Ip6FcHDH.js";
2
+ export { legacyConfigRules, normalizeCompatibilityConfig };
package/dist/index.js ADDED
@@ -0,0 +1,18 @@
1
+ import { defineBundledChannelEntry } from "klaw/plugin-sdk/channel-entry-contract";
2
+ //#region extensions/tlon/index.ts
3
+ var tlon_default = defineBundledChannelEntry({
4
+ id: "tlon",
5
+ name: "Tlon",
6
+ description: "Tlon/Urbit channel plugin",
7
+ importMetaUrl: import.meta.url,
8
+ plugin: {
9
+ specifier: "./channel-plugin-api.js",
10
+ exportName: "tlonPlugin"
11
+ },
12
+ runtime: {
13
+ specifier: "./api.js",
14
+ exportName: "setTlonRuntime"
15
+ }
16
+ });
17
+ //#endregion
18
+ export { tlon_default as default };
@@ -0,0 +1,8 @@
1
+ import { createPluginRuntimeStore } from "klaw/plugin-sdk/runtime-store";
2
+ //#region extensions/tlon/src/runtime.ts
3
+ const { setRuntime: setTlonRuntime, getRuntime: getTlonRuntime } = createPluginRuntimeStore({
4
+ pluginId: "tlon",
5
+ errorMessage: "Tlon runtime not initialized"
6
+ });
7
+ //#endregion
8
+ export { setTlonRuntime as n, getTlonRuntime as t };
@@ -0,0 +1,4 @@
1
+ import { createDedupeCache } from "klaw/plugin-sdk/core";
2
+ import { createLoggerBackedRuntime } from "klaw/plugin-sdk/runtime";
3
+ import { SsrFBlockedError, fetchWithSsrFGuard as fetchWithSsrFGuard$1, isBlockedHostnameOrIp as isBlockedHostnameOrIp$1, ssrfPolicyFromAllowPrivateNetwork, ssrfPolicyFromDangerouslyAllowPrivateNetwork } from "klaw/plugin-sdk/ssrf-runtime";
4
+ export { ssrfPolicyFromAllowPrivateNetwork as a, isBlockedHostnameOrIp$1 as i, createDedupeCache as n, ssrfPolicyFromDangerouslyAllowPrivateNetwork as o, fetchWithSsrFGuard$1 as r, createLoggerBackedRuntime as s, SsrFBlockedError as t };
@@ -0,0 +1,2 @@
1
+ import { a as ssrfPolicyFromAllowPrivateNetwork, i as isBlockedHostnameOrIp, n as createDedupeCache, o as ssrfPolicyFromDangerouslyAllowPrivateNetwork, r as fetchWithSsrFGuard, s as createLoggerBackedRuntime, t as SsrFBlockedError } from "./runtime-api-Dq8wkBC_.js";
2
+ export { SsrFBlockedError, createDedupeCache, createLoggerBackedRuntime, fetchWithSsrFGuard, isBlockedHostnameOrIp, ssrfPolicyFromAllowPrivateNetwork, ssrfPolicyFromDangerouslyAllowPrivateNetwork };
@@ -0,0 +1,3 @@
1
+ import { a as tlonSetupAdapter } from "./setup-core-CF3ryHqs.js";
2
+ import { t as tlonSetupWizard } from "./setup-surface-BM5_V_XL.js";
3
+ export { tlonSetupAdapter, tlonSetupWizard };
@@ -0,0 +1,387 @@
1
+ import { hasLegacyFlatAllowPrivateNetworkAlias, isBlockedHostnameOrIp, isPrivateNetworkOptInEnabled } from "klaw/plugin-sdk/ssrf-runtime";
2
+ import { DEFAULT_ACCOUNT_ID, createSetupInputPresenceValidator, createSetupTranslator, formatDocsLink, normalizeAccountId, patchScopedAccountConfig, prepareScopedSetupConfig } from "klaw/plugin-sdk/setup";
3
+ import { normalizeOptionalString, normalizeStringifiedOptionalString } from "klaw/plugin-sdk/string-coerce-runtime";
4
+ import { DEFAULT_ACCOUNT_ID as DEFAULT_ACCOUNT_ID$1, listCombinedAccountIds, normalizeAccountId as normalizeAccountId$1, resolveMergedAccountConfig } from "klaw/plugin-sdk/account-resolution";
5
+ //#region extensions/tlon/src/targets.ts
6
+ const SHIP_RE = /^~?[a-z-]+$/i;
7
+ const NEST_RE = /^chat\/([^/]+)\/([^/]+)$/i;
8
+ function normalizeShip(raw) {
9
+ const trimmed = raw.trim();
10
+ if (!trimmed) return trimmed;
11
+ return trimmed.startsWith("~") ? trimmed : `~${trimmed}`;
12
+ }
13
+ function parseChannelNest(raw) {
14
+ const match = NEST_RE.exec(raw.trim());
15
+ if (!match) return null;
16
+ return {
17
+ hostShip: normalizeShip(match[1]),
18
+ channelName: match[2]
19
+ };
20
+ }
21
+ function makeGroupTarget(parsed) {
22
+ return {
23
+ kind: "group",
24
+ nest: `chat/${parsed.hostShip}/${parsed.channelName}`,
25
+ hostShip: parsed.hostShip,
26
+ channelName: parsed.channelName
27
+ };
28
+ }
29
+ function parseTlonTarget(raw) {
30
+ const trimmed = raw?.trim();
31
+ if (!trimmed) return null;
32
+ const withoutPrefix = trimmed.replace(/^tlon:/i, "");
33
+ const dmPrefix = withoutPrefix.match(/^dm[/:](.+)$/i);
34
+ if (dmPrefix) return {
35
+ kind: "dm",
36
+ ship: normalizeShip(dmPrefix[1])
37
+ };
38
+ const groupPrefix = withoutPrefix.match(/^(group|room)[/:](.+)$/i);
39
+ if (groupPrefix) {
40
+ const groupTarget = groupPrefix[2].trim();
41
+ if (groupTarget.startsWith("chat/")) {
42
+ const parsed = parseChannelNest(groupTarget);
43
+ if (!parsed) return null;
44
+ return makeGroupTarget(parsed);
45
+ }
46
+ const parts = groupTarget.split("/");
47
+ if (parts.length === 2) {
48
+ const hostShip = normalizeShip(parts[0]);
49
+ const channelName = parts[1];
50
+ return {
51
+ kind: "group",
52
+ nest: `chat/${hostShip}/${channelName}`,
53
+ hostShip,
54
+ channelName
55
+ };
56
+ }
57
+ return null;
58
+ }
59
+ if (withoutPrefix.startsWith("chat/")) {
60
+ const parsed = parseChannelNest(withoutPrefix);
61
+ if (!parsed) return null;
62
+ return makeGroupTarget(parsed);
63
+ }
64
+ if (SHIP_RE.test(withoutPrefix)) return {
65
+ kind: "dm",
66
+ ship: normalizeShip(withoutPrefix)
67
+ };
68
+ return null;
69
+ }
70
+ function resolveTlonOutboundTarget(to) {
71
+ const parsed = parseTlonTarget(to ?? "");
72
+ if (!parsed) return {
73
+ ok: false,
74
+ error: /* @__PURE__ */ new Error(`Invalid Tlon target. Use ${formatTargetHint()}`)
75
+ };
76
+ if (parsed.kind === "dm") return {
77
+ ok: true,
78
+ to: parsed.ship
79
+ };
80
+ return {
81
+ ok: true,
82
+ to: parsed.nest
83
+ };
84
+ }
85
+ function formatTargetHint() {
86
+ return "dm/~sampel-palnet | ~sampel-palnet | chat/~host-ship/channel | group:~host-ship/channel";
87
+ }
88
+ //#endregion
89
+ //#region extensions/tlon/src/account-fields.ts
90
+ function buildTlonAccountFields(input) {
91
+ return {
92
+ ...input.ship ? { ship: input.ship } : {},
93
+ ...input.url ? { url: input.url } : {},
94
+ ...input.code ? { code: input.code } : {},
95
+ ...typeof input.dangerouslyAllowPrivateNetwork === "boolean" ? { network: { dangerouslyAllowPrivateNetwork: input.dangerouslyAllowPrivateNetwork } } : {},
96
+ ...input.groupChannels ? { groupChannels: input.groupChannels } : {},
97
+ ...input.dmAllowlist ? { dmAllowlist: input.dmAllowlist } : {},
98
+ ...typeof input.autoDiscoverChannels === "boolean" ? { autoDiscoverChannels: input.autoDiscoverChannels } : {},
99
+ ...input.ownerShip ? { ownerShip: input.ownerShip } : {}
100
+ };
101
+ }
102
+ //#endregion
103
+ //#region extensions/tlon/src/types.ts
104
+ function resolveTlonChannelConfig(cfg) {
105
+ return cfg.channels?.tlon;
106
+ }
107
+ function resolveMergedTlonAccountConfig(cfg, accountId) {
108
+ const channel = resolveTlonChannelConfig(cfg);
109
+ if (accountId === DEFAULT_ACCOUNT_ID$1) return channel ?? {};
110
+ return resolveMergedAccountConfig({
111
+ channelConfig: channel ?? {},
112
+ accounts: channel?.accounts,
113
+ accountId,
114
+ normalizeAccountId: normalizeAccountId$1
115
+ });
116
+ }
117
+ function resolveTlonAccount(cfg, accountId) {
118
+ const resolvedAccountId = normalizeAccountId$1(accountId);
119
+ if (!resolveTlonChannelConfig(cfg)) return {
120
+ accountId: resolvedAccountId,
121
+ name: null,
122
+ enabled: false,
123
+ configured: false,
124
+ ship: null,
125
+ url: null,
126
+ code: null,
127
+ dangerouslyAllowPrivateNetwork: null,
128
+ groupChannels: [],
129
+ dmAllowlist: [],
130
+ groupInviteAllowlist: [],
131
+ autoDiscoverChannels: null,
132
+ showModelSignature: null,
133
+ autoAcceptDmInvites: null,
134
+ autoAcceptGroupInvites: null,
135
+ defaultAuthorizedShips: [],
136
+ ownerShip: null
137
+ };
138
+ const merged = resolveMergedTlonAccountConfig(cfg, resolvedAccountId);
139
+ const ship = merged.ship ?? null;
140
+ const url = merged.url ?? null;
141
+ const code = merged.code ?? null;
142
+ const dangerouslyAllowPrivateNetwork = isPrivateNetworkOptInEnabled(merged) ? true : typeof merged.network?.dangerouslyAllowPrivateNetwork === "boolean" ? merged.network.dangerouslyAllowPrivateNetwork : hasLegacyFlatAllowPrivateNetworkAlias(merged) && typeof merged.allowPrivateNetwork === "boolean" ? merged.allowPrivateNetwork : null;
143
+ const groupChannels = merged.groupChannels ?? [];
144
+ const dmAllowlist = merged.dmAllowlist ?? [];
145
+ const groupInviteAllowlist = merged.groupInviteAllowlist ?? [];
146
+ const autoDiscoverChannels = merged.autoDiscoverChannels ?? null;
147
+ const showModelSignature = merged.showModelSignature ?? null;
148
+ const autoAcceptDmInvites = merged.autoAcceptDmInvites ?? null;
149
+ const autoAcceptGroupInvites = merged.autoAcceptGroupInvites ?? null;
150
+ const ownerShip = merged.ownerShip ?? null;
151
+ const defaultAuthorizedShips = merged.defaultAuthorizedShips ?? [];
152
+ const configured = Boolean(ship && url && code);
153
+ return {
154
+ accountId: resolvedAccountId,
155
+ name: merged.name ?? null,
156
+ enabled: merged.enabled !== false,
157
+ configured,
158
+ ship,
159
+ url,
160
+ code,
161
+ dangerouslyAllowPrivateNetwork,
162
+ groupChannels,
163
+ dmAllowlist,
164
+ groupInviteAllowlist,
165
+ autoDiscoverChannels,
166
+ showModelSignature,
167
+ autoAcceptDmInvites,
168
+ autoAcceptGroupInvites,
169
+ defaultAuthorizedShips,
170
+ ownerShip
171
+ };
172
+ }
173
+ function listTlonAccountIds(cfg) {
174
+ const base = resolveTlonChannelConfig(cfg);
175
+ if (!base) return [];
176
+ return listCombinedAccountIds({
177
+ configuredAccountIds: Object.keys(base.accounts ?? {}).map(normalizeAccountId$1),
178
+ implicitAccountId: base.ship ? DEFAULT_ACCOUNT_ID$1 : void 0
179
+ });
180
+ }
181
+ //#endregion
182
+ //#region extensions/tlon/src/urbit/base-url.ts
183
+ function hasScheme(value) {
184
+ return /^[a-zA-Z][a-zA-Z0-9+.-]*:\/\//.test(value);
185
+ }
186
+ function normalizeUrbitHostname(hostname) {
187
+ return (hostname ?? "").trim().toLowerCase().replace(/\.$/, "");
188
+ }
189
+ function validateUrbitBaseUrl(raw) {
190
+ const trimmed = raw.trim();
191
+ if (!trimmed) return {
192
+ ok: false,
193
+ error: "Required"
194
+ };
195
+ const candidate = hasScheme(trimmed) ? trimmed : `https://${trimmed}`;
196
+ let parsed;
197
+ try {
198
+ parsed = new URL(candidate);
199
+ } catch {
200
+ return {
201
+ ok: false,
202
+ error: "Invalid URL"
203
+ };
204
+ }
205
+ if (!["http:", "https:"].includes(parsed.protocol)) return {
206
+ ok: false,
207
+ error: "URL must use http:// or https://"
208
+ };
209
+ if (parsed.username || parsed.password) return {
210
+ ok: false,
211
+ error: "URL must not include credentials"
212
+ };
213
+ const hostname = normalizeUrbitHostname(parsed.hostname);
214
+ if (!hostname) return {
215
+ ok: false,
216
+ error: "Invalid hostname"
217
+ };
218
+ const isIpv6 = hostname.includes(":");
219
+ const host = parsed.port ? `${isIpv6 ? `[${hostname}]` : hostname}:${parsed.port}` : isIpv6 ? `[${hostname}]` : hostname;
220
+ return {
221
+ ok: true,
222
+ baseUrl: `${parsed.protocol}//${host}`,
223
+ hostname
224
+ };
225
+ }
226
+ function isBlockedUrbitHostname(hostname) {
227
+ const normalized = normalizeUrbitHostname(hostname);
228
+ if (!normalized) return false;
229
+ return isBlockedHostnameOrIp(normalized);
230
+ }
231
+ //#endregion
232
+ //#region extensions/tlon/src/setup-core.ts
233
+ const t = createSetupTranslator();
234
+ function tlonChannelId() {
235
+ return "tlon";
236
+ }
237
+ function isConfigured(account) {
238
+ return Boolean(account.ship && account.url && account.code);
239
+ }
240
+ function createTlonSetupWizardBase(params) {
241
+ return {
242
+ channel: tlonChannelId(),
243
+ status: {
244
+ configuredLabel: t("wizard.channels.statusConfigured"),
245
+ unconfiguredLabel: t("wizard.channels.statusNeedsSetup"),
246
+ configuredHint: t("wizard.channels.statusConfigured"),
247
+ unconfiguredHint: t("wizard.channels.statusUrbitMessenger"),
248
+ configuredScore: 1,
249
+ unconfiguredScore: 4,
250
+ resolveConfigured: ({ cfg, accountId }) => params.resolveConfigured({
251
+ cfg,
252
+ accountId
253
+ }),
254
+ resolveStatusLines: ({ cfg, accountId, configured }) => params.resolveStatusLines?.({
255
+ cfg,
256
+ accountId,
257
+ configured
258
+ }) ?? []
259
+ },
260
+ introNote: {
261
+ title: t("wizard.tlon.setupTitle"),
262
+ lines: [
263
+ t("wizard.tlon.helpNeedsUrlCode"),
264
+ t("wizard.tlon.helpExampleUrl"),
265
+ t("wizard.tlon.helpExampleShip"),
266
+ t("wizard.tlon.helpPrivateNetwork"),
267
+ `Docs: ${formatDocsLink("/channels/tlon", "channels/tlon")}`
268
+ ]
269
+ },
270
+ credentials: [],
271
+ textInputs: [
272
+ {
273
+ inputKey: "ship",
274
+ message: t("wizard.tlon.shipPrompt"),
275
+ placeholder: "~sampel-palnet",
276
+ currentValue: ({ cfg, accountId }) => resolveTlonAccount(cfg, accountId).ship ?? void 0,
277
+ validate: ({ value }) => normalizeStringifiedOptionalString(value) ? void 0 : "Required",
278
+ normalizeValue: ({ value }) => normalizeShip(normalizeStringifiedOptionalString(value) ?? ""),
279
+ applySet: async ({ cfg, accountId, value }) => applyTlonSetupConfig({
280
+ cfg,
281
+ accountId,
282
+ input: { ship: value }
283
+ })
284
+ },
285
+ {
286
+ inputKey: "url",
287
+ message: t("wizard.tlon.shipUrlPrompt"),
288
+ placeholder: "https://your-ship-host",
289
+ currentValue: ({ cfg, accountId }) => resolveTlonAccount(cfg, accountId).url ?? void 0,
290
+ validate: ({ value }) => {
291
+ const next = validateUrbitBaseUrl(value ?? "");
292
+ if (!next.ok) return next.error;
293
+ },
294
+ normalizeValue: ({ value }) => normalizeStringifiedOptionalString(value) ?? "",
295
+ applySet: async ({ cfg, accountId, value }) => applyTlonSetupConfig({
296
+ cfg,
297
+ accountId,
298
+ input: { url: value }
299
+ })
300
+ },
301
+ {
302
+ inputKey: "code",
303
+ message: t("wizard.tlon.loginCodePrompt"),
304
+ placeholder: "lidlut-tabwed-pillex-ridrup",
305
+ currentValue: ({ cfg, accountId }) => resolveTlonAccount(cfg, accountId).code ?? void 0,
306
+ validate: ({ value }) => normalizeStringifiedOptionalString(value) ? void 0 : "Required",
307
+ normalizeValue: ({ value }) => normalizeStringifiedOptionalString(value) ?? "",
308
+ applySet: async ({ cfg, accountId, value }) => applyTlonSetupConfig({
309
+ cfg,
310
+ accountId,
311
+ input: { code: value }
312
+ })
313
+ }
314
+ ],
315
+ finalize: params.finalize
316
+ };
317
+ }
318
+ async function resolveTlonSetupConfigured(cfg, accountId) {
319
+ if (accountId) return isConfigured(resolveTlonAccount(cfg, accountId));
320
+ const accountIds = listTlonAccountIds(cfg);
321
+ return accountIds.length > 0 ? accountIds.some((resolvedAccountId) => isConfigured(resolveTlonAccount(cfg, resolvedAccountId))) : isConfigured(resolveTlonAccount(cfg, DEFAULT_ACCOUNT_ID));
322
+ }
323
+ async function resolveTlonSetupStatusLines(cfg, accountId) {
324
+ const configured = await resolveTlonSetupConfigured(cfg, accountId);
325
+ return [`${accountId && accountId !== DEFAULT_ACCOUNT_ID ? `Tlon (${accountId})` : "Tlon"}: ${configured ? "configured" : "needs setup"}`];
326
+ }
327
+ function applyTlonSetupConfig(params) {
328
+ const { cfg, accountId, input } = params;
329
+ const useDefault = accountId === DEFAULT_ACCOUNT_ID;
330
+ const namedConfig = prepareScopedSetupConfig({
331
+ cfg,
332
+ channelKey: tlonChannelId(),
333
+ accountId,
334
+ name: input.name
335
+ });
336
+ const base = namedConfig.channels?.tlon ?? {};
337
+ const payload = buildTlonAccountFields(input);
338
+ if (useDefault) return {
339
+ ...namedConfig,
340
+ channels: {
341
+ ...namedConfig.channels,
342
+ tlon: {
343
+ ...base,
344
+ enabled: true,
345
+ ...payload
346
+ }
347
+ }
348
+ };
349
+ return patchScopedAccountConfig({
350
+ cfg: namedConfig,
351
+ channelKey: tlonChannelId(),
352
+ accountId,
353
+ patch: { enabled: base.enabled ?? true },
354
+ accountPatch: {
355
+ enabled: true,
356
+ ...payload
357
+ },
358
+ ensureChannelEnabled: false,
359
+ ensureAccountEnabled: false
360
+ });
361
+ }
362
+ const tlonSetupAdapter = {
363
+ resolveAccountId: ({ accountId }) => normalizeAccountId(accountId),
364
+ applyAccountName: ({ cfg, accountId, name }) => prepareScopedSetupConfig({
365
+ cfg,
366
+ channelKey: tlonChannelId(),
367
+ accountId,
368
+ name
369
+ }),
370
+ validateInput: createSetupInputPresenceValidator({ validate: ({ cfg, accountId, input }) => {
371
+ const resolved = resolveTlonAccount(cfg, accountId ?? void 0);
372
+ const ship = normalizeOptionalString(input.ship) || resolved.ship;
373
+ const url = normalizeOptionalString(input.url) || resolved.url;
374
+ const code = normalizeOptionalString(input.code) || resolved.code;
375
+ if (!ship) return "Tlon requires --ship.";
376
+ if (!url) return "Tlon requires --url.";
377
+ if (!code) return "Tlon requires --code.";
378
+ return null;
379
+ } }),
380
+ applyAccountConfig: ({ cfg, accountId, input }) => applyTlonSetupConfig({
381
+ cfg,
382
+ accountId,
383
+ input
384
+ })
385
+ };
386
+ //#endregion
387
+ export { tlonSetupAdapter as a, validateUrbitBaseUrl as c, formatTargetHint as d, normalizeShip as f, resolveTlonOutboundTarget as h, resolveTlonSetupStatusLines as i, listTlonAccountIds as l, parseTlonTarget as m, createTlonSetupWizardBase as n, isBlockedUrbitHostname as o, parseChannelNest as p, resolveTlonSetupConfigured as r, normalizeUrbitHostname as s, applyTlonSetupConfig as t, resolveTlonAccount as u };
@@ -0,0 +1,11 @@
1
+ import { defineBundledChannelSetupEntry } from "klaw/plugin-sdk/channel-entry-contract";
2
+ //#region extensions/tlon/setup-entry.ts
3
+ var setup_entry_default = defineBundledChannelSetupEntry({
4
+ importMetaUrl: import.meta.url,
5
+ plugin: {
6
+ specifier: "./api.js",
7
+ exportName: "tlonPlugin"
8
+ }
9
+ });
10
+ //#endregion
11
+ export { setup_entry_default as default };
@@ -0,0 +1,74 @@
1
+ import { c as validateUrbitBaseUrl, f as normalizeShip, i as resolveTlonSetupStatusLines, n as createTlonSetupWizardBase, o as isBlockedUrbitHostname, r as resolveTlonSetupConfigured, t as applyTlonSetupConfig, u as resolveTlonAccount } from "./setup-core-CF3ryHqs.js";
2
+ import { createSetupTranslator } from "klaw/plugin-sdk/setup-runtime";
3
+ //#region extensions/tlon/src/setup-surface.ts
4
+ const t = createSetupTranslator();
5
+ function parseList(value) {
6
+ return value.split(/[\n,;]+/g).map((entry) => entry.trim()).filter(Boolean);
7
+ }
8
+ const tlonSetupWizard = createTlonSetupWizardBase({
9
+ resolveConfigured: async ({ cfg, accountId }) => await resolveTlonSetupConfigured(cfg, accountId),
10
+ resolveStatusLines: async ({ cfg, accountId }) => await resolveTlonSetupStatusLines(cfg, accountId),
11
+ finalize: async ({ cfg, accountId, prompter }) => {
12
+ let next = cfg;
13
+ const resolved = resolveTlonAccount(next, accountId);
14
+ const validatedUrl = validateUrbitBaseUrl(resolved.url ?? "");
15
+ if (!validatedUrl.ok) throw new Error(`Invalid URL: ${validatedUrl.error}`);
16
+ let dangerouslyAllowPrivateNetwork = resolved.dangerouslyAllowPrivateNetwork ?? false;
17
+ if (isBlockedUrbitHostname(validatedUrl.hostname)) {
18
+ dangerouslyAllowPrivateNetwork = await prompter.confirm({
19
+ message: t("wizard.tlon.privateNetworkPrompt"),
20
+ initialValue: dangerouslyAllowPrivateNetwork
21
+ });
22
+ if (!dangerouslyAllowPrivateNetwork) throw new Error("Refusing private/internal ship URL without explicit network opt-in");
23
+ }
24
+ next = applyTlonSetupConfig({
25
+ cfg: next,
26
+ accountId,
27
+ input: { dangerouslyAllowPrivateNetwork }
28
+ });
29
+ const currentGroups = resolved.groupChannels;
30
+ if (await prompter.confirm({
31
+ message: t("wizard.tlon.addGroupsPrompt"),
32
+ initialValue: currentGroups.length > 0
33
+ })) {
34
+ const entry = await prompter.text({
35
+ message: t("wizard.tlon.groupChannelsPrompt"),
36
+ placeholder: "chat/~host-ship/general, chat/~host-ship/support",
37
+ initialValue: currentGroups.join(", ") || void 0
38
+ });
39
+ next = applyTlonSetupConfig({
40
+ cfg: next,
41
+ accountId,
42
+ input: { groupChannels: parseList(entry ?? "") }
43
+ });
44
+ }
45
+ const currentAllowlist = resolved.dmAllowlist;
46
+ if (await prompter.confirm({
47
+ message: t("wizard.tlon.restrictDmsPrompt"),
48
+ initialValue: currentAllowlist.length > 0
49
+ })) {
50
+ const entry = await prompter.text({
51
+ message: t("wizard.tlon.dmAllowlistPrompt"),
52
+ placeholder: "~zod, ~nec",
53
+ initialValue: currentAllowlist.join(", ") || void 0
54
+ });
55
+ next = applyTlonSetupConfig({
56
+ cfg: next,
57
+ accountId,
58
+ input: { dmAllowlist: parseList(entry ?? "").map((ship) => normalizeShip(ship)) }
59
+ });
60
+ }
61
+ const autoDiscoverChannels = await prompter.confirm({
62
+ message: t("wizard.tlon.autoDiscoveryPrompt"),
63
+ initialValue: resolved.autoDiscoverChannels ?? true
64
+ });
65
+ next = applyTlonSetupConfig({
66
+ cfg: next,
67
+ accountId,
68
+ input: { autoDiscoverChannels }
69
+ });
70
+ return { cfg: next };
71
+ }
72
+ });
73
+ //#endregion
74
+ export { tlonSetupWizard as t };
@@ -0,0 +1,2 @@
1
+ import { t as tlonPlugin } from "./channel-Bvzym9ez.js";
2
+ export { tlonPlugin };
@@ -0,0 +1 @@
1
+ export { normalizeCompatibilityConfig, legacyConfigRules } from "./src/doctor-contract.js";
package/index.ts ADDED
@@ -0,0 +1,16 @@
1
+ import { defineBundledChannelEntry } from "klaw/plugin-sdk/channel-entry-contract";
2
+
3
+ export default defineBundledChannelEntry({
4
+ id: "tlon",
5
+ name: "Tlon",
6
+ description: "Tlon/Urbit channel plugin",
7
+ importMetaUrl: import.meta.url,
8
+ plugin: {
9
+ specifier: "./channel-plugin-api.js",
10
+ exportName: "tlonPlugin",
11
+ },
12
+ runtime: {
13
+ specifier: "./api.js",
14
+ exportName: "setTlonRuntime",
15
+ },
16
+ });