@unisonlabs/sixminute-admin 0.1.1236 → 0.1.1239-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.
- package/dist/sixminute-admin.mjs +262 -15
- package/package.json +1 -1
package/dist/sixminute-admin.mjs
CHANGED
|
@@ -16,12 +16,12 @@ 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
|
|
19
|
+
import { readFileSync as readFileSync4 } 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.
|
|
24
|
+
var BUILT = { version: "0.1.1239-next", commit: "ea645e5f9b8893b766f446bdb64291f28a968289", surface: "3b2db9633e846531" };
|
|
25
25
|
|
|
26
26
|
// tools/admin/src/run.ts
|
|
27
27
|
import { rmSync as rmSync3 } from "node:fs";
|
|
@@ -4075,7 +4075,7 @@ var SnapshotSchema = exports_external.object({
|
|
|
4075
4075
|
message: "a diff snapshot must name its base; a full snapshot must not have one",
|
|
4076
4076
|
path: ["base"]
|
|
4077
4077
|
});
|
|
4078
|
-
var DerivationNamespaceSchema = exports_external.enum(["deterministic", "llm"]);
|
|
4078
|
+
var DerivationNamespaceSchema = exports_external.enum(["deterministic", "llm", "calendar_attendance", "calendar_span_narrative"]);
|
|
4079
4079
|
var DerivationCacheKeySchema = exports_external.object({
|
|
4080
4080
|
content_hash: exports_external.string().min(1),
|
|
4081
4081
|
namespace: DerivationNamespaceSchema,
|
|
@@ -5054,7 +5054,8 @@ var TenantPolicySchema = exports_external.object({
|
|
|
5054
5054
|
security_tier: exports_external.enum(["standard", "confidential"]).default("standard"),
|
|
5055
5055
|
raw_archive: exports_external.boolean().default(false),
|
|
5056
5056
|
research_export: exports_external.boolean().default(false),
|
|
5057
|
-
context_retention_days: exports_external.number().int().min(0).max(365).default(30)
|
|
5057
|
+
context_retention_days: exports_external.number().int().min(0).max(365).default(30),
|
|
5058
|
+
demo: exports_external.object({ enabled: exports_external.boolean() }).optional()
|
|
5058
5059
|
}).default({});
|
|
5059
5060
|
var NARRATIVE_GUIDANCE_MAX_CHARS = 1500;
|
|
5060
5061
|
var GUIDANCE_CONTROL = /[\u0000-\u0009\u000B-\u001F\u007F-\u009F]/;
|
|
@@ -5546,7 +5547,10 @@ var CompletionPurposeSchema = exports_external.enum([
|
|
|
5546
5547
|
"merge_prose",
|
|
5547
5548
|
"assign",
|
|
5548
5549
|
"judge",
|
|
5549
|
-
"judge_shortlist"
|
|
5550
|
+
"judge_shortlist",
|
|
5551
|
+
"judge_calendar_event",
|
|
5552
|
+
"judge_calendar_matter",
|
|
5553
|
+
"judge_calendar_attendance"
|
|
5550
5554
|
]);
|
|
5551
5555
|
var CompletionDecisionSchema = exports_external.object({
|
|
5552
5556
|
property: exports_external.string().min(1),
|
|
@@ -5962,6 +5966,10 @@ var AdminResearchExportSetSchema = exports_external.object({ research_export: ex
|
|
|
5962
5966
|
var AdminResearchExportFlagSchema = exports_external.object({
|
|
5963
5967
|
tenant: exports_external.object({ id: TenantIdSchema, name: exports_external.string(), research_export: exports_external.boolean() })
|
|
5964
5968
|
});
|
|
5969
|
+
var AdminDemoSetSchema = exports_external.object({ enabled: exports_external.boolean() });
|
|
5970
|
+
var AdminDemoFlagSchema = exports_external.object({
|
|
5971
|
+
tenant: exports_external.object({ id: TenantIdSchema, name: exports_external.string(), demo: exports_external.boolean() })
|
|
5972
|
+
});
|
|
5965
5973
|
var AdminDeviceVersionSchema = exports_external.object({
|
|
5966
5974
|
id: DeviceIdSchema,
|
|
5967
5975
|
name: exports_external.string(),
|
|
@@ -6256,6 +6264,140 @@ var ServedConfigBundleSchema = ConfigBundleSchema.extend({
|
|
|
6256
6264
|
prompts: ServedConfigPromptsSchema,
|
|
6257
6265
|
vocabularies: ServedConfigVocabulariesSchema
|
|
6258
6266
|
});
|
|
6267
|
+
// packages/contracts/src/demo.ts
|
|
6268
|
+
var ClockSchema = exports_external.string().regex(/^([01]\d|2[0-3]):[0-5]\d$/, "a local clock time, HH:MM");
|
|
6269
|
+
var KeySchema = exports_external.string().regex(/^[a-z0-9][a-z0-9_-]{0,63}$/i, "a key: letters, digits, _ and -");
|
|
6270
|
+
var DemoObservationSchema = exports_external.object({
|
|
6271
|
+
from: ClockSchema,
|
|
6272
|
+
to: ClockSchema,
|
|
6273
|
+
app: exports_external.string().min(1),
|
|
6274
|
+
bundle: exports_external.string().min(1),
|
|
6275
|
+
title: exports_external.string(),
|
|
6276
|
+
typed: exports_external.string().default(""),
|
|
6277
|
+
url: exports_external.string().nullable().default(null),
|
|
6278
|
+
files: exports_external.array(exports_external.string().min(1)).default([]),
|
|
6279
|
+
screen: exports_external.string().optional()
|
|
6280
|
+
}).strict();
|
|
6281
|
+
var DemoDecisionSchema = exports_external.object({ summary: exports_external.string().min(1), activity: exports_external.string().min(1).nullable(), matter: KeySchema.nullable() }).strict();
|
|
6282
|
+
var DemoIntervalSchema = exports_external.object({ from: ClockSchema, to: ClockSchema, observations: exports_external.array(DemoObservationSchema).default([]), decision: DemoDecisionSchema.optional() }).strict();
|
|
6283
|
+
var DemoEntrySchema = exports_external.object({ from: ClockSchema, to: ClockSchema, matter: KeySchema.nullable(), narrative: exports_external.string().min(1), codes: exports_external.record(exports_external.string().min(1)).default({}) }).strict();
|
|
6284
|
+
var DemoRecordingSchema = exports_external.object({ start: ClockSchema, stop: ClockSchema, intervals: exports_external.array(DemoIntervalSchema).min(1) }).strict();
|
|
6285
|
+
var minutes = (clock) => Number(clock.slice(0, 2)) * 60 + Number(clock.slice(3));
|
|
6286
|
+
function problemsOf(s) {
|
|
6287
|
+
const out = [];
|
|
6288
|
+
const clients = new Set(s.clients.map((c) => c.key));
|
|
6289
|
+
const matters = new Set(s.matters.map((m) => m.key));
|
|
6290
|
+
if (clients.size !== s.clients.length)
|
|
6291
|
+
out.push([["clients"], "a client key is used twice"]);
|
|
6292
|
+
if (matters.size !== s.matters.length)
|
|
6293
|
+
out.push([["matters"], "a matter key is used twice"]);
|
|
6294
|
+
s.matters.forEach((m, i) => clients.has(m.client) || out.push([["matters", i, "client"], `no client ${m.client}`]));
|
|
6295
|
+
const all = [];
|
|
6296
|
+
let previousStop = -1;
|
|
6297
|
+
s.recordings.forEach((rec, r) => {
|
|
6298
|
+
const [start, stop] = [minutes(rec.start), minutes(rec.stop)];
|
|
6299
|
+
if (stop <= start)
|
|
6300
|
+
out.push([["recordings", r, "stop"], "a recording stops after it starts"]);
|
|
6301
|
+
if (start < previousStop)
|
|
6302
|
+
out.push([["recordings", r, "start"], "recordings are in order and do not overlap"]);
|
|
6303
|
+
previousStop = stop;
|
|
6304
|
+
rec.intervals.forEach((one, i) => {
|
|
6305
|
+
const [from, to] = [minutes(one.from), minutes(one.to)];
|
|
6306
|
+
const at = ["recordings", r, "intervals", i];
|
|
6307
|
+
if (from % s.unit_minutes !== 0 || to - from !== s.unit_minutes)
|
|
6308
|
+
out.push([at, `an interval is one unit of ${s.unit_minutes} minutes on the grid`]);
|
|
6309
|
+
if (from < start || to > stop)
|
|
6310
|
+
out.push([at, "an interval lies inside its recording"]);
|
|
6311
|
+
if (i > 0 && minutes(rec.intervals[i - 1].to) !== from)
|
|
6312
|
+
out.push([at, "intervals are contiguous"]);
|
|
6313
|
+
one.observations.forEach((o, k) => {
|
|
6314
|
+
if (minutes(o.from) < from || minutes(o.to) > to || minutes(o.to) <= minutes(o.from))
|
|
6315
|
+
out.push([[...at, "observations", k], "an observation lies inside its interval"]);
|
|
6316
|
+
});
|
|
6317
|
+
const matter = one.decision?.matter ?? null;
|
|
6318
|
+
if (matter !== null && !matters.has(matter))
|
|
6319
|
+
out.push([[...at, "decision", "matter"], `no matter ${matter}`]);
|
|
6320
|
+
const work = one.decision === undefined || one.decision.activity === null ? null : JSON.stringify([one.decision.activity, matter]);
|
|
6321
|
+
all.push({ r, from, to, work, matter });
|
|
6322
|
+
});
|
|
6323
|
+
});
|
|
6324
|
+
const covered = new Set;
|
|
6325
|
+
s.entries.forEach((e, i) => {
|
|
6326
|
+
const at = ["entries", i];
|
|
6327
|
+
if (e.matter !== null && !matters.has(e.matter))
|
|
6328
|
+
out.push([[...at, "matter"], `no matter ${e.matter}`]);
|
|
6329
|
+
const first = all.findIndex((one) => one.from === minutes(e.from));
|
|
6330
|
+
const last = all.findIndex((one) => one.to === minutes(e.to));
|
|
6331
|
+
if (first < 0 || last < first || all[first].r !== all[last].r) {
|
|
6332
|
+
out.push([at, "an entry starts and ends on interval bounds of one recording"]);
|
|
6333
|
+
return;
|
|
6334
|
+
}
|
|
6335
|
+
const inside = all.slice(first, last + 1);
|
|
6336
|
+
if (inside.some((one, k) => one.work === null || one.matter !== e.matter || k > 0 && inside[k - 1].to !== one.from)) {
|
|
6337
|
+
out.push([at, "an entry covers only contiguous decided work on its own matter"]);
|
|
6338
|
+
}
|
|
6339
|
+
const [before, after] = [all[first - 1], all[last + 1]];
|
|
6340
|
+
const joins = (a, b) => a !== undefined && a.r === b.r && a.to === b.from && a.work === b.work;
|
|
6341
|
+
if (joins(before, inside[0]) || after !== undefined && joins(inside.at(-1), after))
|
|
6342
|
+
out.push([at, "an entry starts and ends where an activity block does"]);
|
|
6343
|
+
for (let k = first;k <= last; k++)
|
|
6344
|
+
covered.has(k) ? out.push([at, "entries do not overlap"]) : covered.add(k);
|
|
6345
|
+
});
|
|
6346
|
+
const clock = (m) => `${String(Math.floor(m / 60)).padStart(2, "0")}:${String(m % 60).padStart(2, "0")}`;
|
|
6347
|
+
if (s.entries.length > 0)
|
|
6348
|
+
all.forEach((one, k) => one.work !== null && !covered.has(k) && out.push([["entries"], `the work at ${clock(one.from)} is in no entry`]));
|
|
6349
|
+
return out;
|
|
6350
|
+
}
|
|
6351
|
+
var DemoScenarioSchema = exports_external.object({
|
|
6352
|
+
format: exports_external.literal(1),
|
|
6353
|
+
name: KeySchema,
|
|
6354
|
+
description: exports_external.string().default(""),
|
|
6355
|
+
time_zone: exports_external.string().min(1),
|
|
6356
|
+
unit_minutes: exports_external.number().int().min(1).max(60),
|
|
6357
|
+
clients: exports_external.array(exports_external.object({ key: KeySchema, name: exports_external.string().min(1) }).strict()),
|
|
6358
|
+
matters: exports_external.array(exports_external.object({ key: KeySchema, client: KeySchema, number: exports_external.string().min(1), name: exports_external.string().min(1) }).strict()),
|
|
6359
|
+
codes: exports_external.object({ set: exports_external.union([exports_external.string().min(1), exports_external.array(exports_external.string().min(1)).min(1)]) }).strict().optional(),
|
|
6360
|
+
recordings: exports_external.array(DemoRecordingSchema).min(1),
|
|
6361
|
+
entries: exports_external.array(DemoEntrySchema).default([])
|
|
6362
|
+
}).strict().superRefine((s, ctx) => {
|
|
6363
|
+
for (const [path, message] of problemsOf(s))
|
|
6364
|
+
ctx.addIssue({ code: exports_external.ZodIssueCode.custom, path, message });
|
|
6365
|
+
});
|
|
6366
|
+
function replayGap(s) {
|
|
6367
|
+
if (s.recordings.some((r) => r.intervals.some((i) => i.decision === undefined)))
|
|
6368
|
+
return "every interval needs its decision";
|
|
6369
|
+
return s.entries.length === 0 ? "a replay needs the scenario's entries" : null;
|
|
6370
|
+
}
|
|
6371
|
+
var AdminDemoSeedSchema = exports_external.object({
|
|
6372
|
+
scenario: DemoScenarioSchema,
|
|
6373
|
+
day: LocalDaySchema.optional(),
|
|
6374
|
+
replay: exports_external.boolean().default(false),
|
|
6375
|
+
generate: exports_external.boolean().default(false)
|
|
6376
|
+
});
|
|
6377
|
+
var AdminDemoDaySchema = exports_external.object({ day: LocalDaySchema, generate: exports_external.boolean().default(false) });
|
|
6378
|
+
var counts = { member_id: MemberIdSchema, day: LocalDaySchema };
|
|
6379
|
+
var AdminDemoSeedResultSchema = exports_external.object({
|
|
6380
|
+
...counts,
|
|
6381
|
+
mode: exports_external.enum(["live", "replay"]),
|
|
6382
|
+
recordings: exports_external.number().int(),
|
|
6383
|
+
contexts: exports_external.object({ stored: exports_external.number().int(), repeated: exports_external.number().int() }),
|
|
6384
|
+
created: exports_external.object({ clients: exports_external.number().int(), matters: exports_external.number().int() }),
|
|
6385
|
+
generate_run: exports_external.string().nullable()
|
|
6386
|
+
});
|
|
6387
|
+
var AdminDemoResetResultSchema = exports_external.object({
|
|
6388
|
+
...counts,
|
|
6389
|
+
runs_deleted: exports_external.number().int(),
|
|
6390
|
+
drafts_deleted: exports_external.number().int(),
|
|
6391
|
+
released_kept: exports_external.number().int(),
|
|
6392
|
+
generate_run: exports_external.string().nullable()
|
|
6393
|
+
});
|
|
6394
|
+
var AdminDemoRemoveResultSchema = exports_external.object({ ...counts, recordings_deleted: exports_external.number().int(), entries_deleted: exports_external.number().int() });
|
|
6395
|
+
var AdminDemoStatusSchema = exports_external.object({
|
|
6396
|
+
tenant: exports_external.object({ id: TenantIdSchema, name: exports_external.string(), demo: exports_external.boolean() }),
|
|
6397
|
+
member_id: MemberIdSchema,
|
|
6398
|
+
caps: exports_external.object({ calls_per_day: exports_external.number().int(), cost_usd_per_day: exports_external.number() }),
|
|
6399
|
+
days: exports_external.array(exports_external.object({ day: LocalDaySchema, mode: exports_external.enum(["live", "replay"]), recordings: exports_external.number().int(), entries: exports_external.number().int() }))
|
|
6400
|
+
});
|
|
6259
6401
|
// packages/contracts/src/device.ts
|
|
6260
6402
|
var DEVICE_CREDENTIAL_PREFIX = "smd_";
|
|
6261
6403
|
var ENROLLMENT_CODE_PREFIX = "sme_";
|
|
@@ -6384,7 +6526,7 @@ var LocalSettingsSchema = exports_external.object({
|
|
|
6384
6526
|
capture_enabled: exports_external.boolean(),
|
|
6385
6527
|
excluded_bundles: exports_external.array(exports_external.string().min(1)),
|
|
6386
6528
|
excluded_sites: exports_external.array(SiteSchema).default([]),
|
|
6387
|
-
policy: TenantPolicySchema,
|
|
6529
|
+
policy: TenantPolicySchema.removeDefault().omit({ demo: true }).default({}),
|
|
6388
6530
|
lists_revision: exports_external.string().min(1).nullable().default(null),
|
|
6389
6531
|
raw_archive: exports_external.object({
|
|
6390
6532
|
allowed: exports_external.boolean(),
|
|
@@ -7316,11 +7458,11 @@ function fieldLines(summary) {
|
|
|
7316
7458
|
];
|
|
7317
7459
|
}
|
|
7318
7460
|
function summaryLines(summary) {
|
|
7319
|
-
const
|
|
7461
|
+
const counts2 = summary.counts;
|
|
7320
7462
|
return [
|
|
7321
7463
|
`connector ${summary.kind} ${summary.status}`,
|
|
7322
7464
|
`team ${summary.team_bound} bound, ${summary.team_unbound} unbound`,
|
|
7323
|
-
`lists ${
|
|
7465
|
+
`lists ${counts2.matters} matters, ${counts2.clients} clients, ` + `${counts2.timekeepers} timekeepers, ${counts2.options} options`,
|
|
7324
7466
|
`last sync ${summary.last_sync_at ?? "never"}`,
|
|
7325
7467
|
`last error ${summary.last_error ?? "none"}`,
|
|
7326
7468
|
...summary.capabilities.accepts_by === "approval" ? [`reviewer ${reviewerLine(summary)}`, `approval ${approvalLine(summary)}`] : []
|
|
@@ -7480,6 +7622,84 @@ function googleAccessToken(env, platform) {
|
|
|
7480
7622
|
throw new UnsealError("no Google access token: set GOOGLE_OAUTH_ACCESS_TOKEN, or install the gcloud CLI and run " + "`gcloud auth login` as yourself. The unseal runs as YOU, never as a service account.");
|
|
7481
7623
|
}
|
|
7482
7624
|
|
|
7625
|
+
// tools/admin/src/demo.ts
|
|
7626
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
7627
|
+
function readScenario(path) {
|
|
7628
|
+
let raw;
|
|
7629
|
+
try {
|
|
7630
|
+
raw = JSON.parse(readFileSync3(path, "utf8"));
|
|
7631
|
+
} catch (err) {
|
|
7632
|
+
throw new AdminUsageError(`${path} is not readable JSON: ${err.message}`);
|
|
7633
|
+
}
|
|
7634
|
+
const parsed = DemoScenarioSchema.safeParse(raw);
|
|
7635
|
+
if (!parsed.success) {
|
|
7636
|
+
throw new AdminUsageError(`${path} is not a demo scenario:
|
|
7637
|
+
${parsed.error.issues.map((i) => ` ${i.path.join(".") || "(root)"}: ${i.message}`).join(`
|
|
7638
|
+
`)}`);
|
|
7639
|
+
}
|
|
7640
|
+
return parsed.data;
|
|
7641
|
+
}
|
|
7642
|
+
function validateScenario(argv) {
|
|
7643
|
+
const [path, ...rest] = argv;
|
|
7644
|
+
if (path === undefined || path.startsWith("--") || rest.some((arg) => arg !== "--json"))
|
|
7645
|
+
throw new AdminUsageError("demo validate takes one scenario file, and --json");
|
|
7646
|
+
const s = readScenario(path);
|
|
7647
|
+
const facts = {
|
|
7648
|
+
name: s.name,
|
|
7649
|
+
recordings: s.recordings.length,
|
|
7650
|
+
intervals: s.recordings.reduce((n, r) => n + r.intervals.length, 0),
|
|
7651
|
+
entries: s.entries.length,
|
|
7652
|
+
replay: replayGap(s) ?? "yes"
|
|
7653
|
+
};
|
|
7654
|
+
if (rest.includes("--json"))
|
|
7655
|
+
return { code: 0, lines: [JSON.stringify({ valid: true, ...facts }, null, 2)] };
|
|
7656
|
+
return { code: 0, lines: [`valid ${facts.name}`, `recordings ${facts.recordings}, ${facts.intervals} intervals, ${facts.entries} entries`, `replay ${facts.replay}`] };
|
|
7657
|
+
}
|
|
7658
|
+
async function runDemo(run2, subcommand) {
|
|
7659
|
+
if (run2.environment.name === "production")
|
|
7660
|
+
throw new AdminUsageError("demo commands run on staging (or local) only: production never enters demo mode");
|
|
7661
|
+
const tenantId = required(run2.flags, "tenant", "which firm, an internal one");
|
|
7662
|
+
const path = (rest) => tenantPath(tenantId, rest);
|
|
7663
|
+
if (subcommand === "on" || subcommand === "off") {
|
|
7664
|
+
const { tenant } = AdminDemoFlagSchema.parse(expectOk(await call(run2.api, "PUT", path("/demo"), { enabled: subcommand === "on" })));
|
|
7665
|
+
return render(run2, { tenant }, [`tenant ${tenant.id} ${tenant.name}`, `demo ${tenant.demo}`]);
|
|
7666
|
+
}
|
|
7667
|
+
if (subcommand === "status") {
|
|
7668
|
+
const status = AdminDemoStatusSchema.parse(expectOk(await call(run2.api, "GET", path("/demo"))));
|
|
7669
|
+
return render(run2, status, [
|
|
7670
|
+
`tenant ${status.tenant.id} ${status.tenant.name} demo ${status.tenant.demo}`,
|
|
7671
|
+
`member ${status.member_id}`,
|
|
7672
|
+
`caps ${status.caps.calls_per_day} calls, ${status.caps.cost_usd_per_day} USD per UTC day`,
|
|
7673
|
+
...status.days.length === 0 ? ["no seeded day"] : status.days.map((d) => `${d.day} ${d.mode} ${d.recordings} recordings, ${d.entries} entries`)
|
|
7674
|
+
]);
|
|
7675
|
+
}
|
|
7676
|
+
if (subcommand === "seed") {
|
|
7677
|
+
const scenario = readScenario(required(run2.flags, "scenario", "the scenario file to seed"));
|
|
7678
|
+
const day = run2.flags.values.has("day") ? dayOf2(run2.flags, "day", "the day to seed on") : undefined;
|
|
7679
|
+
const body = { scenario, ...day === undefined ? {} : { day }, replay: switched(run2.flags, "replay"), generate: switched(run2.flags, "generate") };
|
|
7680
|
+
const done = AdminDemoSeedResultSchema.parse(expectOk(await call(run2.api, "POST", path("/demo/seed"), body)));
|
|
7681
|
+
return render(run2, done, [
|
|
7682
|
+
`seeded ${scenario.name} on ${done.day}, ${done.mode}, into ${done.member_id}`,
|
|
7683
|
+
`contexts ${done.contexts.stored} stored, ${done.contexts.repeated} already there, over ${done.recordings} recordings`,
|
|
7684
|
+
`created ${done.created.clients} clients, ${done.created.matters} matters`,
|
|
7685
|
+
done.generate_run === null ? "Generate the day from the app, or run demo reset --generate." : `generate ${done.generate_run}`
|
|
7686
|
+
]);
|
|
7687
|
+
}
|
|
7688
|
+
if (subcommand === "reset" || subcommand === "remove") {
|
|
7689
|
+
const day = dayOf2(run2.flags, "day", `the seeded day to ${subcommand}`);
|
|
7690
|
+
if (subcommand === "remove") {
|
|
7691
|
+
const done2 = AdminDemoRemoveResultSchema.parse(expectOk(await call(run2.api, "POST", path("/demo/remove"), { day })));
|
|
7692
|
+
return render(run2, done2, [`removed ${done2.day}: ${done2.recordings_deleted} recordings, ${done2.entries_deleted} entries`]);
|
|
7693
|
+
}
|
|
7694
|
+
const done = AdminDemoResetResultSchema.parse(expectOk(await call(run2.api, "POST", path("/demo/reset"), { day, generate: switched(run2.flags, "generate") })));
|
|
7695
|
+
return render(run2, done, [
|
|
7696
|
+
`reset ${done.day}: ${done.runs_deleted} Generate runs, ${done.drafts_deleted} drafts deleted, ${done.released_kept} released kept`,
|
|
7697
|
+
...done.generate_run === null ? [] : [`generate ${done.generate_run}`]
|
|
7698
|
+
]);
|
|
7699
|
+
}
|
|
7700
|
+
throw new AdminUsageError(`unknown command: demo ${subcommand ?? ""}`.trim());
|
|
7701
|
+
}
|
|
7702
|
+
|
|
7483
7703
|
// tools/admin/src/run.ts
|
|
7484
7704
|
var ROLE_SLUGS = FIRM_ROLE_SLUGS;
|
|
7485
7705
|
var CONFIDENTIAL_EXIT_CODE = 3;
|
|
@@ -7630,6 +7850,27 @@ is required; without one every command answers 403.
|
|
|
7630
7850
|
--dry-run prints every row it would remove and writes nothing; without
|
|
7631
7851
|
it and without --yes the command prints the same list and refuses.
|
|
7632
7852
|
|
|
7853
|
+
sixminute-admin demo on --env staging --tenant tenant_... [--json]
|
|
7854
|
+
sixminute-admin demo off --env staging --tenant tenant_... [--json]
|
|
7855
|
+
The firm's demo mode: its members' runs get the demo caps (50,000 calls
|
|
7856
|
+
and 100 USD a day), and the demo commands below work on it. Internal
|
|
7857
|
+
firms only, on staging only: every demo command refuses production.
|
|
7858
|
+
sixminute-admin demo status --env staging --tenant tenant_... [--json]
|
|
7859
|
+
The flag, the caps in force, and your own seeded days, live or replay.
|
|
7860
|
+
sixminute-admin demo validate tools/demo-scenarios/example-small.json [--json]
|
|
7861
|
+
Checks a scenario file offline: its shape, its times and every reference.
|
|
7862
|
+
sixminute-admin demo seed --env staging --tenant tenant_... --scenario <file> [--day YYYY-MM-DD] [--replay] [--generate] [--json]
|
|
7863
|
+
Seeds the scenario into YOUR OWN account of the firm (accept an
|
|
7864
|
+
invitation with the same address first), on its own Scenario device.
|
|
7865
|
+
Without --day, the most recent day it lies wholly in the past on, with
|
|
7866
|
+
nothing captured. --replay makes the day's Generate answer from the
|
|
7867
|
+
scenario with no model; --generate starts that Generate.
|
|
7868
|
+
sixminute-admin demo reset --env staging --tenant tenant_... --day YYYY-MM-DD [--generate] [--json]
|
|
7869
|
+
Drops the seeded day's Generate runs and drafts, so Generate runs
|
|
7870
|
+
again over the same activity. Released entries stay.
|
|
7871
|
+
sixminute-admin demo remove --env staging --tenant tenant_... --day YYYY-MM-DD [--json]
|
|
7872
|
+
Deletes the seeded day entirely. Captured time is never touched.
|
|
7873
|
+
|
|
7633
7874
|
sixminute-admin attribute add --env <env> --tenant tenant_... --key utbms_task --label "UTBMS task" [--required] [--proposed] [--default-set aset_a,aset_b] [--json]
|
|
7634
7875
|
Writes one attribute, whole: a bounded single-select field on an entry,
|
|
7635
7876
|
which is what a firm calls its codes. --default-set names the sets a
|
|
@@ -7805,7 +8046,9 @@ var BOOLEAN_FLAGS = new Set([
|
|
|
7805
8046
|
"on",
|
|
7806
8047
|
"off",
|
|
7807
8048
|
"auto",
|
|
7808
|
-
"review"
|
|
8049
|
+
"review",
|
|
8050
|
+
"replay",
|
|
8051
|
+
"generate"
|
|
7809
8052
|
]);
|
|
7810
8053
|
function parseFlags(argv, offset) {
|
|
7811
8054
|
const values = new Map;
|
|
@@ -7909,6 +8152,8 @@ async function runAdmin(argv, deps) {
|
|
|
7909
8152
|
if (group === "--version") {
|
|
7910
8153
|
return { code: 0, lines: [`sixminute-admin ${deps.build.version} (API ${deps.build.commit}, operator surface ${deps.build.surface})`] };
|
|
7911
8154
|
}
|
|
8155
|
+
if (group === "demo" && subcommand === "validate")
|
|
8156
|
+
return validateScenario(rest);
|
|
7912
8157
|
const oneWord = ["login", "logout", "whoami", "doctor", "versions", "audit"].includes(group);
|
|
7913
8158
|
const vocabulary2 = group === "attribute" || group === "matter" && subcommand === "sets";
|
|
7914
8159
|
const threeDeep = group === "tenant" && subcommand === "internal";
|
|
@@ -8000,6 +8245,8 @@ async function runAdmin(argv, deps) {
|
|
|
8000
8245
|
return runEntriesPurge(run2);
|
|
8001
8246
|
if (group === "connector")
|
|
8002
8247
|
return runConnector(run2, subcommand);
|
|
8248
|
+
if (group === "demo")
|
|
8249
|
+
return runDemo(run2, subcommand);
|
|
8003
8250
|
if (group === "firm" || group === "invite")
|
|
8004
8251
|
return runFirm(run2, group, subcommand);
|
|
8005
8252
|
throw new AdminUsageError(`unknown command ${JSON.stringify([group, subcommand].filter(Boolean).join(" "))}.
|
|
@@ -8264,9 +8511,9 @@ async function runVersions(run2) {
|
|
|
8264
8511
|
async function runEntriesPurge(run2) {
|
|
8265
8512
|
const { flags } = run2;
|
|
8266
8513
|
const tenantId = required(flags, "tenant", "which firm's rows are being removed");
|
|
8267
|
-
const
|
|
8514
|
+
const demo2 = switched(flags, "demo");
|
|
8268
8515
|
const unmatched = switched(flags, "unmatched");
|
|
8269
|
-
if (!
|
|
8516
|
+
if (!demo2 && !unmatched) {
|
|
8270
8517
|
throw new AdminUsageError("name what to remove: --demo (the rows tools/demo-seed minted) or --unmatched " + "(engine-written rows charged to no matter), or both. This command never guesses a selector.");
|
|
8271
8518
|
}
|
|
8272
8519
|
const author = optional(flags, "author");
|
|
@@ -8275,15 +8522,15 @@ async function runEntriesPurge(run2) {
|
|
|
8275
8522
|
}
|
|
8276
8523
|
const dryRun = switched(flags, "dry-run");
|
|
8277
8524
|
const yes = switched(flags, "yes");
|
|
8278
|
-
const purge = async (dry) => AdminEntriesPurgeResultSchema.parse(expectOk(await call(run2.api, "POST", tenantPath(tenantId, "/entries/purge"), { author, demo, unmatched, dry_run: dry })));
|
|
8525
|
+
const purge = async (dry) => AdminEntriesPurgeResultSchema.parse(expectOk(await call(run2.api, "POST", tenantPath(tenantId, "/entries/purge"), { author, demo: demo2, unmatched, dry_run: dry })));
|
|
8279
8526
|
const done = await purge(dryRun || !yes);
|
|
8280
8527
|
const total = done.rows.demo.length + done.rows.unmatched.length;
|
|
8281
8528
|
const listing = [
|
|
8282
8529
|
`tenant ${tenantId}`,
|
|
8283
8530
|
`author ${author} (user ${done.user})`,
|
|
8284
8531
|
"",
|
|
8285
|
-
...
|
|
8286
|
-
...
|
|
8532
|
+
...demo2 ? purgeSection("demo entries (entry_demo-)", done.rows.demo) : [],
|
|
8533
|
+
...demo2 && unmatched ? [""] : [],
|
|
8287
8534
|
...unmatched ? purgeSection("unmatched captured entries (no matter, engine-written)", done.rows.unmatched) : [],
|
|
8288
8535
|
""
|
|
8289
8536
|
];
|
|
@@ -8937,7 +9184,7 @@ function adminSurfaceOf(document) {
|
|
|
8937
9184
|
// tools/admin/src/main.ts
|
|
8938
9185
|
function sourceBuild() {
|
|
8939
9186
|
const document = fileURLToPath(new URL("../../../services/api/openapi.json", import.meta.url));
|
|
8940
|
-
const surface = adminSurfaceOf(JSON.parse(
|
|
9187
|
+
const surface = adminSurfaceOf(JSON.parse(readFileSync4(document, "utf8")));
|
|
8941
9188
|
return { version: "0.0.0-dev", commit: "source", surface };
|
|
8942
9189
|
}
|
|
8943
9190
|
function openBrowser(url) {
|
package/package.json
CHANGED