trackfw 2.15.1 → 3.0.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.
@@ -1,7 +1,11 @@
1
1
  'use strict'
2
2
 
3
+ const os = require('node:os')
3
4
  const { Command } = require('commander')
4
- const { catalog, execute, parseSurfaces } = require('../integrations')
5
+ const { catalog, execute, parseSurfaces, buildPlans } = require('../integrations')
6
+ const identityStore = require('../identity')
7
+ const identityWizard = require('./identity-wizard')
8
+ const { t } = require('../i18n')
5
9
 
6
10
  const csv = value => String(value).split(',').map(entry => entry.trim()).filter(Boolean)
7
11
  const collect = (value, previous) => previous.concat(value)
@@ -23,6 +27,66 @@ async function promptSelection(kind, options, prompts = require('@inquirer/promp
23
27
  options.items = await checkbox({ message: `${kind} to manage`, choices: catalog[kind].map(item => ({ name: item.name, value: item.id })), required: true })
24
28
  }
25
29
 
30
+ // resolveScope decide o escopo de instalação (ADR-2026-07-25-escopo-de-
31
+ // instalacao-selecionavel-para-agents-e-skills):
32
+ //
33
+ // - `--scope` explícito (`options.scope !== undefined`) é sempre respeitado
34
+ // e nunca dispara prompt — apenas validado contra os valores aceitos. A
35
+ // detecção é por *flag-set* (undefined), nunca por comparação de valor,
36
+ // para não confundir um `--scope project` explícito com o default.
37
+ // - Sem flag e `interactive` desligado (comando `list`, D6): default
38
+ // `global`, nunca lança erro (list nunca é destrutivo).
39
+ // - Sem flag e stdin não é um TTY (D1): default `global` — exceto para
40
+ // `uninstall` (ADR D8), que falha exigindo `--scope` explícito. D1 foi
41
+ // aprovado no enquadramento "onde instalar"; aplicá-lo a uninstall
42
+ // permitiria que um script de CI que hoje limpa `.claude/agents/` do
43
+ // repositório passasse a apagar arquivos do home do usuário.
44
+ // - Sem flag, `interactive` ligado e TTY (D2): pergunta o escopo, com
45
+ // `global` pré-selecionado, para toda operação incluindo uninstall — o
46
+ // usuário vê a escolha antes de destruir. Usa o mesmo mecanismo de prompt
47
+ // (`@inquirer/prompts`) já empregado por `identity-wizard.js`, sem
48
+ // dependência nova.
49
+ //
50
+ // `interactive` é passado pelo chamador como `false` para `list` — comando de
51
+ // leitura que nunca deve bloquear em prompt, mas que ainda assim precisa do
52
+ // mesmo default `global` para não reportar deployments divergentes dos que o
53
+ // `install` gravou (D6). `operation` identifica install/update/uninstall/list
54
+ // para o branch de D8. Espelha
55
+ // internal/commands/integrations_flags.go:resolveScope.
56
+ async function resolveScope(options, { interactive = true, operation, prompts = require('@inquirer/prompts') } = {}) {
57
+ if (options.scope !== undefined) {
58
+ if (options.scope !== 'project' && options.scope !== 'global') throw new Error(`Unsupported scope: ${options.scope}`)
59
+ return options.scope
60
+ }
61
+ if (!interactive) return 'global'
62
+ if (!process.stdin.isTTY) {
63
+ if (operation === 'uninstall') throw new Error('uninstall requires --scope in non-interactive mode')
64
+ return 'global'
65
+ }
66
+ const { select } = prompts
67
+ return select({
68
+ message: 'Onde instalar os artefatos?',
69
+ default: 'global',
70
+ choices: [
71
+ { name: 'Pasta do usuário (~/.claude) — vale para todos os projetos', value: 'global' },
72
+ { name: 'Este projeto (.claude) — apenas neste repositório', value: 'project' },
73
+ ],
74
+ })
75
+ }
76
+
77
+ // printResolvedDestinations imprime, antes da gravação, o escopo escolhido e
78
+ // os caminhos de destino que serão afetados (ADR D5) — nunca em modo --json,
79
+ // que já é o canal determinístico consumido por scripts. Recalcula os planos
80
+ // via buildPlans (sem side effect) só para enumerar destinos; a gravação em
81
+ // si acontece depois, em execute().
82
+ function printResolvedDestinations(kind, options) {
83
+ if (options.json) return
84
+ const plans = buildPlans(kind, options)
85
+ const destinations = [...new Set(plans.map(plan => plan.destination))].sort()
86
+ console.log(`Destino (${options.scope}):`)
87
+ for (const destination of destinations) console.log(` ${destination}`)
88
+ }
89
+
26
90
  async function promptAmbiguousSurfaces(kind, options, prompts = require('@inquirer/prompts')) {
27
91
  const { select } = prompts
28
92
  const selected = parseSurfaces(options.surfaces)
@@ -39,23 +103,86 @@ async function promptAmbiguousSurfaces(kind, options, prompts = require('@inquir
39
103
  function createLifecycleCommand(kind) {
40
104
  const root = new Command(kind).description(`Manage trackfw ${kind}`)
41
105
  for (const operation of ['list', 'install', 'uninstall', 'update']) {
106
+ const mutation = operation !== 'list'
42
107
  const command = new Command(operation)
43
108
  .option('--targets <targets>', 'Comma-separated target CLIs', csv)
44
109
  .option('--items <items>', `Comma-separated ${kind} IDs`, csv)
45
- .option('--scope <scope>', 'Installation scope: project or global', 'project')
110
+ .option('--scope <scope>', 'Installation scope: project or global (default: global; asks interactively)')
46
111
  .option('--surface <target=surface>', 'Surface selection (repeatable)', collect, [])
47
112
  .option('--json', 'Print deterministic JSON')
48
113
  .option('--force', 'Replace or remove modified artifacts')
49
- command.action(async options => {
114
+
115
+ // Flags de identidade são exclusivas de agents (ADR D5): skills não têm
116
+ // identidade, e createLifecycleCommand é compartilhado entre `agents` e
117
+ // `skills` — sem este filtro por kind, `trackfw skills install
118
+ // --identity` aceitaria silenciosamente uma flag sem nenhum efeito.
119
+ // Espelha internal/commands/integrations_flags.go:addIntegrationFlags.
120
+ if (mutation && kind === 'agents') {
121
+ command
122
+ .option('--identity', 'Reconfigure agent identity even if ~/.trackfw/identity.json already exists')
123
+ .option('--identity-preset <preset>', `Agent identity preset (non-interactive): none, neutral, ${identityStore.presetNames().join(', ')}`)
124
+ }
125
+
126
+ command.action(async (options, cmd) => {
50
127
  options.surfaces = options.surface || []
51
- if (options.scope !== 'project' && options.scope !== 'global') throw new Error(`Unsupported scope: ${options.scope}`)
52
- const mutation = operation !== 'list'
128
+
129
+ // Gate de escopo (ADR D1-D3, D6): resolvido incondicionalmente aqui,
130
+ // antes de qualquer seleção de targets/surfaces ou construção de
131
+ // planos, e independentemente de --targets já ter sido informado —
132
+ // caso contrário o caso mais comum (`agents install --targets claude`)
133
+ // nunca passaria por prompt algum. `list` (mutation === false) nunca
134
+ // pergunta (comando de leitura), apenas adota o default `global`.
135
+ options.scope = await resolveScope(options, { interactive: mutation, operation })
136
+
137
+ // O booleano de --identity nunca deve chegar a execute()/buildPlans()
138
+ // sob a chave "identity" — essa chave ali é reservada para uma Config
139
+ // de identidade já resolvida (ver src/integrations/index.js:execute).
140
+ // Colidir os dois faria "agents install --identity" renderizar uma
141
+ // identidade booleana nos artefatos em vez da Config real.
142
+ const forceIdentity = options.identity === true
143
+ delete options.identity
144
+
145
+ // --identity-preset é validado e persistido incondicionalmente, acima
146
+ // de qualquer ramo dependente de TTY abaixo — isso é o que faz um
147
+ // --identity-preset inválido falhar alto em CI em vez de
148
+ // silenciosamente não fazer nada. Espelha init.js e
149
+ // internal/commands/integrations_flags.go:executeIntegrationMutation.
150
+ let presetChanged = false
151
+ if (mutation && kind === 'agents') {
152
+ presetChanged = cmd.getOptionValueSource('identityPreset') === 'cli'
153
+ if (presetChanged) identityWizard.applyIdentityPresetFlag(os.homedir(), options.identityPreset, operation)
154
+ }
155
+
53
156
  if (mutation && (!options.targets || !options.targets.length)) {
54
157
  if (!process.stdin.isTTY) throw new Error(`${operation} requires --targets in non-interactive mode`)
55
158
  await promptSelection(kind, options)
56
159
  }
57
160
  if (mutation && process.stdin.isTTY) await promptAmbiguousSurfaces(kind, options)
58
161
  options.allSurfaces = operation === 'list'
162
+
163
+ // Disparo do wizard de identidade (ADR D2): mostrado somente quando o
164
+ // caminho da flag acima ainda não resolveu a identidade desta
165
+ // execução, e somente para agents (nunca skills, D5). Roda depois da
166
+ // seleção de alvo/superfície e antes de execute() para que a
167
+ // identidade recém-gravada pelo wizard seja a que é renderizada nos
168
+ // planos abaixo. Espelha
169
+ // internal/commands/integrations_flags.go:executeIntegrationMutation.
170
+ if (mutation && kind === 'agents' && !presetChanged) {
171
+ const homeRoot = os.homedir()
172
+ const identityExists = identityWizard.identityFileExists(homeRoot)
173
+ const isTTY = Boolean(process.stdin.isTTY)
174
+ if (identityWizard.shouldPromptIdentity(kind, isTTY, identityExists, forceIdentity)) {
175
+ await identityWizard.runIdentityWizard(catalog, homeRoot)
176
+ } else if (identityExists && !options.json) {
177
+ const existing = identityStore.load(homeRoot)
178
+ console.log(t('identity.inUse', { count: String(Object.keys(existing.agents || {}).length) }))
179
+ }
180
+ }
181
+
182
+ // D5: caminhos de destino impressos antes da gravação, apenas para
183
+ // operações de mutação (install/update/uninstall) e fora de --json.
184
+ if (mutation) printResolvedDestinations(kind, options)
185
+
59
186
  const output = execute(kind, operation, options)
60
187
  console.log(options.json ? JSON.stringify(output) : human(output))
61
188
  })
@@ -64,4 +191,4 @@ function createLifecycleCommand(kind) {
64
191
  return root
65
192
  }
66
193
 
67
- module.exports = { createLifecycleCommand, csv, human, promptSelection, promptAmbiguousSurfaces }
194
+ module.exports = { createLifecycleCommand, csv, human, promptSelection, promptAmbiguousSurfaces, resolveScope }
@@ -2,7 +2,9 @@
2
2
 
3
3
  const { Command } = require('commander');
4
4
  const fs = require('fs');
5
+ const os = require('os');
5
6
  const path = require('path');
7
+ const identityStore = require('../identity');
6
8
 
7
9
  function readUpdateConfig(rootDir) {
8
10
  const yaml = path.join(rootDir, 'trackfw.yaml');
@@ -62,6 +64,11 @@ cmd.action(() => {
62
64
  const generators = require('../generators/init');
63
65
  const discover = require('./discover');
64
66
 
67
+ // A identidade é carregada uma única vez, fora de qualquer try/catch —
68
+ // um identity.json corrompido deve abortar o comando inteiro, nunca cair
69
+ // silenciosamente para os nomes neutros default.
70
+ const identityConfig = identityStore.load(os.homedir());
71
+
65
72
  console.log('trackfw update — re-aplicando templates atuais...\n');
66
73
 
67
74
  // 1. Agent rules (marker-delimited, idempotent)
@@ -77,7 +84,7 @@ cmd.action(() => {
77
84
  const roots = { projectRoot: cwd };
78
85
  const manager = new IntegrationManager(roots);
79
86
  for (const kind of ['agents', 'skills']) {
80
- const plans = buildPlans(kind, { targets: ['codex'], scope: 'project' });
87
+ const plans = buildPlans(kind, { targets: ['codex'], scope: 'project', identity: identityConfig });
81
88
  const statuses = manager.inspect(plans);
82
89
  const existing = plans.filter((_, index) => statuses[index].state !== 'not-installed');
83
90
  manager.update(existing);
@@ -1006,12 +1006,35 @@ async function installAmazonQ(cwd = process.cwd()) {
1006
1006
  }
1007
1007
  }
1008
1008
 
1009
- /** Install the canonical agents and skills for one AI target. */
1010
- async function installIntegrationTarget(target, cwd = process.cwd()) {
1011
- const { execute } = require('../integrations')
1009
+ /**
1010
+ * Install the canonical agents and skills for one AI target.
1011
+ *
1012
+ * `scope` defaults to "project" for backward compatibility with the
1013
+ * compatibility entrypoints below (installAgents, installGemini, etc.), that
1014
+ * historical tests exercise directly without going through the `init`
1015
+ * wizard's scope prompt. `trackfw init` itself always passes the scope it
1016
+ * resolved (ADR-2026-07-25-escopo-de-instalacao-selecionavel-para-agents-e-
1017
+ * skills, D4).
1018
+ */
1019
+ async function installIntegrationTarget(target, cwd = process.cwd(), scope = 'project') {
1020
+ const { execute, buildPlans } = require('../integrations')
1012
1021
  const roots = { projectRoot: cwd }
1013
- execute('agents', 'install', { targets: [target], scope: 'project' }, roots)
1014
- execute('skills', 'install', { targets: [target], scope: 'project' }, roots)
1022
+ const options = { targets: [target], scope }
1023
+ // D5 — transparency: print resolved destinations before writing anything.
1024
+ // buildPlans has no side effects, so it is safe to call here purely to
1025
+ // enumerate destinations; the actual write happens below via execute().
1026
+ // Mirrors printResolvedDestinations in commands/integrations.js and Go's
1027
+ // installAITools (internal/commands/init.go) — this call site (init's AI
1028
+ // tools install) was the one place that stayed silent (divergence #2,
1029
+ // ROADMAP-2026-07-25-escopo-de-instalacao-selecionavel-para-agents-e-skills).
1030
+ const destinations = [
1031
+ ...buildPlans('agents', options).map(plan => plan.destination),
1032
+ ...buildPlans('skills', options).map(plan => plan.destination),
1033
+ ]
1034
+ console.log(`Destino (${scope}):`)
1035
+ for (const destination of [...new Set(destinations)].sort()) console.log(` ${destination}`)
1036
+ execute('agents', 'install', options, roots)
1037
+ execute('skills', 'install', options, roots)
1015
1038
  console.log(` ✓ ${target} agents and skills`)
1016
1039
  }
1017
1040
 
@@ -15,10 +15,21 @@
15
15
  "projectType_fullstack": "Full-stack (frontend + backend)",
16
16
  "projectType_frontend": "Frontend only",
17
17
  "projectType_backend": "Backend only",
18
- "projectType_governance": "Governance only (no build stack)"
18
+ "projectType_governance": "Governance only (no build stack)",
19
+ "identityPreset": "How would you like to name your agents?",
20
+ "identityCustomName": "Display name for the agent",
21
+ "identityNickname": "What should we call you? (optional)"
19
22
  },
20
23
  "success": "✓ trackfw initialized — run 'trackfw status' to see your governance state."
21
24
  },
25
+ "identity": {
26
+ "inUse": "identity: {{count}} custom agent(s)",
27
+ "wizard": {
28
+ "confirmHeader": "── Confirmation ──────────────────────────────",
29
+ "confirmQuestion": "Confirm?",
30
+ "nicknameRowLabel": "What we'll call you:"
31
+ }
32
+ },
22
33
  "adr": {
23
34
  "description": "Manage Architecture Decision Records",
24
35
  "new": {
@@ -15,10 +15,21 @@
15
15
  "projectType_fullstack": "Full-stack (frontend + backend)",
16
16
  "projectType_frontend": "Solo frontend",
17
17
  "projectType_backend": "Solo backend",
18
- "projectType_governance": "Solo gobernanza (sin stack de build)"
18
+ "projectType_governance": "Solo gobernanza (sin stack de build)",
19
+ "identityPreset": "¿Cómo quieres nombrar a tus agentes?",
20
+ "identityCustomName": "Nombre de exhibición para el agente",
21
+ "identityNickname": "¿Cómo debemos llamarte? (opcional)"
19
22
  },
20
23
  "success": "✓ trackfw inicializado — ejecuta 'trackfw status' para ver el estado de gobernanza."
21
24
  },
25
+ "identity": {
26
+ "inUse": "identidad: {{count}} agente(s) personalizado(s)",
27
+ "wizard": {
28
+ "confirmHeader": "── Confirmación ──────────────────────────────",
29
+ "confirmQuestion": "¿Confirmar?",
30
+ "nicknameRowLabel": "Cómo te llamaremos:"
31
+ }
32
+ },
22
33
  "adr": {
23
34
  "description": "Gestionar Architecture Decision Records",
24
35
  "new": {
@@ -15,10 +15,21 @@
15
15
  "projectType_fullstack": "Full-stack (frontend + backend)",
16
16
  "projectType_frontend": "Somente frontend",
17
17
  "projectType_backend": "Somente backend",
18
- "projectType_governance": "Somente governança (sem stack de build)"
18
+ "projectType_governance": "Somente governança (sem stack de build)",
19
+ "identityPreset": "Como você quer nomear seus agentes?",
20
+ "identityCustomName": "Nome de exibição para o agente",
21
+ "identityNickname": "Como devemos te chamar? (opcional)"
19
22
  },
20
23
  "success": "✓ trackfw inicializado — execute 'trackfw status' para ver o estado de governança."
21
24
  },
25
+ "identity": {
26
+ "inUse": "identidade: {{count}} agente(s) personalizado(s)",
27
+ "wizard": {
28
+ "confirmHeader": "── Confirmação ──────────────────────────────",
29
+ "confirmQuestion": "Confirmar?",
30
+ "nicknameRowLabel": "Como você será chamado:"
31
+ }
32
+ },
22
33
  "adr": {
23
34
  "description": "Gerenciar Architecture Decision Records",
24
35
  "new": {
@@ -0,0 +1,134 @@
1
+ 'use strict'
2
+
3
+ const crypto = require('node:crypto')
4
+ const fs = require('node:fs')
5
+ const path = require('node:path')
6
+
7
+ // SCHEMA_VERSION é a versão de schema atual do arquivo de configuração de
8
+ // identidade. Espelha internal/identity/identity.go:schemaVersion.
9
+ const SCHEMA_VERSION = 1
10
+
11
+ // slugPattern casa um slug válido: segmentos alfanuméricos em minúsculas
12
+ // unidos por hífens simples, ex.: "zeus", "meu-agente".
13
+ const SLUG_PATTERN = /^[a-z0-9]+(-[a-z0-9]+)*$/
14
+
15
+ function identityPath(homeDir) {
16
+ return path.join(homeDir, '.trackfw', 'identity.json')
17
+ }
18
+
19
+ // load lê a configuração de identidade de <homeDir>/.trackfw/identity.json.
20
+ //
21
+ // Se o arquivo não existir, load retorna uma Config vazia sem erro — este é
22
+ // o caminho de não-regressão e nunca deve surgir como falha para quem ainda
23
+ // não customizou sua identidade. Espelha internal/identity/identity.go:Load.
24
+ function load(homeDir) {
25
+ const filename = identityPath(homeDir)
26
+ let data
27
+ try {
28
+ data = fs.readFileSync(filename, 'utf8')
29
+ } catch (err) {
30
+ if (err.code === 'ENOENT') return { schema_version: SCHEMA_VERSION, agents: {} }
31
+ throw new Error(`identity: falha ao ler ${filename}: ${err.message}`)
32
+ }
33
+
34
+ let parsed
35
+ try {
36
+ parsed = JSON.parse(data)
37
+ } catch (err) {
38
+ throw new Error(`identity: falha ao decodificar ${filename}: ${err.message}`)
39
+ }
40
+ if (parsed.schema_version !== SCHEMA_VERSION) {
41
+ throw new Error(`identity: versao de schema nao suportada em ${filename}: ${parsed.schema_version} (esperado ${SCHEMA_VERSION})`)
42
+ }
43
+ if (!parsed.agents || typeof parsed.agents !== 'object') parsed.agents = {}
44
+ return parsed
45
+ }
46
+
47
+ // save persiste cfg em <homeDir>/.trackfw/identity.json de forma atômica.
48
+ // Espelha internal/identity/identity.go:Save — inclusive a omissão de
49
+ // user_nickname vazio e agents vazio, que resulta do comportamento
50
+ // omitempty do encoding/json do Go, e a ordenação alfabética das chaves do
51
+ // mapa agents, que resulta da ordenação de mapas do encoding/json do Go.
52
+ function save(homeDir, cfg) {
53
+ const out = { schema_version: SCHEMA_VERSION }
54
+ if (cfg.user_nickname) out.user_nickname = cfg.user_nickname
55
+ const agents = cfg.agents || {}
56
+ const ids = Object.keys(agents).sort()
57
+ if (ids.length > 0) {
58
+ const sortedAgents = {}
59
+ for (const id of ids) {
60
+ sortedAgents[id] = { display_name: agents[id].display_name, slug: agents[id].slug }
61
+ }
62
+ out.agents = sortedAgents
63
+ }
64
+
65
+ const data = `${JSON.stringify(out, null, 2)}\n`
66
+ const filename = identityPath(homeDir)
67
+ atomicWrite(filename, data, 0o600)
68
+ }
69
+
70
+ // atomicWrite grava data em filename de forma atômica: cria um arquivo
71
+ // temporário no mesmo diretório, escreve, sincroniza e então renomeia para o
72
+ // lugar final. Espelha internal/identity/identity.go:atomicWrite.
73
+ function atomicWrite(filename, data, mode) {
74
+ const directory = path.dirname(filename)
75
+ fs.mkdirSync(directory, { recursive: true, mode: 0o700 })
76
+ const temporaryName = path.join(directory, `.trackfw-tmp-${process.pid}-${crypto.randomBytes(6).toString('hex')}`)
77
+ const fd = fs.openSync(temporaryName, 'w', mode)
78
+ try {
79
+ fs.writeSync(fd, data)
80
+ fs.fsyncSync(fd)
81
+ } finally {
82
+ fs.closeSync(fd)
83
+ }
84
+ fs.renameSync(temporaryName, filename)
85
+ }
86
+
87
+ // agentName retorna o display name customizado sufixado com "-tf". Este é o
88
+ // único lugar do código onde o sufixo "-tf" é aplicado a um slug. Espelha
89
+ // internal/identity/identity.go:AgentName.
90
+ function agentName(slug) {
91
+ return `${slug}-tf`
92
+ }
93
+
94
+ // validate verifica cfg quanto à integridade estrutural e referencial:
95
+ // - todo id de agente em cfg.agents deve estar presente em knownIds
96
+ // - display_name não pode ser vazio
97
+ // - slug deve casar com ^[a-z0-9]+(-[a-z0-9]+)*$
98
+ // - slugs devem ser únicos entre os agentes
99
+ // - slug não pode terminar com o sufixo "-tf" (ele é acrescentado
100
+ // automaticamente por agentName)
101
+ // Espelha internal/identity/identity.go:Validate.
102
+ function validate(cfg, knownIds) {
103
+ const known = new Set(knownIds)
104
+ const seenSlugs = new Map()
105
+ const agents = (cfg && cfg.agents) || {}
106
+
107
+ for (const [id, agent] of Object.entries(agents)) {
108
+ if (!known.has(id)) {
109
+ throw new Error(`identity: agente desconhecido "${id}" nao esta na lista de agentes conhecidos`)
110
+ }
111
+ if (!agent.display_name) {
112
+ throw new Error(`identity: display_name vazio para o agente "${id}"`)
113
+ }
114
+ if (!SLUG_PATTERN.test(agent.slug)) {
115
+ throw new Error(`identity: slug invalido "${agent.slug}" para o agente "${id}" (esperado padrao ${SLUG_PATTERN.source})`)
116
+ }
117
+ if (seenSlugs.has(agent.slug)) {
118
+ throw new Error(`identity: slug duplicado "${agent.slug}" entre os agentes "${seenSlugs.get(agent.slug)}" e "${id}"`)
119
+ }
120
+ if (agent.slug.endsWith('-tf')) {
121
+ throw new Error(`identity: slug "${agent.slug}" do agente "${id}" nao deve incluir o sufixo "-tf"; ele e acrescentado automaticamente (use "${agent.slug.slice(0, -3)}" em vez de "${agent.slug}")`)
122
+ }
123
+ seenSlugs.set(agent.slug, id)
124
+ }
125
+ }
126
+
127
+ // lookup retorna a identidade configurada para id, se houver. Espelha
128
+ // internal/identity/identity.go:Lookup.
129
+ function lookup(cfg, id) {
130
+ const agents = (cfg && cfg.agents) || {}
131
+ return Object.prototype.hasOwnProperty.call(agents, id) ? agents[id] : undefined
132
+ }
133
+
134
+ module.exports = { SCHEMA_VERSION, load, save, agentName, validate, lookup, identityPath }
@@ -0,0 +1,19 @@
1
+ 'use strict'
2
+
3
+ const { slugify } = require('./slug')
4
+ const { knownAgentIds, preset, presetNames } = require('./preset')
5
+ const { load, save, agentName, validate, lookup, identityPath, SCHEMA_VERSION } = require('./config')
6
+
7
+ module.exports = {
8
+ slugify,
9
+ knownAgentIds,
10
+ preset,
11
+ presetNames,
12
+ load,
13
+ save,
14
+ agentName,
15
+ validate,
16
+ lookup,
17
+ identityPath,
18
+ SCHEMA_VERSION,
19
+ }