@solarains/va-cli 0.1.1

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.
Files changed (62) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +132 -0
  3. package/dist/cli/command-types.js +2 -0
  4. package/dist/cli/help-copy.js +97 -0
  5. package/dist/cli/help.js +29 -0
  6. package/dist/cli/root-command.js +24 -0
  7. package/dist/cli/run-cli.js +107 -0
  8. package/dist/commands/assets/assets-command.js +10 -0
  9. package/dist/commands/assets/list-command.js +52 -0
  10. package/dist/commands/auth/auth-command.js +11 -0
  11. package/dist/commands/auth/login-command.js +127 -0
  12. package/dist/commands/auth/logout-command.js +57 -0
  13. package/dist/commands/doctor/doctor-command.js +33 -0
  14. package/dist/commands/init/init-command.js +19 -0
  15. package/dist/commands/intelligence/intelligence-command.js +10 -0
  16. package/dist/commands/intelligence/query-command.js +109 -0
  17. package/dist/commands/reports/get-command.js +69 -0
  18. package/dist/commands/reports/list-command.js +33 -0
  19. package/dist/commands/reports/reports-command.js +11 -0
  20. package/dist/commands/usage/usage-command.js +27 -0
  21. package/dist/config/locale.js +30 -0
  22. package/dist/config/runtime-config.js +64 -0
  23. package/dist/features/assets/assets-api-client.js +18 -0
  24. package/dist/features/assets/assets-output.js +28 -0
  25. package/dist/features/auth/auth-api-client.js +60 -0
  26. package/dist/features/auth/authenticated-api-client.js +142 -0
  27. package/dist/features/auth/browser-login.js +191 -0
  28. package/dist/features/auth/installation-state.js +65 -0
  29. package/dist/features/auth/loopback-callback-page.js +231 -0
  30. package/dist/features/daily-reports/daily-reports-api-client.js +22 -0
  31. package/dist/features/daily-reports/daily-reports-output.js +54 -0
  32. package/dist/features/init/detect-targets.js +57 -0
  33. package/dist/features/init/init-copy.js +348 -0
  34. package/dist/features/init/init-flow.js +661 -0
  35. package/dist/features/init/installer.js +122 -0
  36. package/dist/features/init/manifest.js +29 -0
  37. package/dist/features/init/presentation.js +147 -0
  38. package/dist/features/init/prompt.js +135 -0
  39. package/dist/features/init/target-registry.js +45 -0
  40. package/dist/features/init/templates.js +84 -0
  41. package/dist/features/init/types.js +2 -0
  42. package/dist/features/intelligence/intelligence-api-client.js +18 -0
  43. package/dist/features/intelligence/intelligence-output.js +36 -0
  44. package/dist/features/intelligence/intelligence-query-state.js +20 -0
  45. package/dist/features/usage/usage-api-client.js +7 -0
  46. package/dist/features/usage/usage-output.js +52 -0
  47. package/dist/main.js +7 -0
  48. package/dist/shared/argv.js +65 -0
  49. package/dist/shared/logger.js +32 -0
  50. package/dist/shared/output.js +22 -0
  51. package/dist/state/paths.js +32 -0
  52. package/dist/state/stores/file-json-store.js +29 -0
  53. package/dist/state/stores/installation-store.js +20 -0
  54. package/dist/state/stores/token-store.js +20 -0
  55. package/package.json +19 -0
  56. package/skills/visionalpha-operator/SKILL.md +38 -0
  57. package/skills/visionalpha-operator/agents/openai.yaml +4 -0
  58. package/skills/visionalpha-operator/references/asset-intelligence.md +35 -0
  59. package/skills/visionalpha-operator/references/assets.md +29 -0
  60. package/skills/visionalpha-operator/references/auth-recovery.md +26 -0
  61. package/skills/visionalpha-operator/references/daily-reports.md +30 -0
  62. package/skills/visionalpha-operator/references/usage-summary.md +24 -0
@@ -0,0 +1,191 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createPkcePair = createPkcePair;
7
+ exports.createCliState = createCliState;
8
+ exports.createCliSessionId = createCliSessionId;
9
+ exports.startLoopbackListener = startLoopbackListener;
10
+ exports.openSystemBrowser = openSystemBrowser;
11
+ exports.renderQrCode = renderQrCode;
12
+ exports.pollForCliRedeemableSession = pollForCliRedeemableSession;
13
+ const node_crypto_1 = require("node:crypto");
14
+ const node_http_1 = require("node:http");
15
+ const node_child_process_1 = require("node:child_process");
16
+ const node_os_1 = require("node:os");
17
+ const qrcode_terminal_1 = __importDefault(require("qrcode-terminal"));
18
+ const loopback_callback_page_1 = require("./loopback-callback-page");
19
+ function delay(ms) {
20
+ return new Promise((resolve) => {
21
+ setTimeout(resolve, ms);
22
+ });
23
+ }
24
+ function createPkcePair() {
25
+ const codeVerifier = (0, node_crypto_1.randomBytes)(48).toString('base64url');
26
+ const codeChallenge = (0, node_crypto_1.createHash)('sha256')
27
+ .update(codeVerifier)
28
+ .digest('base64url');
29
+ return {
30
+ codeVerifier,
31
+ codeChallenge,
32
+ method: 'S256',
33
+ };
34
+ }
35
+ function createCliState() {
36
+ return (0, node_crypto_1.randomBytes)(24).toString('base64url');
37
+ }
38
+ function createCliSessionId() {
39
+ return (0, node_crypto_1.randomUUID)();
40
+ }
41
+ async function startLoopbackListener(expectedState, locale = 'en') {
42
+ let resolveCallback = null;
43
+ let rejectCallback = null;
44
+ const callbackPromise = new Promise((resolve, reject) => {
45
+ resolveCallback = resolve;
46
+ rejectCallback = reject;
47
+ });
48
+ callbackPromise.catch(() => {
49
+ // Prevent an early callback validation failure from surfacing as an
50
+ // unhandled rejection before waitForCallback attaches.
51
+ });
52
+ const server = (0, node_http_1.createServer)((request, response) => {
53
+ const url = new URL(request.url ?? '/', 'http://127.0.0.1');
54
+ if (url.pathname !== '/callback') {
55
+ const page = (0, loopback_callback_page_1.renderLoopbackCallbackPage)({ kind: 'not-found' }, locale);
56
+ response.writeHead(page.statusCode, {
57
+ 'content-type': 'text/html; charset=utf-8',
58
+ });
59
+ response.end(page.html);
60
+ return;
61
+ }
62
+ const sessionId = url.searchParams.get('session_id');
63
+ const state = url.searchParams.get('state') ?? undefined;
64
+ if (!sessionId) {
65
+ const page = (0, loopback_callback_page_1.renderLoopbackCallbackPage)({ kind: 'missing-session' }, locale);
66
+ response.writeHead(page.statusCode, {
67
+ 'content-type': 'text/html; charset=utf-8',
68
+ });
69
+ response.end(page.html);
70
+ rejectCallback?.(new Error('Loopback callback is missing session_id.'));
71
+ return;
72
+ }
73
+ if (state !== expectedState) {
74
+ const page = (0, loopback_callback_page_1.renderLoopbackCallbackPage)({ kind: 'state-mismatch' }, locale);
75
+ response.writeHead(page.statusCode, {
76
+ 'content-type': 'text/html; charset=utf-8',
77
+ });
78
+ response.end(page.html);
79
+ rejectCallback?.(new Error('Loopback callback state validation failed.'));
80
+ return;
81
+ }
82
+ const page = (0, loopback_callback_page_1.renderLoopbackCallbackPage)({ kind: 'success', sessionId }, locale);
83
+ response.writeHead(page.statusCode, {
84
+ 'content-type': 'text/html; charset=utf-8',
85
+ });
86
+ response.end(page.html);
87
+ resolveCallback?.({
88
+ sessionId,
89
+ state,
90
+ });
91
+ });
92
+ await new Promise((resolve, reject) => {
93
+ server.once('error', reject);
94
+ server.listen(0, '127.0.0.1', () => {
95
+ server.off('error', reject);
96
+ resolve();
97
+ });
98
+ });
99
+ const address = server.address();
100
+ if (!address || typeof address === 'string') {
101
+ throw new Error('Failed to resolve loopback callback port.');
102
+ }
103
+ return {
104
+ port: address.port,
105
+ async waitForCallback(timeoutMs) {
106
+ return new Promise((resolve, reject) => {
107
+ const timer = setTimeout(() => {
108
+ reject(new Error('Loopback callback timed out.'));
109
+ }, timeoutMs);
110
+ callbackPromise
111
+ .then((payload) => {
112
+ clearTimeout(timer);
113
+ resolve(payload);
114
+ })
115
+ .catch((error) => {
116
+ clearTimeout(timer);
117
+ reject(error);
118
+ });
119
+ });
120
+ },
121
+ async close() {
122
+ await new Promise((resolve, reject) => {
123
+ server.close((error) => {
124
+ if (error) {
125
+ reject(error);
126
+ return;
127
+ }
128
+ resolve();
129
+ });
130
+ });
131
+ },
132
+ };
133
+ }
134
+ async function openSystemBrowser(url) {
135
+ const currentPlatform = (0, node_os_1.platform)();
136
+ const command = currentPlatform === 'darwin'
137
+ ? {
138
+ file: 'open',
139
+ args: [url],
140
+ }
141
+ : currentPlatform === 'win32'
142
+ ? {
143
+ file: 'cmd',
144
+ args: ['/c', 'start', '', url],
145
+ }
146
+ : {
147
+ file: 'xdg-open',
148
+ args: [url],
149
+ };
150
+ return new Promise((resolve) => {
151
+ const child = (0, node_child_process_1.spawn)(command.file, command.args, {
152
+ detached: true,
153
+ stdio: 'ignore',
154
+ });
155
+ let settled = false;
156
+ child.once('error', () => {
157
+ if (!settled) {
158
+ settled = true;
159
+ resolve(false);
160
+ }
161
+ });
162
+ child.once('spawn', () => {
163
+ if (!settled) {
164
+ settled = true;
165
+ child.unref();
166
+ resolve(true);
167
+ }
168
+ });
169
+ });
170
+ }
171
+ function renderQrCode(url) {
172
+ let output = '';
173
+ qrcode_terminal_1.default.generate(url, { small: true }, (qr) => {
174
+ output = qr;
175
+ });
176
+ return output.trim();
177
+ }
178
+ async function pollForCliRedeemableSession(getSession, timeoutMs) {
179
+ const deadline = Date.now() + timeoutMs;
180
+ while (Date.now() < deadline) {
181
+ const session = await getSession();
182
+ if (session.canRedeemInCli) {
183
+ return session;
184
+ }
185
+ if (session.status === 'error') {
186
+ throw new Error(`Auth session entered ${session.state} state before redemption.`);
187
+ }
188
+ await delay(2000);
189
+ }
190
+ throw new Error('Timed out while waiting for browser authentication to finish.');
191
+ }
@@ -0,0 +1,65 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ensureInstallationState = ensureInstallationState;
4
+ const node_crypto_1 = require("node:crypto");
5
+ const node_os_1 = require("node:os");
6
+ const CLI_VERSION = '0.1.0';
7
+ function exportPublicKeyPem(publicKey) {
8
+ return publicKey.export({
9
+ type: 'spki',
10
+ format: 'pem',
11
+ });
12
+ }
13
+ function exportPrivateKeyPem(privateKey) {
14
+ return privateKey.export({
15
+ type: 'pkcs8',
16
+ format: 'pem',
17
+ });
18
+ }
19
+ function hasUsableKeys(installation) {
20
+ return Boolean(installation?.installationId &&
21
+ installation.publicKey &&
22
+ installation.privateKey &&
23
+ installation.keyFingerprint &&
24
+ installation.clientPlatform &&
25
+ installation.clientArch &&
26
+ installation.cliVersion &&
27
+ installation.createdAt &&
28
+ installation.updatedAt);
29
+ }
30
+ async function ensureInstallationState(installationStore) {
31
+ const existing = await installationStore.read();
32
+ const now = new Date().toISOString();
33
+ if (hasUsableKeys(existing)) {
34
+ const nextState = {
35
+ ...existing,
36
+ clientPlatform: (0, node_os_1.platform)(),
37
+ clientArch: (0, node_os_1.arch)(),
38
+ cliVersion: existing.cliVersion ?? CLI_VERSION,
39
+ deviceName: existing.deviceName ?? (0, node_os_1.hostname)(),
40
+ updatedAt: now,
41
+ };
42
+ await installationStore.save(nextState);
43
+ return nextState;
44
+ }
45
+ const { publicKey, privateKey } = (0, node_crypto_1.generateKeyPairSync)('ed25519');
46
+ const publicKeyPem = exportPublicKeyPem(publicKey);
47
+ const privateKeyPem = exportPrivateKeyPem(privateKey);
48
+ const keyFingerprint = (0, node_crypto_1.createHash)('sha256')
49
+ .update(publicKeyPem)
50
+ .digest('hex');
51
+ const installation = {
52
+ installationId: (0, node_crypto_1.randomUUID)(),
53
+ publicKey: publicKeyPem,
54
+ privateKey: privateKeyPem,
55
+ keyFingerprint,
56
+ clientPlatform: (0, node_os_1.platform)(),
57
+ clientArch: (0, node_os_1.arch)(),
58
+ cliVersion: CLI_VERSION,
59
+ deviceName: (0, node_os_1.hostname)(),
60
+ createdAt: now,
61
+ updatedAt: now,
62
+ };
63
+ await installationStore.save(installation);
64
+ return installation;
65
+ }
@@ -0,0 +1,231 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderLoopbackCallbackPage = renderLoopbackCallbackPage;
4
+ const CALLBACK_PAGE_COPY = {
5
+ en: {
6
+ brand: 'VAOne',
7
+ closeAction: 'Close page',
8
+ success: {
9
+ title: 'Login successful',
10
+ description: 'You can return to the terminal. The CLI is finishing setup.',
11
+ },
12
+ errors: {
13
+ 'missing-session': {
14
+ title: 'Callback data is incomplete',
15
+ description: 'The localhost callback did not include the session identifier required to finish CLI login.',
16
+ },
17
+ 'state-mismatch': {
18
+ title: 'Callback validation failed',
19
+ description: 'The localhost callback did not match the expected CLI login state.',
20
+ },
21
+ 'not-found': {
22
+ title: 'Callback path was not found',
23
+ description: 'This local VAOne listener only serves the CLI auth callback endpoint.',
24
+ },
25
+ },
26
+ },
27
+ 'zh-CN': {
28
+ brand: 'VAOne',
29
+ closeAction: '关闭页面',
30
+ success: {
31
+ title: '登录成功',
32
+ description: '现在可以返回终端,CLI 正在完成剩余设置。',
33
+ },
34
+ errors: {
35
+ 'missing-session': {
36
+ title: '回调数据不完整',
37
+ description: '本地回调没有包含完成 CLI 登录所需的 session identifier。',
38
+ },
39
+ 'state-mismatch': {
40
+ title: '回调校验失败',
41
+ description: '本地回调与预期的 CLI 登录状态不匹配。',
42
+ },
43
+ 'not-found': {
44
+ title: '未找到回调路径',
45
+ description: '这个本地 VAOne 监听器只服务于 CLI 登录回调端点。',
46
+ },
47
+ },
48
+ },
49
+ };
50
+ function escapeHtml(value) {
51
+ return value
52
+ .replaceAll('&', '&amp;')
53
+ .replaceAll('<', '&lt;')
54
+ .replaceAll('>', '&gt;')
55
+ .replaceAll('"', '&quot;')
56
+ .replaceAll("'", '&#39;');
57
+ }
58
+ function getTitle(copy, state) {
59
+ return state.kind === 'success'
60
+ ? copy.success.title
61
+ : copy.errors[state.kind].title;
62
+ }
63
+ function getStatusCode(state) {
64
+ switch (state.kind) {
65
+ case 'success':
66
+ return 200;
67
+ case 'not-found':
68
+ return 404;
69
+ default:
70
+ return 400;
71
+ }
72
+ }
73
+ function renderBody(copy, state) {
74
+ if (state.kind === 'success') {
75
+ return `
76
+ <h1>${escapeHtml(copy.success.title)}</h1>
77
+ <p class="description">${escapeHtml(copy.success.description)}</p>
78
+ <div class="actions">
79
+ <button type="button" onclick="closeCurrentPage()">${escapeHtml(copy.closeAction)}</button>
80
+ </div>
81
+ `;
82
+ }
83
+ const errorCopy = copy.errors[state.kind];
84
+ return `
85
+ <h1>${escapeHtml(errorCopy.title)}</h1>
86
+ <p class="description">${escapeHtml(errorCopy.description)}</p>
87
+ <div class="actions">
88
+ <button type="button" onclick="closeCurrentPage()">${escapeHtml(copy.closeAction)}</button>
89
+ </div>
90
+ `;
91
+ }
92
+ function renderLoopbackCallbackPage(state, locale = 'en') {
93
+ const copy = CALLBACK_PAGE_COPY[locale];
94
+ const title = getTitle(copy, state);
95
+ return {
96
+ statusCode: getStatusCode(state),
97
+ html: `<!doctype html>
98
+ <html lang="${locale}">
99
+ <head>
100
+ <meta charset="utf-8" />
101
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
102
+ <title>${escapeHtml(copy.brand)} | ${escapeHtml(title)}</title>
103
+ <script>
104
+ function closeCurrentPage() {
105
+ try {
106
+ window.close();
107
+ } catch {}
108
+
109
+ setTimeout(() => {
110
+ try {
111
+ window.open('', '_self');
112
+ } catch {}
113
+
114
+ try {
115
+ window.close();
116
+ } catch {}
117
+
118
+ setTimeout(() => {
119
+ if (!document.hidden) {
120
+ window.location.replace('about:blank');
121
+ }
122
+ }, 120);
123
+ }, 80);
124
+ }
125
+ </script>
126
+ <style>
127
+ :root {
128
+ color-scheme: dark;
129
+ --color-bg-base: #111315;
130
+ --color-bg-surface: #121821;
131
+ --color-border-subtle: #2b3644;
132
+ --color-text-primary: #eff5fb;
133
+ --color-text-secondary: #a2b2c4;
134
+ --color-brand: #6cb6ff;
135
+ }
136
+
137
+ * {
138
+ box-sizing: border-box;
139
+ }
140
+
141
+ body {
142
+ margin: 0;
143
+ min-height: 100vh;
144
+ display: flex;
145
+ align-items: center;
146
+ justify-content: center;
147
+ padding: 24px;
148
+ background:
149
+ radial-gradient(circle at top, rgb(108 182 255 / 16%), transparent 24%),
150
+ linear-gradient(180deg, #0c1117 0%, #0a0f14 58%, #080c11 100%);
151
+ color: var(--color-text-primary);
152
+ font-family: "IBM Plex Sans", "Segoe UI", sans-serif;
153
+ }
154
+
155
+ .panel {
156
+ width: min(100%, 560px);
157
+ border: 1px solid var(--color-border-subtle);
158
+ border-radius: 22px;
159
+ background: var(--color-bg-surface);
160
+ box-shadow: 0 20px 60px rgb(0 0 0 / 28%);
161
+ padding: 28px 28px 30px;
162
+ }
163
+
164
+ .brand {
165
+ margin: 0 0 24px;
166
+ font-family: "JetBrains Mono", "SFMono-Regular", monospace;
167
+ font-size: 13px;
168
+ font-weight: 700;
169
+ letter-spacing: 0.16em;
170
+ text-transform: uppercase;
171
+ color: var(--color-brand);
172
+ }
173
+
174
+ h1 {
175
+ margin: 0;
176
+ font-size: 34px;
177
+ line-height: 1.05;
178
+ }
179
+
180
+ .description {
181
+ margin: 16px 0 0;
182
+ max-width: 28rem;
183
+ line-height: 1.6;
184
+ color: var(--color-text-secondary);
185
+ }
186
+
187
+ .actions {
188
+ margin-top: 24px;
189
+ }
190
+
191
+ button {
192
+ appearance: none;
193
+ border: 1px solid var(--color-border-subtle);
194
+ border-radius: 999px;
195
+ background: #1b2532;
196
+ color: var(--color-text-primary);
197
+ padding: 10px 18px;
198
+ font: inherit;
199
+ font-weight: 600;
200
+ cursor: pointer;
201
+ }
202
+
203
+ button:hover {
204
+ background: #243243;
205
+ }
206
+
207
+ @media (max-width: 640px) {
208
+ body {
209
+ padding: 16px;
210
+ }
211
+
212
+ .panel {
213
+ padding: 22px;
214
+ border-radius: 20px;
215
+ }
216
+
217
+ h1 {
218
+ font-size: 30px;
219
+ }
220
+ }
221
+ </style>
222
+ </head>
223
+ <body>
224
+ <main class="panel">
225
+ <p class="brand">${escapeHtml(copy.brand)}</p>
226
+ ${renderBody(copy, state)}
227
+ </main>
228
+ </body>
229
+ </html>`,
230
+ };
231
+ }
@@ -0,0 +1,22 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.listDailyReports = listDailyReports;
4
+ exports.getDailyReportDetail = getDailyReportDetail;
5
+ const authenticated_api_client_1 = require("../auth/authenticated-api-client");
6
+ function createQueryString(query) {
7
+ const searchParams = new URLSearchParams();
8
+ for (const [key, value] of Object.entries(query)) {
9
+ if (value === undefined) {
10
+ continue;
11
+ }
12
+ searchParams.set(key, String(value));
13
+ }
14
+ const encoded = searchParams.toString();
15
+ return encoded ? `?${encoded}` : '';
16
+ }
17
+ async function listDailyReports(context, query) {
18
+ return (0, authenticated_api_client_1.requestAuthenticatedJson)(context, `/v1/asset-intelligence/daily-reports${createQueryString(query)}`);
19
+ }
20
+ async function getDailyReportDetail(context, assetId, query) {
21
+ return (0, authenticated_api_client_1.requestAuthenticatedJson)(context, `/v1/asset-intelligence/daily-reports/${encodeURIComponent(assetId)}${createQueryString(query)}`);
22
+ }
@@ -0,0 +1,54 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.renderDailyReportList = renderDailyReportList;
4
+ exports.renderDailyReportDetail = renderDailyReportDetail;
5
+ const output_1 = require("../../shared/output");
6
+ function formatAssetName(report) {
7
+ return report.assetNameZh || report.assetNameEn || report.assetId;
8
+ }
9
+ function renderDailyReportList(logger, reports, selectionSummary) {
10
+ (0, output_1.printSection)(logger, 'Daily report selection');
11
+ (0, output_1.printKeyValue)(logger, 'Status', selectionSummary.statusLabel);
12
+ if (selectionSummary.selectedReportKey) {
13
+ (0, output_1.printKeyValue)(logger, 'Locked report', selectionSummary.selectedReportKey);
14
+ }
15
+ (0, output_1.printSection)(logger, 'Available reports');
16
+ if (!reports.length) {
17
+ logger.info('No reports matched the current filters.');
18
+ return;
19
+ }
20
+ reports.forEach((report, index) => {
21
+ if (index > 0) {
22
+ logger.plain('');
23
+ }
24
+ (0, output_1.printKeyValue)(logger, 'Asset', `${formatAssetName(report)} (${report.assetId})`);
25
+ (0, output_1.printKeyValue)(logger, 'Date', report.date);
26
+ (0, output_1.printKeyValue)(logger, 'Category', report.category || 'unknown');
27
+ (0, output_1.printKeyValue)(logger, 'Sentiment', `bullish ${report.sentimentCount.bullish}, neutral ${report.sentimentCount.neutral}, bearish ${report.sentimentCount.bearish}`);
28
+ (0, output_1.printList)(logger, [report.summary || 'No summary available.']);
29
+ });
30
+ }
31
+ function renderDailyReportDetail(logger, report, selectionMessage) {
32
+ (0, output_1.printSection)(logger, 'Daily report');
33
+ (0, output_1.printKeyValue)(logger, 'Asset', `${formatAssetName(report)} (${report.assetId})`);
34
+ (0, output_1.printKeyValue)(logger, 'Date', report.date);
35
+ (0, output_1.printKeyValue)(logger, 'Category', report.category || 'unknown');
36
+ (0, output_1.printKeyValue)(logger, 'Selection', selectionMessage);
37
+ (0, output_1.printSection)(logger, 'Summary');
38
+ (0, output_1.printList)(logger, [report.summary || 'No summary available.']);
39
+ (0, output_1.printSection)(logger, 'Content');
40
+ const paragraphs = report.content
41
+ .split(/\n\s*\n/g)
42
+ .map((paragraph) => paragraph.trim())
43
+ .filter(Boolean);
44
+ if (!paragraphs.length) {
45
+ logger.info('No report content is available.');
46
+ return;
47
+ }
48
+ paragraphs.forEach((paragraph, index) => {
49
+ if (index > 0) {
50
+ logger.plain('');
51
+ }
52
+ logger.plain(paragraph);
53
+ });
54
+ }
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.detectAgentTargets = detectAgentTargets;
4
+ const promises_1 = require("node:fs/promises");
5
+ const node_fs_1 = require("node:fs");
6
+ const node_path_1 = require("node:path");
7
+ const target_registry_1 = require("./target-registry");
8
+ async function exists(path) {
9
+ try {
10
+ await (0, promises_1.access)(path, node_fs_1.constants.F_OK);
11
+ return true;
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ }
17
+ async function detectAgentTargets(input) {
18
+ const projectCodex = await exists((0, node_path_1.join)(input.cwd, '.codex'));
19
+ const userCodex = await exists((0, node_path_1.join)(input.homeDir, '.codex'));
20
+ const projectClaude = await exists((0, node_path_1.join)(input.cwd, '.claude'));
21
+ const userClaude = await exists((0, node_path_1.join)(input.homeDir, '.claude'));
22
+ const projectOpenClaw = await exists((0, node_path_1.join)(input.cwd, 'skills'));
23
+ const userOpenClaw = await exists((0, node_path_1.join)(input.homeDir, '.openclaw'));
24
+ return (0, target_registry_1.getAgentTargetRegistry)().map((target) => {
25
+ const detectedIn = [];
26
+ if (target.id === 'codex') {
27
+ if (projectCodex) {
28
+ detectedIn.push('workspace');
29
+ }
30
+ if (userCodex) {
31
+ detectedIn.push('user');
32
+ }
33
+ }
34
+ if (target.id === 'claude') {
35
+ if (projectClaude) {
36
+ detectedIn.push('workspace');
37
+ }
38
+ if (userClaude) {
39
+ detectedIn.push('user');
40
+ }
41
+ }
42
+ if (target.id === 'openclaw') {
43
+ if (projectOpenClaw) {
44
+ detectedIn.push('workspace');
45
+ }
46
+ if (userOpenClaw) {
47
+ detectedIn.push('user');
48
+ }
49
+ }
50
+ return {
51
+ targetId: target.id,
52
+ displayName: target.displayName,
53
+ detected: detectedIn.length > 0,
54
+ detectedIn,
55
+ };
56
+ });
57
+ }