@netnodeag/kraftwerk 0.48.0 → 0.49.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 +96 -5
- package/dist/cli/doctor.js +7 -1
- package/dist/cli/init.js +1 -0
- package/dist/cli/kraftwerk.js +5 -1
- package/dist/cli/projects.d.ts +0 -14
- package/dist/cli/projects.js +181 -107
- package/dist/cli/repos.js +1 -1
- package/dist/cli/ui.js +1 -1
- package/dist/cli/vibeables.js +1 -1
- package/dist/cli/workspaces.d.ts +16 -0
- package/dist/cli/workspaces.js +149 -0
- package/dist/config.d.ts +22 -1
- package/dist/config.js +19 -3
- package/dist/inspector/agents.js +2 -2
- package/dist/inspector/channels.d.ts +10 -1
- package/dist/inspector/channels.js +11 -2
- package/dist/inspector/chat/sessions.d.ts +8 -3
- package/dist/inspector/chat/sessions.js +58 -19
- package/dist/inspector/chat/types.d.ts +11 -0
- package/dist/inspector/git.js +5 -3
- package/dist/inspector/instances.d.ts +14 -12
- package/dist/inspector/instances.js +71 -31
- package/dist/inspector/notifications.js +2 -0
- package/dist/inspector/projects.d.ts +167 -0
- package/dist/inspector/projects.js +520 -0
- package/dist/inspector/search.d.ts +1 -1
- package/dist/inspector/search.js +3 -3
- package/dist/inspector/server.js +111 -20
- package/dist/inspector/settings.d.ts +3 -1
- package/dist/inspector/settings.js +14 -3
- package/inspector/dist/assets/{dist-Dk5ZcwMb.js → dist-B-8ArbIm.js} +1 -1
- package/inspector/dist/assets/{dist-DUq31xBw.js → dist-BFJWVdBm.js} +1 -1
- package/inspector/dist/assets/{dist-D6fPO3Jz.js → dist-BIvcKBWo.js} +1 -1
- package/inspector/dist/assets/{dist-n8GR1Slo.js → dist-BgYnJawU.js} +1 -1
- package/inspector/dist/assets/dist-Bl1ogZZM.js +1 -0
- package/inspector/dist/assets/{dist-O97r5Ket.js → dist-BrgwFqUR.js} +1 -1
- package/inspector/dist/assets/{dist-CmH9PvLb.js → dist-Bz0bwjja.js} +1 -1
- package/inspector/dist/assets/{dist-Dv40dCIn.js → dist-CFvGR87v.js} +1 -1
- package/inspector/dist/assets/{dist-Bz3XVWXl.js → dist-CjtxxsZ2.js} +1 -1
- package/inspector/dist/assets/{dist-BNGsfwfO.js → dist-QwwTJ2wk.js} +1 -1
- package/inspector/dist/assets/{dist-BWkBM49t.js → dist-tfmgozDB.js} +1 -1
- package/inspector/dist/assets/{editor-toVFopCO.js → editor-C6m40u-h.js} +3 -3
- package/inspector/dist/assets/{index-CC0tY40i.js → index-Cvq-RiV2.js} +11 -11
- package/inspector/dist/assets/{index-DQrsXEWt.css → index-DcQwGaV2.css} +1 -1
- package/inspector/dist/index.html +2 -2
- package/package.json +1 -1
- package/schema/kraftwerk.schema.json +18 -0
- package/inspector/dist/assets/dist-JaBY_kZV.js +0 -1
|
@@ -14,25 +14,28 @@ import { selfCommand } from "./self-command.js";
|
|
|
14
14
|
* Registered ports are verified by probing /api/meta on read; entries
|
|
15
15
|
* that stop answering are pruned, so crashes leave no ghosts.
|
|
16
16
|
*
|
|
17
|
-
*
|
|
17
|
+
* workspaces/<hash(root)>.json — durable. One record per workspace root that
|
|
18
18
|
* ever ran the inspector on this machine, keyed by the root and never
|
|
19
19
|
* pruned automatically. Everything else (name, icon, port) is derived from
|
|
20
20
|
* the root's kraftwerk.yml at read time, so it is always current. A
|
|
21
21
|
* project that is not running can be started again from the switcher or
|
|
22
|
-
* `kraftwerk
|
|
22
|
+
* `kraftwerk workspaces start` — that is what makes a killed UI findable.
|
|
23
23
|
* The record also carries the project's agent roster (written whenever the
|
|
24
24
|
* instance reads it), so the ⌘K palette can list every workspace's agents
|
|
25
25
|
* from a handful of small files without probing anything.
|
|
26
26
|
*/
|
|
27
27
|
const HOME = path.join(os.homedir(), ".kraftwerk");
|
|
28
28
|
const INSTANCES_DIR = path.join(HOME, "instances");
|
|
29
|
-
const
|
|
29
|
+
const WORKSPACES_DIR = path.join(HOME, "workspaces");
|
|
30
|
+
/** Where the records lived before 0.49 (`kraftwerk projects`); moved over once, see migrateRegistry. */
|
|
31
|
+
const LEGACY_WORKSPACES_DIR = path.join(HOME, "projects");
|
|
30
32
|
const LOGS_DIR = path.join(HOME, "logs");
|
|
31
33
|
const selfFile = () => path.join(INSTANCES_DIR, `${process.pid}.json`);
|
|
32
34
|
let selfPort = null;
|
|
33
35
|
let selfRoot = null;
|
|
34
|
-
|
|
35
|
-
const
|
|
36
|
+
/** File name of a root's record: a hash of the absolute root, the same under the old and the new directory. */
|
|
37
|
+
export const workspaceRecordName = (root) => `${createHash("sha1").update(absolutePath(root)).digest("hex").slice(0, 16)}.json`;
|
|
38
|
+
const recordFile = (root) => path.join(WORKSPACES_DIR, workspaceRecordName(root));
|
|
36
39
|
/** ~/… for display. */
|
|
37
40
|
export const tildify = (p) => {
|
|
38
41
|
const home = os.homedir();
|
|
@@ -132,8 +135,38 @@ export async function discoverInstances() {
|
|
|
132
135
|
cache = { at: Date.now(), entries };
|
|
133
136
|
return entries;
|
|
134
137
|
}
|
|
135
|
-
/* ----------
|
|
136
|
-
|
|
138
|
+
/* ---------- workspaces (durable) ---------- */
|
|
139
|
+
let migrated = null;
|
|
140
|
+
/**
|
|
141
|
+
* Records were written under ~/.kraftwerk/projects until 0.48. Copy them
|
|
142
|
+
* into the new directory once per process, never overwriting a record
|
|
143
|
+
* that is already there, so a machine that upgrades keeps every workspace
|
|
144
|
+
* it knew. The old directory stays: an inspector of an older version may
|
|
145
|
+
* still be running and reading it. forgetWorkspace drops a record from
|
|
146
|
+
* both, so a forgotten workspace does not come back on the next start.
|
|
147
|
+
* Best-effort like everything else in this registry.
|
|
148
|
+
*/
|
|
149
|
+
function migrateRegistry() {
|
|
150
|
+
return (migrated ??= (async () => {
|
|
151
|
+
let legacy;
|
|
152
|
+
try {
|
|
153
|
+
legacy = await fs.readdir(LEGACY_WORKSPACES_DIR);
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
try {
|
|
159
|
+
await fs.mkdir(WORKSPACES_DIR, { recursive: true });
|
|
160
|
+
for (const f of legacy) {
|
|
161
|
+
if (!f.endsWith(".json"))
|
|
162
|
+
continue;
|
|
163
|
+
await fs.copyFile(path.join(LEGACY_WORKSPACES_DIR, f), path.join(WORKSPACES_DIR, f), fsSync.constants.COPYFILE_EXCL).catch(() => { });
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
catch { }
|
|
167
|
+
})());
|
|
168
|
+
}
|
|
169
|
+
async function readRecord(file) {
|
|
137
170
|
try {
|
|
138
171
|
const rec = JSON.parse(await fs.readFile(file, "utf8"));
|
|
139
172
|
if (typeof rec.root !== "string")
|
|
@@ -153,12 +186,13 @@ async function readProject(file) {
|
|
|
153
186
|
}
|
|
154
187
|
}
|
|
155
188
|
/** Upsert the project record for a root (call on every inspector start). */
|
|
156
|
-
export async function
|
|
189
|
+
export async function registerWorkspace(root) {
|
|
190
|
+
await migrateRegistry();
|
|
157
191
|
const abs = absolutePath(root);
|
|
158
192
|
const now = new Date().toISOString();
|
|
159
193
|
try {
|
|
160
|
-
await fs.mkdir(
|
|
161
|
-
const prev = await
|
|
194
|
+
await fs.mkdir(WORKSPACES_DIR, { recursive: true });
|
|
195
|
+
const prev = await readRecord(recordFile(abs));
|
|
162
196
|
const rec = {
|
|
163
197
|
root: abs,
|
|
164
198
|
firstSeen: prev?.firstSeen || now,
|
|
@@ -167,7 +201,7 @@ export async function registerProject(root) {
|
|
|
167
201
|
...(prev?.agents ? { agents: prev.agents } : {}),
|
|
168
202
|
...(prev?.channels ? { channels: prev.channels } : {}),
|
|
169
203
|
};
|
|
170
|
-
await fs.writeFile(
|
|
204
|
+
await fs.writeFile(recordFile(abs), JSON.stringify(rec, null, 2));
|
|
171
205
|
}
|
|
172
206
|
catch { } // best-effort, like the instance file
|
|
173
207
|
}
|
|
@@ -178,13 +212,14 @@ const synced = new Map();
|
|
|
178
212
|
* changes when the list did. Roots with no record (a CLI run in a project
|
|
179
213
|
* that never started the inspector) are left alone.
|
|
180
214
|
*/
|
|
181
|
-
async function
|
|
215
|
+
async function syncWorkspaceList(root, key, list) {
|
|
216
|
+
await migrateRegistry();
|
|
182
217
|
const stamp = `${root}\n${JSON.stringify(list)}`;
|
|
183
218
|
if (synced.get(key) === stamp)
|
|
184
219
|
return;
|
|
185
220
|
try {
|
|
186
|
-
const file =
|
|
187
|
-
const rec = await
|
|
221
|
+
const file = recordFile(root);
|
|
222
|
+
const rec = await readRecord(file);
|
|
188
223
|
if (!rec)
|
|
189
224
|
return;
|
|
190
225
|
await fs.writeFile(file, JSON.stringify({ ...rec, [key]: list }, null, 2));
|
|
@@ -192,41 +227,46 @@ async function syncProjectList(root, key, list) {
|
|
|
192
227
|
}
|
|
193
228
|
catch { }
|
|
194
229
|
}
|
|
195
|
-
export const
|
|
196
|
-
export const
|
|
230
|
+
export const syncWorkspaceAgents = (root, agents) => syncWorkspaceList(root, "agents", agents);
|
|
231
|
+
export const syncWorkspaceChannels = (root, channels) => syncWorkspaceList(root, "channels", channels);
|
|
197
232
|
/** Stamp lastStopped on clean shutdown. Sync so exit handlers can call it. */
|
|
198
|
-
export function
|
|
233
|
+
export function markWorkspaceStopped() {
|
|
199
234
|
if (!selfRoot)
|
|
200
235
|
return;
|
|
201
236
|
try {
|
|
202
|
-
const file =
|
|
237
|
+
const file = recordFile(selfRoot);
|
|
203
238
|
const rec = JSON.parse(fsSync.readFileSync(file, "utf8"));
|
|
204
239
|
rec.lastStopped = new Date().toISOString();
|
|
205
240
|
fsSync.writeFileSync(file, JSON.stringify(rec, null, 2));
|
|
206
241
|
}
|
|
207
242
|
catch { }
|
|
208
243
|
}
|
|
209
|
-
/** All known
|
|
210
|
-
export async function
|
|
244
|
+
/** All known workspaces, most recently started first. */
|
|
245
|
+
export async function listWorkspaceRecords() {
|
|
246
|
+
await migrateRegistry();
|
|
211
247
|
let files;
|
|
212
248
|
try {
|
|
213
|
-
files = await fs.readdir(
|
|
249
|
+
files = await fs.readdir(WORKSPACES_DIR);
|
|
214
250
|
}
|
|
215
251
|
catch {
|
|
216
252
|
return [];
|
|
217
253
|
}
|
|
218
|
-
const recs = await Promise.all(files.filter((f) => f.endsWith(".json")).map((f) =>
|
|
254
|
+
const recs = await Promise.all(files.filter((f) => f.endsWith(".json")).map((f) => readRecord(path.join(WORKSPACES_DIR, f))));
|
|
219
255
|
return recs
|
|
220
256
|
.filter((r) => r != null)
|
|
221
257
|
.sort((a, b) => b.lastStarted.localeCompare(a.lastStarted));
|
|
222
258
|
}
|
|
223
|
-
/** Drop a
|
|
224
|
-
export async function
|
|
259
|
+
/** Drop a workspace record (the root itself is untouched) — from the legacy directory too, see migrateRegistry. */
|
|
260
|
+
export async function forgetWorkspace(root) {
|
|
261
|
+
await migrateRegistry();
|
|
262
|
+
const legacy = fs.unlink(path.join(LEGACY_WORKSPACES_DIR, workspaceRecordName(root))).catch(() => { });
|
|
225
263
|
try {
|
|
226
|
-
await fs.unlink(
|
|
264
|
+
await fs.unlink(recordFile(root));
|
|
265
|
+
await legacy;
|
|
227
266
|
return true;
|
|
228
267
|
}
|
|
229
268
|
catch {
|
|
269
|
+
await legacy;
|
|
230
270
|
return false;
|
|
231
271
|
}
|
|
232
272
|
}
|
|
@@ -268,7 +308,7 @@ let wsCache = null;
|
|
|
268
308
|
export async function discoverWorkspaces() {
|
|
269
309
|
if (wsCache && Date.now() - wsCache.at < 5_000)
|
|
270
310
|
return wsCache.entries;
|
|
271
|
-
const [projects, live] = await Promise.all([
|
|
311
|
+
const [projects, live] = await Promise.all([listWorkspaceRecords(), discoverInstances()]);
|
|
272
312
|
const liveByRoot = new Map(live.filter((i) => i.root).map((i) => [i.root, i]));
|
|
273
313
|
const consumed = new Set();
|
|
274
314
|
const entries = (await Promise.all(projects
|
|
@@ -353,7 +393,7 @@ export async function listWorkspacesDetailed() {
|
|
|
353
393
|
const all = [...others];
|
|
354
394
|
if (selfRoot && selfPort) {
|
|
355
395
|
const d = await describeRoot(selfRoot);
|
|
356
|
-
const rec = await
|
|
396
|
+
const rec = await readRecord(recordFile(selfRoot));
|
|
357
397
|
all.unshift({
|
|
358
398
|
name: d.name,
|
|
359
399
|
icon: d.icon,
|
|
@@ -373,7 +413,7 @@ export async function listWorkspacesDetailed() {
|
|
|
373
413
|
const base = { ...e, current, state: e.live ? "running" : "stopped" };
|
|
374
414
|
if (!e.root)
|
|
375
415
|
return base;
|
|
376
|
-
const rec = await
|
|
416
|
+
const rec = await readRecord(recordFile(e.root));
|
|
377
417
|
base.firstSeen = rec?.firstSeen;
|
|
378
418
|
base.startCount = rec?.startCount;
|
|
379
419
|
// "missing" = the folder is gone; "orphaned" = it is still there but
|
|
@@ -418,7 +458,7 @@ export async function listWorkspacesDetailed() {
|
|
|
418
458
|
* Waits up to ~5s for the new inspector to answer so callers can link
|
|
419
459
|
* straight to it.
|
|
420
460
|
*/
|
|
421
|
-
export async function
|
|
461
|
+
export async function startWorkspace(root) {
|
|
422
462
|
const abs = absolutePath(root);
|
|
423
463
|
const { name, port, exists } = await describeRoot(abs);
|
|
424
464
|
if (!exists)
|
|
@@ -441,7 +481,7 @@ export async function startProject(root) {
|
|
|
441
481
|
let stdio = ["ignore", "ignore", "ignore"];
|
|
442
482
|
try {
|
|
443
483
|
await fs.mkdir(LOGS_DIR, { recursive: true });
|
|
444
|
-
log = path.join(LOGS_DIR,
|
|
484
|
+
log = path.join(LOGS_DIR, workspaceRecordName(abs).replace(/\.json$/, ".log"));
|
|
445
485
|
const fd = fsSync.openSync(log, "a");
|
|
446
486
|
fsSync.writeSync(fd, `\n--- ${new Date().toISOString()} start ${name} (${abs}) ---\n`);
|
|
447
487
|
stdio = ["ignore", fd, fd];
|
|
@@ -487,7 +527,7 @@ export async function startProject(root) {
|
|
|
487
527
|
* follows. Matched by root, or by url for instances from older versions
|
|
488
528
|
* that registered no root. Waits up to ~5s for the port to go quiet.
|
|
489
529
|
*/
|
|
490
|
-
export async function
|
|
530
|
+
export async function stopWorkspace(target) {
|
|
491
531
|
if (target.root && absolutePath(target.root) === selfRoot) {
|
|
492
532
|
return { ok: false, error: "that is this workspace — stop it from its own terminal or pid" };
|
|
493
533
|
}
|
|
@@ -43,6 +43,8 @@ export function clipBody(text) {
|
|
|
43
43
|
export function chatHref(meta) {
|
|
44
44
|
if (meta.scope.kind === "channel")
|
|
45
45
|
return `/channels/${meta.scope.slug}`;
|
|
46
|
+
if (meta.scope.kind === "project")
|
|
47
|
+
return `/projects/${meta.scope.slug}/chat/${meta.id}`;
|
|
46
48
|
return meta.scope.kind === "agent" ? `/agents/${meta.scope.slug}/chat/${meta.id}` : `/agents/chats/${meta.id}`;
|
|
47
49
|
}
|
|
48
50
|
export async function pushNotification(input) {
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import { type Project } from "../config.js";
|
|
2
|
+
import type { ChatAgentId } from "./chat/types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Projects: a goal with everything the agents need to reach it gathered in
|
|
5
|
+
* one folder — the brief, the systems of record where the truth is managed
|
|
6
|
+
* outside kraftwerk, and links to the workspace's knowledge, vibeables,
|
|
7
|
+
* repositories, workflows and agents. Working in a project is chat: a
|
|
8
|
+
* session scoped to it carries all of this as context (sessions.ts).
|
|
9
|
+
*
|
|
10
|
+
* One folder per project under the projects root (`projects.root` in
|
|
11
|
+
* kraftwerk.yml, default kraftwerk-data/projects/). Like agents and
|
|
12
|
+
* knowledge it is part of the workspace: versioned by the workspace git,
|
|
13
|
+
* no repository of its own. The folder is the registry — whatever
|
|
14
|
+
* directory under the root holds a project.yml is a project.
|
|
15
|
+
*
|
|
16
|
+
* projects/<slug>/project.yml # title, status, goal, harness/model/effort, records, links (slug lists)
|
|
17
|
+
* projects/<slug>/brief.md # the goal in full: what done looks like, constraints, stakeholders
|
|
18
|
+
* projects/<slug>/state.md # current state, rewritten at the end of a session
|
|
19
|
+
* projects/<slug>/log.md # append-only: decisions, milestones (newest first, stamped)
|
|
20
|
+
*
|
|
21
|
+
* Links are one-directional lists of slugs; a target that does not exist
|
|
22
|
+
* is reported ("configured but not found"), never an error — the same
|
|
23
|
+
* rule agent knowledge follows. A system of record is context, not a
|
|
24
|
+
* credential and not a grant: it says where the truth lives and how it is
|
|
25
|
+
* usually reached, and the harness decides what the agent may call.
|
|
26
|
+
*/
|
|
27
|
+
export declare const PROJECT_FILE = "project.yml";
|
|
28
|
+
export declare const PROJECT_STATUSES: readonly ["active", "paused", "done", "archived"];
|
|
29
|
+
export type ProjectStatus = (typeof PROJECT_STATUSES)[number];
|
|
30
|
+
/** Where truth is managed outside kraftwerk: a tool, a folder, a board. */
|
|
31
|
+
export interface SystemOfRecord {
|
|
32
|
+
/** my-netnode | google-drive | github | bitbucket | notion | slack | url | anything else (rendered by name). */
|
|
33
|
+
kind: string;
|
|
34
|
+
/** Display name ("Figma board", "Contracts folder"). */
|
|
35
|
+
title?: string;
|
|
36
|
+
url?: string;
|
|
37
|
+
/** For tools with workspaces or boards: the id the agent needs (a my.netnode.ch workspace, a Slack channel). */
|
|
38
|
+
workspace?: string;
|
|
39
|
+
/** What lives there and how it is usually reached ("tickets and roadmap, `my` CLI"). */
|
|
40
|
+
note?: string;
|
|
41
|
+
}
|
|
42
|
+
/** What kraftwerk knows about a record kind: a label and what such a place usually holds. */
|
|
43
|
+
export declare const RECORD_KINDS: Record<string, {
|
|
44
|
+
label: string;
|
|
45
|
+
hint: string;
|
|
46
|
+
}>;
|
|
47
|
+
export declare const LINK_KINDS: readonly ["knowledge", "vibeables", "repos", "workflows", "agents"];
|
|
48
|
+
export type LinkKind = (typeof LINK_KINDS)[number];
|
|
49
|
+
export interface ProjectDef {
|
|
50
|
+
slug: string;
|
|
51
|
+
title: string;
|
|
52
|
+
status: ProjectStatus;
|
|
53
|
+
/** One line: what the project is for. brief.md holds the long form. */
|
|
54
|
+
goal: string;
|
|
55
|
+
/** Harness every chat in the project runs on, like an agent's. Default: claude. */
|
|
56
|
+
harness: ChatAgentId;
|
|
57
|
+
/** Model for that harness; absent = the harness default. */
|
|
58
|
+
model?: string;
|
|
59
|
+
/** Reasoning effort (low … max); absent = the harness default. */
|
|
60
|
+
effort?: string;
|
|
61
|
+
records: SystemOfRecord[];
|
|
62
|
+
/** OKF bundle names. */
|
|
63
|
+
knowledge: string[];
|
|
64
|
+
/** Folder names under the vibeables root. */
|
|
65
|
+
vibeables: string[];
|
|
66
|
+
/** Folder names under the repos root. */
|
|
67
|
+
repos: string[];
|
|
68
|
+
/** Workflow slugs. */
|
|
69
|
+
workflows: string[];
|
|
70
|
+
/** Agent slugs. */
|
|
71
|
+
agents: string[];
|
|
72
|
+
}
|
|
73
|
+
export interface ProjectSummary extends ProjectDef {
|
|
74
|
+
/** Absolute folder path. */
|
|
75
|
+
path: string;
|
|
76
|
+
/** Newest change inside the folder. */
|
|
77
|
+
updatedAt?: string;
|
|
78
|
+
/** Set when project.yml exists but is unusable; the rest shows defaults. */
|
|
79
|
+
configError?: string;
|
|
80
|
+
}
|
|
81
|
+
/** One linked slug and whether its target exists right now. */
|
|
82
|
+
export interface LinkState {
|
|
83
|
+
slug: string;
|
|
84
|
+
found: boolean;
|
|
85
|
+
/** Display name of the target when found (an agent's name, a workflow's description). */
|
|
86
|
+
label?: string;
|
|
87
|
+
}
|
|
88
|
+
export type ProjectLinks = Record<LinkKind, LinkState[]>;
|
|
89
|
+
export interface ProjectDetail extends ProjectSummary {
|
|
90
|
+
brief: string;
|
|
91
|
+
state: string;
|
|
92
|
+
log: string;
|
|
93
|
+
links: ProjectLinks;
|
|
94
|
+
}
|
|
95
|
+
export interface ProjectsView {
|
|
96
|
+
enabled: boolean;
|
|
97
|
+
root?: string;
|
|
98
|
+
projects: ProjectSummary[];
|
|
99
|
+
/** Set when the feature is off or the root cannot be read. */
|
|
100
|
+
error?: string;
|
|
101
|
+
}
|
|
102
|
+
/** Every field optional: omitted = untouched. */
|
|
103
|
+
export interface SaveProjectInput {
|
|
104
|
+
title?: string;
|
|
105
|
+
status?: string;
|
|
106
|
+
goal?: string;
|
|
107
|
+
harness?: string;
|
|
108
|
+
model?: string;
|
|
109
|
+
effort?: string;
|
|
110
|
+
records?: unknown;
|
|
111
|
+
knowledge?: unknown;
|
|
112
|
+
vibeables?: unknown;
|
|
113
|
+
repos?: unknown;
|
|
114
|
+
workflows?: unknown;
|
|
115
|
+
agents?: unknown;
|
|
116
|
+
brief?: string;
|
|
117
|
+
state?: string;
|
|
118
|
+
}
|
|
119
|
+
export declare function safeProjectSlug(slug: string): string;
|
|
120
|
+
/** Derive a slug from a title: "Relaunch netnode.ch" -> "relaunch-netnode-ch". */
|
|
121
|
+
export declare function projectSlugFromTitle(title: string): string;
|
|
122
|
+
export declare function openProjects(): Promise<{
|
|
123
|
+
project?: Project;
|
|
124
|
+
root?: string;
|
|
125
|
+
off?: boolean;
|
|
126
|
+
error?: string;
|
|
127
|
+
}>;
|
|
128
|
+
/** Make sure the root exists; runs when the feature is switched on. */
|
|
129
|
+
export declare function ensureProjectsRoot(): Promise<void>;
|
|
130
|
+
/** Every project under the root: active ones first, then by title. */
|
|
131
|
+
export declare function listProjects(): Promise<ProjectsView>;
|
|
132
|
+
/** Which of the linked slugs exist right now, with a display label for the ones that do. */
|
|
133
|
+
export declare function resolveLinks(def: ProjectDef): Promise<ProjectLinks>;
|
|
134
|
+
/** One project with its files and link states; null when the folder is not a project. */
|
|
135
|
+
export declare function getProject(slug: string): Promise<ProjectDetail | null>;
|
|
136
|
+
/** The files a fresh project starts with. */
|
|
137
|
+
export declare function projectStarter(def: ProjectDef): Record<string, string>;
|
|
138
|
+
export declare function createProject(input: {
|
|
139
|
+
title: string;
|
|
140
|
+
goal?: string;
|
|
141
|
+
slug?: string;
|
|
142
|
+
harness?: string;
|
|
143
|
+
model?: string;
|
|
144
|
+
effort?: string;
|
|
145
|
+
}): Promise<ProjectDetail>;
|
|
146
|
+
/** Merge the given fields into project.yml (and brief/state when given); lists replace wholesale. */
|
|
147
|
+
export declare function saveProject(slug: string, input: SaveProjectInput): Promise<ProjectDetail>;
|
|
148
|
+
/** Add or remove one slug in a link list. */
|
|
149
|
+
export declare function linkProject(slug: string, kind: string, target: string, remove?: boolean): Promise<ProjectDetail>;
|
|
150
|
+
/**
|
|
151
|
+
* Prepend an entry under today's heading in log.md (newest first, like a
|
|
152
|
+
* knowledge bundle's log). The actor is stamped so a line written by an
|
|
153
|
+
* agent reads as such next to a human's.
|
|
154
|
+
*/
|
|
155
|
+
export declare function appendProjectLog(slug: string, entry: string, actor: string): Promise<string>;
|
|
156
|
+
/** Remove the folder; its history stays in the workspace git. */
|
|
157
|
+
export declare function deleteProject(slug: string): Promise<void>;
|
|
158
|
+
/** One line per system of record: what it is, where, and what usually lives there. */
|
|
159
|
+
export declare function recordLine(r: SystemOfRecord): string;
|
|
160
|
+
/**
|
|
161
|
+
* The "## …" blocks a project chat starts with: brief, state, records,
|
|
162
|
+
* every link with how to reach it, and how to keep the project current.
|
|
163
|
+
* `actor` is stamped on log entries the agent writes.
|
|
164
|
+
*/
|
|
165
|
+
export declare function projectContext(slug: string, actor: string, opts?: {
|
|
166
|
+
member?: boolean;
|
|
167
|
+
}): Promise<string>;
|