@dokebi/cli 0.2.5 → 0.4.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 (81) hide show
  1. package/AI.md +181 -87
  2. package/README.md +55 -24
  3. package/catalog.json +2199 -0
  4. package/dist/app-urls.d.ts +21 -0
  5. package/dist/app-urls.js +94 -0
  6. package/dist/banner-art.d.ts +4 -0
  7. package/dist/banner-art.js +31 -0
  8. package/dist/banner.d.ts +25 -0
  9. package/dist/banner.js +202 -0
  10. package/dist/bin.d.ts +2 -1
  11. package/dist/bin.js +475 -46
  12. package/dist/catalog.d.ts +161 -0
  13. package/dist/catalog.js +110 -0
  14. package/dist/check/form-rules.d.ts +13 -0
  15. package/dist/check/form-rules.js +224 -0
  16. package/dist/check/index.d.ts +23 -0
  17. package/dist/check/index.js +70 -0
  18. package/dist/check/packages.d.ts +17 -0
  19. package/dist/check/packages.js +161 -0
  20. package/dist/check/problems.d.ts +19 -0
  21. package/dist/check/problems.js +22 -0
  22. package/dist/check/project-rules.d.ts +10 -0
  23. package/dist/check/project-rules.js +142 -0
  24. package/dist/check/source-rules.d.ts +28 -0
  25. package/dist/check/source-rules.js +273 -0
  26. package/dist/check/typescript.d.ts +46 -0
  27. package/dist/check/typescript.js +168 -0
  28. package/dist/commands.d.ts +18 -0
  29. package/dist/commands.js +372 -0
  30. package/dist/config-edit.d.ts +5 -4
  31. package/dist/config-edit.js +9 -8
  32. package/dist/configuration.d.ts +1 -0
  33. package/dist/configuration.js +2 -1
  34. package/dist/dev-infra.d.ts +62 -0
  35. package/dist/dev-infra.js +215 -0
  36. package/dist/env-file.d.ts +24 -0
  37. package/dist/env-file.js +100 -0
  38. package/dist/generator.d.ts +24 -1
  39. package/dist/generator.js +57 -47
  40. package/dist/guide.d.ts +29 -0
  41. package/dist/guide.js +90 -0
  42. package/dist/install-plan.d.ts +8 -0
  43. package/dist/install-plan.js +21 -4
  44. package/dist/layout.d.ts +5 -4
  45. package/dist/layout.js +8 -7
  46. package/dist/log-writer-control.d.ts +55 -0
  47. package/dist/log-writer-control.js +327 -0
  48. package/dist/log-writer.js +53 -12
  49. package/dist/manifest.d.ts +28 -0
  50. package/dist/manifest.js +77 -2
  51. package/dist/npm-client.d.ts +49 -0
  52. package/dist/npm-client.js +162 -41
  53. package/dist/output.d.ts +1 -1
  54. package/dist/output.js +14 -8
  55. package/dist/plugin-lifecycle.d.ts +7 -0
  56. package/dist/plugin-lifecycle.js +21 -3
  57. package/dist/plugins-command.d.ts +47 -0
  58. package/dist/plugins-command.js +189 -0
  59. package/dist/project-guide.d.ts +36 -1
  60. package/dist/project-guide.js +141 -18
  61. package/dist/project-inspector.d.ts +10 -0
  62. package/dist/project-inspector.js +51 -2
  63. package/dist/project-state.d.ts +13 -2
  64. package/dist/project-state.js +8 -6
  65. package/dist/project.d.ts +89 -7
  66. package/dist/project.js +835 -85
  67. package/dist/recipe-record.d.ts +24 -0
  68. package/dist/recipe-record.js +67 -0
  69. package/dist/recipe.d.ts +120 -0
  70. package/dist/recipe.js +793 -0
  71. package/dist/registry.d.ts +79 -0
  72. package/dist/registry.js +212 -0
  73. package/dist/rotating-log.d.ts +12 -0
  74. package/dist/rotating-log.js +19 -0
  75. package/dist/server.d.ts +94 -4
  76. package/dist/server.js +624 -64
  77. package/dist/verify.d.ts +62 -0
  78. package/dist/verify.js +380 -0
  79. package/dist/vite-host-process.d.ts +20 -0
  80. package/dist/vite-host-process.js +117 -0
  81. package/package.json +30 -6
@@ -0,0 +1,24 @@
1
+ export declare const RECIPE_RECORD = ".dokebi/recipe.json";
2
+ export interface RecordedAnswer {
3
+ readonly value: string;
4
+ readonly answeredBy: 'flag' | 'default' | 'recipe';
5
+ readonly defaultReason?: string;
6
+ }
7
+ export interface AppliedRecipe {
8
+ readonly recipe: string;
9
+ readonly appliedAt: string;
10
+ /** `user`: the user answered; `defaults`: the user left the choices to the agent. */
11
+ readonly answersFrom: 'user' | 'defaults';
12
+ readonly answers: Readonly<Record<string, RecordedAnswer>>;
13
+ /** What the user must know about the result (one line each). */
14
+ readonly notices: readonly string[];
15
+ }
16
+ /** The recipes recorded in the project; none when there is no record (or it is unreadable). */
17
+ export declare function readAppliedRecipes(root: string): Promise<AppliedRecipe[]>;
18
+ /** Records an applied recipe, replacing an earlier application of the same recipe. */
19
+ export declare function saveAppliedRecipe(root: string, entry: AppliedRecipe): Promise<void>;
20
+ /**
21
+ * The lines for the user's final message: each answer and who chose it (a default with its
22
+ * reason), then the notices. Answers fixed by the recipe itself are not choices and are left out.
23
+ */
24
+ export declare function tellUserLines(recipes: readonly AppliedRecipe[]): string[];
@@ -0,0 +1,67 @@
1
+ // What a recipe chose, kept for the end of the work (AI-M03): `dokebi recipe` writes
2
+ // `.dokebi/recipe.json` when it applied a recipe, and `dokebi verify` (the last command an agent
3
+ // runs) returns the lines the agent must pass on to the user as `tellUser`: every answer with who
4
+ // gave it (a default with its reason) and the notices, such as no CSRF protection for forms.
5
+ // The file holds choice values and catalog texts only, never secrets.
6
+ import { mkdir, readFile, rename, rm, writeFile } from 'node:fs/promises';
7
+ import { dirname, join } from 'node:path';
8
+ import { debug, isMissing, reportError } from './diagnostics.js';
9
+ export const RECIPE_RECORD = '.dokebi/recipe.json';
10
+ /** The recipes recorded in the project; none when there is no record (or it is unreadable). */
11
+ export async function readAppliedRecipes(root) {
12
+ let text;
13
+ try {
14
+ text = await readFile(join(root, RECIPE_RECORD), 'utf8');
15
+ }
16
+ catch (error) {
17
+ if (!isMissing(error))
18
+ reportError('RECIPE_RECORD_READ_FAILED', `Unable to read ${RECIPE_RECORD}`, error);
19
+ return [];
20
+ }
21
+ try {
22
+ const parsed = JSON.parse(text);
23
+ if (parsed.version === 1 && Array.isArray(parsed.recipes))
24
+ return parsed.recipes.filter((entry) => typeof entry?.recipe === 'string' &&
25
+ entry.answers !== null &&
26
+ typeof entry.answers === 'object' &&
27
+ Array.isArray(entry.notices));
28
+ debug('Recipe record ignored: unexpected content', { file: RECIPE_RECORD });
29
+ }
30
+ catch (error) {
31
+ reportError('RECIPE_RECORD_INVALID', `${RECIPE_RECORD} is not valid JSON; ignoring it`, error);
32
+ }
33
+ return [];
34
+ }
35
+ /** Records an applied recipe, replacing an earlier application of the same recipe. */
36
+ export async function saveAppliedRecipe(root, entry) {
37
+ const file = join(root, RECIPE_RECORD);
38
+ const recipes = [
39
+ ...(await readAppliedRecipes(root)).filter((item) => item.recipe !== entry.recipe),
40
+ entry,
41
+ ];
42
+ const record = { version: 1, recipes };
43
+ await mkdir(dirname(file), { recursive: true });
44
+ await rm(`${file}.tmp`, { force: true });
45
+ await writeFile(`${file}.tmp`, `${JSON.stringify(record, null, 2)}\n`);
46
+ await rename(`${file}.tmp`, file);
47
+ debug('Recipe record written', { recipe: entry.recipe, file: RECIPE_RECORD });
48
+ }
49
+ /**
50
+ * The lines for the user's final message: each answer and who chose it (a default with its
51
+ * reason), then the notices. Answers fixed by the recipe itself are not choices and are left out.
52
+ */
53
+ export function tellUserLines(recipes) {
54
+ const lines = [];
55
+ for (const entry of recipes) {
56
+ const prefix = recipes.length > 1 ? `[${entry.recipe}] ` : '';
57
+ for (const [id, answer] of Object.entries(entry.answers)) {
58
+ if (answer.answeredBy === 'default')
59
+ lines.push(`${prefix}${id} = ${answer.value}: chosen by default (${answer.defaultReason ?? 'the recipe default'})`);
60
+ else if (answer.answeredBy === 'flag')
61
+ lines.push(`${prefix}${id} = ${answer.value}: ${entry.answersFrom === 'user' ? 'your answer' : 'chosen by the AI (you left the choice to it)'}`);
62
+ }
63
+ for (const notice of entry.notices)
64
+ lines.push(`${prefix}${notice}`);
65
+ }
66
+ return lines;
67
+ }
@@ -0,0 +1,120 @@
1
+ import { type CatalogOption } from './catalog.js';
2
+ import { type NextStep, type Project } from './project.js';
3
+ export interface RecipeRequest {
4
+ readonly name: string;
5
+ /** Project folder; created with init when it does not exist yet. Default: the current one. */
6
+ readonly dir?: string;
7
+ /** Answers by question id (view, db, devInfra, admin). */
8
+ readonly answers: Readonly<Record<string, string>>;
9
+ /** Fill unanswered questions with their defaults (only when the user delegated the choice). */
10
+ readonly defaults: boolean;
11
+ /**
12
+ * Where the answers came from (D-0065 §11): `user` — the user answered the questions;
13
+ * `defaults` — the user said to decide (same as `defaults`). Applying needs one of them.
14
+ */
15
+ readonly answersFrom?: 'user' | 'defaults';
16
+ readonly dryRun: boolean;
17
+ readonly port?: number;
18
+ readonly dbPort?: number;
19
+ }
20
+ export interface QuestionOption extends CatalogOption {
21
+ /** Programs of `needs` that were not found on PATH (devInfra docker, AI-M03); the user decides. */
22
+ readonly missingNeeds?: readonly string[];
23
+ /** What was found about this option on this machine, for the user. */
24
+ readonly note?: string;
25
+ }
26
+ export interface Question {
27
+ readonly id: string;
28
+ readonly flag: string;
29
+ readonly question: string;
30
+ readonly options: readonly QuestionOption[];
31
+ readonly default: string;
32
+ readonly defaultReason: string;
33
+ /** Asked only when another answer matches (devInfra: when db is not none). */
34
+ readonly when?: Readonly<Record<string, string>>;
35
+ readonly answer?: Answer;
36
+ }
37
+ export interface Answer {
38
+ readonly value: string;
39
+ readonly answeredBy: 'flag' | 'default' | 'recipe';
40
+ readonly defaultReason?: string;
41
+ }
42
+ export interface PlanStep {
43
+ readonly id: string;
44
+ /** The real command (run it yourself to do the same step). */
45
+ readonly command: string;
46
+ readonly why: string;
47
+ }
48
+ export interface Preflight {
49
+ readonly code: string;
50
+ readonly severity: 'error' | 'info';
51
+ readonly message: string;
52
+ readonly fix?: string;
53
+ /** PORT_IN_USE: a free port to propose to the user (pass it with the flag in `fix`). */
54
+ readonly alternative?: number;
55
+ /**
56
+ * PORT_IN_USE (AI-M03): the port is the user's call. True unless the user left the choices to
57
+ * the agent (--defaults / --answers-from defaults); `question` is what to ask, `next` what to do.
58
+ */
59
+ readonly waitForUser?: boolean;
60
+ readonly question?: string;
61
+ readonly next?: string;
62
+ }
63
+ export interface NeedsAnswers {
64
+ readonly status: 'needs-answers';
65
+ readonly recipe: string;
66
+ readonly summary: string;
67
+ readonly questions: readonly Question[];
68
+ readonly missing: readonly string[];
69
+ /** What to do now (D-0065 §11): ask, end the turn, and only then run the command. */
70
+ readonly next: string;
71
+ /** Always true: the agent's turn ends with the questions; nothing runs before the answers. */
72
+ readonly waitForUser: true;
73
+ }
74
+ /** The first sentence of `next` (and of the guide and AGENTS.md §1), word for word. */
75
+ export declare const ASK_AND_WAIT = "Ask these questions and end your turn; do not run recipe/add/config until the user answers. Only use --defaults if the user said to decide.";
76
+ export interface RecipeOutcome {
77
+ readonly status: 'planned' | 'applied';
78
+ readonly recipe: string;
79
+ readonly root: string;
80
+ readonly answers: Readonly<Record<string, Answer>>;
81
+ /** Where the answers came from (`--answers-from`, `--defaults`); null: not stated (dry run). */
82
+ readonly answersFrom: 'user' | 'defaults' | null;
83
+ /**
84
+ * Tell the user (one line each): the answers and who gave them, defaults with their reasons,
85
+ * and what the app does not protect (no CSRF protection for HTML forms, csrf is absent).
86
+ */
87
+ readonly report: string;
88
+ readonly plan: {
89
+ readonly steps: readonly PlanStep[];
90
+ };
91
+ readonly preflight: readonly Preflight[];
92
+ readonly completedSteps: readonly string[];
93
+ readonly skippedSteps: readonly string[];
94
+ readonly nextSteps: readonly NextStep[];
95
+ /**
96
+ * A dry run whose preflight found a taken port (AI-M03): true when the agent must ask the user
97
+ * before applying (`question`, `next`); false when the user left the choices to the agent.
98
+ */
99
+ readonly waitForUser?: boolean;
100
+ readonly question?: string;
101
+ readonly next?: string;
102
+ }
103
+ /** `dokebi recipe list`. */
104
+ export declare function listRecipes(): Promise<{
105
+ recipes: {
106
+ name: string;
107
+ summary: string;
108
+ questions: string[];
109
+ match: string[];
110
+ }[];
111
+ }>;
112
+ /** The devInfra docker note when `docker` is not on PATH (the default stays; the user decides). */
113
+ export declare const DOCKER_NOT_FOUND = "docker was not found on PATH: install and start Docker before applying with docker, or choose url (a database server you already have)";
114
+ /**
115
+ * Runs a command in the project folder; a failure becomes STEP_COMMAND_FAILED with the bounded,
116
+ * redacted end of its output (`OutputTail` of npm-client).
117
+ */
118
+ export declare function shell(root: string, command: string, args: string[], timeoutMs: number): Promise<void>;
119
+ /** `dokebi recipe <name> …`. */
120
+ export declare function runRecipe(request: RecipeRequest, project: Project): Promise<NeedsAnswers | RecipeOutcome>;