@powerhousedao/ph-clint-cli 0.1.0-dev.2 → 0.1.0-dev.20
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/dist/cli.js +13 -8
- package/dist/codegen/builders/app-index-ts.d.ts +3 -10
- package/dist/codegen/builders/app-index-ts.js +10 -14
- package/dist/codegen/builders/cli-package-json.js +47 -8
- package/dist/codegen/builders/cli-ts.js +18 -15
- package/dist/codegen/builders/config-ts.js +11 -15
- package/dist/codegen/builders/framework-gen-ts.d.ts +4 -5
- package/dist/codegen/builders/framework-gen-ts.js +22 -19
- package/dist/codegen/builders/framework-ts.js +21 -1
- package/dist/codegen/builders/index.d.ts +1 -0
- package/dist/codegen/builders/index.js +1 -0
- package/dist/codegen/builders/publish-config-ts.d.ts +6 -0
- package/dist/codegen/builders/publish-config-ts.js +21 -0
- package/dist/codegen/builders/readme-md.js +8 -7
- package/dist/codegen/builders/root-package-json.d.ts +3 -0
- package/dist/codegen/builders/root-package-json.js +24 -11
- package/dist/codegen/index.js +24 -11
- package/dist/codegen/scaffold.d.ts +8 -0
- package/dist/codegen/scaffold.js +51 -2
- package/dist/commands/clint-project-init.d.ts +11 -6
- package/dist/commands/clint-project-init.js +8 -7
- package/dist/commands/clint-project-publish.d.ts +1 -0
- package/dist/commands/clint-project-publish.js +5 -3
- package/dist/commands/clint-project-regen.js +2 -2
- package/dist/commands/clint-skills-sync.d.ts +10 -0
- package/dist/commands/clint-skills-sync.js +56 -0
- package/dist/config.d.ts +3 -5
- package/dist/config.js +5 -7
- package/dist/framework.d.ts +5 -160
- package/dist/framework.gen.d.ts +1 -32
- package/dist/framework.gen.js +1 -1
- package/dist/skills/sync.d.ts +40 -0
- package/dist/skills/sync.js +119 -0
- package/dist/spec/from-document.js +11 -7
- package/dist/spec/types.d.ts +55 -6
- package/dist/spec/types.js +61 -25
- package/dist/triggers/publish-trigger.d.ts +11 -0
- package/dist/triggers/publish-trigger.js +172 -0
- package/dist/triggers/spec-change.d.ts +1 -32
- package/dist/triggers/spec-change.js +59 -0
- package/package.json +5 -5
package/dist/framework.gen.d.ts
CHANGED
|
@@ -2,36 +2,5 @@
|
|
|
2
2
|
* Typed document registry derived from the reactor package's document models.
|
|
3
3
|
* Regenerated whenever the spec's `documentTypes` list changes.
|
|
4
4
|
*/
|
|
5
|
-
export declare const registry: import("@powerhousedao/ph-clint").InferRegistry<readonly [
|
|
6
|
-
readonly version: 1;
|
|
7
|
-
readonly reducer: import("document-model").Reducer<import("@powerhousedao/ph-clint-app").PhClintProjectPHState>;
|
|
8
|
-
readonly actions: {
|
|
9
|
-
enableRoutine: (input: import("@powerhousedao/ph-clint-app").EnableRoutineInput) => import("@powerhousedao/ph-clint-app").EnableRoutineAction;
|
|
10
|
-
disableRoutine: (input: import("@powerhousedao/ph-clint-app").DisableRoutineInput) => import("@powerhousedao/ph-clint-app").DisableRoutineAction;
|
|
11
|
-
enableMastra: (input: import("@powerhousedao/ph-clint-app").EnableMastraInput) => import("@powerhousedao/ph-clint-app").EnableMastraAction;
|
|
12
|
-
disableMastra: (input: import("@powerhousedao/ph-clint-app").DisableMastraInput) => import("@powerhousedao/ph-clint-app").DisableMastraAction;
|
|
13
|
-
enablePowerhouse: (input: import("@powerhousedao/ph-clint-app").EnablePowerhouseInput) => import("@powerhousedao/ph-clint-app").EnablePowerhouseAction;
|
|
14
|
-
setPowerhouseSwitchboard: (input: import("@powerhousedao/ph-clint-app").SetPowerhouseSwitchboardInput) => import("@powerhousedao/ph-clint-app").SetPowerhouseSwitchboardAction;
|
|
15
|
-
setPowerhouseConnect: (input: import("@powerhousedao/ph-clint-app").SetPowerhouseConnectInput) => import("@powerhousedao/ph-clint-app").SetPowerhouseConnectAction;
|
|
16
|
-
setPackageName: (input: import("@powerhousedao/ph-clint-app").SetPackageNameInput) => import("@powerhousedao/ph-clint-app").SetPackageNameAction;
|
|
17
|
-
clearBin: (input: import("@powerhousedao/ph-clint-app").ClearBinInput) => import("@powerhousedao/ph-clint-app").ClearBinAction;
|
|
18
|
-
setBin: (input: import("@powerhousedao/ph-clint-app").SetBinInput) => import("@powerhousedao/ph-clint-app").SetBinAction;
|
|
19
|
-
setDescription: (input: import("@powerhousedao/ph-clint-app").SetDescriptionInput) => import("@powerhousedao/ph-clint-app").SetDescriptionAction;
|
|
20
|
-
setVersion: (input: import("@powerhousedao/ph-clint-app").SetVersionInput) => import("@powerhousedao/ph-clint-app").SetVersionAction;
|
|
21
|
-
clearScope: (input: import("@powerhousedao/ph-clint-app").ClearScopeInput) => import("@powerhousedao/ph-clint-app").ClearScopeAction;
|
|
22
|
-
setScope: (input: import("@powerhousedao/ph-clint-app").SetScopeInput) => import("@powerhousedao/ph-clint-app").SetScopeAction;
|
|
23
|
-
setName: (name: string | {
|
|
24
|
-
name: string;
|
|
25
|
-
}) => import("document-model").SetNameAction;
|
|
26
|
-
undo: (count?: number, scope?: string) => import("document-model").UndoAction;
|
|
27
|
-
redo: (count?: number, scope?: string) => import("document-model").RedoAction;
|
|
28
|
-
prune: (start?: number, end?: number, scope?: string) => import("document-model").SchemaPruneAction;
|
|
29
|
-
loadState: <TState extends import("document-model").PHBaseState = import("document-model").PHBaseState>(state: TState & {
|
|
30
|
-
name: string;
|
|
31
|
-
}, operations: number) => import("document-model").LoadStateAction;
|
|
32
|
-
noop: (scope?: string) => import("document-model").NOOPAction;
|
|
33
|
-
};
|
|
34
|
-
readonly utils: import("document-model").DocumentModelUtils<import("@powerhousedao/ph-clint-app").PhClintProjectPHState>;
|
|
35
|
-
readonly documentModel: import("document-model").DocumentModelPHState;
|
|
36
|
-
}]>;
|
|
5
|
+
export declare const registry: import("@powerhousedao/ph-clint").InferRegistry<readonly [import("document-model").DocumentModelModule<import("@powerhousedao/ph-clint-app").PhClintProjectPHState>]>;
|
|
37
6
|
export type Registry = typeof registry;
|
package/dist/framework.gen.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* THIS FILE IS GENERATED BY ph-clint-cli. DO NOT EDIT.
|
|
3
3
|
*
|
|
4
|
-
* Regenerated on every `ph-clint regen` and on every spec-change tick.
|
|
4
|
+
* Regenerated on every `ph-clint clint-project-regen` and on every spec-change tick.
|
|
5
5
|
* Put custom bindings in `framework.ts`, not here — edits to this file
|
|
6
6
|
* will be overwritten.
|
|
7
7
|
*/
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { ExternalSkill } from '../spec/types.js';
|
|
2
|
+
/** On-disk manifest entry (what is currently installed). */
|
|
3
|
+
export interface ManifestEntry {
|
|
4
|
+
name: string;
|
|
5
|
+
githubUrl: string;
|
|
6
|
+
}
|
|
7
|
+
/** Persisted manifest — a simple JSON array. */
|
|
8
|
+
export type SkillsManifest = ManifestEntry[];
|
|
9
|
+
export interface SyncResult {
|
|
10
|
+
added: string[];
|
|
11
|
+
removed: string[];
|
|
12
|
+
unchanged: string[];
|
|
13
|
+
}
|
|
14
|
+
export interface SyncOptions {
|
|
15
|
+
/** Project root (CLI sub-tree when split layout). */
|
|
16
|
+
targetDir: string;
|
|
17
|
+
/** Desired state from the spec. */
|
|
18
|
+
desired: ExternalSkill[];
|
|
19
|
+
/** Optional log callback. */
|
|
20
|
+
log?: (msg: string) => void;
|
|
21
|
+
}
|
|
22
|
+
export declare function readManifest(targetDir: string): Promise<SkillsManifest>;
|
|
23
|
+
/**
|
|
24
|
+
* Compute the diff between desired (spec) and current (manifest) skills.
|
|
25
|
+
*
|
|
26
|
+
* Pure function — no I/O.
|
|
27
|
+
*/
|
|
28
|
+
export declare function diffSkills(desired: ExternalSkill[], current: SkillsManifest): {
|
|
29
|
+
toAdd: ExternalSkill[];
|
|
30
|
+
toRemove: ManifestEntry[];
|
|
31
|
+
unchanged: string[];
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Synchronise external skills on disk to match the spec.
|
|
35
|
+
*
|
|
36
|
+
* - Clones missing skills via `git clone --depth 1`.
|
|
37
|
+
* - Removes skills no longer in the spec.
|
|
38
|
+
* - Updates the `.skills-manifest.json`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function syncExternalSkills(opts: SyncOptions): Promise<SyncResult>;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* External skills synchronisation.
|
|
3
|
+
*
|
|
4
|
+
* Reads the desired external skills from a `ClintProjectSpec`, diffs them
|
|
5
|
+
* against a `.skills-manifest.json` on disk, and runs `git clone` / `rm -rf`
|
|
6
|
+
* to reconcile. Each external skill ends up as a subdirectory of
|
|
7
|
+
* `prompts/skills-ext/<name>/` so the build-skills pipeline picks it up.
|
|
8
|
+
*/
|
|
9
|
+
import { spawn } from 'node:child_process';
|
|
10
|
+
import fs from 'node:fs/promises';
|
|
11
|
+
import path from 'node:path';
|
|
12
|
+
const MANIFEST_FILE = '.skills-manifest.json';
|
|
13
|
+
const SKILLS_DIR = path.join('prompts', 'skills-ext');
|
|
14
|
+
function manifestPath(targetDir) {
|
|
15
|
+
return path.join(targetDir, SKILLS_DIR, MANIFEST_FILE);
|
|
16
|
+
}
|
|
17
|
+
export async function readManifest(targetDir) {
|
|
18
|
+
try {
|
|
19
|
+
const raw = await fs.readFile(manifestPath(targetDir), 'utf8');
|
|
20
|
+
return JSON.parse(raw);
|
|
21
|
+
}
|
|
22
|
+
catch {
|
|
23
|
+
return [];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
async function writeManifest(targetDir, manifest) {
|
|
27
|
+
const file = manifestPath(targetDir);
|
|
28
|
+
await fs.mkdir(path.dirname(file), { recursive: true });
|
|
29
|
+
await fs.writeFile(file, JSON.stringify(manifest, null, 2) + '\n', 'utf8');
|
|
30
|
+
}
|
|
31
|
+
function execGit(args, cwd) {
|
|
32
|
+
return new Promise((resolve, reject) => {
|
|
33
|
+
const proc = spawn('git', args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
34
|
+
let stderr = '';
|
|
35
|
+
if (proc.stderr) {
|
|
36
|
+
proc.stderr.on('data', (d) => {
|
|
37
|
+
stderr += d.toString();
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
proc.on('close', (code) => resolve({ code: code ?? 1, stderr }));
|
|
41
|
+
proc.on('error', (err) => reject(err));
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Compute the diff between desired (spec) and current (manifest) skills.
|
|
46
|
+
*
|
|
47
|
+
* Pure function — no I/O.
|
|
48
|
+
*/
|
|
49
|
+
export function diffSkills(desired, current) {
|
|
50
|
+
const desiredMap = new Map(desired.map((s) => [s.name, s]));
|
|
51
|
+
const currentMap = new Map(current.map((s) => [s.name, s]));
|
|
52
|
+
const toAdd = [];
|
|
53
|
+
const toRemove = [];
|
|
54
|
+
const unchanged = [];
|
|
55
|
+
for (const [name, skill] of desiredMap) {
|
|
56
|
+
const existing = currentMap.get(name);
|
|
57
|
+
if (!existing) {
|
|
58
|
+
toAdd.push(skill);
|
|
59
|
+
}
|
|
60
|
+
else if (existing.githubUrl !== skill.githubUrl) {
|
|
61
|
+
// URL changed — remove old, add new.
|
|
62
|
+
toRemove.push(existing);
|
|
63
|
+
toAdd.push(skill);
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
unchanged.push(name);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
for (const [name, entry] of currentMap) {
|
|
70
|
+
if (!desiredMap.has(name)) {
|
|
71
|
+
toRemove.push(entry);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return { toAdd, toRemove, unchanged };
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Synchronise external skills on disk to match the spec.
|
|
78
|
+
*
|
|
79
|
+
* - Clones missing skills via `git clone --depth 1`.
|
|
80
|
+
* - Removes skills no longer in the spec.
|
|
81
|
+
* - Updates the `.skills-manifest.json`.
|
|
82
|
+
*/
|
|
83
|
+
export async function syncExternalSkills(opts) {
|
|
84
|
+
const { targetDir, desired, log } = opts;
|
|
85
|
+
const skillsDir = path.join(targetDir, SKILLS_DIR);
|
|
86
|
+
await fs.mkdir(skillsDir, { recursive: true });
|
|
87
|
+
const current = await readManifest(targetDir);
|
|
88
|
+
const { toAdd, toRemove, unchanged } = diffSkills(desired, current);
|
|
89
|
+
const result = { added: [], removed: [], unchanged };
|
|
90
|
+
// Remove skills.
|
|
91
|
+
for (const entry of toRemove) {
|
|
92
|
+
const dir = path.join(skillsDir, entry.name);
|
|
93
|
+
log?.(`Removing skill "${entry.name}"…`);
|
|
94
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
95
|
+
result.removed.push(entry.name);
|
|
96
|
+
}
|
|
97
|
+
// Add skills.
|
|
98
|
+
for (const skill of toAdd) {
|
|
99
|
+
const dir = path.join(skillsDir, skill.name);
|
|
100
|
+
log?.(`Cloning skill "${skill.name}" from ${skill.githubUrl}…`);
|
|
101
|
+
// Ensure the target doesn't exist (re-add after URL change).
|
|
102
|
+
await fs.rm(dir, { recursive: true, force: true });
|
|
103
|
+
const { code, stderr } = await execGit(['clone', '--depth', '1', skill.githubUrl, skill.name], skillsDir);
|
|
104
|
+
if (code !== 0) {
|
|
105
|
+
log?.(`Warning: git clone failed for "${skill.name}": ${stderr.trim()}`);
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
// Remove .git dir — we don't want a nested repo.
|
|
109
|
+
await fs.rm(path.join(dir, '.git'), { recursive: true, force: true });
|
|
110
|
+
result.added.push(skill.name);
|
|
111
|
+
}
|
|
112
|
+
// Write updated manifest.
|
|
113
|
+
const newManifest = desired.map((s) => ({
|
|
114
|
+
name: s.name,
|
|
115
|
+
githubUrl: s.githubUrl,
|
|
116
|
+
}));
|
|
117
|
+
await writeManifest(targetDir, newManifest);
|
|
118
|
+
return result;
|
|
119
|
+
}
|
|
@@ -8,11 +8,6 @@ import { clintProjectSpecSchema, } from './types.js';
|
|
|
8
8
|
export function specFromDocumentState(state) {
|
|
9
9
|
if (!state.name)
|
|
10
10
|
return null;
|
|
11
|
-
// `documentTypes` is a forward-compatible field — older `ph-clint-project`
|
|
12
|
-
// document states don't declare it yet. Pull through if present, otherwise
|
|
13
|
-
// leave it to the schema default (`[]`). Codegen treats the empty list as
|
|
14
|
-
// "no registry entries" and emits an empty `defineRegistry([...])`.
|
|
15
|
-
const documentTypes = state.documentTypes ?? undefined;
|
|
16
11
|
const parsed = clintProjectSpecSchema.safeParse({
|
|
17
12
|
name: state.name,
|
|
18
13
|
scope: state.scope ?? undefined,
|
|
@@ -20,11 +15,20 @@ export function specFromDocumentState(state) {
|
|
|
20
15
|
description: state.description,
|
|
21
16
|
bin: state.bin ?? undefined,
|
|
22
17
|
features: {
|
|
23
|
-
powerhouse:
|
|
18
|
+
powerhouse: state.features.powerhouse,
|
|
24
19
|
mastra: { ...state.features.mastra },
|
|
25
20
|
routine: { ...state.features.routine },
|
|
26
21
|
},
|
|
27
|
-
|
|
22
|
+
packages: state.packages.map((p) => ({
|
|
23
|
+
id: p.id,
|
|
24
|
+
packageName: p.packageName,
|
|
25
|
+
documentTypes: [...p.documentTypes],
|
|
26
|
+
})),
|
|
27
|
+
externalSkills: state.externalSkills.map((s) => ({
|
|
28
|
+
id: s.id,
|
|
29
|
+
name: s.name,
|
|
30
|
+
githubUrl: s.githubUrl,
|
|
31
|
+
})),
|
|
28
32
|
});
|
|
29
33
|
return parsed.success ? parsed.data : null;
|
|
30
34
|
}
|
package/dist/spec/types.d.ts
CHANGED
|
@@ -9,6 +9,32 @@
|
|
|
9
9
|
* generator step actually needs it.
|
|
10
10
|
*/
|
|
11
11
|
import { z } from 'zod';
|
|
12
|
+
/**
|
|
13
|
+
* Ordered Powerhouse integration level. Higher levels imply all lower ones:
|
|
14
|
+
* Disabled → Reactor → Switchboard → Connect
|
|
15
|
+
*/
|
|
16
|
+
export declare const POWERHOUSE_LEVELS: readonly ["Disabled", "Reactor", "Switchboard", "Connect"];
|
|
17
|
+
export declare const powerhouseLevelSchema: z.ZodDefault<z.ZodEnum<{
|
|
18
|
+
Disabled: "Disabled";
|
|
19
|
+
Reactor: "Reactor";
|
|
20
|
+
Switchboard: "Switchboard";
|
|
21
|
+
Connect: "Connect";
|
|
22
|
+
}>>;
|
|
23
|
+
export type PowerhouseLevel = z.infer<typeof powerhouseLevelSchema>;
|
|
24
|
+
/** Check whether `level` is at least `threshold` in the ordered enum. */
|
|
25
|
+
export declare function phAtLeast(level: PowerhouseLevel, threshold: PowerhouseLevel): boolean;
|
|
26
|
+
export declare const powerhousePackageSchema: z.ZodObject<{
|
|
27
|
+
id: z.ZodString;
|
|
28
|
+
packageName: z.ZodString;
|
|
29
|
+
documentTypes: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
30
|
+
}, z.core.$strip>;
|
|
31
|
+
export type PowerhousePackage = z.infer<typeof powerhousePackageSchema>;
|
|
32
|
+
export declare const externalSkillSchema: z.ZodObject<{
|
|
33
|
+
id: z.ZodString;
|
|
34
|
+
name: z.ZodString;
|
|
35
|
+
githubUrl: z.ZodString;
|
|
36
|
+
}, z.core.$strip>;
|
|
37
|
+
export type ExternalSkill = z.infer<typeof externalSkillSchema>;
|
|
12
38
|
export declare const clintProjectSpecSchema: z.ZodObject<{
|
|
13
39
|
name: z.ZodString;
|
|
14
40
|
scope: z.ZodOptional<z.ZodString>;
|
|
@@ -16,11 +42,12 @@ export declare const clintProjectSpecSchema: z.ZodObject<{
|
|
|
16
42
|
description: z.ZodDefault<z.ZodString>;
|
|
17
43
|
bin: z.ZodOptional<z.ZodString>;
|
|
18
44
|
features: z.ZodDefault<z.ZodObject<{
|
|
19
|
-
powerhouse: z.ZodDefault<z.
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
45
|
+
powerhouse: z.ZodDefault<z.ZodEnum<{
|
|
46
|
+
Disabled: "Disabled";
|
|
47
|
+
Reactor: "Reactor";
|
|
48
|
+
Switchboard: "Switchboard";
|
|
49
|
+
Connect: "Connect";
|
|
50
|
+
}>>;
|
|
24
51
|
mastra: z.ZodDefault<z.ZodObject<{
|
|
25
52
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
26
53
|
}, z.core.$strip>>;
|
|
@@ -28,7 +55,16 @@ export declare const clintProjectSpecSchema: z.ZodObject<{
|
|
|
28
55
|
enabled: z.ZodDefault<z.ZodBoolean>;
|
|
29
56
|
}, z.core.$strip>>;
|
|
30
57
|
}, z.core.$strip>>;
|
|
31
|
-
|
|
58
|
+
packages: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
59
|
+
id: z.ZodString;
|
|
60
|
+
packageName: z.ZodString;
|
|
61
|
+
documentTypes: z.ZodDefault<z.ZodArray<z.ZodString>>;
|
|
62
|
+
}, z.core.$strip>>>;
|
|
63
|
+
externalSkills: z.ZodDefault<z.ZodArray<z.ZodObject<{
|
|
64
|
+
id: z.ZodString;
|
|
65
|
+
name: z.ZodString;
|
|
66
|
+
githubUrl: z.ZodString;
|
|
67
|
+
}, z.core.$strip>>>;
|
|
32
68
|
}, z.core.$strip>;
|
|
33
69
|
export type ClintProjectSpec = z.infer<typeof clintProjectSpecSchema>;
|
|
34
70
|
export type ClintProjectSpecInput = z.input<typeof clintProjectSpecSchema>;
|
|
@@ -45,6 +81,19 @@ export declare function getCliFolderName(spec: ClintProjectSpec): string;
|
|
|
45
81
|
* Name of the reactor-package sub-folder when the project is split.
|
|
46
82
|
*/
|
|
47
83
|
export declare function getAppFolderName(spec: ClintProjectSpec): string;
|
|
84
|
+
/**
|
|
85
|
+
* Flat list of all document types across all packages. Convenience for
|
|
86
|
+
* codegen that needs to iterate every registered document type regardless
|
|
87
|
+
* of which package provides it.
|
|
88
|
+
*/
|
|
89
|
+
export declare function getAllDocumentTypes(spec: ClintProjectSpec): string[];
|
|
90
|
+
/**
|
|
91
|
+
* The app npm package name (`@scope/{name}-app` or `{name}-app`).
|
|
92
|
+
* Matches what the document model reducer stores in `packages[].packageName`.
|
|
93
|
+
*/
|
|
94
|
+
export declare function getAppPackageName(spec: ClintProjectSpec): string;
|
|
95
|
+
/** Directory name for the app package (`{name}-app`, no scope prefix). */
|
|
96
|
+
export declare function getAppDirName(spec: ClintProjectSpec): string;
|
|
48
97
|
/**
|
|
49
98
|
* Folder slug for a documentType ID. The slug is the portion after the last
|
|
50
99
|
* `/` (e.g. `powerhouse/ph-clint-project` → `ph-clint-project`). Matches the
|
package/dist/spec/types.js
CHANGED
|
@@ -9,29 +9,41 @@
|
|
|
9
9
|
* generator step actually needs it.
|
|
10
10
|
*/
|
|
11
11
|
import { z } from 'zod';
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Ordered Powerhouse integration level. Higher levels imply all lower ones:
|
|
14
|
+
* Disabled → Reactor → Switchboard → Connect
|
|
15
|
+
*/
|
|
16
|
+
export const POWERHOUSE_LEVELS = [
|
|
17
|
+
'Disabled',
|
|
18
|
+
'Reactor',
|
|
19
|
+
'Switchboard',
|
|
20
|
+
'Connect',
|
|
21
|
+
];
|
|
22
|
+
export const powerhouseLevelSchema = z
|
|
23
|
+
.enum(POWERHOUSE_LEVELS)
|
|
24
|
+
.default('Disabled');
|
|
25
|
+
/** Check whether `level` is at least `threshold` in the ordered enum. */
|
|
26
|
+
export function phAtLeast(level, threshold) {
|
|
27
|
+
return (POWERHOUSE_LEVELS.indexOf(level) >= POWERHOUSE_LEVELS.indexOf(threshold));
|
|
28
|
+
}
|
|
17
29
|
const mastraFeatureSchema = z.object({
|
|
18
30
|
enabled: z.boolean().default(false),
|
|
19
31
|
});
|
|
20
32
|
const routineFeatureSchema = z.object({
|
|
21
33
|
enabled: z.boolean().default(false),
|
|
22
34
|
});
|
|
23
|
-
// Zod 4 does not cascade `.default({})` through nested object schemas — a
|
|
24
|
-
// missing outer key fills in `{}`, but nested keys with their own `.default`
|
|
25
|
-
// are not re-defaulted unless the object itself re-parses. We spell out full
|
|
26
|
-
// defaults here so `clintProjectSpecSchema.parse({ name: 'x' })` yields a
|
|
27
|
-
// fully-populated feature tree.
|
|
28
|
-
const DEFAULT_POWERHOUSE = {
|
|
29
|
-
enabled: false,
|
|
30
|
-
switchboard: true,
|
|
31
|
-
connect: true,
|
|
32
|
-
};
|
|
33
35
|
const DEFAULT_MASTRA = { enabled: false };
|
|
34
36
|
const DEFAULT_ROUTINE = { enabled: false };
|
|
37
|
+
export const powerhousePackageSchema = z.object({
|
|
38
|
+
id: z.string(),
|
|
39
|
+
packageName: z.string(),
|
|
40
|
+
documentTypes: z.array(z.string()).default([]),
|
|
41
|
+
});
|
|
42
|
+
export const externalSkillSchema = z.object({
|
|
43
|
+
id: z.string(),
|
|
44
|
+
name: z.string(),
|
|
45
|
+
githubUrl: z.string(),
|
|
46
|
+
});
|
|
35
47
|
export const clintProjectSpecSchema = z.object({
|
|
36
48
|
name: z
|
|
37
49
|
.string()
|
|
@@ -40,29 +52,33 @@ export const clintProjectSpecSchema = z.object({
|
|
|
40
52
|
.string()
|
|
41
53
|
.regex(/^[a-z0-9][a-z0-9-]*$/, 'lowercase letters, digits, hyphens only')
|
|
42
54
|
.optional(),
|
|
43
|
-
version: z.string().default('0.1.0'),
|
|
55
|
+
version: z.string().default('0.0.1-dev.0'),
|
|
44
56
|
description: z.string().default(''),
|
|
45
57
|
bin: z.string().optional(),
|
|
46
58
|
features: z
|
|
47
59
|
.object({
|
|
48
|
-
powerhouse:
|
|
60
|
+
powerhouse: powerhouseLevelSchema,
|
|
49
61
|
mastra: mastraFeatureSchema.default(DEFAULT_MASTRA),
|
|
50
62
|
routine: routineFeatureSchema.default(DEFAULT_ROUTINE),
|
|
51
63
|
})
|
|
52
64
|
.default({
|
|
53
|
-
powerhouse:
|
|
65
|
+
powerhouse: 'Disabled',
|
|
54
66
|
mastra: DEFAULT_MASTRA,
|
|
55
67
|
routine: DEFAULT_ROUTINE,
|
|
56
68
|
}),
|
|
57
69
|
/**
|
|
58
|
-
*
|
|
59
|
-
*
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
* package.
|
|
70
|
+
* Reactor packages and their document types. Each entry groups a package
|
|
71
|
+
* name with the document type IDs it provides. The app package (project's
|
|
72
|
+
* own reactor package) uses a `file:` dependency; external packages become
|
|
73
|
+
* versioned npm dependencies. Drives codegen of `framework.gen.ts`,
|
|
74
|
+
* `app/index.ts`, and `cli/package.json`.
|
|
64
75
|
*/
|
|
65
|
-
|
|
76
|
+
packages: z.array(powerhousePackageSchema).default([]),
|
|
77
|
+
/**
|
|
78
|
+
* External skills from the skills.sh ecosystem. Each skill has a name
|
|
79
|
+
* (kebab-case) and a GitHub URL for installation.
|
|
80
|
+
*/
|
|
81
|
+
externalSkills: z.array(externalSkillSchema).default([]),
|
|
66
82
|
});
|
|
67
83
|
/** npm package name (e.g. `@scope/foo` or `foo`). */
|
|
68
84
|
export function getPackageName(spec) {
|
|
@@ -85,6 +101,26 @@ export function getCliFolderName(spec) {
|
|
|
85
101
|
export function getAppFolderName(spec) {
|
|
86
102
|
return `${spec.name}-app`;
|
|
87
103
|
}
|
|
104
|
+
/**
|
|
105
|
+
* Flat list of all document types across all packages. Convenience for
|
|
106
|
+
* codegen that needs to iterate every registered document type regardless
|
|
107
|
+
* of which package provides it.
|
|
108
|
+
*/
|
|
109
|
+
export function getAllDocumentTypes(spec) {
|
|
110
|
+
return spec.packages.flatMap((p) => p.documentTypes);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* The app npm package name (`@scope/{name}-app` or `{name}-app`).
|
|
114
|
+
* Matches what the document model reducer stores in `packages[].packageName`.
|
|
115
|
+
*/
|
|
116
|
+
export function getAppPackageName(spec) {
|
|
117
|
+
const base = `${spec.name}-app`;
|
|
118
|
+
return spec.scope ? `@${spec.scope}/${base}` : base;
|
|
119
|
+
}
|
|
120
|
+
/** Directory name for the app package (`{name}-app`, no scope prefix). */
|
|
121
|
+
export function getAppDirName(spec) {
|
|
122
|
+
return `${spec.name}-app`;
|
|
123
|
+
}
|
|
88
124
|
/**
|
|
89
125
|
* Folder slug for a documentType ID. The slug is the portion after the last
|
|
90
126
|
* `/` (e.g. `powerhouse/ph-clint-project` → `ph-clint-project`). Matches the
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/** Map document model PublishTag values to CLI --tag values. */
|
|
2
|
+
export declare function tagToCli(tag: string): 'dev' | 'staging' | 'production' | null;
|
|
3
|
+
export declare const publishTrigger: import("@powerhousedao/ph-clint").Trigger<{
|
|
4
|
+
pending: number;
|
|
5
|
+
}, {
|
|
6
|
+
model: string;
|
|
7
|
+
devServicePort: number;
|
|
8
|
+
phVersion: string;
|
|
9
|
+
projectDocumentId?: string | undefined;
|
|
10
|
+
apiKey?: string | undefined;
|
|
11
|
+
}, import("@powerhousedao/ph-clint").InferRegistry<readonly [import("document-model").DocumentModelModule<import("@powerhousedao/ph-clint-app").PhClintProjectPHState>]>>;
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `publish-trigger` — watches `powerhouse/ph-clint-project` documents for
|
|
3
|
+
* publish records with `Pending` status and invokes the publish pipeline.
|
|
4
|
+
*
|
|
5
|
+
* Unlike `spec-change`, this trigger does NOT convert to a `ClintProjectSpec`.
|
|
6
|
+
* It reads `publishHistory` directly from the document state, since publish
|
|
7
|
+
* records are a doc-model concern (not a codegen input).
|
|
8
|
+
*
|
|
9
|
+
* Flow:
|
|
10
|
+
* 1. On document change, scans `publishHistory` for records with status
|
|
11
|
+
* `"Pending"`.
|
|
12
|
+
* 2. For each pending record, returns a work item that:
|
|
13
|
+
* a. Dispatches SET_PUBLISH_STATUS({ id, status: "InProgress" }).
|
|
14
|
+
* b. Runs `ph-clint clint-project-publish --tag <tag>`.
|
|
15
|
+
* c. Dispatches SET_PUBLISH_STATUS({ id, status: "Succeeded"|"Failed" }).
|
|
16
|
+
*/
|
|
17
|
+
import { spawn } from 'node:child_process';
|
|
18
|
+
import { setPublishStatus } from '@powerhousedao/ph-clint-app';
|
|
19
|
+
import { createDocumentChangeTrigger } from '../framework.js';
|
|
20
|
+
const DOCUMENT_TYPE = 'powerhouse/ph-clint-project';
|
|
21
|
+
const TAG = '[publish]';
|
|
22
|
+
/** Find the first ph-clint-project document on the default drive. */
|
|
23
|
+
async function findProjectDocumentId(client, driveId) {
|
|
24
|
+
try {
|
|
25
|
+
const children = await client.getChildren(driveId);
|
|
26
|
+
const results = (children?.results ?? []);
|
|
27
|
+
for (const node of results) {
|
|
28
|
+
if (node.header?.documentType === DOCUMENT_TYPE && node.header.id) {
|
|
29
|
+
return node.header.id;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// fall through
|
|
35
|
+
}
|
|
36
|
+
return undefined;
|
|
37
|
+
}
|
|
38
|
+
/** Run a shell command and return stdout. Rejects on non-zero exit. */
|
|
39
|
+
function exec(cmd, args, cwd, onLog) {
|
|
40
|
+
return new Promise((resolve, reject) => {
|
|
41
|
+
const proc = spawn(cmd, args, { cwd, stdio: ['ignore', 'pipe', 'pipe'] });
|
|
42
|
+
const out = [];
|
|
43
|
+
const err = [];
|
|
44
|
+
proc.stdout?.on('data', (d) => {
|
|
45
|
+
const s = d.toString();
|
|
46
|
+
out.push(s);
|
|
47
|
+
if (onLog)
|
|
48
|
+
for (const line of s.split('\n'))
|
|
49
|
+
if (line.trim())
|
|
50
|
+
onLog(line.trim());
|
|
51
|
+
});
|
|
52
|
+
proc.stderr?.on('data', (d) => {
|
|
53
|
+
const s = d.toString();
|
|
54
|
+
err.push(s);
|
|
55
|
+
if (onLog)
|
|
56
|
+
for (const line of s.split('\n'))
|
|
57
|
+
if (line.trim())
|
|
58
|
+
onLog(line.trim());
|
|
59
|
+
});
|
|
60
|
+
proc.on('error', reject);
|
|
61
|
+
proc.on('close', (code) => {
|
|
62
|
+
if (code === 0)
|
|
63
|
+
resolve(out.join(''));
|
|
64
|
+
else
|
|
65
|
+
reject(new Error(`${cmd} ${args.join(' ')} exited ${code}\n${err.join('')}`));
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
/** Map document model PublishTag values to CLI --tag values. */
|
|
70
|
+
export function tagToCli(tag) {
|
|
71
|
+
switch (tag) {
|
|
72
|
+
case 'Dev':
|
|
73
|
+
return 'dev';
|
|
74
|
+
case 'Staging':
|
|
75
|
+
return 'staging';
|
|
76
|
+
case 'Production':
|
|
77
|
+
return 'production';
|
|
78
|
+
default:
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export const publishTrigger = createDocumentChangeTrigger({
|
|
83
|
+
id: 'publish-trigger',
|
|
84
|
+
documentType: DOCUMENT_TYPE,
|
|
85
|
+
// Don't fire on startup — only react to new pending records.
|
|
86
|
+
initialReconcile: false,
|
|
87
|
+
// Resolve the target document lazily (same pattern as spec-change trigger).
|
|
88
|
+
documentId: async (ctx) => {
|
|
89
|
+
const configured = ctx.context.config.projectDocumentId &&
|
|
90
|
+
ctx.context.config.projectDocumentId.length > 0
|
|
91
|
+
? ctx.context.config.projectDocumentId
|
|
92
|
+
: undefined;
|
|
93
|
+
if (configured)
|
|
94
|
+
return configured;
|
|
95
|
+
const reactor = await ctx.reactor();
|
|
96
|
+
if (!reactor)
|
|
97
|
+
return undefined;
|
|
98
|
+
return findProjectDocumentId(reactor.client, reactor.driveId);
|
|
99
|
+
},
|
|
100
|
+
async onChange(doc, ctx) {
|
|
101
|
+
const log = ctx.context.log;
|
|
102
|
+
const publishHistory = doc.state.global.publishHistory ?? [];
|
|
103
|
+
const pending = publishHistory.filter((r) => r.status === 'Pending');
|
|
104
|
+
if (pending.length === 0)
|
|
105
|
+
return null;
|
|
106
|
+
// Process the first pending record (one per tick to avoid races).
|
|
107
|
+
const record = pending[0];
|
|
108
|
+
const cliTag = tagToCli(record.tag);
|
|
109
|
+
if (!cliTag) {
|
|
110
|
+
log?.warn(`${TAG} unknown publish tag "${record.tag}" for record ${record.id}`);
|
|
111
|
+
return null;
|
|
112
|
+
}
|
|
113
|
+
log?.info(`${TAG} found pending ${record.tag} publish (id=${record.id}, version=${record.version})`);
|
|
114
|
+
return {
|
|
115
|
+
type: 'function',
|
|
116
|
+
params: {
|
|
117
|
+
fn: async () => {
|
|
118
|
+
const reactor = await ctx.reactor();
|
|
119
|
+
if (!reactor) {
|
|
120
|
+
log?.error(`${TAG} reactor not available, cannot update publish status`);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
// Mark as InProgress.
|
|
124
|
+
log?.info(`${TAG} marking record ${record.id} as InProgress`);
|
|
125
|
+
await reactor.client.execute(doc.header.id, 'main', [
|
|
126
|
+
setPublishStatus({ id: record.id, status: 'InProgress' }),
|
|
127
|
+
]);
|
|
128
|
+
// Install dependencies in the generated project before publishing.
|
|
129
|
+
const workdir = ctx.context.workdir;
|
|
130
|
+
log?.info(`${TAG} running pnpm install in ${workdir}`);
|
|
131
|
+
try {
|
|
132
|
+
await exec('pnpm', ['install'], workdir, (msg) => log?.debug(`${TAG} [install] ${msg}`));
|
|
133
|
+
}
|
|
134
|
+
catch (installErr) {
|
|
135
|
+
log?.error(`${TAG} pnpm install failed: ${installErr instanceof Error ? installErr.message : String(installErr)}`);
|
|
136
|
+
}
|
|
137
|
+
// Run the publish command.
|
|
138
|
+
let success = false;
|
|
139
|
+
try {
|
|
140
|
+
log?.info(`${TAG} invoking clint-project-publish --tag ${cliTag}`);
|
|
141
|
+
const { clintProjectPublish } = await import('../commands/clint-project-publish.js');
|
|
142
|
+
const result = await clintProjectPublish.execute({ tag: cliTag, dryRun: false, skipBuild: false, skipGitCheck: true, verbose: false },
|
|
143
|
+
// @ts-expect-error — minimal context; publish uses workdir + stdout.
|
|
144
|
+
{
|
|
145
|
+
workdir: ctx.context.workdir,
|
|
146
|
+
stdout: (msg) => log?.info(`${TAG} ${msg}`),
|
|
147
|
+
log,
|
|
148
|
+
});
|
|
149
|
+
success = !result.text.includes('failed');
|
|
150
|
+
}
|
|
151
|
+
catch (err) {
|
|
152
|
+
log?.error(`${TAG} publish failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
153
|
+
}
|
|
154
|
+
// Update status.
|
|
155
|
+
const finalStatus = success ? 'Succeeded' : 'Failed';
|
|
156
|
+
log?.info(`${TAG} marking record ${record.id} as ${finalStatus}`);
|
|
157
|
+
await reactor.client.execute(doc.header.id, 'main', [
|
|
158
|
+
setPublishStatus({
|
|
159
|
+
id: record.id,
|
|
160
|
+
status: finalStatus,
|
|
161
|
+
}),
|
|
162
|
+
]);
|
|
163
|
+
},
|
|
164
|
+
},
|
|
165
|
+
callbacks: {
|
|
166
|
+
onFailure: (err) => {
|
|
167
|
+
log?.error(`${TAG} publish trigger failed: ${err.message}`);
|
|
168
|
+
},
|
|
169
|
+
},
|
|
170
|
+
};
|
|
171
|
+
},
|
|
172
|
+
});
|