@devdogsuga/backstage 0.1.4 → 0.1.5

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
@@ -112,8 +112,10 @@ entry for them (`envFree` in the command tree).
112
112
  ## Shared logins: `creds`
113
113
 
114
114
  The club's shared logins (Instagram, Canva, ArchPass, Linktree, later project
115
- API keys) live only in the **Shared Accounts** collection of the DevDogs
116
- Bitwarden organization. `creds` hands them to officers as Bitwarden Sends:
115
+ API keys) live in the DevDogs Bitwarden organization, in whatever collection
116
+ suits them. `creds` hands any login or secure note there to officers as a
117
+ Bitwarden Send. Personal items, other organizations' items and production
118
+ secrets (which are in Secrets Manager, not the vault) are out of its reach.
117
119
 
118
120
  - **`send`** picks items and recipients (officers by name or by role, read live
119
121
  from production: everyone holding a role other than `Member`, by UGA MyID
@@ -122,16 +124,18 @@ Bitwarden organization. `creds` hands them to officers as Bitwarden Sends:
122
124
  recipients and deleted 30 days out. Removing an address revokes it. Addresses
123
125
  off the roster need `--allow-email`. Non-interactive: `--item`, `--to`,
124
126
  `--role`, `--yes`.
125
- - **`add`** saves a new login into the collection (org-owned), then sends it.
126
- The password is typed at a hidden prompt, or read with `--password-stdin`;
127
- never argv.
127
+ - **`add`** saves a new login to the organization, then sends it. It goes in
128
+ the one collection you can see, or the one you pick (`--collection` with no
129
+ terminal). The password is typed at a hidden prompt, or read with
130
+ `--password-stdin`; never argv.
128
131
  - **`renew`** pushes each Send's deletion 30 days out in place (the link stays)
129
132
  and re-syncs its recipients and body from the item. It asks first for any Send
130
133
  with more than 7 days left.
131
134
  - **`list`** prints items, recipients and expiry (`--json` for scripts).
132
135
  - **`report`** regenerates the **Shared Accounts** Linear document (Platform &
133
- DevOps initiative), which `send`, `add` and `renew` also do. It is generated
134
- whole; edits there are overwritten.
136
+ DevOps initiative), which `send`, `add` and `renew` also do. It lists only
137
+ items that have been shared, and is generated whole; edits there are
138
+ overwritten.
135
139
 
136
140
  Each item's custom fields are the only access record: `Recipients`, `Owner`,
137
141
  `Send ID`, `Send link`, `Send expires`, `Send account`. Sends belong to the
@@ -928,7 +928,7 @@ const completionsCommand = {
928
928
  const ITEM = {
929
929
  flag: "--item",
930
930
  value: "<name>",
931
- summary: "A Shared Accounts item, by name or id. Repeat for several."
931
+ summary: "A DevDogs organization item, by name or id. Repeat for several."
932
932
  };
933
933
  const TO = {
934
934
  flag: "--to",
@@ -982,7 +982,7 @@ const credsCommand = {
982
982
  {
983
983
  name: "add",
984
984
  envFree: true,
985
- summary: "Save a new shared login to the collection, then send it.",
985
+ summary: "Save a new login to the DevDogs organization, then send it.",
986
986
  options: [
987
987
  {
988
988
  flag: "--name",
@@ -1004,6 +1004,11 @@ const credsCommand = {
1004
1004
  value: "<name>",
1005
1005
  summary: "The officer responsible for the account."
1006
1006
  },
1007
+ {
1008
+ flag: "--collection",
1009
+ value: "<name>",
1010
+ summary: "Where to save it, when you can see more than one."
1011
+ },
1007
1012
  {
1008
1013
  flag: "--password-stdin",
1009
1014
  summary: "Read the password from stdin, for scripts."
@@ -1,5 +1,5 @@
1
1
  import { C as discoverRepoRoot, T as cliName, b as ownPackageDir, c as reportDevtoolsError, f as isNoEnv, l as reportDevtoolsFailure, n as captureDevtoolsError, o as initDevtoolsTelemetry, p as isNonInteractive, w as findRepoRoot, x as ownVersion } from "./telemetry-Bjoz29Hl.js";
2
- import { a as recordEnteredTier, f as helpPath, i as beginInvocation, l as dbPush, m as renderHelp, n as bareGroupStartPath, o as recordResolved, p as renderCommandList, r as runMenu, s as reproducibleCommand, t as catalog, u as dbPushDryRun } from "./catalog-BuNLUu08.js";
2
+ import { a as recordEnteredTier, f as helpPath, i as beginInvocation, l as dbPush, m as renderHelp, n as bareGroupStartPath, o as recordResolved, p as renderCommandList, r as runMenu, s as reproducibleCommand, t as catalog, u as dbPushDryRun } from "./catalog-BUOwCd2Q.js";
3
3
  import { a as repoPeerUrl, n as loadEnv, r as loadEnvLoad, t as getEnvSync } from "./peers-B0KDkI6a.js";
4
4
  import { t as nonEmpty } from "./connection-D3XsOE3S.js";
5
5
  import { a as dryRunKind, l as wouldRunLine, o as isDryRun } from "./options-BTjOf5KP.js";
@@ -5270,7 +5270,7 @@ const HANDLERS = {
5270
5270
  graphics: async (rest) => (await import("./commands-Dv5TXjvb.js")).handleGraphics(rest),
5271
5271
  qr: async (rest) => (await import("./commands-Bk7IbhPD.js")).handleQr(rest),
5272
5272
  newsletter: async (rest) => (await import("./commands-D345F-Od.js")).handleNewsletter(rest),
5273
- creds: async (rest) => (await import("./commands-Dl3UlhBf.js")).handleCreds(rest)
5273
+ creds: async (rest) => (await import("./commands-BMQ1OZIr.js")).handleCreds(rest)
5274
5274
  };
5275
5275
  /**
5276
5276
  * Names that were commands of the contributor CLI, refused with where they
@@ -4,7 +4,7 @@ import { a as explainError, i as explain, o as unwrap, r as errorMessage, t as U
4
4
  import { c as EnvDocument, i as readPasswordFromVault, n as bwArgs, r as openVault, s as bwCommand } from "./vault-C8LYJFOQ.js";
5
5
  import { t as DONE } from "./dispatch-D048O65I.js";
6
6
  import { resolve } from "node:path";
7
- import { confirm, log, multiselect, note, password, text } from "@clack/prompts";
7
+ import { confirm, log, multiselect, note, password, select, text } from "@clack/prompts";
8
8
  import { readFile } from "node:fs/promises";
9
9
  import { spawn } from "node:child_process";
10
10
  import { parseArgs } from "node:util";
@@ -51,12 +51,12 @@ var CredsError = class extends Error {
51
51
  //#endregion
52
52
  //#region src/creds/item.ts
53
53
  /**
54
- * A shared account as `creds` sees it: a Bitwarden item in the Shared Accounts
55
- * collection, plus the custom fields that record who has it.
54
+ * A shared account as `creds` sees it: a login or secure note in the DevDogs
55
+ * Bitwarden organization, plus the custom fields that record who has it.
56
56
  *
57
57
  * Those fields are the only access record. The Send, the Linear report and
58
58
  * every "who has Canva?" answer are generated from them, so they are plain
59
- * text fields anyone in the collection can read and correct in Bitwarden
59
+ * text fields anyone with the item can read and correct in Bitwarden
60
60
  * itself:
61
61
  *
62
62
  * Recipients comma-separated UGA emails, the Send's `--emails` list
@@ -194,8 +194,8 @@ function isoDay(date) {
194
194
  //#endregion
195
195
  //#region src/creds/bitwarden.ts
196
196
  /**
197
- * The Shared Accounts collection and the Sends made from it, through the
198
- * bundled `bw` on the officer's personal session (`bws/vault.ts`).
197
+ * The DevDogs Bitwarden organization's items and the Sends made from them,
198
+ * through the bundled `bw` on the officer's personal session (`bws/vault.ts`).
199
199
  *
200
200
  * Sends belong to the account that creates them, not to the organization, so
201
201
  * this always runs as a person and never on CI.
@@ -203,20 +203,23 @@ function isoDay(date) {
203
203
  * ## Secret handling
204
204
  *
205
205
  * * Every value travels to `bw` on **stdin**, base64 JSON as `bw create`,
206
- * `bw edit` and `bw send create|edit` document. argv carries only ids,
207
- * the collection id and the session key `bwArgs` already passes.
206
+ * `bw edit` and `bw send create|edit` document. argv carries only ids and
207
+ * the session key `bwArgs` already passes.
208
208
  * * `bw`'s stdout is parsed and never printed: an item or a Send read back
209
209
  * holds the password.
210
210
  * * `bw`'s stderr is only shown through `CredsError`, which scrubs every value
211
211
  * registered so far.
212
212
  *
213
- * ## The collection fence
213
+ * ## The organization fence
214
214
  *
215
- * `creds` reads and writes items in ONE collection, found by name. An item
216
- * that is not in it (a personal login, a production secret) is never listed,
217
- * never edited and never sent; `updateItem` re-checks before every write.
215
+ * `creds` reads and writes the logins and secure notes of ONE organization,
216
+ * found by name. Anything else the account can see (a personal login, another
217
+ * organization's item) is never listed, never edited and never sent;
218
+ * `updateItem` re-checks before every write. Production secrets are not
219
+ * vault items at all: they live in Secrets Manager, which `bw` cannot read.
218
220
  */
219
- const COLLECTION_NAME = "Shared Accounts";
221
+ /** Matched case-insensitively against the organizations the account belongs to. */
222
+ const ORGANIZATION_NAME = "DevDogs";
220
223
  function encode(value) {
221
224
  return Buffer.from(JSON.stringify(value)).toString("base64");
222
225
  }
@@ -249,7 +252,7 @@ function spawnRunner(key) {
249
252
  });
250
253
  }
251
254
  let runnerOverride;
252
- /** Opens the vault (asking first) and the Shared Accounts collection in it. */
255
+ /** Opens the vault (asking first) and finds the DevDogs organization in it. */
253
256
  async function connectSharedVault() {
254
257
  let runner = runnerOverride;
255
258
  if (!runner) {
@@ -259,7 +262,7 @@ async function connectSharedVault() {
259
262
  }
260
263
  const shared = new SharedVault(runner);
261
264
  await shared.sync();
262
- await shared.collection();
265
+ await shared.organization();
263
266
  return shared;
264
267
  }
265
268
  var SharedVault = class {
@@ -287,34 +290,45 @@ var SharedVault = class {
287
290
  async account() {
288
291
  return (await this.json(["status"])).userEmail ?? "unknown";
289
292
  }
290
- /** The one collection named {@link COLLECTION_NAME} this account can see. */
291
- async collection() {
293
+ /** The one organization named like {@link ORGANIZATION_NAME} this account belongs to. */
294
+ async organization() {
292
295
  if (this.found) return this.found;
293
- const matches = (await this.json(["list", "collections"])).filter((c) => c.name === COLLECTION_NAME);
294
- if (matches.length !== 1) throw new CredsError(matches.length === 0 ? `No "${COLLECTION_NAME}" collection is visible to your Bitwarden account. Ask a DevDogs organization admin to create it or give you access.` : `${matches.length} collections are called "${COLLECTION_NAME}"; rename the extras so \`creds\` cannot pick the wrong one.`);
296
+ const matches = (await this.json(["list", "organizations"])).filter((o) => o.name.toLowerCase().includes(ORGANIZATION_NAME.toLowerCase()));
297
+ if (matches.length !== 1) throw new CredsError(matches.length === 0 ? `Your Bitwarden account is not in the ${ORGANIZATION_NAME} organization. Ask an organization admin to invite you.` : `Your Bitwarden account is in ${matches.length} organizations named like "${ORGANIZATION_NAME}" (${matches.map((o) => o.name).join(", ")}); creds cannot tell which one to use.`);
295
298
  this.found = matches[0];
296
299
  return this.found;
297
300
  }
298
- inCollection(raw, collection) {
299
- return raw.organizationId === collection.organizationId && (raw.collectionIds ?? []).includes(collection.id);
301
+ shareable(raw, org) {
302
+ return raw.organizationId === org.id && (raw.type === ITEM_TYPE.login || raw.type === ITEM_TYPE.secureNote);
300
303
  }
301
- /** Every item in the collection, by name. */
304
+ /** Every login and secure note in the organization, by name. */
302
305
  async listItems() {
303
- const collection = await this.collection();
306
+ const org = await this.organization();
304
307
  return (await this.json([
305
308
  "list",
306
309
  "items",
307
- "--collectionid",
308
- collection.id
309
- ])).filter((item) => this.inCollection(item, collection)).map(parseItem).sort((a, b) => a.name.localeCompare(b.name));
310
+ "--organizationid",
311
+ org.id
312
+ ])).filter((item) => this.shareable(item, org)).map(parseItem).sort((a, b) => a.name.localeCompare(b.name));
310
313
  }
311
- /** A new login, owned by the organization and placed in the collection. */
314
+ /** The organization's collections this account can see, by name. */
315
+ async collections() {
316
+ const org = await this.organization();
317
+ return (await this.json([
318
+ "list",
319
+ "collections",
320
+ "--organizationid",
321
+ org.id
322
+ ])).filter((c) => c.organizationId === org.id).sort((a, b) => a.name.localeCompare(b.name));
323
+ }
324
+ /** A new login, owned by the organization and placed in `collection`. */
312
325
  async createLogin(input) {
313
326
  registerSecret(input.password);
314
- const collection = await this.collection();
327
+ const org = await this.organization();
328
+ if (input.collection.organizationId !== org.id) throw new CredsError(`The "${input.collection.name}" collection is not in the ${org.name} organization.`);
315
329
  const item = {
316
- organizationId: collection.organizationId,
317
- collectionIds: [collection.id],
330
+ organizationId: org.id,
331
+ collectionIds: [input.collection.id],
318
332
  folderId: null,
319
333
  type: 1,
320
334
  name: input.name,
@@ -338,10 +352,10 @@ var SharedVault = class {
338
352
  };
339
353
  return parseItem(await this.json(["create", "item"], encode(item)));
340
354
  }
341
- /** Saves `raw`, which must still be an item of the collection. */
355
+ /** Saves `raw`, which must still be a login or secure note of the organization. */
342
356
  async updateItem(raw) {
343
- const collection = await this.collection();
344
- if (!this.inCollection(raw, collection)) throw new CredsError(`"${raw.name}" is not in the ${COLLECTION_NAME} collection; creds will not change it.`);
357
+ const org = await this.organization();
358
+ if (!this.shareable(raw, org)) throw new CredsError(`"${raw.name}" is not a login or secure note of the ${org.name} organization; creds will not change it.`);
345
359
  return parseItem(await this.json([
346
360
  "edit",
347
361
  "item",
@@ -420,7 +434,7 @@ function sendName(item) {
420
434
  }
421
435
  /** Private to the sender: how to find the item this Send came from. */
422
436
  function sendNotes(item) {
423
- return `Made by backstage creds from the ${COLLECTION_NAME} item "${item.name}" (${item.id}).`;
437
+ return `Made by backstage creds from the ${ORGANIZATION_NAME} item "${item.name}" (${item.id}).`;
424
438
  }
425
439
  function deletionDate(now) {
426
440
  return new Date(now.getTime() + 2592e6).toISOString();
@@ -653,17 +667,18 @@ function cell(text) {
653
667
  /** The document's markdown. Pure: `now` decides the statuses. */
654
668
  function renderReport(items, now, roster) {
655
669
  const lines = [
656
- "> Generated by `backstage creds` from the Shared Accounts collection in the DevDogs Bitwarden organization. Manual edits are overwritten on the next run; change the item's fields in Bitwarden, or run `backstage creds send`.",
670
+ "> Generated by `backstage creds` from the DevDogs Bitwarden organization: every item shared as a Send. Manual edits are overwritten on the next run; change the item's fields in Bitwarden, or run `backstage creds send`.",
657
671
  "",
658
672
  `Last generated ${now.toISOString().slice(0, 16).replace("T", " ")} UTC. Sends are deleted 30 days after they were last renewed; **Expires Soon** means within 7 days, so run \`backstage creds renew\`.`,
659
673
  ""
660
674
  ];
661
- if (items.length === 0) {
662
- lines.push("No shared accounts in the collection yet.");
675
+ const shared = items.filter((i) => i.recipients.length > 0 || i.send !== void 0);
676
+ if (shared.length === 0) {
677
+ lines.push("Nothing has been shared yet.");
663
678
  return `${lines.join("\n")}\n`;
664
679
  }
665
680
  lines.push("| Credential | Account | Recipients | Owner | Status | Expires | Send |", "| --- | --- | --- | --- | --- | --- | --- |");
666
- for (const item of items) {
681
+ for (const item of shared) {
667
682
  const recipients = item.recipients.length > 0 ? item.recipients.map((e) => describeRecipient(roster, e)).join(", ") : "—";
668
683
  const link = item.send?.link ? `[Open](${item.send.link})` : "—";
669
684
  lines.push(`| ${[
@@ -729,7 +744,7 @@ async function writeReport(token, documentId, content, fetchImpl = fetch) {
729
744
  * shared as Bitwarden Sends that only the officers they name can open.
730
745
  *
731
746
  * send pick shared accounts and officers; create or update one Send each
732
- * add save a new login into the Shared Accounts collection, then send it
747
+ * add save a new login to the DevDogs organization, then send it
733
748
  * renew push every Send's deletion 30 days out and re-sync its recipients
734
749
  * list what is shared with whom, until when
735
750
  * report regenerate the Shared Accounts Linear document
@@ -738,8 +753,8 @@ async function writeReport(token, documentId, content, fetchImpl = fetch) {
738
753
  *
739
754
  * ## Where things live
740
755
  *
741
- * The values live only in the DevDogs Bitwarden organization's Shared
742
- * Accounts collection. Each item's custom fields record its recipients and its
756
+ * The values live only in the DevDogs Bitwarden organization: any login or
757
+ * secure note in it can be shared. Each item's custom fields record its recipients and its
743
758
  * Send (`item.ts`); the Linear document is generated from them (`report.ts`);
744
759
  * the roster of who may receive one is read live from production
745
760
  * (`roster.ts`).
@@ -790,6 +805,7 @@ function parse(argv) {
790
805
  url: { type: "string" },
791
806
  username: { type: "string" },
792
807
  owner: { type: "string" },
808
+ collection: { type: "string" },
793
809
  "password-stdin": { type: "boolean" },
794
810
  document: { type: "string" },
795
811
  "dry-run": { type: "boolean" }
@@ -831,7 +847,7 @@ function findItem(items, typed) {
831
847
  }
832
848
  async function pickItems(items, typed, message) {
833
849
  if (typed && typed.length > 0) return typed.map((t) => findItem(items, t));
834
- if (items.length === 0) throw new UsageError("The Shared Accounts collection is empty. Add one with `backstage creds add`.");
850
+ if (items.length === 0) throw new UsageError("The DevDogs organization has no logins you can see. Add one with `backstage creds add`.");
835
851
  if (!interactive()) throw new UsageError("Name the accounts with --item <name>.");
836
852
  const ids = unwrap(await multiselect({
837
853
  message,
@@ -1023,17 +1039,40 @@ async function ask(given, message, required) {
1023
1039
  validate: required ? (v) => v?.trim() ? void 0 : "Required." : void 0
1024
1040
  }))?.trim() || void 0;
1025
1041
  }
1042
+ /** `--collection`, the only one there is, or a choice. Every organization item needs one. */
1043
+ async function pickCollection(vault, typed) {
1044
+ const all = await vault.collections();
1045
+ if (typed !== void 0) {
1046
+ const needle = typed.trim().toLowerCase();
1047
+ const match = all.find((c) => c.name.toLowerCase() === needle);
1048
+ if (!match) throw new UsageError(`No collection called "${typed}" that you can see. There are: ${all.map((c) => c.name).join(", ") || "none"}.`);
1049
+ return match;
1050
+ }
1051
+ if (all.length === 0) throw new UsageError("You cannot see any collection in the DevDogs organization to save to. Ask an organization admin for access to one.");
1052
+ if (all.length === 1) return all[0];
1053
+ if (!interactive()) throw new UsageError(`Name the collection with --collection: ${all.map((c) => c.name).join(", ")}.`);
1054
+ const id = unwrap(await select({
1055
+ message: "Which collection?",
1056
+ options: all.map((c) => ({
1057
+ value: c.id,
1058
+ label: c.name
1059
+ }))
1060
+ }));
1061
+ return all.find((c) => c.id === id);
1062
+ }
1026
1063
  async function runAdd(vault, values, deps) {
1027
1064
  const existing = await vault.listItems();
1028
1065
  const name = await ask(values.name, "Name? (e.g. Instagram)", true);
1029
- if (existing.some((i) => i.name.toLowerCase() === name.toLowerCase())) throw new UsageError(`"${name}" is already in the collection. Use \`backstage creds send --item "${name}"\`.`);
1066
+ if (existing.some((i) => i.name.toLowerCase() === name.toLowerCase())) throw new UsageError(`"${name}" is already in the organization. Use \`backstage creds send --item "${name}"\`.`);
1030
1067
  const url = await ask(values.url, "Login page URL? (optional)", false);
1031
1068
  const username = await ask(values.username, "Username or email? (optional)", false);
1032
1069
  const password = await readPassword(values, deps);
1033
1070
  registerSecret(password);
1034
1071
  const notes = interactive() ? await ask(void 0, "Notes for recipients? (optional, never printed)", false) : void 0;
1035
1072
  const owner = await ask(values.owner, "Owner? (the officer responsible)", false);
1073
+ const collection = await pickCollection(vault, values.collection);
1036
1074
  const item = await vault.createLogin({
1075
+ collection,
1037
1076
  name,
1038
1077
  url,
1039
1078
  username,
@@ -1041,7 +1080,7 @@ async function runAdd(vault, values, deps) {
1041
1080
  notes,
1042
1081
  owner
1043
1082
  });
1044
- say(`Saved ${item.name} to the Shared Accounts collection.`, "success");
1083
+ say(`Saved ${item.name} to the ${collection.name} collection.`, "success");
1045
1084
  await sendItems(vault, [item], values, deps);
1046
1085
  }
1047
1086
  async function runRenew(vault, values, deps) {
package/dist/launch.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { C as discoverRepoRoot, E as setCliName, S as RepoNotFoundError, T as cliName, _ as installFailureLog, d as hasYes, g as stripTierFlag, h as stripNoEnvFlag, m as setNoEnv, n as captureDevtoolsError, o as initDevtoolsTelemetry, p as isNonInteractive, s as lastSentryEventId } from "./telemetry-Bjoz29Hl.js";
2
- import { c as setMenuEnvHook, d as formatCommand, f as helpPath, n as bareGroupStartPath, t as catalog } from "./catalog-BuNLUu08.js";
2
+ import { c as setMenuEnvHook, d as formatCommand, f as helpPath, n as bareGroupStartPath, t as catalog } from "./catalog-BUOwCd2Q.js";
3
3
  import { i as loadEnvSession, r as loadEnvLoad } from "./peers-B0KDkI6a.js";
4
4
  import { t as nonEmpty } from "./connection-D3XsOE3S.js";
5
5
  import { c as setDryRun, o as isDryRun, s as resolveDryRun } from "./options-BTjOf5KP.js";
@@ -322,7 +322,7 @@ async function launchWith(argv, options) {
322
322
  */
323
323
  async function launch(argv) {
324
324
  await launchWith(argv, { dispatch: async (args) => {
325
- const { main } = await import("./cli-BI5Vn9sR.js");
325
+ const { main } = await import("./cli-B2S6gw9o.js");
326
326
  await main(args);
327
327
  } });
328
328
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@devdogsuga/backstage",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Officer and production CLI for DevDogsUGA: deploys, env sync with Bitwarden and GitHub, the migration planner role, club graphics and QR codes, GitHub rulesets and settings, and the newsletter. Ships built JS; run it anywhere with `pnpm dlx @devdogsuga/backstage` (no checkout needed to start; commands that read a checkout say so).",
@@ -40,10 +40,10 @@
40
40
  "tsx": "^4.23.12",
41
41
  "typescript": "^6.0.3",
42
42
  "zod": "4.4.3",
43
- "@devdogsuga/telemetry": "0.1.3",
44
- "@devdogsuga/brand": "0.1.5",
45
43
  "@devdogsuga/events": "0.1.5",
46
- "@devdogsuga/newsletter": "0.1.9"
44
+ "@devdogsuga/newsletter": "0.1.10",
45
+ "@devdogsuga/brand": "0.1.5",
46
+ "@devdogsuga/telemetry": "0.1.3"
47
47
  },
48
48
  "peerDependencies": {
49
49
  "@devdogsuga/db": "*",
@@ -65,8 +65,8 @@
65
65
  "tsdown": "^0.23.0",
66
66
  "vitest": "^4.1.11",
67
67
  "@devdogsuga/cli-core": "0.0.0",
68
- "@devdogsuga/db": "0.1.3",
69
68
  "@devdogsuga/config": "0.1.2",
69
+ "@devdogsuga/db": "0.1.3",
70
70
  "@devdogsuga/env": "0.1.5"
71
71
  },
72
72
  "scripts": {