@unisonlabs/sixminute-admin 0.1.1261-next → 0.1.1263-next

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.
@@ -16,12 +16,15 @@ var __export = (target, all) => {
16
16
 
17
17
  // tools/admin/src/main.ts
18
18
  import { spawn } from "node:child_process";
19
- import { readFileSync as readFileSync4 } from "node:fs";
19
+ import { readFileSync as readFileSync5 } from "node:fs";
20
20
  import { homedir } from "node:os";
21
21
  import { fileURLToPath } from "node:url";
22
22
 
23
23
  // tools/admin/src/build-info.ts
24
- var BUILT = { version: "0.1.1261-next", commit: "954f16f8ffc4f8282d15240934f4fb969246915e", surface: "e4b83a8db444655b" };
24
+ var BUILT = { version: "0.1.1263-next", commit: "ad5a49aafefa58571d742f90fc5fdb1050d842a8", surface: "301dc344f2374600" };
25
+
26
+ // tools/admin/src/run.ts
27
+ import { readFileSync as readFileSync4, writeFileSync as writeFileSync2 } from "node:fs";
25
28
 
26
29
  // node_modules/.bun/zod@3.25.76/node_modules/zod/v3/external.js
27
30
  var exports_external = {};
@@ -4102,6 +4105,53 @@ var OperatorAuditQuerySchema = exports_external.object({
4102
4105
  limit: exports_external.coerce.number().int().min(1).max(1000).default(100)
4103
4106
  });
4104
4107
  var OperatorAuditListSchema = exports_external.object({ actions: exports_external.array(OperatorActionSchema) });
4108
+ // packages/contracts/src/narrative-activities.ts
4109
+ var NARRATIVE_PRIMARY_LANGUAGES = ["nl", "en", "de", "fr", "es", "it", "pt", "pl", "sv", "da"];
4110
+ var NarrativePrimaryLanguageSchema = exports_external.enum(NARRATIVE_PRIMARY_LANGUAGES);
4111
+ var NarrativeActivitySchema = exports_external.object({
4112
+ key: exports_external.string().regex(/^[a-z][a-z0-9_]{0,39}$/, "a key of lowercase letters, digits and underscores, starting with a letter"),
4113
+ when: exports_external.string().trim().min(1).max(800),
4114
+ patterns: exports_external.object(Object.fromEntries(NARRATIVE_PRIMARY_LANGUAGES.map((tag) => [tag, exports_external.string().trim().min(1).max(200).optional()]))).strict().refine((patterns) => Object.values(patterns).some((pattern) => pattern !== undefined), "at least one pattern")
4115
+ });
4116
+ var NarrativeActivitiesSchema = exports_external.array(NarrativeActivitySchema).min(1).max(40).refine((rows) => new Set(rows.map((row) => row.key)).size === rows.length, "every key once");
4117
+ var PUBLIC_MAIL_DOMAINS = new Set([
4118
+ "gmail.com",
4119
+ "googlemail.com",
4120
+ "outlook.com",
4121
+ "hotmail.com",
4122
+ "live.com",
4123
+ "msn.com",
4124
+ "icloud.com",
4125
+ "me.com",
4126
+ "mac.com",
4127
+ "yahoo.com",
4128
+ "aol.com",
4129
+ "mail.com",
4130
+ "proton.me",
4131
+ "protonmail.com",
4132
+ "gmx.com",
4133
+ "gmx.de",
4134
+ "gmx.net",
4135
+ "web.de",
4136
+ "t-online.de",
4137
+ "hotmail.nl",
4138
+ "live.nl",
4139
+ "outlook.nl",
4140
+ "ziggo.nl",
4141
+ "kpnmail.nl",
4142
+ "planet.nl",
4143
+ "home.nl",
4144
+ "xs4all.nl",
4145
+ "telenet.be",
4146
+ "skynet.be",
4147
+ "orange.fr",
4148
+ "free.fr",
4149
+ "wanadoo.fr",
4150
+ "laposte.net",
4151
+ "libero.it",
4152
+ "virgilio.it"
4153
+ ]);
4154
+
4105
4155
  // packages/contracts/src/vocabulary.ts
4106
4156
  var AttributeKeySchema = exports_external.string().regex(/^[a-z][a-z0-9_]{0,39}$/, "an attribute key is a lower-case machine name: a letter, then letters, digits or underscores, at most 40");
4107
4157
  var RESERVED_ATTRIBUTE_KEYS = ["utbms_task", "utbms_activity"];
@@ -4228,8 +4278,20 @@ var ClientSchema = exports_external.object({
4228
4278
  created_at: TimestampSchema,
4229
4279
  updated_at: TimestampSchema
4230
4280
  });
4231
- var NARRATIVE_LANGUAGE_PATTERN = /^[a-z]{2,3}(-[A-Z][a-z]{3})?(-([A-Z]{2}|[0-9]{3}))?$/;
4232
- var NarrativeLanguageSchema = exports_external.string().regex(NARRATIVE_LANGUAGE_PATTERN, "a language tag such as en, nl, de or en-GB: language, then an optional script and region");
4281
+ var NARRATIVE_LANGUAGES = [...NARRATIVE_PRIMARY_LANGUAGES, "en-GB", "pt-PT"];
4282
+ var NarrativeLanguageSchema = exports_external.enum(NARRATIVE_LANGUAGES);
4283
+ var STORED_TAG = /^[a-z]{2,3}(-[A-Z][a-z]{3})?(-([A-Z]{2}|[0-9]{3}))?$/;
4284
+ var heldLanguage = (value) => {
4285
+ const read = NarrativeLanguageSchema.safeParse(value);
4286
+ if (read.success)
4287
+ return read.data;
4288
+ if (typeof value !== "string" || !STORED_TAG.test(value))
4289
+ return;
4290
+ const primary = NarrativeLanguageSchema.safeParse(value.split("-", 1)[0]);
4291
+ return primary.success ? primary.data : undefined;
4292
+ };
4293
+ var StoredNarrativeLanguageSchema = exports_external.preprocess((value) => heldLanguage(value) ?? null, NarrativeLanguageSchema.nullable());
4294
+ var StoredFirmLanguageSchema = exports_external.preprocess((value) => heldLanguage(value), NarrativeLanguageSchema.default("en"));
4233
4295
  var MatterAttributeRequiredSchema = exports_external.record(AttributeKeySchema.refine((key) => !key.includes("__"), {
4234
4296
  message: "an attribute key here carries no double underscore"
4235
4297
  }), exports_external.boolean());
@@ -4279,7 +4341,7 @@ var MatterSchema = exports_external.object({
4279
4341
  billing: MatterBillingSchema,
4280
4342
  attribute_sets: MatterAttributeSetsSchema.default({}),
4281
4343
  attribute_required: MatterAttributeRequiredSchema.default({}),
4282
- narrative_language: NarrativeLanguageSchema.nullable().default(null),
4344
+ narrative_language: StoredNarrativeLanguageSchema,
4283
4345
  sync_source: SyncSourceSchema.default("manual"),
4284
4346
  created_at: TimestampSchema,
4285
4347
  updated_at: TimestampSchema
@@ -4770,6 +4832,25 @@ var TeamConfirmationSchema = exports_external.array(exports_external.object({
4770
4832
  external_ref: exports_external.string().min(1),
4771
4833
  member_id: MemberIdSchema.nullable()
4772
4834
  }));
4835
+ var LEGALSENSE_HOST = exports_external.string().min(1).regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?\.legalsense\.nl$/i, "a LegalSense host is <installation>.legalsense.nl");
4836
+ var LEGALSENSE_SIGN_IN = {
4837
+ username: exports_external.string().min(1).optional(),
4838
+ password: exports_external.string().min(1).optional(),
4839
+ totp: exports_external.string().regex(/^[0-9]{6}$/, "the code is six digits").optional(),
4840
+ api_token: exports_external.string().trim().min(1).optional()
4841
+ };
4842
+ function oneSignIn(body, ctx) {
4843
+ const login = body.username !== undefined || body.password !== undefined || body.totp !== undefined;
4844
+ if (body.api_token !== undefined) {
4845
+ if (login)
4846
+ ctx.addIssue({ code: exports_external.ZodIssueCode.custom, path: ["api_token"], message: "a token, or a username and password, not both" });
4847
+ return;
4848
+ }
4849
+ for (const key of ["username", "password"]) {
4850
+ if (body[key] === undefined)
4851
+ ctx.addIssue({ code: exports_external.ZodIssueCode.custom, path: [key], message: "Required" });
4852
+ }
4853
+ }
4773
4854
  var ConnectRequestSchema = exports_external.discriminatedUnion("kind", [
4774
4855
  exports_external.object({
4775
4856
  kind: exports_external.literal("urios"),
@@ -4779,18 +4860,15 @@ var ConnectRequestSchema = exports_external.discriminatedUnion("kind", [
4779
4860
  user: exports_external.string().min(1),
4780
4861
  password: exports_external.string().min(1)
4781
4862
  }),
4782
- exports_external.object({
4783
- kind: exports_external.literal("legalsense"),
4784
- host: exports_external.string().min(1).regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?\.legalsense\.nl$/i, "a LegalSense host is <installation>.legalsense.nl"),
4785
- username: exports_external.string().min(1),
4786
- password: exports_external.string().min(1),
4787
- totp: exports_external.string().regex(/^[0-9]{6}$/, "the code is six digits").optional()
4788
- }),
4863
+ exports_external.object({ kind: exports_external.literal("legalsense"), host: LEGALSENSE_HOST, ...LEGALSENSE_SIGN_IN }),
4789
4864
  exports_external.object({
4790
4865
  kind: exports_external.literal("fake"),
4791
4866
  seed: exports_external.string().optional()
4792
4867
  })
4793
- ]);
4868
+ ]).superRefine((body, ctx) => {
4869
+ if (body.kind === "legalsense")
4870
+ oneSignIn(body, ctx);
4871
+ });
4794
4872
  var ConnectorSummarySchema = exports_external.object({
4795
4873
  kind: ConnectorKindSchema,
4796
4874
  status: ConnectorStatusSchema,
@@ -4818,11 +4896,7 @@ var ConnectResultSchema = exports_external.object({
4818
4896
  connector: ConnectorSummarySchema,
4819
4897
  team: TeamProposalSchema.nullable()
4820
4898
  });
4821
- var ConnectorAccountPutSchema = exports_external.object({
4822
- username: exports_external.string().min(1),
4823
- password: exports_external.string().min(1),
4824
- totp: exports_external.string().regex(/^[0-9]{6}$/, "the code is six digits").optional()
4825
- }).strict();
4899
+ var ConnectorAccountPutSchema = exports_external.object(LEGALSENSE_SIGN_IN).strict().superRefine(oneSignIn);
4826
4900
  var SyncReportSchema = exports_external.object({
4827
4901
  kind: ConnectorKindSchema,
4828
4902
  started_at: TimestampSchema,
@@ -5907,7 +5981,7 @@ var FirmSettingsSchema = exports_external.object({
5907
5981
  unit_minutes: UnitMinutesSchema.default(6),
5908
5982
  narrative: exports_external.object({
5909
5983
  max_chars: NarrativeMaxCharsSchema.nullable().default(null),
5910
- language: NarrativeLanguageSchema.default("en"),
5984
+ language: StoredFirmLanguageSchema,
5911
5985
  guidance: NarrativeGuidanceSchema.default(""),
5912
5986
  lowered_to_system: exports_external.object({ kind: ConnectorKindSchema, at: TimestampSchema }).nullable().default(null)
5913
5987
  }).default({}),
@@ -5952,7 +6026,7 @@ var MemberSchema = exports_external.object({
5952
6026
  user_id: UserIdSchema,
5953
6027
  role: RoleSchema,
5954
6028
  active: exports_external.boolean().default(true),
5955
- narrative_language: NarrativeLanguageSchema.nullable().default(null),
6029
+ narrative_language: StoredNarrativeLanguageSchema,
5956
6030
  entry_size: EntrySizeSchema.nullable().default(null),
5957
6031
  created_at: TimestampSchema,
5958
6032
  updated_at: TimestampSchema
@@ -5994,7 +6068,7 @@ var MeSystemLinkSchema = exports_external.object({
5994
6068
  code: exports_external.string().min(1).nullable(),
5995
6069
  status: exports_external.enum(["connected", "needs_reconnect"]).optional(),
5996
6070
  synced: exports_external.boolean().optional(),
5997
- narrative_language: NarrativeLanguageSchema.nullable().optional(),
6071
+ narrative_language: StoredNarrativeLanguageSchema.optional(),
5998
6072
  approval: ApprovalModeSchema.optional()
5999
6073
  });
6000
6074
  var MeSchema = exports_external.object({
@@ -6105,7 +6179,7 @@ var MatterPatchSchema = MatterSchema.omit({
6105
6179
  updated_at: true,
6106
6180
  attribute_required: true,
6107
6181
  firm_internal: true
6108
- }).partial();
6182
+ }).extend({ narrative_language: NarrativeLanguageSchema.nullable() }).partial();
6109
6183
  var FirmInternalPutSchema = exports_external.object({ firm_internal: exports_external.boolean() });
6110
6184
  var AttributeSetPutSchema = AttributeSetSchema.omit({ updated_at: true });
6111
6185
  var AttributeSetsQuerySchema = exports_external.object({ attribute: AttributeKeySchema.optional() });
@@ -6636,6 +6710,17 @@ var AdminSealedBackfillResultSchema = exports_external.object({
6636
6710
  judged_sealed: exports_external.number().int(),
6637
6711
  done: exports_external.boolean()
6638
6712
  });
6713
+ var AdminNarrativeInstructionsSetSchema = exports_external.object({ instructions: NarrativeGuidanceSchema });
6714
+ var AdminNarrativeInstructionsSchema = exports_external.object({
6715
+ tenant: exports_external.object({ id: TenantIdSchema, name: exports_external.string() }),
6716
+ instructions: exports_external.string()
6717
+ });
6718
+ var AdminNarrativeActivitiesSetSchema = exports_external.object({ activities: NarrativeActivitiesSchema });
6719
+ var AdminNarrativeActivitiesSchema = exports_external.object({
6720
+ tenant: exports_external.object({ id: TenantIdSchema, name: exports_external.string() }),
6721
+ language: NarrativeLanguageSchema,
6722
+ activities: exports_external.array(NarrativeActivitySchema)
6723
+ });
6639
6724
  // packages/contracts/src/admin-firm-acting.ts
6640
6725
  var ACTING_AS_HEADER = "X-Six-Minute-Acting-As";
6641
6726
  var TeamApplyRequestSchema = exports_external.object({
@@ -6841,6 +6926,13 @@ var COMPAT_MARKERS = [
6841
6926
  "services/api/test/t42-intervals.test.ts",
6842
6927
  "services/api/test/t53-bundle.test.ts"
6843
6928
  ]),
6929
+ ...markers("frozen-short", [
6930
+ "services/api/src/platform/models/bundle.ts",
6931
+ "services/api/src/platform/models/compat/activity-types.ts",
6932
+ "services/api/src/platform/models/compat/narrative-short.md",
6933
+ "services/api/test/compat-180.test.ts",
6934
+ "services/api/test/t53-bundle.test.ts"
6935
+ ]),
6844
6936
  ...markers("narrative-languages", ["services/api/src/app.ts", "services/api/src/features/update/legacy-languages.ts"]),
6845
6937
  ...markers("pr4", [
6846
6938
  "boundaries.ts",
@@ -8137,16 +8229,16 @@ function syncLines(report) {
8137
8229
 
8138
8230
  // tools/admin/src/sets.ts
8139
8231
  import { readFileSync as readFileSync2 } from "node:fs";
8140
- function readDocument(path, schema, what) {
8232
+ function readDocument(path, schema, what, flag = "from-file") {
8141
8233
  let raw;
8142
8234
  try {
8143
8235
  raw = JSON.parse(readFileSync2(path, "utf8"));
8144
8236
  } catch (err) {
8145
- throw new AdminUsageError(`--from-file ${path} is not readable JSON: ${err.message}`);
8237
+ throw new AdminUsageError(`--${flag} ${path} is not readable JSON: ${err.message}`);
8146
8238
  }
8147
8239
  const parsed = schema.safeParse(raw);
8148
8240
  if (!parsed.success) {
8149
- throw new AdminUsageError(`--from-file ${path} is not ${what}: ` + parsed.error.issues.slice(0, 4).map((i) => `${i.path.join(".") || "(root)"} ${i.message}`).join("; "));
8241
+ throw new AdminUsageError(`--${flag} ${path} is not ${what}: ` + parsed.error.issues.slice(0, 4).map((i) => `${i.path.join(".") || "(root)"} ${i.message}`).join("; "));
8150
8242
  }
8151
8243
  return { raw, parsed: parsed.data };
8152
8244
  }
@@ -8545,6 +8637,23 @@ is required; without one every command answers 403.
8545
8637
  setting deletes at the next hourly purge. Summaries, labels and entries
8546
8638
  are not affected.
8547
8639
 
8640
+ sixminute-admin narrative show --env <env> --tenant tenant_... [--file <path>] [--json]
8641
+ sixminute-admin narrative set --env <env> --tenant tenant_... --file <path> [--json]
8642
+ The firm's narrative instructions: the text every narrative of the firm
8643
+ follows, over every default, at most 1,500 characters. show prints it,
8644
+ or writes it to --file; set replaces it with the text of --file (an
8645
+ empty file clears it).
8646
+
8647
+ sixminute-admin narrative activities show --env <env> --tenant tenant_... [--file <path>] [--json]
8648
+ sixminute-admin narrative activities set --env <env> --tenant tenant_... --file <path> [--json]
8649
+ The firm's narrative activities, by rank: the first row whose "when"
8650
+ the evidence meets opens the line. show prints the ranks and keys, or
8651
+ writes the whole table to --file as a JSON array; set replaces the
8652
+ firm's rows with the array in --file, in its order. Edit by show to a
8653
+ file, edit, set. set is refused, and the rows stay as they were, for an
8654
+ empty table, a repeated key, or a table where no row has a pattern in
8655
+ the firm's default narrative language.
8656
+
8548
8657
  sixminute-admin pipeline shadow-report --env <env> --day YYYY-MM-DD [--json]
8549
8658
  The cloud pipeline's shadow against the device for one local day, for
8550
8659
  every person of an internal firm with a shadow recording: intervals
@@ -8907,6 +9016,7 @@ async function runAdmin(argv, deps) {
8907
9016
  const oneWord = ["login", "logout", "whoami", "doctor", "versions", "audit"].includes(group);
8908
9017
  const vocabulary2 = group === "attribute" || group === "matter" && subcommand === "sets";
8909
9018
  const threeDeep = group === "tenant" && subcommand === "internal";
9019
+ const narrativeTable = group === "narrative" && subcommand === "activities";
8910
9020
  let flagArgv;
8911
9021
  const positionals = [];
8912
9022
  if (oneWord)
@@ -8916,6 +9026,11 @@ async function runAdmin(argv, deps) {
8916
9026
  if (verb !== "set")
8917
9027
  throw new AdminUsageError(`unknown command: tenant internal ${verb ?? ""}`.trim());
8918
9028
  flagArgv = more;
9029
+ } else if (narrativeTable) {
9030
+ const [verb, ...more] = rest;
9031
+ if (verb !== "show" && verb !== "set")
9032
+ throw new AdminUsageError(`unknown command: narrative activities ${verb ?? ""}`.trim());
9033
+ flagArgv = more;
8919
9034
  } else if (vocabulary2) {
8920
9035
  const argvOf = group === "attribute" && subcommand === "set" ? rest.slice(1) : rest;
8921
9036
  const forFlags = [];
@@ -8981,6 +9096,10 @@ async function runAdmin(argv, deps) {
8981
9096
  return runRawArchive(run2, subcommand);
8982
9097
  if (group === "retention" && subcommand === "set")
8983
9098
  return runRetentionSet(run2);
9099
+ if (narrativeTable)
9100
+ return runNarrativeActivities(run2, rest[0] === "set");
9101
+ if (group === "narrative" && (subcommand === "show" || subcommand === "set"))
9102
+ return runNarrativeInstructions(run2, subcommand === "set");
8984
9103
  if (group === "backfill" && subcommand === "sealed")
8985
9104
  return runBackfillSealed(run2);
8986
9105
  if (group === "scrub" && subcommand === "plaintext")
@@ -9170,6 +9289,48 @@ async function runRetentionSet(run2) {
9170
9289
  `moved ${moved}`
9171
9290
  ]);
9172
9291
  }
9292
+ async function runNarrativeInstructions(run2, set) {
9293
+ const tenantId = required(run2.flags, "tenant", "whose narrative instructions");
9294
+ const file = set ? required(run2.flags, "file", "the instructions are the text of a file") : optional(run2.flags, "file");
9295
+ let body;
9296
+ if (set) {
9297
+ try {
9298
+ body = { instructions: readFileSync4(file, "utf8") };
9299
+ } catch (err) {
9300
+ throw new AdminUsageError(`--file ${file} is not readable: ${err.message}`);
9301
+ }
9302
+ }
9303
+ const answer = await call(run2.api, set ? "PUT" : "GET", tenantPath(tenantId, "/narrative-instructions"), body);
9304
+ if (answer.status !== 200)
9305
+ return refused(run2, answer, [actingHead(tenantId, answer)]);
9306
+ const done = AdminNarrativeInstructionsSchema.parse(answer.body);
9307
+ const lead = [actingHead(tenantId, answer), `instructions ${done.instructions.length} characters${set ? ", set" : ""}`];
9308
+ if (!set && file !== undefined) {
9309
+ writeFileSync2(file, done.instructions);
9310
+ return render(run2, { ...done, file }, [...lead, `written ${file}`]);
9311
+ }
9312
+ return render(run2, done, set || done.instructions === "" ? lead : [...lead, "", ...done.instructions.split(`
9313
+ `)]);
9314
+ }
9315
+ async function runNarrativeActivities(run2, set) {
9316
+ const tenantId = required(run2.flags, "tenant", "whose narrative activities");
9317
+ const file = set ? required(run2.flags, "file", "the table is a JSON array of rows, by rank, in a file") : optional(run2.flags, "file");
9318
+ const body = set ? { activities: readDocument(file, NarrativeActivitiesSchema, "a narrative activity table (a JSON array of rows, by rank)", "file").raw } : undefined;
9319
+ const answer = await call(run2.api, set ? "PUT" : "GET", tenantPath(tenantId, "/narrative-activities"), body);
9320
+ if (answer.status !== 200)
9321
+ return refused(run2, answer, [actingHead(tenantId, answer)]);
9322
+ const done = AdminNarrativeActivitiesSchema.parse(answer.body);
9323
+ if (!set && file !== undefined)
9324
+ writeFileSync2(file, `${JSON.stringify(done.activities, null, 2)}
9325
+ `);
9326
+ return render(run2, !set && file !== undefined ? { ...done, file } : done, [
9327
+ actingHead(tenantId, answer),
9328
+ `language ${done.language}`,
9329
+ `activities ${done.activities.length}${set ? ", set" : ""}`,
9330
+ ...done.activities.map((row, i) => ` ${String(i + 1).padStart(2)} ${row.key} ${Object.keys(row.patterns).join(", ")}`),
9331
+ ...!set && file !== undefined ? [`written ${file}`] : []
9332
+ ]);
9333
+ }
9173
9334
  async function runBackfillSealed(run2) {
9174
9335
  const tenantId = required(run2.flags, "tenant", "whose rows to seal");
9175
9336
  const total = { decisions_sealed: 0, intervals_aged: 0, judged_sealed: 0 };
@@ -9801,7 +9962,7 @@ function adminSurfaceOf(document) {
9801
9962
  // tools/admin/src/main.ts
9802
9963
  function sourceBuild() {
9803
9964
  const document = fileURLToPath(new URL("../../../services/api/openapi.json", import.meta.url));
9804
- const surface = adminSurfaceOf(JSON.parse(readFileSync4(document, "utf8")));
9965
+ const surface = adminSurfaceOf(JSON.parse(readFileSync5(document, "utf8")));
9805
9966
  return { version: "0.0.0-dev", commit: "source", surface };
9806
9967
  }
9807
9968
  function openBrowser(url) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@unisonlabs/sixminute-admin",
3
- "version": "0.1.1261-next",
3
+ "version": "0.1.1263-next",
4
4
  "description": "Sixminute operator tool: the admin commands of the Sixminute API, run with your own operator sign-in.",
5
5
  "license": "UNLICENSED",
6
6
  "repository": {