@sun-asterisk/sungen 3.2.3 → 3.2.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/dashboard.js +2 -2
- package/dist/cli/commands/dashboard.js.map +1 -1
- package/dist/cli/commands/delivery.d.ts.map +1 -1
- package/dist/cli/commands/delivery.js +22 -5
- package/dist/cli/commands/delivery.js.map +1 -1
- package/dist/cli/commands/feedback.d.ts +11 -0
- package/dist/cli/commands/feedback.d.ts.map +1 -1
- package/dist/cli/commands/feedback.js +154 -57
- package/dist/cli/commands/feedback.js.map +1 -1
- package/dist/cli/commands/login.d.ts +19 -0
- package/dist/cli/commands/login.d.ts.map +1 -0
- package/dist/cli/commands/login.js +76 -0
- package/dist/cli/commands/login.js.map +1 -0
- package/dist/cli/commands/qa-feedback.d.ts +14 -0
- package/dist/cli/commands/qa-feedback.d.ts.map +1 -0
- package/dist/cli/commands/qa-feedback.js +83 -0
- package/dist/cli/commands/qa-feedback.js.map +1 -0
- package/dist/cli/index.js +4 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/dashboard/snapshot-builder.d.ts +3 -0
- package/dist/dashboard/snapshot-builder.d.ts.map +1 -1
- package/dist/dashboard/snapshot-builder.js +155 -52
- package/dist/dashboard/snapshot-builder.js.map +1 -1
- package/dist/dashboard/templates/index.html +23 -23
- package/dist/dashboard/types.d.ts +8 -0
- package/dist/dashboard/types.d.ts.map +1 -1
- package/dist/exporters/api-testcase-formatter.d.ts +55 -0
- package/dist/exporters/api-testcase-formatter.d.ts.map +1 -0
- package/dist/exporters/api-testcase-formatter.js +304 -0
- package/dist/exporters/api-testcase-formatter.js.map +1 -0
- package/dist/exporters/csv-exporter.d.ts +13 -1
- package/dist/exporters/csv-exporter.d.ts.map +1 -1
- package/dist/exporters/csv-exporter.js +35 -6
- package/dist/exporters/csv-exporter.js.map +1 -1
- package/dist/exporters/feature-parser.d.ts +4 -0
- package/dist/exporters/feature-parser.d.ts.map +1 -1
- package/dist/exporters/feature-parser.js +7 -2
- package/dist/exporters/feature-parser.js.map +1 -1
- package/dist/exporters/json-exporter.d.ts.map +1 -1
- package/dist/exporters/json-exporter.js +11 -3
- package/dist/exporters/json-exporter.js.map +1 -1
- package/dist/exporters/playwright-report-parser.d.ts +21 -2
- package/dist/exporters/playwright-report-parser.d.ts.map +1 -1
- package/dist/exporters/playwright-report-parser.js +52 -12
- package/dist/exporters/playwright-report-parser.js.map +1 -1
- package/dist/exporters/scenario-merger.d.ts.map +1 -1
- package/dist/exporters/scenario-merger.js +50 -2
- package/dist/exporters/scenario-merger.js.map +1 -1
- package/dist/exporters/spec-parser.js +1 -1
- package/dist/exporters/spec-parser.js.map +1 -1
- package/dist/exporters/test-data-resolver.d.ts +7 -0
- package/dist/exporters/test-data-resolver.d.ts.map +1 -1
- package/dist/exporters/test-data-resolver.js +13 -0
- package/dist/exporters/test-data-resolver.js.map +1 -1
- package/dist/exporters/types.d.ts +13 -0
- package/dist/exporters/types.d.ts.map +1 -1
- package/dist/exporters/xlsx-exporter.d.ts.map +1 -1
- package/dist/exporters/xlsx-exporter.js +131 -42
- package/dist/exporters/xlsx-exporter.js.map +1 -1
- package/dist/generators/gherkin-parser/index.d.ts +15 -0
- package/dist/generators/gherkin-parser/index.d.ts.map +1 -1
- package/dist/generators/gherkin-parser/index.js +33 -1
- package/dist/generators/gherkin-parser/index.js.map +1 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +29 -8
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +21 -17
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +26 -6
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/is-hidden-assertion.hbs +3 -1
- package/dist/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +14 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/gestures/tap-top-action.hbs +3 -0
- package/dist/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -2
- package/dist/generators/test-generator/adapters/appium/templates/test-file.hbs +115 -0
- package/dist/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +1 -1
- package/dist/generators/test-generator/patterns/index.d.ts +12 -0
- package/dist/generators/test-generator/patterns/index.d.ts.map +1 -1
- package/dist/generators/test-generator/patterns/index.js +21 -1
- package/dist/generators/test-generator/patterns/index.js.map +1 -1
- package/dist/harness/script-check.d.ts.map +1 -1
- package/dist/harness/script-check.js +10 -5
- package/dist/harness/script-check.js.map +1 -1
- package/dist/orchestrator/ai-skills/writer.d.ts.map +1 -1
- package/dist/orchestrator/ai-skills/writer.js +46 -4
- package/dist/orchestrator/ai-skills/writer.js.map +1 -1
- package/dist/orchestrator/project-initializer.d.ts +14 -0
- package/dist/orchestrator/project-initializer.d.ts.map +1 -1
- package/dist/orchestrator/project-initializer.js +58 -0
- package/dist/orchestrator/project-initializer.js.map +1 -1
- package/dist/orchestrator/templates/ai-src/commands/add-flow.md +52 -5
- package/dist/orchestrator/templates/ai-src/commands/create-test.md +7 -0
- package/dist/orchestrator/templates/ai-src/commands/feedback.md +6 -6
- package/dist/orchestrator/templates/ai-src/commands/locale.md +26 -4
- package/dist/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +5 -4
- package/dist/orchestrator/templates/wdio.conf.ts +39 -3
- package/dist/tools/sungen-api/edit-in-editor.d.ts +15 -0
- package/dist/tools/sungen-api/edit-in-editor.d.ts.map +1 -0
- package/dist/tools/sungen-api/edit-in-editor.js +85 -0
- package/dist/tools/sungen-api/edit-in-editor.js.map +1 -0
- package/dist/tools/sungen-api/feedback-client.d.ts +24 -0
- package/dist/tools/sungen-api/feedback-client.d.ts.map +1 -0
- package/dist/tools/sungen-api/feedback-client.js +53 -0
- package/dist/tools/sungen-api/feedback-client.js.map +1 -0
- package/dist/tools/sungen-api/read-stdin.d.ts +6 -0
- package/dist/tools/sungen-api/read-stdin.d.ts.map +1 -0
- package/dist/tools/sungen-api/read-stdin.js +19 -0
- package/dist/tools/sungen-api/read-stdin.js.map +1 -0
- package/dist/tools/sungen-auth/api-base-url.d.ts +14 -0
- package/dist/tools/sungen-auth/api-base-url.d.ts.map +1 -0
- package/dist/tools/sungen-auth/api-base-url.js +28 -0
- package/dist/tools/sungen-auth/api-base-url.js.map +1 -0
- package/dist/tools/sungen-auth/credentials-store.d.ts +19 -0
- package/dist/tools/sungen-auth/credentials-store.d.ts.map +1 -0
- package/dist/tools/sungen-auth/credentials-store.js +94 -0
- package/dist/tools/sungen-auth/credentials-store.js.map +1 -0
- package/dist/tools/sungen-auth/login-flow.d.ts +33 -0
- package/dist/tools/sungen-auth/login-flow.d.ts.map +1 -0
- package/dist/tools/sungen-auth/login-flow.js +107 -0
- package/dist/tools/sungen-auth/login-flow.js.map +1 -0
- package/dist/tools/sungen-auth/loopback-server.d.ts +20 -0
- package/dist/tools/sungen-auth/loopback-server.d.ts.map +1 -0
- package/dist/tools/sungen-auth/loopback-server.js +132 -0
- package/dist/tools/sungen-auth/loopback-server.js.map +1 -0
- package/dist/tools/sungen-auth/open-browser.d.ts +10 -0
- package/dist/tools/sungen-auth/open-browser.d.ts.map +1 -0
- package/dist/tools/sungen-auth/open-browser.js +43 -0
- package/dist/tools/sungen-auth/open-browser.js.map +1 -0
- package/dist/tools/sungen-auth/pkce.d.ts +13 -0
- package/dist/tools/sungen-auth/pkce.d.ts.map +1 -0
- package/dist/tools/sungen-auth/pkce.js +27 -0
- package/dist/tools/sungen-auth/pkce.js.map +1 -0
- package/dist/tools/sungen-auth/sungen-auth-types.d.ts +41 -0
- package/dist/tools/sungen-auth/sungen-auth-types.d.ts.map +1 -0
- package/dist/tools/sungen-auth/sungen-auth-types.js +7 -0
- package/dist/tools/sungen-auth/sungen-auth-types.js.map +1 -0
- package/package.json +4 -3
- package/src/cli/commands/dashboard.ts +2 -2
- package/src/cli/commands/delivery.ts +24 -6
- package/src/cli/commands/feedback.ts +123 -51
- package/src/cli/commands/login.ts +75 -0
- package/src/cli/commands/qa-feedback.ts +77 -0
- package/src/cli/index.ts +4 -0
- package/src/dashboard/snapshot-builder.ts +146 -46
- package/src/dashboard/templates/index.html +23 -23
- package/src/dashboard/types.ts +10 -2
- package/src/exporters/api-testcase-formatter.ts +325 -0
- package/src/exporters/csv-exporter.ts +46 -7
- package/src/exporters/feature-parser.ts +7 -2
- package/src/exporters/json-exporter.ts +11 -3
- package/src/exporters/playwright-report-parser.ts +50 -11
- package/src/exporters/scenario-merger.ts +49 -3
- package/src/exporters/spec-parser.ts +1 -1
- package/src/exporters/test-data-resolver.ts +13 -0
- package/src/exporters/types.ts +13 -0
- package/src/exporters/xlsx-exporter.ts +155 -49
- package/src/generators/gherkin-parser/index.ts +44 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/check-action.hbs +29 -8
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/scroll-action.hbs +21 -17
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/toggle-action.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/actions/uncheck-action.hbs +26 -6
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/is-hidden-assertion.hbs +3 -1
- package/src/generators/test-generator/adapters/appium/templates/steps/assertions/visible-assertion.hbs +14 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/gestures/tap-top-action.hbs +3 -0
- package/src/generators/test-generator/adapters/appium/templates/steps/partials/locator.hbs +9 -2
- package/src/generators/test-generator/adapters/appium/templates/test-file.hbs +115 -0
- package/src/generators/test-generator/adapters/playwright/templates/steps/actions/table-action-in-row.hbs +1 -1
- package/src/generators/test-generator/patterns/index.ts +21 -1
- package/src/harness/script-check.ts +10 -5
- package/src/orchestrator/ai-skills/writer.ts +39 -4
- package/src/orchestrator/project-initializer.ts +59 -0
- package/src/orchestrator/templates/ai-src/commands/add-flow.md +52 -5
- package/src/orchestrator/templates/ai-src/commands/create-test.md +7 -0
- package/src/orchestrator/templates/ai-src/commands/feedback.md +6 -6
- package/src/orchestrator/templates/ai-src/commands/locale.md +26 -4
- package/src/orchestrator/templates/ai-src/skills/sungen-delivery/SKILL.md +5 -4
- package/src/orchestrator/templates/wdio.conf.ts +39 -3
- package/src/tools/sungen-api/edit-in-editor.ts +48 -0
- package/src/tools/sungen-api/feedback-client.ts +62 -0
- package/src/tools/sungen-api/read-stdin.ts +15 -0
- package/src/tools/sungen-auth/api-base-url.ts +23 -0
- package/src/tools/sungen-auth/credentials-store.ts +57 -0
- package/src/tools/sungen-auth/login-flow.ts +135 -0
- package/src/tools/sungen-auth/loopback-server.ts +113 -0
- package/src/tools/sungen-auth/open-browser.ts +42 -0
- package/src/tools/sungen-auth/pkce.ts +27 -0
- package/src/tools/sungen-auth/sungen-auth-types.ts +45 -0
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared types for the sungen CLI login flow (RFC 8252 native-app login against
|
|
3
|
+
* a FastSchema v0.13.0 backend: PKCE-S256 + loopback redirect + one-time code).
|
|
4
|
+
*/
|
|
5
|
+
/** Persisted, user-level credentials at ~/.sungen/credentials.json (mode 0600). */
|
|
6
|
+
export interface Credentials {
|
|
7
|
+
/** Bearer JWT minted by the backend exchange step. */
|
|
8
|
+
token: string;
|
|
9
|
+
/** Authenticated account email (from /api/auth/me). */
|
|
10
|
+
email: string;
|
|
11
|
+
/** Backend base URL the token was issued against (so `feedback` knows where to POST). */
|
|
12
|
+
api_base_url: string;
|
|
13
|
+
/** Unix epoch seconds when the token expires, if the backend reports it (single token, no refresh). */
|
|
14
|
+
expires_at?: number;
|
|
15
|
+
}
|
|
16
|
+
/** Response of POST /api/auth/cli/initiate (unwrapped from the {data} envelope). */
|
|
17
|
+
export interface InitiateResponse {
|
|
18
|
+
/** Signed carrier the dashboard login page consumes. */
|
|
19
|
+
carrier: string;
|
|
20
|
+
/** Path (not absolute) to open in the browser, e.g. /dash/login?cli=<carrier>. */
|
|
21
|
+
authorize_url: string;
|
|
22
|
+
}
|
|
23
|
+
/** Response of POST /api/auth/exchange (unwrapped from the {data} envelope). */
|
|
24
|
+
export interface ExchangeResponse {
|
|
25
|
+
/** Bearer JWT. */
|
|
26
|
+
token: string;
|
|
27
|
+
/** ISO expiry string the backend returns (e.g. "2026-07-08T06:42:30Z"). */
|
|
28
|
+
expires?: string;
|
|
29
|
+
/** Numeric epoch-seconds expiry, if a build reports it instead. */
|
|
30
|
+
expires_at?: number;
|
|
31
|
+
}
|
|
32
|
+
/** Response of GET /api/auth/me (subset we rely on, unwrapped from {data}). */
|
|
33
|
+
export interface MeResponse {
|
|
34
|
+
email: string;
|
|
35
|
+
}
|
|
36
|
+
/** PKCE material generated per login attempt; never persisted to disk. */
|
|
37
|
+
export interface PkceMaterial {
|
|
38
|
+
verifier: string;
|
|
39
|
+
challenge: string;
|
|
40
|
+
}
|
|
41
|
+
//# sourceMappingURL=sungen-auth-types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sungen-auth-types.d.ts","sourceRoot":"","sources":["../../../src/tools/sungen-auth/sungen-auth-types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,mFAAmF;AACnF,MAAM,WAAW,WAAW;IAC1B,sDAAsD;IACtD,KAAK,EAAE,MAAM,CAAC;IACd,uDAAuD;IACvD,KAAK,EAAE,MAAM,CAAC;IACd,yFAAyF;IACzF,YAAY,EAAE,MAAM,CAAC;IACrB,uGAAuG;IACvG,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,oFAAoF;AACpF,MAAM,WAAW,gBAAgB;IAC/B,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,kFAAkF;IAClF,aAAa,EAAE,MAAM,CAAC;CACvB;AAED,gFAAgF;AAChF,MAAM,WAAW,gBAAgB;IAC/B,kBAAkB;IAClB,KAAK,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,+EAA+E;AAC/E,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,MAAM,CAAC;CACf;AAED,0EAA0E;AAC1E,MAAM,WAAW,YAAY;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB"}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shared types for the sungen CLI login flow (RFC 8252 native-app login against
|
|
4
|
+
* a FastSchema v0.13.0 backend: PKCE-S256 + loopback redirect + one-time code).
|
|
5
|
+
*/
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
//# sourceMappingURL=sungen-auth-types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sungen-auth-types.js","sourceRoot":"","sources":["../../../src/tools/sungen-auth/sungen-auth-types.ts"],"names":[],"mappings":";AAAA;;;GAGG"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sun-asterisk/sungen",
|
|
3
|
-
"version": "3.2.
|
|
3
|
+
"version": "3.2.5",
|
|
4
4
|
"description": "Deterministic E2E Test Compiler - Gherkin + Selectors → Playwright tests",
|
|
5
5
|
"main": "src/index.ts",
|
|
6
6
|
"types": "src/index.ts",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"copy-templates": "mkdir -p dist/generators/test-generator/adapters/playwright/templates/steps && mkdir -p dist/generators/test-generator/templates && mkdir -p dist/orchestrator/templates && mkdir -p dist/dashboard/templates && cp -r src/generators/test-generator/adapters/playwright/templates/*.hbs dist/generators/test-generator/adapters/playwright/templates/ 2>/dev/null || true && cp -r src/generators/test-generator/adapters/playwright/templates/steps dist/generators/test-generator/adapters/playwright/templates/ && mkdir -p dist/generators/test-generator/adapters/appium/templates/steps && cp -r src/generators/test-generator/adapters/appium/templates/*.hbs dist/generators/test-generator/adapters/appium/templates/ 2>/dev/null || true && cp -r src/generators/test-generator/adapters/appium/templates/steps dist/generators/test-generator/adapters/appium/templates/ && cp src/generators/test-generator/templates/*.hbs dist/generators/test-generator/templates/ 2>/dev/null || true && cp -r src/orchestrator/templates/* dist/orchestrator/templates/ && cp src/dashboard/templates/index.html dist/dashboard/templates/index.html && mkdir -p dist/harness/catalog && cp src/harness/catalog/*.yaml dist/harness/catalog/",
|
|
13
13
|
"build:dashboard": "cd ../../dashboard && npm install --silent && npm run build && cd - && cp ../../dashboard/dist/index.html src/dashboard/templates/index.html",
|
|
14
14
|
"dev": "tsx src/cli/index.ts",
|
|
15
|
-
"test": "tsx tests/golden/run.ts && tsx tests/audit/run.ts && tsx tests/ingest/run.ts && tsx tests/eval/run.ts && tsx tests/exporter/run.ts && tsx tests/exporter/feature-parser-category.run.ts && tsx tests/exporter/api-detail-sheet.run.ts && tsx tests/exporter/mobile-app-id.run.ts && tsx tests/api-runtime/base-path-url-join.run.ts && tsx tests/capabilities/run.ts && tsx tests/openapi/run.ts && tsx tests/packaging/run.ts && tsx tests/generate-hint/run.ts && tsx tests/template-assertion/run.ts && tsx tests/journey/run.ts && tsx tests/harness/serial-cascade.run.ts && tsx src/orchestrator/ai-skills/golden-skills.test.ts",
|
|
15
|
+
"test": "tsx tests/golden/run.ts && tsx tests/audit/run.ts && tsx tests/ingest/run.ts && tsx tests/eval/run.ts && tsx tests/exporter/run.ts && tsx tests/exporter/feature-parser-category.run.ts && tsx tests/exporter/api-detail-sheet.run.ts && tsx tests/exporter/overview-sheet.run.ts && tsx tests/exporter/delivery-divider-tcid.run.ts && tsx tests/exporter/note-expected-actual.run.ts && tsx tests/exporter/test-data-vars.run.ts && tsx tests/exporter/mobile-app-id.run.ts && tsx tests/exporter/api-testcase-cells.run.ts && tsx tests/dashboard/api-flows-discovery.run.ts && tsx tests/codegen/verb-ref-masking.run.ts && tsx tests/api-runtime/base-path-url-join.run.ts && tsx tests/capabilities/run.ts && tsx tests/openapi/run.ts && tsx tests/packaging/run.ts && tsx tests/generate-hint/run.ts && tsx tests/template-assertion/run.ts && tsx tests/journey/run.ts && tsx tests/harness/serial-cascade.run.ts && tsx tests/ai-skills/cross-assistant-orphan.run.ts && tsx tests/init/codex-mcp-config.run.ts && tsx src/orchestrator/ai-skills/golden-skills.test.ts",
|
|
16
16
|
"test:update": "tsx tests/golden/run.ts --update && tsx tests/audit/run.ts --update && tsx tests/ingest/run.ts --update",
|
|
17
17
|
"prepublishOnly": "npm run build:dashboard && npm run build"
|
|
18
18
|
},
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
"node": ">=18.0.0"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@sungen/driver-ui": "3.2.
|
|
36
|
+
"@sungen/driver-ui": "3.2.5",
|
|
37
37
|
"@anthropic-ai/sdk": "^0.71.0",
|
|
38
38
|
"@babel/parser": "^7.28.5",
|
|
39
39
|
"@babel/traverse": "^7.28.5",
|
|
@@ -47,6 +47,7 @@
|
|
|
47
47
|
"fast-glob": "^3.3.3",
|
|
48
48
|
"glob": "^13.0.0",
|
|
49
49
|
"handlebars": "^4.7.8",
|
|
50
|
+
"jszip": "^3.10.1",
|
|
50
51
|
"ora": "^9.0.0",
|
|
51
52
|
"plop": "^4.0.4",
|
|
52
53
|
"tsx": "^4.22.4",
|
|
@@ -82,12 +82,12 @@ export function registerDashboardCommand(program: Command): void {
|
|
|
82
82
|
} else {
|
|
83
83
|
targets = listDashboardTargets(cwd);
|
|
84
84
|
if (targets.length === 0) {
|
|
85
|
-
console.error(`${COLOR.red}No
|
|
85
|
+
console.error(`${COLOR.red}No units found in qa/screens/, qa/flows/, or qa/api/${COLOR.reset}`);
|
|
86
86
|
process.exit(1);
|
|
87
87
|
}
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
-
const labels = targets.map((t) => t.
|
|
90
|
+
const labels = targets.map((t) => t.kind === 'flow' ? `flow/${t.name}` : t.kind === 'api' ? `api/${t.name}` : t.name);
|
|
91
91
|
log(`${COLOR.bold}sungen dashboard${COLOR.reset} — building report for ${targets.length} target(s): ${labels.join(', ')}`);
|
|
92
92
|
|
|
93
93
|
// 2. Build snapshot
|
|
@@ -137,18 +137,30 @@ function qaDir(cwd: string, target: DeliveryTarget): string {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
/**
|
|
140
|
-
* Load the apis.yaml catalog for an api-kind unit
|
|
141
|
-
*
|
|
142
|
-
*
|
|
140
|
+
* Load the apis.yaml catalog for an api-kind unit, merging the shared catalog under
|
|
141
|
+
* the unit catalog. The runtime resolves an endpoint unit-scope-first then falls back
|
|
142
|
+
* to the shared catalog (driver-api api-catalog.ts), so an endpoint defined only in
|
|
143
|
+
* `qa/api/apis.yaml` still runs — we must load it too, or its request block would come
|
|
144
|
+
* back empty (the very bug this feature fixes). Unit wins on a name collision.
|
|
145
|
+
* Returns an empty object when neither file is present (graceful degradation).
|
|
143
146
|
*/
|
|
144
147
|
function loadApiCatalog(cwd: string, target: DeliveryTarget): Record<string, ApiCatalogEntry> {
|
|
145
148
|
if (target.kind !== 'api') return {};
|
|
146
|
-
const
|
|
149
|
+
const shared = readApiCatalogFile(path.join(cwd, 'qa', 'api', 'apis.yaml'));
|
|
150
|
+
const unit = readApiCatalogFile(path.join(cwd, 'qa', 'api', target.screen, 'api', 'apis.yaml'));
|
|
151
|
+
return { ...shared, ...unit };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/** Parse one apis.yaml file into an endpoint map, unwrapping the optional `apis:` wrapper. */
|
|
155
|
+
function readApiCatalogFile(catalogPath: string): Record<string, ApiCatalogEntry> {
|
|
147
156
|
if (!fs.existsSync(catalogPath)) return {};
|
|
148
157
|
try {
|
|
149
|
-
const parsed = parseYaml(fs.readFileSync(catalogPath, 'utf-8'));
|
|
158
|
+
const parsed = parseYaml(fs.readFileSync(catalogPath, 'utf-8')) as Record<string, unknown> | null;
|
|
150
159
|
if (parsed && typeof parsed === 'object') {
|
|
151
|
-
|
|
160
|
+
// The runtime loader accepts both a flat map and an `apis:` wrapper — unwrap it
|
|
161
|
+
// here too so both loaders read the same file identically.
|
|
162
|
+
const root = parsed.apis && typeof parsed.apis === 'object' ? parsed.apis : parsed;
|
|
163
|
+
return root as Record<string, ApiCatalogEntry>;
|
|
152
164
|
}
|
|
153
165
|
} catch { /* malformed yaml — skip */ }
|
|
154
166
|
return {};
|
|
@@ -489,6 +501,8 @@ async function exportTarget(
|
|
|
489
501
|
results: resolveResultsPath(cwd, target) ? loadPlaywrightReport(resolveResultsPath(cwd, target)!) : null,
|
|
490
502
|
env,
|
|
491
503
|
selectorKeyMap,
|
|
504
|
+
kind: target.kind,
|
|
505
|
+
apiCatalog,
|
|
492
506
|
});
|
|
493
507
|
const tempSummary = buildSummary(label, rows, '');
|
|
494
508
|
const csv = renderCsv(tempSummary, rows, specLink);
|
|
@@ -533,6 +547,8 @@ async function exportTarget(
|
|
|
533
547
|
results: variant.resultsPath ? loadPlaywrightReport(variant.resultsPath) : null,
|
|
534
548
|
env,
|
|
535
549
|
selectorKeyMap,
|
|
550
|
+
kind: target.kind,
|
|
551
|
+
apiCatalog,
|
|
536
552
|
});
|
|
537
553
|
const csv = renderCsv(buildSummary(label, variantRows, ''), variantRows, specLink);
|
|
538
554
|
const csvPath = writeCsv(cwd, target.featureBaseName, csv, variant.locale);
|
|
@@ -580,6 +596,8 @@ async function exportTarget(
|
|
|
580
596
|
results: variantResults,
|
|
581
597
|
env,
|
|
582
598
|
selectorKeyMap,
|
|
599
|
+
kind: target.kind,
|
|
600
|
+
apiCatalog,
|
|
583
601
|
});
|
|
584
602
|
|
|
585
603
|
// CSV: always one file per locale, every row (CSV has no sheet concept).
|
|
@@ -1,62 +1,134 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sungen feedback "<content>"` — submit PRODUCT feedback to the remote backend.
|
|
3
|
+
*
|
|
4
|
+
* One markdown record per call. Content comes from the positional arg, piped
|
|
5
|
+
* stdin, or — when run bare in a TTY — an $EDITOR session. Login-gated:
|
|
6
|
+
* missing/expired token prompts an interactive login. The author (created_by)
|
|
7
|
+
* is NEVER sent; the backend stamps it from the bearer token.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: distinct from `sungen qa-feedback` (commands/qa-feedback.ts) — that writes
|
|
10
|
+
* the LOCAL QA-feedback log and is what the /sungen:feedback slash command calls.
|
|
11
|
+
*/
|
|
12
|
+
|
|
1
13
|
import { Command } from 'commander';
|
|
2
|
-
import
|
|
14
|
+
import * as readline from 'readline';
|
|
15
|
+
import { loadCredentials, isExpired } from '../../tools/sungen-auth/credentials-store';
|
|
16
|
+
import { resolveApiBaseUrl } from '../../tools/sungen-auth/api-base-url';
|
|
17
|
+
import { performLogin } from './login';
|
|
18
|
+
import { submitFeedback, AuthExpiredError, FeedbackPayload } from '../../tools/sungen-api/feedback-client';
|
|
19
|
+
import { readStdin } from '../../tools/sungen-api/read-stdin';
|
|
20
|
+
import { composeInEditor } from '../../tools/sungen-api/edit-in-editor';
|
|
3
21
|
|
|
4
|
-
const
|
|
5
|
-
const
|
|
22
|
+
const CATEGORIES = ['quality', 'feature_request', 'bug', 'other'];
|
|
23
|
+
const QA_WORDS = ['record', 'list'];
|
|
24
|
+
const MAX_CONTENT_LEN = 10_000;
|
|
6
25
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
.command('feedback')
|
|
10
|
-
.description('Local-first QA feedback (test-design knowledge + product telemetry). Synced later when a server exists.');
|
|
26
|
+
// CLI version → sungen_version; same source as cli/index.ts so it never drifts.
|
|
27
|
+
const { version: SUNGEN_VERSION } = require('../../../package.json') as { version: string };
|
|
11
28
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
.
|
|
16
|
-
.
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
.option('--decision <d>', `QA decision (${DECISIONS.join('|')})`, 'none')
|
|
20
|
-
.option('--reason <text>', 'Why')
|
|
21
|
-
.option('--source <who>', 'Who gave the feedback', 'qa')
|
|
22
|
-
.action((o) => {
|
|
23
|
-
try {
|
|
24
|
-
if (!TYPES.includes(o.type)) throw new Error(`--type must be one of ${TYPES.join(', ')}`);
|
|
25
|
-
if (!DECISIONS.includes(o.decision)) throw new Error(`--decision must be one of ${DECISIONS.join(', ')}`);
|
|
26
|
-
const p = recordFeedback({
|
|
27
|
-
type: o.type as FeedbackType,
|
|
28
|
-
screen: o.screen,
|
|
29
|
-
target: o.target,
|
|
30
|
-
decision: o.decision as FeedbackDecision,
|
|
31
|
-
message: o.message,
|
|
32
|
-
reason: o.reason,
|
|
33
|
-
source: o.source,
|
|
34
|
-
});
|
|
35
|
-
console.log(`✓ feedback recorded (${o.type}${o.screen ? ' · ' + o.screen : ''}) → ${p}`);
|
|
36
|
-
} catch (e) {
|
|
37
|
-
console.error('Error:', e instanceof Error ? e.message : e);
|
|
38
|
-
process.exit(1);
|
|
39
|
-
}
|
|
29
|
+
/** Ask a yes/no question on the terminal; defaults to "no" on empty input. */
|
|
30
|
+
function promptYesNo(question: string): Promise<boolean> {
|
|
31
|
+
return new Promise((resolve) => {
|
|
32
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
33
|
+
rl.question(question, (answer) => {
|
|
34
|
+
rl.close();
|
|
35
|
+
resolve(/^y(es)?$/i.test(answer.trim()));
|
|
40
36
|
});
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Resolve feedback content from positional arg or piped stdin. */
|
|
41
|
+
async function resolveContent(positional?: string): Promise<string | null> {
|
|
42
|
+
if (positional && positional.trim()) return positional;
|
|
43
|
+
const piped = await readStdin();
|
|
44
|
+
if (piped && piped.trim()) return piped;
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** Ensure we have a valid token; prompt an interactive login if not. Returns creds or null if the user declines. */
|
|
49
|
+
async function ensureLogin(apiBaseUrl: string): Promise<{ token: string; api_base_url: string } | null> {
|
|
50
|
+
const creds = loadCredentials();
|
|
51
|
+
if (creds && !isExpired(creds)) return creds;
|
|
52
|
+
|
|
53
|
+
const reason = creds ? 'Your session has expired.' : 'You are not logged in.';
|
|
54
|
+
const yes = await promptYesNo(`${reason} Log in now? (y/n) `);
|
|
55
|
+
if (!yes) return null;
|
|
56
|
+
return performLogin(apiBaseUrl);
|
|
57
|
+
}
|
|
41
58
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
.
|
|
45
|
-
.
|
|
46
|
-
.option('--
|
|
47
|
-
.option('--
|
|
48
|
-
.action((
|
|
59
|
+
export function registerFeedbackCommand(program: Command): void {
|
|
60
|
+
program
|
|
61
|
+
.command('feedback [content]')
|
|
62
|
+
.description('Send product feedback about sungen (one record; pass as an argument, pipe via stdin, or run with no argument to compose in $EDITOR)')
|
|
63
|
+
.option('--category <c>', `Feedback category (${CATEGORIES.join('|')})`, 'other')
|
|
64
|
+
.option('--api-base-url <url>', 'Backend base URL (overrides SUNGEN_API_BASE_URL + stored value)')
|
|
65
|
+
.action(async (content: string | undefined, options: { category: string; apiBaseUrl?: string }) => {
|
|
49
66
|
try {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
67
|
+
// Footgun guard: a bare `record`/`list` with no stdin is almost certainly
|
|
68
|
+
// someone reaching for the moved QA primitive, not real feedback text.
|
|
69
|
+
if (content && QA_WORDS.includes(content.trim()) && process.stdin.isTTY) {
|
|
70
|
+
console.error(
|
|
71
|
+
`The local QA log moved to \`sungen qa-feedback ${content.trim()}\`.\n` +
|
|
72
|
+
`To send "${content.trim()}" as product feedback, pipe it via stdin: echo "${content.trim()}" | sungen feedback`,
|
|
73
|
+
);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
let text = await resolveContent(content);
|
|
78
|
+
if (!text) {
|
|
79
|
+
if (process.stdin.isTTY) {
|
|
80
|
+
// No arg and interactive: open the editor (like `git commit`).
|
|
81
|
+
text = composeInEditor();
|
|
82
|
+
if (!text) {
|
|
83
|
+
console.error('Aborting — no feedback provided (empty message).');
|
|
84
|
+
process.exit(1);
|
|
85
|
+
}
|
|
86
|
+
} else {
|
|
87
|
+
// Piped but empty.
|
|
88
|
+
console.error('Provide feedback text as an argument or pipe it via stdin.');
|
|
89
|
+
process.exit(1);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
if (text.length > MAX_CONTENT_LEN) {
|
|
93
|
+
console.error(`Feedback is too long (${text.length} chars; max ${MAX_CONTENT_LEN}).`);
|
|
94
|
+
process.exit(1);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (!CATEGORIES.includes(options.category)) {
|
|
98
|
+
console.error(`--category must be one of ${CATEGORIES.join(', ')}`);
|
|
99
|
+
process.exit(1);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
const apiBaseUrl = resolveApiBaseUrl(options.apiBaseUrl);
|
|
103
|
+
const creds = await ensureLogin(apiBaseUrl);
|
|
104
|
+
if (!creds) {
|
|
105
|
+
console.error('Run `sungen login` first, then re-send your feedback.');
|
|
106
|
+
process.exit(1);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
const payload: FeedbackPayload = {
|
|
110
|
+
content: text,
|
|
111
|
+
category: options.category,
|
|
112
|
+
sungen_version: SUNGEN_VERSION,
|
|
113
|
+
os: process.platform,
|
|
114
|
+
source: 'cli',
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const { id } = await submitFeedback(creds.api_base_url, creds.token, payload);
|
|
119
|
+
console.log(`Feedback submitted (id=${id}).`);
|
|
120
|
+
} catch (err) {
|
|
121
|
+
if (err instanceof AuthExpiredError) {
|
|
122
|
+
// Token died between load and POST — re-login once, then retry.
|
|
123
|
+
const yes = await promptYesNo('Your session expired. Log in again and retry? (y/n) ');
|
|
124
|
+
if (!yes) { console.error('Aborted.'); process.exit(1); }
|
|
125
|
+
const fresh = await performLogin(apiBaseUrl);
|
|
126
|
+
const { id } = await submitFeedback(fresh.api_base_url, fresh.token, payload);
|
|
127
|
+
console.log(`Feedback submitted (id=${id}).`);
|
|
128
|
+
} else {
|
|
129
|
+
throw err;
|
|
130
|
+
}
|
|
58
131
|
}
|
|
59
|
-
console.log('');
|
|
60
132
|
} catch (e) {
|
|
61
133
|
console.error('Error:', e instanceof Error ? e.message : e);
|
|
62
134
|
process.exit(1);
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sungen login` / `sungen logout`.
|
|
3
|
+
*
|
|
4
|
+
* login — RFC 8252 native-app login (PKCE-S256 + loopback + one-time code)
|
|
5
|
+
* against the FastSchema backend; caches the token at
|
|
6
|
+
* ~/.sungen/credentials.json (0600).
|
|
7
|
+
* logout — clears that file.
|
|
8
|
+
*
|
|
9
|
+
* Mirrors the structure of `commands/figma.ts` (commander + scrubbed errors).
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { Command } from 'commander';
|
|
13
|
+
import { generatePkce, randomCorrelation } from '../../tools/sungen-auth/pkce';
|
|
14
|
+
import { startLoopback } from '../../tools/sungen-auth/loopback-server';
|
|
15
|
+
import { runLoginFlow } from '../../tools/sungen-auth/login-flow';
|
|
16
|
+
import { saveCredentials, clearCredentials, credentialsPath } from '../../tools/sungen-auth/credentials-store';
|
|
17
|
+
import { openBrowser } from '../../tools/sungen-auth/open-browser';
|
|
18
|
+
import { resolveApiBaseUrl } from '../../tools/sungen-auth/api-base-url';
|
|
19
|
+
import type { Credentials } from '../../tools/sungen-auth/sungen-auth-types';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Run the full login flow against `apiBaseUrl`, persist the token, and return
|
|
23
|
+
* the stored credentials. Reused by `sungen feedback` for on-demand re-auth.
|
|
24
|
+
*/
|
|
25
|
+
export async function performLogin(apiBaseUrl: string): Promise<Credentials> {
|
|
26
|
+
const pkce = generatePkce();
|
|
27
|
+
const correlation = randomCorrelation();
|
|
28
|
+
const loopback = await startLoopback();
|
|
29
|
+
try {
|
|
30
|
+
const result = await runLoginFlow({
|
|
31
|
+
apiBaseUrl,
|
|
32
|
+
pkce,
|
|
33
|
+
correlation,
|
|
34
|
+
redirectUri: loopback.redirectUri,
|
|
35
|
+
openBrowser,
|
|
36
|
+
waitForCode: (corr, timeoutMs) => loopback.waitForCode(corr, timeoutMs),
|
|
37
|
+
});
|
|
38
|
+
const creds: Credentials = {
|
|
39
|
+
token: result.token,
|
|
40
|
+
email: result.email,
|
|
41
|
+
api_base_url: apiBaseUrl,
|
|
42
|
+
expires_at: result.expires_at,
|
|
43
|
+
};
|
|
44
|
+
saveCredentials(creds);
|
|
45
|
+
return creds;
|
|
46
|
+
} finally {
|
|
47
|
+
loopback.close();
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export function registerLoginCommand(program: Command): void {
|
|
52
|
+
program
|
|
53
|
+
.command('login')
|
|
54
|
+
.description('Log in to the sungen feedback backend (opens your browser)')
|
|
55
|
+
.option('--api-base-url <url>', 'Backend base URL (overrides SUNGEN_API_BASE_URL + stored value)')
|
|
56
|
+
.action(async (options: { apiBaseUrl?: string }) => {
|
|
57
|
+
const apiBaseUrl = resolveApiBaseUrl(options.apiBaseUrl);
|
|
58
|
+
try {
|
|
59
|
+
const creds = await performLogin(apiBaseUrl);
|
|
60
|
+
console.log(`Logged in as ${creds.email}.`);
|
|
61
|
+
console.log(`Token saved to ${credentialsPath()} (mode 0600).`);
|
|
62
|
+
} catch (err: any) {
|
|
63
|
+
console.error(`Login failed: ${err?.message ?? err}`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
program
|
|
69
|
+
.command('logout')
|
|
70
|
+
.description('Remove the cached sungen login token')
|
|
71
|
+
.action(() => {
|
|
72
|
+
clearCredentials();
|
|
73
|
+
console.log(`Logged out. Removed ${credentialsPath()} (if present).`);
|
|
74
|
+
});
|
|
75
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `sungen qa-feedback` — the LOCAL QA-feedback log (test-design knowledge +
|
|
3
|
+
* product telemetry), persisted to .sungen/feedback/feedback.jsonl.
|
|
4
|
+
*
|
|
5
|
+
* This is the persistence primitive behind the `/sungen:feedback` slash command
|
|
6
|
+
* (which auto-attaches screen/viewpoint/decision context and then calls
|
|
7
|
+
* `sungen qa-feedback record`). It is rarely typed by hand.
|
|
8
|
+
*
|
|
9
|
+
* NOTE: distinct from `sungen feedback` (see commands/feedback.ts) — that command
|
|
10
|
+
* submits PRODUCT feedback to the remote backend. This one only writes locally.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { Command } from 'commander';
|
|
14
|
+
import { recordFeedback, summarize, FeedbackType, FeedbackDecision } from '../../harness/feedback';
|
|
15
|
+
|
|
16
|
+
const TYPES = ['test-design', 'product', 'other'];
|
|
17
|
+
const DECISIONS = ['accept', 'reject', 'edit', 'add', 'none'];
|
|
18
|
+
|
|
19
|
+
export function registerQaFeedbackCommand(program: Command): void {
|
|
20
|
+
const fb = program
|
|
21
|
+
.command('qa-feedback')
|
|
22
|
+
.description('Local-first QA feedback log (test-design knowledge + product telemetry). Primarily invoked by the /sungen:feedback slash command.');
|
|
23
|
+
|
|
24
|
+
fb
|
|
25
|
+
.command('record')
|
|
26
|
+
.description('Record a feedback entry')
|
|
27
|
+
.requiredOption('--message <text>', 'The feedback')
|
|
28
|
+
.option('--type <t>', `Feedback type (${TYPES.join('|')})`, 'test-design')
|
|
29
|
+
.option('-s, --screen <name>', 'Screen/flow the feedback relates to')
|
|
30
|
+
.option('--target <ref>', 'What it is about (viewpoint id / scenario / command / artifact)')
|
|
31
|
+
.option('--decision <d>', `QA decision (${DECISIONS.join('|')})`, 'none')
|
|
32
|
+
.option('--reason <text>', 'Why')
|
|
33
|
+
.option('--source <who>', 'Who gave the feedback', 'qa')
|
|
34
|
+
.action((o) => {
|
|
35
|
+
try {
|
|
36
|
+
if (!TYPES.includes(o.type)) throw new Error(`--type must be one of ${TYPES.join(', ')}`);
|
|
37
|
+
if (!DECISIONS.includes(o.decision)) throw new Error(`--decision must be one of ${DECISIONS.join(', ')}`);
|
|
38
|
+
const p = recordFeedback({
|
|
39
|
+
type: o.type as FeedbackType,
|
|
40
|
+
screen: o.screen,
|
|
41
|
+
target: o.target,
|
|
42
|
+
decision: o.decision as FeedbackDecision,
|
|
43
|
+
message: o.message,
|
|
44
|
+
reason: o.reason,
|
|
45
|
+
source: o.source,
|
|
46
|
+
});
|
|
47
|
+
console.log(`✓ feedback recorded (${o.type}${o.screen ? ' · ' + o.screen : ''}) → ${p}`);
|
|
48
|
+
} catch (e) {
|
|
49
|
+
console.error('Error:', e instanceof Error ? e.message : e);
|
|
50
|
+
process.exit(1);
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
fb
|
|
55
|
+
.command('list')
|
|
56
|
+
.description('List / summarise recorded feedback')
|
|
57
|
+
.option('-s, --screen <name>', 'Filter by screen')
|
|
58
|
+
.option('--type <t>', 'Filter by type')
|
|
59
|
+
.option('--json', 'Output raw JSON')
|
|
60
|
+
.action((o) => {
|
|
61
|
+
try {
|
|
62
|
+
const s = summarize({ screen: o.screen, type: o.type });
|
|
63
|
+
if (o.json) { console.log(JSON.stringify(s, null, 2)); return; }
|
|
64
|
+
console.log(`\n━━━ Feedback (${s.total}) ━━━`);
|
|
65
|
+
console.log(` by type: ${Object.entries(s.byType).map(([k, v]) => `${k}=${v}`).join(' ') || '—'}`);
|
|
66
|
+
console.log(` by decision: ${Object.entries(s.byDecision).map(([k, v]) => `${k}=${v}`).join(' ') || '—'}\n`);
|
|
67
|
+
for (const e of s.entries.slice(-20)) {
|
|
68
|
+
console.log(` [${e.type}${e.decision && e.decision !== 'none' ? '/' + e.decision : ''}] ${e.screen ? e.screen + ' · ' : ''}${e.target ? e.target + ' — ' : ''}${e.message}`);
|
|
69
|
+
if (e.reason) console.log(` reason: ${e.reason}`);
|
|
70
|
+
}
|
|
71
|
+
console.log('');
|
|
72
|
+
} catch (e) {
|
|
73
|
+
console.error('Error:', e instanceof Error ? e.message : e);
|
|
74
|
+
process.exit(1);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
}
|
package/src/cli/index.ts
CHANGED
|
@@ -23,6 +23,7 @@ import { registerEvalCommand } from './commands/eval';
|
|
|
23
23
|
import { registerManifestCommand } from './commands/manifest';
|
|
24
24
|
import { registerLedgerCommand } from './commands/ledger';
|
|
25
25
|
import { registerFeedbackCommand } from './commands/feedback';
|
|
26
|
+
import { registerQaFeedbackCommand } from './commands/qa-feedback';
|
|
26
27
|
import { registerScriptCheckCommand } from './commands/script-check';
|
|
27
28
|
import { registerTraceCommand } from './commands/trace';
|
|
28
29
|
import { registerChallengeCommand } from './commands/challenge';
|
|
@@ -31,6 +32,7 @@ import { registerCapabilityCommand } from './commands/capability';
|
|
|
31
32
|
import { registerFlowCheckCommand } from './commands/flow-check';
|
|
32
33
|
import { registerContextCommand } from './commands/context';
|
|
33
34
|
import { registerRepairCommand } from './commands/repair';
|
|
35
|
+
import { registerLoginCommand } from './commands/login';
|
|
34
36
|
import { capabilityRegistry } from '../capabilities/registry';
|
|
35
37
|
import { discoverAndRegisterCapabilities } from '../capabilities/discover';
|
|
36
38
|
|
|
@@ -66,6 +68,7 @@ async function main() {
|
|
|
66
68
|
registerManifestCommand(program);
|
|
67
69
|
registerLedgerCommand(program);
|
|
68
70
|
registerFeedbackCommand(program);
|
|
71
|
+
registerQaFeedbackCommand(program);
|
|
69
72
|
registerScriptCheckCommand(program);
|
|
70
73
|
registerTraceCommand(program);
|
|
71
74
|
registerChallengeCommand(program);
|
|
@@ -76,6 +79,7 @@ async function main() {
|
|
|
76
79
|
registerRepairCommand(program);
|
|
77
80
|
registerIngestCommand(program);
|
|
78
81
|
registerEvalCommand(program);
|
|
82
|
+
registerLoginCommand(program);
|
|
79
83
|
|
|
80
84
|
// Capability-contributed CLI commands (Capability SPI): drivers own their authoring commands
|
|
81
85
|
// (e.g. @sungen/driver-api → `sungen api import`). Discover, then register each.
|