@contentful/experience-design-system-cli 2.2.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 (165) hide show
  1. package/README.md +532 -0
  2. package/bin/cli.js +58 -0
  3. package/dist/package.json +56 -0
  4. package/dist/src/analyze/command.d.ts +3 -0
  5. package/dist/src/analyze/command.js +175 -0
  6. package/dist/src/analyze/extract/astro.d.ts +5 -0
  7. package/dist/src/analyze/extract/astro.js +280 -0
  8. package/dist/src/analyze/extract/pipeline.d.ts +6 -0
  9. package/dist/src/analyze/extract/pipeline.js +298 -0
  10. package/dist/src/analyze/extract/react.d.ts +2 -0
  11. package/dist/src/analyze/extract/react.js +1949 -0
  12. package/dist/src/analyze/extract/slot-detection.d.ts +35 -0
  13. package/dist/src/analyze/extract/slot-detection.js +101 -0
  14. package/dist/src/analyze/extract/stencil.d.ts +2 -0
  15. package/dist/src/analyze/extract/stencil.js +293 -0
  16. package/dist/src/analyze/extract/tsx-shared.d.ts +8 -0
  17. package/dist/src/analyze/extract/tsx-shared.js +263 -0
  18. package/dist/src/analyze/extract/vue-tsx.d.ts +2 -0
  19. package/dist/src/analyze/extract/vue-tsx.js +498 -0
  20. package/dist/src/analyze/extract/vue.d.ts +5 -0
  21. package/dist/src/analyze/extract/vue.js +647 -0
  22. package/dist/src/analyze/extract/web-components.d.ts +2 -0
  23. package/dist/src/analyze/extract/web-components.js +866 -0
  24. package/dist/src/analyze/pre-classify.d.ts +17 -0
  25. package/dist/src/analyze/pre-classify.js +144 -0
  26. package/dist/src/analyze/select/command.d.ts +2 -0
  27. package/dist/src/analyze/select/command.js +256 -0
  28. package/dist/src/analyze/select/index.d.ts +6 -0
  29. package/dist/src/analyze/select/index.js +5 -0
  30. package/dist/src/analyze/select/parser.d.ts +6 -0
  31. package/dist/src/analyze/select/parser.js +53 -0
  32. package/dist/src/analyze/select/persistence.d.ts +9 -0
  33. package/dist/src/analyze/select/persistence.js +42 -0
  34. package/dist/src/analyze/select/stdout.d.ts +7 -0
  35. package/dist/src/analyze/select/stdout.js +3 -0
  36. package/dist/src/analyze/select/tui/App.d.ts +8 -0
  37. package/dist/src/analyze/select/tui/App.js +491 -0
  38. package/dist/src/analyze/select/tui/components/ComponentDetail.d.ts +20 -0
  39. package/dist/src/analyze/select/tui/components/ComponentDetail.js +43 -0
  40. package/dist/src/analyze/select/tui/components/FieldEditor.d.ts +11 -0
  41. package/dist/src/analyze/select/tui/components/FieldEditor.js +531 -0
  42. package/dist/src/analyze/select/tui/components/FinalizeDialog.d.ts +10 -0
  43. package/dist/src/analyze/select/tui/components/FinalizeDialog.js +15 -0
  44. package/dist/src/analyze/select/tui/components/HelpOverlay.d.ts +7 -0
  45. package/dist/src/analyze/select/tui/components/HelpOverlay.js +11 -0
  46. package/dist/src/analyze/select/tui/components/JsonEditor.d.ts +11 -0
  47. package/dist/src/analyze/select/tui/components/JsonEditor.js +154 -0
  48. package/dist/src/analyze/select/tui/components/JsonPanel.d.ts +11 -0
  49. package/dist/src/analyze/select/tui/components/JsonPanel.js +62 -0
  50. package/dist/src/analyze/select/tui/components/PreviewSummaryBar.d.ts +8 -0
  51. package/dist/src/analyze/select/tui/components/PreviewSummaryBar.js +29 -0
  52. package/dist/src/analyze/select/tui/components/QuitDialog.d.ts +8 -0
  53. package/dist/src/analyze/select/tui/components/QuitDialog.js +14 -0
  54. package/dist/src/analyze/select/tui/components/Sidebar.d.ts +15 -0
  55. package/dist/src/analyze/select/tui/components/Sidebar.js +48 -0
  56. package/dist/src/analyze/select/tui/components/SourcePanel.d.ts +11 -0
  57. package/dist/src/analyze/select/tui/components/SourcePanel.js +52 -0
  58. package/dist/src/analyze/select/tui/components/StatusBar.d.ts +11 -0
  59. package/dist/src/analyze/select/tui/components/StatusBar.js +6 -0
  60. package/dist/src/analyze/select/tui/components/TopBar.d.ts +10 -0
  61. package/dist/src/analyze/select/tui/components/TopBar.js +5 -0
  62. package/dist/src/analyze/select/tui/hooks/useImmediateInput.d.ts +24 -0
  63. package/dist/src/analyze/select/tui/hooks/useImmediateInput.js +68 -0
  64. package/dist/src/analyze/select/tui/hooks/useKeymap.d.ts +24 -0
  65. package/dist/src/analyze/select/tui/hooks/useKeymap.js +67 -0
  66. package/dist/src/analyze/select/tui/hooks/useSession.d.ts +19 -0
  67. package/dist/src/analyze/select/tui/hooks/useSession.js +52 -0
  68. package/dist/src/analyze/select/tui/hooks/useUndo.d.ts +8 -0
  69. package/dist/src/analyze/select/tui/hooks/useUndo.js +26 -0
  70. package/dist/src/analyze/select/types.d.ts +46 -0
  71. package/dist/src/analyze/select/types.js +20 -0
  72. package/dist/src/analyze/select-agent/command.d.ts +2 -0
  73. package/dist/src/analyze/select-agent/command.js +208 -0
  74. package/dist/src/analyze/tui/AnalyzeView.d.ts +24 -0
  75. package/dist/src/analyze/tui/AnalyzeView.js +38 -0
  76. package/dist/src/apply/api-client.d.ts +35 -0
  77. package/dist/src/apply/api-client.js +143 -0
  78. package/dist/src/apply/command.d.ts +6 -0
  79. package/dist/src/apply/command.js +787 -0
  80. package/dist/src/apply/manifest.d.ts +1 -0
  81. package/dist/src/apply/manifest.js +1 -0
  82. package/dist/src/apply/tui/SelectView.d.ts +18 -0
  83. package/dist/src/apply/tui/SelectView.js +34 -0
  84. package/dist/src/apply/tui/ServerApplyView.d.ts +32 -0
  85. package/dist/src/apply/tui/ServerApplyView.js +42 -0
  86. package/dist/src/apply/tui/ServerPreviewView.d.ts +9 -0
  87. package/dist/src/apply/tui/ServerPreviewView.js +21 -0
  88. package/dist/src/credentials-store.d.ts +8 -0
  89. package/dist/src/credentials-store.js +30 -0
  90. package/dist/src/generate/agent-runner.d.ts +86 -0
  91. package/dist/src/generate/agent-runner.js +314 -0
  92. package/dist/src/generate/command.d.ts +2 -0
  93. package/dist/src/generate/command.js +545 -0
  94. package/dist/src/generate/edit/command.d.ts +2 -0
  95. package/dist/src/generate/edit/command.js +126 -0
  96. package/dist/src/generate/prompt-builder.d.ts +18 -0
  97. package/dist/src/generate/prompt-builder.js +202 -0
  98. package/dist/src/generate/tui/GenerateView.d.ts +12 -0
  99. package/dist/src/generate/tui/GenerateView.js +10 -0
  100. package/dist/src/import/command.d.ts +2 -0
  101. package/dist/src/import/command.js +96 -0
  102. package/dist/src/import/orchestrator.d.ts +37 -0
  103. package/dist/src/import/orchestrator.js +374 -0
  104. package/dist/src/import/path-utils.d.ts +15 -0
  105. package/dist/src/import/path-utils.js +30 -0
  106. package/dist/src/import/tui/WizardApp.d.ts +10 -0
  107. package/dist/src/import/tui/WizardApp.js +906 -0
  108. package/dist/src/import/tui/steps/CredentialsStep.d.ts +15 -0
  109. package/dist/src/import/tui/steps/CredentialsStep.js +79 -0
  110. package/dist/src/import/tui/steps/DoneStep.d.ts +20 -0
  111. package/dist/src/import/tui/steps/DoneStep.js +17 -0
  112. package/dist/src/import/tui/steps/ErrorStep.d.ts +8 -0
  113. package/dist/src/import/tui/steps/ErrorStep.js +11 -0
  114. package/dist/src/import/tui/steps/GateStep.d.ts +14 -0
  115. package/dist/src/import/tui/steps/GateStep.js +20 -0
  116. package/dist/src/import/tui/steps/GenerateReviewStep.d.ts +8 -0
  117. package/dist/src/import/tui/steps/GenerateReviewStep.js +208 -0
  118. package/dist/src/import/tui/steps/PathValidationStep.d.ts +10 -0
  119. package/dist/src/import/tui/steps/PathValidationStep.js +151 -0
  120. package/dist/src/import/tui/steps/PreviewStep.d.ts +21 -0
  121. package/dist/src/import/tui/steps/PreviewStep.js +36 -0
  122. package/dist/src/import/tui/steps/RunningStep.d.ts +10 -0
  123. package/dist/src/import/tui/steps/RunningStep.js +20 -0
  124. package/dist/src/import/tui/steps/TokenInputStep.d.ts +8 -0
  125. package/dist/src/import/tui/steps/TokenInputStep.js +70 -0
  126. package/dist/src/import/tui/steps/WelcomeStep.d.ts +7 -0
  127. package/dist/src/import/tui/steps/WelcomeStep.js +33 -0
  128. package/dist/src/import/tui/steps/WizardPreviewStep.d.ts +15 -0
  129. package/dist/src/import/tui/steps/WizardPreviewStep.js +121 -0
  130. package/dist/src/import/tui/steps/preview-diff.d.ts +10 -0
  131. package/dist/src/import/tui/steps/preview-diff.js +132 -0
  132. package/dist/src/index.d.ts +1 -0
  133. package/dist/src/index.js +2 -0
  134. package/dist/src/output/format.d.ts +23 -0
  135. package/dist/src/output/format.js +110 -0
  136. package/dist/src/print/command.d.ts +2 -0
  137. package/dist/src/print/command.js +199 -0
  138. package/dist/src/print/validate/tui/ValidateView.d.ts +15 -0
  139. package/dist/src/print/validate/tui/ValidateView.js +37 -0
  140. package/dist/src/print/validate/validators/cdf-validator.d.ts +2 -0
  141. package/dist/src/print/validate/validators/cdf-validator.js +104 -0
  142. package/dist/src/print/validate/validators/dtcg-validator.d.ts +2 -0
  143. package/dist/src/print/validate/validators/dtcg-validator.js +110 -0
  144. package/dist/src/print/validate/validators/format-errors.d.ts +12 -0
  145. package/dist/src/print/validate/validators/format-errors.js +18 -0
  146. package/dist/src/program.d.ts +2 -0
  147. package/dist/src/program.js +25 -0
  148. package/dist/src/session/command.d.ts +2 -0
  149. package/dist/src/session/command.js +261 -0
  150. package/dist/src/session/db.d.ts +111 -0
  151. package/dist/src/session/db.js +1114 -0
  152. package/dist/src/session/migration.d.ts +4 -0
  153. package/dist/src/session/migration.js +117 -0
  154. package/dist/src/session/session-id.d.ts +1 -0
  155. package/dist/src/session/session-id.js +212 -0
  156. package/dist/src/session/stats.d.ts +27 -0
  157. package/dist/src/session/stats.js +89 -0
  158. package/dist/src/setup/command.d.ts +2 -0
  159. package/dist/src/setup/command.js +765 -0
  160. package/dist/src/types.d.ts +48 -0
  161. package/dist/src/types.js +1 -0
  162. package/package.json +55 -0
  163. package/skills/generate-components.md +361 -0
  164. package/skills/generate-tokens.md +194 -0
  165. package/skills/select-components.md +180 -0
@@ -0,0 +1 @@
1
+ export { readTokensFromPath, hasBreakingChangesWithImpact } from './command.js';
@@ -0,0 +1 @@
1
+ export { readTokensFromPath, hasBreakingChangesWithImpact } from './command.js';
@@ -0,0 +1,18 @@
1
+ import React from 'react';
2
+ export interface SelectableEntity {
3
+ id: string;
4
+ kind: 'token' | 'component';
5
+ status: 'new' | 'changed';
6
+ isBreaking?: boolean;
7
+ }
8
+ export declare function makeSelectKey(kind: 'token' | 'component', id: string): string;
9
+ interface SelectViewProps {
10
+ entities: SelectableEntity[];
11
+ spaceId: string;
12
+ environmentId: string;
13
+ selectedIndex: number;
14
+ selected: Set<string>;
15
+ importing: boolean;
16
+ }
17
+ export declare function SelectView({ entities, spaceId, environmentId, selectedIndex, selected, importing, }: SelectViewProps): React.ReactElement;
18
+ export {};
@@ -0,0 +1,34 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ export function makeSelectKey(kind, id) {
4
+ return `${kind}:${id}`;
5
+ }
6
+ const STATUS_ICON = {
7
+ new: '✦',
8
+ changed: '~',
9
+ };
10
+ const STATUS_COLOR = {
11
+ new: 'green',
12
+ changed: 'yellow',
13
+ };
14
+ export function SelectView({ entities, spaceId, environmentId, selectedIndex, selected, importing, }) {
15
+ const tokens = entities.filter((e) => e.kind === 'token');
16
+ const components = entities.filter((e) => e.kind === 'component');
17
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, width: "100%", children: [_jsxs(Text, { bold: true, children: ["Select \u2014 ", environmentId, " @ ", spaceId] }), _jsx(Text, { children: " " }), tokens.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: " Design Tokens" }), tokens.map((entity, i) => {
18
+ const key = makeSelectKey('token', entity.id);
19
+ return renderRow(entity, i, selectedIndex, selected.has(key), importing);
20
+ }), _jsx(Text, { children: " " })] })), components.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { bold: true, children: " Component Types" }), components.map((entity, i) => {
21
+ const globalIdx = tokens.length + i;
22
+ const key = makeSelectKey('component', entity.id);
23
+ return renderRow(entity, globalIdx, selectedIndex, selected.has(key), importing);
24
+ }), _jsx(Text, { children: " " })] })), entities.length === 0 && _jsx(Text, { dimColor: true, children: " No entities to display." }), importing ? (_jsx(Text, { dimColor: true, children: " Applying selected entities..." })) : (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { dimColor: true, children: [" ", selected.size, " selected"] }), _jsx(Text, { dimColor: true, children: " \u2191\u2193 navigate Space toggle A all N none I apply selected Q quit" })] }))] }));
25
+ }
26
+ function renderRow(entity, globalIdx, selectedIndex, checked, importing) {
27
+ const isCursor = globalIdx === selectedIndex;
28
+ const icon = STATUS_ICON[entity.status] ?? '?';
29
+ const color = STATUS_COLOR[entity.status] ?? 'white';
30
+ const checkbox = checked ? '[✓]' : '[ ]';
31
+ const cursor = isCursor ? '>' : ' ';
32
+ const breakingNote = entity.isBreaking ? ' ⚠ breaking' : '';
33
+ return (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: isCursor ? 'cyan' : undefined, children: [cursor, " "] }), _jsxs(Text, { color: checked ? 'cyan' : 'gray', children: [checkbox, " "] }), _jsxs(Text, { color: color, children: [icon, " "] }), _jsx(Box, { flexGrow: 1, marginRight: 1, children: _jsx(Text, { color: isCursor ? 'cyan' : undefined, wrap: "wrap", children: entity.id }) }), _jsxs(Text, { color: color, children: [entity.status, breakingNote] }), importing && checked && isCursor && _jsx(Text, { dimColor: true, children: " (writing...)" })] }, entity.id));
34
+ }
@@ -0,0 +1,32 @@
1
+ import React from 'react';
2
+ import type { ServerPreviewResponse, ApplyOperationResponse } from '@contentful/experience-design-system-types';
3
+ interface ServerPreviewConfirmProps {
4
+ preview: ServerPreviewResponse;
5
+ spaceId: string;
6
+ environmentId: string;
7
+ breakingWithImpact: boolean;
8
+ onConfirm: (acknowledge: boolean) => void;
9
+ onCancel: () => void;
10
+ }
11
+ export declare function ServerPreviewConfirm({ preview, spaceId, environmentId, breakingWithImpact, onConfirm, onCancel, }: ServerPreviewConfirmProps): React.ReactElement;
12
+ interface ServerPreviewAppProps {
13
+ preview: ServerPreviewResponse;
14
+ spaceId: string;
15
+ environmentId: string;
16
+ }
17
+ export declare function ServerPreviewApp({ preview, spaceId, environmentId }: ServerPreviewAppProps): React.ReactElement;
18
+ interface ServerApplyProgressProps {
19
+ spaceId: string;
20
+ environmentId: string;
21
+ status: 'applying' | 'polling' | 'error';
22
+ operationId?: string;
23
+ error?: string;
24
+ }
25
+ export declare function ServerApplyProgress({ spaceId, environmentId, status, operationId, error, }: ServerApplyProgressProps): React.ReactElement;
26
+ interface ServerApplyDoneProps {
27
+ operation: ApplyOperationResponse;
28
+ spaceId: string;
29
+ environmentId: string;
30
+ }
31
+ export declare function ServerApplyDone({ operation, spaceId, environmentId }: ServerApplyDoneProps): React.ReactElement;
32
+ export {};
@@ -0,0 +1,42 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text, useInput } from 'ink';
3
+ import { ServerPreviewView } from './ServerPreviewView.js';
4
+ export function ServerPreviewConfirm({ preview, spaceId, environmentId, breakingWithImpact, onConfirm, onCancel, }) {
5
+ useInput((input, key) => {
6
+ if (key.return)
7
+ onConfirm(breakingWithImpact);
8
+ if (key.escape || input === 'q')
9
+ onCancel();
10
+ });
11
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(ServerPreviewView, { preview: preview, spaceId: spaceId, environmentId: environmentId }), _jsxs(Box, { paddingX: 2, flexDirection: "column", children: [breakingWithImpact && (_jsxs(Text, { color: "red", bold: true, children: [' ', "\u26A0 Breaking changes will affect downstream entities. Press Enter to acknowledge and apply."] })), _jsxs(Text, { children: [' ', "Press ", _jsx(Text, { bold: true, children: "Enter" }), " to apply, ", _jsx(Text, { bold: true, children: "Esc" }), " to cancel"] })] })] }));
12
+ }
13
+ export function ServerPreviewApp({ preview, spaceId, environmentId }) {
14
+ useInput((input, key) => {
15
+ if (key.escape || input === 'q')
16
+ process.exit(0);
17
+ });
18
+ return _jsx(ServerPreviewView, { preview: preview, spaceId: spaceId, environmentId: environmentId });
19
+ }
20
+ export function ServerApplyProgress({ spaceId, environmentId, status, operationId, error, }) {
21
+ useInput((_input, key) => {
22
+ if (status === 'error' && (key.escape || _input === 'q'))
23
+ process.exit(1);
24
+ });
25
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsxs(Text, { bold: true, children: ["Import \u2014 ", environmentId, " @ ", spaceId] }), _jsx(Text, { children: " " }), status === 'applying' && _jsx(Text, { children: " Submitting apply request..." }), status === 'polling' && _jsxs(Text, { children: [" Operation ", operationId, " in progress..."] }), status === 'error' && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "red", children: [" \u2717 Error: ", error] }), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: " Press Q to exit." })] }))] }));
26
+ }
27
+ export function ServerApplyDone({ operation, spaceId, environmentId }) {
28
+ useInput((input, key) => {
29
+ if (key.escape || input === 'q') {
30
+ process.exit(operation.sys.status === 'succeeded' ? 0 : 1);
31
+ }
32
+ });
33
+ const failures = (operation.items ?? []).filter((item) => item.status === 'failed');
34
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsxs(Text, { bold: true, children: ["Import complete \u2014 ", environmentId, " @ ", spaceId] }), _jsx(Text, { children: " " }), _jsxs(Text, { color: "green", children: [" \u2713 ", operation.summary.succeeded, " succeeded"] }), operation.summary.failed > 0 && _jsxs(Text, { color: "red", children: [" \u2717 ", operation.summary.failed, " failed"] }), operation.summary.failed === 0 && _jsx(Text, { dimColor: true, children: " All entities imported successfully." }), failures.length > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { children: " " }), _jsx(Text, { bold: true, children: " Failures:" }), failures.map((item, i) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "red", children: [' ', "\u2717 ", item.entityType, ": ", item.id] }), item.error && _jsxs(Text, { dimColor: true, children: [" ", formatItemError(item.error)] })] }, i)))] })), _jsx(Text, { children: " " }), _jsx(Text, { dimColor: true, children: " Press Q to exit." })] }));
35
+ }
36
+ function formatItemError(error) {
37
+ if (!error)
38
+ return '';
39
+ if (typeof error === 'string')
40
+ return error;
41
+ return `${error.code}: ${error.message}`;
42
+ }
@@ -0,0 +1,9 @@
1
+ import React from 'react';
2
+ import type { ServerPreviewResponse } from '@contentful/experience-design-system-types';
3
+ interface ServerPreviewViewProps {
4
+ preview: ServerPreviewResponse;
5
+ spaceId: string;
6
+ environmentId: string;
7
+ }
8
+ export declare function ServerPreviewView({ preview, spaceId, environmentId }: ServerPreviewViewProps): React.ReactElement;
9
+ export {};
@@ -0,0 +1,21 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ function BreakingBadge({ item }) {
4
+ if (!item.changeClassification || item.changeClassification.classification !== 'breaking')
5
+ return null;
6
+ const reasons = item.changeClassification.breakingChanges.map((bc) => `${bc.propertyId}: ${bc.reason}`).join(', ');
7
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { color: "red", children: [" \u26A0 BREAKING: ", reasons] }), item.impact && (item.impact.affectedFragments > 0 || item.impact.affectedExperiences > 0) && (_jsxs(Text, { color: "red", children: [' ', "\u2192 affects ", item.impact.affectedFragments, " Fragments, ", item.impact.affectedExperiences, " Experiences"] }))] }));
8
+ }
9
+ function DraftWarning({ hasDraft }) {
10
+ if (!hasDraft)
11
+ return null;
12
+ return _jsx(Text, { color: "yellow", children: " \u26A1 has pending draft changes" });
13
+ }
14
+ export function ServerPreviewView({ preview, spaceId, environmentId }) {
15
+ const { components, tokens } = preview;
16
+ const totalComponents = components.new.length + components.changed.length + components.unchanged.length + components.removed.length;
17
+ const totalTokens = tokens.new.length + tokens.changed.length + tokens.unchanged.length + tokens.removed.length;
18
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 2, paddingY: 1, children: [_jsxs(Text, { bold: true, children: ["Preview \u2014 ", environmentId, " @ ", spaceId] }), _jsx(Text, { children: " " }), totalComponents > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, children: [" Component Types (", totalComponents, " total)"] }), _jsxs(Text, { color: "green", children: [" \u2726 ", components.new.length, " to create"] }), _jsxs(Text, { color: "yellow", children: [" ~ ", components.changed.length, " to update"] }), _jsxs(Text, { color: "red", children: [" \u2717 ", components.removed.length, " to remove"] }), _jsxs(Text, { dimColor: true, children: [" \u00B7 ", components.unchanged.length, " unchanged"] }), components.changed.map((item, i) => (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: "yellow", children: [" ~ ", item.current.name] }), _jsx(DraftWarning, { hasDraft: item.hasPendingDraftChanges })] }), _jsx(BreakingBadge, { item: item })] }, i))), _jsx(Text, { children: " " })] })), totalTokens > 0 && (_jsxs(Box, { flexDirection: "column", children: [_jsxs(Text, { bold: true, children: [" Design Tokens (", totalTokens, " total)"] }), _jsxs(Text, { color: "green", children: [" \u2726 ", tokens.new.length, " to create"] }), _jsxs(Text, { color: "yellow", children: [" ~ ", tokens.changed.length, " to update"] }), _jsxs(Text, { color: "red", children: [" \u2717 ", tokens.removed.length, " to remove"] }), _jsxs(Text, { dimColor: true, children: [" \u00B7 ", tokens.unchanged.length, " unchanged"] }), tokens.changed
19
+ .filter((t) => t.hasPendingDraftChanges)
20
+ .map((item, i) => (_jsxs(Box, { flexDirection: "row", children: [_jsxs(Text, { color: "yellow", children: [" ~ ", item.current.name] }), _jsx(DraftWarning, { hasDraft: true })] }, i))), _jsx(Text, { children: " " })] })), _jsx(Text, { dimColor: true, children: " Press Q to exit." })] }));
21
+ }
@@ -0,0 +1,8 @@
1
+ export type ExoCredentials = {
2
+ spaceId: string;
3
+ environmentId: string;
4
+ cmaToken: string;
5
+ };
6
+ export declare function readExoCredentials(): Promise<ExoCredentials>;
7
+ export declare function writeExoCredentials(creds: ExoCredentials): Promise<void>;
8
+ export declare function exoCredentialsPath(): string;
@@ -0,0 +1,30 @@
1
+ import { readFile, writeFile, mkdir } from 'node:fs/promises';
2
+ import { join } from 'node:path';
3
+ import { homedir } from 'node:os';
4
+ const CREDENTIALS_DIR = join(homedir(), '.config', 'exo');
5
+ const CREDENTIALS_PATH = join(CREDENTIALS_DIR, 'credentials.json');
6
+ export async function readExoCredentials() {
7
+ try {
8
+ const raw = await readFile(CREDENTIALS_PATH, 'utf8');
9
+ const parsed = JSON.parse(raw);
10
+ return {
11
+ spaceId: process.env['CONTENTFUL_SPACE_ID'] ?? parsed.spaceId ?? '',
12
+ environmentId: process.env['CONTENTFUL_ENVIRONMENT_ID'] ?? parsed.environmentId ?? '',
13
+ cmaToken: process.env['CONTENTFUL_MANAGEMENT_TOKEN'] ?? parsed.cmaToken ?? '',
14
+ };
15
+ }
16
+ catch {
17
+ return {
18
+ spaceId: process.env['CONTENTFUL_SPACE_ID'] ?? '',
19
+ environmentId: process.env['CONTENTFUL_ENVIRONMENT_ID'] ?? '',
20
+ cmaToken: process.env['CONTENTFUL_MANAGEMENT_TOKEN'] ?? '',
21
+ };
22
+ }
23
+ }
24
+ export async function writeExoCredentials(creds) {
25
+ await mkdir(CREDENTIALS_DIR, { recursive: true });
26
+ await writeFile(CREDENTIALS_PATH, JSON.stringify(creds, null, 2) + '\n', { mode: 0o600 });
27
+ }
28
+ export function exoCredentialsPath() {
29
+ return CREDENTIALS_PATH;
30
+ }
@@ -0,0 +1,86 @@
1
+ export type AgentName = 'claude' | 'codex' | 'opencode' | 'cursor';
2
+ export interface AgentRunResult {
3
+ exitCode: number;
4
+ stdout: string;
5
+ stderr: string;
6
+ timedOut: boolean;
7
+ }
8
+ export interface ClassifyPropCall {
9
+ tool: 'classify_prop';
10
+ prop: string;
11
+ cdf_type: string;
12
+ cdf_category: 'content' | 'design' | 'state';
13
+ values?: string[];
14
+ token_kind?: string;
15
+ required?: boolean;
16
+ description?: string;
17
+ default?: string | boolean;
18
+ }
19
+ export interface ExcludePropCall {
20
+ tool: 'exclude_prop';
21
+ prop: string;
22
+ reason: string;
23
+ }
24
+ export interface ClassifyComponentCall {
25
+ tool: 'classify_component';
26
+ description?: string;
27
+ }
28
+ export interface ClassifySlotCall {
29
+ tool: 'classify_slot';
30
+ slot: string;
31
+ required?: boolean;
32
+ allowed_components?: string[];
33
+ description?: string;
34
+ }
35
+ export type ToolCall = ClassifyPropCall | ExcludePropCall | ClassifyComponentCall | ClassifySlotCall;
36
+ export interface SelectComponentCall {
37
+ tool: 'select_component';
38
+ name: string;
39
+ reason?: string;
40
+ }
41
+ export interface RejectComponentCall {
42
+ tool: 'reject_component';
43
+ name: string;
44
+ reason?: string;
45
+ }
46
+ export type SelectToolCall = SelectComponentCall | RejectComponentCall;
47
+ export interface ParsedSelectToolCalls {
48
+ calls: SelectToolCall[];
49
+ warnings: string[];
50
+ }
51
+ export declare function parseSelectToolCallLines(stdout: string): ParsedSelectToolCalls;
52
+ export interface SetTokenCall {
53
+ tool: 'set_token';
54
+ path: string;
55
+ type: string;
56
+ value: unknown;
57
+ description?: string;
58
+ }
59
+ export interface SetGroupCall {
60
+ tool: 'set_group';
61
+ path: string;
62
+ description?: string;
63
+ }
64
+ export type TokenToolCall = SetTokenCall | SetGroupCall;
65
+ export interface ParsedTokenToolCalls {
66
+ calls: TokenToolCall[];
67
+ warnings: string[];
68
+ }
69
+ export interface ParsedToolCalls {
70
+ calls: ToolCall[];
71
+ warnings: string[];
72
+ }
73
+ export declare function parseToolCallLines(stdout: string): ParsedToolCalls;
74
+ export declare function parseTokenToolCallLines(stdout: string): ParsedTokenToolCalls;
75
+ export declare function resolveBinary(agent: AgentName): string;
76
+ export declare function runAgent(options: {
77
+ agent: AgentName;
78
+ prompt: string;
79
+ interactive: boolean;
80
+ timeoutMs: number;
81
+ model?: string;
82
+ onOutput?: (chunk: string) => void;
83
+ }): Promise<AgentRunResult>;
84
+ export type AgentAuthStatus = 'ok' | 'unauthenticated' | 'not-found';
85
+ export declare function checkAgentAuth(agent: AgentName): Promise<AgentAuthStatus>;
86
+ export declare function extractSentinelOutput(stdout: string): string | null | 'multiple';
@@ -0,0 +1,314 @@
1
+ import { spawn } from 'node:child_process';
2
+ const VALID_SELECT_TOOL_NAMES = new Set(['select_component', 'reject_component']);
3
+ export function parseSelectToolCallLines(stdout) {
4
+ const calls = [];
5
+ const warnings = [];
6
+ for (const raw of stdout.split('\n')) {
7
+ const line = raw.trim();
8
+ if (!line.startsWith('{'))
9
+ continue;
10
+ let obj;
11
+ try {
12
+ obj = JSON.parse(line);
13
+ }
14
+ catch {
15
+ warnings.push(`unparseable line: ${line.slice(0, 120)}`);
16
+ continue;
17
+ }
18
+ if (typeof obj !== 'object' || obj === null || !('tool' in obj))
19
+ continue;
20
+ const rec = obj;
21
+ if (!VALID_SELECT_TOOL_NAMES.has(rec.tool))
22
+ continue;
23
+ if (typeof rec.name !== 'string' || !rec.name) {
24
+ warnings.push(`${String(rec.tool)} missing name — skipped`);
25
+ continue;
26
+ }
27
+ const call = {
28
+ tool: rec.tool,
29
+ name: rec.name,
30
+ };
31
+ if (typeof rec.reason === 'string')
32
+ call.reason = rec.reason;
33
+ calls.push(call);
34
+ }
35
+ return { calls, warnings };
36
+ }
37
+ const VALID_TOOL_NAMES = new Set(['classify_prop', 'exclude_prop', 'classify_component', 'classify_slot']);
38
+ const VALID_TOKEN_TOOL_NAMES = new Set(['set_token', 'set_group']);
39
+ const VALID_CDF_TYPES = new Set(['string', 'richtext', 'media', 'enum', 'token', 'boolean']);
40
+ const VALID_CATEGORIES = new Set(['content', 'design', 'state']);
41
+ export function parseToolCallLines(stdout) {
42
+ const calls = [];
43
+ const warnings = [];
44
+ for (const raw of stdout.split('\n')) {
45
+ const line = raw.trim();
46
+ if (!line.startsWith('{'))
47
+ continue;
48
+ let obj;
49
+ try {
50
+ obj = JSON.parse(line);
51
+ }
52
+ catch {
53
+ warnings.push(`unparseable line: ${line.slice(0, 120)}`);
54
+ continue;
55
+ }
56
+ if (typeof obj !== 'object' || obj === null || !('tool' in obj))
57
+ continue;
58
+ const rec = obj;
59
+ if (!VALID_TOOL_NAMES.has(rec.tool)) {
60
+ warnings.push(`unknown tool: ${String(rec.tool)}`);
61
+ continue;
62
+ }
63
+ const tool = rec.tool;
64
+ if (tool === 'classify_prop') {
65
+ if (typeof rec.prop !== 'string' || !rec.prop) {
66
+ warnings.push('classify_prop missing prop name — skipped');
67
+ continue;
68
+ }
69
+ if (typeof rec.cdf_type !== 'string' || !VALID_CDF_TYPES.has(rec.cdf_type)) {
70
+ warnings.push(`classify_prop '${rec.prop}': invalid cdf_type '${String(rec.cdf_type)}' — skipped`);
71
+ continue;
72
+ }
73
+ if (typeof rec.cdf_category !== 'string' || !VALID_CATEGORIES.has(rec.cdf_category)) {
74
+ warnings.push(`classify_prop '${rec.prop}': invalid cdf_category '${String(rec.cdf_category)}' — skipped`);
75
+ continue;
76
+ }
77
+ const call = {
78
+ tool: 'classify_prop',
79
+ prop: rec.prop,
80
+ cdf_type: rec.cdf_type,
81
+ cdf_category: rec.cdf_category,
82
+ };
83
+ if (Array.isArray(rec.values) && rec.values.every((v) => typeof v === 'string')) {
84
+ call.values = rec.values;
85
+ }
86
+ if (typeof rec.token_kind === 'string')
87
+ call.token_kind = rec.token_kind;
88
+ if (typeof rec.required === 'boolean')
89
+ call.required = rec.required;
90
+ if (typeof rec.description === 'string')
91
+ call.description = rec.description;
92
+ if (typeof rec.default === 'string' || typeof rec.default === 'boolean')
93
+ call.default = rec.default;
94
+ calls.push(call);
95
+ }
96
+ else if (tool === 'exclude_prop') {
97
+ if (typeof rec.prop !== 'string' || !rec.prop) {
98
+ warnings.push('exclude_prop missing prop name — skipped');
99
+ continue;
100
+ }
101
+ calls.push({
102
+ tool: 'exclude_prop',
103
+ prop: rec.prop,
104
+ reason: typeof rec.reason === 'string' ? rec.reason : '',
105
+ });
106
+ }
107
+ else if (tool === 'classify_component') {
108
+ const call = { tool: 'classify_component' };
109
+ if (typeof rec.description === 'string')
110
+ call.description = rec.description;
111
+ calls.push(call);
112
+ }
113
+ else if (tool === 'classify_slot') {
114
+ if (typeof rec.slot !== 'string' || !rec.slot) {
115
+ warnings.push('classify_slot missing slot name — skipped');
116
+ continue;
117
+ }
118
+ const call = { tool: 'classify_slot', slot: rec.slot };
119
+ if (typeof rec.required === 'boolean')
120
+ call.required = rec.required;
121
+ if (Array.isArray(rec.allowed_components) && rec.allowed_components.every((v) => typeof v === 'string')) {
122
+ call.allowed_components = rec.allowed_components;
123
+ }
124
+ if (typeof rec.description === 'string')
125
+ call.description = rec.description;
126
+ calls.push(call);
127
+ }
128
+ }
129
+ return { calls, warnings };
130
+ }
131
+ export function parseTokenToolCallLines(stdout) {
132
+ const calls = [];
133
+ const warnings = [];
134
+ for (const raw of stdout.split('\n')) {
135
+ const line = raw.trim();
136
+ if (!line.startsWith('{'))
137
+ continue;
138
+ let obj;
139
+ try {
140
+ obj = JSON.parse(line);
141
+ }
142
+ catch {
143
+ warnings.push(`unparseable line: ${line.slice(0, 120)}`);
144
+ continue;
145
+ }
146
+ if (typeof obj !== 'object' || obj === null || !('tool' in obj))
147
+ continue;
148
+ const rec = obj;
149
+ if (!VALID_TOKEN_TOOL_NAMES.has(rec.tool))
150
+ continue; // not a token call — skip silently
151
+ if (rec.tool === 'set_token') {
152
+ if (typeof rec.path !== 'string' || !rec.path) {
153
+ warnings.push('set_token missing path — skipped');
154
+ continue;
155
+ }
156
+ if (typeof rec.type !== 'string' || !rec.type) {
157
+ warnings.push(`set_token '${rec.path}': missing type — skipped`);
158
+ continue;
159
+ }
160
+ if (!('value' in rec)) {
161
+ warnings.push(`set_token '${rec.path}': missing value — skipped`);
162
+ continue;
163
+ }
164
+ const call = { tool: 'set_token', path: rec.path, type: rec.type, value: rec.value };
165
+ if (typeof rec.description === 'string')
166
+ call.description = rec.description;
167
+ calls.push(call);
168
+ }
169
+ else if (rec.tool === 'set_group') {
170
+ if (typeof rec.path !== 'string' || !rec.path) {
171
+ warnings.push('set_group missing path — skipped');
172
+ continue;
173
+ }
174
+ const call = { tool: 'set_group', path: rec.path };
175
+ if (typeof rec.description === 'string')
176
+ call.description = rec.description;
177
+ calls.push(call);
178
+ }
179
+ }
180
+ return { calls, warnings };
181
+ }
182
+ // --- Agent invocation ---
183
+ const AGENT_BINARIES = {
184
+ claude: 'claude',
185
+ codex: 'codex',
186
+ opencode: 'opencode',
187
+ cursor: 'cursor-agent',
188
+ };
189
+ // Default to small/fast models for single-component classification — cheap and accurate enough.
190
+ const DEFAULT_MODELS = {
191
+ claude: 'haiku',
192
+ codex: 'gpt-4.1-nano', // requires OPENAI_API_KEY; ChatGPT account users must pass --model
193
+ opencode: 'claude-haiku-4-5',
194
+ cursor: 'claude-3-5-haiku-20241022',
195
+ };
196
+ export function resolveBinary(agent) {
197
+ return AGENT_BINARIES[agent];
198
+ }
199
+ function buildArgs(agent, prompt, model) {
200
+ const m = model ?? DEFAULT_MODELS[agent];
201
+ switch (agent) {
202
+ case 'claude':
203
+ return ['--print', '--model', m, prompt];
204
+ case 'codex':
205
+ // --dangerously-bypass-approvals-and-sandbox required for non-interactive use
206
+ return ['exec', '--model', m, '--dangerously-bypass-approvals-and-sandbox', prompt];
207
+ case 'opencode':
208
+ return ['run', '--model', m, prompt];
209
+ case 'cursor':
210
+ // cursor-agent uses --print for non-interactive stdout output
211
+ return ['--print', '--model', m, prompt];
212
+ }
213
+ }
214
+ export async function runAgent(options) {
215
+ const { agent, prompt, interactive, timeoutMs, model, onOutput } = options;
216
+ const binary = resolveBinary(agent);
217
+ const args = buildArgs(agent, prompt, model);
218
+ return new Promise((resolve) => {
219
+ const child = spawn(binary, args, {
220
+ stdio: interactive ? 'inherit' : ['ignore', 'pipe', 'pipe'],
221
+ });
222
+ let stdout = '';
223
+ let stderr = '';
224
+ let timedOut = false;
225
+ const timer = setTimeout(() => {
226
+ timedOut = true;
227
+ child.kill('SIGTERM');
228
+ }, timeoutMs);
229
+ if (!interactive) {
230
+ child.stdout?.on('data', (chunk) => {
231
+ const text = chunk.toString();
232
+ stdout += text;
233
+ onOutput?.(text);
234
+ });
235
+ child.stderr?.on('data', (chunk) => {
236
+ stderr += chunk.toString();
237
+ });
238
+ }
239
+ child.on('close', (code, signal) => {
240
+ clearTimeout(timer);
241
+ resolve({
242
+ exitCode: signal ? 1 : (code ?? 1),
243
+ stdout,
244
+ stderr,
245
+ timedOut,
246
+ });
247
+ });
248
+ });
249
+ }
250
+ export async function checkAgentAuth(agent) {
251
+ if (agent !== 'claude')
252
+ return 'ok';
253
+ const binary = resolveBinary(agent);
254
+ // Verify the binary exists first
255
+ const whichResult = await new Promise((resolve) => {
256
+ const child = spawn('which', [binary], { stdio: 'ignore' });
257
+ child.on('close', (code) => resolve(code ?? 1));
258
+ });
259
+ if (whichResult !== 0)
260
+ return 'not-found';
261
+ // Use `claude auth status` — fast, no API call, works regardless of which
262
+ // auth provider (direct, Bedrock, Vertex) or whether AWS_PROFILE is set.
263
+ return new Promise((resolve) => {
264
+ const child = spawn(binary, ['auth', 'status', '--json'], {
265
+ stdio: ['ignore', 'pipe', 'pipe'],
266
+ });
267
+ let stdout = '';
268
+ let done = false;
269
+ const timer = setTimeout(() => {
270
+ if (!done) {
271
+ done = true;
272
+ child.kill('SIGTERM');
273
+ resolve('unauthenticated');
274
+ }
275
+ }, 5000);
276
+ child.stdout?.on('data', (chunk) => {
277
+ stdout += chunk.toString();
278
+ });
279
+ child.on('close', (code) => {
280
+ if (done)
281
+ return;
282
+ done = true;
283
+ clearTimeout(timer);
284
+ if (code !== 0) {
285
+ resolve('unauthenticated');
286
+ return;
287
+ }
288
+ try {
289
+ const status = JSON.parse(stdout);
290
+ resolve(status.loggedIn ? 'ok' : 'unauthenticated');
291
+ }
292
+ catch {
293
+ resolve('unauthenticated');
294
+ }
295
+ });
296
+ });
297
+ }
298
+ export function extractSentinelOutput(stdout) {
299
+ const START = '<<<EDS_OUTPUT_START>>>';
300
+ const END = '<<<EDS_OUTPUT_END>>>';
301
+ const startIdx = stdout.indexOf(START);
302
+ const endIdx = stdout.indexOf(END);
303
+ if (startIdx === -1 || endIdx === -1 || endIdx <= startIdx)
304
+ return null;
305
+ // Check for multiple blocks
306
+ const secondStart = stdout.indexOf(START, startIdx + START.length);
307
+ if (secondStart !== -1 && secondStart < endIdx)
308
+ return 'multiple';
309
+ const afterStart = stdout.indexOf(END, startIdx);
310
+ const secondEnd = stdout.indexOf(END, afterStart + END.length);
311
+ if (secondEnd !== -1)
312
+ return 'multiple';
313
+ return stdout.slice(startIdx + START.length, endIdx).trim();
314
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from 'commander';
2
+ export declare function registerGenerateCommand(program: Command): void;