@sdods/cli 0.2.0

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 (124) hide show
  1. package/LICENSE +17 -0
  2. package/bin/sdods.js +19 -0
  3. package/dist/.tsbuildinfo +1 -0
  4. package/dist/bin.d.ts +2 -0
  5. package/dist/bin.js +11 -0
  6. package/dist/commands/agent.d.ts +3 -0
  7. package/dist/commands/agent.js +219 -0
  8. package/dist/commands/analyze.d.ts +20 -0
  9. package/dist/commands/analyze.js +216 -0
  10. package/dist/commands/auth.d.ts +7 -0
  11. package/dist/commands/auth.js +97 -0
  12. package/dist/commands/browsers.d.ts +17 -0
  13. package/dist/commands/browsers.js +103 -0
  14. package/dist/commands/completion.d.ts +17 -0
  15. package/dist/commands/completion.js +143 -0
  16. package/dist/commands/config.d.ts +3 -0
  17. package/dist/commands/config.js +79 -0
  18. package/dist/commands/coverage.d.ts +3 -0
  19. package/dist/commands/coverage.js +65 -0
  20. package/dist/commands/data.d.ts +7 -0
  21. package/dist/commands/data.js +188 -0
  22. package/dist/commands/db.d.ts +3 -0
  23. package/dist/commands/db.js +241 -0
  24. package/dist/commands/doctor.d.ts +3 -0
  25. package/dist/commands/doctor.js +323 -0
  26. package/dist/commands/env.d.ts +3 -0
  27. package/dist/commands/env.js +65 -0
  28. package/dist/commands/features.d.ts +3 -0
  29. package/dist/commands/features.js +57 -0
  30. package/dist/commands/feedback.d.ts +7 -0
  31. package/dist/commands/feedback.js +96 -0
  32. package/dist/commands/har.d.ts +3 -0
  33. package/dist/commands/har.js +124 -0
  34. package/dist/commands/heal.d.ts +4 -0
  35. package/dist/commands/heal.js +63 -0
  36. package/dist/commands/init.d.ts +38 -0
  37. package/dist/commands/init.js +363 -0
  38. package/dist/commands/insights.d.ts +7 -0
  39. package/dist/commands/insights.js +127 -0
  40. package/dist/commands/integrations.d.ts +14 -0
  41. package/dist/commands/integrations.js +306 -0
  42. package/dist/commands/lint.d.ts +3 -0
  43. package/dist/commands/lint.js +74 -0
  44. package/dist/commands/mcp.d.ts +3 -0
  45. package/dist/commands/mcp.js +190 -0
  46. package/dist/commands/project.d.ts +3 -0
  47. package/dist/commands/project.js +133 -0
  48. package/dist/commands/proposals.d.ts +3 -0
  49. package/dist/commands/proposals.js +106 -0
  50. package/dist/commands/record.d.ts +3 -0
  51. package/dist/commands/record.js +174 -0
  52. package/dist/commands/report.d.ts +3 -0
  53. package/dist/commands/report.js +255 -0
  54. package/dist/commands/run.d.ts +41 -0
  55. package/dist/commands/run.js +369 -0
  56. package/dist/commands/schedule.d.ts +3 -0
  57. package/dist/commands/schedule.js +337 -0
  58. package/dist/commands/serve.d.ts +3 -0
  59. package/dist/commands/serve.js +38 -0
  60. package/dist/commands/steps.d.ts +8 -0
  61. package/dist/commands/steps.js +67 -0
  62. package/dist/commands/tokens.d.ts +3 -0
  63. package/dist/commands/tokens.js +110 -0
  64. package/dist/commands/trace.d.ts +10 -0
  65. package/dist/commands/trace.js +104 -0
  66. package/dist/commands/upgrade.d.ts +14 -0
  67. package/dist/commands/upgrade.js +115 -0
  68. package/dist/commands/users.d.ts +3 -0
  69. package/dist/commands/users.js +183 -0
  70. package/dist/commands/watch.d.ts +7 -0
  71. package/dist/commands/watch.js +116 -0
  72. package/dist/commands/workspace.d.ts +8 -0
  73. package/dist/commands/workspace.js +83 -0
  74. package/dist/context.d.ts +17 -0
  75. package/dist/context.js +22 -0
  76. package/dist/index.d.ts +3 -0
  77. package/dist/index.js +3 -0
  78. package/dist/program.d.ts +7 -0
  79. package/dist/program.js +90 -0
  80. package/dist/templates/project.d.ts +10 -0
  81. package/dist/templates/project.js +115 -0
  82. package/dist/ui.d.ts +16 -0
  83. package/dist/ui.js +67 -0
  84. package/package.json +61 -0
  85. package/templates/.claude/skills/sdods-record/SKILL.md +75 -0
  86. package/templates/.claude/skills/sdods-run/SKILL.md +54 -0
  87. package/templates/.env.example +62 -0
  88. package/templates/docker-compose.yml +40 -0
  89. package/templates/projects/demo-shop/.env.example +4 -0
  90. package/templates/projects/demo-shop/data/common/posts.yaml +11 -0
  91. package/templates/projects/demo-shop/data/common/products.json +8 -0
  92. package/templates/projects/demo-shop/data/common/users.csv +7 -0
  93. package/templates/projects/demo-shop/data/factories.ts +16 -0
  94. package/templates/projects/demo-shop/data/staging/users.csv +6 -0
  95. package/templates/projects/demo-shop/envs/local.yaml +17 -0
  96. package/templates/projects/demo-shop/envs/staging.yaml +23 -0
  97. package/templates/projects/demo-shop/features/__screenshots__/demo-shop--ui--chromium/darwin/inventory.png +0 -0
  98. package/templates/projects/demo-shop/features/api/posts.feature +90 -0
  99. package/templates/projects/demo-shop/features/auth/data-driven-login.feature +17 -0
  100. package/templates/projects/demo-shop/features/auth/login.feature +32 -0
  101. package/templates/projects/demo-shop/features/auth/user-pool.feature +20 -0
  102. package/templates/projects/demo-shop/features/cart/cart.feature +33 -0
  103. package/templates/projects/demo-shop/features/hybrid/seed-then-verify.feature +28 -0
  104. package/templates/projects/demo-shop/features/inventory/inventory.feature +38 -0
  105. package/templates/projects/demo-shop/features/inventory/network-intercept.feature +29 -0
  106. package/templates/projects/demo-shop/features/inventory/visual.feature +10 -0
  107. package/templates/projects/demo-shop/har/staging/cart.har +1 -0
  108. package/templates/projects/demo-shop/har/staging/cart.har.json +11 -0
  109. package/templates/projects/demo-shop/har/staging/login.har +1 -0
  110. package/templates/projects/demo-shop/har/staging/login.har.json +11 -0
  111. package/templates/projects/demo-shop/har/staging/posts.api.har +2061 -0
  112. package/templates/projects/demo-shop/har/staging/posts.har.json +8 -0
  113. package/templates/projects/demo-shop/har/staging/products.har +1 -0
  114. package/templates/projects/demo-shop/har/staging/products.har.json +11 -0
  115. package/templates/projects/demo-shop/har/staging/site.api.har +148 -0
  116. package/templates/projects/demo-shop/har/staging/site.har +1 -0
  117. package/templates/projects/demo-shop/har/staging/site.har.json +11 -0
  118. package/templates/projects/demo-shop/pages/CartPage.ts +20 -0
  119. package/templates/projects/demo-shop/pages/InventoryPage.ts +103 -0
  120. package/templates/projects/demo-shop/pages/LoginPage.ts +42 -0
  121. package/templates/projects/demo-shop/schemas/post.schema.json +13 -0
  122. package/templates/projects/demo-shop/sdods.project.yaml +185 -0
  123. package/templates/projects/demo-shop/steps/auth.ts +7 -0
  124. package/templates/projects/demo-shop/steps/fixtures.ts +39 -0
@@ -0,0 +1,22 @@
1
+ import { ProjectRegistry, setLogJson, setLogLevel } from '@sdods/core';
2
+ export function globalOptions(cmd) {
3
+ let root = cmd;
4
+ while (root.parent)
5
+ root = root.parent;
6
+ return root.opts();
7
+ }
8
+ export function createContext(cmd) {
9
+ const opts = globalOptions(cmd);
10
+ const rootDir = ProjectRegistry.findRepoRoot(opts.cwd ?? process.cwd());
11
+ if (opts.verbose)
12
+ setLogLevel('debug');
13
+ else if (opts.quiet)
14
+ setLogLevel('error');
15
+ if (opts.json)
16
+ setLogJson(true);
17
+ if (opts.color === false)
18
+ process.env.NO_COLOR = '1';
19
+ const registry = ProjectRegistry.discover(rootDir);
20
+ return { rootDir, registry, opts };
21
+ }
22
+ //# sourceMappingURL=context.js.map
@@ -0,0 +1,3 @@
1
+ export { buildProgram } from './program.js';
2
+ export { createContext, type CliContext, type GlobalOptions } from './context.js';
3
+ //# sourceMappingURL=index.d.ts.map
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { buildProgram } from './program.js';
2
+ export { createContext } from './context.js';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ import { Command } from 'commander';
2
+ /**
3
+ * All commands are registered eagerly (cheap) but each module keeps heavy imports
4
+ * (server, agents, DB, browser runner) inside its action via dynamic import.
5
+ */
6
+ export declare function buildProgram(): Command;
7
+ //# sourceMappingURL=program.d.ts.map
@@ -0,0 +1,90 @@
1
+ import { Command } from 'commander';
2
+ import { VERSION } from '@sdods/core';
3
+ import { registerProjectCommands } from './commands/project.js';
4
+ import { registerEnvCommands } from './commands/env.js';
5
+ import { registerConfigCommands } from './commands/config.js';
6
+ import { registerDoctorCommand } from './commands/doctor.js';
7
+ import { registerWorkspaceCommands } from './commands/workspace.js';
8
+ import { register as registerRun } from './commands/run.js';
9
+ import { register as registerLint } from './commands/lint.js';
10
+ import { register as registerInit } from './commands/init.js';
11
+ import { register as registerAnalyze } from './commands/analyze.js';
12
+ import { register as registerRecord } from './commands/record.js';
13
+ import { register as registerHar } from './commands/har.js';
14
+ import { register as registerAuth } from './commands/auth.js';
15
+ import { register as registerData } from './commands/data.js';
16
+ import { register as registerDb } from './commands/db.js';
17
+ import { register as registerReport } from './commands/report.js';
18
+ import { register as registerHeal } from './commands/heal.js';
19
+ import { register as registerInsights } from './commands/insights.js';
20
+ import { register as registerSteps } from './commands/steps.js';
21
+ import { register as registerFeatures } from './commands/features.js';
22
+ import { register as registerCoverage } from './commands/coverage.js';
23
+ import { register as registerBrowsers } from './commands/browsers.js';
24
+ import { register as registerWatch } from './commands/watch.js';
25
+ import { register as registerTrace } from './commands/trace.js';
26
+ import { register as registerAgent } from './commands/agent.js';
27
+ import { register as registerProposals } from './commands/proposals.js';
28
+ import { register as registerMcp } from './commands/mcp.js';
29
+ import { register as registerServe } from './commands/serve.js';
30
+ import { register as registerUsers } from './commands/users.js';
31
+ import { register as registerTokens } from './commands/tokens.js';
32
+ import { register as registerSchedule } from './commands/schedule.js';
33
+ import { register as registerIntegrations } from './commands/integrations.js';
34
+ import { register as registerCompletion } from './commands/completion.js';
35
+ import { register as registerUpgrade } from './commands/upgrade.js';
36
+ import { register as registerFeedback } from './commands/feedback.js';
37
+ /**
38
+ * All commands are registered eagerly (cheap) but each module keeps heavy imports
39
+ * (server, agents, DB, browser runner) inside its action via dynamic import.
40
+ */
41
+ export function buildProgram() {
42
+ const program = new Command('sdods');
43
+ program
44
+ .description('SDODS — automation and orchestration for reliable business workflows: BDD for UI, API and hybrid flows.')
45
+ .version(VERSION, '-V, --version')
46
+ .option('--json', 'machine-readable output')
47
+ .option('-q, --quiet', 'only errors')
48
+ .option('--verbose', 'debug logging')
49
+ .option('--cwd <dir>', 'repo root (default: auto-detect from the current directory)')
50
+ .option('--no-color', 'disable colours')
51
+ .showHelpAfterError('(add --help for usage)')
52
+ .showSuggestionAfterError()
53
+ .configureHelp({ sortSubcommands: true });
54
+ registerProjectCommands(program);
55
+ registerEnvCommands(program);
56
+ registerConfigCommands(program);
57
+ registerDoctorCommand(program);
58
+ registerWorkspaceCommands(program);
59
+ registerRun(program);
60
+ registerLint(program);
61
+ registerInit(program);
62
+ registerAnalyze(program);
63
+ registerRecord(program);
64
+ registerHar(program);
65
+ registerAuth(program);
66
+ registerData(program);
67
+ registerDb(program);
68
+ registerReport(program);
69
+ registerHeal(program);
70
+ registerInsights(program);
71
+ registerSteps(program);
72
+ registerFeatures(program);
73
+ registerCoverage(program);
74
+ registerBrowsers(program);
75
+ registerWatch(program);
76
+ registerTrace(program);
77
+ registerAgent(program);
78
+ registerProposals(program);
79
+ registerMcp(program);
80
+ registerServe(program);
81
+ registerUsers(program);
82
+ registerTokens(program);
83
+ registerSchedule(program);
84
+ registerIntegrations(program);
85
+ registerCompletion(program);
86
+ registerUpgrade(program);
87
+ registerFeedback(program);
88
+ return program;
89
+ }
90
+ //# sourceMappingURL=program.js.map
@@ -0,0 +1,10 @@
1
+ import type { ProjectConfig } from '@sdods/contracts';
2
+ export interface TemplateInput {
3
+ config: ProjectConfig;
4
+ envName: string;
5
+ uiUrl: string;
6
+ apiUrl: string;
7
+ }
8
+ /** Files written by `sdods project create` (relative path → content). */
9
+ export declare function projectTemplateFiles({ config, envName, uiUrl, apiUrl, }: TemplateInput): Record<string, string>;
10
+ //# sourceMappingURL=project.d.ts.map
@@ -0,0 +1,115 @@
1
+ /** Files written by `sdods project create` (relative path → content). */
2
+ export function projectTemplateFiles({ config, envName, uiUrl, apiUrl, }) {
3
+ const slug = config.slug;
4
+ const pascal = slug.replace(/(^|-)(\w)/g, (_m, _p, c) => c.toUpperCase());
5
+ return {
6
+ [`envs/${envName}.yaml`]: `name: ${envName}
7
+ ui:
8
+ baseUrl: ${uiUrl}
9
+ api:
10
+ baseUrl: ${apiUrl}
11
+ headers: { Accept: application/json }
12
+ auth: { type: none }
13
+ # auth: { type: bearer, token: '\${API_TOKEN}' }
14
+ users:
15
+ poolSize: 2
16
+ vars: {}
17
+ `,
18
+ '.env.example': `# Copy to .env.${envName} (gitignored). Values referenced as \${VAR} in yaml.\n# API_TOKEN=\n`,
19
+ 'steps/fixtures.ts': `import { test as base, createBdd } from '@sdods/core/fixtures';
20
+ import { HomePage } from '../pages/HomePage.js';
21
+ import { auth } from './auth.js';
22
+
23
+ /**
24
+ * Project-level test object: extends the SDODS merged fixtures with this project's
25
+ * auth strategy and page objects. playwright-bdd imports this file (importTestFrom).
26
+ */
27
+ export const test = base.extend<{ homePage: HomePage }>({
28
+ auth: [auth, { scope: 'worker', option: true }],
29
+ homePage: async ({ pages }, use) => {
30
+ await use(pages.get(HomePage));
31
+ },
32
+ });
33
+
34
+ export const { Given, When, Then, Step, BeforeScenario, AfterScenario, BeforeStep, AfterStep, BeforeWorker, AfterWorker } =
35
+ createBdd(test);
36
+ `,
37
+ 'steps/auth.ts': `import { defineAuth } from '@sdods/core/auth';
38
+
39
+ /** Login strategy used for storageState reuse. Replace with your app's flow. */
40
+ export const auth = defineAuth({
41
+ strategy: 'none',
42
+ });
43
+ `,
44
+ [`steps/${slug}.steps.ts`]: `import { expect } from '@playwright/test';
45
+ import { Then } from './fixtures.js';
46
+
47
+ // Project-specific steps. Generic UI/API/data steps come from @sdods/core/steps.
48
+ Then('the ${config.name} title should be visible', async ({ homePage }) => {
49
+ await expect(homePage.title).toBeVisible();
50
+ });
51
+ `,
52
+ 'pages/HomePage.ts': `import { Fixture, Given } from 'playwright-bdd/decorators';
53
+ import { BasePage } from '@sdods/core/pages';
54
+ import type { test } from '../steps/fixtures.js';
55
+
56
+ @Fixture<typeof test>('homePage')
57
+ export class HomePage extends BasePage {
58
+ readonly title = this.heal.locator(this.page.locator('h1').first(), {
59
+ role: 'heading',
60
+ description: 'page title',
61
+ });
62
+
63
+ @Given('I open the home page')
64
+ async open() {
65
+ await this.goto('home');
66
+ }
67
+ }
68
+ `,
69
+ 'features/health.feature': `@api @smoke
70
+ Feature: ${config.name} API health
71
+ As an engineer I want the API to answer so that the rest of the suite is meaningful.
72
+
73
+ Scenario: The API root responds
74
+ When I send a GET request to "/"
75
+ Then the response status should be 200
76
+ `,
77
+ 'features/ui/home.feature': `@ui @smoke
78
+ Feature: ${config.name} home page
79
+ Scenario: The home page loads
80
+ Given I open the home page
81
+ Then the page URL should contain "/"
82
+ `,
83
+ 'data/common/users.csv': `id,username,password,role\n1,user1,\${USER1_PASSWORD},standard\n2,user2,\${USER2_PASSWORD},standard\n3,admin1,\${ADMIN1_PASSWORD},admin\n`,
84
+ [`data/${envName}/.gitkeep`]: '',
85
+ 'data/factories.ts': `import { defineFactories } from '@sdods/core/data';
86
+
87
+ export default defineFactories({
88
+ user: (f) => ({
89
+ email: f.internet.email(),
90
+ firstName: f.person.firstName(),
91
+ lastName: f.person.lastName(),
92
+ }),
93
+ });
94
+ `,
95
+ 'recorded/.gitkeep': '',
96
+ 'har/.gitkeep': '',
97
+ 'README.md': `# ${config.name} (${slug})
98
+
99
+ SDODS project. Run:
100
+
101
+ \`\`\`bash
102
+ sdods run -p ${slug} -e ${envName} -l api
103
+ sdods run -p ${slug} -e ${envName} -l ui -b chromium -t @smoke
104
+ sdods lint -p ${slug}
105
+ \`\`\`
106
+
107
+ - \`sdods.project.yaml\` — project settings (layers, browsers, tags, data, auth, screenshots)
108
+ - \`envs/\` — one yaml per environment; secrets via \`\${VAR}\` from \`.env.<env>\`
109
+ - \`features/\` — Gherkin; \`steps/\` — project steps + fixtures; \`pages/\` — page objects (decorators)
110
+ - \`data/\` — CSV/JSON/YAML per environment with \`data/common\` fallback
111
+ - \`${pascal}\` page object in \`pages/HomePage.ts\` shows the heal-aware locator style
112
+ `,
113
+ };
114
+ }
115
+ //# sourceMappingURL=project.js.map
package/dist/ui.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ export interface OutputOptions {
2
+ json?: boolean;
3
+ quiet?: boolean;
4
+ }
5
+ export declare function out(text: string, opts?: OutputOptions): void;
6
+ export declare function json(value: unknown): void;
7
+ export declare function table(rows: Array<Record<string, unknown>>, columns?: string[]): void;
8
+ export declare function ok(msg: string): void;
9
+ export declare function warn(msg: string): void;
10
+ export declare function info(msg: string): void;
11
+ export declare function heading(msg: string): void;
12
+ /** Render an error and return the process exit code. */
13
+ export declare function renderError(e: unknown, asJson: boolean): number;
14
+ export declare function parseIntFlag(name: string): (v: string) => number;
15
+ export declare function collect(value: string, previous?: string[]): string[];
16
+ //# sourceMappingURL=ui.d.ts.map
package/dist/ui.js ADDED
@@ -0,0 +1,67 @@
1
+ import pc from 'picocolors';
2
+ import { errorToJson, isSdodsError } from '@sdods/core';
3
+ export function out(text, opts = {}) {
4
+ if (opts.quiet)
5
+ return;
6
+ process.stdout.write(text.endsWith('\n') ? text : text + '\n');
7
+ }
8
+ export function json(value) {
9
+ process.stdout.write(JSON.stringify(value, null, 2) + '\n');
10
+ }
11
+ export function table(rows, columns) {
12
+ if (rows.length === 0)
13
+ return out(pc.dim('(none)'));
14
+ const cols = columns ?? Object.keys(rows[0]);
15
+ const widths = cols.map((c) => Math.max(c.length, ...rows.map((r) => String(r[c] ?? '').length)));
16
+ const line = (vals) => vals.map((v, i) => v.padEnd(widths[i])).join(' ');
17
+ out(pc.bold(line(cols)));
18
+ out(pc.dim(line(widths.map((w) => '─'.repeat(w)))));
19
+ for (const r of rows)
20
+ out(line(cols.map((c) => String(r[c] ?? ''))));
21
+ }
22
+ export function ok(msg) {
23
+ out(`${pc.green('✔')} ${msg}`);
24
+ }
25
+ export function warn(msg) {
26
+ process.stderr.write(`${pc.yellow('⚠')} ${msg}\n`);
27
+ }
28
+ export function info(msg) {
29
+ out(`${pc.cyan('ℹ')} ${msg}`);
30
+ }
31
+ export function heading(msg) {
32
+ out(`\n${pc.bold(pc.underline(msg))}`);
33
+ }
34
+ /** Render an error and return the process exit code. */
35
+ export function renderError(e, asJson) {
36
+ const body = errorToJson(e);
37
+ if (asJson) {
38
+ process.stderr.write(JSON.stringify({ error: body }) + '\n');
39
+ }
40
+ else {
41
+ process.stderr.write(`${pc.red('✖')} ${pc.bold(body.code)}: ${body.message}\n`);
42
+ if (body.hint)
43
+ process.stderr.write(` ${pc.dim('hint:')} ${body.hint}\n`);
44
+ if (body.docsUrl)
45
+ process.stderr.write(` ${pc.dim('docs:')} ${body.docsUrl}\n`);
46
+ if (process.env.SDODS_DEBUG && e instanceof Error && e.stack)
47
+ process.stderr.write(pc.dim(e.stack) + '\n');
48
+ }
49
+ if (isSdodsError(e))
50
+ return e.exitCode;
51
+ return 1;
52
+ }
53
+ export function parseIntFlag(name) {
54
+ return (v) => {
55
+ const n = Number(v);
56
+ if (!Number.isInteger(n) || n < 0)
57
+ throw new Error(`--${name} expects a non-negative integer, got "${v}"`);
58
+ return n;
59
+ };
60
+ }
61
+ export function collect(value, previous = []) {
62
+ return previous.concat(value
63
+ .split(',')
64
+ .map((s) => s.trim())
65
+ .filter(Boolean));
66
+ }
67
+ //# sourceMappingURL=ui.js.map
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@sdods/cli",
3
+ "version": "0.2.0",
4
+ "description": "SDODS command line: run, record, analyze, agents, MCP server, scheduler and the web server.",
5
+ "license": "Apache-2.0",
6
+ "author": "SDODS <admin@sdods.com>",
7
+ "type": "module",
8
+ "bin": {
9
+ "sdods": "./bin/sdods.js"
10
+ },
11
+ "exports": {
12
+ ".": "./dist/index.js"
13
+ },
14
+ "publishConfig": {
15
+ "access": "public"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "bin",
20
+ "templates"
21
+ ],
22
+ "engines": {
23
+ "node": ">=22"
24
+ },
25
+ "dependencies": {
26
+ "@sdods/contracts": "0.2.0",
27
+ "@sdods/core": "0.2.0",
28
+ "commander": "^15.0.0",
29
+ "execa": "^10.0.1",
30
+ "picocolors": "^1.1.1",
31
+ "semver": "^7.8.5",
32
+ "yaml": "^2.9.0",
33
+ "zod": "^4.5.4",
34
+ "@playwright/test": "^1.62.1",
35
+ "playwright-core": "^1.62.1",
36
+ "@sdods/integrations": "0.2.0",
37
+ "@sdods/mcp": "0.2.0",
38
+ "@sdods/agents": "0.2.0",
39
+ "@sdods/db": "0.2.0",
40
+ "csv-parse": "^7.0.2",
41
+ "@sdods/server": "0.2.0"
42
+ },
43
+ "homepage": "https://sdods.com",
44
+ "bugs": {
45
+ "url": "https://github.com/siri1410/SDODS/issues"
46
+ },
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/siri1410/SDODS.git",
50
+ "directory": "packages/cli"
51
+ },
52
+ "keywords": [
53
+ "sdods",
54
+ "automation",
55
+ "orchestration",
56
+ "testing",
57
+ "bdd",
58
+ "ai-agents",
59
+ "mcp"
60
+ ]
61
+ }
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: sdods-record
3
+ description: Record a browser flow with SDODS for a given project, environment, user role and device, save it as a runnable spec, replay it, and optionally convert it into a Gherkin feature with reusable steps. Use when the user says "record", "codegen", "capture a flow", "re-record as <role>", or wants the same flow recorded across environments or users.
4
+ ---
5
+
6
+ # SDODS record & playback skill
7
+
8
+ Re-runnable recipe. Ask for the parameters you do not have, then follow the steps. Never edit `main` directly; recordings land in `projects/<project>/recorded/` and conversions become proposals.
9
+
10
+ ## Parameters
11
+
12
+ | Name | Required | Example | Notes |
13
+ |---|---|---|---|
14
+ | `project` | yes | `demo-shop` | `bun run sdods project list` shows slugs |
15
+ | `env` | yes | `staging`, `local` | `bun run sdods env list -p <project>` |
16
+ | `name` | yes | `checkout-standard` | becomes `recorded/<name>.spec.ts`; include the role in the name |
17
+ | `role` | no | `standard`, `problem`, `admin` | leases a pool user of that role and starts logged in (storageState) |
18
+ | `url` | no | `/inventory.html` | route path or absolute URL to open first |
19
+ | `device` | no | `"iPhone 15"`, `"Pixel 7"` | device name for mobile emulation |
20
+ | `browser` | no | `chromium` (default), `firefox`, `webkit` | |
21
+ | `har` | no | `true` | also capture network into `har/<env>/<name>.har` for offline replay |
22
+
23
+ ## Steps
24
+
25
+ 1. **Check the environment** (fail fast on missing vars):
26
+ ```bash
27
+ bun run sdods doctor -p <project>
28
+ bun run sdods env list -p <project>
29
+ ```
30
+ 2. **Make sure the role has login state** (skip when no role):
31
+ ```bash
32
+ bun run sdods auth list -p <project> -e <env>
33
+ bun run sdods auth capture -p <project> -e <env> --user <role> # only if missing or expired
34
+ ```
35
+ 3. **Record** (opens the recorder; the person performs the flow and closes the window):
36
+ ```bash
37
+ bun run sdods record -p <project> -e <env> --name <name> [--user <role>] [--url <url>] [--device "<device>"] [--browser <browser>] [--save-har]
38
+ ```
39
+ Output: `projects/<project>/recorded/<name>.spec.ts`, post-processed (SDODS fixtures, routes instead of absolute URLs, tags `@recorded @ui @regression`, header comment with project/env/role/device).
40
+ 4. **Play it back** on the same environment, then on another one:
41
+ ```bash
42
+ bun run sdods run -p <project> -e <env> -l recorded --grep "<name>"
43
+ bun run sdods run -p <project> -e <other-env> -l recorded --grep "<name>"
44
+ ```
45
+ Look at `.sdods/runs/<runId>/` for screenshots and the HTML report (`bun run sdods report --last --open`).
46
+ 5. **Repeat for other roles, environments or devices** by changing only the parameters. Use distinct names (`checkout-standard`, `checkout-problem`, `checkout-standard-iphone`).
47
+ 6. **Convert to Gherkin** when the flow is stable (agent proposal, reviewed by a person):
48
+ ```bash
49
+ bun run sdods record convert projects/<project>/recorded/<name>.spec.ts
50
+ bun run sdods proposals list
51
+ bun run sdods proposals show <id>
52
+ bun run sdods proposals accept <id> --branch sdods/<id> # after review
53
+ bun run sdods lint -p <project>
54
+ ```
55
+ The proposal reuses existing steps first (`bun run sdods steps list -p <project>`), adds only unmatched steps, and creates a page object with heal-aware locators.
56
+ 7. **Offline replay** (CI-friendly) when HAR was captured:
57
+ ```bash
58
+ bun run sdods har replay -p <project> -e <env> --strict -t "@har:<name>"
59
+ ```
60
+
61
+ ## Matrix template
62
+
63
+ Ask the user which cells to record, then run step 3 per cell:
64
+
65
+ | role \ env | local | staging |
66
+ |---|---|---|
67
+ | standard | `--user standard -e local` | `--user standard -e staging` |
68
+ | problem | `--user problem -e local` | `--user problem -e staging` |
69
+ | admin (mobile) | `--user admin -e local --device "iPhone 15"` | `--user admin -e staging --device "iPhone 15"` |
70
+
71
+ ## Done when
72
+
73
+ - Each recording runs green with `-l recorded` on its environment.
74
+ - Names, roles and envs are visible in `sdods features list -p <project> --json` or `recorded/` file headers.
75
+ - Converted features pass `sdods lint` and are tagged with one layer tag and one suite tag.
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: sdods-run
3
+ description: Run SDODS test suites for a project across environments, layers (ui/api/hybrid/recorded), browsers, suites (@smoke/@regression) and user roles, then read the results. Use when the user says "run the tests", "smoke on staging", "regression on all browsers", "run as <role>", or asks what failed.
4
+ ---
5
+
6
+ # SDODS run skill
7
+
8
+ ## Parameters
9
+
10
+ | Name | Default | Notes |
11
+ |---|---|---|
12
+ | `project` | only project if one exists | `bun run sdods project list` |
13
+ | `env` | project `envs.default` | `bun run sdods env list -p <project>` |
14
+ | `layer` | all layers in the project | `-l ui`, `-l api`, `-l hybrid`, `-l recorded` (repeatable) |
15
+ | `browser` | project `browsers` | `-b chromium -b firefox -b webkit`, or `--project-matrix` for all |
16
+ | `tags` | none | Cucumber expression, e.g. `"@smoke and not @mock"`, `@user:admin`, `@jira:DEMO-12` |
17
+ | `headed` | false | `--headed` for a visible browser |
18
+ | `workers` | runner default | `-w 2` |
19
+ | `shard` | none | `--shard 1/3` |
20
+ | `har` | off | `--har-replay --strict` for offline runs, `--har-update` to refresh |
21
+
22
+ ## Steps
23
+
24
+ 1. Validate before running (cheap, catches config and tag mistakes):
25
+ ```bash
26
+ bun run sdods config validate
27
+ bun run sdods lint -p <project>
28
+ bun run sdods run -p <project> -e <env> --list # shows the run targets that would run
29
+ ```
30
+ 2. Run the requested slice. Typical combinations:
31
+ ```bash
32
+ bun run sdods run -p <project> -e <env> -l api # API only, no browser
33
+ bun run sdods run -p <project> -e <env> -l ui -b chromium -t @smoke # quick UI smoke
34
+ bun run sdods run -p <project> -e <env> -t @regression --project-matrix # every browser in the yaml
35
+ bun run sdods run -p <project> -e <env> -t "@user:admin" # scenarios that lease admin users
36
+ bun run sdods run -p <project> -e <env> -l hybrid -w 2 # API→UI chaining
37
+ bun run sdods run -p <project> -e <env> --har-replay --strict -t @smoke # offline, CI-style
38
+ ```
39
+ 3. Read results:
40
+ ```bash
41
+ bun run sdods report --last # summary: totals, failed, flaky, report paths
42
+ bun run sdods report --last --open # HTML report + dashboard
43
+ bun run sdods heal report --last # locators that were healed
44
+ ```
45
+ Artifacts: `.sdods/runs/<runId>/<project>/<fingerprint>/r<retry>/` holds `scenario-start.png`, `NN-before.png`/`NN-after.png` per UI step (policy by suite tag), `api/*.json` per API call, `heal.jsonl`.
46
+ 4. If something failed, collect: the scenario name, the error from `summary.json`, the `failure.png`, and the trace (`bun run sdods trace --last`). Then either fix the feature/page object or hand it to the healer agent:
47
+ ```bash
48
+ bun run sdods agent heal -p <project> --scenario <fingerprint> --dry-run
49
+ ```
50
+ 5. Repeat for other environments or roles by changing `-e` / `-t "@user:<role>"` only.
51
+
52
+ ## Exit codes
53
+
54
+ `0` all passed · `1` test failures · `2` configuration or usage error · `3` lint errors · `130` cancelled.
@@ -0,0 +1,62 @@
1
+ # ── SDODS: tokens and keys ─────────────────────────────────────────────────
2
+ # Nothing here is mandatory for the platform itself. SDODS API tokens are
3
+ # self-issued and free (`sdods tokens create`). Check status: `sdods doctor`.
4
+ #
5
+ # mandatory only when the feature next to it is used
6
+ # one-of agents need ANY ONE of the four options in the Agents block
7
+ # optional turns on an integration
8
+ # ci-only GitHub Actions secrets, never needed locally
9
+
10
+ # ── Database (mandatory: none; DATABASE_URL only when DB_DRIVER=postgres) ─────
11
+ DB_DRIVER=sqlite
12
+ SQLITE_PATH=.sdods/sdods.db
13
+ # DATABASE_URL=postgres://sdods:sdods@localhost:5432/sdods
14
+
15
+ # ── Web server (`sdods serve`) — SESSION_SECRET mandatory for serve only ────
16
+ PORT=4444
17
+ HOST=127.0.0.1
18
+ SESSION_SECRET=change-me-32-chars-minimum-please # openssl rand -hex 32
19
+ # SDODS_PUBLIC_URL=https://sdods.example.com
20
+ # AUTH_DISABLED=1 # local dev only
21
+
22
+ # Where projects and run artifacts live (defaults shown)
23
+ # SDODS_PROJECTS_DIR=projects
24
+ # SDODS_ARTIFACTS_DIR=.sdods/runs
25
+
26
+ # ── Agents (one-of; every agent command also has --dry-run and --adapter fake) ─
27
+ # Pick ONE way for agents to reach a model. Auto-detection order when unset:
28
+ # ANTHROPIC_API_KEY → `claude` CLI on PATH → `codex` CLI on PATH → OPENAI_API_KEY
29
+ # → a local Ollama → fake
30
+ # SDODS_LLM_PROVIDER=claude-code # claude | claude-code | codex | openai-compatible | ollama | fake
31
+ #
32
+ # 1) Claude via API key (billed by Anthropic)
33
+ # ANTHROPIC_API_KEY=
34
+ #
35
+ # 2) Claude Code CLI login — no key needed: `npm i -g @anthropic-ai/claude-code && claude login`
36
+ # (adapter claude-code; optional SDODS_CLAUDE_CODE_MODEL=... to pin a model)
37
+ #
38
+ # 3) Codex CLI login — no key needed: `npm i -g @openai/codex && codex login`
39
+ # (adapter codex; optional SDODS_CODEX_MODEL=... to pin a model)
40
+ #
41
+ # 4) Any OpenAI-compatible endpoint (billed by the provider)
42
+ # OPENAI_API_KEY=
43
+ # OPENAI_BASE_URL= # optional: Azure, vLLM, LM Studio, llama.cpp
44
+ #
45
+ # 5) A model on this machine — no key, no per-token cost (adapter ollama)
46
+ # `ollama serve && ollama pull qwen2.5-coder:7b`
47
+ # OLLAMA_HOST=http://127.0.0.1:11434
48
+ # OLLAMA_MODEL=qwen2.5-coder:7b
49
+ # OLLAMA_CONTEXT_LENGTH=16384 # Ollama defaults to 4096 and truncates longer prompts
50
+
51
+ # ── Integrations (optional; project yaml stores only the variable NAMES) ──────
52
+ # GITHUB_TOKEN= # check runs, PR comments, issues
53
+ # JIRA_EMAIL= # Jira Cloud basic auth
54
+ # JIRA_API_TOKEN=
55
+
56
+ # ── SDODS's own token (optional, free): MCP over HTTP and CI ingest ─────────
57
+ # SDODS_SERVER_URL=http://127.0.0.1:4444
58
+ # SDODS_TOKEN=amx_... # sdods tokens create --user <name> --scopes runs:ingest,runs:read
59
+
60
+ # ── CI-only GitHub secrets (never needed locally) ────────────────────────────
61
+ # FIREBASE_SERVICE_ACCOUNT_AUTOMAX_DOCS docs deploy (Firebase Hosting)
62
+ # NPM_TOKEN publishing @sdods/* packages
@@ -0,0 +1,40 @@
1
+ services:
2
+ postgres:
3
+ image: postgres:16
4
+ container_name: sdods-postgres
5
+ environment:
6
+ POSTGRES_USER: sdods
7
+ POSTGRES_PASSWORD: sdods
8
+ POSTGRES_DB: sdods
9
+ ports:
10
+ - '5432:5432'
11
+ volumes:
12
+ - sdods-pgdata:/var/lib/postgresql/data
13
+ healthcheck:
14
+ test: ['CMD-SHELL', 'pg_isready -U sdods -d sdods']
15
+ interval: 5s
16
+ timeout: 3s
17
+ retries: 20
18
+
19
+ # Optional: the SDODS web server on Postgres. Build with `docker build -t sdods .` first.
20
+ sdods:
21
+ image: sdods:latest
22
+ profiles: ['server']
23
+ depends_on:
24
+ postgres:
25
+ condition: service_healthy
26
+ environment:
27
+ DB_DRIVER: postgres
28
+ DATABASE_URL: postgres://sdods:sdods@postgres:5432/sdods
29
+ HOST: 0.0.0.0
30
+ PORT: 4444
31
+ SESSION_SECRET: change-me-32-chars-minimum-please
32
+ ports:
33
+ - '4444:4444'
34
+ volumes:
35
+ - ./projects:/app/projects
36
+ - sdods-runs:/app/.sdods
37
+
38
+ volumes:
39
+ sdods-pgdata:
40
+ sdods-runs:
@@ -0,0 +1,4 @@
1
+ # Copy to .env.staging / .env.local next to this file (both are gitignored).
2
+ DEMO_SHOP_PASSWORD=secret_sauce
3
+ # API_TOKEN=
4
+ # SDODS_TESTDATA_DB_URL=file:./projects/demo-shop/data/staging/testdata.sqlite
@@ -0,0 +1,11 @@
1
+ # Rows for the "posts" dataset (JSONPlaceholder ids and their owners).
2
+ rows:
3
+ - id: 1
4
+ userId: 1
5
+ title: sunt aut facere repellat provident occaecati excepturi optio reprehenderit
6
+ - id: 11
7
+ userId: 2
8
+ title: et ea vero quia laudantium autem
9
+ - id: 21
10
+ userId: 3
11
+ title: asperiores ea ipsam voluptatibus modi minima quia sint
@@ -0,0 +1,8 @@
1
+ [
2
+ { "id": 4, "name": "Sauce Labs Backpack", "price": 29.99 },
3
+ { "id": 0, "name": "Sauce Labs Bike Light", "price": 9.99 },
4
+ { "id": 1, "name": "Sauce Labs Bolt T-Shirt", "price": 15.99 },
5
+ { "id": 5, "name": "Sauce Labs Fleece Jacket", "price": 49.99 },
6
+ { "id": 2, "name": "Sauce Labs Onesie", "price": 7.99 },
7
+ { "id": 3, "name": "Test.allTheThings() T-Shirt (Red)", "price": 15.99 }
8
+ ]