@formigio/fazemos-cli 0.10.14 → 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.
@@ -1,19 +1,31 @@
1
1
  /**
2
- * F16 — Role-aware copy for the `PROJECT_CONNECTION_UNAVAILABLE`
3
- * structured error (tech spec §5.14).
2
+ * F16 / F22 — Role × reason × provider-aware copy for the
3
+ * `PROJECT_CONNECTION_UNAVAILABLE` structured error (tech spec §5.14
4
+ * + F22 spec §8.3 item 7 + UX manifest §3).
4
5
  *
5
6
  * Mirrors the web's `renderProjectConnectionUnavailableCopy` so admin
6
7
  * and member experiences stay consistent across surfaces. CLI variant
7
8
  * adds the settings URL as a trailing line for admins; members see
8
9
  * "Ask your admin" without a URL.
9
10
  *
11
+ * F22 added the `provider` axis: copy now branches on
12
+ * - reason (missing | revoked | suspended | uninstalled)
13
+ * - role (owner|admin vs member)
14
+ * - provider (github | bitbucket | null/unknown)
15
+ *
16
+ * The matrix is the table in Sage UX-manifest §3 ("Role-Aware Pipeline
17
+ * Error Copy"). For unknown provider (defensive fallback) we render a
18
+ * provider-neutral line; the user can always go to settings and see
19
+ * which Connection is bound.
20
+ *
10
21
  * The `getEnv()` helper supplies the web origin so we can construct
11
22
  * fully-qualified URLs the user can click in the terminal.
12
23
  */
13
24
  /**
14
25
  * Structured payload returned by pipeline-run endpoints when the
15
26
  * Project's Connection is missing/revoked/suspended/uninstalled.
16
- * Matches the contract in tech spec §5.14.
27
+ * Matches the contract in tech spec §5.14 (F16) + F22 §9.4 (added
28
+ * `provider` for provider-aware copy).
17
29
  */
18
30
  export interface ProjectConnectionUnavailableErrorBody {
19
31
  error: 'project_connection_unavailable';
@@ -24,6 +36,14 @@ export interface ProjectConnectionUnavailableErrorBody {
24
36
  projectId: string;
25
37
  projectSlug: string;
26
38
  connectionId: string | null;
39
+ /**
40
+ * F22 — Provider for the Project's bound (or expected) Connection.
41
+ * `null`/absent when reason='missing' AND no Connection row exists
42
+ * yet (the server can't know which provider the user wants); the
43
+ * copy falls back to a provider-neutral "Code platform connection"
44
+ * line. Forward-compat: an unknown provider string also falls back.
45
+ */
46
+ provider?: 'github' | 'bitbucket' | null;
27
47
  }
28
48
  export interface ConnectionErrorLines {
29
49
  /** Lead headline. */
@@ -42,5 +62,8 @@ export declare function isProjectConnectionUnavailable(body: unknown): body is P
42
62
  * Produce the role-aware lines for terminal rendering. Caller decides
43
63
  * how to color / format. The shape is plain so a JSON test fixture
44
64
  * stays readable.
65
+ *
66
+ * F22 — Branches on `error.provider` per UX-manifest §3 table. Falls
67
+ * back to a provider-neutral line when provider is unknown/null.
45
68
  */
46
- export declare function renderProjectConnectionUnavailableCopy(error: Pick<ProjectConnectionUnavailableErrorBody, 'reason' | 'orgSlug' | 'connectionId'>, role: 'owner' | 'admin' | 'member' | string): ConnectionErrorLines;
69
+ export declare function renderProjectConnectionUnavailableCopy(error: Pick<ProjectConnectionUnavailableErrorBody, 'reason' | 'orgSlug' | 'connectionId' | 'provider'>, role: 'owner' | 'admin' | 'member' | string): ConnectionErrorLines;
@@ -1,12 +1,23 @@
1
1
  /**
2
- * F16 — Role-aware copy for the `PROJECT_CONNECTION_UNAVAILABLE`
3
- * structured error (tech spec §5.14).
2
+ * F16 / F22 — Role × reason × provider-aware copy for the
3
+ * `PROJECT_CONNECTION_UNAVAILABLE` structured error (tech spec §5.14
4
+ * + F22 spec §8.3 item 7 + UX manifest §3).
4
5
  *
5
6
  * Mirrors the web's `renderProjectConnectionUnavailableCopy` so admin
6
7
  * and member experiences stay consistent across surfaces. CLI variant
7
8
  * adds the settings URL as a trailing line for admins; members see
8
9
  * "Ask your admin" without a URL.
9
10
  *
11
+ * F22 added the `provider` axis: copy now branches on
12
+ * - reason (missing | revoked | suspended | uninstalled)
13
+ * - role (owner|admin vs member)
14
+ * - provider (github | bitbucket | null/unknown)
15
+ *
16
+ * The matrix is the table in Sage UX-manifest §3 ("Role-Aware Pipeline
17
+ * Error Copy"). For unknown provider (defensive fallback) we render a
18
+ * provider-neutral line; the user can always go to settings and see
19
+ * which Connection is bound.
20
+ *
10
21
  * The `getEnv()` helper supplies the web origin so we can construct
11
22
  * fully-qualified URLs the user can click in the terminal.
12
23
  */
@@ -56,10 +67,26 @@ function deriveWebOrigin() {
56
67
  return null;
57
68
  }
58
69
  }
70
+ /**
71
+ * Human-friendly provider label. Defensive: unknown providers render
72
+ * as a generic "code platform" string so the copy stays useful if a
73
+ * future provider ships before this file is updated (UX manifest §3
74
+ * "Defensive fallback (unknown provider)").
75
+ */
76
+ function providerLabel(provider) {
77
+ if (provider === 'github')
78
+ return 'GitHub';
79
+ if (provider === 'bitbucket')
80
+ return 'BitBucket';
81
+ return null;
82
+ }
59
83
  /**
60
84
  * Produce the role-aware lines for terminal rendering. Caller decides
61
85
  * how to color / format. The shape is plain so a JSON test fixture
62
86
  * stays readable.
87
+ *
88
+ * F22 — Branches on `error.provider` per UX-manifest §3 table. Falls
89
+ * back to a provider-neutral line when provider is unknown/null.
63
90
  */
64
91
  export function renderProjectConnectionUnavailableCopy(error, role) {
65
92
  const isAdmin = role === 'owner' || role === 'admin';
@@ -68,34 +95,65 @@ export function renderProjectConnectionUnavailableCopy(error, role) {
68
95
  ? `/org/${error.orgSlug}/settings/connections/${error.connectionId}`
69
96
  : `/org/${error.orgSlug}/settings/connections`;
70
97
  const ctaUrl = origin ? `${origin}${settingsPath}` : settingsPath;
98
+ const label = providerLabel(error.provider);
99
+ // ── Defensive fallback: unknown provider ──────────────────────
100
+ // UX manifest §3 last paragraph: "render the provider name as a raw
101
+ // string if neither github nor bitbucket — 'Code platform connection
102
+ // is unavailable. Check the project's connection in settings.'"
103
+ if (label === null) {
104
+ if (error.reason === 'missing') {
105
+ return isAdmin
106
+ ? {
107
+ title: 'Connect a code platform to run this pipeline.',
108
+ body: 'This project needs a code platform connection to clone its repos.',
109
+ ctaUrl,
110
+ }
111
+ : {
112
+ title: 'This project needs a code platform connection.',
113
+ body: 'Ask your admin to add one in Project Settings.',
114
+ };
115
+ }
116
+ return isAdmin
117
+ ? {
118
+ title: `Code platform connection ${error.reason}.`,
119
+ body: "Check the project's connection in settings.",
120
+ ctaUrl,
121
+ }
122
+ : {
123
+ title: 'This project needs a code platform connection.',
124
+ body: 'Ask your admin — the existing connection is not usable.',
125
+ };
126
+ }
127
+ // ── reason: 'missing' ─────────────────────────────────────────
71
128
  if (error.reason === 'missing') {
72
129
  return isAdmin
73
130
  ? {
74
- title: 'Connect GitHub to run this pipeline.',
75
- body: 'This project needs a GitHub connection to clone its repos.',
131
+ title: `Connect ${label} to run this pipeline.`,
132
+ body: `This project needs a ${label} connection to clone its repos.`,
76
133
  ctaUrl,
77
134
  }
78
135
  : {
79
- title: 'This project needs a GitHub connection.',
136
+ title: `This project needs a ${label} connection.`,
80
137
  body: 'Ask your admin to add one in Project Settings.',
81
138
  };
82
139
  }
140
+ // ── reason: 'revoked' | 'uninstalled' | 'suspended' ───────────
83
141
  if (error.reason === 'revoked' ||
84
142
  error.reason === 'uninstalled' ||
85
143
  error.reason === 'suspended') {
86
144
  return isAdmin
87
145
  ? {
88
- title: `GitHub connection ${error.reason}.`,
89
- body: 'New pipeline steps that need GitHub will fail until the project is bound to a different connection.',
146
+ title: `${label} connection ${error.reason}.`,
147
+ body: `New pipeline steps that need ${label} will fail until the project is bound to a different connection.`,
90
148
  ctaUrl,
91
149
  }
92
150
  : {
93
- title: 'This project needs a GitHub connection.',
151
+ title: `This project needs a ${label} connection.`,
94
152
  body: 'Ask your admin — the existing connection is not usable.',
95
153
  };
96
154
  }
97
155
  return {
98
- title: 'GitHub connection is unavailable.',
156
+ title: `${label} connection is unavailable.`,
99
157
  body: '',
100
158
  };
101
159
  }
@@ -1 +1 @@
1
- {"version":3,"file":"connectionErrorCopy.js","sourceRoot":"","sources":["../src/connectionErrorCopy.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"connectionErrorCopy.js","sourceRoot":"","sources":["../src/connectionErrorCopy.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,96 @@
1
+ /**
2
+ * `fazemos dispatch` — write an inbox file for a role and notify any
3
+ * configured human-filled recipient via the API.
4
+ *
5
+ * Data flow:
6
+ *
7
+ * CLI invocation
8
+ * ↓ resolve recipient via .fazemos/roles.json
9
+ * ↓ generate frontmatter + body markdown
10
+ * ↓ write file at <recipient.inbox>/<timestamp>-<from>-<slug>.md
11
+ * ↓ POST /api/notifications/dispatch (summary + notification config)
12
+ * ↓ API gates on filler.type === 'human' + notification configured
13
+ * ↓ API fans out (Slack today)
14
+ *
15
+ * Workspaces have `.fazemos/roles.json` registries. The CLI walks up from
16
+ * cwd to find the local registry, then can hop to other workspaces via
17
+ * the `cross_workspace_roles` block.
18
+ */
19
+ export interface RoleNotification {
20
+ channel: 'slack';
21
+ secret_key: string;
22
+ }
23
+ export interface RoleEntry {
24
+ filler: {
25
+ type: 'human' | 'agent';
26
+ identity: string;
27
+ };
28
+ subagent: string | null;
29
+ inbox: string;
30
+ briefs: string;
31
+ persona: string;
32
+ notification?: RoleNotification;
33
+ scope: 'org' | 'project';
34
+ }
35
+ export interface CrossWorkspaceRef {
36
+ workspace_path: string;
37
+ registry_path: string;
38
+ }
39
+ export interface RolesRegistry {
40
+ workspace: string;
41
+ org: string;
42
+ project?: string;
43
+ roles: Record<string, RoleEntry>;
44
+ cross_workspace_roles?: Record<string, CrossWorkspaceRef>;
45
+ _workspaceRoot: string;
46
+ _registryPath: string;
47
+ }
48
+ /**
49
+ * Walk up from `startDir` looking for a `.fazemos/roles.json`. Returns the
50
+ * loaded registry or null if not found within ~10 levels.
51
+ */
52
+ export declare function findLocalRegistry(startDir: string): RolesRegistry | null;
53
+ /**
54
+ * Load a registry from an explicit workspace path (used when following
55
+ * `cross_workspace_roles` references).
56
+ */
57
+ export declare function loadRegistryAt(workspacePath: string): RolesRegistry | null;
58
+ /**
59
+ * Find a role by slug. Looks in the local registry first; falls back to
60
+ * the cross-workspace references if not found locally. Returns the role
61
+ * entry together with the registry it was resolved from (so callers know
62
+ * which workspace to write into).
63
+ */
64
+ export declare function resolveRole(slug: string, localRegistry: RolesRegistry): {
65
+ role: RoleEntry;
66
+ registry: RolesRegistry;
67
+ } | null;
68
+ export interface DispatchInput {
69
+ to: string;
70
+ from: string;
71
+ type: 'question' | 'task' | 'signal' | 'response' | 'flag' | 'decision' | 'direction';
72
+ priority?: 'low' | 'normal' | 'high';
73
+ body: string;
74
+ re?: string;
75
+ thread?: string;
76
+ expiresAt?: string;
77
+ }
78
+ export declare function buildInboxFile(input: DispatchInput): {
79
+ filename: string;
80
+ content: string;
81
+ summary: string;
82
+ };
83
+ export declare function writeInboxFile(workspaceRoot: string, role: RoleEntry, fileName: string, content: string): string;
84
+ export interface NotificationPayload {
85
+ to_role: string;
86
+ to_filler_type: 'human' | 'agent';
87
+ notification?: RoleNotification;
88
+ from_role: string;
89
+ type: string;
90
+ priority: 'low' | 'normal' | 'high';
91
+ summary: string;
92
+ ref?: string;
93
+ source: 'cli';
94
+ }
95
+ export declare function buildNotificationPayload(input: DispatchInput, role: RoleEntry, fileRef: string, summary: string): NotificationPayload;
96
+ export declare function gitCommitInboxFile(workspaceRoot: string, relativePath: string, message: string): void;
@@ -0,0 +1,169 @@
1
+ /**
2
+ * `fazemos dispatch` — write an inbox file for a role and notify any
3
+ * configured human-filled recipient via the API.
4
+ *
5
+ * Data flow:
6
+ *
7
+ * CLI invocation
8
+ * ↓ resolve recipient via .fazemos/roles.json
9
+ * ↓ generate frontmatter + body markdown
10
+ * ↓ write file at <recipient.inbox>/<timestamp>-<from>-<slug>.md
11
+ * ↓ POST /api/notifications/dispatch (summary + notification config)
12
+ * ↓ API gates on filler.type === 'human' + notification configured
13
+ * ↓ API fans out (Slack today)
14
+ *
15
+ * Workspaces have `.fazemos/roles.json` registries. The CLI walks up from
16
+ * cwd to find the local registry, then can hop to other workspaces via
17
+ * the `cross_workspace_roles` block.
18
+ */
19
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
20
+ import { dirname, join, resolve as resolvePath } from 'path';
21
+ import { homedir } from 'os';
22
+ import { execSync } from 'child_process';
23
+ // ── Registry loading ────────────────────────────────────────────
24
+ /**
25
+ * Walk up from `startDir` looking for a `.fazemos/roles.json`. Returns the
26
+ * loaded registry or null if not found within ~10 levels.
27
+ */
28
+ export function findLocalRegistry(startDir) {
29
+ let dir = resolvePath(startDir);
30
+ for (let i = 0; i < 10; i++) {
31
+ const candidate = join(dir, '.fazemos', 'roles.json');
32
+ if (existsSync(candidate)) {
33
+ const raw = JSON.parse(readFileSync(candidate, 'utf-8'));
34
+ return {
35
+ ...raw,
36
+ _workspaceRoot: dir,
37
+ _registryPath: candidate,
38
+ };
39
+ }
40
+ const parent = dirname(dir);
41
+ if (parent === dir)
42
+ break;
43
+ dir = parent;
44
+ }
45
+ return null;
46
+ }
47
+ /**
48
+ * Load a registry from an explicit workspace path (used when following
49
+ * `cross_workspace_roles` references).
50
+ */
51
+ export function loadRegistryAt(workspacePath) {
52
+ const expanded = workspacePath.startsWith('~')
53
+ ? join(homedir(), workspacePath.slice(1).replace(/^\/+/, ''))
54
+ : workspacePath;
55
+ const candidate = join(expanded, '.fazemos', 'roles.json');
56
+ if (!existsSync(candidate))
57
+ return null;
58
+ const raw = JSON.parse(readFileSync(candidate, 'utf-8'));
59
+ return {
60
+ ...raw,
61
+ _workspaceRoot: expanded,
62
+ _registryPath: candidate,
63
+ };
64
+ }
65
+ /**
66
+ * Find a role by slug. Looks in the local registry first; falls back to
67
+ * the cross-workspace references if not found locally. Returns the role
68
+ * entry together with the registry it was resolved from (so callers know
69
+ * which workspace to write into).
70
+ */
71
+ export function resolveRole(slug, localRegistry) {
72
+ // 1. Local registry
73
+ if (localRegistry.roles[slug]) {
74
+ return { role: localRegistry.roles[slug], registry: localRegistry };
75
+ }
76
+ // 2. Cross-workspace hop
77
+ const xref = localRegistry.cross_workspace_roles?.[slug];
78
+ if (xref) {
79
+ const remote = loadRegistryAt(xref.workspace_path);
80
+ if (remote && remote.roles[slug]) {
81
+ return { role: remote.roles[slug], registry: remote };
82
+ }
83
+ }
84
+ return null;
85
+ }
86
+ function slugify(s) {
87
+ return s
88
+ .toLowerCase()
89
+ .replace(/[^a-z0-9]+/g, '-')
90
+ .replace(/^-+|-+$/g, '')
91
+ .slice(0, 50);
92
+ }
93
+ function nowParts() {
94
+ // UTC throughout: inbox filenames embed the date+time, and the
95
+ // canonical id includes the filename. Using local time made files
96
+ // inconsistent across timezones — a 2026-05-23T01:52Z dispatch run
97
+ // in MDT would render as 2026-05-22-1952 but the same dispatch from
98
+ // a UTC host would render as 2026-05-23-0152. Pin to UTC so the
99
+ // filename matches the iso timestamp.
100
+ const d = new Date();
101
+ const yyyy = d.getUTCFullYear();
102
+ const mm = String(d.getUTCMonth() + 1).padStart(2, '0');
103
+ const dd = String(d.getUTCDate()).padStart(2, '0');
104
+ const HH = String(d.getUTCHours()).padStart(2, '0');
105
+ const MM = String(d.getUTCMinutes()).padStart(2, '0');
106
+ return {
107
+ iso: d.toISOString(),
108
+ filename: `${yyyy}-${mm}-${dd}-${HH}${MM}`,
109
+ date: `${yyyy}-${mm}-${dd}`,
110
+ };
111
+ }
112
+ export function buildInboxFile(input) {
113
+ const { filename, iso } = nowParts();
114
+ const slug = slugify(input.body.split('\n')[0] || input.type);
115
+ const id = `${filename}-${input.from}-${slug}`;
116
+ const fileName = `${id}.md`;
117
+ const fm = ['---'];
118
+ fm.push(`id: ${id}`);
119
+ fm.push(`type: ${input.type}`);
120
+ fm.push(`from: ${input.from}`);
121
+ fm.push(`to: ${input.to}`);
122
+ fm.push(`priority: ${input.priority ?? 'normal'}`);
123
+ fm.push(`created_at: ${iso}`);
124
+ if (input.re)
125
+ fm.push(`re: ${input.re}`);
126
+ if (input.thread)
127
+ fm.push(`thread: ${input.thread}`);
128
+ if (input.expiresAt)
129
+ fm.push(`expires_at: ${input.expiresAt}`);
130
+ fm.push('source: cli');
131
+ fm.push('---');
132
+ const content = `${fm.join('\n')}\n\n${input.body}\n`;
133
+ const summary = input.body.split('\n')[0].slice(0, 200);
134
+ return { filename: fileName, content, summary };
135
+ }
136
+ export function writeInboxFile(workspaceRoot, role, fileName, content) {
137
+ const inboxDir = join(workspaceRoot, role.inbox);
138
+ if (!existsSync(inboxDir)) {
139
+ mkdirSync(inboxDir, { recursive: true });
140
+ }
141
+ const fullPath = join(inboxDir, fileName);
142
+ writeFileSync(fullPath, content, 'utf-8');
143
+ return fullPath;
144
+ }
145
+ export function buildNotificationPayload(input, role, fileRef, summary) {
146
+ return {
147
+ to_role: input.to,
148
+ to_filler_type: role.filler.type,
149
+ notification: role.notification,
150
+ from_role: input.from,
151
+ type: input.type,
152
+ priority: input.priority ?? 'normal',
153
+ summary,
154
+ ref: fileRef,
155
+ source: 'cli',
156
+ };
157
+ }
158
+ // ── Optional git add + commit ───────────────────────────────────
159
+ export function gitCommitInboxFile(workspaceRoot, relativePath, message) {
160
+ try {
161
+ execSync(`git add "${relativePath}"`, { cwd: workspaceRoot, stdio: 'pipe' });
162
+ execSync(`git commit -m "${message.replace(/"/g, '\\"')}"`, { cwd: workspaceRoot, stdio: 'pipe' });
163
+ }
164
+ catch (err) {
165
+ // Bubble up so caller can decide whether to surface or ignore
166
+ throw new Error(`git commit failed in ${workspaceRoot}: ${err instanceof Error ? err.message : String(err)}`);
167
+ }
168
+ }
169
+ //# sourceMappingURL=dispatch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch.js","sourceRoot":"","sources":["../src/dispatch.ts"],"names":[],"mappings":"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"}
package/dist/index.d.ts CHANGED
@@ -1,4 +1,45 @@
1
1
  #!/usr/bin/env node
2
2
  import { Command } from 'commander';
3
3
  declare const program: Command;
4
+ /**
5
+ * F22 — Normalize a Connection row from the API into a shape the CLI
6
+ * commands can render. The backend serializes the new
7
+ * `serializeVcsConnection` shape with snake_case fields (provider,
8
+ * account_login, supports_contents_api, last_health_check_at, ...) per
9
+ * backend handoff notes. F16 callers historically read camelCase
10
+ * (githubAccountLogin, lastHealthCheckAt, installedAt, ...). During the
11
+ * dual-write rollout BOTH shapes can appear; this helper picks the
12
+ * first defined value.
13
+ *
14
+ * Exported so unit tests can assert the mapping without spinning up
15
+ * the full CLI.
16
+ */
17
+ export interface NormalizedConnection {
18
+ id: string;
19
+ provider: 'github' | 'bitbucket' | string | null;
20
+ name: string | null;
21
+ accountLogin: string | null;
22
+ accountType: string | null;
23
+ status: string;
24
+ installedAt: string | null;
25
+ lastHealthCheckAt: string | null;
26
+ lastUsedAt: string | null;
27
+ supportsContentsApi: boolean | null;
28
+ projectCount: number | null;
29
+ }
30
+ export declare function normalizeConnection(raw: any): NormalizedConnection;
31
+ /**
32
+ * F22 — Human-friendly provider label used in CLI columns and lines.
33
+ */
34
+ export declare function formatProviderLabel(provider: string | null | undefined): string;
35
+ /**
36
+ * F22 §8.3 item 3 — interactive provider picker. Used by
37
+ * `fazemos connections install` when no `--provider` flag is supplied
38
+ * so the user explicitly chooses GitHub or BitBucket before the URL
39
+ * mint hits the API. Mirrors the web `ProviderPickerModal` choice
40
+ * without the modal chrome.
41
+ *
42
+ * Returns 'github' or 'bitbucket'. Repeats on invalid input.
43
+ */
44
+ export declare function promptProviderChoice(): Promise<'github' | 'bitbucket'>;
4
45
  export { program };