@justfortytwo/installer 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/LICENSE +21 -0
- package/README.md +70 -0
- package/dist/commands/doctor.d.ts +36 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +126 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/enrich.d.ts +2 -0
- package/dist/commands/enrich.d.ts.map +1 -0
- package/dist/commands/enrich.js +26 -0
- package/dist/commands/enrich.js.map +1 -0
- package/dist/commands/forget.d.ts +2 -0
- package/dist/commands/forget.d.ts.map +1 -0
- package/dist/commands/forget.js +19 -0
- package/dist/commands/forget.js.map +1 -0
- package/dist/commands/init.d.ts +48 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +284 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/pair.d.ts +2 -0
- package/dist/commands/pair.d.ts.map +1 -0
- package/dist/commands/pair.js +26 -0
- package/dist/commands/pair.js.map +1 -0
- package/dist/commands/rollback.d.ts +2 -0
- package/dist/commands/rollback.d.ts.map +1 -0
- package/dist/commands/rollback.js +32 -0
- package/dist/commands/rollback.js.map +1 -0
- package/dist/commands/unbind.d.ts +16 -0
- package/dist/commands/unbind.d.ts.map +1 -0
- package/dist/commands/unbind.js +67 -0
- package/dist/commands/unbind.js.map +1 -0
- package/dist/commands/update.d.ts +2 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +60 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/engine.d.ts +37 -0
- package/dist/engine.d.ts.map +1 -0
- package/dist/engine.js +170 -0
- package/dist/engine.js.map +1 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +106 -0
- package/dist/index.js.map +1 -0
- package/dist/render.d.ts +78 -0
- package/dist/render.d.ts.map +1 -0
- package/dist/render.js +113 -0
- package/dist/render.js.map +1 -0
- package/dist/state.d.ts +58 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +93 -0
- package/dist/state.js.map +1 -0
- package/package.json +79 -0
package/dist/engine.js
ADDED
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
// engine.ts — access to the optional @justfortytwo/* engine siblings.
|
|
2
|
+
//
|
|
3
|
+
// The installer declares the engine packages as OPTIONAL peerDependencies and
|
|
4
|
+
// reaches them via dynamic import, so a partial install (e.g. gate-only) never
|
|
5
|
+
// crashes the CLI — a missing sibling makes the dependent command/check report
|
|
6
|
+
// "not installed" instead of throwing.
|
|
7
|
+
import { createRequire } from 'node:module';
|
|
8
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
import { dirname, join, parse } from 'node:path';
|
|
11
|
+
const require = createRequire(import.meta.url);
|
|
12
|
+
/** Dynamically import an engine sibling; null if it is not installed. */
|
|
13
|
+
export async function loadSibling(spec) {
|
|
14
|
+
try {
|
|
15
|
+
return (await import(spec));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export const loadGate = () => loadSibling('@justfortytwo/gate');
|
|
22
|
+
export const loadMemory = () => loadSibling('@justfortytwo/memory');
|
|
23
|
+
export const loadTelegram = () => loadSibling('@justfortytwo/telegram');
|
|
24
|
+
/** Installed version of a sibling (its package.json `version`), or null. */
|
|
25
|
+
export function readInstalledVersion(spec) {
|
|
26
|
+
// Prefer the direct package.json subpath; packages with a restrictive
|
|
27
|
+
// `exports` map block it, so fall back to resolving the entry and walking up
|
|
28
|
+
// to the package.json whose `name` matches the spec.
|
|
29
|
+
const readVersion = (pkgPath) => {
|
|
30
|
+
try {
|
|
31
|
+
return JSON.parse(readFileSync(pkgPath, 'utf8')).version ?? null;
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
try {
|
|
38
|
+
return readVersion(require.resolve(`${spec}/package.json`));
|
|
39
|
+
}
|
|
40
|
+
catch {
|
|
41
|
+
/* exports-restricted — fall through */
|
|
42
|
+
}
|
|
43
|
+
try {
|
|
44
|
+
// Resolve the entry with the ESM resolver (matches the package's `import`
|
|
45
|
+
// condition — the same path loadSibling uses), then walk up to the
|
|
46
|
+
// package.json whose name matches. CJS require.resolve can't see
|
|
47
|
+
// import-only exports maps, so prefer import.meta.resolve.
|
|
48
|
+
let entry = null;
|
|
49
|
+
const esmResolve = import.meta.resolve;
|
|
50
|
+
if (esmResolve) {
|
|
51
|
+
try {
|
|
52
|
+
entry = fileURLToPath(esmResolve(spec));
|
|
53
|
+
}
|
|
54
|
+
catch { /* try CJS next */ }
|
|
55
|
+
}
|
|
56
|
+
if (!entry)
|
|
57
|
+
entry = require.resolve(spec);
|
|
58
|
+
let dir = dirname(entry);
|
|
59
|
+
const root = parse(dir).root;
|
|
60
|
+
while (true) {
|
|
61
|
+
const candidate = join(dir, 'package.json');
|
|
62
|
+
if (existsSync(candidate)) {
|
|
63
|
+
const json = JSON.parse(readFileSync(candidate, 'utf8'));
|
|
64
|
+
if (json.name === spec)
|
|
65
|
+
return json.version ?? null;
|
|
66
|
+
}
|
|
67
|
+
if (dir === root)
|
|
68
|
+
return null;
|
|
69
|
+
dir = dirname(dir);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch {
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
/** Walk up from this module to find the installer's own package.json. */
|
|
77
|
+
function findSelfPackageJson() {
|
|
78
|
+
let dir = dirname(fileURLToPath(import.meta.url));
|
|
79
|
+
const root = parse(dir).root;
|
|
80
|
+
while (true) {
|
|
81
|
+
const candidate = join(dir, 'package.json');
|
|
82
|
+
if (existsSync(candidate)) {
|
|
83
|
+
try {
|
|
84
|
+
return JSON.parse(readFileSync(candidate, 'utf8'));
|
|
85
|
+
}
|
|
86
|
+
catch {
|
|
87
|
+
return null;
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
if (dir === root)
|
|
91
|
+
return null;
|
|
92
|
+
dir = dirname(dir);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
/** The CLI's declared compat ranges (package.json `fortytwo.compat`). */
|
|
96
|
+
export function readSelfCompatRanges() {
|
|
97
|
+
const pkg = findSelfPackageJson();
|
|
98
|
+
const fortytwo = (pkg?.fortytwo ?? {});
|
|
99
|
+
return fortytwo.compat ?? {};
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Minimal semver-range satisfaction for the forms this CLI's compat contract
|
|
103
|
+
* uses: exact (`1.2.3`) and caret (`^1.2.3`, with npm's 0.x rule where the
|
|
104
|
+
* left-most non-zero element is the lock point). Avoids a runtime dependency.
|
|
105
|
+
*/
|
|
106
|
+
export function satisfiesRange(version, range) {
|
|
107
|
+
const parse3 = (v) => {
|
|
108
|
+
const [a = 0, b = 0, c = 0] = v.replace(/^[v^~]/, '').split('.').map((n) => parseInt(n, 10));
|
|
109
|
+
return [a, b, c];
|
|
110
|
+
};
|
|
111
|
+
const [vMaj, vMin, vPatch] = parse3(version);
|
|
112
|
+
const [rMaj, rMin, rPatch] = parse3(range);
|
|
113
|
+
if (Number.isNaN(vMaj) || Number.isNaN(rMaj))
|
|
114
|
+
return false;
|
|
115
|
+
if (!range.startsWith('^'))
|
|
116
|
+
return vMaj === rMaj && vMin === rMin && vPatch === rPatch;
|
|
117
|
+
const geFloor = vMaj > rMaj || (vMaj === rMaj && (vMin > rMin || (vMin === rMin && vPatch >= rPatch)));
|
|
118
|
+
if (!geFloor)
|
|
119
|
+
return false;
|
|
120
|
+
if (rMaj > 0)
|
|
121
|
+
return vMaj === rMaj; // ^1.2.3 -> >=1.2.3 <2.0.0
|
|
122
|
+
if (rMin > 0)
|
|
123
|
+
return vMaj === 0 && vMin === rMin; // ^0.1.2 -> >=0.1.2 <0.2.0
|
|
124
|
+
return vMaj === 0 && vMin === 0 && vPatch === rPatch; // ^0.0.z -> exact
|
|
125
|
+
}
|
|
126
|
+
/** GET {baseUrl}/api/tags; return installed model names, or null if unreachable. */
|
|
127
|
+
export async function fetchOllamaModels(baseUrl) {
|
|
128
|
+
try {
|
|
129
|
+
const res = await fetch(`${baseUrl.replace(/\/$/, '')}/api/tags`);
|
|
130
|
+
if (!res.ok)
|
|
131
|
+
return null;
|
|
132
|
+
const data = (await res.json());
|
|
133
|
+
return (data.models ?? []).map((m) => m.name);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return null;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* Migration health for the memory DB: 'missing' (no DB file → run init),
|
|
141
|
+
* 'ok' (the migrated `memories` table exists), 'pending' (DB present but not
|
|
142
|
+
* migrated), or 'unavailable' (memory package not installed / DB unreadable).
|
|
143
|
+
*/
|
|
144
|
+
export async function readMigrationState(dbPath) {
|
|
145
|
+
const mem = await loadMemory();
|
|
146
|
+
if (!mem || typeof mem.openDb !== 'function')
|
|
147
|
+
return 'unavailable';
|
|
148
|
+
if (!existsSync(dbPath))
|
|
149
|
+
return 'missing';
|
|
150
|
+
let handle;
|
|
151
|
+
try {
|
|
152
|
+
handle = mem.openDb(dbPath);
|
|
153
|
+
const migrated = await handle.k.schema.hasTable('memories');
|
|
154
|
+
return migrated ? 'ok' : 'pending';
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
return 'unavailable';
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
try {
|
|
161
|
+
await handle?.k?.destroy?.();
|
|
162
|
+
}
|
|
163
|
+
catch { /* best-effort close */ }
|
|
164
|
+
try {
|
|
165
|
+
handle?.close?.();
|
|
166
|
+
}
|
|
167
|
+
catch { /* best-effort close */ }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
//# sourceMappingURL=engine.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"engine.js","sourceRoot":"","sources":["../src/engine.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
export type Verb = 'init' | 'pair' | 'doctor' | 'update' | 'rollback' | 'enrich' | 'forget' | 'unbind';
|
|
3
|
+
/**
|
|
4
|
+
* Decide the verb. `create-fortytwo` with no verb means `init`; `fortytwo` with
|
|
5
|
+
* no verb prints usage. The invoked-as name comes from argv[1]'s basename.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveVerb(argv: string[], invokedAs: string): Verb | 'help' | null;
|
|
8
|
+
export declare function main(argv?: string[]): Promise<number>;
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AA4BA,MAAM,MAAM,IAAI,GACZ,MAAM,GACN,MAAM,GACN,QAAQ,GACR,QAAQ,GACR,UAAU,GACV,QAAQ,GACR,QAAQ,GACR,QAAQ,CAAC;AAwBb;;;GAGG;AACH,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,GAAG,IAAI,CASnF;AAED,wBAAsB,IAAI,CAAC,IAAI,WAAwB,GAAG,OAAO,CAAC,MAAM,CAAC,CAqBxE"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// @justfortytwo/installer — the all-in-one installer + lifecycle CLI.
|
|
3
|
+
//
|
|
4
|
+
// Exposed as two bins (same entry):
|
|
5
|
+
// create-fortytwo — the install-time alias (`npm create fortytwo` / `npx
|
|
6
|
+
// create-fortytwo`); with no verb it implies `init`.
|
|
7
|
+
// fortytwo — the post-install lifecycle alias for everyday verbs.
|
|
8
|
+
//
|
|
9
|
+
// This is the operator's single surface over BOTH halves of the system:
|
|
10
|
+
// - the npm ENGINE (@justfortytwo/memory, /gate, /telegram, embedder), and
|
|
11
|
+
// - the scaffolded PERSONA (CLAUDE.md + context/*, rendered from
|
|
12
|
+
// @justfortytwo/persona templates against .fortytwo/identity.json).
|
|
13
|
+
//
|
|
14
|
+
// init/doctor/unbind are wired against the local engine siblings; update/rollback
|
|
15
|
+
// (npm-registry installs) and forget (a memory deletion API) report clearly that
|
|
16
|
+
// they await publish / an upstream API rather than failing opaquely.
|
|
17
|
+
import { fileURLToPath } from 'node:url';
|
|
18
|
+
import { realpathSync } from 'node:fs';
|
|
19
|
+
import { runInit } from './commands/init.js';
|
|
20
|
+
import { runPair } from './commands/pair.js';
|
|
21
|
+
import { runDoctor } from './commands/doctor.js';
|
|
22
|
+
import { runUpdate } from './commands/update.js';
|
|
23
|
+
import { runRollback } from './commands/rollback.js';
|
|
24
|
+
import { runEnrich } from './commands/enrich.js';
|
|
25
|
+
import { runForget } from './commands/forget.js';
|
|
26
|
+
import { runUnbind } from './commands/unbind.js';
|
|
27
|
+
const USAGE = `fortytwo — install + lifecycle CLI for fortytwo
|
|
28
|
+
|
|
29
|
+
Usage:
|
|
30
|
+
create-fortytwo [init] [options] first-run install + scaffold (init implied)
|
|
31
|
+
fortytwo <verb> [options]
|
|
32
|
+
|
|
33
|
+
Verbs:
|
|
34
|
+
init Capture identity, write .env + .fortytwo/identity.json, render
|
|
35
|
+
persona, provision (ollama pull + db migrate), issue a pairing code.
|
|
36
|
+
pair Issue a one-time /login pairing code for a channel (e.g. Telegram).
|
|
37
|
+
doctor Health-check the engine: memory MCP contract, gate, db migrations,
|
|
38
|
+
embedder model, and declared peerDeps / fortytwo.compat.
|
|
39
|
+
update Resolve latest-in-range, install, run doctor, report. Points to
|
|
40
|
+
rollback on failure.
|
|
41
|
+
rollback Restore the prior version set recorded in .fortytwo/state.json.
|
|
42
|
+
enrich Deepen the persona by capturing more answers; re-render (no clobber).
|
|
43
|
+
forget Redact/remove specific memories from the memory MCP store.
|
|
44
|
+
unbind Revoke a channel binding (un-pair a chat / drop the allowlist entry).
|
|
45
|
+
|
|
46
|
+
Run "fortytwo <verb> --help" for verb-specific options.
|
|
47
|
+
`;
|
|
48
|
+
/**
|
|
49
|
+
* Decide the verb. `create-fortytwo` with no verb means `init`; `fortytwo` with
|
|
50
|
+
* no verb prints usage. The invoked-as name comes from argv[1]'s basename.
|
|
51
|
+
*/
|
|
52
|
+
export function resolveVerb(argv, invokedAs) {
|
|
53
|
+
const first = argv[0];
|
|
54
|
+
const verbs = ['init', 'pair', 'doctor', 'update', 'rollback', 'enrich', 'forget', 'unbind'];
|
|
55
|
+
if (first && verbs.includes(first))
|
|
56
|
+
return first;
|
|
57
|
+
if (!first || first === '--help' || first === '-h' || first === 'help') {
|
|
58
|
+
// `create-fortytwo` with no verb implies init; otherwise show help.
|
|
59
|
+
return invokedAs.startsWith('create-') && !first ? 'init' : 'help';
|
|
60
|
+
}
|
|
61
|
+
return null; // unknown verb
|
|
62
|
+
}
|
|
63
|
+
export async function main(argv = process.argv.slice(2)) {
|
|
64
|
+
const invokedAs = (process.argv[1] ?? '').split('/').pop() ?? 'fortytwo';
|
|
65
|
+
const verb = resolveVerb(argv, invokedAs);
|
|
66
|
+
const rest = argv.slice(verb && verb !== 'help' && argv[0] === verb ? 1 : 0);
|
|
67
|
+
switch (verb) {
|
|
68
|
+
case 'init': return runInit(rest);
|
|
69
|
+
case 'pair': return runPair(rest);
|
|
70
|
+
case 'doctor': return runDoctor(rest);
|
|
71
|
+
case 'update': return runUpdate(rest);
|
|
72
|
+
case 'rollback': return runRollback(rest);
|
|
73
|
+
case 'enrich': return runEnrich(rest);
|
|
74
|
+
case 'forget': return runForget(rest);
|
|
75
|
+
case 'unbind': return runUnbind(rest);
|
|
76
|
+
case 'help':
|
|
77
|
+
process.stdout.write(USAGE);
|
|
78
|
+
return 0;
|
|
79
|
+
default:
|
|
80
|
+
process.stderr.write(`Unknown command: ${argv[0]}\n\n${USAGE}`);
|
|
81
|
+
return 2;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// Run only when invoked as a bin — never when imported (e.g. by tests). Compare
|
|
85
|
+
// realpaths so the npm bin SYMLINK (node_modules/.bin/create-fortytwo -> dist/
|
|
86
|
+
// index.js) still matches this module's real path.
|
|
87
|
+
function invokedAsBin() {
|
|
88
|
+
const argv1 = process.argv[1];
|
|
89
|
+
if (!argv1)
|
|
90
|
+
return false;
|
|
91
|
+
try {
|
|
92
|
+
return realpathSync(argv1) === realpathSync(fileURLToPath(import.meta.url));
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (invokedAsBin()) {
|
|
99
|
+
main()
|
|
100
|
+
.then((code) => process.exit(code))
|
|
101
|
+
.catch((err) => {
|
|
102
|
+
process.stderr.write(`fortytwo: ${err?.stack ?? err}\n`);
|
|
103
|
+
process.exit(1);
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/render.d.ts
ADDED
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import type { Identity, Answers } from './state.js';
|
|
2
|
+
/** Maps each persona template to an output path + render mode. */
|
|
3
|
+
export interface PersonaFile {
|
|
4
|
+
/** Template path, relative to the persona package's templates dir. */
|
|
5
|
+
template: string;
|
|
6
|
+
/** Output path, relative to the project root. */
|
|
7
|
+
output: string;
|
|
8
|
+
/** CAPTURED = write-once (user-owned after); MANAGED = re-render every run. */
|
|
9
|
+
mode: 'captured' | 'managed';
|
|
10
|
+
}
|
|
11
|
+
/** One persona manifest field (drives `fortytwo init` prompts + answer resolution). */
|
|
12
|
+
export interface PersonaManifestField {
|
|
13
|
+
key: string;
|
|
14
|
+
prompt: string;
|
|
15
|
+
type: string;
|
|
16
|
+
required?: boolean;
|
|
17
|
+
default?: unknown;
|
|
18
|
+
}
|
|
19
|
+
/** The persona package's manifest, resolved for rendering. */
|
|
20
|
+
export interface PersonaManifest {
|
|
21
|
+
manifestVersion: number;
|
|
22
|
+
files: PersonaFile[];
|
|
23
|
+
/** The raw field descriptors (for init's prompt + answer resolution). */
|
|
24
|
+
fields: PersonaManifestField[];
|
|
25
|
+
/** Field keys the renderer must have a value for (manifest fields, required=true). */
|
|
26
|
+
requiredVars: string[];
|
|
27
|
+
/** Field keys that are optional (required=false). */
|
|
28
|
+
optionalVars: string[];
|
|
29
|
+
/** Absolute path to the package's `templates/` dir. */
|
|
30
|
+
templatesDir: string;
|
|
31
|
+
}
|
|
32
|
+
export interface RenderOptions {
|
|
33
|
+
/** Project root that receives CLAUDE.md + context/*. Defaults to cwd. */
|
|
34
|
+
root?: string;
|
|
35
|
+
/** Resolved location of the @justfortytwo/persona package (for loadPersonaManifest). */
|
|
36
|
+
personaPackageDir?: string;
|
|
37
|
+
/** If true, report what WOULD be written without touching disk. */
|
|
38
|
+
dryRun?: boolean;
|
|
39
|
+
/** Injected file map (hermetic/testable; bypasses loadPersonaManifest). */
|
|
40
|
+
files?: PersonaFile[];
|
|
41
|
+
/** Directory the `template` paths are resolved against (hermetic/testable). */
|
|
42
|
+
templatesDir?: string;
|
|
43
|
+
}
|
|
44
|
+
export interface RenderResult {
|
|
45
|
+
written: string[];
|
|
46
|
+
skipped: string[];
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Substitute `{{key}}` references with captured answers (flat, keyed by the
|
|
50
|
+
* persona manifest field keys — snake_case, exactly what templates use).
|
|
51
|
+
* - string value: inlined as-is.
|
|
52
|
+
* - list value (array): joined with "\n- " so it slots after a leading "- "
|
|
53
|
+
* in the template (the markdown bullet convention the templates rely on).
|
|
54
|
+
* Fails LOUDLY on a referenced variable that's missing/null — a half-filled
|
|
55
|
+
* persona (blank owner name, blank agent name) is worse than a clear error at
|
|
56
|
+
* scaffold time. Own-property only: `{{toString}}`/`{{__proto__}}` fail loud,
|
|
57
|
+
* they never resolve a prototype member into the rendered persona.
|
|
58
|
+
*/
|
|
59
|
+
export declare function renderTemplate(template: string, answers: Answers): string;
|
|
60
|
+
/**
|
|
61
|
+
* Locate the persona package and read its manifest. With `personaPackageDir`
|
|
62
|
+
* (tests, or an explicitly-resolved path) we read `<dir>/manifest.json`;
|
|
63
|
+
* otherwise we resolve `@justfortytwo/persona/manifest.json` from the installed
|
|
64
|
+
* dependency so we honor the user's installed version. The manifest's `files`
|
|
65
|
+
* map drives rendering; `fields` (required flag) gives the required/optional
|
|
66
|
+
* var split; templates live under `<packageDir>/templates`.
|
|
67
|
+
*/
|
|
68
|
+
export declare function loadPersonaManifest(personaPackageDir?: string): PersonaManifest;
|
|
69
|
+
/**
|
|
70
|
+
* Render the whole persona surface. Idempotent and non-clobbering:
|
|
71
|
+
* - MANAGED outputs: always (re)written from templates.
|
|
72
|
+
* - CAPTURED outputs: written only if they don't already exist; if present,
|
|
73
|
+
* left exactly as the user edited them (recorded in `skipped`).
|
|
74
|
+
* The file map + templates dir come from the persona manifest unless injected
|
|
75
|
+
* via opts (hermetic tests).
|
|
76
|
+
*/
|
|
77
|
+
export declare function renderPersona(identity: Identity, opts?: RenderOptions): RenderResult;
|
|
78
|
+
//# sourceMappingURL=render.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAkBA,OAAO,KAAK,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,YAAY,CAAC;AAEpD,kEAAkE;AAClE,MAAM,WAAW,WAAW;IAC1B,sEAAsE;IACtE,QAAQ,EAAE,MAAM,CAAC;IACjB,iDAAiD;IACjD,MAAM,EAAE,MAAM,CAAC;IACf,+EAA+E;IAC/E,IAAI,EAAE,UAAU,GAAG,SAAS,CAAC;CAC9B;AAED,uFAAuF;AACvF,MAAM,WAAW,oBAAoB;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAED,8DAA8D;AAC9D,MAAM,WAAW,eAAe;IAC9B,eAAe,EAAE,MAAM,CAAC;IACxB,KAAK,EAAE,WAAW,EAAE,CAAC;IACrB,yEAAyE;IACzE,MAAM,EAAE,oBAAoB,EAAE,CAAC;IAC/B,sFAAsF;IACtF,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,qDAAqD;IACrD,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,uDAAuD;IACvD,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,aAAa;IAC5B,yEAAyE;IACzE,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,wFAAwF;IACxF,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,mEAAmE;IACnE,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,2EAA2E;IAC3E,KAAK,CAAC,EAAE,WAAW,EAAE,CAAC;IACtB,+EAA+E;IAC/E,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,EAAE,CAAC;IAClB,OAAO,EAAE,MAAM,EAAE,CAAC;CACnB;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,OAAO,GAAG,MAAM,CASzE;AAED;;;;;;;GAOG;AACH,wBAAgB,mBAAmB,CAAC,iBAAiB,CAAC,EAAE,MAAM,GAAG,eAAe,CAyB/E;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,GAAE,aAAkB,GAAG,YAAY,CAkCxF"}
|
package/dist/render.js
ADDED
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
// render.ts — materialize the PERSONA SURFACE.
|
|
2
|
+
//
|
|
3
|
+
// Two-surface model:
|
|
4
|
+
// 1. The npm "engine" (memory MCP, safety gate, channel adapters, embedder) —
|
|
5
|
+
// installed as @justfortytwo/* packages, wired as Claude Code plugins.
|
|
6
|
+
// 2. The PERSONA — CLAUDE.md + context/* . This is NOT a plugin. It is per-user,
|
|
7
|
+
// gitignored, and personal. The CLI SCAFFOLDS it by rendering the
|
|
8
|
+
// @justfortytwo/persona package's `templates/` against the user's captured
|
|
9
|
+
// `.fortytwo/identity.json`, guided by that package's manifest.
|
|
10
|
+
//
|
|
11
|
+
// IDEMPOTENCE CONTRACT: re-rendering must NOT clobber captured fields. A user who
|
|
12
|
+
// hand-edits context/SOUL.md after init keeps those edits on the next
|
|
13
|
+
// `update`/`enrich` re-render. Strategy: MANAGED outputs are (re)written every
|
|
14
|
+
// run; CAPTURED outputs are written only on first render (when absent).
|
|
15
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
16
|
+
import { dirname, join, resolve, sep } from 'node:path';
|
|
17
|
+
import { createRequire } from 'node:module';
|
|
18
|
+
/**
|
|
19
|
+
* Substitute `{{key}}` references with captured answers (flat, keyed by the
|
|
20
|
+
* persona manifest field keys — snake_case, exactly what templates use).
|
|
21
|
+
* - string value: inlined as-is.
|
|
22
|
+
* - list value (array): joined with "\n- " so it slots after a leading "- "
|
|
23
|
+
* in the template (the markdown bullet convention the templates rely on).
|
|
24
|
+
* Fails LOUDLY on a referenced variable that's missing/null — a half-filled
|
|
25
|
+
* persona (blank owner name, blank agent name) is worse than a clear error at
|
|
26
|
+
* scaffold time. Own-property only: `{{toString}}`/`{{__proto__}}` fail loud,
|
|
27
|
+
* they never resolve a prototype member into the rendered persona.
|
|
28
|
+
*/
|
|
29
|
+
export function renderTemplate(template, answers) {
|
|
30
|
+
return template.replace(/\{\{\s*([\w.]+)\s*\}\}/g, (_m, key) => {
|
|
31
|
+
const has = Object.prototype.hasOwnProperty.call(answers, key);
|
|
32
|
+
const v = has ? answers[key] : undefined;
|
|
33
|
+
if (v === undefined || v === null) {
|
|
34
|
+
throw new Error(`renderTemplate: missing required variable {{${key}}} in identity`);
|
|
35
|
+
}
|
|
36
|
+
return Array.isArray(v) ? v.join('\n- ') : String(v);
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Locate the persona package and read its manifest. With `personaPackageDir`
|
|
41
|
+
* (tests, or an explicitly-resolved path) we read `<dir>/manifest.json`;
|
|
42
|
+
* otherwise we resolve `@justfortytwo/persona/manifest.json` from the installed
|
|
43
|
+
* dependency so we honor the user's installed version. The manifest's `files`
|
|
44
|
+
* map drives rendering; `fields` (required flag) gives the required/optional
|
|
45
|
+
* var split; templates live under `<packageDir>/templates`.
|
|
46
|
+
*/
|
|
47
|
+
export function loadPersonaManifest(personaPackageDir) {
|
|
48
|
+
let manifestPath;
|
|
49
|
+
let pkgDir;
|
|
50
|
+
if (personaPackageDir) {
|
|
51
|
+
pkgDir = resolve(personaPackageDir);
|
|
52
|
+
manifestPath = join(pkgDir, 'manifest.json');
|
|
53
|
+
}
|
|
54
|
+
else {
|
|
55
|
+
const require = createRequire(import.meta.url);
|
|
56
|
+
manifestPath = require.resolve('@justfortytwo/persona/manifest.json');
|
|
57
|
+
pkgDir = dirname(manifestPath);
|
|
58
|
+
}
|
|
59
|
+
const raw = JSON.parse(readFileSync(manifestPath, 'utf8'));
|
|
60
|
+
const fields = raw.fields ?? [];
|
|
61
|
+
return {
|
|
62
|
+
manifestVersion: raw.manifestVersion ?? 1,
|
|
63
|
+
files: raw.files ?? [],
|
|
64
|
+
fields,
|
|
65
|
+
requiredVars: fields.filter((f) => f.required).map((f) => f.key),
|
|
66
|
+
optionalVars: fields.filter((f) => !f.required).map((f) => f.key),
|
|
67
|
+
templatesDir: join(pkgDir, 'templates'),
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Render the whole persona surface. Idempotent and non-clobbering:
|
|
72
|
+
* - MANAGED outputs: always (re)written from templates.
|
|
73
|
+
* - CAPTURED outputs: written only if they don't already exist; if present,
|
|
74
|
+
* left exactly as the user edited them (recorded in `skipped`).
|
|
75
|
+
* The file map + templates dir come from the persona manifest unless injected
|
|
76
|
+
* via opts (hermetic tests).
|
|
77
|
+
*/
|
|
78
|
+
export function renderPersona(identity, opts = {}) {
|
|
79
|
+
const root = resolve(opts.root ?? process.cwd());
|
|
80
|
+
let files = opts.files;
|
|
81
|
+
let tplRoot = opts.templatesDir ? resolve(opts.templatesDir) : undefined;
|
|
82
|
+
if (!files || !tplRoot) {
|
|
83
|
+
const manifest = loadPersonaManifest(opts.personaPackageDir);
|
|
84
|
+
files = files ?? manifest.files;
|
|
85
|
+
tplRoot = tplRoot ?? resolve(manifest.templatesDir);
|
|
86
|
+
}
|
|
87
|
+
const written = [];
|
|
88
|
+
const skipped = [];
|
|
89
|
+
for (const f of files) {
|
|
90
|
+
// Defense-in-depth: even though the manifest ships in the trusted persona
|
|
91
|
+
// package, never let an output/template path escape its root via `..`.
|
|
92
|
+
const outPath = resolve(root, f.output);
|
|
93
|
+
if (outPath !== root && !outPath.startsWith(root + sep)) {
|
|
94
|
+
throw new Error(`renderPersona: output path escapes project root: ${f.output}`);
|
|
95
|
+
}
|
|
96
|
+
if (f.mode === 'captured' && existsSync(outPath)) {
|
|
97
|
+
skipped.push(f.output);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
const tplPath = resolve(tplRoot, f.template);
|
|
101
|
+
if (!tplPath.startsWith(tplRoot + sep)) {
|
|
102
|
+
throw new Error(`renderPersona: template path escapes templates dir: ${f.template}`);
|
|
103
|
+
}
|
|
104
|
+
const rendered = renderTemplate(readFileSync(tplPath, 'utf8'), identity.answers);
|
|
105
|
+
if (!opts.dryRun) {
|
|
106
|
+
mkdirSync(dirname(outPath), { recursive: true });
|
|
107
|
+
writeFileSync(outPath, rendered, 'utf8');
|
|
108
|
+
}
|
|
109
|
+
written.push(f.output);
|
|
110
|
+
}
|
|
111
|
+
return { written, skipped };
|
|
112
|
+
}
|
|
113
|
+
//# sourceMappingURL=render.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
|
package/dist/state.d.ts
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export declare const FORTYTWO_DIR = ".fortytwo";
|
|
2
|
+
export declare const IDENTITY_FILE = "identity.json";
|
|
3
|
+
export declare const STATE_FILE = "state.json";
|
|
4
|
+
/**
|
|
5
|
+
* Captured answers, keyed by the persona manifest's field `key` (snake_case) —
|
|
6
|
+
* exactly the `{{tokens}}` the templates reference. A `string` for scalar
|
|
7
|
+
* fields, a `string[]` for `list` fields. This is the single map render.ts
|
|
8
|
+
* substitutes against; there is no separate owner sub-object.
|
|
9
|
+
*/
|
|
10
|
+
export type Answers = Record<string, string | string[]>;
|
|
11
|
+
/** Channel binding captured at init/pair (e.g. the Telegram chat allowlist). */
|
|
12
|
+
export interface ChannelBinding {
|
|
13
|
+
channel: 'telegram' | string;
|
|
14
|
+
allowedChatIds?: string[];
|
|
15
|
+
pairedAt?: string;
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* `.fortytwo/identity.json` — captured answers. Persona templates render
|
|
19
|
+
* against this. Secrets do NOT live here (they go to `.env`); only the
|
|
20
|
+
* non-secret identity/answers the persona needs to render.
|
|
21
|
+
*/
|
|
22
|
+
export interface Identity {
|
|
23
|
+
/** Schema version of THIS file's shape, so future CLI versions can migrate it. */
|
|
24
|
+
identityVersion: number;
|
|
25
|
+
/**
|
|
26
|
+
* Captured answers keyed by persona manifest field key (e.g. `agent_name`,
|
|
27
|
+
* `owner_name`, `owner_values`). `enrich` adds/updates keys here over time.
|
|
28
|
+
*/
|
|
29
|
+
answers: Answers;
|
|
30
|
+
channels?: ChannelBinding[];
|
|
31
|
+
createdAt: string;
|
|
32
|
+
updatedAt: string;
|
|
33
|
+
}
|
|
34
|
+
/** One resolved sibling-package version pin. */
|
|
35
|
+
export interface VersionPin {
|
|
36
|
+
name: string;
|
|
37
|
+
range: string;
|
|
38
|
+
resolved: string;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* `.fortytwo/state.json` — installed version set + rollback ledger.
|
|
42
|
+
* `current` is what's installed now; `previous` is the set BEFORE the last
|
|
43
|
+
* update (the rollback target). Update safety = install latest-in-range, run
|
|
44
|
+
* doctor (post-verify health check), and on success keep `current`; rollback is
|
|
45
|
+
* MANUAL — the user runs `fortytwo rollback`, which restores `previous`.
|
|
46
|
+
*/
|
|
47
|
+
export interface InstallState {
|
|
48
|
+
stateVersion: number;
|
|
49
|
+
current: VersionPin[];
|
|
50
|
+
previous: VersionPin[] | null;
|
|
51
|
+
lastUpdatedAt: string;
|
|
52
|
+
}
|
|
53
|
+
export declare function readIdentity(root?: string): Identity | null;
|
|
54
|
+
export declare function writeIdentity(identity: Identity, root?: string): void;
|
|
55
|
+
export declare function readState(root?: string): InstallState | null;
|
|
56
|
+
export declare function writeState(state: InstallState, root?: string): void;
|
|
57
|
+
export declare function recordVersionSet(next: VersionPin[], root?: string): InstallState;
|
|
58
|
+
//# sourceMappingURL=state.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../src/state.ts"],"names":[],"mappings":"AAuBA,eAAO,MAAM,YAAY,cAAc,CAAC;AACxC,eAAO,MAAM,aAAa,kBAAkB,CAAC;AAC7C,eAAO,MAAM,UAAU,eAAe,CAAC;AAEvC;;;;;GAKG;AACH,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC,CAAC;AAExD,gFAAgF;AAChF,MAAM,WAAW,cAAc;IAG7B,OAAO,EAAE,UAAU,GAAG,MAAM,CAAC;IAC7B,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED;;;;GAIG;AACH,MAAM,WAAW,QAAQ;IACvB,kFAAkF;IAClF,eAAe,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,CAAC,EAAE,cAAc,EAAE,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,gDAAgD;AAChD,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,YAAY;IAC3B,YAAY,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,UAAU,EAAE,CAAC;IACtB,QAAQ,EAAE,UAAU,EAAE,GAAG,IAAI,CAAC;IAC9B,aAAa,EAAE,MAAM,CAAC;CACvB;AAyBD,wBAAgB,YAAY,CAAC,IAAI,SAAgB,GAAG,QAAQ,GAAG,IAAI,CAElE;AAED,wBAAgB,aAAa,CAAC,QAAQ,EAAE,QAAQ,EAAE,IAAI,SAAgB,GAAG,IAAI,CAQ5E;AAID,wBAAgB,SAAS,CAAC,IAAI,SAAgB,GAAG,YAAY,GAAG,IAAI,CAEnE;AAED,wBAAgB,UAAU,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,SAAgB,GAAG,IAAI,CAE1E;AAeD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,UAAU,EAAE,EAAE,IAAI,SAAgB,GAAG,YAAY,CAevF"}
|