tands 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -13,6 +13,8 @@ npx tands dev # a local workspace for it, on http://localho
13
13
  - **`tands check [manifest]`** checks a manifest (a file, or its address) as a workspace would.
14
14
  - **`tands dev [manifest address]`** stands in for a workspace: your tab as someone you choose,
15
15
  real tokens signed with its own keys, your data in memory, your events and your actions.
16
+ - **`tands publish [--changes "…"]`** sends a new version of your listed app to the marketplace
17
+ for review, with a publishing key from its workspace (`TANDS_URL`, `TANDS_PUBLISH_KEY`).
16
18
 
17
19
  For your own build, the SDK is [`@tabs-and-spaces/sdk`](https://www.npmjs.com/package/@tabs-and-spaces/sdk).
18
20
 
@@ -1,3 +1,6 @@
1
+ // ../sdk/src/protocol.ts
2
+ var isHome = (context) => Boolean(context.home);
3
+
1
4
  // ../sdk/src/server.ts
2
5
  var TokenError = class extends Error {
3
6
  };
@@ -121,6 +124,20 @@ function dataClient({ dataKey, dataUrl }, fetcher = fetch) {
121
124
  }
122
125
  };
123
126
  }
127
+ async function reportEvent({ key, eventsUrl }, name, fields = {}, fetcher = fetch) {
128
+ const response = await fetcher(eventsUrl, {
129
+ method: "POST",
130
+ headers: { "content-type": "application/json", authorization: `Bearer ${key}` },
131
+ body: JSON.stringify({ name, data: fields })
132
+ });
133
+ if (!response.ok) {
134
+ const body = await response.json().catch(() => null);
135
+ throw new DataError(
136
+ body?.error?.message ?? `It answered ${response.status}.`,
137
+ body?.error?.code ?? null
138
+ );
139
+ }
140
+ }
124
141
  var DataError = class extends Error {
125
142
  constructor(message, code) {
126
143
  super(message);
@@ -133,5 +150,7 @@ export {
133
150
  TokenError,
134
151
  dataClient,
135
152
  fetchKeys,
153
+ isHome,
154
+ reportEvent,
136
155
  verifyToken
137
156
  };
@@ -1,5 +1,6 @@
1
1
  // ../sdk/src/protocol.ts
2
2
  var PROTOCOL_VERSION = 1;
3
+ var isHome = (context) => Boolean(context.home);
3
4
  var isRecord = (value) => typeof value === "object" && value !== null;
4
5
  function readAppMessage(data) {
5
6
  if (!isRecord(data) || data.tands !== PROTOCOL_VERSION) return null;
@@ -10,6 +11,27 @@ function readAppMessage(data) {
10
11
  if (data.type === "data" && typeof data.id === "string" && data.id.length <= 64 && isRecord(data.request) && typeof data.request.op === "string") {
11
12
  return { tands: 1, type: "data", id: data.id, request: data.request };
12
13
  }
14
+ if (data.type === "eventKey" && typeof data.id === "string" && data.id.length <= 64) {
15
+ return { tands: 1, type: "eventKey", id: data.id };
16
+ }
17
+ if (data.type === "openTrigger" && typeof data.id === "string" && data.id.length <= 64 && (data.event === void 0 || data.event === null || typeof data.event === "string" && data.event.length <= 40)) {
18
+ return {
19
+ tands: 1,
20
+ type: "openTrigger",
21
+ id: data.id,
22
+ event: typeof data.event === "string" ? data.event : null
23
+ };
24
+ }
25
+ const id = (value) => typeof value === "string" && value.length > 0 && value.length <= 64;
26
+ if (data.type === "open" && id(data.id) && id(data.space) && (data.tab === void 0 || data.tab === null || id(data.tab))) {
27
+ return {
28
+ tands: 1,
29
+ type: "open",
30
+ id: data.id,
31
+ space: data.space,
32
+ tab: typeof data.tab === "string" ? data.tab : null
33
+ };
34
+ }
13
35
  if (data.type === "event" && typeof data.id === "string" && data.id.length <= 64 && typeof data.name === "string" && data.name.length <= 40 && (data.data === void 0 || isRecord(data.data))) {
14
36
  return {
15
37
  tands: 1,
@@ -155,6 +177,19 @@ function connect({ timeoutMs = 1e4, window: win = window } = {}) {
155
177
  async emit(name, fields = {}) {
156
178
  await ask((id) => ({ tands: 1, type: "event", id, name, data: fields }));
157
179
  },
180
+ requestEventKey: () => ask((id) => ({ tands: 1, type: "eventKey", id })),
181
+ async open(target) {
182
+ await ask((id) => ({
183
+ tands: 1,
184
+ type: "open",
185
+ id,
186
+ space: target.space,
187
+ tab: target.tab ?? null
188
+ }));
189
+ },
190
+ async openTrigger(event) {
191
+ await ask((id) => ({ tands: 1, type: "openTrigger", id, event: event ?? null }));
192
+ },
158
193
  values(scope) {
159
194
  return {
160
195
  get: async (key) => (await data({ op: "values.get", scope, key })).value,
@@ -198,6 +233,7 @@ export {
198
233
  PROTOCOL_VERSION,
199
234
  WorkspaceError,
200
235
  connect,
236
+ isHome,
201
237
  readAppMessage,
202
238
  readHostMessage
203
239
  };
package/dist/tands.js CHANGED
@@ -19676,9 +19676,101 @@ function date4(params) {
19676
19676
  return _coercedDate(ZodDate, params);
19677
19677
  }
19678
19678
 
19679
+ // ../types/src/time.ts
19680
+ function isTimeZone(value) {
19681
+ if (!value || value.length > 64) return false;
19682
+ try {
19683
+ new Intl.DateTimeFormat("en", { timeZone: value });
19684
+ return true;
19685
+ } catch {
19686
+ return false;
19687
+ }
19688
+ }
19689
+ var TimeZoneSchema = external_exports.string().refine(isTimeZone, "Not a known time zone.");
19690
+ var DEFAULT_TIME_ZONE = "UTC";
19691
+
19692
+ // ../types/src/recurrence.ts
19693
+ var WeekdaySchema = external_exports.number().int().min(0).max(6);
19694
+ var RecurrenceSchema = external_exports.discriminatedUnion("freq", [
19695
+ external_exports.object({ freq: external_exports.literal("daily"), interval: external_exports.number().int().min(1).max(365) }),
19696
+ external_exports.object({
19697
+ freq: external_exports.literal("weekly"),
19698
+ interval: external_exports.number().int().min(1).max(52),
19699
+ /** The days of the week it happens on (Monday to Friday: every weekday). */
19700
+ weekdays: external_exports.array(WeekdaySchema).min(1).max(7)
19701
+ }),
19702
+ external_exports.object({
19703
+ freq: external_exports.literal("monthly"),
19704
+ interval: external_exports.number().int().min(1).max(12),
19705
+ on: external_exports.discriminatedUnion("kind", [
19706
+ /** A day of the month; in shorter months, their last day. */
19707
+ external_exports.object({ kind: external_exports.literal("day"), day: external_exports.number().int().min(1).max(31) }),
19708
+ /** The first … fourth, or last (-1), such weekday of the month. */
19709
+ external_exports.object({
19710
+ kind: external_exports.literal("weekday"),
19711
+ week: external_exports.union([external_exports.literal(1), external_exports.literal(2), external_exports.literal(3), external_exports.literal(4), external_exports.literal(-1)]),
19712
+ weekday: WeekdaySchema
19713
+ })
19714
+ ])
19715
+ })
19716
+ ]);
19717
+ var IsoDateSchema = external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/, "A date like 2026-10-16.");
19718
+ var ClockTimeSchema = external_exports.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "A time like 17:00.");
19719
+ var DAY_MS = 24 * 60 * 6e4;
19720
+
19721
+ // ../types/src/notifications.ts
19722
+ var NOTIFICATION_KINDS = ["mention", "dm", "approval", "thread"];
19723
+ var NotificationKindSchema = external_exports.enum(NOTIFICATION_KINDS);
19724
+ var QuietHoursSchema = external_exports.object({
19725
+ on: external_exports.boolean().default(false),
19726
+ from: ClockTimeSchema.default("22:00"),
19727
+ to: ClockTimeSchema.default("08:00"),
19728
+ days: external_exports.array(external_exports.number().int().min(0).max(6)).max(7).default([0, 1, 2, 3, 4, 5, 6])
19729
+ });
19730
+ var NotificationSettingsSchema = external_exports.object({
19731
+ /** A gentle chime (and the Inbox icon's pulse) when something new arrives. */
19732
+ sound: external_exports.boolean().default(true),
19733
+ /** Which kinds chime and pop up (threads you follow: quiet, unless you add them). */
19734
+ alert: external_exports.array(NotificationKindSchema).default(["mention", "dm", "approval"]),
19735
+ /** Do not disturb (decision 194): quiet hours, each week. */
19736
+ quietHours: QuietHoursSchema.default(QuietHoursSchema.parse({})),
19737
+ /** Paused until then (a quick "for an hour"); null: not paused. */
19738
+ pausedUntil: external_exports.iso.datetime().nullable().default(null),
19739
+ /** What still gets through while you're quiet (a DM, a request for your yes). */
19740
+ allowWhenQuiet: external_exports.array(NotificationKindSchema).default([]),
19741
+ /**
19742
+ * Away from the app (decision 195): what's emailed when it's still unread a
19743
+ * little later (grouped into one email), and what's sent to your linked chats.
19744
+ */
19745
+ email: external_exports.array(NotificationKindSchema).default(["mention", "dm", "approval"]),
19746
+ chat: external_exports.array(NotificationKindSchema).default(["mention", "dm", "approval"]),
19747
+ /** A short email when something has waited unread for hours (at most one a day). */
19748
+ reminder: external_exports.boolean().default(true)
19749
+ });
19750
+ var DEFAULT_NOTIFICATION_SETTINGS = NotificationSettingsSchema.parse(
19751
+ {}
19752
+ );
19753
+ var UpdateNotificationSettingsSchema = external_exports.object({
19754
+ sound: external_exports.boolean(),
19755
+ alert: external_exports.array(NotificationKindSchema).max(NOTIFICATION_KINDS.length),
19756
+ quietHours: QuietHoursSchema,
19757
+ pausedUntil: external_exports.iso.datetime().nullable(),
19758
+ allowWhenQuiet: external_exports.array(NotificationKindSchema).max(NOTIFICATION_KINDS.length),
19759
+ email: external_exports.array(NotificationKindSchema).max(NOTIFICATION_KINDS.length),
19760
+ chat: external_exports.array(NotificationKindSchema).max(NOTIFICATION_KINDS.length),
19761
+ reminder: external_exports.boolean()
19762
+ }).partial().strict();
19763
+ var NotificationSettingsResponseSchema = external_exports.object({
19764
+ settings: NotificationSettingsSchema
19765
+ });
19766
+ var SPACE_NOTIFY_LEVELS = ["all", "mentions", "muted"];
19767
+ var SpaceNotifySchema = external_exports.enum(SPACE_NOTIFY_LEVELS);
19768
+ var SetSpaceNotifyRequestSchema = external_exports.object({ level: SpaceNotifySchema });
19769
+
19679
19770
  // ../types/src/apps.ts
19680
19771
  var APP_MANIFEST_VERSION = 1;
19681
19772
  var MAX_APP_TABS = 10;
19773
+ var MAX_APP_HOME_PAGES = 3;
19682
19774
  var APP_TOKEN_SECONDS = 600;
19683
19775
  var APP_DATA_LIMIT_BYTES = 100 * 1024 * 1024;
19684
19776
  var APP_RECORD_MAX_BYTES = 32 * 1024;
@@ -19751,6 +19843,18 @@ var AppManifestSchema = external_exports.object({
19751
19843
  url: external_exports.string().min(1).max(500)
19752
19844
  })
19753
19845
  ).min(1, "An app needs at least one tab.").max(MAX_APP_TABS),
19846
+ /**
19847
+ * Home pages it offers (decision 177): an owner or admin may choose one as the
19848
+ * workspace's Home. Shown like a tab, without a space around it.
19849
+ */
19850
+ home: external_exports.array(
19851
+ external_exports.object({
19852
+ id: AppTabIdSchema,
19853
+ name: external_exports.string().trim().min(1).max(40),
19854
+ description: external_exports.string().trim().max(200).default(""),
19855
+ url: external_exports.string().min(1).max(500)
19856
+ })
19857
+ ).max(MAX_APP_HOME_PAGES).optional(),
19754
19858
  /** The data it keeps in the workspace (M9 slice 2). */
19755
19859
  data: external_exports.object({
19756
19860
  collections: external_exports.record(DataNameSchema, AppCollectionSchema).refine(
@@ -19761,8 +19865,15 @@ var AppManifestSchema = external_exports.object({
19761
19865
  /** What it does when agents ask (M9 slice 3). */
19762
19866
  actions: external_exports.record(DataNameSchema, AppActionSchema).refine((actions) => Object.keys(actions).length <= 20, "An app has at most 20 actions.").optional(),
19763
19867
  /** What happens in it, for triggers to listen for (M9 slice 5). */
19764
- events: external_exports.record(DataNameSchema, AppEventSchema).refine((events) => Object.keys(events).length <= 20, "An app has at most 20 events.").optional()
19868
+ events: external_exports.record(DataNameSchema, AppEventSchema).refine((events) => Object.keys(events).length <= 20, "An app has at most 20 events.").optional(),
19869
+ /**
19870
+ * Publishing it to the marketplace (decision 203): `verify` is the token of
19871
+ * the workspace that may publish it, shown on its Publish page. Only whoever
19872
+ * controls the manifest's address can put it there.
19873
+ */
19874
+ marketplace: external_exports.object({ verify: external_exports.string().trim().max(100) }).optional()
19765
19875
  });
19876
+ var ManifestHashSchema = external_exports.string().regex(/^[0-9a-f]{64}$/, "A manifest's hash.");
19766
19877
  var AppActionInfoSchema = external_exports.object({
19767
19878
  name: external_exports.string(),
19768
19879
  description: external_exports.string(),
@@ -19823,6 +19934,8 @@ var AppSchema = external_exports.object({
19823
19934
  origin: external_exports.string(),
19824
19935
  manifestUrl: external_exports.string(),
19825
19936
  tabs: external_exports.array(AppTabSchema),
19937
+ /** Home pages it offers (decision 177). */
19938
+ homePages: external_exports.array(AppTabSchema).default([]),
19826
19939
  /** The data it keeps (M9 slice 2). */
19827
19940
  collections: external_exports.array(AppCollectionInfoSchema),
19828
19941
  /** What it does when agents ask (M9 slice 3). */
@@ -19834,7 +19947,14 @@ var AppSchema = external_exports.object({
19834
19947
  dataLimitBytes: external_exports.number(),
19835
19948
  installedBy: external_exports.object({ userId: external_exports.string(), name: external_exports.string() }),
19836
19949
  installedAt: external_exports.string(),
19837
- updatedAt: external_exports.string()
19950
+ updatedAt: external_exports.string(),
19951
+ /** The marketplace listing it was installed from (decision 205; null: by its address). */
19952
+ fromListing: external_exports.object({ id: external_exports.string(), slug: external_exports.string() }).nullable().default(null),
19953
+ /**
19954
+ * Tabs & Spaces took that listing down (decision 205), and why: it still
19955
+ * works here, for the workspace's owners and admins to decide.
19956
+ */
19957
+ takenDown: external_exports.object({ reason: external_exports.string(), at: external_exports.string() }).nullable().default(null)
19838
19958
  });
19839
19959
  var AppListSchema = external_exports.object({
19840
19960
  apps: external_exports.array(AppSchema),
@@ -19843,7 +19963,9 @@ var AppListSchema = external_exports.object({
19843
19963
  });
19844
19964
  var AppResponseSchema = external_exports.object({ app: AppSchema });
19845
19965
  var InstallAppRequestSchema = external_exports.object({
19846
- manifestUrl: external_exports.url({ protocol: /^https?$/, error: "Enter the manifest's full address." })
19966
+ manifestUrl: external_exports.url({ protocol: /^https?$/, error: "Enter the manifest's full address." }),
19967
+ /** From a listing (decision 203): only the manifest it reviewed, by its hash. */
19968
+ manifestHash: ManifestHashSchema.optional()
19847
19969
  });
19848
19970
  var AppPreviewSchema = external_exports.object({
19849
19971
  app: AppSchema.omit({
@@ -19852,7 +19974,9 @@ var AppPreviewSchema = external_exports.object({
19852
19974
  updatedAt: true,
19853
19975
  iconUrl: true,
19854
19976
  dataBytes: true,
19855
- dataLimitBytes: true
19977
+ dataLimitBytes: true,
19978
+ fromListing: true,
19979
+ takenDown: true
19856
19980
  }),
19857
19981
  /** Its picture, inline (it isn't kept until installed). */
19858
19982
  icon: external_exports.string().nullable(),
@@ -19876,11 +20000,30 @@ var AppContextSchema = external_exports.object({
19876
20000
  app: external_exports.object({ id: external_exports.string() }),
19877
20001
  locale: external_exports.string()
19878
20002
  });
20003
+ var AppHomeContextSchema = AppContextSchema.extend({
20004
+ user: external_exports.object({ id: external_exports.string(), name: external_exports.string(), guest: external_exports.boolean() }),
20005
+ space: external_exports.null(),
20006
+ tab: external_exports.null(),
20007
+ /** Which of the app's home pages. */
20008
+ home: external_exports.object({ id: external_exports.string() })
20009
+ });
20010
+ var AppEventKeySchema = external_exports.object({ key: external_exports.string(), eventsUrl: external_exports.string() });
20011
+ var AppEventKeyStatusSchema = external_exports.object({
20012
+ on: external_exports.boolean(),
20013
+ /** Its app declares events (else there's nothing to report). */
20014
+ available: external_exports.boolean(),
20015
+ /** Who allowed it: events report for them. */
20016
+ by: external_exports.object({ id: external_exports.string(), name: external_exports.string() }).nullable(),
20017
+ createdAt: external_exports.string().nullable(),
20018
+ lastUsedAt: external_exports.string().nullable(),
20019
+ /** You may turn it on or off (you manage this space's tabs). */
20020
+ canManage: external_exports.boolean()
20021
+ });
19879
20022
  var AppTokenResponseSchema = external_exports.object({
19880
20023
  /** A JWT (EdDSA), verified with the deployment's keys at /api/apps/keys. */
19881
20024
  token: external_exports.string(),
19882
20025
  expiresAt: external_exports.string(),
19883
- context: AppContextSchema,
20026
+ context: external_exports.union([AppContextSchema, AppHomeContextSchema]),
19884
20027
  /** The frame's address and origin (messages go to and come from it only). */
19885
20028
  url: external_exports.string(),
19886
20029
  origin: external_exports.string()
@@ -20024,6 +20167,7 @@ var TabSettingsSchema = external_exports.object({
20024
20167
  });
20025
20168
  var AgentManagementSchema = external_exports.enum(["admins", "members"]);
20026
20169
  var TaskAssignmentSchema = external_exports.enum(["members", "admins"]);
20170
+ var GroupMentionsSchema = external_exports.enum(["members", "admins"]);
20027
20171
  var SpaceSchema = external_exports.object({
20028
20172
  id: SpaceIdSchema,
20029
20173
  kind: SpaceKindSchema,
@@ -20043,6 +20187,8 @@ var SpaceSchema = external_exports.object({
20043
20187
  unpinnedByYou: external_exports.boolean(),
20044
20188
  /** In your Starred section. */
20045
20189
  starred: external_exports.boolean(),
20190
+ /** How much it notifies you (decision 199): your own choice. */
20191
+ notify: SpaceNotifySchema.default("all"),
20046
20192
  /** DMs only: the participants (you included), so each reader can name it (§9.1). */
20047
20193
  people: external_exports.array(external_exports.object({ userId: external_exports.string(), name: external_exports.string() })),
20048
20194
  /** Your tab row: pinned first, then the rest in your (or the space's) order. */
@@ -20076,8 +20222,15 @@ var SpaceSchema = external_exports.object({
20076
20222
  canManage: external_exports.boolean(),
20077
20223
  /** Set once archived: hidden from sidebars, restorable or deletable by managers. */
20078
20224
  archivedAt: external_exports.iso.datetime().nullable(),
20079
- createdAt: external_exports.iso.datetime()
20080
- });
20225
+ createdAt: external_exports.iso.datetime(),
20226
+ /** Who may notify everyone here with `@space` and `@here` (decision 197). */
20227
+ groupMentions: GroupMentionsSchema.default("members"),
20228
+ /** You may: a member of a normal space as its rule says, General's for workspace admins. */
20229
+ canMentionEveryone: external_exports.boolean().default(false),
20230
+ /** Its icon (normal spaces and General), a picture its managers chose; versioned, so cacheable. */
20231
+ iconUrl: external_exports.string().nullable().default(null)
20232
+ });
20233
+ var MAX_SPACE_ICON_BYTES = 1024 * 1024;
20081
20234
  var SpaceListSchema = external_exports.object({
20082
20235
  spaces: external_exports.array(SpaceSchema),
20083
20236
  canCreate: external_exports.boolean()
@@ -20092,7 +20245,8 @@ var CreateSpaceRequestSchema = external_exports.object({
20092
20245
  var UpdateSpaceRequestSchema = external_exports.object({
20093
20246
  name: SpaceNameSchema,
20094
20247
  description: SpaceDescriptionSchema,
20095
- visibility: SpaceVisibilitySchema
20248
+ visibility: SpaceVisibilitySchema,
20249
+ groupMentions: GroupMentionsSchema
20096
20250
  }).partial().refine((patch) => Object.keys(patch).length > 0, "Nothing to change.");
20097
20251
  var ArchivedSpacesResponseSchema = external_exports.object({ spaces: external_exports.array(SpaceSchema) });
20098
20252
  var SpaceMemberSchema = external_exports.object({
@@ -20164,48 +20318,6 @@ var UpdateAgentSettingsRequestSchema = external_exports.object({
20164
20318
  respondingAgentId: external_exports.string().nullable()
20165
20319
  }).partial().refine((patch) => Object.keys(patch).length > 0, "Nothing to change.");
20166
20320
 
20167
- // ../types/src/time.ts
20168
- function isTimeZone(value) {
20169
- if (!value || value.length > 64) return false;
20170
- try {
20171
- new Intl.DateTimeFormat("en", { timeZone: value });
20172
- return true;
20173
- } catch {
20174
- return false;
20175
- }
20176
- }
20177
- var TimeZoneSchema = external_exports.string().refine(isTimeZone, "Not a known time zone.");
20178
- var DEFAULT_TIME_ZONE = "UTC";
20179
-
20180
- // ../types/src/recurrence.ts
20181
- var WeekdaySchema = external_exports.number().int().min(0).max(6);
20182
- var RecurrenceSchema = external_exports.discriminatedUnion("freq", [
20183
- external_exports.object({ freq: external_exports.literal("daily"), interval: external_exports.number().int().min(1).max(365) }),
20184
- external_exports.object({
20185
- freq: external_exports.literal("weekly"),
20186
- interval: external_exports.number().int().min(1).max(52),
20187
- /** The days of the week it happens on (Monday to Friday: every weekday). */
20188
- weekdays: external_exports.array(WeekdaySchema).min(1).max(7)
20189
- }),
20190
- external_exports.object({
20191
- freq: external_exports.literal("monthly"),
20192
- interval: external_exports.number().int().min(1).max(12),
20193
- on: external_exports.discriminatedUnion("kind", [
20194
- /** A day of the month; in shorter months, their last day. */
20195
- external_exports.object({ kind: external_exports.literal("day"), day: external_exports.number().int().min(1).max(31) }),
20196
- /** The first … fourth, or last (-1), such weekday of the month. */
20197
- external_exports.object({
20198
- kind: external_exports.literal("weekday"),
20199
- week: external_exports.union([external_exports.literal(1), external_exports.literal(2), external_exports.literal(3), external_exports.literal(4), external_exports.literal(-1)]),
20200
- weekday: WeekdaySchema
20201
- })
20202
- ])
20203
- })
20204
- ]);
20205
- var IsoDateSchema = external_exports.string().regex(/^\d{4}-\d{2}-\d{2}$/, "A date like 2026-10-16.");
20206
- var ClockTimeSchema = external_exports.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "A time like 17:00.");
20207
- var DAY_MS = 24 * 60 * 6e4;
20208
-
20209
20321
  // ../types/src/workflows.ts
20210
20322
  var MAX_WORKFLOW_STEPS = 30;
20211
20323
  var WorkflowStepIdSchema = external_exports.string().regex(/^[a-z0-9]{1,16}$/, "A step id.");
@@ -20394,6 +20506,7 @@ var FinishPersonStepRequestSchema = external_exports.object({ note: external_exp
20394
20506
  // ../types/src/triggers.ts
20395
20507
  var TaskStatusSchema = external_exports.enum(["todo", "in_progress", "waiting", "done", "cancelled"]);
20396
20508
  var MAX_TASK_ASSIGNEES = 10;
20509
+ var INBOX_TRIGGER_KINDS = ["mention", "dm", "thread"];
20397
20510
  var TriggerEventSchema = external_exports.discriminatedUnion("kind", [
20398
20511
  /** Someone posts in a conversation of the space (optionally with words in it, or by someone). */
20399
20512
  external_exports.object({
@@ -20428,6 +20541,14 @@ var TriggerEventSchema = external_exports.discriminatedUnion("kind", [
20428
20541
  event: external_exports.string().min(1).max(40),
20429
20542
  field: external_exports.string().max(40).nullable().default(null),
20430
20543
  value: external_exports.string().trim().max(200).nullable().default(null)
20544
+ }),
20545
+ /**
20546
+ * Something new in your Inbox (decision 198): a personal trigger, only in
20547
+ * your own My Space; what people (not agents) send you, from elsewhere.
20548
+ */
20549
+ external_exports.object({
20550
+ kind: external_exports.literal("inbox"),
20551
+ kinds: external_exports.array(external_exports.enum(INBOX_TRIGGER_KINDS)).min(1, "Choose what starts it.").max(INBOX_TRIGGER_KINDS.length).default(["mention", "dm"])
20431
20552
  })
20432
20553
  ]);
20433
20554
  var TriggerTaskSchema = external_exports.object({
@@ -20512,6 +20633,8 @@ var TaskRunStatusSchema = external_exports.enum([
20512
20633
  var TaskRunErrorSchema = external_exports.enum([
20513
20634
  "NOT_CONNECTED",
20514
20635
  "NOT_READY",
20636
+ // Every Gateway the deployment runs was busy, each time it tried (decision 121).
20637
+ "BUSY",
20515
20638
  "PROVIDER_REFUSED",
20516
20639
  "RUN_FAILED",
20517
20640
  "BUDGET_TURNS",
@@ -20654,7 +20777,13 @@ var TaskSeriesSchema = external_exports.object({
20654
20777
  lastAt: external_exports.string().nullable(),
20655
20778
  occurrences: external_exports.number().int(),
20656
20779
  createdBy: external_exports.object({ userId: external_exports.string(), name: external_exports.string() }),
20657
- createdAt: external_exports.string()
20780
+ createdAt: external_exports.string(),
20781
+ /** Made ready-made (decision 198): the unread report. */
20782
+ preset: external_exports.enum(["unread_report"]).nullable().default(null)
20783
+ });
20784
+ var UnreadReportResponseSchema = external_exports.object({ report: TaskSeriesSchema.nullable() });
20785
+ var CreateUnreadReportRequestSchema = external_exports.object({
20786
+ time: ClockTimeSchema.default("08:00")
20658
20787
  });
20659
20788
  var TaskSeriesResponseSchema = external_exports.object({ series: TaskSeriesSchema });
20660
20789
  var CreateTaskSeriesRequestSchema = external_exports.object({
@@ -20810,7 +20939,9 @@ var ApprovalSchema = external_exports.object({
20810
20939
  description: external_exports.string(),
20811
20940
  input: external_exports.record(external_exports.string(), external_exports.unknown()),
20812
20941
  result: external_exports.string().nullable(),
20813
- error: external_exports.string().nullable()
20942
+ error: external_exports.string().nullable(),
20943
+ /** A connector's tool: whose account it uses (the workspace's, or the asker's own). */
20944
+ account: external_exports.enum(["workspace", "person"]).nullable().default(null)
20814
20945
  }).nullable().default(null),
20815
20946
  /** An email (M9 slice 4): from, to, subject; its text is `body` (editable before it's sent). */
20816
20947
  email: EmailDraftSchema.nullable().default(null),
@@ -21049,7 +21180,9 @@ var AgentToolSchema = external_exports.enum([
21049
21180
  // An agent changing its own profile (title, face, instructions…) when asked, approved first.
21050
21181
  "update_my_profile",
21051
21182
  // Connectors (M12 slice 2): a remote MCP server's tools the workspace chose (reading ones).
21052
- "use_connector"
21183
+ "use_connector",
21184
+ // Notifications (decision 198): the asker's own Inbox, where only they read the answer.
21185
+ "read_inbox"
21053
21186
  ]);
21054
21187
  var AgentStepSchema = external_exports.object({
21055
21188
  tool: AgentToolSchema,
@@ -21154,7 +21287,9 @@ var ReferenceSchema = external_exports.object({
21154
21287
  id: external_exports.string(),
21155
21288
  kind: external_exports.enum(["general", "personal", "normal", "dm"]),
21156
21289
  name: external_exports.string().nullable(),
21157
- people: external_exports.array(external_exports.object({ userId: external_exports.string(), name: external_exports.string() }))
21290
+ people: external_exports.array(external_exports.object({ userId: external_exports.string(), name: external_exports.string() })),
21291
+ /** Its icon, when it has one (decision 190). */
21292
+ iconUrl: external_exports.string().nullable().default(null)
21158
21293
  }).nullable(),
21159
21294
  tab: external_exports.object({
21160
21295
  id: external_exports.string(),
@@ -21463,12 +21598,15 @@ var AUDIT_ACTIONS = {
21463
21598
  "member.invite_link_created",
21464
21599
  "member.invitation_revoked",
21465
21600
  "member.joined",
21466
- "member.title_changed"
21601
+ "member.title_changed",
21602
+ "member.role_changed"
21467
21603
  ],
21468
21604
  spaces: [
21469
21605
  "space.created",
21470
21606
  "space.updated",
21471
21607
  "space.deleted",
21608
+ "space.icon_changed",
21609
+ "space.icon_removed",
21472
21610
  "space.archived",
21473
21611
  "space.unarchived",
21474
21612
  "space.converted",
@@ -21508,6 +21646,9 @@ var AUDIT_ACTIONS = {
21508
21646
  "agent.avatar_changed",
21509
21647
  "agent.host_access_changed",
21510
21648
  "agent.memory_changed",
21649
+ "agent.published",
21650
+ "agent.unpublished",
21651
+ "agent.added_from_marketplace",
21511
21652
  "approval.requested",
21512
21653
  "gateway.connected",
21513
21654
  "gateway.disconnected",
@@ -21536,6 +21677,14 @@ var AUDIT_ACTIONS = {
21536
21677
  "app.installed",
21537
21678
  "app.updated",
21538
21679
  "app.removed",
21680
+ "app.added_from_marketplace",
21681
+ "app.published",
21682
+ "app.unpublished",
21683
+ "app.publishing_key_created",
21684
+ "app.publishing_key_revoked",
21685
+ "app.taken_down",
21686
+ "app.event_key_allowed",
21687
+ "app.event_key_turned_off",
21539
21688
  "app.action_run",
21540
21689
  "connection.added",
21541
21690
  "connection.removed",
@@ -21784,12 +21933,18 @@ var DeploymentSettingsFields = external_exports.object({
21784
21933
  * for you") keep it awake, never put to sleep (decision 151)? They pay its
21785
21934
  * hours where the deployment bills them.
21786
21935
  */
21787
- allowGatewayKeepAwake: external_exports.boolean()
21936
+ allowGatewayKeepAwake: external_exports.boolean(),
21937
+ /**
21938
+ * Workspaces browse and add from the marketplace (decision 161): Tabs &
21939
+ * Spaces' catalogue, reviewed by us. On unless its admins turn it off.
21940
+ */
21941
+ marketplace: external_exports.boolean()
21788
21942
  });
21789
21943
  var DeploymentSettingsSchema = DeploymentSettingsFields.extend({
21790
21944
  // Missing in older responses and fixtures: off.
21791
21945
  shareFeedback: external_exports.boolean().default(false),
21792
- allowGatewayKeepAwake: external_exports.boolean().default(false)
21946
+ allowGatewayKeepAwake: external_exports.boolean().default(false),
21947
+ marketplace: external_exports.boolean().default(true)
21793
21948
  });
21794
21949
  var DeploymentSettingsPatchSchema = DeploymentSettingsFields.partial().strict().refine((patch) => Object.keys(patch).length > 0, "Nothing to update");
21795
21950
  var DeploymentInfoSchema = external_exports.object({
@@ -22073,6 +22228,10 @@ var WorkspaceAccentSchema = external_exports.object({ light: HexColorSchema, dar
22073
22228
  var WorkspaceRoleSchema = external_exports.enum(["owner", "admin", "member", "guest"]);
22074
22229
  var WorkspaceSlugSchema = external_exports.string().regex(/^[a-z0-9](?:[a-z0-9-]{0,38}[a-z0-9])?$/, "Use lowercase letters, numbers and hyphens");
22075
22230
  var WorkspaceNameSchema = external_exports.string().trim().min(1).max(60);
22231
+ var WorkspaceHomePageSchema = external_exports.object({
22232
+ appId: external_exports.string().min(1).max(64),
22233
+ pageId: external_exports.string().min(1).max(40)
22234
+ });
22076
22235
  var WorkspaceSchema = external_exports.object({
22077
22236
  id: external_exports.string(),
22078
22237
  slug: WorkspaceSlugSchema,
@@ -22086,7 +22245,9 @@ var WorkspaceSchema = external_exports.object({
22086
22245
  /** Its accent colour for each mode; null: the default ink. */
22087
22246
  accent: WorkspaceAccentSchema.nullable().default(null),
22088
22247
  /** Its default time zone (M8 slice 1): for schedules when a person's own isn't known. */
22089
- timeZone: TimeZoneSchema.default(DEFAULT_TIME_ZONE)
22248
+ timeZone: TimeZoneSchema.default(DEFAULT_TIME_ZONE),
22249
+ /** Its Home (decision 177): one of an installed app's home pages; null: ours. */
22250
+ homePage: WorkspaceHomePageSchema.nullable().default(null)
22090
22251
  });
22091
22252
  var MAX_WORKSPACE_LOGO_BYTES = 2 * 1024 * 1024;
22092
22253
  var WorkspaceListSchema = external_exports.object({
@@ -22194,7 +22355,9 @@ var UpdateWorkspaceRequestSchema = external_exports.object({
22194
22355
  /** Owners and admins: the accent colour (null: back to the default). */
22195
22356
  accent: WorkspaceAccentSchema.nullable(),
22196
22357
  /** Owners and admins: the default time zone. */
22197
- timeZone: TimeZoneSchema
22358
+ timeZone: TimeZoneSchema,
22359
+ /** Owners and admins: Home, from an installed app (null: back to ours). */
22360
+ homePage: WorkspaceHomePageSchema.nullable()
22198
22361
  }).partial().strict().refine((patch) => Object.keys(patch).length > 0, "Nothing to update");
22199
22362
  var WorkspaceResponseSchema = external_exports.object({ workspace: WorkspaceSchema });
22200
22363
  var TRANSLITERATIONS = {
@@ -22267,10 +22430,13 @@ var WorkspaceMemberSchema = external_exports.object({
22267
22430
  title: external_exports.string().default(""),
22268
22431
  /** Their profile picture's address (null: initials), decision 145. */
22269
22432
  picture: external_exports.string().nullable().default(null),
22270
- joinedAt: external_exports.iso.datetime()
22433
+ joinedAt: external_exports.iso.datetime(),
22434
+ /** Their notifications are paused, or in quiet hours, until then (decision 194); null: not. */
22435
+ quietUntil: external_exports.iso.datetime().nullable().default(null)
22271
22436
  });
22272
22437
  var TitleSchema = external_exports.string().trim().max(60, "Keep the title under 60 characters.");
22273
22438
  var UpdateMemberRequestSchema = external_exports.object({ title: TitleSchema });
22439
+ var ChangeRoleRequestSchema = external_exports.object({ role: WorkspaceRoleSchema }).strict();
22274
22440
  var WorkspaceMemberListSchema = external_exports.object({ members: external_exports.array(WorkspaceMemberSchema) });
22275
22441
  var MAX_PROFILE_PICTURE_BYTES = 2 * 1024 * 1024;
22276
22442
 
@@ -23163,7 +23329,19 @@ var McpConnectorSchema = external_exports.object({
23163
23329
  addedAt: external_exports.string(),
23164
23330
  checkedAt: external_exports.string().nullable()
23165
23331
  });
23166
- var McpConnectorListSchema = external_exports.object({ connectors: external_exports.array(McpConnectorSchema) });
23332
+ var ConnectorPresetSchema = external_exports.object({
23333
+ id: external_exports.string(),
23334
+ name: external_exports.string(),
23335
+ url: external_exports.string(),
23336
+ auth: external_exports.enum(["oauth", "header"]),
23337
+ headerName: external_exports.string().nullable(),
23338
+ /** How its key is written ("Bearer github_pat_…"). */
23339
+ keyHint: external_exports.string().nullable()
23340
+ });
23341
+ var McpConnectorListSchema = external_exports.object({
23342
+ connectors: external_exports.array(McpConnectorSchema),
23343
+ presets: external_exports.array(ConnectorPresetSchema).default([])
23344
+ });
23167
23345
  var HeaderNameSchema = external_exports.string().trim().regex(/^[A-Za-z0-9-]{1,64}$/, "A header's name: letters, digits and dashes.").refine(
23168
23346
  (name) => !["host", "content-length", "content-type", "accept", "connection"].includes(
23169
23347
  name.toLowerCase()
@@ -23219,6 +23397,574 @@ var MyConnectorSchema = external_exports.object({
23219
23397
  var MyConnectorListSchema = external_exports.object({ connectors: external_exports.array(MyConnectorSchema) });
23220
23398
  var SetMyConnectorKeyRequestSchema = external_exports.object({ value: external_exports.string().trim().min(1, "Enter your key.").max(4e3) }).strict();
23221
23399
 
23400
+ // ../types/src/marketplace.ts
23401
+ var LISTING_KINDS = ["agent", "app"];
23402
+ var ListingKindSchema = external_exports.enum(LISTING_KINDS);
23403
+ var LISTING_CATEGORIES = [
23404
+ "productivity",
23405
+ "research",
23406
+ "writing",
23407
+ "support",
23408
+ "sales",
23409
+ "marketing",
23410
+ "engineering",
23411
+ "operations",
23412
+ "finance",
23413
+ "people",
23414
+ "education",
23415
+ "personal"
23416
+ ];
23417
+ var ListingCategorySchema = external_exports.enum(LISTING_CATEGORIES);
23418
+ var LISTED_AGENT = "@self";
23419
+ var LISTED_TABBY = "@tabby";
23420
+ var LISTED_ADDER = "@adder";
23421
+ var LISTED_TABS = {
23422
+ conversations: "@conversations",
23423
+ docs: "@docs",
23424
+ tasks: "@tasks"
23425
+ };
23426
+ var listedAppTab = (app) => `@app:${app.id}/${app.tab}`;
23427
+ var AGENT_PLACEHOLDERS = [LISTED_AGENT, LISTED_TABBY];
23428
+ var TAB_PLACEHOLDERS = Object.values(LISTED_TABS);
23429
+ var ASSIGNEE_PLACEHOLDERS = [LISTED_AGENT, LISTED_TABBY, LISTED_ADDER];
23430
+ var ListedAssigneesSchema = external_exports.array(external_exports.enum([LISTED_AGENT, LISTED_TABBY, LISTED_ADDER])).max(3).default([LISTED_AGENT]);
23431
+ var ListedSkillSchema = external_exports.discriminatedUnion("source", [
23432
+ external_exports.object({
23433
+ source: external_exports.literal("builtin"),
23434
+ name: external_exports.string().regex(/^[a-z0-9][a-z0-9-]{0,63}$/, "A skill's name."),
23435
+ displayName: external_exports.string().trim().min(1).max(80)
23436
+ }),
23437
+ external_exports.object({
23438
+ source: external_exports.literal("clawhub"),
23439
+ ref: ClawHubRefSchema,
23440
+ displayName: external_exports.string().trim().min(1).max(80)
23441
+ })
23442
+ ]);
23443
+ var ListedConnectorSchema = external_exports.object({
23444
+ name: external_exports.string().trim().min(1).max(40),
23445
+ url: external_exports.url({ protocol: /^https$/, message: "A connector's https:// address." }).max(500),
23446
+ auth: external_exports.enum(["none", "header", "oauth"]),
23447
+ /** With a header: its name (its value is asked for when it's added). */
23448
+ headerName: external_exports.string().regex(/^[A-Za-z0-9-]{1,64}$/).nullable().default(null),
23449
+ identity: ConnectorIdentitySchema.default("workspace"),
23450
+ /** OAuth: what its sign-in asks for (null: what its server suggests, or our default for it). */
23451
+ scope: external_exports.string().trim().max(500).nullable().default(null),
23452
+ /** What it's for, for whoever adds it. */
23453
+ description: external_exports.string().trim().max(300).default(""),
23454
+ /** Its tools the agent may use, by name (the rest stay off). */
23455
+ tools: external_exports.array(external_exports.string().max(200)).max(500).default([])
23456
+ });
23457
+ var ListedAppSchema = external_exports.object({
23458
+ /** Its manifest's `id`. */
23459
+ id: external_exports.string().min(3).max(64),
23460
+ name: external_exports.string().trim().min(1).max(40),
23461
+ manifestUrl: external_exports.url({ protocol: /^https?$/, message: "The manifest's address." }).max(500),
23462
+ /** Which of its tabs goes in the space: `listedAppTab()` names it. */
23463
+ tab: external_exports.string().min(1).max(40),
23464
+ /** What it's for, for whoever adds it. */
23465
+ description: external_exports.string().trim().max(300).default("")
23466
+ });
23467
+ var ListedWorkflowSchema = external_exports.object({
23468
+ key: WorkflowStepIdSchema,
23469
+ name: external_exports.string().trim().min(1).max(120),
23470
+ description: external_exports.string().max(2e3).default(""),
23471
+ steps: external_exports.array(WorkflowStepSchema).min(1).max(30)
23472
+ });
23473
+ var ListedSeriesSchema = external_exports.object({
23474
+ title: external_exports.string().trim().min(1).max(200),
23475
+ description: external_exports.string().max(4e3).default(""),
23476
+ assignees: ListedAssigneesSchema,
23477
+ recurrence: RecurrenceSchema,
23478
+ time: ClockTimeSchema,
23479
+ /** Its results are also posted in a conversation (`@conversations`). */
23480
+ postTo: external_exports.literal(LISTED_TABS.conversations).nullable().default(null),
23481
+ approveResults: external_exports.boolean().default(false),
23482
+ workflowKey: WorkflowStepIdSchema.nullable().default(null)
23483
+ });
23484
+ var ListedTriggerSchema = external_exports.object({
23485
+ name: external_exports.string().trim().min(1).max(120),
23486
+ event: TriggerEventSchema,
23487
+ task: external_exports.object({
23488
+ title: external_exports.string().trim().min(1).max(200),
23489
+ description: external_exports.string().max(4e3).default(""),
23490
+ assignees: ListedAssigneesSchema,
23491
+ workflowKey: WorkflowStepIdSchema.nullable().default(null)
23492
+ })
23493
+ });
23494
+ var AgentListingContentSchema = external_exports.object({
23495
+ agent: external_exports.object({
23496
+ name: external_exports.string().trim().min(1).max(40),
23497
+ title: external_exports.string().trim().max(60).default(""),
23498
+ // An agent's own limits, so whatever's listed can be added.
23499
+ description: external_exports.string().trim().min(1, "Say what it's for.").max(120),
23500
+ instructions: external_exports.string().max(MAX_AGENT_INSTRUCTIONS).default(""),
23501
+ /** One of the drawn faces; or "custom", with the listing's picture. */
23502
+ avatar: external_exports.enum([...AGENT_AVATARS, "custom"])
23503
+ }),
23504
+ skills: external_exports.array(ListedSkillSchema).max(30).default([]),
23505
+ connectors: external_exports.array(ListedConnectorSchema).max(10).default([]),
23506
+ apps: external_exports.array(ListedAppSchema).max(3).default([]),
23507
+ workflows: external_exports.array(ListedWorkflowSchema).max(10).default([]),
23508
+ series: external_exports.array(ListedSeriesSchema).max(10).default([]),
23509
+ triggers: external_exports.array(ListedTriggerSchema).max(10).default([])
23510
+ }).superRefine((content, ctx) => {
23511
+ for (const problem of portabilityProblems(content))
23512
+ ctx.addIssue({ code: "custom", ...problem });
23513
+ });
23514
+ function portabilityProblems(content) {
23515
+ const problems = [];
23516
+ const keys = new Set(content.workflows.map((w) => w.key));
23517
+ const appTabs = new Set((content.apps ?? []).map(listedAppTab));
23518
+ const agent = (id, path) => {
23519
+ if (!AGENT_PLACEHOLDERS.includes(id)) {
23520
+ problems.push({ message: "It can only use this agent or Tabby.", path });
23521
+ }
23522
+ };
23523
+ const tab = (id, path) => {
23524
+ if (!TAB_PLACEHOLDERS.includes(id) && !appTabs.has(id)) {
23525
+ problems.push({ message: "It can only use a conversation, Docs or Tasks tab.", path });
23526
+ }
23527
+ };
23528
+ const person = (id, path) => {
23529
+ if (!ASSIGNEE_PLACEHOLDERS.includes(id)) {
23530
+ problems.push({ message: "It can only be for this agent, Tabby or whoever adds it.", path });
23531
+ }
23532
+ };
23533
+ const workflow = (key, path) => {
23534
+ if (key !== null && !keys.has(key)) {
23535
+ problems.push({ message: "It names a workflow the listing doesn't have.", path });
23536
+ }
23537
+ };
23538
+ content.workflows.forEach((w, i) => {
23539
+ if (content.workflows.findIndex((other) => other.key === w.key) !== i) {
23540
+ problems.push({ message: "Workflows' keys must differ.", path: ["workflows", i, "key"] });
23541
+ }
23542
+ for (const step of allSteps(w.steps)) {
23543
+ const at = ["workflows", i, "steps", step.id];
23544
+ switch (step.kind) {
23545
+ case "agent":
23546
+ agent(step.agentId, at);
23547
+ break;
23548
+ case "post":
23549
+ agent(step.agentId, at);
23550
+ tab(step.tabId, at);
23551
+ break;
23552
+ case "create_task":
23553
+ tab(step.tabId, at);
23554
+ step.assigneeIds.forEach((id) => person(id, at));
23555
+ break;
23556
+ case "person":
23557
+ person(step.assigneeId, at);
23558
+ break;
23559
+ case "if":
23560
+ if (step.condition.op === "agent") agent(step.condition.agentId, at);
23561
+ break;
23562
+ case "app_action":
23563
+ if (!appTabs.has(step.tabId)) {
23564
+ problems.push({ message: "Its actions need an app the listing brings.", path: at });
23565
+ }
23566
+ break;
23567
+ }
23568
+ }
23569
+ });
23570
+ content.series.forEach((s, i) => workflow(s.workflowKey, ["series", i, "workflowKey"]));
23571
+ content.triggers.forEach((t, i) => {
23572
+ const at = ["triggers", i, "event"];
23573
+ const event = t.event;
23574
+ if (event.kind === "app") {
23575
+ if (!appTabs.has(event.tabId)) {
23576
+ problems.push({ message: "Its events need an app the listing brings.", path: at });
23577
+ }
23578
+ } else if (event.kind === "inbox") {
23579
+ problems.push({ message: "A personal trigger (someone's Inbox) can't be listed.", path: at });
23580
+ } else if (event.kind !== "webhook") tab(event.tabId, at);
23581
+ if (event.kind === "message" && event.fromId !== null) person(event.fromId, at);
23582
+ workflow(t.task.workflowKey, ["triggers", i, "task", "workflowKey"]);
23583
+ });
23584
+ return problems;
23585
+ }
23586
+ var ListingPublisherSchema = external_exports.object({
23587
+ /** The workspace that publishes it, or Tabs & Spaces. */
23588
+ name: external_exports.string(),
23589
+ /** Made by Tabs & Spaces (decision 167): kept in our repository, listed as it changes. */
23590
+ official: external_exports.boolean().default(false)
23591
+ });
23592
+ var ListingNeedsSchema = external_exports.object({
23593
+ skills: external_exports.number().int().nonnegative(),
23594
+ connectors: external_exports.number().int().nonnegative(),
23595
+ workflows: external_exports.number().int().nonnegative(),
23596
+ recurring: external_exports.number().int().nonnegative(),
23597
+ triggers: external_exports.number().int().nonnegative(),
23598
+ tabs: external_exports.number().int().nonnegative().default(0),
23599
+ /** An agent's apps (decision 178). */
23600
+ apps: external_exports.number().int().nonnegative().default(0),
23601
+ actions: external_exports.number().int().nonnegative().default(0),
23602
+ events: external_exports.number().int().nonnegative().default(0)
23603
+ });
23604
+ var ListingSummarySchema = external_exports.object({
23605
+ id: external_exports.string(),
23606
+ slug: external_exports.string(),
23607
+ kind: ListingKindSchema,
23608
+ name: external_exports.string(),
23609
+ /** Its job title (agents), or a tagline. */
23610
+ title: external_exports.string(),
23611
+ description: external_exports.string(),
23612
+ categories: external_exports.array(ListingCategorySchema),
23613
+ avatar: external_exports.string(),
23614
+ /** Its own picture's address, if it has one. */
23615
+ pictureUrl: external_exports.string().nullable(),
23616
+ publisher: ListingPublisherSchema,
23617
+ version: external_exports.number().int().positive(),
23618
+ installs: external_exports.number().int().nonnegative(),
23619
+ needs: ListingNeedsSchema,
23620
+ publishedAt: external_exports.iso.datetime(),
23621
+ updatedAt: external_exports.iso.datetime()
23622
+ });
23623
+ var ListingSearchSchema = external_exports.object({
23624
+ listings: external_exports.array(ListingSummarySchema),
23625
+ total: external_exports.number().int().nonnegative()
23626
+ });
23627
+ var ListingImageViewSchema = external_exports.object({
23628
+ id: external_exports.string(),
23629
+ url: external_exports.string(),
23630
+ caption: external_exports.string()
23631
+ });
23632
+ var ListingPage = ListingSummarySchema.extend({
23633
+ /** A longer account of it, in Markdown. */
23634
+ about: external_exports.string(),
23635
+ /** What changed in this version. */
23636
+ changes: external_exports.string(),
23637
+ /** Screenshots and pictures of it at work (decision 202), in order. */
23638
+ images: external_exports.array(ListingImageViewSchema).default([])
23639
+ });
23640
+ var AgentListingSchema = ListingPage.extend({
23641
+ kind: external_exports.literal("agent"),
23642
+ content: AgentListingContentSchema
23643
+ });
23644
+ var AppListingSchema = ListingPage.extend({
23645
+ kind: external_exports.literal("app"),
23646
+ content: external_exports.lazy(() => AppListingContentSchema)
23647
+ });
23648
+ var ListingSchema = external_exports.discriminatedUnion("kind", [AgentListingSchema, AppListingSchema]);
23649
+ var ListingResponseSchema = external_exports.object({ listing: ListingSchema });
23650
+ var ListingPictureSchema = external_exports.object({
23651
+ type: external_exports.enum(["image/png", "image/jpeg", "image/webp"]),
23652
+ base64: external_exports.string().max(35e4, "The picture is too big (256 kB at most).").regex(/^[A-Za-z0-9+/]+=*$/)
23653
+ });
23654
+ var MAX_LISTING_IMAGES = 6;
23655
+ var MAX_LISTING_IMAGE_BYTES = 1048576;
23656
+ var ListingImageIdSchema = external_exports.string().regex(/^[0-9a-f]{64}$/, "An image's id.");
23657
+ var ImageCaption = external_exports.string().trim().max(140, "A caption is 140 characters at most.").default("");
23658
+ var ListingImageSchema = external_exports.union([
23659
+ external_exports.object({
23660
+ type: external_exports.enum(["image/png", "image/jpeg", "image/webp"]),
23661
+ base64: external_exports.string().max(Math.ceil(MAX_LISTING_IMAGE_BYTES / 3) * 4, "An image is 1 MB at most.").regex(/^[A-Za-z0-9+/]+=*$/),
23662
+ caption: ImageCaption
23663
+ }).strict(),
23664
+ external_exports.object({ id: ListingImageIdSchema, caption: ImageCaption }).strict()
23665
+ ]);
23666
+ var ListingImages = external_exports.array(ListingImageSchema).max(MAX_LISTING_IMAGES, `${MAX_LISTING_IMAGES} images at most.`).default([]);
23667
+ var PrivacyUrlSchema = external_exports.url({ protocol: /^https$/, message: "The privacy policy's address, starting https://." }).max(500);
23668
+ var AppListingContentSchema = external_exports.object({
23669
+ app: external_exports.object({
23670
+ /** Its manifest's `id`. */
23671
+ id: external_exports.string().min(3).max(64),
23672
+ name: external_exports.string().trim().min(1).max(40),
23673
+ /** A line about it. */
23674
+ title: external_exports.string().trim().max(60).default(""),
23675
+ description: external_exports.string().trim().min(1).max(300),
23676
+ // https, as installing requires (http only where a deployment allows private networks).
23677
+ manifestUrl: external_exports.url({ protocol: /^https?$/, message: "The manifest's address." }).max(500),
23678
+ /**
23679
+ * The manifest we reviewed (decision 203): installing from the listing refuses
23680
+ * another. Ours have none (we serve them, and change them as we deploy).
23681
+ */
23682
+ manifestHash: ManifestHashSchema.optional(),
23683
+ /**
23684
+ * Its developer's privacy policy (decision 205): what happens to the data
23685
+ * the app is sent. Every workspace's app has one; ours are on our site.
23686
+ */
23687
+ privacyUrl: PrivacyUrlSchema.optional()
23688
+ }),
23689
+ tabs: external_exports.array(
23690
+ external_exports.object({
23691
+ id: external_exports.string().max(40),
23692
+ name: external_exports.string().max(40),
23693
+ description: external_exports.string().max(200).default("")
23694
+ })
23695
+ ).min(1).max(10),
23696
+ actions: external_exports.array(
23697
+ external_exports.object({
23698
+ name: external_exports.string().max(40),
23699
+ description: external_exports.string().max(300),
23700
+ changes: external_exports.boolean()
23701
+ })
23702
+ ).max(20).default([]),
23703
+ events: external_exports.array(external_exports.object({ name: external_exports.string().max(40), description: external_exports.string().max(300) })).max(20).default([])
23704
+ });
23705
+ var DraftCommon = {
23706
+ categories: external_exports.array(ListingCategorySchema).min(1, "Choose a category.").max(3),
23707
+ about: external_exports.string().max(1e4).default(""),
23708
+ changes: external_exports.string().max(2e3).default(""),
23709
+ picture: ListingPictureSchema.nullable().default(null),
23710
+ images: ListingImages
23711
+ };
23712
+ var AgentListingDraftSchema = external_exports.object({
23713
+ kind: external_exports.literal("agent"),
23714
+ ...DraftCommon,
23715
+ content: AgentListingContentSchema
23716
+ });
23717
+ var ListingDraftSchema = external_exports.discriminatedUnion("kind", [
23718
+ AgentListingDraftSchema,
23719
+ external_exports.object({ kind: external_exports.literal("app"), ...DraftCommon, content: AppListingContentSchema })
23720
+ ]);
23721
+ var PublishedFrom = {
23722
+ /** Its listing, for a new version (one this workspace published). */
23723
+ listingId: external_exports.string().max(64).nullable().default(null),
23724
+ workspace: external_exports.object({ id: external_exports.string().min(1).max(64), name: external_exports.string().trim().min(1).max(120) }),
23725
+ /** Who sent it (we write to them about the review; never shown). */
23726
+ publishedBy: external_exports.object({ name: external_exports.string().trim().min(1).max(120), email: external_exports.email() }),
23727
+ /**
23728
+ * The publisher terms' version its sender accepted just now (decision 205;
23729
+ * null: the workspace accepted these terms before).
23730
+ */
23731
+ acceptedTerms: external_exports.string().max(20).nullable().default(null)
23732
+ };
23733
+ var ControlPublishAppSchema = external_exports.object({
23734
+ kind: external_exports.literal("app"),
23735
+ ...DraftCommon,
23736
+ manifestUrl: external_exports.url({ protocol: /^https?$/, message: "The manifest's address." }).max(500),
23737
+ manifestHash: ManifestHashSchema,
23738
+ privacyUrl: PrivacyUrlSchema,
23739
+ ...PublishedFrom
23740
+ });
23741
+ var ControlPublishListingSchema = external_exports.discriminatedUnion("kind", [
23742
+ AgentListingDraftSchema.extend(PublishedFrom),
23743
+ ControlPublishAppSchema
23744
+ ]);
23745
+ var ListingStatusSchema = external_exports.enum(["pending", "approved", "rejected", "removed"]);
23746
+ var PublishedListingSchema = external_exports.object({
23747
+ id: external_exports.string(),
23748
+ slug: external_exports.string(),
23749
+ kind: ListingKindSchema,
23750
+ name: external_exports.string(),
23751
+ /** The version everyone sees (null: none approved yet). */
23752
+ liveVersion: external_exports.number().int().positive().nullable(),
23753
+ /** The newest version and its review. */
23754
+ latest: external_exports.object({
23755
+ version: external_exports.number().int().positive(),
23756
+ status: external_exports.enum(["pending", "approved", "rejected"]),
23757
+ /** Why we didn't list it. */
23758
+ reason: external_exports.string().nullable(),
23759
+ submittedAt: external_exports.iso.datetime()
23760
+ }),
23761
+ /** Taken down by us (with why), or by the workspace. */
23762
+ removed: external_exports.object({ reason: external_exports.string(), at: external_exports.iso.datetime() }).nullable(),
23763
+ installs: external_exports.number().int().nonnegative()
23764
+ });
23765
+ var PublishedListingsSchema = external_exports.object({
23766
+ listings: external_exports.array(PublishedListingSchema),
23767
+ /**
23768
+ * The workspace's token (decision 203): an app's manifest names it in
23769
+ * `marketplace.verify` to say this workspace may publish it.
23770
+ */
23771
+ publisherToken: external_exports.string().nullable().default(null),
23772
+ /** The publisher terms (decision 205), and the version this workspace accepted. */
23773
+ terms: external_exports.object({ version: external_exports.string(), url: external_exports.string(), accepted: external_exports.string().nullable() }).nullable().default(null)
23774
+ });
23775
+ var PublisherTermsSchema = external_exports.object({
23776
+ version: external_exports.string(),
23777
+ url: external_exports.string(),
23778
+ /** This workspace accepted this version. */
23779
+ accepted: external_exports.boolean()
23780
+ });
23781
+ var ListingStatusRequestSchema = external_exports.object({
23782
+ ids: external_exports.array(external_exports.string().min(1).max(64)).min(1).max(100)
23783
+ });
23784
+ var ListingStatusesSchema = external_exports.object({
23785
+ listings: external_exports.array(
23786
+ external_exports.object({
23787
+ id: external_exports.string(),
23788
+ /** Live, for anyone to add. */
23789
+ listed: external_exports.boolean(),
23790
+ /** Taken down by Tabs & Spaces, and why (its workspace taking it out isn't this). */
23791
+ takenDown: external_exports.object({ reason: external_exports.string(), at: external_exports.iso.datetime() }).nullable()
23792
+ })
23793
+ )
23794
+ });
23795
+ var PublishingItemSchema = external_exports.object({
23796
+ id: external_exports.string(),
23797
+ name: external_exports.string(),
23798
+ /** A line about it ("ClawHub: @acme/notes", "Every Friday at 16:00"). */
23799
+ detail: external_exports.string(),
23800
+ /** Ticked to start with. */
23801
+ suggested: external_exports.boolean(),
23802
+ /** Why it can't be listed (empty: it can). */
23803
+ problems: external_exports.array(external_exports.string())
23804
+ });
23805
+ var AgentPublishingSchema = external_exports.object({
23806
+ /** This deployment is registered with Tabs & Spaces (publishing needs it). */
23807
+ registered: external_exports.boolean(),
23808
+ /** The agent's listing, if it was published. */
23809
+ listing: PublishedListingSchema.nullable(),
23810
+ /** Its live version's words, to start a new version from. */
23811
+ previous: external_exports.object({
23812
+ categories: external_exports.array(ListingCategorySchema),
23813
+ about: external_exports.string(),
23814
+ /** Its images, kept by id unless they're taken out (decision 202). */
23815
+ images: external_exports.array(ListingImageViewSchema).default([])
23816
+ }).nullable(),
23817
+ /** Where the marketplace shows a listing (its slug follows). */
23818
+ listingBase: external_exports.string(),
23819
+ /** The publisher terms (null: not registered, or unknown). */
23820
+ terms: PublisherTermsSchema.nullable().default(null),
23821
+ skills: external_exports.array(PublishingItemSchema),
23822
+ connectors: external_exports.array(PublishingItemSchema),
23823
+ /** Spaces where it has workflows, recurring tasks or triggers. */
23824
+ spaces: external_exports.array(external_exports.object({ id: external_exports.string(), name: external_exports.string() })),
23825
+ /** The space whose work is shown below (null: none). */
23826
+ spaceId: external_exports.string().nullable(),
23827
+ workflows: external_exports.array(PublishingItemSchema),
23828
+ series: external_exports.array(PublishingItemSchema),
23829
+ triggers: external_exports.array(PublishingItemSchema)
23830
+ });
23831
+ var PublishAgentRequestSchema = external_exports.object({
23832
+ categories: external_exports.array(ListingCategorySchema).min(1, "Choose a category.").max(3),
23833
+ about: external_exports.string().max(1e4).default(""),
23834
+ changes: external_exports.string().max(2e3).default(""),
23835
+ spaceId: external_exports.string().max(64).nullable().default(null),
23836
+ skills: external_exports.array(external_exports.string().max(200)).max(30).default([]),
23837
+ connectors: external_exports.array(external_exports.string().max(64)).max(10).default([]),
23838
+ workflows: external_exports.array(external_exports.string().max(64)).max(10).default([]),
23839
+ series: external_exports.array(external_exports.string().max(64)).max(10).default([]),
23840
+ triggers: external_exports.array(external_exports.string().max(64)).max(10).default([]),
23841
+ images: ListingImages,
23842
+ /** The publisher terms' version accepted now (decision 205; left out: accepted before). */
23843
+ acceptTerms: external_exports.string().max(20).optional()
23844
+ }).strict();
23845
+ var PublishedListingResponseSchema = external_exports.object({ listing: PublishedListingSchema });
23846
+ var AppPublishingSchema = external_exports.object({
23847
+ /** This deployment is registered with Tabs & Spaces (publishing needs it). */
23848
+ registered: external_exports.boolean(),
23849
+ /** The app's listing, if this workspace published it. */
23850
+ listing: PublishedListingSchema.nullable(),
23851
+ /** Its live version's words and images, to start a new version from. */
23852
+ previous: external_exports.object({
23853
+ categories: external_exports.array(ListingCategorySchema),
23854
+ about: external_exports.string(),
23855
+ images: external_exports.array(ListingImageViewSchema).default([]),
23856
+ privacyUrl: external_exports.string().nullable().default(null)
23857
+ }).nullable(),
23858
+ listingBase: external_exports.string(),
23859
+ terms: PublisherTermsSchema.nullable().default(null),
23860
+ /** What its manifest says to name this workspace (null: not registered, or unknown). */
23861
+ publisherToken: external_exports.string().nullable(),
23862
+ /** The installed manifest names it. */
23863
+ verified: external_exports.boolean(),
23864
+ /** Why it can't be sent yet, as the installed manifest stands. */
23865
+ problems: external_exports.array(external_exports.string())
23866
+ });
23867
+ var PublishAppRequestSchema = external_exports.object({
23868
+ categories: external_exports.array(ListingCategorySchema).min(1, "Choose a category.").max(3),
23869
+ about: external_exports.string().max(1e4).default(""),
23870
+ changes: external_exports.string().max(2e3).default(""),
23871
+ images: ListingImages,
23872
+ privacyUrl: PrivacyUrlSchema,
23873
+ acceptTerms: external_exports.string().max(20).optional()
23874
+ }).strict();
23875
+ var PublishingKeySchema = external_exports.object({
23876
+ id: external_exports.string(),
23877
+ /** Its first characters, to tell keys apart (the rest is never shown again). */
23878
+ prefix: external_exports.string(),
23879
+ createdBy: external_exports.object({ name: external_exports.string() }),
23880
+ createdAt: external_exports.iso.datetime(),
23881
+ lastUsedAt: external_exports.iso.datetime().nullable()
23882
+ });
23883
+ var PublishingKeysSchema = external_exports.object({ keys: external_exports.array(PublishingKeySchema) });
23884
+ var CreatedPublishingKeySchema = external_exports.object({
23885
+ key: PublishingKeySchema,
23886
+ /** Shown once: `tands publish` sends it (TANDS_PUBLISH_KEY). */
23887
+ secret: external_exports.string()
23888
+ });
23889
+ var KeyPublishRequestSchema = external_exports.object({ changes: external_exports.string().max(2e3).default("") }).strict();
23890
+ var KeyPublishResultSchema = external_exports.object({
23891
+ app: external_exports.object({ id: external_exports.string(), name: external_exports.string() }),
23892
+ listing: PublishedListingSchema,
23893
+ /** The installed manifest changed (checked for updates first). */
23894
+ updated: external_exports.boolean()
23895
+ });
23896
+ var MarketplaceBrowseSchema = external_exports.object({
23897
+ /** Off: its deployment's admins turned the marketplace off. */
23898
+ enabled: external_exports.boolean(),
23899
+ listings: external_exports.array(ListingSummarySchema),
23900
+ total: external_exports.number().int().nonnegative(),
23901
+ /** Listings this workspace has added: listing id → its agent. */
23902
+ added: external_exports.record(external_exports.string(), external_exports.string()),
23903
+ /** Workspace owners and admins add them (skills and connectors are theirs to set up). */
23904
+ canAdd: external_exports.boolean()
23905
+ });
23906
+ var MarketplaceListingSchema = external_exports.object({
23907
+ listing: ListingSchema,
23908
+ /** The agent it was added as, if it was. */
23909
+ addedAs: external_exports.string().nullable(),
23910
+ /** What the workspace has already (it's shared, not added twice). */
23911
+ has: external_exports.object({ skills: external_exports.array(external_exports.string()), connectors: external_exports.array(external_exports.string()) }),
23912
+ /**
23913
+ * How each of its connectors signs in here (by its place in the listing):
23914
+ * a known server (GitHub) uses OAuth where this deployment registered its
23915
+ * app, else each person's own key in `headerName` (decision 167).
23916
+ */
23917
+ connectorAuth: external_exports.array(
23918
+ external_exports.object({
23919
+ auth: external_exports.enum(["none", "header", "oauth"]),
23920
+ headerName: external_exports.string().nullable(),
23921
+ keyHint: external_exports.string().nullable()
23922
+ })
23923
+ ).default([]),
23924
+ canAdd: external_exports.boolean()
23925
+ });
23926
+ var AddListingRequestSchema = external_exports.object({
23927
+ /** Where its work goes (needed when it has some). */
23928
+ spaceId: external_exports.string().max(64).nullable().default(null),
23929
+ /** Its recurring tasks' time zone: the adder's. */
23930
+ timeZone: external_exports.string().min(1).max(64),
23931
+ /** Keys for its connectors that sign in with one, by the connector's place in the listing. */
23932
+ keys: external_exports.record(external_exports.string().regex(/^\d{1,2}$/), external_exports.string().trim().min(1).max(4e3)).default({})
23933
+ }).strict();
23934
+ var AddedStepSchema = external_exports.object({
23935
+ what: external_exports.enum([
23936
+ "agent",
23937
+ "picture",
23938
+ "skill",
23939
+ "connector",
23940
+ "space",
23941
+ "workflow",
23942
+ "series",
23943
+ "trigger",
23944
+ "app",
23945
+ "tab"
23946
+ ]),
23947
+ name: external_exports.string(),
23948
+ ok: external_exports.boolean(),
23949
+ /** Why it didn't work (it can be done by hand). */
23950
+ message: external_exports.string().nullable()
23951
+ });
23952
+ var AddListingResultSchema = external_exports.object({
23953
+ /** The agent it was added as. */
23954
+ agent: external_exports.object({ id: external_exports.string(), name: external_exports.string() }),
23955
+ steps: external_exports.array(AddedStepSchema),
23956
+ /** Connectors waiting for an OAuth sign-in. */
23957
+ signIns: external_exports.array(external_exports.object({ connectorId: external_exports.string(), name: external_exports.string() }))
23958
+ });
23959
+ var AddAppListingRequestSchema = external_exports.object({ spaceId: external_exports.string().max(64).nullable().default(null) }).strict();
23960
+ var AddAppListingResultSchema = external_exports.object({
23961
+ /** The app, installed (or updated, if it was). */
23962
+ app: external_exports.object({ id: external_exports.string(), name: external_exports.string() }),
23963
+ /** Its tab, where it was added. */
23964
+ tab: external_exports.object({ spaceId: external_exports.string(), tabId: external_exports.string() }).nullable(),
23965
+ steps: external_exports.array(AddedStepSchema)
23966
+ });
23967
+
23222
23968
  // src/harness.ts
23223
23969
  import { createHash, generateKeyPairSync, randomBytes, sign } from "node:crypto";
23224
23970
  import { createServer } from "node:http";
@@ -23948,6 +24694,49 @@ async function startHarness({
23948
24694
  };
23949
24695
  }
23950
24696
 
24697
+ // src/publish.ts
24698
+ async function publish({
24699
+ url: url2,
24700
+ key,
24701
+ changes = "",
24702
+ fetchImpl = fetch
24703
+ }) {
24704
+ let origin;
24705
+ try {
24706
+ origin = new URL(url2).origin;
24707
+ } catch {
24708
+ return { ok: false, message: `\u201C${url2}\u201D isn't a web address (TANDS_URL).` };
24709
+ }
24710
+ let response;
24711
+ try {
24712
+ response = await fetchImpl(`${origin}/api/publish/app`, {
24713
+ method: "POST",
24714
+ headers: { authorization: `Bearer ${key}`, "content-type": "application/json" },
24715
+ body: JSON.stringify({ changes }),
24716
+ signal: AbortSignal.timeout(6e4)
24717
+ });
24718
+ } catch (error62) {
24719
+ return {
24720
+ ok: false,
24721
+ message: `${origin} can't be reached: ${error62 instanceof Error ? error62.message : String(error62)}`
24722
+ };
24723
+ }
24724
+ const json2 = await response.json().catch(() => null);
24725
+ if (!response.ok || !json2?.app || !json2.listing) {
24726
+ return {
24727
+ ok: false,
24728
+ message: json2?.error?.message ?? `${origin} answered HTTP ${response.status}.`
24729
+ };
24730
+ }
24731
+ return {
24732
+ ok: true,
24733
+ app: json2.app,
24734
+ version: json2.listing.latest.version,
24735
+ live: json2.listing.liveVersion,
24736
+ updated: Boolean(json2.updated)
24737
+ };
24738
+ }
24739
+
23951
24740
  // src/scaffold.ts
23952
24741
  import { existsSync } from "node:fs";
23953
24742
  import { copyFile, mkdir, readdir, writeFile } from "node:fs/promises";
@@ -24212,7 +25001,7 @@ your manifest's address. See https://developer.tabsandspaces.dev.
24212
25001
  `;
24213
25002
 
24214
25003
  // src/bin.ts
24215
- var version2 = true ? "0.1.0" : "dev";
25004
+ var version2 = true ? "0.2.0" : "dev";
24216
25005
  var USAGE = `tands: developer tools for Tabs & Spaces apps
24217
25006
 
24218
25007
  tands new <folder> [--name "My app"] [--id my-app]
@@ -24227,13 +25016,21 @@ var USAGE = `tands: developer tools for Tabs & Spaces apps
24227
25016
  choose, with your data in memory, your events and your actions.
24228
25017
  Default: http://localhost:5480/tands.json
24229
25018
 
25019
+ tands publish [--changes "What changed"]
25020
+ Sends a new version of your app to the marketplace for review. Its
25021
+ workspace checks it for updates first, then sends it with its listing's
25022
+ words and images. Needs TANDS_URL (the workspace's address) and
25023
+ TANDS_PUBLISH_KEY (a key from Settings \u2192 Apps \u2192 Publish\u2026 there).
25024
+
24230
25025
  tands --version
24231
25026
 
24232
25027
  Docs: https://developer.tabsandspaces.dev`;
24233
25028
  async function main(argv, {
24234
25029
  cwd = process.env.INIT_CWD ?? process.cwd(),
24235
25030
  out = (line) => console.log(line),
24236
- err = (line) => console.error(line)
25031
+ err = (line) => console.error(line),
25032
+ env = process.env,
25033
+ fetchImpl = fetch
24237
25034
  } = {}) {
24238
25035
  const { values, positionals } = parseArgs({
24239
25036
  args: argv,
@@ -24242,6 +25039,7 @@ async function main(argv, {
24242
25039
  name: { type: "string" },
24243
25040
  id: { type: "string" },
24244
25041
  port: { type: "string" },
25042
+ changes: { type: "string" },
24245
25043
  help: { type: "boolean", short: "h" },
24246
25044
  version: { type: "boolean", short: "v" }
24247
25045
  }
@@ -24334,6 +25132,29 @@ async function main(argv, {
24334
25132
  return 1;
24335
25133
  }
24336
25134
  }
25135
+ case "publish": {
25136
+ const url2 = env.TANDS_URL?.trim();
25137
+ const key = env.TANDS_PUBLISH_KEY?.trim();
25138
+ if (!url2 || !key) {
25139
+ err("tands publish needs your workspace's address and a publishing key:");
25140
+ err(" TANDS_URL=https://your.workspace TANDS_PUBLISH_KEY=tas-pk-\u2026 npx tands publish");
25141
+ err("Make the key in the workspace: Settings \u2192 Apps \u2192 Publish\u2026 \u2192 Make a key.");
25142
+ return 1;
25143
+ }
25144
+ const sent = await publish({ url: url2, key, changes: values.changes ?? "", fetchImpl });
25145
+ if (!sent.ok) {
25146
+ err(sent.message);
25147
+ return 1;
25148
+ }
25149
+ out(
25150
+ `\u2713 Sent ${sent.app.name} (${sent.app.id}) version ${sent.version} to the marketplace for review.`
25151
+ );
25152
+ if (sent.updated) out(" Its workspace had an older manifest: it's up to date now.");
25153
+ out(
25154
+ sent.live ? ` Version ${sent.live} stays listed until it's reviewed; you'll get an email then.` : " You'll get an email when it's reviewed."
25155
+ );
25156
+ return 0;
25157
+ }
24337
25158
  default:
24338
25159
  err(`There's no command \u201C${command}\u201D.`);
24339
25160
  out(USAGE);
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "tands",
3
- "version": "0.1.0",
4
- "description": "Developer tools for Tabs & Spaces apps: `tands new`, `tands check` and `tands dev` (a local harness that stands in for a workspace).",
3
+ "version": "0.2.0",
4
+ "description": "Developer tools for Tabs & Spaces apps: `tands new`, `tands check`, `tands dev` (a local harness that stands in for a workspace) and `tands publish`.",
5
5
  "license": "MIT",
6
6
  "author": "Tabs & Spaces",
7
7
  "homepage": "https://developer.tabsandspaces.dev/tools/",
@@ -38,7 +38,7 @@
38
38
  "test:coverage": "vitest run --coverage"
39
39
  },
40
40
  "devDependencies": {
41
- "@tabs-and-spaces/sdk": "0.1.0",
41
+ "@tabs-and-spaces/sdk": "0.3.0",
42
42
  "@tabs-and-spaces/types": "0.0.0",
43
43
  "@types/node": "^26.6.3",
44
44
  "@vitest/coverage-v8": "5.0.2",