@camstack/addon-waste-collections 0.1.2 → 0.1.3

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.
@@ -52760,18 +52760,6 @@ DEFAULT_NATIVE_LEASE_SETTINGS.admission;
52760
52760
  var MB = 1024 * 1024;
52761
52761
  1024 * MB, 3072 * MB;
52762
52762
  //#endregion
52763
- //#region src/custom-actions.ts
52764
- var wasteCollectionsActions = defineCustomActions({ fetchNow: customAction(object({
52765
- /** When set, refresh only this configured source id; otherwise all enabled. */
52766
- sourceId: string().min(1).optional() }).optional(), object({
52767
- ok: literal(true),
52768
- refreshed: array(object({
52769
- sourceId: string(),
52770
- entryCount: number().int(),
52771
- error: string().optional()
52772
- }))
52773
- }), { kind: "mutation" }) });
52774
- //#endregion
52775
52763
  //#region src/waste-types.ts
52776
52764
  var WasteSourceConfigSchema = object({
52777
52765
  id: string().min(1),
@@ -52812,6 +52800,54 @@ var WasteFetchResultSchema = discriminatedUnion("ok", [object({
52812
52800
  error: string()
52813
52801
  })]);
52814
52802
  //#endregion
52803
+ //#region src/custom-actions.ts
52804
+ var SourceIdSchema = string().min(1).regex(/^[a-z0-9][a-z0-9-]{0,63}$/, "id must be a lowercase slug");
52805
+ var WasteSourceRowSchema = WasteSourceConfigSchema.extend({ formValues: record(string(), unknown()).optional() });
52806
+ var wasteCollectionsActions = defineCustomActions({
52807
+ fetchNow: customAction(object({
52808
+ /** When set, refresh only this configured source id; otherwise all enabled. */
52809
+ sourceId: string().min(1).optional() }).optional(), object({
52810
+ ok: literal(true),
52811
+ refreshed: array(object({
52812
+ sourceId: string(),
52813
+ entryCount: number().int(),
52814
+ error: string().optional()
52815
+ }))
52816
+ }), {
52817
+ kind: "mutation",
52818
+ auth: "admin"
52819
+ }),
52820
+ listSources: customAction(object({}), array(WasteSourceRowSchema).readonly(), {
52821
+ kind: "mutation",
52822
+ auth: "admin"
52823
+ }),
52824
+ sourceFormSchema: customAction(object({}), unknown(), {
52825
+ kind: "mutation",
52826
+ auth: "admin"
52827
+ }),
52828
+ createSource: customAction(object({
52829
+ id: SourceIdSchema,
52830
+ label: string().min(1)
52831
+ }), object({ id: string() }), {
52832
+ kind: "mutation",
52833
+ auth: "admin"
52834
+ }),
52835
+ updateSource: customAction(object({
52836
+ id: string().min(1),
52837
+ label: string().min(1),
52838
+ module: string().min(1).optional(),
52839
+ argsJson: string().optional(),
52840
+ enabled: boolean().optional()
52841
+ }), object({ ok: literal(true) }), {
52842
+ kind: "mutation",
52843
+ auth: "admin"
52844
+ }),
52845
+ deleteSource: customAction(object({ id: string().min(1) }), object({ ok: literal(true) }), {
52846
+ kind: "mutation",
52847
+ auth: "admin"
52848
+ })
52849
+ });
52850
+ //#endregion
52815
52851
  //#region src/fetch-source.ts
52816
52852
  /**
52817
52853
  * Spawn the embedded Python bridge once per source fetch.
@@ -52956,11 +52992,56 @@ function createWasteCalendarSource(deps) {
52956
52992
  };
52957
52993
  }
52958
52994
  //#endregion
52995
+ //#region src/waste-source-schema.ts
52996
+ var WASTE_SOURCE_FORM_SCHEMA = { sections: [{
52997
+ id: "source",
52998
+ title: "Source",
52999
+ columns: 2,
53000
+ fields: [
53001
+ {
53002
+ type: "text",
53003
+ key: "label",
53004
+ label: "Label",
53005
+ description: "Shown under System → Calendars.",
53006
+ default: "New source"
53007
+ },
53008
+ {
53009
+ type: "text",
53010
+ key: "module",
53011
+ label: "Source module",
53012
+ description: "HACS provider name, e.g. `ics`, `junker_app`, `abfallnavi_de`.",
53013
+ default: "ics"
53014
+ },
53015
+ {
53016
+ type: "textarea",
53017
+ key: "argsJson",
53018
+ label: "Args (JSON object)",
53019
+ description: "Constructor kwargs for the HACS source, e.g. `{\"url\":\"https://…/calendar.ics\"}`.",
53020
+ rows: 4,
53021
+ default: "{}",
53022
+ span: 2
53023
+ },
53024
+ {
53025
+ type: "boolean",
53026
+ key: "enabled",
53027
+ label: "Enabled",
53028
+ default: true
53029
+ }
53030
+ ]
53031
+ }] };
53032
+ var WASTE_SOURCE_CREATE_VALUES = {
53033
+ label: "New source",
53034
+ module: "ics",
53035
+ argsJson: "{}",
53036
+ enabled: true
53037
+ };
53038
+ //#endregion
52959
53039
  //#region src/waste-collections.addon.ts
52960
53040
  /**
52961
53041
  * Waste Collections — hub-only calendar-source over the vendored HACS
52962
53042
  * `waste_collection_schedule` Python core (full source catalog).
52963
53043
  */
53044
+ var SOURCE_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
52964
53045
  var DAY_MS = 1440 * 60 * 1e3;
52965
53046
  var WasteCollectionsAddon = class extends BaseAddon {
52966
53047
  pythonPath = null;
@@ -53008,7 +53089,14 @@ var WasteCollectionsAddon = class extends BaseAddon {
53008
53089
  provider
53009
53090
  }],
53010
53091
  customActions: wasteCollectionsActions,
53011
- actionHandlers: { fetchNow: async (input) => this.fetchNow(input) }
53092
+ actionHandlers: {
53093
+ fetchNow: async (input) => this.fetchNow(input),
53094
+ listSources: async () => this.listSources(),
53095
+ sourceFormSchema: async () => WASTE_SOURCE_FORM_SCHEMA,
53096
+ createSource: async (input) => this.createSource(input),
53097
+ updateSource: async (input) => this.updateSource(input),
53098
+ deleteSource: async (input) => this.deleteSource(input)
53099
+ }
53012
53100
  };
53013
53101
  }
53014
53102
  async onConfigChanged() {
@@ -53017,6 +53105,49 @@ var WasteCollectionsAddon = class extends BaseAddon {
53017
53105
  this.persistCache();
53018
53106
  this.refreshAll("config-changed");
53019
53107
  }
53108
+ listSources() {
53109
+ return this.config.sources.map((s) => ({
53110
+ ...s,
53111
+ formValues: {
53112
+ label: s.label,
53113
+ module: s.module,
53114
+ argsJson: s.argsJson,
53115
+ enabled: s.enabled
53116
+ }
53117
+ }));
53118
+ }
53119
+ async createSource(input) {
53120
+ if (!SOURCE_ID_PATTERN.test(input.id)) throw new Error("id must be a lowercase slug (e.g. ics-home, junker)");
53121
+ if (this.config.sources.some((s) => s.id === input.id)) throw new Error(`source '${input.id}' already exists`);
53122
+ const row = WasteSourceConfigSchema.parse({
53123
+ ...WASTE_SOURCE_CREATE_VALUES,
53124
+ id: input.id,
53125
+ label: input.label
53126
+ });
53127
+ await this.updateGlobalSettings({ sources: [...this.config.sources, row] });
53128
+ return { id: input.id };
53129
+ }
53130
+ async updateSource(input) {
53131
+ const idx = this.config.sources.findIndex((s) => s.id === input.id);
53132
+ if (idx < 0) throw new Error(`source '${input.id}' not found`);
53133
+ const prev = this.config.sources[idx];
53134
+ const next = WasteSourceConfigSchema.parse({
53135
+ id: prev.id,
53136
+ label: input.label,
53137
+ module: input.module ?? prev.module,
53138
+ argsJson: input.argsJson ?? prev.argsJson,
53139
+ enabled: input.enabled ?? prev.enabled
53140
+ });
53141
+ parseSourceArgs(next);
53142
+ const sources = this.config.sources.map((s, i) => i === idx ? next : s);
53143
+ await this.updateGlobalSettings({ sources });
53144
+ return { ok: true };
53145
+ }
53146
+ async deleteSource(input) {
53147
+ if (!this.config.sources.some((s) => s.id === input.id)) throw new Error(`source '${input.id}' not found`);
53148
+ await this.updateGlobalSettings({ sources: this.config.sources.filter((s) => s.id !== input.id) });
53149
+ return { ok: true };
53150
+ }
53020
53151
  async fetchNow(input) {
53021
53152
  const targets = this.config.sources.filter((s) => {
53022
53153
  if (!s.enabled) return false;
@@ -53119,76 +53250,29 @@ var WasteCollectionsAddon = class extends BaseAddon {
53119
53250
  }
53120
53251
  globalSettingsSchema() {
53121
53252
  return this.schema({ sections: [{
53122
- id: "sources",
53123
- title: "Waste sources",
53124
- description: "Each row becomes one calendar under System → Calendars. `module` must be a HACS source name from the bundled catalog (978 providers). `args` are the provider kwargs — see the HACS docs for that module. Use Fetch now after editing.",
53125
- columns: 1,
53126
- fields: [
53127
- {
53128
- type: "editable-array",
53129
- key: "sources",
53130
- label: "Configured sources",
53131
- itemIdKey: "id",
53132
- itemFields: [
53133
- {
53134
- type: "text",
53135
- key: "id",
53136
- label: "Id",
53137
- description: "Stable id (calendar local id)."
53138
- },
53139
- {
53140
- type: "text",
53141
- key: "label",
53142
- label: "Label"
53143
- },
53144
- {
53145
- type: "text",
53146
- key: "module",
53147
- label: "Source module",
53148
- description: "e.g. `ics`, `junker_app`, `abfallnavi_de`."
53149
- },
53150
- {
53151
- type: "textarea",
53152
- key: "argsJson",
53153
- label: "Args (JSON object)",
53154
- description: "Constructor kwargs for the HACS source, e.g. `{\"url\":\"https://…/calendar.ics\"}`.",
53155
- rows: 4
53156
- },
53157
- {
53158
- type: "boolean",
53159
- key: "enabled",
53160
- label: "Enabled"
53161
- }
53162
- ],
53163
- defaultItem: {
53164
- id: "",
53165
- label: "New source",
53166
- module: "ics",
53167
- argsJson: "{}",
53168
- enabled: true
53169
- }
53170
- },
53171
- {
53172
- type: "number",
53173
- key: "fetchHour",
53174
- label: "Daily fetch hour (local)",
53175
- min: 0,
53176
- max: 23,
53177
- step: 1,
53178
- description: "Informational — the addon refreshes about once per day from boot; use Fetch now to force."
53179
- },
53180
- {
53181
- type: "addon-action-button",
53182
- key: "_fetchNow",
53183
- label: "Refresh",
53184
- buttonLabel: "Fetch now",
53185
- description: "Refresh every enabled source immediately.",
53186
- addonId: "waste-collections",
53187
- action: "fetchNow",
53188
- variant: "primary",
53189
- paramsStatic: {}
53190
- }
53191
- ]
53253
+ id: "refresh",
53254
+ title: "Refresh",
53255
+ description: "Sources are managed in the table above (Add / Edit). Each source becomes one calendar under System → Calendars.",
53256
+ columns: 2,
53257
+ fields: [{
53258
+ type: "number",
53259
+ key: "fetchHour",
53260
+ label: "Daily fetch hour (local)",
53261
+ min: 0,
53262
+ max: 23,
53263
+ step: 1,
53264
+ description: "Informational — the addon refreshes about once per day from boot; use Fetch now to force."
53265
+ }, {
53266
+ type: "addon-action-button",
53267
+ key: "_fetchNow",
53268
+ label: "Refresh",
53269
+ buttonLabel: "Fetch now",
53270
+ description: "Refresh every enabled source immediately.",
53271
+ addonId: "waste-collections",
53272
+ action: "fetchNow",
53273
+ variant: "primary",
53274
+ paramsStatic: {}
53275
+ }]
53192
53276
  }] });
53193
53277
  }
53194
53278
  };
@@ -52756,18 +52756,6 @@ DEFAULT_NATIVE_LEASE_SETTINGS.admission;
52756
52756
  var MB = 1024 * 1024;
52757
52757
  1024 * MB, 3072 * MB;
52758
52758
  //#endregion
52759
- //#region src/custom-actions.ts
52760
- var wasteCollectionsActions = defineCustomActions({ fetchNow: customAction(object({
52761
- /** When set, refresh only this configured source id; otherwise all enabled. */
52762
- sourceId: string().min(1).optional() }).optional(), object({
52763
- ok: literal(true),
52764
- refreshed: array(object({
52765
- sourceId: string(),
52766
- entryCount: number().int(),
52767
- error: string().optional()
52768
- }))
52769
- }), { kind: "mutation" }) });
52770
- //#endregion
52771
52759
  //#region src/waste-types.ts
52772
52760
  var WasteSourceConfigSchema = object({
52773
52761
  id: string().min(1),
@@ -52808,6 +52796,54 @@ var WasteFetchResultSchema = discriminatedUnion("ok", [object({
52808
52796
  error: string()
52809
52797
  })]);
52810
52798
  //#endregion
52799
+ //#region src/custom-actions.ts
52800
+ var SourceIdSchema = string().min(1).regex(/^[a-z0-9][a-z0-9-]{0,63}$/, "id must be a lowercase slug");
52801
+ var WasteSourceRowSchema = WasteSourceConfigSchema.extend({ formValues: record(string(), unknown()).optional() });
52802
+ var wasteCollectionsActions = defineCustomActions({
52803
+ fetchNow: customAction(object({
52804
+ /** When set, refresh only this configured source id; otherwise all enabled. */
52805
+ sourceId: string().min(1).optional() }).optional(), object({
52806
+ ok: literal(true),
52807
+ refreshed: array(object({
52808
+ sourceId: string(),
52809
+ entryCount: number().int(),
52810
+ error: string().optional()
52811
+ }))
52812
+ }), {
52813
+ kind: "mutation",
52814
+ auth: "admin"
52815
+ }),
52816
+ listSources: customAction(object({}), array(WasteSourceRowSchema).readonly(), {
52817
+ kind: "mutation",
52818
+ auth: "admin"
52819
+ }),
52820
+ sourceFormSchema: customAction(object({}), unknown(), {
52821
+ kind: "mutation",
52822
+ auth: "admin"
52823
+ }),
52824
+ createSource: customAction(object({
52825
+ id: SourceIdSchema,
52826
+ label: string().min(1)
52827
+ }), object({ id: string() }), {
52828
+ kind: "mutation",
52829
+ auth: "admin"
52830
+ }),
52831
+ updateSource: customAction(object({
52832
+ id: string().min(1),
52833
+ label: string().min(1),
52834
+ module: string().min(1).optional(),
52835
+ argsJson: string().optional(),
52836
+ enabled: boolean().optional()
52837
+ }), object({ ok: literal(true) }), {
52838
+ kind: "mutation",
52839
+ auth: "admin"
52840
+ }),
52841
+ deleteSource: customAction(object({ id: string().min(1) }), object({ ok: literal(true) }), {
52842
+ kind: "mutation",
52843
+ auth: "admin"
52844
+ })
52845
+ });
52846
+ //#endregion
52811
52847
  //#region src/fetch-source.ts
52812
52848
  /**
52813
52849
  * Spawn the embedded Python bridge once per source fetch.
@@ -52952,11 +52988,56 @@ function createWasteCalendarSource(deps) {
52952
52988
  };
52953
52989
  }
52954
52990
  //#endregion
52991
+ //#region src/waste-source-schema.ts
52992
+ var WASTE_SOURCE_FORM_SCHEMA = { sections: [{
52993
+ id: "source",
52994
+ title: "Source",
52995
+ columns: 2,
52996
+ fields: [
52997
+ {
52998
+ type: "text",
52999
+ key: "label",
53000
+ label: "Label",
53001
+ description: "Shown under System → Calendars.",
53002
+ default: "New source"
53003
+ },
53004
+ {
53005
+ type: "text",
53006
+ key: "module",
53007
+ label: "Source module",
53008
+ description: "HACS provider name, e.g. `ics`, `junker_app`, `abfallnavi_de`.",
53009
+ default: "ics"
53010
+ },
53011
+ {
53012
+ type: "textarea",
53013
+ key: "argsJson",
53014
+ label: "Args (JSON object)",
53015
+ description: "Constructor kwargs for the HACS source, e.g. `{\"url\":\"https://…/calendar.ics\"}`.",
53016
+ rows: 4,
53017
+ default: "{}",
53018
+ span: 2
53019
+ },
53020
+ {
53021
+ type: "boolean",
53022
+ key: "enabled",
53023
+ label: "Enabled",
53024
+ default: true
53025
+ }
53026
+ ]
53027
+ }] };
53028
+ var WASTE_SOURCE_CREATE_VALUES = {
53029
+ label: "New source",
53030
+ module: "ics",
53031
+ argsJson: "{}",
53032
+ enabled: true
53033
+ };
53034
+ //#endregion
52955
53035
  //#region src/waste-collections.addon.ts
52956
53036
  /**
52957
53037
  * Waste Collections — hub-only calendar-source over the vendored HACS
52958
53038
  * `waste_collection_schedule` Python core (full source catalog).
52959
53039
  */
53040
+ var SOURCE_ID_PATTERN = /^[a-z0-9][a-z0-9-]{0,63}$/;
52960
53041
  var DAY_MS = 1440 * 60 * 1e3;
52961
53042
  var WasteCollectionsAddon = class extends BaseAddon {
52962
53043
  pythonPath = null;
@@ -53004,7 +53085,14 @@ var WasteCollectionsAddon = class extends BaseAddon {
53004
53085
  provider
53005
53086
  }],
53006
53087
  customActions: wasteCollectionsActions,
53007
- actionHandlers: { fetchNow: async (input) => this.fetchNow(input) }
53088
+ actionHandlers: {
53089
+ fetchNow: async (input) => this.fetchNow(input),
53090
+ listSources: async () => this.listSources(),
53091
+ sourceFormSchema: async () => WASTE_SOURCE_FORM_SCHEMA,
53092
+ createSource: async (input) => this.createSource(input),
53093
+ updateSource: async (input) => this.updateSource(input),
53094
+ deleteSource: async (input) => this.deleteSource(input)
53095
+ }
53008
53096
  };
53009
53097
  }
53010
53098
  async onConfigChanged() {
@@ -53013,6 +53101,49 @@ var WasteCollectionsAddon = class extends BaseAddon {
53013
53101
  this.persistCache();
53014
53102
  this.refreshAll("config-changed");
53015
53103
  }
53104
+ listSources() {
53105
+ return this.config.sources.map((s) => ({
53106
+ ...s,
53107
+ formValues: {
53108
+ label: s.label,
53109
+ module: s.module,
53110
+ argsJson: s.argsJson,
53111
+ enabled: s.enabled
53112
+ }
53113
+ }));
53114
+ }
53115
+ async createSource(input) {
53116
+ if (!SOURCE_ID_PATTERN.test(input.id)) throw new Error("id must be a lowercase slug (e.g. ics-home, junker)");
53117
+ if (this.config.sources.some((s) => s.id === input.id)) throw new Error(`source '${input.id}' already exists`);
53118
+ const row = WasteSourceConfigSchema.parse({
53119
+ ...WASTE_SOURCE_CREATE_VALUES,
53120
+ id: input.id,
53121
+ label: input.label
53122
+ });
53123
+ await this.updateGlobalSettings({ sources: [...this.config.sources, row] });
53124
+ return { id: input.id };
53125
+ }
53126
+ async updateSource(input) {
53127
+ const idx = this.config.sources.findIndex((s) => s.id === input.id);
53128
+ if (idx < 0) throw new Error(`source '${input.id}' not found`);
53129
+ const prev = this.config.sources[idx];
53130
+ const next = WasteSourceConfigSchema.parse({
53131
+ id: prev.id,
53132
+ label: input.label,
53133
+ module: input.module ?? prev.module,
53134
+ argsJson: input.argsJson ?? prev.argsJson,
53135
+ enabled: input.enabled ?? prev.enabled
53136
+ });
53137
+ parseSourceArgs(next);
53138
+ const sources = this.config.sources.map((s, i) => i === idx ? next : s);
53139
+ await this.updateGlobalSettings({ sources });
53140
+ return { ok: true };
53141
+ }
53142
+ async deleteSource(input) {
53143
+ if (!this.config.sources.some((s) => s.id === input.id)) throw new Error(`source '${input.id}' not found`);
53144
+ await this.updateGlobalSettings({ sources: this.config.sources.filter((s) => s.id !== input.id) });
53145
+ return { ok: true };
53146
+ }
53016
53147
  async fetchNow(input) {
53017
53148
  const targets = this.config.sources.filter((s) => {
53018
53149
  if (!s.enabled) return false;
@@ -53115,76 +53246,29 @@ var WasteCollectionsAddon = class extends BaseAddon {
53115
53246
  }
53116
53247
  globalSettingsSchema() {
53117
53248
  return this.schema({ sections: [{
53118
- id: "sources",
53119
- title: "Waste sources",
53120
- description: "Each row becomes one calendar under System → Calendars. `module` must be a HACS source name from the bundled catalog (978 providers). `args` are the provider kwargs — see the HACS docs for that module. Use Fetch now after editing.",
53121
- columns: 1,
53122
- fields: [
53123
- {
53124
- type: "editable-array",
53125
- key: "sources",
53126
- label: "Configured sources",
53127
- itemIdKey: "id",
53128
- itemFields: [
53129
- {
53130
- type: "text",
53131
- key: "id",
53132
- label: "Id",
53133
- description: "Stable id (calendar local id)."
53134
- },
53135
- {
53136
- type: "text",
53137
- key: "label",
53138
- label: "Label"
53139
- },
53140
- {
53141
- type: "text",
53142
- key: "module",
53143
- label: "Source module",
53144
- description: "e.g. `ics`, `junker_app`, `abfallnavi_de`."
53145
- },
53146
- {
53147
- type: "textarea",
53148
- key: "argsJson",
53149
- label: "Args (JSON object)",
53150
- description: "Constructor kwargs for the HACS source, e.g. `{\"url\":\"https://…/calendar.ics\"}`.",
53151
- rows: 4
53152
- },
53153
- {
53154
- type: "boolean",
53155
- key: "enabled",
53156
- label: "Enabled"
53157
- }
53158
- ],
53159
- defaultItem: {
53160
- id: "",
53161
- label: "New source",
53162
- module: "ics",
53163
- argsJson: "{}",
53164
- enabled: true
53165
- }
53166
- },
53167
- {
53168
- type: "number",
53169
- key: "fetchHour",
53170
- label: "Daily fetch hour (local)",
53171
- min: 0,
53172
- max: 23,
53173
- step: 1,
53174
- description: "Informational — the addon refreshes about once per day from boot; use Fetch now to force."
53175
- },
53176
- {
53177
- type: "addon-action-button",
53178
- key: "_fetchNow",
53179
- label: "Refresh",
53180
- buttonLabel: "Fetch now",
53181
- description: "Refresh every enabled source immediately.",
53182
- addonId: "waste-collections",
53183
- action: "fetchNow",
53184
- variant: "primary",
53185
- paramsStatic: {}
53186
- }
53187
- ]
53249
+ id: "refresh",
53250
+ title: "Refresh",
53251
+ description: "Sources are managed in the table above (Add / Edit). Each source becomes one calendar under System → Calendars.",
53252
+ columns: 2,
53253
+ fields: [{
53254
+ type: "number",
53255
+ key: "fetchHour",
53256
+ label: "Daily fetch hour (local)",
53257
+ min: 0,
53258
+ max: 23,
53259
+ step: 1,
53260
+ description: "Informational — the addon refreshes about once per day from boot; use Fetch now to force."
53261
+ }, {
53262
+ type: "addon-action-button",
53263
+ key: "_fetchNow",
53264
+ label: "Refresh",
53265
+ buttonLabel: "Fetch now",
53266
+ description: "Refresh every enabled source immediately.",
53267
+ addonId: "waste-collections",
53268
+ action: "fetchNow",
53269
+ variant: "primary",
53270
+ paramsStatic: {}
53271
+ }]
53188
53272
  }] });
53189
53273
  }
53190
53274
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@camstack/addon-waste-collections",
3
- "version": "0.1.2",
3
+ "version": "0.1.3",
4
4
  "description": "Waste collection schedules via the vendored HACS waste_collection_schedule Python core — full source catalog as calendar-source.",
5
5
  "keywords": [
6
6
  "camstack",