@formigio/fazemos-cli 0.10.15 → 0.10.16
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/index.js +146 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10,6 +10,7 @@ import { isProjectConnectionUnavailable, renderProjectConnectionUnavailableCopy,
|
|
|
10
10
|
import { loadYaml, summarize } from './yaml/load.js';
|
|
11
11
|
import { printFindings, printJson } from './yaml/format.js';
|
|
12
12
|
import { validateManifest } from './manifest/checks.js';
|
|
13
|
+
import { findLocalRegistry, resolveRole, buildInboxFile, writeInboxFile, buildNotificationPayload, gitCommitInboxFile, } from './dispatch.js';
|
|
13
14
|
import { execSync } from 'child_process';
|
|
14
15
|
import { readFileSync, readdirSync, writeFileSync, mkdirSync, existsSync, statSync } from 'fs';
|
|
15
16
|
import { fileURLToPath } from 'url';
|
|
@@ -8052,6 +8053,151 @@ Examples:
|
|
|
8052
8053
|
if (summary.errors > 0)
|
|
8053
8054
|
process.exit(1);
|
|
8054
8055
|
});
|
|
8056
|
+
// ── `fazemos dispatch` — role-to-role dispatch ─────────────────────
|
|
8057
|
+
//
|
|
8058
|
+
// Writes an inbox markdown file in the recipient role's operating dir and
|
|
8059
|
+
// calls POST /api/notifications/dispatch so the API can fire any
|
|
8060
|
+
// configured human-filler notifications (Slack today).
|
|
8061
|
+
//
|
|
8062
|
+
// Recipients are resolved via `.fazemos/roles.json` walked up from cwd.
|
|
8063
|
+
// Cross-workspace roles are followed via the local registry's
|
|
8064
|
+
// `cross_workspace_roles` block.
|
|
8065
|
+
//
|
|
8066
|
+
// Example:
|
|
8067
|
+
// fazemos dispatch founder question --from business-strategist \
|
|
8068
|
+
// --body "Should we ship F19 before F7?" --priority high --commit
|
|
8069
|
+
program
|
|
8070
|
+
.command('dispatch <to> <type>')
|
|
8071
|
+
.description(`Write an inbox markdown file in <to>'s operating dir and (optionally) fire
|
|
8072
|
+
notifications via the API.
|
|
8073
|
+
|
|
8074
|
+
Recipients are resolved via the nearest .fazemos/roles.json registry, walking
|
|
8075
|
+
up from the current directory. Cross-workspace recipients are followed via
|
|
8076
|
+
the local registry's cross_workspace_roles block.
|
|
8077
|
+
|
|
8078
|
+
Types: question | task | signal | response | flag | decision | direction`)
|
|
8079
|
+
.requiredOption('--from <role>', 'sender role-slug (required)')
|
|
8080
|
+
.option('--body <text>', 'markdown body of the dispatch (or use --body-file)')
|
|
8081
|
+
.option('--body-file <path>', 'read body from a file')
|
|
8082
|
+
.option('--priority <level>', 'low | normal | high', 'normal')
|
|
8083
|
+
.option('--re <ref>', 'optional reference (worksheet id, file path, etc.)')
|
|
8084
|
+
.option('--thread <id>', 'optional prior item id this responds to')
|
|
8085
|
+
.option('--expires-at <iso>', 'optional deadline (ISO 8601)')
|
|
8086
|
+
.option('--commit', 'git add + commit the inbox file after writing')
|
|
8087
|
+
.option('--no-notify', 'skip the API notification call (file-only)')
|
|
8088
|
+
.action(async (to, type, opts) => {
|
|
8089
|
+
try {
|
|
8090
|
+
// Validate type
|
|
8091
|
+
const allowedTypes = ['question', 'task', 'signal', 'response', 'flag', 'decision', 'direction'];
|
|
8092
|
+
if (!allowedTypes.includes(type)) {
|
|
8093
|
+
throw new Error(`Invalid type "${type}". Allowed: ${allowedTypes.join(', ')}`);
|
|
8094
|
+
}
|
|
8095
|
+
// Resolve body
|
|
8096
|
+
let body = opts.body;
|
|
8097
|
+
if (!body && opts.bodyFile) {
|
|
8098
|
+
body = readFileSync(opts.bodyFile, 'utf-8').trim();
|
|
8099
|
+
}
|
|
8100
|
+
if (!body) {
|
|
8101
|
+
throw new Error('--body or --body-file is required');
|
|
8102
|
+
}
|
|
8103
|
+
// Find local registry
|
|
8104
|
+
const localRegistry = findLocalRegistry(process.cwd());
|
|
8105
|
+
if (!localRegistry) {
|
|
8106
|
+
throw new Error(`No .fazemos/roles.json found in cwd or any parent. ` +
|
|
8107
|
+
`Run from inside a Fazemos-aware workspace.`);
|
|
8108
|
+
}
|
|
8109
|
+
// Resolve recipient
|
|
8110
|
+
const resolved = resolveRole(to, localRegistry);
|
|
8111
|
+
if (!resolved) {
|
|
8112
|
+
throw new Error(`Role "${to}" not found in local registry or any cross-workspace ref. ` +
|
|
8113
|
+
`Check .fazemos/roles.json.`);
|
|
8114
|
+
}
|
|
8115
|
+
const { role, registry } = resolved;
|
|
8116
|
+
const input = {
|
|
8117
|
+
to,
|
|
8118
|
+
from: opts.from,
|
|
8119
|
+
type: type,
|
|
8120
|
+
priority: (opts.priority ?? 'normal'),
|
|
8121
|
+
body,
|
|
8122
|
+
re: opts.re,
|
|
8123
|
+
thread: opts.thread,
|
|
8124
|
+
expiresAt: opts.expiresAt,
|
|
8125
|
+
};
|
|
8126
|
+
// Build + write file
|
|
8127
|
+
const { filename, content, summary } = buildInboxFile(input);
|
|
8128
|
+
const fullPath = writeInboxFile(registry._workspaceRoot, role, filename, content);
|
|
8129
|
+
const relPath = fullPath.startsWith(registry._workspaceRoot)
|
|
8130
|
+
? fullPath.slice(registry._workspaceRoot.length + 1)
|
|
8131
|
+
: fullPath;
|
|
8132
|
+
console.log(chalk.green(`✓ Wrote inbox file:`));
|
|
8133
|
+
console.log(` ${chalk.cyan(fullPath)}`);
|
|
8134
|
+
console.log(` Workspace: ${registry.workspace} Filler: ${role.filler.identity} (${role.filler.type})`);
|
|
8135
|
+
// Notify (unless --no-notify)
|
|
8136
|
+
if (opts.notify !== false) {
|
|
8137
|
+
try {
|
|
8138
|
+
const payload = buildNotificationPayload(input, role, relPath, summary);
|
|
8139
|
+
const resp = await api('POST', '/api/notifications/dispatch', payload);
|
|
8140
|
+
if (resp.notified) {
|
|
8141
|
+
console.log(chalk.green(`✓ Notification fired: ${resp.channel}`));
|
|
8142
|
+
}
|
|
8143
|
+
else {
|
|
8144
|
+
console.log(chalk.gray(` (no notification fired — ${resp.reason ?? 'unknown'})`));
|
|
8145
|
+
}
|
|
8146
|
+
}
|
|
8147
|
+
catch (err) {
|
|
8148
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8149
|
+
console.log(chalk.yellow(` warning: notification call failed — ${msg}`));
|
|
8150
|
+
console.log(chalk.yellow(` (inbox file was still written successfully)`));
|
|
8151
|
+
}
|
|
8152
|
+
}
|
|
8153
|
+
else {
|
|
8154
|
+
console.log(chalk.gray(' (--no-notify; API not called)'));
|
|
8155
|
+
}
|
|
8156
|
+
// Commit if requested
|
|
8157
|
+
if (opts.commit) {
|
|
8158
|
+
try {
|
|
8159
|
+
gitCommitInboxFile(registry._workspaceRoot, relPath, `dispatch(${opts.from} → ${to}): ${type}`);
|
|
8160
|
+
console.log(chalk.green('✓ Committed.'));
|
|
8161
|
+
}
|
|
8162
|
+
catch (err) {
|
|
8163
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
8164
|
+
console.log(chalk.yellow(` warning: commit failed — ${msg}`));
|
|
8165
|
+
}
|
|
8166
|
+
}
|
|
8167
|
+
}
|
|
8168
|
+
catch (err) {
|
|
8169
|
+
console.error(chalk.red(err instanceof Error ? err.message : String(err)));
|
|
8170
|
+
process.exit(1);
|
|
8171
|
+
}
|
|
8172
|
+
});
|
|
8173
|
+
program
|
|
8174
|
+
.command('dispatch-list-roles')
|
|
8175
|
+
.description('List roles available in the nearest .fazemos/roles.json registry')
|
|
8176
|
+
.action(() => {
|
|
8177
|
+
const reg = findLocalRegistry(process.cwd());
|
|
8178
|
+
if (!reg) {
|
|
8179
|
+
console.error(chalk.red('No .fazemos/roles.json found in cwd or any parent.'));
|
|
8180
|
+
process.exit(1);
|
|
8181
|
+
}
|
|
8182
|
+
console.log(chalk.cyan(`Registry: ${reg._registryPath}`));
|
|
8183
|
+
console.log(chalk.cyan(`Workspace: ${reg.workspace} Org: ${reg.org}${reg.project ? ' Project: ' + reg.project : ''}`));
|
|
8184
|
+
console.log();
|
|
8185
|
+
console.log(chalk.bold('Local roles:'));
|
|
8186
|
+
for (const [slug, role] of Object.entries(reg.roles)) {
|
|
8187
|
+
const fillerTag = role.filler.type === 'human'
|
|
8188
|
+
? chalk.yellow(`${role.filler.identity} (human)`)
|
|
8189
|
+
: chalk.gray(`${role.filler.identity} (agent)`);
|
|
8190
|
+
const notifyTag = role.notification ? chalk.green(' [notify]') : '';
|
|
8191
|
+
console.log(` ${chalk.cyan(slug.padEnd(32))} ${fillerTag}${notifyTag}`);
|
|
8192
|
+
}
|
|
8193
|
+
if (reg.cross_workspace_roles) {
|
|
8194
|
+
console.log();
|
|
8195
|
+
console.log(chalk.bold('Cross-workspace roles:'));
|
|
8196
|
+
for (const [slug, xref] of Object.entries(reg.cross_workspace_roles)) {
|
|
8197
|
+
console.log(` ${chalk.cyan(slug.padEnd(32))} → ${xref.workspace_path}`);
|
|
8198
|
+
}
|
|
8199
|
+
}
|
|
8200
|
+
});
|
|
8055
8201
|
// Skip auto-parse only when running under Vitest (which sets process.env.VITEST).
|
|
8056
8202
|
// Tests import `program` and drive it via `program.parseAsync(...)` after mocking
|
|
8057
8203
|
// `./api.js`. In every other context — direct invocation, npx tsx, OR the bin
|