@praxis-framework/cli 0.1.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 (53) hide show
  1. package/README.md +51 -0
  2. package/dist/app.js +122 -0
  3. package/dist/app.js.map +1 -0
  4. package/dist/commands/init-config.js +85 -0
  5. package/dist/commands/init-config.js.map +1 -0
  6. package/dist/commands/init.js +59 -0
  7. package/dist/commands/init.js.map +1 -0
  8. package/dist/commands/log.js +171 -0
  9. package/dist/commands/log.js.map +1 -0
  10. package/dist/flows/capabilities.js +14 -0
  11. package/dist/flows/capabilities.js.map +1 -0
  12. package/dist/flows/inhibitions.js +14 -0
  13. package/dist/flows/inhibitions.js.map +1 -0
  14. package/dist/flows/initial-verbs.js +82 -0
  15. package/dist/flows/initial-verbs.js.map +1 -0
  16. package/dist/flows/organisation.js +112 -0
  17. package/dist/flows/organisation.js.map +1 -0
  18. package/dist/flows/path-choice.js +22 -0
  19. package/dist/flows/path-choice.js.map +1 -0
  20. package/dist/flows/review.js +31 -0
  21. package/dist/flows/review.js.map +1 -0
  22. package/dist/flows/role-definition.js +77 -0
  23. package/dist/flows/role-definition.js.map +1 -0
  24. package/dist/flows/tools.js +63 -0
  25. package/dist/flows/tools.js.map +1 -0
  26. package/dist/flows/voice.js +146 -0
  27. package/dist/flows/voice.js.map +1 -0
  28. package/dist/flows/welcome.js +17 -0
  29. package/dist/flows/welcome.js.map +1 -0
  30. package/dist/flows/wrote.js +51 -0
  31. package/dist/flows/wrote.js.map +1 -0
  32. package/dist/index.js +57 -0
  33. package/dist/index.js.map +1 -0
  34. package/dist/lib/catalog.js +190 -0
  35. package/dist/lib/catalog.js.map +1 -0
  36. package/dist/lib/seed-adapter.js +28 -0
  37. package/dist/lib/seed-adapter.js.map +1 -0
  38. package/dist/lib/traits.js +13 -0
  39. package/dist/lib/traits.js.map +1 -0
  40. package/dist/state/form.js +63 -0
  41. package/dist/state/form.js.map +1 -0
  42. package/dist/state/steps.js +28 -0
  43. package/dist/state/steps.js.map +1 -0
  44. package/dist/ui/header.js +14 -0
  45. package/dist/ui/header.js.map +1 -0
  46. package/dist/ui/list-builder-state.js +179 -0
  47. package/dist/ui/list-builder-state.js.map +1 -0
  48. package/dist/ui/list-builder.js +144 -0
  49. package/dist/ui/list-builder.js.map +1 -0
  50. package/dist/ui/theme.js +8 -0
  51. package/dist/ui/theme.js.map +1 -0
  52. package/examples/sample-role.json +24 -0
  53. package/package.json +66 -0
package/dist/index.js ADDED
@@ -0,0 +1,57 @@
1
+ #!/usr/bin/env node
2
+ import { program } from 'commander';
3
+ import { initCommand } from './commands/init.js';
4
+ import { LogError, runLog } from './commands/log.js';
5
+ program
6
+ .name('praxis')
7
+ .description('praxis-framework CLI')
8
+ .version('0.0.1');
9
+ program
10
+ .command('init')
11
+ .description('set up a new praxis role')
12
+ .option('--path <path>', 'directory to scaffold the role into', process.cwd())
13
+ .option('--config <file>', 'seed non-interactively from a JSON file matching SeedInput (see cli/examples/sample-role.json)')
14
+ .option('--overwrite', 'overwrite existing files at the target (only with --config)', false)
15
+ .addHelpText('after', `
16
+ Examples:
17
+ $ praxis init # interactive wizard
18
+ $ praxis init --config ./role.json --path ./my-role # non-interactive seed
19
+ $ praxis init --config ./role.json --overwrite # re-seed an existing dir
20
+
21
+ A sample config lives at cli/examples/sample-role.json — copy and edit it
22
+ as a starting template for the non-interactive flow.
23
+ `)
24
+ .action(initCommand);
25
+ program
26
+ .command('log')
27
+ .description("append a JSONL entry to today's log")
28
+ .option('--campaign <id>', 'campaign id (logs land under campaigns/{id}/logs/)')
29
+ .option('--agent <name>', 'agent / verb name responsible for the action')
30
+ .option('--action <verb>', 'action verb, e.g. email_drafted, decision')
31
+ .option('--prospect <id>', 'prospect id (optional conventional extra)')
32
+ .option('--details <text>', 'short narrative (optional)')
33
+ .option('--subject <text>', 'email/message subject (optional)')
34
+ .option('--echo', 'print the JSON line that was written', false)
35
+ .argument('[extras...]', 'extra key=value pairs to merge into the entry')
36
+ .addHelpText('after', `
37
+ Examples:
38
+ $ praxis log --campaign=q1-outreach --agent=draft-emails --action=email_drafted
39
+ $ praxis log --campaign=manual-leads --agent=monitor-channels --action=channel_intake \\
40
+ channel=notifications-searchai message_ts=1234.5
41
+ `)
42
+ .action(async (extras, options) => {
43
+ try {
44
+ await runLog(options, extras);
45
+ }
46
+ catch (err) {
47
+ if (err instanceof LogError) {
48
+ process.stderr.write(`${err.message}\n`);
49
+ process.exit(1);
50
+ }
51
+ const message = err instanceof Error ? err.message : String(err);
52
+ process.stderr.write(`praxis log: ${message}\n`);
53
+ process.exit(1);
54
+ }
55
+ });
56
+ program.parse();
57
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAC;AACjD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAA0B,MAAM,mBAAmB,CAAC;AAE7E,OAAO;KACJ,IAAI,CAAC,QAAQ,CAAC;KACd,WAAW,CAAC,sBAAsB,CAAC;KACnC,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,MAAM,CAAC;KACf,WAAW,CAAC,0BAA0B,CAAC;KACvC,MAAM,CAAC,eAAe,EAAE,qCAAqC,EAAE,OAAO,CAAC,GAAG,EAAE,CAAC;KAC7E,MAAM,CACL,iBAAiB,EACjB,gGAAgG,CACjG;KACA,MAAM,CAAC,aAAa,EAAE,6DAA6D,EAAE,KAAK,CAAC;KAC3F,WAAW,CACV,OAAO,EACP;;;;;;;;CAQH,CACE;KACA,MAAM,CAAC,WAAW,CAAC,CAAC;AAEvB,OAAO;KACJ,OAAO,CAAC,KAAK,CAAC;KACd,WAAW,CAAC,qCAAqC,CAAC;KAClD,MAAM,CAAC,iBAAiB,EAAE,oDAAoD,CAAC;KAC/E,MAAM,CAAC,gBAAgB,EAAE,8CAA8C,CAAC;KACxE,MAAM,CAAC,iBAAiB,EAAE,2CAA2C,CAAC;KACtE,MAAM,CAAC,iBAAiB,EAAE,2CAA2C,CAAC;KACtE,MAAM,CAAC,kBAAkB,EAAE,4BAA4B,CAAC;KACxD,MAAM,CAAC,kBAAkB,EAAE,kCAAkC,CAAC;KAC9D,MAAM,CAAC,QAAQ,EAAE,sCAAsC,EAAE,KAAK,CAAC;KAC/D,QAAQ,CAAC,aAAa,EAAE,+CAA+C,CAAC;KACxE,WAAW,CACV,OAAO,EACP;;;;;CAKH,CACE;KACA,MAAM,CAAC,KAAK,EAAE,MAAgB,EAAE,OAA0B,EAAE,EAAE;IAC7D,IAAI,CAAC;QACH,MAAM,MAAM,CAAC,OAAO,EAAE,MAAM,CAAC,CAAC;IAChC,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,IAAI,GAAG,YAAY,QAAQ,EAAE,CAAC;YAC5B,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,GAAG,CAAC,OAAO,IAAI,CAAC,CAAC;YACzC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;QACjE,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,eAAe,OAAO,IAAI,CAAC,CAAC;QACjD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
@@ -0,0 +1,190 @@
1
+ import fs from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { resolveTemplatePath } from '@praxis-framework/seed';
4
+ import { z } from 'zod';
5
+ /**
6
+ * Framework-level catalog of tool capabilities that roles can opt into.
7
+ *
8
+ * Roles declare which capabilities they need per-agent; the runtime maps
9
+ * capabilities to concrete adapters at startup. The CLI's `praxis init`
10
+ * wizard surfaces the optional ones for operator selection.
11
+ */
12
+ export const CapabilitySchema = z
13
+ .object({
14
+ description: z.string(),
15
+ transport_options: z.array(z.string()).min(1),
16
+ default_transport: z.string(),
17
+ always_available: z.boolean().optional().default(false),
18
+ default_auth_env: z.string().nullable().optional(),
19
+ docker_image: z.string().optional(),
20
+ })
21
+ // Permissive on unknown fields — schema may grow without breaking older CLIs.
22
+ .passthrough();
23
+ /**
24
+ * Resolve `<template-root>/lib/tools.yaml` by delegating to the seed package's
25
+ * template resolver. The seed package owns template-path resolution: it
26
+ * checks `<package-root>/template/` first (the published-mode layout, where
27
+ * the template ships inside the seed tarball), then walks up looking for a
28
+ * sibling `template/` directory (the dev-monorepo layout). Using its
29
+ * resolver here keeps both packages in lockstep on where the template lives.
30
+ */
31
+ function catalogPath() {
32
+ return path.join(resolveTemplatePath(), 'lib', 'tools.yaml');
33
+ }
34
+ /**
35
+ * Load the framework catalog from `template/lib/tools.yaml`. Throws if the
36
+ * file is missing or unparseable — the CLI cannot function without it.
37
+ */
38
+ export async function loadCatalog() {
39
+ const CATALOG_PATH = catalogPath();
40
+ let text;
41
+ try {
42
+ text = await fs.readFile(CATALOG_PATH, 'utf-8');
43
+ }
44
+ catch (e) {
45
+ const cause = e instanceof Error ? e.message : String(e);
46
+ throw new Error(`praxis catalog not found at ${CATALOG_PATH}: ${cause}`);
47
+ }
48
+ return parseCatalog(text);
49
+ }
50
+ /** Parse a catalog YAML payload. Exported for tests. */
51
+ export function parseCatalog(text) {
52
+ const raw = parseToolsYaml(text);
53
+ const capabilities = [];
54
+ for (const [name, fields] of Object.entries(raw)) {
55
+ const result = CapabilitySchema.safeParse(fields);
56
+ if (!result.success) {
57
+ throw new Error(`praxis catalog: invalid entry "${name}" — ${result.error.issues
58
+ .map((i) => `${i.path.join('.') || '<root>'}: ${i.message}`)
59
+ .join('; ')}`);
60
+ }
61
+ const cap = {
62
+ name,
63
+ description: result.data.description,
64
+ transport_options: result.data.transport_options,
65
+ default_transport: result.data.default_transport,
66
+ always_available: result.data.always_available,
67
+ };
68
+ if (result.data.default_auth_env !== undefined) {
69
+ cap.default_auth_env = result.data.default_auth_env;
70
+ }
71
+ if (result.data.docker_image !== undefined) {
72
+ cap.docker_image = result.data.docker_image;
73
+ }
74
+ capabilities.push(cap);
75
+ }
76
+ if (capabilities.length === 0) {
77
+ throw new Error('praxis catalog: no capabilities defined in tools.yaml');
78
+ }
79
+ return {
80
+ capabilities,
81
+ builtins: () => capabilities.filter((c) => c.always_available),
82
+ optional: () => capabilities.filter((c) => !c.always_available),
83
+ };
84
+ }
85
+ export function parseToolsYaml(text) {
86
+ const lines = text.split('\n');
87
+ const out = {};
88
+ let i = 0;
89
+ let inCapabilities = false;
90
+ let currentCap = null;
91
+ let currentCapIndent = 0;
92
+ let fieldIndent = null;
93
+ while (i < lines.length) {
94
+ const raw = lines[i] ?? '';
95
+ const trimmed = raw.trim();
96
+ // Skip blanks and comments at any depth.
97
+ if (trimmed.length === 0 || trimmed.startsWith('#')) {
98
+ i += 1;
99
+ continue;
100
+ }
101
+ if (!inCapabilities) {
102
+ if (/^capabilities\s*:\s*$/.test(trimmed)) {
103
+ inCapabilities = true;
104
+ }
105
+ i += 1;
106
+ continue;
107
+ }
108
+ const leading = raw.length - raw.trimStart().length;
109
+ // A new top-level key (zero indent) ends the capabilities block.
110
+ if (leading === 0 && /:\s*$/.test(trimmed)) {
111
+ break;
112
+ }
113
+ // Capability header: `<name>:` at the first indent level under
114
+ // `capabilities:`. The header line has the form `<indent><name>:` (line
115
+ // ends with `:` and nothing after) and the indent is the same as (or
116
+ // first establishes) the capability indent. The name itself may contain
117
+ // colons — `mcp:slack:` is `mcp:slack` followed by the trailing `:`.
118
+ const isHeaderLine = trimmed.endsWith(':') && !trimmed.startsWith('-');
119
+ if (isHeaderLine && (currentCap === null || leading <= currentCapIndent)) {
120
+ const name = trimmed.slice(0, -1).trim();
121
+ if (name.length > 0) {
122
+ currentCap = name;
123
+ currentCapIndent = leading;
124
+ fieldIndent = null;
125
+ out[name] = {};
126
+ i += 1;
127
+ continue;
128
+ }
129
+ }
130
+ // Field under the current capability.
131
+ if (currentCap !== null && leading > currentCapIndent) {
132
+ if (fieldIndent === null)
133
+ fieldIndent = leading;
134
+ const fieldMatch = /^\s*([A-Za-z_][A-Za-z0-9_]*)\s*:\s*(.*)$/.exec(raw);
135
+ if (fieldMatch) {
136
+ const key = (fieldMatch[1] ?? '').trim();
137
+ const rest = (fieldMatch[2] ?? '').trim();
138
+ out[currentCap][key] = parseScalar(rest);
139
+ }
140
+ i += 1;
141
+ continue;
142
+ }
143
+ i += 1;
144
+ }
145
+ return out;
146
+ }
147
+ /**
148
+ * Parse a YAML scalar / inline-list value. Handles:
149
+ * - empty → null
150
+ * - `null` / `~` → null
151
+ * - `true` / `false` → boolean
152
+ * - `[a, b, c]` → string[]
153
+ * - quoted "string" → string (quotes stripped)
154
+ * - bare string → string
155
+ * - integer literal → number
156
+ */
157
+ function parseScalar(raw) {
158
+ const value = raw.trim();
159
+ if (value.length === 0)
160
+ return null;
161
+ if (value === 'null' || value === '~')
162
+ return null;
163
+ if (value === 'true')
164
+ return true;
165
+ if (value === 'false')
166
+ return false;
167
+ if (value.startsWith('[') && value.endsWith(']')) {
168
+ const inner = value.slice(1, -1).trim();
169
+ if (inner.length === 0)
170
+ return [];
171
+ return inner.split(',').map((part) => stripQuotes(part.trim()));
172
+ }
173
+ if (/^-?\d+$/.test(value)) {
174
+ const n = Number.parseInt(value, 10);
175
+ if (Number.isFinite(n))
176
+ return n;
177
+ }
178
+ return stripQuotes(value);
179
+ }
180
+ function stripQuotes(value) {
181
+ if (value.length < 2)
182
+ return value;
183
+ const first = value[0];
184
+ const last = value[value.length - 1];
185
+ if ((first === '"' && last === '"') || (first === "'" && last === "'")) {
186
+ return value.slice(1, -1);
187
+ }
188
+ return value;
189
+ }
190
+ //# sourceMappingURL=catalog.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"catalog.js","sourceRoot":"","sources":["../../src/lib/catalog.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ import { SeedInputSchema } from '@praxis-framework/seed';
2
+ export function adaptFormToSeedInput(form) {
3
+ // Parse via the package schema directly. The CLI's Form already mirrors
4
+ // the SeedInput shape one-to-one (modulo the `path` and partial wrapping)
5
+ // so we can hand the relevant slice over and let zod do the strict
6
+ // validation in one place.
7
+ const candidate = {
8
+ organisation: form.organisation,
9
+ role_definition: form.role_definition,
10
+ voice_traits: form.voice_traits,
11
+ capabilities: form.capabilities,
12
+ inhibitions: form.inhibitions,
13
+ initial_verbs: form.initial_verbs,
14
+ tools: form.tools,
15
+ };
16
+ const parsed = SeedInputSchema.safeParse(candidate);
17
+ if (!parsed.success) {
18
+ return {
19
+ ok: false,
20
+ issues: parsed.error.issues.map((i) => ({
21
+ path: i.path.join('.') || '<root>',
22
+ message: i.message,
23
+ })),
24
+ };
25
+ }
26
+ return { ok: true, input: parsed.data };
27
+ }
28
+ //# sourceMappingURL=seed-adapter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"seed-adapter.js","sourceRoot":"","sources":["../../src/lib/seed-adapter.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAkB,MAAM,wBAAwB,CAAC;AAmBzE,MAAM,UAAU,oBAAoB,CAAC,IAAU;IAC7C,wEAAwE;IACxE,0EAA0E;IAC1E,mEAAmE;IACnE,2BAA2B;IAC3B,MAAM,SAAS,GAAG;QAChB,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,eAAe,EAAE,IAAI,CAAC,eAAe;QACrC,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,YAAY,EAAE,IAAI,CAAC,YAAY;QAC/B,WAAW,EAAE,IAAI,CAAC,WAAW;QAC7B,aAAa,EAAE,IAAI,CAAC,aAAa;QACjC,KAAK,EAAE,IAAI,CAAC,KAAK;KAClB,CAAC;IAEF,MAAM,MAAM,GAAG,eAAe,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC;IACpD,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;QACpB,OAAO;YACL,EAAE,EAAE,KAAK;YACT,MAAM,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;gBACtC,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,QAAQ;gBAClC,OAAO,EAAE,CAAC,CAAC,OAAO;aACnB,CAAC,CAAC;SACJ,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,IAAI,EAAE,CAAC;AAC1C,CAAC"}
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Re-export the canonical trait library from `@praxis-framework/seed`. Both the CLI's
3
+ * voice flow (multi-select cloud) and the seeder (persona rendering) need
4
+ * the same authored list, and the seed package owns the source of truth so
5
+ * no copy can drift.
6
+ *
7
+ * A planned follow-up replaces the inline list in the seed package with a
8
+ * loader that reads `template/lib/traits.yaml`, mirroring the way
9
+ * `template/lib/tools.yaml` is loaded today. When that lands, this re-export
10
+ * stays in place and the CLI's import path doesn't change.
11
+ */
12
+ export { TRAIT_LIBRARY, findTrait } from '@praxis-framework/seed';
13
+ //# sourceMappingURL=traits.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"traits.js","sourceRoot":"","sources":["../../src/lib/traits.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,aAAa,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAC"}
@@ -0,0 +1,63 @@
1
+ import { z } from 'zod';
2
+ export const Organisation = z.object({
3
+ name: z.string().min(1),
4
+ website: z.string().optional(),
5
+ sector: z.string().optional(),
6
+ size: z.enum(['solo', 'small', 'mid', 'large', 'enterprise']).optional(),
7
+ description: z.string().optional(),
8
+ moats: z.string().optional(),
9
+ customer_profile: z.string().optional(),
10
+ });
11
+ export const RoleDefinition = z.object({
12
+ role_name: z.string().min(1),
13
+ working_title: z.string().optional(),
14
+ one_sentence_purpose: z.string().min(1),
15
+ day_to_day: z.string().optional(),
16
+ });
17
+ export const VoiceTrait = z.object({
18
+ /** Canonical name from the trait library (`cli/src/lib/traits.ts`). */
19
+ trait: z.string().min(1),
20
+ /** Free-text descriptors qualifying *how* the trait should manifest. */
21
+ qualifiers: z.array(z.string()).default([]),
22
+ });
23
+ export const InitialVerb = z.object({
24
+ /** Filename-shaped slug (lowercase letters, digits, hyphens; starts with a letter). */
25
+ slug: z.string().min(1),
26
+ /**
27
+ * Bullet-shaped body content for the verb file. 0-N free-text strings;
28
+ * each renders as a `- bullet` line in `verbs/<slug>.md`. An empty array
29
+ * is valid — the seeded file falls back to a bare heading + TODO marker.
30
+ */
31
+ description: z.array(z.string()).default([]),
32
+ });
33
+ export const Form = z.object({
34
+ organisation: Organisation.partial(),
35
+ role_definition: RoleDefinition.partial(),
36
+ path: z.enum(['research', 'manual', 'unset']).default('unset'),
37
+ // Optional MCP capability names selected during the tool-selection step.
38
+ // Built-in capabilities (always_available in the catalog) are not stored
39
+ // here — they're implicit. v2 will extend each entry with per-MCP transport
40
+ // and auth overrides; v1 stores names only.
41
+ tools: z.array(z.string()).default([]),
42
+ // Voice & personality — canonical trait name (from the curated library at
43
+ // `cli/src/lib/traits.ts`) plus 0-N free-text qualifiers describing how the
44
+ // trait should manifest.
45
+ voice_traits: z.array(VoiceTrait).default([]),
46
+ // Action-shaped responsibilities for this role.
47
+ capabilities: z.array(z.string()).default([]),
48
+ // Hard "never do" rules, intentionally absolute.
49
+ inhibitions: z.array(z.string()).default([]),
50
+ // First verbs the role will run; slug is filename-shaped.
51
+ initial_verbs: z.array(InitialVerb).default([]),
52
+ });
53
+ export const emptyForm = () => ({
54
+ organisation: {},
55
+ role_definition: {},
56
+ path: 'unset',
57
+ tools: [],
58
+ voice_traits: [],
59
+ capabilities: [],
60
+ inhibitions: [],
61
+ initial_verbs: [],
62
+ });
63
+ //# sourceMappingURL=form.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"form.js","sourceRoot":"","sources":["../../src/state/form.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ export const STEPS = [
2
+ 'welcome',
3
+ 'organisation',
4
+ 'role-definition',
5
+ 'path-choice',
6
+ 'tool-selection',
7
+ 'voice',
8
+ 'capabilities',
9
+ 'inhibitions',
10
+ 'initial-verbs',
11
+ 'review',
12
+ 'wrote',
13
+ ];
14
+ export const firstStep = () => STEPS[0];
15
+ export const nextStep = (current) => {
16
+ const idx = STEPS.indexOf(current);
17
+ if (idx === -1 || idx === STEPS.length - 1)
18
+ return null;
19
+ return STEPS[idx + 1] ?? null;
20
+ };
21
+ export const previousStep = (current) => {
22
+ const idx = STEPS.indexOf(current);
23
+ if (idx <= 0)
24
+ return null;
25
+ return STEPS[idx - 1] ?? null;
26
+ };
27
+ export const isLastStep = (current) => current === STEPS[STEPS.length - 1];
28
+ //# sourceMappingURL=steps.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"steps.js","sourceRoot":"","sources":["../../src/state/steps.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,KAAK,GAAG;IACnB,SAAS;IACT,cAAc;IACd,iBAAiB;IACjB,aAAa;IACb,gBAAgB;IAChB,OAAO;IACP,cAAc;IACd,aAAa;IACb,eAAe;IACf,QAAQ;IACR,OAAO;CACC,CAAC;AAIX,MAAM,CAAC,MAAM,SAAS,GAAG,GAAS,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC;AAE9C,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,OAAa,EAAe,EAAE;IACrD,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,GAAG,KAAK,CAAC,CAAC,IAAI,GAAG,KAAK,KAAK,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACxD,OAAO,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC;AAChC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAAa,EAAe,EAAE;IACzD,MAAM,GAAG,GAAG,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;IACnC,IAAI,GAAG,IAAI,CAAC;QAAE,OAAO,IAAI,CAAC;IAC1B,OAAO,KAAK,CAAC,GAAG,GAAG,CAAC,CAAC,IAAI,IAAI,CAAC;AAChC,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,OAAa,EAAW,EAAE,CACnD,OAAO,KAAK,KAAK,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC"}
@@ -0,0 +1,14 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import BigText from 'ink-big-text';
4
+ import Gradient from 'ink-gradient';
5
+ /** Full hero header — used once at welcome to establish presence. */
6
+ export const Header = () => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsx(Gradient, { name: "atlas", children: _jsx(BigText, { text: "praxis", font: "tiny" }) }), _jsx(Text, { dimColor: true, children: " role-based agents that fit your business" })] }));
7
+ /**
8
+ * Compact persistent header — used on every step after welcome.
9
+ * Single-line brand mark with the same atlas gradient + an optional
10
+ * muted wayfinding string. Keeps the wizard branded without dominating
11
+ * the form below.
12
+ */
13
+ export const CompactHeader = ({ context }) => (_jsxs(Box, { marginBottom: 1, children: [_jsx(Gradient, { name: "atlas", children: _jsx(Text, { children: "praxis" }) }), context && (_jsxs(Text, { dimColor: true, children: [' · ', context] }))] }));
14
+ //# sourceMappingURL=header.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"header.js","sourceRoot":"","sources":["../../src/ui/header.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,MAAM,KAAK,CAAC;AAChC,OAAO,OAAO,MAAM,cAAc,CAAC;AACnC,OAAO,QAAQ,MAAM,cAAc,CAAC;AAEpC,qEAAqE;AACrE,MAAM,CAAC,MAAM,MAAM,GAAG,GAAG,EAAE,CAAC,CAC1B,MAAC,GAAG,IAAC,aAAa,EAAC,QAAQ,EAAC,YAAY,EAAE,CAAC,aACzC,KAAC,QAAQ,IAAC,IAAI,EAAC,OAAO,YACpB,KAAC,OAAO,IAAC,IAAI,EAAC,QAAQ,EAAC,IAAI,EAAC,MAAM,GAAG,GAC5B,EACX,KAAC,IAAI,IAAC,QAAQ,gEAAiD,IAC3D,CACP,CAAC;AAOF;;;;;GAKG;AACH,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,EAAE,OAAO,EAAsB,EAAE,EAAE,CAAC,CAChE,MAAC,GAAG,IAAC,YAAY,EAAE,CAAC,aAClB,KAAC,QAAQ,IAAC,IAAI,EAAC,OAAO,YACpB,KAAC,IAAI,yBAAc,GACV,EACV,OAAO,IAAI,CACV,MAAC,IAAI,IAAC,QAAQ,mBACX,KAAK,EACL,OAAO,IACH,CACR,IACG,CACP,CAAC"}
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Pure state machine for {@link ListBuilder}. Extracted so the navigation /
3
+ * commit / focus rules can be unit-tested without rendering Ink, and so the
4
+ * component itself stays a thin presentational shell over `applyAction()`.
5
+ *
6
+ * The component owns three pieces of state that move together — `items`,
7
+ * `(rowIdx, fieldIdx)`, and the in-flight `draft` buffer — and a bug fixed
8
+ * here lived in the gap between them. The original useInput handler bound
9
+ * plain alphabetic keys (`j`, `k`, `d`) as navigation/delete shortcuts; those
10
+ * shortcuts also fired while a `TextInput` was focused, so a user typing
11
+ * `direct` would have the leading `d` racing with a row-clear. Centralising
12
+ * the rules into one transition function makes it obvious which keys mutate
13
+ * structure (tab/arrow/return/escape/empty-backspace) and which are purely
14
+ * for the text input to consume.
15
+ */
16
+ /**
17
+ * Build the initial state for a fresh ListBuilder mount. Always seeds at
18
+ * least one editable row so the operator has somewhere to start; the empty
19
+ * trailing row is dropped at finalise time.
20
+ */
21
+ export function initialState(initial, config) {
22
+ const items = initial.length > 0 ? [...initial] : [config.empty()];
23
+ const draft = readDraft(items, 0, 0, config);
24
+ return { items, rowIdx: 0, fieldIdx: 0, draft };
25
+ }
26
+ /**
27
+ * Apply a user-driven action to the state. Returns either the next state
28
+ * (`kind: 'state'`) or a finalise signal (`kind: 'finalise'`) carrying the
29
+ * trimmed items the caller should hand to `onNext`.
30
+ *
31
+ * The pure-function shape means tests can drive the same transitions the
32
+ * component does without setting up Ink, React, or input plumbing.
33
+ */
34
+ export function applyAction(state, action, config) {
35
+ switch (action.type) {
36
+ case 'setDraft':
37
+ return { kind: 'state', state: { ...state, draft: action.value } };
38
+ case 'tab': {
39
+ if (config.fields.length === 0) {
40
+ return { kind: 'state', state };
41
+ }
42
+ const committed = commit(state, config);
43
+ const nextField = (state.fieldIdx + 1) % config.fields.length;
44
+ return {
45
+ kind: 'state',
46
+ state: refocus(committed, state.rowIdx, nextField, config),
47
+ };
48
+ }
49
+ case 'down': {
50
+ const committed = commit(state, config);
51
+ if (committed.rowIdx < committed.items.length - 1) {
52
+ return {
53
+ kind: 'state',
54
+ state: refocus(committed, committed.rowIdx + 1, 0, config),
55
+ };
56
+ }
57
+ if (committed.items.length < config.max) {
58
+ const grown = {
59
+ ...committed,
60
+ items: [...committed.items, config.empty()],
61
+ };
62
+ return {
63
+ kind: 'state',
64
+ state: refocus(grown, grown.items.length - 1, 0, config),
65
+ };
66
+ }
67
+ return { kind: 'state', state: committed };
68
+ }
69
+ case 'up': {
70
+ if (state.rowIdx === 0) {
71
+ return { kind: 'state', state };
72
+ }
73
+ const committed = commit(state, config);
74
+ return {
75
+ kind: 'state',
76
+ state: refocus(committed, committed.rowIdx - 1, 0, config),
77
+ };
78
+ }
79
+ case 'deleteRow': {
80
+ // If only one row remains, "delete" clears it back to empty rather
81
+ // than leaving the operator with zero rows to type into.
82
+ if (state.items.length <= 1) {
83
+ const cleared = [config.empty()];
84
+ return {
85
+ kind: 'state',
86
+ state: refocus({ ...state, items: cleared }, 0, 0, config),
87
+ };
88
+ }
89
+ const next = state.items.filter((_, i) => i !== state.rowIdx);
90
+ const nextRow = Math.min(state.rowIdx, next.length - 1);
91
+ return {
92
+ kind: 'state',
93
+ state: refocus({ ...state, items: next }, nextRow, 0, config),
94
+ };
95
+ }
96
+ case 'submit': {
97
+ // Enter is "submit and add another" — commit the in-flight draft, then
98
+ // either advance to the next row (existing or freshly appended) or
99
+ // finalise. Finalise only fires when the operator hits enter on a row
100
+ // that is genuinely empty, mirroring "you've stopped adding things".
101
+ const committed = commit(state, config);
102
+ const focusedRow = committed.items[committed.rowIdx];
103
+ const focusedRowEmpty = focusedRow !== undefined &&
104
+ config.fields.every((f) => f.extract(focusedRow).trim().length === 0);
105
+ const isLast = committed.rowIdx === committed.items.length - 1;
106
+ // Empty trailing row — operator is signalling "I'm done". Drop the
107
+ // empty row and let the parent run its min/validate gates.
108
+ if (isLast && focusedRowEmpty) {
109
+ return {
110
+ kind: 'finalise',
111
+ items: trimEmptyRows(committed.items, config),
112
+ };
113
+ }
114
+ // Row has content — gate advancement on the row-level validator so the
115
+ // operator can't accumulate broken rows and only discover it on
116
+ // finalise. Without an advance, focus stays put and the row's inline
117
+ // error (rendered by the component using the same validator) surfaces.
118
+ if (focusedRow !== undefined && config.validate) {
119
+ const err = config.validate(focusedRow);
120
+ if (err !== null) {
121
+ return { kind: 'state', state: committed };
122
+ }
123
+ }
124
+ if (!isLast) {
125
+ // Same as ↓: commit current, move to existing next row.
126
+ return {
127
+ kind: 'state',
128
+ state: refocus(committed, committed.rowIdx + 1, 0, config),
129
+ };
130
+ }
131
+ // Last row, valid, has content. Try to grow; if at max, finalise so
132
+ // the operator isn't stuck pressing keys with no visible effect.
133
+ if (committed.items.length < config.max) {
134
+ const grown = {
135
+ ...committed,
136
+ items: [...committed.items, config.empty()],
137
+ };
138
+ return {
139
+ kind: 'state',
140
+ state: refocus(grown, grown.items.length - 1, 0, config),
141
+ };
142
+ }
143
+ return { kind: 'finalise', items: trimEmptyRows(committed.items, config) };
144
+ }
145
+ default:
146
+ return { kind: 'state', state };
147
+ }
148
+ }
149
+ /**
150
+ * Strip rows where every field is blank — these are the "press enter to
151
+ * add another" affordance, not real entries.
152
+ */
153
+ export function trimEmptyRows(items, config) {
154
+ return items.filter((item) => config.fields.some((f) => f.extract(item).trim().length > 0));
155
+ }
156
+ function readDraft(items, rowIdx, fieldIdx, config) {
157
+ const item = items[rowIdx];
158
+ const field = config.fields[fieldIdx];
159
+ return item && field ? field.extract(item) : '';
160
+ }
161
+ function commit(state, config) {
162
+ const item = state.items[state.rowIdx];
163
+ const field = config.fields[state.fieldIdx];
164
+ if (!item || !field)
165
+ return state;
166
+ const nextItem = field.apply(item, state.draft.trim());
167
+ const nextItems = [...state.items];
168
+ nextItems[state.rowIdx] = nextItem;
169
+ return { ...state, items: nextItems };
170
+ }
171
+ function refocus(state, rowIdx, fieldIdx, config) {
172
+ return {
173
+ ...state,
174
+ rowIdx,
175
+ fieldIdx,
176
+ draft: readDraft(state.items, rowIdx, fieldIdx, config),
177
+ };
178
+ }
179
+ //# sourceMappingURL=list-builder-state.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"list-builder-state.js","sourceRoot":"","sources":["../../src/ui/list-builder-state.ts"],"names":[],"mappings":"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"}