@krapht/ork 0.3.0 → 0.4.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/dist/__build_info__-CtSVVuZE.mjs +691 -0
- package/dist/bin.mjs +826 -526
- package/dist/clean-Dcr807be.mjs +88 -0
- package/dist/{context-BFGC_C02.mjs → context-C_bksb0C.mjs} +45 -129
- package/dist/{find-BcvhT6UN.mjs → find-DptmbhGd.mjs} +1 -1
- package/dist/{format-DqpObyMl.mjs → format-J-a21Yvn.mjs} +3 -163
- package/dist/{in-a-run-BbLhZOFW.mjs → in-a-run-D1Qc5Mit.mjs} +1 -1
- package/dist/{limits-BKYAmqCR.mjs → limits-BDZCmGEu.mjs} +1 -1
- package/dist/list-ZvY2OcLq.mjs +110 -0
- package/dist/{mcp-BWsdQyri.mjs → mcp-CHVKKg3B.mjs} +6 -6
- package/dist/{output-Q2EdeVUt.mjs → output-CXSJO00X.mjs} +68 -24
- package/dist/{pause-DKACDfV1.mjs → pause-BPSmg4yz.mjs} +4 -4
- package/dist/{relay-CbK-Wme9.mjs → relay-Do-aNbSD.mjs} +2 -2
- package/dist/remove-AwaMMaHf.mjs +194 -0
- package/dist/{send-C6yr04Fe.mjs → send-DhGBkowy.mjs} +3 -3
- package/dist/{src-CZOVF2yU.mjs → src-BHnxpJyH.mjs} +1 -1
- package/dist/{src-Cz1yOUTg.mjs → src-CGqMati3.mjs} +1411 -747
- package/dist/{src-D5ruZFgD.mjs → src-CpDKIulI.mjs} +1200 -55
- package/dist/{src-DtN1k-B3.mjs → src-UWkk1sl9.mjs} +1 -1
- package/dist/ui/assets/app-BgVDC_5l.js +67 -0
- package/dist/ui/assets/{app-C722xNZ3.css → app-DVf68fj5.css} +1 -1
- package/dist/ui/assets/{index-BoG6O-Lp.js → index-MQNNgsyT.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/dist/ui-DhutUMgQ.mjs +472 -0
- package/dist/{usage-BIjf9Xdt.mjs → usage-CT7Z-yjr.mjs} +5 -4
- package/dist/{watch-B8o4Szd_.mjs → watch-0Qr2UfCQ.mjs} +6 -6
- package/dist/worktrees-DHl80k96.mjs +212 -0
- package/package.json +4 -4
- package/dist/__build_info__-CXXMLvjt.mjs +0 -104
- package/dist/ui/assets/app-BlDdP396.js +0 -61
- package/dist/ui-CZS1XKla.mjs +0 -160
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import { T as inspectProject } from "./src-CGqMati3.mjs";
|
|
2
|
+
import { a as write, c as openStore, i as style, o as writeErr, t as confirm } from "./output-CXSJO00X.mjs";
|
|
3
|
+
import { a as planClean, i as keyOf, o as stepActs, t as applyClean } from "./clean-Dcr807be.mjs";
|
|
4
|
+
import { a as readWorktrees, c as planLine, n as lostWork, o as labelOf, r as planRemove, s as mergeStateOf, t as applyRemove, u as stateParts } from "./remove-AwaMMaHf.mjs";
|
|
5
|
+
import { defineCommand } from "citty";
|
|
6
|
+
import path from "node:path";
|
|
7
|
+
// src/project/worktrees.ts
|
|
8
|
+
/**
|
|
9
|
+
* @file What `ork worktrees` prints: each worktree or branch a run left, with
|
|
10
|
+
* its state read from git, the plan `ork worktrees clean` or `remove` would
|
|
11
|
+
* carry out, and what a forced remove would lose. The words are core's
|
|
12
|
+
* (worktree/words.ts), which the Worktrees panel says too; this only colors
|
|
13
|
+
* them.
|
|
14
|
+
*/
|
|
15
|
+
/** One line of state: its merge state, then what clean would care about. */
|
|
16
|
+
const worktreeState = (item) => {
|
|
17
|
+
const merge = mergeStateOf(item);
|
|
18
|
+
const head = merge === "merged" ? [style("green", "merged")] : merge === "detached" ? ["detached"] : [];
|
|
19
|
+
const parts = stateParts(item).map((part) => part.risk ? style("yellow", part.text) : part.text);
|
|
20
|
+
return [...head, ...parts].join(" · ");
|
|
21
|
+
};
|
|
22
|
+
/** A name as the list shows it, saying when it's detached. */
|
|
23
|
+
const nameOf = (item) => mergeStateOf(item) === "detached" ? `${labelOf(item)} (detached)` : labelOf(item);
|
|
24
|
+
const formatWorktrees = (items) => {
|
|
25
|
+
if (items.length === 0) return ["No Ork worktrees or branches."];
|
|
26
|
+
const width = Math.max(...items.map((i) => nameOf(i).length));
|
|
27
|
+
return items.flatMap((item) => [`${style("bold", nameOf(item).padEnd(width))} ${worktreeState(item)}`, ...item.path === null ? [] : [` ${style("dim", item.path)}`]]);
|
|
28
|
+
};
|
|
29
|
+
const formatPlan = (steps) => steps.map((step) => {
|
|
30
|
+
const { text, reason } = planLine(step);
|
|
31
|
+
return ` ${text}${reason === void 0 ? "" : style("dim", ` (${reason})`)}`;
|
|
32
|
+
});
|
|
33
|
+
/** What a forced remove loses, as git prints it, under a heading each. */
|
|
34
|
+
const formatLost = (lost) => [...lost.changes.length === 0 ? [] : ["Uncommitted changes:", ...lost.changes.map((line) => ` ${line}`)], ...lost.commits.length === 0 ? [] : [lost.commitsOn === "no branch" ? "Commits on no branch:" : "Commits not merged:", ...lost.commits.map((line) => ` ${line}`)]];
|
|
35
|
+
// src/commands/worktrees.ts
|
|
36
|
+
/**
|
|
37
|
+
* @file `ork worktrees` (or `ork wt`): lists the worktrees and branches runs
|
|
38
|
+
* left behind (those the runs recorded creating, whatever the project's git
|
|
39
|
+
* rules named them, and any `ork/` branch), with their state read from git;
|
|
40
|
+
* cleans them up safely after showing the plan and asking; and removes one,
|
|
41
|
+
* the twin of the Worktrees panel's Remove…. The plans are core's, so the
|
|
42
|
+
* panel keeps and removes exactly what these do.
|
|
43
|
+
*/
|
|
44
|
+
/** The repo this folder is in, and what Ork created there; null outside one. */
|
|
45
|
+
const here = async () => {
|
|
46
|
+
const root = (await inspectProject(process.cwd()))?.root ?? null;
|
|
47
|
+
if (root === null) {
|
|
48
|
+
writeErr("Not in a git repository with a commit.");
|
|
49
|
+
process.exitCode = 1;
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
const store = openStore();
|
|
53
|
+
try {
|
|
54
|
+
return {
|
|
55
|
+
root,
|
|
56
|
+
...await readWorktrees(store, root)
|
|
57
|
+
};
|
|
58
|
+
} finally {
|
|
59
|
+
store.close();
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
/** Asks before acting, unless `--yes`; without a terminal `--yes` is needed. */
|
|
63
|
+
const goAhead = (yes, verb) => {
|
|
64
|
+
if (yes) return Promise.resolve(true);
|
|
65
|
+
if (!process.stdin.isTTY) {
|
|
66
|
+
writeErr(`Pass --yes to ${verb} without a terminal.`);
|
|
67
|
+
process.exitCode = 1;
|
|
68
|
+
return Promise.resolve(false);
|
|
69
|
+
}
|
|
70
|
+
return confirm("Go ahead? [y/N] ");
|
|
71
|
+
};
|
|
72
|
+
const done = (lines) => {
|
|
73
|
+
for (const line of lines) write(`${style("green", "✓")} ${line}`);
|
|
74
|
+
};
|
|
75
|
+
const show = async (json) => {
|
|
76
|
+
const found = await here();
|
|
77
|
+
if (found === null) return;
|
|
78
|
+
if (json) write(JSON.stringify(found.items, void 0, 2));
|
|
79
|
+
else for (const line of formatWorktrees(found.items)) write(line);
|
|
80
|
+
};
|
|
81
|
+
const list = defineCommand({
|
|
82
|
+
meta: {
|
|
83
|
+
name: "list",
|
|
84
|
+
description: "List Ork's worktrees and branches (the default)"
|
|
85
|
+
},
|
|
86
|
+
args: { json: {
|
|
87
|
+
type: "boolean",
|
|
88
|
+
description: "Print JSON",
|
|
89
|
+
default: false
|
|
90
|
+
} },
|
|
91
|
+
run: ({ args }) => show(args.json)
|
|
92
|
+
});
|
|
93
|
+
const yes = {
|
|
94
|
+
type: "boolean",
|
|
95
|
+
description: "Do not ask",
|
|
96
|
+
default: false
|
|
97
|
+
};
|
|
98
|
+
const clean = defineCommand({
|
|
99
|
+
meta: {
|
|
100
|
+
name: "clean",
|
|
101
|
+
description: "Remove Ork's worktrees (merged ones by default); never one with uncommitted changes unless --force, nor one whose run is still going"
|
|
102
|
+
},
|
|
103
|
+
args: {
|
|
104
|
+
run: {
|
|
105
|
+
type: "string",
|
|
106
|
+
description: "Only this run's worktrees"
|
|
107
|
+
},
|
|
108
|
+
all: {
|
|
109
|
+
type: "boolean",
|
|
110
|
+
description: "Include worktrees whose branch is not merged",
|
|
111
|
+
default: false
|
|
112
|
+
},
|
|
113
|
+
branches: {
|
|
114
|
+
type: "boolean",
|
|
115
|
+
description: "Also delete the branches",
|
|
116
|
+
default: false
|
|
117
|
+
},
|
|
118
|
+
force: {
|
|
119
|
+
type: "boolean",
|
|
120
|
+
description: "Remove uncommitted changes and unmerged branches too",
|
|
121
|
+
default: false
|
|
122
|
+
},
|
|
123
|
+
yes
|
|
124
|
+
},
|
|
125
|
+
run: async ({ args }) => {
|
|
126
|
+
const found = await here();
|
|
127
|
+
if (found === null) return;
|
|
128
|
+
const filter = { merged: !args.all };
|
|
129
|
+
if (args.run !== void 0) filter.runId = args.run;
|
|
130
|
+
const steps = planClean(found.items, filter, {
|
|
131
|
+
branches: args.branches,
|
|
132
|
+
force: args.force,
|
|
133
|
+
live: found.live
|
|
134
|
+
});
|
|
135
|
+
const acting = steps.filter((s) => stepActs(s));
|
|
136
|
+
if (acting.length === 0) {
|
|
137
|
+
for (const line of formatPlan(steps)) write(line);
|
|
138
|
+
write("Nothing to clean.");
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
write("Plan:");
|
|
142
|
+
for (const line of formatPlan(steps)) write(line);
|
|
143
|
+
if (!await goAhead(args.yes, "clean")) return;
|
|
144
|
+
done(await applyClean(found.root, acting, args.force));
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
const remove = defineCommand({
|
|
148
|
+
meta: {
|
|
149
|
+
name: "remove",
|
|
150
|
+
description: "Remove one of Ork's worktrees, by its branch or its path; --force when that loses work"
|
|
151
|
+
},
|
|
152
|
+
args: {
|
|
153
|
+
target: {
|
|
154
|
+
type: "positional",
|
|
155
|
+
description: "The branch, or the worktree's path",
|
|
156
|
+
required: true
|
|
157
|
+
},
|
|
158
|
+
branch: {
|
|
159
|
+
type: "boolean",
|
|
160
|
+
description: "Also delete the branch",
|
|
161
|
+
default: false
|
|
162
|
+
},
|
|
163
|
+
force: {
|
|
164
|
+
type: "boolean",
|
|
165
|
+
description: "Remove even when that loses uncommitted changes or commits",
|
|
166
|
+
default: false
|
|
167
|
+
},
|
|
168
|
+
yes
|
|
169
|
+
},
|
|
170
|
+
run: async ({ args }) => {
|
|
171
|
+
const found = await here();
|
|
172
|
+
if (found === null) return;
|
|
173
|
+
const byPath = path.resolve(args.target);
|
|
174
|
+
const item = found.items.find((i) => keyOf(i) === args.target || i.path === byPath);
|
|
175
|
+
const planned = planRemove(found, item === void 0 ? args.target : keyOf(item), args.branch);
|
|
176
|
+
if (!planned.ok) {
|
|
177
|
+
writeErr(planned.error);
|
|
178
|
+
process.exitCode = 1;
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
const { plan } = planned;
|
|
182
|
+
write("Plan:");
|
|
183
|
+
for (const line of formatPlan([plan.step])) write(line);
|
|
184
|
+
if (plan.force) {
|
|
185
|
+
for (const line of formatLost(await lostWork(found.root, plan))) write(line);
|
|
186
|
+
if (!args.force) {
|
|
187
|
+
writeErr("That loses work; pass --force to remove it anyway.");
|
|
188
|
+
process.exitCode = 1;
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
if (!await goAhead(args.yes, "remove")) return;
|
|
193
|
+
done(await applyRemove(found.root, plan));
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
const worktrees = defineCommand({
|
|
197
|
+
meta: {
|
|
198
|
+
name: "worktrees",
|
|
199
|
+
description: "Track and clean up the git worktrees Ork creates for runs"
|
|
200
|
+
},
|
|
201
|
+
subCommands: {
|
|
202
|
+
list,
|
|
203
|
+
clean,
|
|
204
|
+
remove
|
|
205
|
+
},
|
|
206
|
+
run: async ({ rawArgs }) => {
|
|
207
|
+
const [first] = rawArgs;
|
|
208
|
+
if (first === "list" || first === "clean" || first === "remove") return;
|
|
209
|
+
await show(rawArgs.includes("--json"));
|
|
210
|
+
}
|
|
211
|
+
});
|
|
212
|
+
export { worktrees };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@krapht/ork",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "Orchestrate Claude agents on your machine: hierarchical, serializable workflows with a CLI and a localhost UI.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"claude",
|
|
@@ -39,7 +39,7 @@
|
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
41
|
"@anthropic-ai/claude-agent-sdk": "0.3.309",
|
|
42
|
-
"@anthropic-ai/sdk": "1.
|
|
42
|
+
"@anthropic-ai/sdk": "1.3.0",
|
|
43
43
|
"@modelcontextprotocol/sdk": "1.32.1",
|
|
44
44
|
"citty": "0.2.2",
|
|
45
45
|
"yaml": "2.9.1",
|
|
@@ -53,9 +53,9 @@
|
|
|
53
53
|
"@krapht/ork-schema": "0.0.0",
|
|
54
54
|
"@krapht/ork-server": "0.0.0",
|
|
55
55
|
"@krapht/ork-ui": "0.0.0",
|
|
56
|
-
"tsdown": "0.23.
|
|
56
|
+
"tsdown": "0.23.4"
|
|
57
57
|
},
|
|
58
58
|
"scripts": {
|
|
59
|
-
"build": "pnpm --filter @krapht/ork-ui build && node ../../tools/release/build-info.ts && node ../../tools/release/plugin-skills.ts && tsdown && cp ../../LICENSE ../../THIRD_PARTY_NOTICES.md . && cp -R ../ui/dist dist/ui"
|
|
59
|
+
"build": "pnpm --filter @krapht/ork-ui build && node ../../tools/release/build-info.ts && node ../../tools/release/plugin-skills.ts && node ../../tools/release/builtin-workflows.ts && tsdown && cp ../../LICENSE ../../THIRD_PARTY_NOTICES.md . && cp -R ../ui/dist dist/ui"
|
|
60
60
|
}
|
|
61
61
|
}
|
|
@@ -1,104 +0,0 @@
|
|
|
1
|
-
import { Ft as socketPath, It as wakeRun, rt as liveStatus } from "./src-Cz1yOUTg.mjs";
|
|
2
|
-
// ../core/src/remote/decide.ts
|
|
3
|
-
/**
|
|
4
|
-
* @file The open decisions of live runs, and answering one from another process
|
|
5
|
-
* (`ork decide`, the local server). An answer goes to the store (the first
|
|
6
|
-
* one wins), then the run is woken on its socket so it picks the answer up at
|
|
7
|
-
* once (specs/messaging.md).
|
|
8
|
-
*/
|
|
9
|
-
/** Open decisions whose run is still live, oldest first. */
|
|
10
|
-
const pendingDecisions = (store, filter) => {
|
|
11
|
-
const live = /* @__PURE__ */ new Map();
|
|
12
|
-
return store.decisions({
|
|
13
|
-
...filter,
|
|
14
|
-
status: "open"
|
|
15
|
-
}).filter((decision) => {
|
|
16
|
-
if (!live.has(decision.runId)) {
|
|
17
|
-
const [run] = store.lookup(decision.runId);
|
|
18
|
-
live.set(decision.runId, run !== void 0 && liveStatus(run) === "running");
|
|
19
|
-
}
|
|
20
|
-
return live.get(decision.runId) === true;
|
|
21
|
-
});
|
|
22
|
-
};
|
|
23
|
-
/**
|
|
24
|
-
* Answers an open decision with one of its options, and wakes its run. `home`
|
|
25
|
-
* is `~`, where the sockets are (tests pass a folder); `by` says who answered.
|
|
26
|
-
*/
|
|
27
|
-
const answerDecision = async (store, id, choice, home, by = "decide") => {
|
|
28
|
-
const decision = store.decision(id);
|
|
29
|
-
if (decision === void 0) return {
|
|
30
|
-
ok: false,
|
|
31
|
-
error: `No decision \`${id}\`.`
|
|
32
|
-
};
|
|
33
|
-
if (!decision.options.some((o) => o.id === choice)) return {
|
|
34
|
-
ok: false,
|
|
35
|
-
error: `\`${choice}\` is not an option of \`${id}\`: \`${decision.options.map((o) => o.id).join("`, `")}\``
|
|
36
|
-
};
|
|
37
|
-
if (!store.answerDecision(id, choice, by)) {
|
|
38
|
-
const now = store.decision(id);
|
|
39
|
-
return {
|
|
40
|
-
ok: false,
|
|
41
|
-
error: now?.status === "answered" ? `${id} was already answered: ${now.answer ?? ""} (${now.answeredBy ?? ""})` : `${id} was withdrawn`
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
return {
|
|
45
|
-
ok: true,
|
|
46
|
-
woke: await wakeRun(socketPath(decision.runId, home))
|
|
47
|
-
};
|
|
48
|
-
};
|
|
49
|
-
// ../core/src/remote/session.ts
|
|
50
|
-
const chattable = (type) => type === "claude.leader" || type === "claude.worker";
|
|
51
|
-
/** Reads `node`'s session from a run's events, and every node's last output. */
|
|
52
|
-
const nodeSession = (events, node) => {
|
|
53
|
-
const session = {
|
|
54
|
-
known: false,
|
|
55
|
-
chattable: false,
|
|
56
|
-
running: false
|
|
57
|
-
};
|
|
58
|
-
const outputs = {};
|
|
59
|
-
for (const { event } of events) {
|
|
60
|
-
if (event.type === "run.started") {
|
|
61
|
-
const declared = event.nodes.find((n) => n.id === node);
|
|
62
|
-
if (declared !== void 0) {
|
|
63
|
-
session.known = true;
|
|
64
|
-
session.chattable = chattable(declared.type);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
if (event.type === "node.finished") outputs[event.id] = event.output;
|
|
68
|
-
if (!("id" in event) || event.id !== node) continue;
|
|
69
|
-
if (event.type === "node.started") {
|
|
70
|
-
session.known = true;
|
|
71
|
-
session.chattable = chattable(event.nodeType);
|
|
72
|
-
session.running = true;
|
|
73
|
-
}
|
|
74
|
-
if (event.type === "node.finished") session.running = false;
|
|
75
|
-
if (event.type === "worktree" && event.action === "created") session.checkout = {
|
|
76
|
-
path: event.path,
|
|
77
|
-
...event.branch === void 0 ? {} : { branch: event.branch },
|
|
78
|
-
own: true,
|
|
79
|
-
owner: node
|
|
80
|
-
};
|
|
81
|
-
if (event.type !== "node.event") continue;
|
|
82
|
-
const inner = event.event;
|
|
83
|
-
if (inner.type === "session.started") {
|
|
84
|
-
session.sessionId = inner.sessionId;
|
|
85
|
-
if (inner.cwd !== void 0) session.cwd = inner.cwd;
|
|
86
|
-
if (inner.driver !== void 0) session.driver = inner.driver;
|
|
87
|
-
}
|
|
88
|
-
if (inner.type === "session.finished" && inner.outcome.sessionId !== "") session.sessionId = inner.outcome.sessionId;
|
|
89
|
-
}
|
|
90
|
-
return {
|
|
91
|
-
session,
|
|
92
|
-
outputs
|
|
93
|
-
};
|
|
94
|
-
};
|
|
95
|
-
// src/__build_info__.ts
|
|
96
|
-
/**
|
|
97
|
-
* @file The published package's name and version, bound to
|
|
98
|
-
* `packages/cli/package.json`. Do not edit by hand: `pnpm changesets:version`
|
|
99
|
-
* and `pnpm build` rewrite it (`tools/release/build-info.ts`), and a test
|
|
100
|
-
* fails when it is stale.
|
|
101
|
-
*/
|
|
102
|
-
const ORK_NAME = "@krapht/ork";
|
|
103
|
-
const ORK_VERSION = "0.3.0";
|
|
104
|
-
export { pendingDecisions as a, answerDecision as i, ORK_VERSION as n, nodeSession as r, ORK_NAME as t };
|