@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.
- package/README.md +51 -0
- package/dist/app.js +122 -0
- package/dist/app.js.map +1 -0
- package/dist/commands/init-config.js +85 -0
- package/dist/commands/init-config.js.map +1 -0
- package/dist/commands/init.js +59 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/log.js +171 -0
- package/dist/commands/log.js.map +1 -0
- package/dist/flows/capabilities.js +14 -0
- package/dist/flows/capabilities.js.map +1 -0
- package/dist/flows/inhibitions.js +14 -0
- package/dist/flows/inhibitions.js.map +1 -0
- package/dist/flows/initial-verbs.js +82 -0
- package/dist/flows/initial-verbs.js.map +1 -0
- package/dist/flows/organisation.js +112 -0
- package/dist/flows/organisation.js.map +1 -0
- package/dist/flows/path-choice.js +22 -0
- package/dist/flows/path-choice.js.map +1 -0
- package/dist/flows/review.js +31 -0
- package/dist/flows/review.js.map +1 -0
- package/dist/flows/role-definition.js +77 -0
- package/dist/flows/role-definition.js.map +1 -0
- package/dist/flows/tools.js +63 -0
- package/dist/flows/tools.js.map +1 -0
- package/dist/flows/voice.js +146 -0
- package/dist/flows/voice.js.map +1 -0
- package/dist/flows/welcome.js +17 -0
- package/dist/flows/welcome.js.map +1 -0
- package/dist/flows/wrote.js +51 -0
- package/dist/flows/wrote.js.map +1 -0
- package/dist/index.js +57 -0
- package/dist/index.js.map +1 -0
- package/dist/lib/catalog.js +190 -0
- package/dist/lib/catalog.js.map +1 -0
- package/dist/lib/seed-adapter.js +28 -0
- package/dist/lib/seed-adapter.js.map +1 -0
- package/dist/lib/traits.js +13 -0
- package/dist/lib/traits.js.map +1 -0
- package/dist/state/form.js +63 -0
- package/dist/state/form.js.map +1 -0
- package/dist/state/steps.js +28 -0
- package/dist/state/steps.js.map +1 -0
- package/dist/ui/header.js +14 -0
- package/dist/ui/header.js.map +1 -0
- package/dist/ui/list-builder-state.js +179 -0
- package/dist/ui/list-builder-state.js.map +1 -0
- package/dist/ui/list-builder.js +144 -0
- package/dist/ui/list-builder.js.map +1 -0
- package/dist/ui/theme.js +8 -0
- package/dist/ui/theme.js.map +1 -0
- package/examples/sample-role.json +24 -0
- package/package.json +66 -0
package/README.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# @praxis-framework/cli
|
|
2
|
+
|
|
3
|
+
Operator CLI for the [praxis-framework](https://github.com/steveworley/praxis-framework). Use this for **scripted / CI setup** or for running `praxis log` from inside verbs. For first-time exploration of the framework, the [`docker run` flow](https://github.com/steveworley/praxis-framework#quickstart) is lower friction — no install, the dashboard wizard writes the same files into the mounted directory.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @praxis-framework/cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Requires Node.js 20 or newer.
|
|
12
|
+
|
|
13
|
+
## Quick start
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
mkdir my-role && cd my-role
|
|
17
|
+
|
|
18
|
+
# Interactive wizard — walks organisation context, role definition,
|
|
19
|
+
# voice traits, capabilities, inhibitions, and verbs.
|
|
20
|
+
praxis init
|
|
21
|
+
|
|
22
|
+
# Or hand it a config file (useful for CI / reproducible setups).
|
|
23
|
+
praxis init --config role.json --path .
|
|
24
|
+
|
|
25
|
+
# Set your API key and bring the dashboard up.
|
|
26
|
+
cp .env.example .env && vim .env # set ANTHROPIC_API_KEY
|
|
27
|
+
docker compose up # pulls ghcr.io/steveworley/praxis-framework/dashboard:main
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Open `http://localhost:4321/`.
|
|
31
|
+
|
|
32
|
+
The CLI emits a populated role directory with `persona.md`, `CLAUDE.md`, `verbs/`, `lib/`, `memory/`, `escalations/`, `output/`, plus `docker-compose.yml` + `.env.example` so the role is runnable with one `docker compose up` — no framework clone required. The seed auto-initialises the target as a git repo on `main`, so you can run `praxis init` against a freshly-`mkdir`'d empty dir without pre-running `git init`. Commits are left for you to make when you're ready. See `examples/sample-role.json` for the config schema.
|
|
33
|
+
|
|
34
|
+
## Commands
|
|
35
|
+
|
|
36
|
+
| Command | Purpose |
|
|
37
|
+
|---------|---------|
|
|
38
|
+
| `praxis init` | Walk the wizard and write a new role into the target path. |
|
|
39
|
+
| `praxis init --config <file>` | Skip the wizard and seed from a JSON role definition. |
|
|
40
|
+
| `praxis init --path <dir>` | Target directory for the seeded role (default: current directory). |
|
|
41
|
+
| `praxis log` | Append a structured log entry to the active role's runtime log. |
|
|
42
|
+
|
|
43
|
+
## Docs
|
|
44
|
+
|
|
45
|
+
- [Framework overview](https://github.com/steveworley/praxis-framework#readme)
|
|
46
|
+
- [Creating a role](https://github.com/steveworley/praxis-framework/blob/main/docs/creating-a-role.md)
|
|
47
|
+
- [Sample role config](https://github.com/steveworley/praxis-framework/blob/main/cli/examples/sample-role.json)
|
|
48
|
+
|
|
49
|
+
## License
|
|
50
|
+
|
|
51
|
+
MIT — see [LICENSE](https://github.com/steveworley/praxis-framework/blob/main/LICENSE).
|
package/dist/app.js
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Box, Text, useApp } from 'ink';
|
|
4
|
+
import { emptyForm, } from './state/form.js';
|
|
5
|
+
import { firstStep } from './state/steps.js';
|
|
6
|
+
import { Welcome } from './flows/welcome.js';
|
|
7
|
+
import { OrganisationFlow } from './flows/organisation.js';
|
|
8
|
+
import { RoleDefinitionFlow } from './flows/role-definition.js';
|
|
9
|
+
import { PathChoiceFlow } from './flows/path-choice.js';
|
|
10
|
+
import { ToolSelection } from './flows/tools.js';
|
|
11
|
+
import { VoiceFlow } from './flows/voice.js';
|
|
12
|
+
import { CapabilitiesFlow } from './flows/capabilities.js';
|
|
13
|
+
import { InhibitionsFlow } from './flows/inhibitions.js';
|
|
14
|
+
import { InitialVerbsFlow } from './flows/initial-verbs.js';
|
|
15
|
+
import { Review } from './flows/review.js';
|
|
16
|
+
import { Wrote } from './flows/wrote.js';
|
|
17
|
+
import { adaptFormToSeedInput } from './lib/seed-adapter.js';
|
|
18
|
+
import { Header } from './ui/header.js';
|
|
19
|
+
import { warn } from './ui/theme.js';
|
|
20
|
+
/**
|
|
21
|
+
* Wraps the active step's content in a Box that always has the praxis
|
|
22
|
+
* pixel-art Header at top. The Header is rendered once at the App level
|
|
23
|
+
* and persists across every state transition — keeps the brand presence
|
|
24
|
+
* consistent throughout the wizard.
|
|
25
|
+
*/
|
|
26
|
+
export const App = ({ scaffoldPath, catalog }) => {
|
|
27
|
+
const { exit } = useApp();
|
|
28
|
+
const [step, setStep] = useState(firstStep());
|
|
29
|
+
const [form, setForm] = useState(emptyForm());
|
|
30
|
+
const [seedInput, setSeedInput] = useState(null);
|
|
31
|
+
const [cancelled, setCancelled] = useState(false);
|
|
32
|
+
const cancel = () => {
|
|
33
|
+
setCancelled(true);
|
|
34
|
+
exit();
|
|
35
|
+
};
|
|
36
|
+
const goTo = (next) => setStep(next);
|
|
37
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, {}), cancelled ? (_jsxs(Text, { children: [warn('cancelled'), " \u2014 no changes were written."] })) : (renderStep({
|
|
38
|
+
step,
|
|
39
|
+
form,
|
|
40
|
+
setForm,
|
|
41
|
+
seedInput,
|
|
42
|
+
setSeedInput,
|
|
43
|
+
goTo,
|
|
44
|
+
cancel,
|
|
45
|
+
exit,
|
|
46
|
+
scaffoldPath,
|
|
47
|
+
catalog,
|
|
48
|
+
}))] }));
|
|
49
|
+
};
|
|
50
|
+
const renderStep = ({ step, form, setForm, seedInput, setSeedInput, goTo, cancel, exit, scaffoldPath, catalog, }) => {
|
|
51
|
+
if (step === 'welcome') {
|
|
52
|
+
return _jsx(Welcome, { onNext: () => goTo('organisation'), onCancel: cancel });
|
|
53
|
+
}
|
|
54
|
+
if (step === 'organisation') {
|
|
55
|
+
return (_jsx(OrganisationFlow, { initial: form.organisation, onCancel: cancel, onNext: (next) => {
|
|
56
|
+
setForm({ ...form, organisation: next });
|
|
57
|
+
goTo('role-definition');
|
|
58
|
+
} }));
|
|
59
|
+
}
|
|
60
|
+
if (step === 'role-definition') {
|
|
61
|
+
return (_jsx(RoleDefinitionFlow, { initial: form.role_definition, onCancel: cancel, onNext: (next) => {
|
|
62
|
+
setForm({ ...form, role_definition: next });
|
|
63
|
+
goTo('path-choice');
|
|
64
|
+
} }));
|
|
65
|
+
}
|
|
66
|
+
if (step === 'path-choice') {
|
|
67
|
+
return (_jsx(PathChoiceFlow, { onCancel: cancel, onNext: (path) => {
|
|
68
|
+
setForm({ ...form, path });
|
|
69
|
+
goTo('tool-selection');
|
|
70
|
+
} }));
|
|
71
|
+
}
|
|
72
|
+
if (step === 'tool-selection') {
|
|
73
|
+
return (_jsx(ToolSelection, { catalog: catalog, initial: form.tools, onCancel: cancel, onNext: (tools) => {
|
|
74
|
+
setForm({ ...form, tools });
|
|
75
|
+
goTo('voice');
|
|
76
|
+
} }));
|
|
77
|
+
}
|
|
78
|
+
if (step === 'voice') {
|
|
79
|
+
return (_jsx(VoiceFlow, { initial: form.voice_traits, onCancel: cancel, onNext: (voice_traits) => {
|
|
80
|
+
setForm({ ...form, voice_traits });
|
|
81
|
+
goTo('capabilities');
|
|
82
|
+
} }));
|
|
83
|
+
}
|
|
84
|
+
if (step === 'capabilities') {
|
|
85
|
+
return (_jsx(CapabilitiesFlow, { initial: form.capabilities, onCancel: cancel, onNext: (capabilities) => {
|
|
86
|
+
setForm({ ...form, capabilities });
|
|
87
|
+
goTo('inhibitions');
|
|
88
|
+
} }));
|
|
89
|
+
}
|
|
90
|
+
if (step === 'inhibitions') {
|
|
91
|
+
return (_jsx(InhibitionsFlow, { initial: form.inhibitions, onCancel: cancel, onNext: (inhibitions) => {
|
|
92
|
+
setForm({ ...form, inhibitions });
|
|
93
|
+
goTo('initial-verbs');
|
|
94
|
+
} }));
|
|
95
|
+
}
|
|
96
|
+
if (step === 'initial-verbs') {
|
|
97
|
+
return (_jsx(InitialVerbsFlow, { initial: form.initial_verbs, onCancel: cancel, onNext: (initial_verbs) => {
|
|
98
|
+
setForm({ ...form, initial_verbs });
|
|
99
|
+
goTo('review');
|
|
100
|
+
} }));
|
|
101
|
+
}
|
|
102
|
+
if (step === 'review') {
|
|
103
|
+
return (_jsx(Review, { form: form, scaffoldPath: scaffoldPath, onCancel: cancel, onConfirm: () => {
|
|
104
|
+
// Re-validate at the seam — the review screen has already shown
|
|
105
|
+
// any issues, but we don't trust it to be the only source of
|
|
106
|
+
// truth. If validation fails here it's a wizard bug.
|
|
107
|
+
const result = adaptFormToSeedInput(form);
|
|
108
|
+
if (!result.ok)
|
|
109
|
+
return;
|
|
110
|
+
setSeedInput(result.input);
|
|
111
|
+
goTo('wrote');
|
|
112
|
+
} }));
|
|
113
|
+
}
|
|
114
|
+
// 'wrote'
|
|
115
|
+
if (seedInput === null) {
|
|
116
|
+
// Defensive: someone landed on `wrote` without a validated input.
|
|
117
|
+
// Treat as cancellation rather than try to seed an empty form.
|
|
118
|
+
return _jsx(Text, { children: warn('Internal: no seed input available — cancelled.') });
|
|
119
|
+
}
|
|
120
|
+
return _jsx(Wrote, { input: seedInput, scaffoldPath: scaffoldPath, onExit: exit });
|
|
121
|
+
};
|
|
122
|
+
//# sourceMappingURL=app.js.map
|
package/dist/app.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"app.js","sourceRoot":"","sources":["../src/app.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import fs from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { SeedError, SeedInputSchema, seedRole, } from '@praxis-framework/seed';
|
|
4
|
+
/**
|
|
5
|
+
* Typed error class so the commander wrapper can map clean exit codes
|
|
6
|
+
* without leaking stack traces to operator-facing output. The `cause`
|
|
7
|
+
* preserves the underlying error (Zod issue, fs ENOENT, JSON parse fail)
|
|
8
|
+
* for callers that want to inspect it — tests assert on `code` instead.
|
|
9
|
+
*/
|
|
10
|
+
export class InitConfigError extends Error {
|
|
11
|
+
code;
|
|
12
|
+
cause;
|
|
13
|
+
constructor(message, code, cause) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.cause = cause;
|
|
17
|
+
this.name = 'InitConfigError';
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Read + validate the config file, then call `seedRole`.
|
|
22
|
+
*
|
|
23
|
+
* Errors are normalised into `InitConfigError` so the commander wrapper
|
|
24
|
+
* can write a single-line stderr message and exit non-zero. The result
|
|
25
|
+
* mirrors `seedRole`'s `SeedResult` so callers can format their own
|
|
26
|
+
* success message.
|
|
27
|
+
*/
|
|
28
|
+
export async function runInitConfig(options) {
|
|
29
|
+
const input = await loadAndValidateConfig(options.configPath);
|
|
30
|
+
const absTarget = path.resolve(options.targetPath);
|
|
31
|
+
try {
|
|
32
|
+
return await seedRole(input, absTarget, { overwrite: options.overwrite ?? false });
|
|
33
|
+
}
|
|
34
|
+
catch (err) {
|
|
35
|
+
if (err instanceof SeedError) {
|
|
36
|
+
throw new InitConfigError(err.message, 'SEED_FAILED', err);
|
|
37
|
+
}
|
|
38
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
39
|
+
throw new InitConfigError(`Seed failed: ${message}`, 'SEED_FAILED', err);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Read the file, parse as JSON, validate against `SeedInputSchema`. Each
|
|
44
|
+
* failure mode gets its own error code so tests can distinguish them
|
|
45
|
+
* cleanly without string-matching messages.
|
|
46
|
+
*/
|
|
47
|
+
async function loadAndValidateConfig(configPath) {
|
|
48
|
+
const absConfig = path.resolve(configPath);
|
|
49
|
+
let raw;
|
|
50
|
+
try {
|
|
51
|
+
raw = await fs.readFile(absConfig, 'utf-8');
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
const errno = err.code;
|
|
55
|
+
if (errno === 'ENOENT') {
|
|
56
|
+
throw new InitConfigError(`Config file not found: ${absConfig}`, 'CONFIG_NOT_FOUND', err);
|
|
57
|
+
}
|
|
58
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
59
|
+
throw new InitConfigError(`Failed to read config file ${absConfig}: ${message}`, 'CONFIG_UNREADABLE', err);
|
|
60
|
+
}
|
|
61
|
+
let parsed;
|
|
62
|
+
try {
|
|
63
|
+
parsed = JSON.parse(raw);
|
|
64
|
+
}
|
|
65
|
+
catch (err) {
|
|
66
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
67
|
+
throw new InitConfigError(`Config file is not valid JSON (${absConfig}): ${message}`, 'CONFIG_INVALID_JSON', err);
|
|
68
|
+
}
|
|
69
|
+
const result = SeedInputSchema.safeParse(parsed);
|
|
70
|
+
if (!result.success) {
|
|
71
|
+
const summary = formatZodError(result.error);
|
|
72
|
+
throw new InitConfigError(`Config does not match SeedInput schema: ${summary}`, 'CONFIG_INVALID_SCHEMA', result.error);
|
|
73
|
+
}
|
|
74
|
+
return result.data;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Render a Zod issue list as a single line. Mirrors the `seedRole` internal
|
|
78
|
+
* formatter so operators get the same flavour of error from both surfaces.
|
|
79
|
+
*/
|
|
80
|
+
function formatZodError(error) {
|
|
81
|
+
return error.issues
|
|
82
|
+
.map((i) => `${i.path.join('.') || '<root>'}: ${i.message}`)
|
|
83
|
+
.join('; ');
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=init-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init-config.js","sourceRoot":"","sources":["../../src/commands/init-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { render } from 'ink';
|
|
3
|
+
import { App } from '../app.js';
|
|
4
|
+
import { loadCatalog } from '../lib/catalog.js';
|
|
5
|
+
import { InitConfigError, runInitConfig } from './init-config.js';
|
|
6
|
+
export const initCommand = async (options) => {
|
|
7
|
+
const scaffoldPath = options.path ?? process.cwd();
|
|
8
|
+
// Non-interactive branch — read a JSON config and call `seedRole` directly.
|
|
9
|
+
// We deliberately don't load the framework catalog here: the catalog is the
|
|
10
|
+
// wizard's source of truth for tool/trait options, but a config-driven seed
|
|
11
|
+
// is just "trust the file", so we hand straight off to the seed package.
|
|
12
|
+
if (options.config !== undefined) {
|
|
13
|
+
try {
|
|
14
|
+
const result = await runInitConfig({
|
|
15
|
+
configPath: options.config,
|
|
16
|
+
targetPath: scaffoldPath,
|
|
17
|
+
overwrite: options.overwrite ?? false,
|
|
18
|
+
});
|
|
19
|
+
process.stdout.write(`praxis: seeded ${result.filesWritten.length} files to ${result.targetPath}\n`);
|
|
20
|
+
return;
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
const message = err instanceof InitConfigError ? err.message : err instanceof Error ? err.message : String(err);
|
|
24
|
+
process.stderr.write(`praxis: ${message}\n`);
|
|
25
|
+
process.exit(1);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
// Interactive branch — Ink's useInput requires raw mode, which only works
|
|
29
|
+
// on a real TTY. Refuse to start cleanly when run under CI, piped input,
|
|
30
|
+
// or any non-interactive context — beats letting the React reconciler
|
|
31
|
+
// crash with an obscure "Raw mode is not supported" stack trace.
|
|
32
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
33
|
+
process.stderr.write('praxis init requires an interactive terminal.\n' +
|
|
34
|
+
'Run it from a real shell — not piped, not in CI, not nested in another process.\n' +
|
|
35
|
+
'For non-interactive use, pass --config <path-to-role.json>.\n');
|
|
36
|
+
process.exit(1);
|
|
37
|
+
}
|
|
38
|
+
// Load the framework catalog up-front. The wizard can't function without
|
|
39
|
+
// it, and surfacing the failure here keeps the Ink tree free of a
|
|
40
|
+
// loading-spinner detour for what should be an instant disk read.
|
|
41
|
+
let catalog;
|
|
42
|
+
try {
|
|
43
|
+
catalog = await loadCatalog();
|
|
44
|
+
}
|
|
45
|
+
catch (err) {
|
|
46
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
47
|
+
process.stderr.write(`praxis: ${message}\n`);
|
|
48
|
+
process.exit(1);
|
|
49
|
+
}
|
|
50
|
+
const { waitUntilExit } = render(_jsx(App, { scaffoldPath: scaffoldPath, catalog: catalog }), {
|
|
51
|
+
exitOnCtrlC: true,
|
|
52
|
+
});
|
|
53
|
+
waitUntilExit().catch((err) => {
|
|
54
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
55
|
+
process.stderr.write(`praxis: ${message}\n`);
|
|
56
|
+
process.exit(1);
|
|
57
|
+
});
|
|
58
|
+
};
|
|
59
|
+
//# sourceMappingURL=init.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"init.js","sourceRoot":"","sources":["../../src/commands/init.tsx"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
/**
|
|
4
|
+
* Run the log subcommand. Resolves the role root, writes the JSONL entry,
|
|
5
|
+
* and returns when the file has been appended. Throws on failure so the
|
|
6
|
+
* commander wrapper can map the error to a non-zero exit + clean stderr.
|
|
7
|
+
*
|
|
8
|
+
* Pure side-effect-driven: filesystem write + optional stdout echo. The
|
|
9
|
+
* function returns the JSON line that was written so tests can assert
|
|
10
|
+
* against the in-memory shape without re-reading the file.
|
|
11
|
+
*/
|
|
12
|
+
export async function runLog(options, extras, cwd = process.cwd(), now = new Date()) {
|
|
13
|
+
if (!options.action || options.action.length === 0) {
|
|
14
|
+
throw new LogError('--action is required');
|
|
15
|
+
}
|
|
16
|
+
const parsedExtras = parseExtras(extras);
|
|
17
|
+
const root = resolveRoleRoot(cwd);
|
|
18
|
+
const logsDir = options.campaign
|
|
19
|
+
? path.join(root, 'campaigns', options.campaign, 'logs')
|
|
20
|
+
: path.join(root, 'logs');
|
|
21
|
+
// When a campaign is supplied, the campaign directory must already exist —
|
|
22
|
+
// logging into a non-existent campaign would scatter orphan log files the
|
|
23
|
+
// dashboard never reads. The role-local `logs/` directory is auto-created
|
|
24
|
+
// because there's no equivalent "is this a real campaign?" check available.
|
|
25
|
+
if (options.campaign) {
|
|
26
|
+
const campaignDir = path.dirname(logsDir);
|
|
27
|
+
if (!directoryExists(campaignDir)) {
|
|
28
|
+
throw new LogError(`Campaign directory does not exist: ${campaignDir}. Create it before logging, or check the campaign id.`);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
fs.mkdirSync(logsDir, { recursive: true });
|
|
32
|
+
const today = localDateIsoString(now);
|
|
33
|
+
const logPath = path.join(logsDir, `${today}.jsonl`);
|
|
34
|
+
// Field ordering matters for human-readability of the JSONL — timestamp
|
|
35
|
+
// first, then conventional fields in a fixed order, then operator extras
|
|
36
|
+
// in the order they appeared on the command line. Mirrors the Python
|
|
37
|
+
// implementation so dashboard parsers see no shape change.
|
|
38
|
+
const record = {
|
|
39
|
+
timestamp: localIsoString(now),
|
|
40
|
+
};
|
|
41
|
+
const flagFields = [
|
|
42
|
+
['agent', options.agent],
|
|
43
|
+
['action', options.action],
|
|
44
|
+
['prospect_id', options.prospect],
|
|
45
|
+
['campaign_id', options.campaign],
|
|
46
|
+
['details', options.details],
|
|
47
|
+
['subject', options.subject],
|
|
48
|
+
];
|
|
49
|
+
for (const [key, value] of flagFields) {
|
|
50
|
+
if (value !== undefined)
|
|
51
|
+
record[key] = value;
|
|
52
|
+
}
|
|
53
|
+
for (const [key, value] of parsedExtras) {
|
|
54
|
+
if (key in record) {
|
|
55
|
+
process.stderr.write(`warning: extra '${key}' shadowed by flag value\n`);
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
record[key] = value;
|
|
59
|
+
}
|
|
60
|
+
const line = JSON.stringify(record);
|
|
61
|
+
fs.appendFileSync(logPath, `${line}\n`, 'utf-8');
|
|
62
|
+
if (options.echo) {
|
|
63
|
+
process.stdout.write(`${line}\n`);
|
|
64
|
+
}
|
|
65
|
+
return { line, logPath };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Thin error class used by the command implementation. The commander wrapper
|
|
69
|
+
* (in index.tsx) catches these and writes the message to stderr + exits
|
|
70
|
+
* non-zero, so the CLI never surfaces a stack trace for operator-facing
|
|
71
|
+
* failures.
|
|
72
|
+
*/
|
|
73
|
+
export class LogError extends Error {
|
|
74
|
+
constructor(message) {
|
|
75
|
+
super(message);
|
|
76
|
+
this.name = 'LogError';
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Walk up from `start` looking for a directory containing `persona.md`.
|
|
81
|
+
* That's the framework's canonical role-root marker — every seeded role has
|
|
82
|
+
* one, and only role roots have one at the top level. Throws if we hit the
|
|
83
|
+
* filesystem root without finding it.
|
|
84
|
+
*/
|
|
85
|
+
function resolveRoleRoot(start) {
|
|
86
|
+
let cur = path.resolve(start);
|
|
87
|
+
// Loop guard: stop once parent equals self (filesystem root).
|
|
88
|
+
while (true) {
|
|
89
|
+
if (fileExists(path.join(cur, 'persona.md'))) {
|
|
90
|
+
return cur;
|
|
91
|
+
}
|
|
92
|
+
const parent = path.dirname(cur);
|
|
93
|
+
if (parent === cur) {
|
|
94
|
+
throw new LogError(`Could not locate a 'persona.md' walking up from ${start}. Run from inside a praxis role directory.`);
|
|
95
|
+
}
|
|
96
|
+
cur = parent;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Parse trailing `key=value` arguments. Empty keys or pairs missing the
|
|
101
|
+
* separator are both fatal — silently dropping malformed extras would let
|
|
102
|
+
* agents log garbage forever without noticing.
|
|
103
|
+
*/
|
|
104
|
+
function parseExtras(pairs) {
|
|
105
|
+
const out = [];
|
|
106
|
+
for (const p of pairs) {
|
|
107
|
+
const eq = p.indexOf('=');
|
|
108
|
+
if (eq < 0) {
|
|
109
|
+
throw new LogError(`Extra arg must be key=value (got: ${JSON.stringify(p)})`);
|
|
110
|
+
}
|
|
111
|
+
const key = p.slice(0, eq);
|
|
112
|
+
const value = p.slice(eq + 1);
|
|
113
|
+
if (key.length === 0) {
|
|
114
|
+
throw new LogError(`Extra arg has empty key: ${JSON.stringify(p)}`);
|
|
115
|
+
}
|
|
116
|
+
out.push([key, value]);
|
|
117
|
+
}
|
|
118
|
+
return out;
|
|
119
|
+
}
|
|
120
|
+
function fileExists(p) {
|
|
121
|
+
try {
|
|
122
|
+
return fs.statSync(p).isFile();
|
|
123
|
+
}
|
|
124
|
+
catch {
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
function directoryExists(p) {
|
|
129
|
+
try {
|
|
130
|
+
return fs.statSync(p).isDirectory();
|
|
131
|
+
}
|
|
132
|
+
catch {
|
|
133
|
+
return false;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Render the local date as `YYYY-MM-DD`. Matches Python's
|
|
138
|
+
* `datetime.now().astimezone().date().isoformat()` — the day boundary is
|
|
139
|
+
* the local boundary, not UTC, so a midnight-PT log lands in the right
|
|
140
|
+
* file regardless of where the operator is.
|
|
141
|
+
*/
|
|
142
|
+
function localDateIsoString(d) {
|
|
143
|
+
const yyyy = d.getFullYear();
|
|
144
|
+
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
|
145
|
+
const dd = String(d.getDate()).padStart(2, '0');
|
|
146
|
+
return `${yyyy}-${mm}-${dd}`;
|
|
147
|
+
}
|
|
148
|
+
/**
|
|
149
|
+
* Render an ISO 8601 string with a local timezone offset (e.g.
|
|
150
|
+
* `2026-05-08T10:23:45+10:00`). Mirrors Python's
|
|
151
|
+
* `datetime.now().astimezone().isoformat(timespec='seconds')`.
|
|
152
|
+
*
|
|
153
|
+
* `Date.prototype.toISOString()` returns UTC with a trailing `Z`, which
|
|
154
|
+
* loses the operator's local clock context — useful when reading logs
|
|
155
|
+
* during a session — so we assemble the offset string by hand.
|
|
156
|
+
*/
|
|
157
|
+
function localIsoString(d) {
|
|
158
|
+
const yyyy = d.getFullYear();
|
|
159
|
+
const mm = String(d.getMonth() + 1).padStart(2, '0');
|
|
160
|
+
const dd = String(d.getDate()).padStart(2, '0');
|
|
161
|
+
const hh = String(d.getHours()).padStart(2, '0');
|
|
162
|
+
const mi = String(d.getMinutes()).padStart(2, '0');
|
|
163
|
+
const ss = String(d.getSeconds()).padStart(2, '0');
|
|
164
|
+
const offsetMinutes = -d.getTimezoneOffset();
|
|
165
|
+
const sign = offsetMinutes >= 0 ? '+' : '-';
|
|
166
|
+
const absOffset = Math.abs(offsetMinutes);
|
|
167
|
+
const offHh = String(Math.floor(absOffset / 60)).padStart(2, '0');
|
|
168
|
+
const offMm = String(absOffset % 60).padStart(2, '0');
|
|
169
|
+
return `${yyyy}-${mm}-${dd}T${hh}:${mi}:${ss}${sign}${offHh}:${offMm}`;
|
|
170
|
+
}
|
|
171
|
+
//# sourceMappingURL=log.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"log.js","sourceRoot":"","sources":["../../src/commands/log.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ListBuilder } from '../ui/list-builder.js';
|
|
3
|
+
const FIELDS = [
|
|
4
|
+
{
|
|
5
|
+
key: 'value',
|
|
6
|
+
label: 'capability',
|
|
7
|
+
placeholder: 'e.g. drafts cold-outreach emails',
|
|
8
|
+
extract: (i) => i.value,
|
|
9
|
+
apply: (i, value) => ({ ...i, value }),
|
|
10
|
+
},
|
|
11
|
+
];
|
|
12
|
+
const validate = (i) => i.value.trim().length === 0 ? 'capability is required' : null;
|
|
13
|
+
export const CapabilitiesFlow = ({ initial, onNext, onCancel }) => (_jsx(ListBuilder, { title: "Capabilities", helper: "What this role is responsible for. Action-shaped.", initial: initial.map((value) => ({ value })), fields: FIELDS, empty: () => ({ value: '' }), validate: validate, min: 1, max: 10, onNext: (items) => onNext(items.map((i) => i.value.trim())), onCancel: onCancel }));
|
|
14
|
+
//# sourceMappingURL=capabilities.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"capabilities.js","sourceRoot":"","sources":["../../src/flows/capabilities.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAkB,MAAM,uBAAuB,CAAC;AAYpE,MAAM,MAAM,GAAsB;IAChC;QACE,GAAG,EAAE,OAAO;QACZ,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,kCAAkC;QAC/C,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK;QACvB,KAAK,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC;KACvC;CACF,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,CAAO,EAAiB,EAAE,CAC1C,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,IAAI,CAAC;AAEhE,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAS,EAAE,EAAE,CAAC,CACxE,KAAC,WAAW,IACV,KAAK,EAAC,cAAc,EACpB,MAAM,EAAC,mDAAmD,EAC1D,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,EAC5C,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,EAC5B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,CAAC,EACN,GAAG,EAAE,EAAE,EACP,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,EAC3D,QAAQ,EAAE,QAAQ,GAClB,CACH,CAAC"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
2
|
+
import { ListBuilder } from '../ui/list-builder.js';
|
|
3
|
+
const FIELDS = [
|
|
4
|
+
{
|
|
5
|
+
key: 'value',
|
|
6
|
+
label: 'inhibition',
|
|
7
|
+
placeholder: 'e.g. never quote prices without sign-off',
|
|
8
|
+
extract: (i) => i.value,
|
|
9
|
+
apply: (i, value) => ({ ...i, value }),
|
|
10
|
+
},
|
|
11
|
+
];
|
|
12
|
+
const validate = (i) => i.value.trim().length === 0 ? 'inhibition is required' : null;
|
|
13
|
+
export const InhibitionsFlow = ({ initial, onNext, onCancel }) => (_jsx(ListBuilder, { title: "Hard inhibitions", helper: "Things this role must never do. Use sparingly \u2014 these are absolute.", initial: initial.map((value) => ({ value })), fields: FIELDS, empty: () => ({ value: '' }), validate: validate, min: 0, max: 10, onNext: (items) => onNext(items.map((i) => i.value.trim())), onCancel: onCancel }));
|
|
14
|
+
//# sourceMappingURL=inhibitions.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"inhibitions.js","sourceRoot":"","sources":["../../src/flows/inhibitions.tsx"],"names":[],"mappings":";AACA,OAAO,EAAE,WAAW,EAAkB,MAAM,uBAAuB,CAAC;AAYpE,MAAM,MAAM,GAAsB;IAChC;QACE,GAAG,EAAE,OAAO;QACZ,KAAK,EAAE,YAAY;QACnB,WAAW,EAAE,0CAA0C;QACvD,OAAO,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK;QACvB,KAAK,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,GAAG,CAAC,EAAE,KAAK,EAAE,CAAC;KACvC;CACF,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,CAAO,EAAiB,EAAE,CAC1C,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,wBAAwB,CAAC,CAAC,CAAC,IAAI,CAAC;AAEhE,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAS,EAAE,EAAE,CAAC,CACvE,KAAC,WAAW,IACV,KAAK,EAAC,kBAAkB,EACxB,MAAM,EAAC,0EAAqE,EAC5E,OAAO,EAAE,OAAO,CAAC,GAAG,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,EAC5C,MAAM,EAAE,MAAM,EACd,KAAK,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,EAC5B,QAAQ,EAAE,QAAQ,EAClB,GAAG,EAAE,CAAC,EACN,GAAG,EAAE,EAAE,EACP,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,EAC3D,QAAQ,EAAE,QAAQ,GAClB,CACH,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Box, Text } from 'ink';
|
|
4
|
+
import { ListBuilder } from '../ui/list-builder.js';
|
|
5
|
+
import { accent, muted } from '../ui/theme.js';
|
|
6
|
+
const MIN_VERBS = 1;
|
|
7
|
+
const MAX_VERBS = 6;
|
|
8
|
+
const MAX_BULLETS_PER_VERB = 6;
|
|
9
|
+
const SLUG_RE = /^[a-z][a-z0-9-]*$/;
|
|
10
|
+
export const InitialVerbsFlow = ({ initial, onNext, onCancel }) => {
|
|
11
|
+
const [stage, setStage] = useState({
|
|
12
|
+
kind: 'name',
|
|
13
|
+
verbs: initial.length > 0 ? initial : [],
|
|
14
|
+
});
|
|
15
|
+
if (stage.kind === 'name') {
|
|
16
|
+
return (_jsx(NameVerbs, { initial: stage.verbs, onCancel: onCancel, onNext: (verbs) => {
|
|
17
|
+
setStage({ kind: 'author', verbs, cursor: 0 });
|
|
18
|
+
} }));
|
|
19
|
+
}
|
|
20
|
+
// Authoring stage — one verb at a time. Cursor is bounded by the verb
|
|
21
|
+
// list we just built, so an out-of-range cursor would be a bug rather
|
|
22
|
+
// than a user-reachable state.
|
|
23
|
+
const verb = stage.verbs[stage.cursor];
|
|
24
|
+
if (!verb) {
|
|
25
|
+
return (_jsx(Box, { children: _jsx(Text, { color: "red", children: "Internal: verb cursor out of range \u2014 please cancel and retry." }) }));
|
|
26
|
+
}
|
|
27
|
+
return (_jsx(AuthorBullets, { slug: verb.slug, position: stage.cursor + 1, total: stage.verbs.length, initial: verb.description, onCancel: () => setStage({ kind: 'name', verbs: stage.verbs }), onNext: (description) => {
|
|
28
|
+
const nextVerbs = stage.verbs.map((v, i) => i === stage.cursor ? { ...v, description } : v);
|
|
29
|
+
if (stage.cursor === stage.verbs.length - 1) {
|
|
30
|
+
onNext(nextVerbs);
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
setStage({ kind: 'author', verbs: nextVerbs, cursor: stage.cursor + 1 });
|
|
34
|
+
} }));
|
|
35
|
+
};
|
|
36
|
+
const SLUG_FIELDS = [
|
|
37
|
+
{
|
|
38
|
+
key: 'slug',
|
|
39
|
+
label: 'slug',
|
|
40
|
+
placeholder: 'e.g. account-curator',
|
|
41
|
+
extract: (r) => r.slug,
|
|
42
|
+
apply: (r, value) => ({ ...r, slug: value }),
|
|
43
|
+
},
|
|
44
|
+
];
|
|
45
|
+
const NameVerbs = ({ initial, onNext, onCancel }) => {
|
|
46
|
+
// Seed slug rows from any prior pass; carry their authored descriptions
|
|
47
|
+
// forward via a side map so backing out of stage 2 doesn't lose work.
|
|
48
|
+
const initialRows = initial.map((v) => ({ slug: v.slug }));
|
|
49
|
+
const priorDescriptions = new Map(initial.map((v) => [v.slug, [...v.description]]));
|
|
50
|
+
return (_jsx(ListBuilder, { title: "Initial verbs \u2014 name the verbs", helper: `The first verbs the role will run. Slug is filename-shaped (lowercase letters, digits, hyphens; starts with a letter). ${MIN_VERBS}-${MAX_VERBS} verbs. You'll author the bullets for each one in the next step.`, initial: initialRows, fields: SLUG_FIELDS, empty: () => ({ slug: '' }), validate: (r) => {
|
|
51
|
+
const slug = r.slug.trim();
|
|
52
|
+
if (slug.length === 0)
|
|
53
|
+
return 'slug is required';
|
|
54
|
+
if (!SLUG_RE.test(slug)) {
|
|
55
|
+
return 'slug must be lowercase letters, digits, and hyphens (start with a letter)';
|
|
56
|
+
}
|
|
57
|
+
return null;
|
|
58
|
+
}, min: MIN_VERBS, max: MAX_VERBS, onNext: (rows) => {
|
|
59
|
+
const verbs = rows.map((r) => {
|
|
60
|
+
const slug = r.slug.trim();
|
|
61
|
+
return {
|
|
62
|
+
slug,
|
|
63
|
+
description: priorDescriptions.get(slug) ?? [],
|
|
64
|
+
};
|
|
65
|
+
});
|
|
66
|
+
onNext(verbs);
|
|
67
|
+
}, onCancel: onCancel }));
|
|
68
|
+
};
|
|
69
|
+
const BULLET_FIELDS = [
|
|
70
|
+
{
|
|
71
|
+
key: 'text',
|
|
72
|
+
label: 'bullet',
|
|
73
|
+
placeholder: 'one bullet describing what this verb does (or leave blank to skip)',
|
|
74
|
+
extract: (r) => r.text,
|
|
75
|
+
apply: (r, value) => ({ ...r, text: value }),
|
|
76
|
+
},
|
|
77
|
+
];
|
|
78
|
+
const AuthorBullets = ({ slug, position, total, initial, onNext, onCancel, }) => {
|
|
79
|
+
const initialRows = initial.map((text) => ({ text }));
|
|
80
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Box, { children: _jsx(Text, { children: muted(`verb ${position} of ${total}`) }) }), _jsx(Box, { children: _jsxs(Text, { children: [accent('→ '), accent(slug)] }) }), _jsx(Box, { marginBottom: 1, children: _jsx(Text, { children: muted('Author the bullets that describe what this verb does.') }) }), _jsx(ListBuilder, { title: "", helper: "", initial: initialRows, fields: BULLET_FIELDS, empty: () => ({ text: '' }), validate: (r) => (r.text.trim().length === 0 ? 'bullet text is required' : null), min: 0, max: MAX_BULLETS_PER_VERB, onNext: (rows) => onNext(rows.map((r) => r.text.trim()).filter((t) => t.length > 0)), onCancel: onCancel }, slug), _jsx(Box, { marginTop: 1, children: _jsx(Text, { dimColor: true, children: "Tip: enter on the last (or empty) row advances to the next verb. Esc returns to the slug list." }) })] }));
|
|
81
|
+
};
|
|
82
|
+
//# sourceMappingURL=initial-verbs.js.map
|