@agentuity/cli 0.0.110 → 0.0.112
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/bin/cli.ts +4 -0
- package/dist/agents-docs.d.ts +5 -4
- package/dist/agents-docs.d.ts.map +1 -1
- package/dist/agents-docs.js +28 -8
- package/dist/agents-docs.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +19 -4
- package/dist/cli.js.map +1 -1
- package/dist/cmd/auth/apikey.d.ts +2 -0
- package/dist/cmd/auth/apikey.d.ts.map +1 -0
- package/dist/cmd/auth/apikey.js +31 -0
- package/dist/cmd/auth/apikey.js.map +1 -0
- package/dist/cmd/auth/index.d.ts.map +1 -1
- package/dist/cmd/auth/index.js +9 -1
- package/dist/cmd/auth/index.js.map +1 -1
- package/dist/cmd/build/ast.d.ts.map +1 -1
- package/dist/cmd/build/ast.js +103 -2
- package/dist/cmd/build/ast.js.map +1 -1
- package/dist/cmd/build/entry-generator.d.ts +2 -1
- package/dist/cmd/build/entry-generator.d.ts.map +1 -1
- package/dist/cmd/build/entry-generator.js +152 -9
- package/dist/cmd/build/entry-generator.js.map +1 -1
- package/dist/cmd/build/vite/agent-discovery.d.ts +1 -1
- package/dist/cmd/build/vite/agent-discovery.d.ts.map +1 -1
- package/dist/cmd/build/vite/agent-discovery.js +7 -6
- package/dist/cmd/build/vite/agent-discovery.js.map +1 -1
- package/dist/cmd/build/vite/index.d.ts.map +1 -1
- package/dist/cmd/build/vite/index.js +3 -2
- package/dist/cmd/build/vite/index.js.map +1 -1
- package/dist/cmd/build/vite/metadata-generator.js +1 -1
- package/dist/cmd/build/vite/metadata-generator.js.map +1 -1
- package/dist/cmd/build/vite/registry-generator.d.ts +1 -1
- package/dist/cmd/build/vite/registry-generator.d.ts.map +1 -1
- package/dist/cmd/build/vite/registry-generator.js +115 -23
- package/dist/cmd/build/vite/registry-generator.js.map +1 -1
- package/dist/cmd/build/vite/route-discovery.d.ts +6 -0
- package/dist/cmd/build/vite/route-discovery.d.ts.map +1 -1
- package/dist/cmd/build/vite/route-discovery.js +19 -0
- package/dist/cmd/build/vite/route-discovery.js.map +1 -1
- package/dist/cmd/build/vite/vite-builder.d.ts.map +1 -1
- package/dist/cmd/build/vite/vite-builder.js +3 -2
- package/dist/cmd/build/vite/vite-builder.js.map +1 -1
- package/dist/cmd/cloud/deploy-fork.d.ts +32 -0
- package/dist/cmd/cloud/deploy-fork.d.ts.map +1 -0
- package/dist/cmd/cloud/deploy-fork.js +258 -0
- package/dist/cmd/cloud/deploy-fork.js.map +1 -0
- package/dist/cmd/cloud/deploy.d.ts.map +1 -1
- package/dist/cmd/cloud/deploy.js +125 -4
- package/dist/cmd/cloud/deploy.js.map +1 -1
- package/dist/cmd/cloud/sandbox/create.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/create.js +18 -0
- package/dist/cmd/cloud/sandbox/create.js.map +1 -1
- package/dist/cmd/cloud/sandbox/delete.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/delete.js +2 -6
- package/dist/cmd/cloud/sandbox/delete.js.map +1 -1
- package/dist/cmd/cloud/sandbox/download.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/download.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/download.js +89 -0
- package/dist/cmd/cloud/sandbox/download.js.map +1 -0
- package/dist/cmd/cloud/sandbox/env.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/env.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/env.js +90 -0
- package/dist/cmd/cloud/sandbox/env.js.map +1 -0
- package/dist/cmd/cloud/sandbox/get.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/get.js +24 -0
- package/dist/cmd/cloud/sandbox/get.js.map +1 -1
- package/dist/cmd/cloud/sandbox/index.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/index.js +14 -0
- package/dist/cmd/cloud/sandbox/index.js.map +1 -1
- package/dist/cmd/cloud/sandbox/ls.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/ls.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/ls.js +119 -0
- package/dist/cmd/cloud/sandbox/ls.js.map +1 -0
- package/dist/cmd/cloud/sandbox/mkdir.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/mkdir.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/mkdir.js +59 -0
- package/dist/cmd/cloud/sandbox/mkdir.js.map +1 -0
- package/dist/cmd/cloud/sandbox/rm.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/rm.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/rm.js +45 -0
- package/dist/cmd/cloud/sandbox/rm.js.map +1 -0
- package/dist/cmd/cloud/sandbox/rmdir.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/rmdir.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/rmdir.js +59 -0
- package/dist/cmd/cloud/sandbox/rmdir.js.map +1 -0
- package/dist/cmd/cloud/sandbox/snapshot/create.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/create.js +0 -2
- package/dist/cmd/cloud/sandbox/snapshot/create.js.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/get.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/get.js +0 -2
- package/dist/cmd/cloud/sandbox/snapshot/get.js.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/list.d.ts.map +1 -1
- package/dist/cmd/cloud/sandbox/snapshot/list.js +0 -3
- package/dist/cmd/cloud/sandbox/snapshot/list.js.map +1 -1
- package/dist/cmd/cloud/sandbox/upload.d.ts +3 -0
- package/dist/cmd/cloud/sandbox/upload.d.ts.map +1 -0
- package/dist/cmd/cloud/sandbox/upload.js +77 -0
- package/dist/cmd/cloud/sandbox/upload.js.map +1 -0
- package/dist/cmd/cloud/ssh.d.ts.map +1 -1
- package/dist/cmd/cloud/ssh.js +9 -3
- package/dist/cmd/cloud/ssh.js.map +1 -1
- package/dist/cmd/dev/index.d.ts.map +1 -1
- package/dist/cmd/dev/index.js +34 -19
- package/dist/cmd/dev/index.js.map +1 -1
- package/dist/cmd/dev/sync.d.ts.map +1 -1
- package/dist/cmd/dev/sync.js +8 -14
- package/dist/cmd/dev/sync.js.map +1 -1
- package/dist/cmd/git/account/add.d.ts +17 -0
- package/dist/cmd/git/account/add.d.ts.map +1 -0
- package/dist/cmd/git/account/add.js +244 -0
- package/dist/cmd/git/account/add.js.map +1 -0
- package/dist/cmd/git/account/index.d.ts +3 -0
- package/dist/cmd/git/account/index.d.ts.map +1 -0
- package/dist/cmd/git/account/index.js +11 -0
- package/dist/cmd/git/account/index.js.map +1 -0
- package/dist/cmd/git/account/list.d.ts +2 -0
- package/dist/cmd/git/account/list.d.ts.map +1 -0
- package/dist/cmd/git/account/list.js +111 -0
- package/dist/cmd/git/account/list.js.map +1 -0
- package/dist/cmd/git/account/remove.d.ts +2 -0
- package/dist/cmd/git/account/remove.d.ts.map +1 -0
- package/dist/cmd/git/account/remove.js +171 -0
- package/dist/cmd/git/account/remove.js.map +1 -0
- package/dist/cmd/git/index.d.ts +3 -0
- package/dist/cmd/git/index.d.ts.map +1 -0
- package/dist/cmd/git/index.js +19 -0
- package/dist/cmd/git/index.js.map +1 -0
- package/dist/cmd/git/link.d.ts +32 -0
- package/dist/cmd/git/link.d.ts.map +1 -0
- package/dist/cmd/git/link.js +357 -0
- package/dist/cmd/git/link.js.map +1 -0
- package/dist/cmd/git/list.d.ts +2 -0
- package/dist/cmd/git/list.d.ts.map +1 -0
- package/dist/cmd/git/list.js +137 -0
- package/dist/cmd/git/list.js.map +1 -0
- package/dist/cmd/git/status.d.ts +2 -0
- package/dist/cmd/git/status.d.ts.map +1 -0
- package/dist/cmd/git/status.js +119 -0
- package/dist/cmd/git/status.js.map +1 -0
- package/dist/cmd/git/unlink.d.ts +2 -0
- package/dist/cmd/git/unlink.d.ts.map +1 -0
- package/dist/cmd/git/unlink.js +98 -0
- package/dist/cmd/git/unlink.js.map +1 -0
- package/dist/cmd/index.d.ts.map +1 -1
- package/dist/cmd/index.js +2 -0
- package/dist/cmd/index.js.map +1 -1
- package/dist/cmd/integration/api.d.ts +61 -0
- package/dist/cmd/integration/api.d.ts.map +1 -0
- package/dist/cmd/integration/api.js +176 -0
- package/dist/cmd/integration/api.js.map +1 -0
- package/dist/cmd/integration/github/connect.d.ts +2 -0
- package/dist/cmd/integration/github/connect.d.ts.map +1 -0
- package/dist/cmd/integration/github/connect.js +197 -0
- package/dist/cmd/integration/github/connect.js.map +1 -0
- package/dist/cmd/integration/github/disconnect.d.ts +2 -0
- package/dist/cmd/integration/github/disconnect.d.ts.map +1 -0
- package/dist/cmd/integration/github/disconnect.js +121 -0
- package/dist/cmd/integration/github/disconnect.js.map +1 -0
- package/dist/cmd/integration/github/index.d.ts +2 -0
- package/dist/cmd/integration/github/index.d.ts.map +1 -0
- package/dist/cmd/integration/github/index.js +21 -0
- package/dist/cmd/integration/github/index.js.map +1 -0
- package/dist/cmd/integration/index.d.ts +2 -0
- package/dist/cmd/integration/index.d.ts.map +1 -0
- package/dist/cmd/integration/index.js +16 -0
- package/dist/cmd/integration/index.js.map +1 -0
- package/dist/config.d.ts +2 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +25 -1
- package/dist/config.js.map +1 -1
- package/dist/errors.d.ts +2 -1
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +5 -0
- package/dist/errors.js.map +1 -1
- package/dist/log-collector.d.ts +30 -0
- package/dist/log-collector.d.ts.map +1 -0
- package/dist/log-collector.js +74 -0
- package/dist/log-collector.js.map +1 -0
- package/dist/output.d.ts.map +1 -1
- package/dist/output.js +2 -1
- package/dist/output.js.map +1 -1
- package/dist/steps.d.ts.map +1 -1
- package/dist/steps.js +48 -3
- package/dist/steps.js.map +1 -1
- package/dist/tui/box.d.ts.map +1 -1
- package/dist/tui/box.js +1 -6
- package/dist/tui/box.js.map +1 -1
- package/dist/tui/symbols.d.ts.map +1 -1
- package/dist/tui/symbols.js +4 -0
- package/dist/tui/symbols.js.map +1 -1
- package/dist/tui.d.ts +21 -12
- package/dist/tui.d.ts.map +1 -1
- package/dist/tui.js +74 -25
- package/dist/tui.js.map +1 -1
- package/dist/types.d.ts +73 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js +4 -0
- package/dist/types.js.map +1 -1
- package/dist/typescript-errors.d.ts.map +1 -1
- package/dist/typescript-errors.js +2 -2
- package/dist/typescript-errors.js.map +1 -1
- package/package.json +6 -6
- package/src/agents-docs.ts +42 -8
- package/src/cli.ts +20 -4
- package/src/cmd/auth/apikey.ts +36 -0
- package/src/cmd/auth/index.ts +9 -1
- package/src/cmd/build/ast.ts +120 -2
- package/src/cmd/build/entry-generator.ts +157 -10
- package/src/cmd/build/vite/agent-discovery.ts +8 -5
- package/src/cmd/build/vite/index.ts +3 -2
- package/src/cmd/build/vite/metadata-generator.ts +1 -1
- package/src/cmd/build/vite/registry-generator.ts +125 -24
- package/src/cmd/build/vite/route-discovery.ts +20 -0
- package/src/cmd/build/vite/vite-builder.ts +3 -2
- package/src/cmd/cloud/deploy-fork.ts +296 -0
- package/src/cmd/cloud/deploy.ts +148 -4
- package/src/cmd/cloud/sandbox/create.ts +22 -0
- package/src/cmd/cloud/sandbox/delete.ts +2 -6
- package/src/cmd/cloud/sandbox/download.ts +96 -0
- package/src/cmd/cloud/sandbox/env.ts +104 -0
- package/src/cmd/cloud/sandbox/get.ts +22 -0
- package/src/cmd/cloud/sandbox/index.ts +14 -0
- package/src/cmd/cloud/sandbox/ls.ts +126 -0
- package/src/cmd/cloud/sandbox/mkdir.ts +65 -0
- package/src/cmd/cloud/sandbox/rm.ts +51 -0
- package/src/cmd/cloud/sandbox/rmdir.ts +65 -0
- package/src/cmd/cloud/sandbox/snapshot/create.ts +0 -2
- package/src/cmd/cloud/sandbox/snapshot/get.ts +0 -2
- package/src/cmd/cloud/sandbox/snapshot/list.ts +0 -3
- package/src/cmd/cloud/sandbox/upload.ts +83 -0
- package/src/cmd/cloud/ssh.ts +13 -3
- package/src/cmd/dev/index.ts +49 -31
- package/src/cmd/dev/sync.ts +26 -30
- package/src/cmd/git/account/add.ts +317 -0
- package/src/cmd/git/account/index.ts +12 -0
- package/src/cmd/git/account/list.ts +139 -0
- package/src/cmd/git/account/remove.ts +212 -0
- package/src/cmd/git/index.ts +20 -0
- package/src/cmd/git/link.ts +468 -0
- package/src/cmd/git/list.ts +161 -0
- package/src/cmd/git/status.ts +144 -0
- package/src/cmd/git/unlink.ts +117 -0
- package/src/cmd/index.ts +2 -0
- package/src/cmd/integration/api.ts +379 -0
- package/src/cmd/integration/github/connect.ts +242 -0
- package/src/cmd/integration/github/disconnect.ts +149 -0
- package/src/cmd/integration/github/index.ts +21 -0
- package/src/cmd/integration/index.ts +16 -0
- package/src/config.ts +35 -1
- package/src/errors.ts +7 -0
- package/src/log-collector.ts +77 -0
- package/src/output.ts +2 -1
- package/src/steps.ts +52 -4
- package/src/tui/box.ts +1 -7
- package/src/tui/symbols.ts +5 -0
- package/src/tui.ts +77 -25
- package/src/types.ts +89 -0
- package/src/typescript-errors.ts +2 -1
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { createSubcommand } from '../../../types';
|
|
2
|
+
import * as tui from '../../../tui';
|
|
3
|
+
import { getCommand } from '../../../command-prefix';
|
|
4
|
+
import { getAPIBaseURL } from '../../../api';
|
|
5
|
+
import { ErrorCode } from '../../../errors';
|
|
6
|
+
import { listOrganizations } from '@agentuity/server';
|
|
7
|
+
import enquirer from 'enquirer';
|
|
8
|
+
import {
|
|
9
|
+
startGithubIntegration,
|
|
10
|
+
pollForGithubIntegration,
|
|
11
|
+
getGithubIntegrationStatus,
|
|
12
|
+
getExistingGithubIntegrations,
|
|
13
|
+
copyGithubIntegration,
|
|
14
|
+
} from '../api';
|
|
15
|
+
|
|
16
|
+
export const connectSubcommand = createSubcommand({
|
|
17
|
+
name: 'connect',
|
|
18
|
+
description: 'Connect your GitHub account to enable automatic deployments',
|
|
19
|
+
tags: ['mutating', 'creates-resource', 'slow', 'api-intensive'],
|
|
20
|
+
idempotent: false,
|
|
21
|
+
requires: { auth: true, apiClient: true },
|
|
22
|
+
examples: [
|
|
23
|
+
{
|
|
24
|
+
command: getCommand('integration github connect'),
|
|
25
|
+
description: 'Connect GitHub to your organization',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
|
|
29
|
+
async handler(ctx) {
|
|
30
|
+
const { logger, apiClient } = ctx;
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
// Fetch organizations
|
|
34
|
+
const orgs = await tui.spinner({
|
|
35
|
+
message: 'Fetching organizations...',
|
|
36
|
+
clearOnSuccess: true,
|
|
37
|
+
callback: () => listOrganizations(apiClient),
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
if (orgs.length === 0) {
|
|
41
|
+
tui.fatal('No organizations found for your account');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Check GitHub status for each org
|
|
45
|
+
const orgStatuses = await tui.spinner({
|
|
46
|
+
message: 'Checking GitHub integration status...',
|
|
47
|
+
clearOnSuccess: true,
|
|
48
|
+
callback: async () => {
|
|
49
|
+
const statuses = await Promise.all(
|
|
50
|
+
orgs.map(async (org) => {
|
|
51
|
+
const status = await getGithubIntegrationStatus(apiClient, org.id);
|
|
52
|
+
return {
|
|
53
|
+
...org,
|
|
54
|
+
connected: status.connected,
|
|
55
|
+
integrations: status.integrations,
|
|
56
|
+
};
|
|
57
|
+
})
|
|
58
|
+
);
|
|
59
|
+
return statuses;
|
|
60
|
+
},
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Sort orgs alphabetically
|
|
64
|
+
const sortedOrgs = [...orgStatuses].sort((a, b) => a.name.localeCompare(b.name));
|
|
65
|
+
|
|
66
|
+
// Build choices showing integration count
|
|
67
|
+
const choices = sortedOrgs.map((org) => {
|
|
68
|
+
const count = org.integrations.length;
|
|
69
|
+
const suffix =
|
|
70
|
+
count > 0 ? tui.muted(` (${count} GitHub account${count > 1 ? 's' : ''})`) : '';
|
|
71
|
+
return {
|
|
72
|
+
name: org.name,
|
|
73
|
+
message: `${org.name}${suffix}`,
|
|
74
|
+
value: org.id,
|
|
75
|
+
};
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
// Show picker
|
|
79
|
+
const response = await enquirer.prompt<{ orgName: string }>({
|
|
80
|
+
type: 'select',
|
|
81
|
+
name: 'orgName',
|
|
82
|
+
message: 'Select an organization to connect',
|
|
83
|
+
choices,
|
|
84
|
+
result(name: string) {
|
|
85
|
+
// @ts-expect-error - this.map exists at runtime
|
|
86
|
+
return this.map(name)[name];
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
const orgId = response.orgName;
|
|
91
|
+
const selectedOrg = sortedOrgs.find((o) => o.id === orgId);
|
|
92
|
+
const orgDisplay = selectedOrg ? selectedOrg.name : orgId;
|
|
93
|
+
const initialCount = selectedOrg?.integrations.length ?? 0;
|
|
94
|
+
|
|
95
|
+
// Check if user has existing GitHub integrations in other orgs
|
|
96
|
+
const existingIntegrations = await tui.spinner({
|
|
97
|
+
message: 'Checking for existing GitHub connections...',
|
|
98
|
+
clearOnSuccess: true,
|
|
99
|
+
callback: () => getExistingGithubIntegrations(apiClient, orgId),
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Filter out integrations already connected to target org
|
|
103
|
+
const alreadyConnectedNames = new Set(
|
|
104
|
+
selectedOrg?.integrations.map((i) => i.githubAccountName) ?? []
|
|
105
|
+
);
|
|
106
|
+
const availableIntegrations = existingIntegrations.filter(
|
|
107
|
+
(i) => !alreadyConnectedNames.has(i.githubAccountName)
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
if (availableIntegrations.length > 0) {
|
|
111
|
+
tui.newline();
|
|
112
|
+
|
|
113
|
+
// Build checkbox choices
|
|
114
|
+
const integrationChoices = availableIntegrations.map((i) => ({
|
|
115
|
+
name: i.id,
|
|
116
|
+
message: `${i.githubAccountName} ${tui.muted(`(from ${i.orgName})`)}`,
|
|
117
|
+
}));
|
|
118
|
+
|
|
119
|
+
console.log(tui.muted('Press enter with none selected to connect a new account'));
|
|
120
|
+
tui.newline();
|
|
121
|
+
|
|
122
|
+
const selectResponse = await enquirer.prompt<{ integrationIds: string[] }>({
|
|
123
|
+
type: 'multiselect',
|
|
124
|
+
name: 'integrationIds',
|
|
125
|
+
message: 'Select GitHub accounts to connect',
|
|
126
|
+
choices: integrationChoices,
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
if (selectResponse.integrationIds.length > 0) {
|
|
130
|
+
const selectedIntegrations = availableIntegrations.filter((i) =>
|
|
131
|
+
selectResponse.integrationIds.includes(i.id)
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
const accountNames = selectedIntegrations.map((i) => i.githubAccountName).join(', ');
|
|
135
|
+
|
|
136
|
+
// Confirm
|
|
137
|
+
const confirmResponse = await enquirer.prompt<{ confirm: boolean }>({
|
|
138
|
+
type: 'confirm',
|
|
139
|
+
name: 'confirm',
|
|
140
|
+
message: `Connect ${tui.bold(accountNames)} to ${tui.bold(orgDisplay)}?`,
|
|
141
|
+
initial: true,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
if (confirmResponse.confirm) {
|
|
145
|
+
await tui.spinner({
|
|
146
|
+
message: `Copying ${selectedIntegrations.length} GitHub connection${selectedIntegrations.length > 1 ? 's' : ''}...`,
|
|
147
|
+
clearOnSuccess: true,
|
|
148
|
+
callback: async () => {
|
|
149
|
+
for (const integration of selectedIntegrations) {
|
|
150
|
+
await copyGithubIntegration(apiClient, integration.orgId, orgId);
|
|
151
|
+
}
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
tui.newline();
|
|
156
|
+
tui.success(`GitHub connected to ${tui.bold(orgDisplay)}`);
|
|
157
|
+
tui.newline();
|
|
158
|
+
console.log(
|
|
159
|
+
'You can now link repositories to your projects for automatic deployments.'
|
|
160
|
+
);
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
const startResult = await tui.spinner({
|
|
167
|
+
message: 'Getting GitHub authorization URL...',
|
|
168
|
+
clearOnSuccess: true,
|
|
169
|
+
callback: () => startGithubIntegration(apiClient, orgId),
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
if (!startResult) {
|
|
173
|
+
tui.error('Failed to get GitHub authorization URL');
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
const { shortId } = startResult;
|
|
178
|
+
const apiBaseUrl = getAPIBaseURL(ctx.config);
|
|
179
|
+
const url = `${apiBaseUrl}/github/connect/${shortId}`;
|
|
180
|
+
|
|
181
|
+
const copied = await tui.copyToClipboard(url);
|
|
182
|
+
|
|
183
|
+
tui.newline();
|
|
184
|
+
if (copied) {
|
|
185
|
+
console.log('GitHub authorization URL copied to clipboard! Open it in your browser:');
|
|
186
|
+
} else {
|
|
187
|
+
console.log('Open this URL in your browser to authorize GitHub access:');
|
|
188
|
+
}
|
|
189
|
+
tui.newline();
|
|
190
|
+
console.log(` ${tui.link(url)}`);
|
|
191
|
+
tui.newline();
|
|
192
|
+
console.log(tui.muted('Press Enter to open in your browser, or Ctrl+C to cancel'));
|
|
193
|
+
tui.newline();
|
|
194
|
+
|
|
195
|
+
const result = await tui.spinner({
|
|
196
|
+
type: 'countdown',
|
|
197
|
+
message: 'Waiting for GitHub authorization',
|
|
198
|
+
timeoutMs: 600000, // 10 minutes
|
|
199
|
+
clearOnSuccess: true,
|
|
200
|
+
onEnterPress: () => {
|
|
201
|
+
const platform = process.platform;
|
|
202
|
+
if (platform === 'win32') {
|
|
203
|
+
Bun.spawn(['cmd', '/c', 'start', '', url], {
|
|
204
|
+
stdout: 'ignore',
|
|
205
|
+
stderr: 'ignore',
|
|
206
|
+
});
|
|
207
|
+
} else {
|
|
208
|
+
const command = platform === 'darwin' ? 'open' : 'xdg-open';
|
|
209
|
+
Bun.spawn([command, url], { stdout: 'ignore', stderr: 'ignore' });
|
|
210
|
+
}
|
|
211
|
+
},
|
|
212
|
+
callback: async () => {
|
|
213
|
+
return await pollForGithubIntegration(apiClient, orgId, initialCount);
|
|
214
|
+
},
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
tui.newline();
|
|
218
|
+
if (result.connected) {
|
|
219
|
+
tui.success(`GitHub connected to ${tui.bold(orgDisplay)}`);
|
|
220
|
+
tui.newline();
|
|
221
|
+
console.log(
|
|
222
|
+
'You can now link repositories to your projects for automatic deployments.'
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
} catch (error) {
|
|
226
|
+
// Handle user cancellation (Ctrl+C) - enquirer throws empty string or Error with empty message
|
|
227
|
+
const isCancel =
|
|
228
|
+
error === '' ||
|
|
229
|
+
(error instanceof Error &&
|
|
230
|
+
(error.message === '' || error.message === 'User cancelled'));
|
|
231
|
+
|
|
232
|
+
if (isCancel) {
|
|
233
|
+
tui.newline();
|
|
234
|
+
tui.info('Cancelled');
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
logger.trace(error);
|
|
239
|
+
logger.fatal('GitHub integration failed: %s', error, ErrorCode.INTEGRATION_FAILED);
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
});
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { createSubcommand } from '../../../types';
|
|
2
|
+
import * as tui from '../../../tui';
|
|
3
|
+
import { getCommand } from '../../../command-prefix';
|
|
4
|
+
import { ErrorCode } from '../../../errors';
|
|
5
|
+
import { listOrganizations } from '@agentuity/server';
|
|
6
|
+
import enquirer from 'enquirer';
|
|
7
|
+
import {
|
|
8
|
+
getGithubIntegrationStatus,
|
|
9
|
+
disconnectGithubIntegration,
|
|
10
|
+
type GithubIntegration,
|
|
11
|
+
} from '../api';
|
|
12
|
+
|
|
13
|
+
export const disconnectSubcommand = createSubcommand({
|
|
14
|
+
name: 'disconnect',
|
|
15
|
+
description: 'Disconnect a GitHub account from your organization',
|
|
16
|
+
tags: ['mutating', 'destructive', 'slow'],
|
|
17
|
+
idempotent: false,
|
|
18
|
+
requires: { auth: true, apiClient: true },
|
|
19
|
+
examples: [
|
|
20
|
+
{
|
|
21
|
+
command: getCommand('integration github disconnect'),
|
|
22
|
+
description: 'Disconnect a GitHub account from your organization',
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
|
|
26
|
+
async handler(ctx) {
|
|
27
|
+
const { logger, apiClient } = ctx;
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// Fetch organizations
|
|
31
|
+
const orgs = await tui.spinner({
|
|
32
|
+
message: 'Fetching organizations...',
|
|
33
|
+
clearOnSuccess: true,
|
|
34
|
+
callback: () => listOrganizations(apiClient),
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
if (orgs.length === 0) {
|
|
38
|
+
tui.fatal('No organizations found for your account');
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// Check GitHub status for each org
|
|
42
|
+
const orgStatuses = await tui.spinner({
|
|
43
|
+
message: 'Checking GitHub integration status...',
|
|
44
|
+
clearOnSuccess: true,
|
|
45
|
+
callback: async () => {
|
|
46
|
+
const statuses = await Promise.all(
|
|
47
|
+
orgs.map(async (org) => {
|
|
48
|
+
const status = await getGithubIntegrationStatus(apiClient, org.id);
|
|
49
|
+
return {
|
|
50
|
+
...org,
|
|
51
|
+
connected: status.connected,
|
|
52
|
+
integrations: status.integrations,
|
|
53
|
+
};
|
|
54
|
+
})
|
|
55
|
+
);
|
|
56
|
+
return statuses;
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Flatten all integrations across orgs
|
|
61
|
+
const allIntegrations: Array<{
|
|
62
|
+
orgId: string;
|
|
63
|
+
orgName: string;
|
|
64
|
+
integration: GithubIntegration;
|
|
65
|
+
}> = [];
|
|
66
|
+
|
|
67
|
+
for (const org of orgStatuses) {
|
|
68
|
+
for (const integration of org.integrations) {
|
|
69
|
+
allIntegrations.push({
|
|
70
|
+
orgId: org.id,
|
|
71
|
+
orgName: org.name,
|
|
72
|
+
integration,
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (allIntegrations.length === 0) {
|
|
78
|
+
tui.newline();
|
|
79
|
+
tui.info('No GitHub accounts are connected.');
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Build choices showing GitHub account and org
|
|
84
|
+
const choices = allIntegrations.map((item) => ({
|
|
85
|
+
name: `${tui.bold(item.integration.githubAccountName)} ${tui.muted(`(${item.integration.githubAccountType})`)} → ${tui.bold(item.orgName)}`,
|
|
86
|
+
value: `${item.orgId}:${item.integration.id}`,
|
|
87
|
+
}));
|
|
88
|
+
|
|
89
|
+
// Show picker
|
|
90
|
+
const response = await enquirer.prompt<{ selection: string }>({
|
|
91
|
+
type: 'select',
|
|
92
|
+
name: 'selection',
|
|
93
|
+
message: 'Select a GitHub account to disconnect',
|
|
94
|
+
choices,
|
|
95
|
+
result(name: string) {
|
|
96
|
+
// Return the value (IDs) instead of the display name
|
|
97
|
+
const choice = choices.find((c) => c.name === name);
|
|
98
|
+
return choice?.value ?? name;
|
|
99
|
+
},
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
const colonIdx = response.selection.indexOf(':');
|
|
103
|
+
if (colonIdx === -1) {
|
|
104
|
+
logger.fatal('Invalid selection format');
|
|
105
|
+
}
|
|
106
|
+
const orgId = response.selection.slice(0, colonIdx);
|
|
107
|
+
const integrationId = response.selection.slice(colonIdx + 1);
|
|
108
|
+
const selected = allIntegrations.find(
|
|
109
|
+
(i) => i.orgId === orgId && i.integration.id === integrationId
|
|
110
|
+
);
|
|
111
|
+
const displayName = selected
|
|
112
|
+
? `${tui.bold(selected.integration.githubAccountName)} from ${tui.bold(selected.orgName)}`
|
|
113
|
+
: response.selection;
|
|
114
|
+
|
|
115
|
+
// Confirm
|
|
116
|
+
const confirmed = await tui.confirm(`Are you sure you want to disconnect ${displayName}?`);
|
|
117
|
+
|
|
118
|
+
if (!confirmed) {
|
|
119
|
+
tui.newline();
|
|
120
|
+
tui.info('Cancelled');
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
await tui.spinner({
|
|
125
|
+
message: 'Disconnecting GitHub...',
|
|
126
|
+
clearOnSuccess: true,
|
|
127
|
+
callback: () => disconnectGithubIntegration(apiClient, orgId, integrationId),
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
tui.newline();
|
|
131
|
+
tui.success(`Disconnected ${displayName}`);
|
|
132
|
+
} catch (error) {
|
|
133
|
+
// Handle user cancellation (Ctrl+C)
|
|
134
|
+
const isCancel =
|
|
135
|
+
error === '' ||
|
|
136
|
+
(error instanceof Error &&
|
|
137
|
+
(error.message === '' || error.message === 'User cancelled'));
|
|
138
|
+
|
|
139
|
+
if (isCancel) {
|
|
140
|
+
tui.newline();
|
|
141
|
+
tui.info('Cancelled');
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
logger.trace(error);
|
|
146
|
+
logger.fatal('GitHub disconnect failed: %s', error, ErrorCode.INTEGRATION_FAILED);
|
|
147
|
+
}
|
|
148
|
+
},
|
|
149
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { createCommand } from '../../../types';
|
|
2
|
+
import { getCommand } from '../../../command-prefix';
|
|
3
|
+
import { connectSubcommand } from './connect';
|
|
4
|
+
import { disconnectSubcommand } from './disconnect';
|
|
5
|
+
|
|
6
|
+
export const githubCommand = createCommand({
|
|
7
|
+
name: 'github',
|
|
8
|
+
description: 'GitHub integration commands',
|
|
9
|
+
tags: ['requires-auth'],
|
|
10
|
+
examples: [
|
|
11
|
+
{
|
|
12
|
+
command: getCommand('integration github connect'),
|
|
13
|
+
description: 'Connect GitHub to your organization',
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
command: getCommand('integration github disconnect'),
|
|
17
|
+
description: 'Disconnect GitHub from your organization',
|
|
18
|
+
},
|
|
19
|
+
],
|
|
20
|
+
subcommands: [connectSubcommand, disconnectSubcommand],
|
|
21
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createCommand } from '../../types';
|
|
2
|
+
import { getCommand } from '../../command-prefix';
|
|
3
|
+
import { githubCommand } from './github';
|
|
4
|
+
|
|
5
|
+
export const command = createCommand({
|
|
6
|
+
name: 'integration',
|
|
7
|
+
description: 'Manage integrations with external services',
|
|
8
|
+
tags: ['requires-auth'],
|
|
9
|
+
examples: [
|
|
10
|
+
{
|
|
11
|
+
command: getCommand('integration github connect'),
|
|
12
|
+
description: 'Connect GitHub to your organization',
|
|
13
|
+
},
|
|
14
|
+
],
|
|
15
|
+
subcommands: [githubCommand],
|
|
16
|
+
});
|
package/src/config.ts
CHANGED
|
@@ -556,7 +556,7 @@ export const InitialProjectConfigSchema = z.intersection(
|
|
|
556
556
|
type InitialProjectConfig = z.infer<typeof InitialProjectConfigSchema>;
|
|
557
557
|
|
|
558
558
|
export async function createProjectConfig(dir: string, config: InitialProjectConfig) {
|
|
559
|
-
const { sdkKey, ...sanitizedConfig } = config;
|
|
559
|
+
const { sdkKey, skipGitSetup: _skipGitSetup, ...sanitizedConfig } = config;
|
|
560
560
|
|
|
561
561
|
// generate the project config
|
|
562
562
|
const configPath = join(dir, 'agentuity.json');
|
|
@@ -596,6 +596,40 @@ export async function createProjectConfig(dir: string, config: InitialProjectCon
|
|
|
596
596
|
await Bun.write(join(vscodeDir, 'settings.json'), JSON.stringify(settings, null, 2));
|
|
597
597
|
}
|
|
598
598
|
|
|
599
|
+
export async function updateProjectConfig(
|
|
600
|
+
dir: string,
|
|
601
|
+
updates: Partial<z.infer<typeof ProjectSchema>>,
|
|
602
|
+
config?: Config | null
|
|
603
|
+
): Promise<void> {
|
|
604
|
+
let configPath = join(dir, 'agentuity.json');
|
|
605
|
+
|
|
606
|
+
if (config?.name) {
|
|
607
|
+
const profileConfigPath = join(dir, `agentuity.${config.name}.json`);
|
|
608
|
+
if (await Bun.file(profileConfigPath).exists()) {
|
|
609
|
+
configPath = profileConfigPath;
|
|
610
|
+
}
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
const file = Bun.file(configPath);
|
|
614
|
+
if (!(await file.exists())) {
|
|
615
|
+
throw new Error(`Project config not found at ${configPath}`);
|
|
616
|
+
}
|
|
617
|
+
|
|
618
|
+
const text = await file.text();
|
|
619
|
+
const existing = JSON5.parse(text);
|
|
620
|
+
const updated = { ...existing, ...updates };
|
|
621
|
+
|
|
622
|
+
const result = ProjectSchema.safeParse(updated);
|
|
623
|
+
if (!result.success) {
|
|
624
|
+
const issues = result.error.issues
|
|
625
|
+
.map((i) => `${i.path.join('.') || 'root'}: ${i.message}`)
|
|
626
|
+
.join(', ');
|
|
627
|
+
throw new Error(`Invalid project config after update: ${issues}`);
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
await Bun.write(configPath, JSON.stringify(updated, null, 2) + '\n');
|
|
631
|
+
}
|
|
632
|
+
|
|
599
633
|
const BuildMetadataNotFoundError = StructuredError('BuildMetadataNotFoundError');
|
|
600
634
|
|
|
601
635
|
export async function loadBuildMetadata(dir: string): Promise<BuildMetadata> {
|
package/src/errors.ts
CHANGED
|
@@ -72,6 +72,9 @@ export enum ErrorCode {
|
|
|
72
72
|
|
|
73
73
|
// Build failed error
|
|
74
74
|
BUILD_FAILED = 'BUILD_FAILED',
|
|
75
|
+
|
|
76
|
+
// Integration errors
|
|
77
|
+
INTEGRATION_FAILED = 'INTEGRATION_FAILED',
|
|
75
78
|
}
|
|
76
79
|
|
|
77
80
|
/**
|
|
@@ -129,6 +132,10 @@ export function getExitCode(errorCode: ErrorCode): ExitCode {
|
|
|
129
132
|
case ErrorCode.BUILD_FAILED:
|
|
130
133
|
return ExitCode.BUILD_FAILED;
|
|
131
134
|
|
|
135
|
+
// Integration errors
|
|
136
|
+
case ErrorCode.INTEGRATION_FAILED:
|
|
137
|
+
return ExitCode.NETWORK_ERROR;
|
|
138
|
+
|
|
132
139
|
// Resource conflicts and other errors
|
|
133
140
|
case ErrorCode.RESOURCE_ALREADY_EXISTS:
|
|
134
141
|
case ErrorCode.RESOURCE_CONFLICT:
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Log Collector for clean build logs
|
|
3
|
+
*
|
|
4
|
+
* Provides a mechanism to collect clean, non-animated log output
|
|
5
|
+
* for streaming to external services (like Pulse) while keeping
|
|
6
|
+
* animated TUI output for the user's terminal.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* - Set AGENTUITY_CLEAN_LOGS_FILE env var to a file path
|
|
10
|
+
* - TUI components call appendLog() for final state messages
|
|
11
|
+
* - Logs are written to the file for the parent process to read
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { appendFileSync, writeFileSync } from 'node:fs';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Get the clean logs file path from environment
|
|
18
|
+
*/
|
|
19
|
+
function getCleanLogsFile(): string | undefined {
|
|
20
|
+
return process.env.AGENTUITY_CLEAN_LOGS_FILE;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Disable log collection (called on write errors to prevent repeated failures)
|
|
25
|
+
*/
|
|
26
|
+
function disableLogCollection(error: unknown): void {
|
|
27
|
+
console.debug('Log collection disabled due to write error: %s', error);
|
|
28
|
+
delete process.env.AGENTUITY_CLEAN_LOGS_FILE;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Check if log collection is enabled (via environment variable)
|
|
33
|
+
*/
|
|
34
|
+
export function isLogCollectionEnabled(): boolean {
|
|
35
|
+
return !!getCleanLogsFile();
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Initialize the clean logs file (clears any existing content)
|
|
40
|
+
*/
|
|
41
|
+
export function initCleanLogsFile(filePath: string): void {
|
|
42
|
+
try {
|
|
43
|
+
writeFileSync(filePath, '');
|
|
44
|
+
process.env.AGENTUITY_CLEAN_LOGS_FILE = filePath;
|
|
45
|
+
} catch (err) {
|
|
46
|
+
console.debug('Failed to initialize clean logs file: %s', err);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Append a clean log line (no ANSI codes, no animation)
|
|
52
|
+
* Only appends if collection is enabled
|
|
53
|
+
*/
|
|
54
|
+
export function appendLog(message: string): void {
|
|
55
|
+
const file = getCleanLogsFile();
|
|
56
|
+
if (file) {
|
|
57
|
+
try {
|
|
58
|
+
appendFileSync(file, message + '\n');
|
|
59
|
+
} catch (err) {
|
|
60
|
+
disableLogCollection(err);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* Append multiple log lines
|
|
67
|
+
*/
|
|
68
|
+
export function appendLogs(messages: string[]): void {
|
|
69
|
+
const file = getCleanLogsFile();
|
|
70
|
+
if (file) {
|
|
71
|
+
try {
|
|
72
|
+
appendFileSync(file, messages.join('\n') + '\n');
|
|
73
|
+
} catch (err) {
|
|
74
|
+
disableLogCollection(err);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
package/src/output.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import type { GlobalOptions } from './types';
|
|
2
|
+
import { isTTYLike } from './tui';
|
|
2
3
|
|
|
3
4
|
/**
|
|
4
5
|
* Output formatting utilities for agent-friendly CLI
|
|
@@ -59,7 +60,7 @@ export function shouldDisableColors(options: GlobalOptions): boolean {
|
|
|
59
60
|
return false;
|
|
60
61
|
}
|
|
61
62
|
// auto mode - disable in JSON/quiet mode or non-TTY
|
|
62
|
-
return options.json === true || options.quiet === true || !
|
|
63
|
+
return options.json === true || options.quiet === true || !isTTYLike();
|
|
63
64
|
}
|
|
64
65
|
|
|
65
66
|
/**
|
package/src/steps.ts
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
import type { ColorScheme } from './terminal';
|
|
9
9
|
import type { LogLevel } from './types';
|
|
10
10
|
import { ValidationInputError, ValidationOutputError, type IssuesType } from '@agentuity/server';
|
|
11
|
-
import { clearLastLines } from './tui';
|
|
11
|
+
import { clearLastLines, isTTYLike } from './tui';
|
|
12
|
+
import { appendLog, isLogCollectionEnabled } from './log-collector';
|
|
12
13
|
|
|
13
14
|
// Spinner frames
|
|
14
15
|
const FRAMES = ['◐', '◓', '◑', '◒'];
|
|
@@ -129,6 +130,38 @@ function renderStepLine(step: StepState, spinner?: string): string {
|
|
|
129
130
|
}
|
|
130
131
|
}
|
|
131
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Generate a clean log line for a completed step (no ANSI, no animation)
|
|
135
|
+
*/
|
|
136
|
+
function getCleanStepLog(step: StepState): string {
|
|
137
|
+
if (step.status === 'success') {
|
|
138
|
+
return `${ICONS.success} ${step.label}`;
|
|
139
|
+
} else if (step.status === 'skipped') {
|
|
140
|
+
const reason = step.skipReason ? ` (${step.skipReason})` : '';
|
|
141
|
+
return `${ICONS.skipped} ${step.label}${reason}`;
|
|
142
|
+
} else if (step.status === 'error') {
|
|
143
|
+
const error = step.errorMessage ? `: ${step.errorMessage}` : '';
|
|
144
|
+
return `${ICONS.error} ${step.label}${error}`;
|
|
145
|
+
}
|
|
146
|
+
return `${ICONS.pending} ${step.label}`;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Emit clean log for a completed step if log collection is enabled
|
|
151
|
+
*/
|
|
152
|
+
function emitCleanStepLog(step: StepState): void {
|
|
153
|
+
if (!isLogCollectionEnabled()) return;
|
|
154
|
+
|
|
155
|
+
appendLog(getCleanStepLog(step));
|
|
156
|
+
|
|
157
|
+
// Also emit any output lines
|
|
158
|
+
if (step.output && step.output.length > 0) {
|
|
159
|
+
for (const line of step.output) {
|
|
160
|
+
appendLog(` ${line}`);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
132
165
|
/**
|
|
133
166
|
* Render all steps from state, including output boxes
|
|
134
167
|
* Returns the rendered output and total line count
|
|
@@ -212,7 +245,7 @@ function enablePauseResume(
|
|
|
212
245
|
* Returns resume function
|
|
213
246
|
*/
|
|
214
247
|
export function pauseStepUI(clear = false): () => void {
|
|
215
|
-
if (!
|
|
248
|
+
if (!isTTYLike() || !getTotalLinesFn) {
|
|
216
249
|
return () => {}; // No-op if not TTY or not in step context
|
|
217
250
|
}
|
|
218
251
|
|
|
@@ -466,6 +499,9 @@ async function runStepsTUI(steps: Step[]): Promise<void> {
|
|
|
466
499
|
activeInterval = null;
|
|
467
500
|
}
|
|
468
501
|
|
|
502
|
+
// Emit clean log for this step (for external log collection)
|
|
503
|
+
emitCleanStepLog(stepState);
|
|
504
|
+
|
|
469
505
|
// Final render with outcome
|
|
470
506
|
stepState.progress = undefined;
|
|
471
507
|
const finalRender = renderAllSteps(state, -1);
|
|
@@ -529,6 +565,19 @@ async function runStepsPlain(steps: Step[]): Promise<void> {
|
|
|
529
565
|
};
|
|
530
566
|
}
|
|
531
567
|
|
|
568
|
+
// Build step state for clean log emission
|
|
569
|
+
const stepState: StepState = {
|
|
570
|
+
label: step.label,
|
|
571
|
+
status: outcome.status,
|
|
572
|
+
output: outcome.output,
|
|
573
|
+
skipReason: outcome.status === 'skipped' ? outcome.reason : undefined,
|
|
574
|
+
errorMessage: outcome.status === 'error' ? outcome.message : undefined,
|
|
575
|
+
errorCause: outcome.status === 'error' ? outcome.cause : undefined,
|
|
576
|
+
};
|
|
577
|
+
|
|
578
|
+
// Emit clean log for this step
|
|
579
|
+
emitCleanStepLog(stepState);
|
|
580
|
+
|
|
532
581
|
// Print final state
|
|
533
582
|
if (outcome.status === 'success') {
|
|
534
583
|
console.log(`${greenColor}${ICONS.success}${COLORS.reset} ${step.label}`);
|
|
@@ -579,8 +628,7 @@ async function runStepsPlain(steps: Step[]): Promise<void> {
|
|
|
579
628
|
* Run a series of steps with animated progress
|
|
580
629
|
*/
|
|
581
630
|
export async function runSteps(steps: Step[], logLevel?: LogLevel): Promise<void> {
|
|
582
|
-
const useTUI =
|
|
583
|
-
process.stdout.isTTY && (!logLevel || ['info', 'warn', 'error'].includes(logLevel));
|
|
631
|
+
const useTUI = isTTYLike() && (!logLevel || ['info', 'warn', 'error'].includes(logLevel));
|
|
584
632
|
|
|
585
633
|
if (useTUI) {
|
|
586
634
|
await runStepsTUI(steps);
|