house-lights 1.4.0 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/skills/cast-list/assets/map.html +38 -37
- package/skills/cast-list/examples/agents/bodega-cupons/topics/OnError.topic.mcs.yml +2 -2
- package/skills/cast-list/examples/equipe.csv +7 -0
- package/skills/cast-list/examples/inventario.csv +10 -0
- package/skills/cast-list/examples/telemetria.csv +10 -0
- package/skills/cast-list/references/format.md +15 -0
- package/skills/cast-list/references/keys.md +2 -0
- package/skills/cast-list/references/telemetry.md +39 -0
- package/skills/cast-list/scripts/build.mjs +90 -23
- package/skills/cast-list/scripts/engine.mjs +112 -110
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
// node build.mjs <agents-folder> --from <edited.xlsx> [--take <key,key…|all>] [--out <folder>]
|
|
10
10
|
// what was filled in the workbook (Declared, the Decision column): listed with a key each,
|
|
11
11
|
// and with --take written to the cast file
|
|
12
|
+
// node build.mjs <agents-folder> --declared <table.csv|.xlsx> [--declared <another>] [--agent-column <title>] [--take <key,…|all>]
|
|
13
|
+
// what a spreadsheet says about the agents (the team's, the admin center's inventory): each
|
|
14
|
+
// column becomes a declared fact, listed with a key, and with --take written to the cast file
|
|
15
|
+
// … --telemetry <export.csv|.xlsx> [--telemetry-days <n>] what fired, from Application Insights (references/telemetry.md):
|
|
16
|
+
// the events in the YAML that did not fire, and who has no line at all
|
|
12
17
|
//
|
|
13
18
|
// With no --html/--xlsx/--lesson it makes the map. Files go in --out (or beside the folder), named by the folder:
|
|
14
19
|
// <id>.html, <id>.xlsx, <id>.lesson.html (a chalk-talk lesson), and <id>.cast.yaml, kept up to date (read again on every
|
|
@@ -20,23 +25,26 @@ import { existsSync, mkdirSync, readFileSync, statSync, writeFileSync } from 'no
|
|
|
20
25
|
import { basename, dirname, join, resolve } from 'node:path'
|
|
21
26
|
import { fileURLToPath } from 'node:url'
|
|
22
27
|
import { runPage } from './browser.mjs'
|
|
23
|
-
import { agentsIn, applyChanges, compare, DEFAULT_PROFILE, extractAgent, fromSheets, knownKinds, labelOf, lessonOf, problemText, readCast, readProfile, reviewCast, validateScene, workbookFile, workbookOf, writeCast } from './engine.mjs'
|
|
28
|
+
import { agentsIn, applyChanges, compare, declaredFrom, DEFAULT_PROFILE, extractAgent, firedFrom, fromSheets, mergeDeclared, knownKinds, labelOf, lessonOf, problemText, readCast, readCsv, readProfile, reviewCast, validateScene, workbookFile, workbookOf, writeCast } from './engine.mjs'
|
|
24
29
|
import { readXlsx } from './read-xlsx.mjs'
|
|
25
30
|
import { fill, inline } from './page.mjs'
|
|
26
31
|
|
|
27
32
|
const here = dirname(fileURLToPath(import.meta.url))
|
|
28
33
|
const argv = process.argv.slice(2)
|
|
29
|
-
const valued = new Set(['--lang', '--out', '--profile', '--from', '--take'])
|
|
34
|
+
const valued = new Set(['--lang', '--out', '--profile', '--from', '--take', '--declared', '--agent-column', '--telemetry', '--telemetry-days'])
|
|
30
35
|
const flags = new Set(['--check', '--html', '--xlsx', '--lesson', '--all', '--facts', ...valued])
|
|
31
36
|
const option = (name) => {
|
|
32
37
|
const at = argv.indexOf(name)
|
|
33
38
|
return at >= 0 ? argv[at + 1] : undefined
|
|
34
39
|
}
|
|
40
|
+
const options = (name) => argv.flatMap((a, i) => (a === name && argv[i + 1] ? [argv[i + 1]] : []))
|
|
35
41
|
const args = argv.filter((a, i) => !a.startsWith('--') && !valued.has(argv[i - 1]))
|
|
36
42
|
const usage = `usage: node build.mjs <agents-folder> --check [--profile <p.cast.yaml>] [--lang en|pt]
|
|
37
43
|
node build.mjs <agents-folder> [--profile <p.cast.yaml>] [--html] [--xlsx] [--lesson] [--all] [--lang en|pt] [--out <folder>]
|
|
38
44
|
node build.mjs <agents-folder> --facts [--out <folder>]
|
|
39
|
-
node build.mjs <agents-folder> --from <edited.xlsx> [--take <key,key…|all>] [--out <folder>]
|
|
45
|
+
node build.mjs <agents-folder> --from <edited.xlsx> [--take <key,key…|all>] [--out <folder>]
|
|
46
|
+
node build.mjs <agents-folder> --declared <table.csv|.xlsx> [--agent-column <title>] [--take <key,key…|all>] [--out <folder>]
|
|
47
|
+
… [--telemetry <export.csv|.xlsx>] [--telemetry-days <n>]`
|
|
40
48
|
const lang = option('--lang') ?? 'en'
|
|
41
49
|
const fail = (msg) => {
|
|
42
50
|
console.error(`${msg}\n${usage}`)
|
|
@@ -47,7 +55,11 @@ const unknown = argv.find((a) => a.startsWith('--') && !flags.has(a))
|
|
|
47
55
|
if (unknown) fail(`✗ unknown option ${unknown}`)
|
|
48
56
|
if (!['en', 'pt'].includes(lang)) fail('✗ --lang is en or pt')
|
|
49
57
|
for (const v of valued) if (argv.includes(v) && !option(v)) fail(`✗ ${v} needs a value`)
|
|
50
|
-
if (argv.includes('--take') && !argv.includes('--from')) fail('✗ --take goes with --from')
|
|
58
|
+
if (argv.includes('--take') && !argv.includes('--from') && !argv.includes('--declared')) fail('✗ --take goes with --from or --declared')
|
|
59
|
+
if (argv.includes('--from') && argv.includes('--declared')) fail('✗ --from or --declared, one at a time')
|
|
60
|
+
for (const f of [...options('--declared'), ...options('--telemetry')]) if (!existsSync(f) || !/\.(csv|xlsx)$/i.test(f)) fail(`✗ ${f}: a .csv or an .xlsx that is there`)
|
|
61
|
+
const days = Number(option('--telemetry-days') ?? 30)
|
|
62
|
+
if (!Number.isInteger(days) || days < 1) fail('✗ --telemetry-days is a whole number of days')
|
|
51
63
|
|
|
52
64
|
const T = {
|
|
53
65
|
en: {
|
|
@@ -57,6 +69,12 @@ const T = {
|
|
|
57
69
|
changes: (n) => (n ? `${n} change${n === 1 ? '' : 's'} in the workbook (take them with --take <key,…> or --take all):` : '✓ the workbook changes nothing'),
|
|
58
70
|
took: (n, f) => `✓ took ${n} change${n === 1 ? '' : 's'} into ${f}`, unknownKey: (k) => `✗ no change with the key «${k}»`,
|
|
59
71
|
declared: 'declared', decision: 'decision', add: '+', set: '~', remove: '−', accepted: 'accepted', rejected: 'rejected', platform: 'platform', specialist: 'specialist', orchestrator: 'orchestrator', none: (f) => `✗ ${f} has no folder with agent.mcs.yml`, privacy: '• the files hold the client’s data: keep them in the client’s environment',
|
|
72
|
+
noColumn: (f, cols) => `✗ ${f}: no column names the agents of the folder (by schema name, folder or display name): say which with --agent-column "<title>" (columns: ${cols.join(', ')})`,
|
|
73
|
+
tie: (f, cols) => `✗ ${f}: the columns ${cols.map((c) => `«${c}»`).join(' and ')} name the agents in different ways: say which with --agent-column "<title>"`,
|
|
74
|
+
noHeader: (f, cols) => `✗ ${f}: no column with that title (columns: ${cols.join(', ')})`,
|
|
75
|
+
noEvent: (f, cols) => `✗ ${f}: no column of the event («evento», «event» or «name»; columns: ${cols.join(', ')})`,
|
|
76
|
+
unmatched: (n, f, list) => `• ${f}: ${n} ${n === 1 ? 'row names' : 'rows name'} nobody in the folder: ${list}`, line: 'line',
|
|
77
|
+
telemetry: (f, n, d) => `✓ telemetry ${f}: ${n} ${n === 1 ? 'row' : 'rows'} of the folder's agents, ${d} days`,
|
|
60
78
|
},
|
|
61
79
|
pt: {
|
|
62
80
|
found: (n, o, s) => `✓ ${n} no elenco: ${o} ${o === 1 ? 'orquestrador' : 'orquestradores'}, ${s} ${s === 1 ? 'especialista' : 'especialistas'}`,
|
|
@@ -65,6 +83,12 @@ const T = {
|
|
|
65
83
|
changes: (n) => (n ? `${n} ${n === 1 ? 'mudança' : 'mudanças'} na planilha (aceite com --take <chave,…> ou --take all):` : '✓ a planilha não muda nada'),
|
|
66
84
|
took: (n, f) => `✓ ${n} ${n === 1 ? 'mudança gravada' : 'mudanças gravadas'} em ${f}`, unknownKey: (k) => `✗ nenhuma mudança com a chave «${k}»`,
|
|
67
85
|
declared: 'declarado', decision: 'decisão', add: '+', set: '~', remove: '−', accepted: 'aceito', rejected: 'rejeitado', platform: 'plataforma', specialist: 'especialista', orchestrator: 'orquestrador', none: (f) => `✗ ${f} não tem nenhuma pasta com agent.mcs.yml`, privacy: '• os arquivos têm dados do cliente: deixe-os no ambiente dele',
|
|
86
|
+
noColumn: (f, cols) => `✗ ${f}: nenhuma coluna cita os agentes da pasta (pelo nome de esquema, pela pasta ou pelo nome de exibição): diga qual com --agent-column "<título>" (colunas: ${cols.join(', ')})`,
|
|
87
|
+
tie: (f, cols) => `✗ ${f}: as colunas ${cols.map((c) => `«${c}»`).join(' e ')} citam os agentes de jeitos diferentes: diga qual com --agent-column "<título>"`,
|
|
88
|
+
noHeader: (f, cols) => `✗ ${f}: nenhuma coluna com esse título (colunas: ${cols.join(', ')})`,
|
|
89
|
+
noEvent: (f, cols) => `✗ ${f}: nenhuma coluna do evento («evento», «event» ou «name»; colunas: ${cols.join(', ')})`,
|
|
90
|
+
unmatched: (n, f, list) => `• ${f}: ${n} ${n === 1 ? 'linha não cita' : 'linhas não citam'} ninguém da pasta: ${list}`, line: 'linha',
|
|
91
|
+
telemetry: (f, n, d) => `✓ telemetria ${f}: ${n} ${n === 1 ? 'linha' : 'linhas'} dos agentes da pasta, ${d} dias`,
|
|
68
92
|
},
|
|
69
93
|
}[lang]
|
|
70
94
|
|
|
@@ -84,36 +108,45 @@ const castFile = join(out, `${id}.cast.yaml`)
|
|
|
84
108
|
const castText = existsSync(castFile) ? readFileSync(castFile, 'utf8') : undefined
|
|
85
109
|
const cast = castText !== undefined ? read(castFile, readCast).cast : { ...(profile.name ? { name: profile.name } : {}), agents: [], blocks: [], declared: [], decisions: [] }
|
|
86
110
|
|
|
87
|
-
//
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
111
|
+
// a table from outside: the sheets of an .xlsx, or a .csv
|
|
112
|
+
const tablesOf = (file) => (/\.xlsx$/i.test(file) ? readXlsx(readFileSync(file)) : [readCsv(readFileSync(file, 'utf8'), '')])
|
|
113
|
+
const tableProblem = (file, p) => {
|
|
114
|
+
console.error(p.problem === 'tie' ? T.tie(basename(file), p.titles) : p.problem === 'header' ? T.noHeader(basename(file), p.titles) : p.problem === 'event' ? T.noEvent(basename(file), p.titles) : T.noColumn(basename(file), p.titles))
|
|
115
|
+
process.exit(1)
|
|
116
|
+
}
|
|
117
|
+
const line = (c) => {
|
|
118
|
+
const x = c.after ?? c.before
|
|
119
|
+
if (c.kind === 'declared') return `${T[c.op]} ${T.declared} ${x.agent} · ${labelOf(x.key.includes(':') ? x.key : `declared:${x.key}`, lang)}${x.value ? ` = «${x.value}»` : ''}${x.source ? ` (${x.source})` : ''}`
|
|
120
|
+
return `${T[c.op]} ${T.decision} ${labelOf(x.key, lang)}: ${T[x.status]} (${T[x.scope]})${x.why ? ` — ${x.why}` : ''}`
|
|
121
|
+
}
|
|
122
|
+
// what changes the cast file: listed, each with its key, and with --take written
|
|
123
|
+
const proposeOrTake = (changes) => {
|
|
100
124
|
const take = option('--take')
|
|
101
125
|
if (!take) {
|
|
102
|
-
console.log(T.changes(
|
|
103
|
-
for (const c of
|
|
126
|
+
console.log(T.changes(changes.length))
|
|
127
|
+
for (const c of changes) console.log(` ${c.key} ${line(c)}`)
|
|
104
128
|
process.exit(0)
|
|
105
129
|
}
|
|
106
|
-
const keys = take === 'all' ?
|
|
107
|
-
const missing = keys.find((k) => !
|
|
130
|
+
const keys = take === 'all' ? changes.map((c) => c.key) : take.split(',').map((k) => k.trim()).filter(Boolean)
|
|
131
|
+
const missing = keys.find((k) => !changes.some((c) => c.key === k))
|
|
108
132
|
if (missing) {
|
|
109
133
|
console.error(T.unknownKey(missing))
|
|
110
134
|
process.exit(2)
|
|
111
135
|
}
|
|
112
136
|
mkdirSync(out, { recursive: true })
|
|
113
|
-
writeFileSync(castFile, writeCast(applyChanges(cast,
|
|
137
|
+
writeFileSync(castFile, writeCast(applyChanges(cast, changes, keys), castText))
|
|
114
138
|
console.log(T.took(keys.length, basename(castFile)))
|
|
115
139
|
process.exit(0)
|
|
116
140
|
}
|
|
141
|
+
// ---- the way back: an edited workbook as the changes it proposes ----
|
|
142
|
+
if (argv.includes('--from')) {
|
|
143
|
+
const edited = fromSheets(cast, readXlsx(readFileSync(resolve(option('--from')))), Object.fromEntries(profile.types.map((t) => [t.id, t.name])))
|
|
144
|
+
if (edited.problems.length) {
|
|
145
|
+
for (const p of edited.problems) console.error(`✗ ${basename(option('--from'))}: ${p}`)
|
|
146
|
+
process.exit(1)
|
|
147
|
+
}
|
|
148
|
+
proposeOrTake(edited.changes)
|
|
149
|
+
}
|
|
117
150
|
|
|
118
151
|
// the schema's kinds, to tell a made-up one; without the schema every kind is taken as real
|
|
119
152
|
const schema = join(here, '../vendor/microsoft/reference/bot.schema.yaml-authoring.json')
|
|
@@ -132,7 +165,41 @@ if (argv.includes('--facts')) {
|
|
|
132
165
|
console.log(T.privacy)
|
|
133
166
|
process.exit(0)
|
|
134
167
|
}
|
|
135
|
-
|
|
168
|
+
if (argv.includes('--declared')) {
|
|
169
|
+
const changes = []
|
|
170
|
+
for (const file of options('--declared')) {
|
|
171
|
+
const sheets = tablesOf(file)
|
|
172
|
+
let read = 0
|
|
173
|
+
let problem
|
|
174
|
+
for (const t of sheets) {
|
|
175
|
+
const r = declaredFrom(t, found, cast, { file: basename(file), lang, agentColumn: option('--agent-column') })
|
|
176
|
+
if (r.problem) {
|
|
177
|
+
problem ??= r.problem
|
|
178
|
+
continue
|
|
179
|
+
}
|
|
180
|
+
read++
|
|
181
|
+
changes.push(...r.changes)
|
|
182
|
+
if (r.unmatched.length) console.log(T.unmatched(r.unmatched.length, basename(file), `${r.unmatched.slice(0, 5).map((u) => `${T.line} ${u.line} («${u.value}»)`).join(', ')}${r.unmatched.length > 5 ? ', …' : ''}`))
|
|
183
|
+
}
|
|
184
|
+
if (!read) tableProblem(file, problem)
|
|
185
|
+
}
|
|
186
|
+
proposeOrTake(mergeDeclared(changes))
|
|
187
|
+
}
|
|
188
|
+
let fired
|
|
189
|
+
if (option('--telemetry')) {
|
|
190
|
+
const file = option('--telemetry')
|
|
191
|
+
let problem
|
|
192
|
+
for (const t of tablesOf(file)) {
|
|
193
|
+
const r = firedFrom(t, found, { file: basename(file), days, agentColumn: option('--agent-column') })
|
|
194
|
+
if ('problem' in r) problem ??= r.problem
|
|
195
|
+
else {
|
|
196
|
+
fired = r
|
|
197
|
+
break
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
if (!fired) tableProblem(file, problem)
|
|
201
|
+
}
|
|
202
|
+
const result = compare(found, profile, cast, fired)
|
|
136
203
|
const looks = reviewCast(result)
|
|
137
204
|
const roles = Object.values(result.roles)
|
|
138
205
|
console.log(T.found(found.length, roles.filter((r) => r === 'orchestrator').length, roles.filter((r) => r === 'specialist').length))
|