@krapht/ork 1.0.0-alpha.15 → 1.0.0-alpha.17
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 +10 -4
- package/dist/{__build_info__-Bsb1kjgF.mjs → __build_info__-C1Nm0hlJ.mjs} +25 -6
- package/dist/apply-Mb0fNpCX.mjs +17 -0
- package/dist/{attach-CPespxlQ.mjs → attach-EP374oMP.mjs} +5 -5
- package/dist/{attach-BHO3X3cf.mjs → attach-RQx__RzH.mjs} +46 -8
- package/dist/bin.mjs +238 -369
- package/dist/brief-Bs3cMQU0.mjs +100 -0
- package/dist/{builtin-files-2bHf76x-.mjs → builtin-files-yYaNTQRU.mjs} +1 -1
- package/dist/command-tree-BkSYWmYJ.mjs +63 -0
- package/dist/{completion-CQ2UYKuJ.mjs → completion-DIXWlnAl.mjs} +87 -6
- package/dist/{context-BNqDw9z1.mjs → context-Dq-VM_sq.mjs} +3 -3
- package/dist/{find-BYLlszqj.mjs → find-k489laBa.mjs} +2 -2
- package/dist/flags-B4ZZWspN.mjs +125 -0
- package/dist/{follow-ups-B4GJ3Vzw.mjs → follow-ups-DD8NTPe6.mjs} +59 -24
- package/dist/{format-C8MsFj8_.mjs → format-DQc9A_ei.mjs} +17 -4
- package/dist/{help-groups-CQ98mLjF.mjs → help-groups-hB6kY34d.mjs} +1 -63
- package/dist/in-a-run-D_9GlAlS.mjs +27 -0
- package/dist/{install-DWtXTq4y.mjs → install-PVlV2QPq.mjs} +1 -1
- package/dist/{limits-B1qzS4cX.mjs → limits-CHqBO7wb.mjs} +1 -1
- package/dist/{mcp-BOrVkyhN.mjs → mcp-D38Ajb0b.mjs} +6 -6
- package/dist/{once-C1fL3GVo.mjs → once-DuUzpzL2.mjs} +3 -3
- package/dist/parse-BzWpMN-a.mjs +107 -0
- package/dist/{pause-DOMK-R8V.mjs → pause-Bx3ePGrn.mjs} +3 -3
- package/dist/{post-BzA6Qobq.mjs → post-2CaJz_mV.mjs} +5 -3
- package/dist/print-n2qetUHc.mjs +19 -0
- package/dist/project-records-Cune1l6I.mjs +230 -0
- package/dist/{relay-OWuu4fib.mjs → relay-D7RN4m68.mjs} +1 -1
- package/dist/{run-CiPsW9Kf.mjs → run-BfxdsQJk.mjs} +1 -1
- package/dist/{run--4-1xAF2.mjs → run-OOWQIKMJ.mjs} +33 -20
- package/dist/{send-CnNNW-ot.mjs → send-CKth_vgn.mjs} +2 -2
- package/dist/{src-B9zWIVqo.mjs → src-BHod38UQ.mjs} +104 -72
- package/dist/{src-Dr_fTSu-.mjs → src-DLsFsmOD.mjs} +790 -502
- package/dist/{src-DQKz2eOH.mjs → src-DOjTY2ET.mjs} +238 -79
- package/dist/{src-DcrhgipG.mjs → src-DmmrX2_C.mjs} +162 -58
- package/dist/{store-oKJ-cCf3.mjs → store-DACH768X.mjs} +1 -1
- package/dist/ui/assets/app-9Q-62Ywz.css +1 -0
- package/dist/ui/assets/app-CN3acQ-1.js +111 -0
- package/dist/ui/assets/{index-BtwkftKe.js → index-B2uGxA4W.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/dist/{ui-CzKlsCnm.mjs → ui-CJ-BX12n.mjs} +3 -3
- package/dist/{usage-DHGNk2JL.mjs → usage-DxZ6JGd4.mjs} +62 -107
- package/dist/{validate-Bs_gg22g.mjs → validate-BS0nCccW.mjs} +35 -8
- package/dist/{watch-CCCfw3n7.mjs → watch-Dp7IB4FR.mjs} +5 -5
- package/dist/{workflows-CW28ZU-z.mjs → workflows-Djo1edlK.mjs} +2 -2
- package/dist/{worktrees-CyE8aF6j.mjs → worktrees-Drof_Nyx.mjs} +49 -20
- package/package.json +8 -4
- package/dist/brief-za0ozN_E.mjs +0 -58
- package/dist/in-a-run-Bp0k0J09.mjs +0 -24
- package/dist/ui/assets/app-Bdc6OBeZ.css +0 -1
- package/dist/ui/assets/app-Drp8Dm9y.js +0 -111
|
@@ -113,68 +113,6 @@ complete -c ork -f -a '(__ork_complete)'
|
|
|
113
113
|
};
|
|
114
114
|
/** The shells there is a script for. */
|
|
115
115
|
const SHELLS = Object.keys(SHELL_SCRIPTS);
|
|
116
|
-
// src/command-tree.ts
|
|
117
|
-
/** How a flag that only Ork passes to itself starts its description. */
|
|
118
|
-
const INTERNAL = "Internal:";
|
|
119
|
-
/** Whether citty was given a function that gives the value. */
|
|
120
|
-
const isThunk = (value) => typeof value === "function";
|
|
121
|
-
/** A value citty lets be given as is, as a promise, or as a function. */
|
|
122
|
-
const resolved = (value) => Promise.resolve(isThunk(value) ? value() : value);
|
|
123
|
-
/** `args` without the flags only Ork passes to itself. */
|
|
124
|
-
const publicArgs = (args) => Object.fromEntries(Object.entries(args).filter(([, def]) => !(def.description ?? "").startsWith(INTERNAL)));
|
|
125
|
-
/** A command's flags and positionals, which citty lets be given lazily. */
|
|
126
|
-
const argsOf = (cmd) => cmd.args === void 0 ? Promise.resolve({}) : resolved(cmd.args);
|
|
127
|
-
const listOf = (value) => value === void 0 ? [] : [value].flat();
|
|
128
|
-
const flagOf = (name, def) => ({
|
|
129
|
-
name,
|
|
130
|
-
description: def.description ?? "",
|
|
131
|
-
takesValue: def.type === "string" || def.type === "enum",
|
|
132
|
-
aliases: "alias" in def ? listOf(def.alias) : [],
|
|
133
|
-
options: def.type === "enum" ? def.options ?? [] : [],
|
|
134
|
-
negatable: def.type === "boolean" && def.default === true
|
|
135
|
-
});
|
|
136
|
-
/** A command's public flags and positionals, in the order it defines them. */
|
|
137
|
-
const argInfoOf = async (cmd) => {
|
|
138
|
-
const entries = Object.entries(publicArgs(await argsOf(cmd)));
|
|
139
|
-
return {
|
|
140
|
-
flags: entries.filter(([, def]) => def.type !== "positional").map(([name, def]) => flagOf(name, def)),
|
|
141
|
-
positionals: entries.filter(([, def]) => def.type === "positional").map(([name, def]) => ({
|
|
142
|
-
name,
|
|
143
|
-
description: def.description ?? ""
|
|
144
|
-
}))
|
|
145
|
-
};
|
|
146
|
-
};
|
|
147
|
-
/**
|
|
148
|
-
* A command's subcommands, in order, loaded ones resolved. A name whose
|
|
149
|
-
* definition is one listed before it (`wt`, `worktrees`'s) is an alias of that
|
|
150
|
-
* one, not an entry of its own.
|
|
151
|
-
*/
|
|
152
|
-
const subcommandsOf = async (cmd) => {
|
|
153
|
-
const subs = cmd.subCommands === void 0 ? {} : await resolved(cmd.subCommands);
|
|
154
|
-
const loaded = await Promise.all(Object.entries(subs).map(async ([name, sub]) => {
|
|
155
|
-
const def = await resolved(sub);
|
|
156
|
-
return {
|
|
157
|
-
name,
|
|
158
|
-
def,
|
|
159
|
-
meta: def.meta === void 0 ? {} : await resolved(def.meta)
|
|
160
|
-
};
|
|
161
|
-
}));
|
|
162
|
-
const entries = [];
|
|
163
|
-
for (const { name, def, meta } of loaded) {
|
|
164
|
-
const same = entries.find((entry) => entry.def === def);
|
|
165
|
-
if (same === void 0) entries.push({
|
|
166
|
-
name,
|
|
167
|
-
aliases: listOf(meta.alias),
|
|
168
|
-
description: meta.description ?? "",
|
|
169
|
-
hidden: meta.hidden === true,
|
|
170
|
-
def
|
|
171
|
-
});
|
|
172
|
-
else same.aliases.push(name);
|
|
173
|
-
}
|
|
174
|
-
return entries;
|
|
175
|
-
};
|
|
176
|
-
/** The subcommand `name` (or one of its aliases) of `entries`. */
|
|
177
|
-
const findEntry = (entries, name) => entries.find((entry) => entry.name === name || entry.aliases.includes(name));
|
|
178
116
|
// src/help-groups.ts
|
|
179
117
|
const HELP_GROUPS = [
|
|
180
118
|
{
|
|
@@ -373,4 +311,4 @@ const groupedEntries = (entries) => {
|
|
|
373
311
|
entries: other
|
|
374
312
|
}].filter((group) => group.entries.length > 0);
|
|
375
313
|
};
|
|
376
|
-
export {
|
|
314
|
+
export { SHELL_SCRIPTS as a, SHELLS as i, groupedEntries as n, COMPLETE_COMMAND as r, groupOf as t };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { R as storePath } from "./src-DLsFsmOD.mjs";
|
|
2
|
+
import { g as storeGuarded, h as runMarkerIn } from "./src-DmmrX2_C.mjs";
|
|
3
|
+
// src/commands/in-a-run.ts
|
|
4
|
+
/**
|
|
5
|
+
* @file The commands that act as the user refuse a program or an agent in a run
|
|
6
|
+
* (ADR-047), in the stores that run guards: the store of a nested run under a
|
|
7
|
+
* temp HOME is not guarded unless an enclosing run lists it. The refusal
|
|
8
|
+
* names the variable it saw, for someone at their own terminal who inherited
|
|
9
|
+
* it. It stops accidents; it is not a boundary.
|
|
10
|
+
*/
|
|
11
|
+
const refusal = (why, env, store) => {
|
|
12
|
+
const marker = runMarkerIn(env);
|
|
13
|
+
return marker === null || !storeGuarded(env, store) ? null : `${why} ${marker} is set here. If this is your own terminal, it inherited that from a program in a run (a tmux server started by one, say); open a shell without it.`;
|
|
14
|
+
};
|
|
15
|
+
/** Why `ork watch --accept` refuses here, or `null` when it may run. */
|
|
16
|
+
const acceptRefusal = (env = process.env, store = storePath()) => refusal("`ork watch --accept` approves code to run with nobody there, so a program or an agent in a run cannot use it.", env, store);
|
|
17
|
+
/** Why `ork chat` refuses here, or `null` when it may run. */
|
|
18
|
+
const chatRefusal = (env = process.env, store = storePath()) => refusal("`ork chat` speaks as you, so a program or an agent in a run cannot use it. A program writes to a session with `ork send`.", env, store);
|
|
19
|
+
/** Why `ork decide` will not answer here, or `null` when it may. */
|
|
20
|
+
const decideRefusal = (env = process.env, store = storePath()) => refusal("`ork decide` answers as you, so a program or an agent in a run cannot answer with it; `ork decide --list` still shows what is open.", env, store);
|
|
21
|
+
/** Why `ork continue` refuses here, or `null` when it may run. */
|
|
22
|
+
const continueRefusal = (env = process.env, store = storePath()) => refusal("`ork continue` undoes a pause you set, so a program or an agent in a run cannot use it.", env, store);
|
|
23
|
+
/** Whether `ork ui` may print its sign-in link, which acts as the user. */
|
|
24
|
+
const mayShowSignIn = (env = process.env, store = storePath()) => !storeGuarded(env, store);
|
|
25
|
+
/** Why `ork attach` will not answer here, or `null` when it may. */
|
|
26
|
+
const attachRefusal = (env = process.env, store = storePath()) => refusal("`ork attach` answers as you, so a program or an agent in a run cannot use it; `ork logs` still shows the run.", env, store);
|
|
27
|
+
export { decideRefusal as a, continueRefusal as i, attachRefusal as n, mayShowSignIn as o, chatRefusal as r, acceptRefusal as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Br as inside, Fr as placeFor, Gr as plainPathInside, Hr as LESSON_CAP_TOKENS, Kr as writePlainFile, Pr as placeFiles, Qn as FEEDBACK_SEVERITIES, Ur as estimateTokens, Wr as guardedBase, Zn as FEEDBACK_KINDS, ir as publishPost, lr as targetOf, sr as fingerprint, yr as maskSecrets } from "./src-DLsFsmOD.mjs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
4
4
|
import { spawn } from "node:child_process";
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ as capsHere, dt as NOT_STARTED, ft as limitsNow, pt as limitsReport } from "./src-DLsFsmOD.mjs";
|
|
2
2
|
// src/run/limits.ts
|
|
3
3
|
/**
|
|
4
4
|
* @file The limits across runs that apply here (RFC-007): the user's settings
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { n as ORK_VERSION } from "./__build_info__-
|
|
3
|
-
import {
|
|
4
|
-
import { n as explainNode } from "./context-
|
|
5
|
-
import { a as openMcp, c as refused, o as tool, s as answer } from "./src-
|
|
1
|
+
import { w as isNewer } from "./src-DLsFsmOD.mjs";
|
|
2
|
+
import { n as ORK_VERSION } from "./__build_info__-C1Nm0hlJ.mjs";
|
|
3
|
+
import { h as runMarkerIn } from "./src-DmmrX2_C.mjs";
|
|
4
|
+
import { n as explainNode } from "./context-Dq-VM_sq.mjs";
|
|
5
|
+
import { a as openMcp, c as refused, o as tool, s as answer } from "./src-DOjTY2ET.mjs";
|
|
6
6
|
import { z } from "zod";
|
|
7
7
|
import { defineCommand } from "citty";
|
|
8
8
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
@@ -43,7 +43,7 @@ const orkMcpServer = (options = {}) => {
|
|
|
43
43
|
...options.script === void 0 ? {} : { script: options.script }
|
|
44
44
|
});
|
|
45
45
|
const all = [...opened.tools, explainTool(opened, options.home)];
|
|
46
|
-
const tools = options.expects !== void 0 && isNewer(options.expects, "1.0.0-alpha.
|
|
46
|
+
const tools = options.expects !== void 0 && isNewer(options.expects, "1.0.0-alpha.17") ? tooOld(all, options.expects) : all;
|
|
47
47
|
const server = new McpServer({
|
|
48
48
|
name: "ork",
|
|
49
49
|
version: ORK_VERSION
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import "./src-
|
|
2
|
-
import { t as runOnce } from "./run-
|
|
1
|
+
import "./src-DLsFsmOD.mjs";
|
|
2
|
+
import { t as runOnce } from "./run-BfxdsQJk.mjs";
|
|
3
3
|
import { c as write, l as writeErr, s as style } from "./output-B1kwo0YU.mjs";
|
|
4
|
-
import { t as ago } from "./format-
|
|
4
|
+
import { t as ago } from "./format-DQc9A_ei.mjs";
|
|
5
5
|
import { spawn } from "node:child_process";
|
|
6
6
|
import { defineCommand, parseArgs } from "citty";
|
|
7
7
|
// src/commands/once.ts
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
// ../core/src/list-filters/time.ts
|
|
2
|
+
/**
|
|
3
|
+
* @file The times every list's `--since` and `--until` take (specs/usage.md,
|
|
4
|
+
* RFC-017), and `ork prune --older-than`'s age: an age (`24h`, `30d`, `12w`)
|
|
5
|
+
* or a day (`2026-11-01`, in the machine's time zone).
|
|
6
|
+
*/
|
|
7
|
+
const UNITS = {
|
|
8
|
+
h: 36e5,
|
|
9
|
+
d: 864e5,
|
|
10
|
+
w: 6048e5
|
|
11
|
+
};
|
|
12
|
+
/** `30d`, `12w`, `24h` before `now`, as an ISO time; `null` when it is not one. */
|
|
13
|
+
const agoTime = (text, now) => {
|
|
14
|
+
const match = /^(\d+)([hdw])$/u.exec(text);
|
|
15
|
+
if (match === null) return null;
|
|
16
|
+
const [, amount = "0", unit = "d"] = match;
|
|
17
|
+
if (Number(amount) === 0) return null;
|
|
18
|
+
const date = new Date(now - Number(amount) * (UNITS[unit] ?? 0));
|
|
19
|
+
return Number.isNaN(date.getTime()) ? null : date.toISOString();
|
|
20
|
+
};
|
|
21
|
+
/** A `YYYY-MM-DD` day's start in the machine's time zone, `after` days on. */
|
|
22
|
+
const dayTime = (text, after) => {
|
|
23
|
+
const match = /^(\d{4})-(\d{2})-(\d{2})$/u.exec(text);
|
|
24
|
+
if (match === null) return null;
|
|
25
|
+
const [, year = "", month = "", day = ""] = match;
|
|
26
|
+
const start = new Date(Number(year), Number(month) - 1, Number(day));
|
|
27
|
+
if (start.getMonth() !== Number(month) - 1) return null;
|
|
28
|
+
if (start.getDate() !== Number(day)) return null;
|
|
29
|
+
start.setDate(start.getDate() + after);
|
|
30
|
+
return start.toISOString();
|
|
31
|
+
};
|
|
32
|
+
/** The start of the day `days` whole days ago, counting today as one. */
|
|
33
|
+
const wholeDays = (days, now) => {
|
|
34
|
+
const today = new Date(now);
|
|
35
|
+
return new Date(today.getFullYear(), today.getMonth(), today.getDate() - (days - 1)).toISOString();
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* `--since`: an age or the day it starts from. Days and weeks are whole days,
|
|
39
|
+
* today among them, as the Usage panel counts them; hours are exact.
|
|
40
|
+
*/
|
|
41
|
+
const sinceTime = (text, now) => {
|
|
42
|
+
const ago = agoTime(text, now);
|
|
43
|
+
if (ago === null) return dayTime(text, 0);
|
|
44
|
+
if (text.endsWith("h")) return ago;
|
|
45
|
+
const amount = Number(text.slice(0, -1));
|
|
46
|
+
return wholeDays(text.endsWith("w") ? amount * 7 : amount, now);
|
|
47
|
+
};
|
|
48
|
+
/** `--until`: the last day it includes. */
|
|
49
|
+
const untilTime = (text) => dayTime(text, 1);
|
|
50
|
+
// ../core/src/list-filters/parse.ts
|
|
51
|
+
/**
|
|
52
|
+
* @file Reads a list's filter flags against its field table (RFC-017): each
|
|
53
|
+
* value checked, times turned into ISO times, and `--limit`.
|
|
54
|
+
*/
|
|
55
|
+
const FOR = /^(agent|me|(project|person):\S+)$/u;
|
|
56
|
+
/** The field's value, checked; `null` when the field does not take it. */
|
|
57
|
+
const valueOf = (field, text, now) => {
|
|
58
|
+
if (text === "all" && field.all === true) return "all";
|
|
59
|
+
return {
|
|
60
|
+
enum: () => field.values?.includes(text) === true ? text : null,
|
|
61
|
+
text: () => text === "" ? null : text,
|
|
62
|
+
for: () => FOR.test(text) ? text : null,
|
|
63
|
+
since: () => sinceTime(text, now),
|
|
64
|
+
until: () => untilTime(text)
|
|
65
|
+
}[field.type]();
|
|
66
|
+
};
|
|
67
|
+
/** What the field takes, for the error that refuses a value. */
|
|
68
|
+
const takes = (field) => {
|
|
69
|
+
const all = field.all === true ? ["all"] : [];
|
|
70
|
+
const or = all.length === 0 ? "" : " or all";
|
|
71
|
+
return {
|
|
72
|
+
enum: `one of ${[...field.values ?? [], ...all].join(", ")}`,
|
|
73
|
+
text: `a value${or}`,
|
|
74
|
+
for: "agent, me, project:<name> or person:<name>",
|
|
75
|
+
since: `an age (30d), a day (2026-11-01)${or}`,
|
|
76
|
+
until: "a day (2026-11-01)"
|
|
77
|
+
}[field.type];
|
|
78
|
+
};
|
|
79
|
+
/**
|
|
80
|
+
* The filter `given` (a command's parsed flags, by field name) asks for, each
|
|
81
|
+
* value checked against `table`; `now` dates an age. The first value a field
|
|
82
|
+
* does not take is refused, naming the flag and what it takes.
|
|
83
|
+
*/
|
|
84
|
+
const parseFilters = (table, given, now) => {
|
|
85
|
+
const filter = {};
|
|
86
|
+
for (const field of table.fields) {
|
|
87
|
+
const text = given[field.name];
|
|
88
|
+
if (typeof text !== "string") continue;
|
|
89
|
+
const value = valueOf(field, text, now);
|
|
90
|
+
if (value === null) return {
|
|
91
|
+
ok: false,
|
|
92
|
+
error: `--${field.name} takes ${takes(field)}, not \`${text}\`.`
|
|
93
|
+
};
|
|
94
|
+
filter[field.name] = value;
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
ok: true,
|
|
98
|
+
filter
|
|
99
|
+
};
|
|
100
|
+
};
|
|
101
|
+
/** `--limit`: a positive whole number, else `null` (`0`, `-1`, `abc`, `2.5`). */
|
|
102
|
+
const parseLimit = (text) => {
|
|
103
|
+
if (!/^[1-9]\d*$/u.test(text)) return null;
|
|
104
|
+
const limit = Number(text);
|
|
105
|
+
return Number.isSafeInteger(limit) ? limit : null;
|
|
106
|
+
};
|
|
107
|
+
export { parseLimit as n, agoTime as r, parseFilters as t };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as findRun } from "./find-
|
|
1
|
+
import { Dn as requestPause, En as requestContinue, L as openStore } from "./src-DLsFsmOD.mjs";
|
|
2
|
+
import { t as findRun } from "./find-k489laBa.mjs";
|
|
3
3
|
import { c as write, l as writeErr, s as style } from "./output-B1kwo0YU.mjs";
|
|
4
|
-
import { i as continueRefusal } from "./in-a-run-
|
|
4
|
+
import { i as continueRefusal } from "./in-a-run-D_9GlAlS.mjs";
|
|
5
5
|
import { defineCommand } from "citty";
|
|
6
6
|
// src/commands/pause.ts
|
|
7
7
|
/**
|
|
@@ -1,11 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { ar as redact } from "./src-DLsFsmOD.mjs";
|
|
2
|
+
import { b as githubDraftRefusal } from "./__build_info__-C1Nm0hlJ.mjs";
|
|
2
3
|
// ../core/src/follow-ups/post.ts
|
|
3
4
|
/** Posts `f`'s draft as a GitHub comment once `confirmed`, and marks it done. */
|
|
4
5
|
const postFollowUpMessage = async (f, options) => {
|
|
5
6
|
const draft = f.draft;
|
|
6
|
-
|
|
7
|
+
const refused = githubDraftRefusal(f);
|
|
8
|
+
if (refused !== void 0 || draft === void 0) return {
|
|
7
9
|
ok: false,
|
|
8
|
-
error:
|
|
10
|
+
error: refused?.error ?? "it has no draft"
|
|
9
11
|
};
|
|
10
12
|
if (!options.confirmed) return {
|
|
11
13
|
ok: false,
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { c as write } from "./output-B1kwo0YU.mjs";
|
|
2
|
+
import { a as refuseIdsWithJson } from "./flags-B4ZZWspN.mjs";
|
|
3
|
+
// src/list/print.ts
|
|
4
|
+
/**
|
|
5
|
+
* Prints `rows`: `--ids` their ids (`table.id`) comma-separated; `--json` the
|
|
6
|
+
* rows (or `json(rows)`); else `human(rows)`, which also says what an empty
|
|
7
|
+
* list is. `--ids` with `--json` is refused (said, exit code set).
|
|
8
|
+
*/
|
|
9
|
+
const printList = (rows, table, args, human, json = (shown) => shown) => {
|
|
10
|
+
if (refuseIdsWithJson(args)) return;
|
|
11
|
+
if (args.ids === true) {
|
|
12
|
+
const id = table.id;
|
|
13
|
+
if (id !== void 0 && rows.length > 0) write(rows.map((row) => id(row)).join(","));
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
if (args.json === true) write(JSON.stringify(json(rows), void 0, 2));
|
|
17
|
+
else human(rows);
|
|
18
|
+
};
|
|
19
|
+
export { printList as t };
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
import { Ir as projectsHome, L as openStore, Mr as inRepoPlace, Nr as newProjectId, Pr as placeFiles, Rr as shownPath, Vr as projectRootOf, jr as hasInRepoFiles } from "./src-DLsFsmOD.mjs";
|
|
2
|
+
import { C as termOf, c as write, l as writeErr, s as style, w as cut } from "./output-B1kwo0YU.mjs";
|
|
3
|
+
import { n as chip } from "./chips-BqcT58qr.mjs";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { existsSync, mkdirSync, realpathSync, statSync } from "node:fs";
|
|
6
|
+
import { defineCommand } from "citty";
|
|
7
|
+
// src/project/register.ts
|
|
8
|
+
/**
|
|
9
|
+
* @file Registering projects (ADR-035): `ork register` attaches a repository
|
|
10
|
+
* from outside it, with its workflows, rules and runs in
|
|
11
|
+
* `~/.ork/projects/<id>/` and nothing written into the repository; `ork init`
|
|
12
|
+
* records an in-repo project, whose files are its `.ork/`.
|
|
13
|
+
*/
|
|
14
|
+
const ID = /^[a-z0-9][a-z0-9._-]*$/u;
|
|
15
|
+
/** The git root of `target`'s main clone, or why there is none. */
|
|
16
|
+
const cloneOf = (target) => {
|
|
17
|
+
if (!existsSync(target)) return {
|
|
18
|
+
ok: false,
|
|
19
|
+
error: `${target} does not exist`
|
|
20
|
+
};
|
|
21
|
+
const root = projectRootOf(realpathSync(target));
|
|
22
|
+
const dotGit = path.join(root, ".git");
|
|
23
|
+
if (!existsSync(dotGit)) return {
|
|
24
|
+
ok: false,
|
|
25
|
+
error: `${target} is not in a git repository`
|
|
26
|
+
};
|
|
27
|
+
if (statSync(dotGit).isFile()) return {
|
|
28
|
+
ok: false,
|
|
29
|
+
error: `${root} is a git worktree; register the main clone instead`
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
ok: true,
|
|
33
|
+
root
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
/**
|
|
37
|
+
* Attaches the repository at `target`: creates `~/.ork/projects/<id>/workflows`
|
|
38
|
+
* and records the project. Registering it again returns the same project.
|
|
39
|
+
*/
|
|
40
|
+
const registerProject = (store, target, options = {}) => {
|
|
41
|
+
const clone = cloneOf(target);
|
|
42
|
+
if (!clone.ok) return clone;
|
|
43
|
+
const { root } = clone;
|
|
44
|
+
const projects = store.projects();
|
|
45
|
+
const existing = projects.find((p) => p.root === root);
|
|
46
|
+
if (existing?.mode === "attached") return {
|
|
47
|
+
ok: true,
|
|
48
|
+
project: existing,
|
|
49
|
+
created: false
|
|
50
|
+
};
|
|
51
|
+
if (existing !== void 0 || hasInRepoFiles(root)) return {
|
|
52
|
+
ok: false,
|
|
53
|
+
error: `${root} keeps Ork's files in its own .ork/ (in-repo), so it is not attached`
|
|
54
|
+
};
|
|
55
|
+
const taken = projects.map((p) => p.id);
|
|
56
|
+
if (options.id !== void 0) {
|
|
57
|
+
if (!ID.test(options.id)) return {
|
|
58
|
+
ok: false,
|
|
59
|
+
error: `--id \`${options.id}\` must be lowercase letters, digits, ".", "_" or "-"`
|
|
60
|
+
};
|
|
61
|
+
if (taken.includes(options.id)) return {
|
|
62
|
+
ok: false,
|
|
63
|
+
error: `a project is already registered as ${options.id}`
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
const id = options.id ?? newProjectId(root, taken);
|
|
67
|
+
const project = {
|
|
68
|
+
id,
|
|
69
|
+
root,
|
|
70
|
+
mode: "attached",
|
|
71
|
+
dir: path.join(projectsHome(options.home), id)
|
|
72
|
+
};
|
|
73
|
+
mkdirSync(placeFiles(project).workflows, { recursive: true });
|
|
74
|
+
return {
|
|
75
|
+
ok: true,
|
|
76
|
+
project: store.addProject(project),
|
|
77
|
+
created: true
|
|
78
|
+
};
|
|
79
|
+
};
|
|
80
|
+
/**
|
|
81
|
+
* Records the in-repo project at `cwd`'s git root, if it is not registered yet;
|
|
82
|
+
* an attached one stays attached (`ork init` must not take it over).
|
|
83
|
+
*/
|
|
84
|
+
const recordInRepo = (store, cwd) => {
|
|
85
|
+
const root = projectRootOf(realpathSync(cwd));
|
|
86
|
+
const projects = store.projects();
|
|
87
|
+
const existing = projects.find((p) => p.root === root);
|
|
88
|
+
if (existing?.mode === "attached") return {
|
|
89
|
+
ok: false,
|
|
90
|
+
error: `${root} is attached: its workflows are in ${shownPath(existing.dir, root)}, and Ork writes nothing into the repository`
|
|
91
|
+
};
|
|
92
|
+
if (existing !== void 0) return {
|
|
93
|
+
ok: true,
|
|
94
|
+
project: existing,
|
|
95
|
+
created: false
|
|
96
|
+
};
|
|
97
|
+
const project = {
|
|
98
|
+
...inRepoPlace(root),
|
|
99
|
+
id: newProjectId(root, projects.map((p) => p.id))
|
|
100
|
+
};
|
|
101
|
+
return {
|
|
102
|
+
ok: true,
|
|
103
|
+
project: store.addProject(project),
|
|
104
|
+
created: true
|
|
105
|
+
};
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Points a registered project at its clone's new place, after the clone was
|
|
109
|
+
* moved or renamed; its runs follow, and an in-repo project's `.ork/` with it.
|
|
110
|
+
*/
|
|
111
|
+
const relocateProject = (store, id, target) => {
|
|
112
|
+
const projects = store.projects();
|
|
113
|
+
const project = projects.find((p) => p.id === id);
|
|
114
|
+
if (project === void 0) return {
|
|
115
|
+
ok: false,
|
|
116
|
+
error: `No project is registered as ${id}.`
|
|
117
|
+
};
|
|
118
|
+
const clone = cloneOf(target);
|
|
119
|
+
if (!clone.ok) return clone;
|
|
120
|
+
const { root } = clone;
|
|
121
|
+
const other = projects.find((p) => p.root === root && p.id !== id);
|
|
122
|
+
if (other !== void 0) return {
|
|
123
|
+
ok: false,
|
|
124
|
+
error: `${root} is already registered as ${other.id}`
|
|
125
|
+
};
|
|
126
|
+
const dir = project.mode === "in-repo" ? inRepoPlace(root).dir : project.dir;
|
|
127
|
+
store.moveProject(id, {
|
|
128
|
+
root,
|
|
129
|
+
mode: project.mode,
|
|
130
|
+
dir
|
|
131
|
+
});
|
|
132
|
+
return {
|
|
133
|
+
ok: true,
|
|
134
|
+
project: {
|
|
135
|
+
...project,
|
|
136
|
+
root,
|
|
137
|
+
dir
|
|
138
|
+
},
|
|
139
|
+
created: false
|
|
140
|
+
};
|
|
141
|
+
};
|
|
142
|
+
/**
|
|
143
|
+
* One project's lines for `ork projects`; rich, its mode and a missing clone
|
|
144
|
+
* are chips, its id is padded to `idWidth` and its root is cut to the screen.
|
|
145
|
+
*/
|
|
146
|
+
const formatProject = (project, term = termOf(process.stdout), idWidth = 0) => {
|
|
147
|
+
const missing = !existsSync(project.root);
|
|
148
|
+
if (!term.rich) {
|
|
149
|
+
const gone = missing ? style("yellow", " (missing)") : "";
|
|
150
|
+
return [`${style("bold", project.id)} ${style("dim", project.mode)} ${project.root}${gone}`, style("dim", ` files: ${shownPath(project.dir, project.root)}`)];
|
|
151
|
+
}
|
|
152
|
+
const mode = chip(term, project.mode.toUpperCase(), "muted");
|
|
153
|
+
const gone = missing ? ` ${chip(term, "MISSING", "warn")}` : "";
|
|
154
|
+
const room = term.width - (idWidth + 2 + project.mode.length + 2 + 2) - (missing ? 10 : 0);
|
|
155
|
+
return [`${style("bold", project.id.padEnd(idWidth))} ${mode} ${cut(project.root, Math.max(8, room))}${gone}`, style("dim", cut(` files: ${shownPath(project.dir, project.root)}`, term.width))];
|
|
156
|
+
};
|
|
157
|
+
/** Every project's lines, their ids aligned when rich. */
|
|
158
|
+
const formatProjects = (projects, term = termOf(process.stdout)) => {
|
|
159
|
+
const idWidth = term.rich ? Math.max(0, ...projects.map((p) => p.id.length)) : 0;
|
|
160
|
+
return projects.flatMap((p) => formatProject(p, term, idWidth));
|
|
161
|
+
};
|
|
162
|
+
/** What `ork register` prints: the project, and where its files go. */
|
|
163
|
+
const formatRegistered = (project, created) => {
|
|
164
|
+
const files = placeFiles(project);
|
|
165
|
+
const shown = (file) => shownPath(file, project.root);
|
|
166
|
+
return [
|
|
167
|
+
`${style("green", created ? "registered" : "already registered")} ${project.id} → ${project.root}`,
|
|
168
|
+
`\nWorkflows: ${shown(files.workflows)}/ · rules: ${shown(files.rules)} · runs: ${shown(files.runs)}/`,
|
|
169
|
+
`Nothing is written into ${project.root}. In it, ${style("bold", "ork run <name>")} finds the workflows above.`
|
|
170
|
+
];
|
|
171
|
+
};
|
|
172
|
+
// src/commands/project-records.ts
|
|
173
|
+
/**
|
|
174
|
+
* @file `ork projects`' subcommands on one project's record (ADR-035): `forget`
|
|
175
|
+
* drops it and keeps its files and runs; `move` points it at its clone's new
|
|
176
|
+
* place after the clone was moved or renamed, and its runs follow.
|
|
177
|
+
*/
|
|
178
|
+
/** Runs `use` with the store open, and closes it. */
|
|
179
|
+
const withStore = (use) => {
|
|
180
|
+
const store = openStore();
|
|
181
|
+
try {
|
|
182
|
+
return use(store);
|
|
183
|
+
} finally {
|
|
184
|
+
store.close();
|
|
185
|
+
}
|
|
186
|
+
};
|
|
187
|
+
const id = {
|
|
188
|
+
type: "positional",
|
|
189
|
+
description: "The project's id",
|
|
190
|
+
required: true
|
|
191
|
+
};
|
|
192
|
+
const forget = defineCommand({
|
|
193
|
+
meta: {
|
|
194
|
+
name: "forget",
|
|
195
|
+
description: "Forget a registered project; its files and runs are kept"
|
|
196
|
+
},
|
|
197
|
+
args: { id },
|
|
198
|
+
run: ({ args }) => {
|
|
199
|
+
if (withStore((store) => store.forgetProject(args.id))) {
|
|
200
|
+
write(`${style("green", "forgot")} ${args.id} (its files are kept)`);
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
writeErr(`No project is registered as ${args.id}.`);
|
|
204
|
+
process.exitCode = 1;
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
const move = defineCommand({
|
|
208
|
+
meta: {
|
|
209
|
+
name: "move",
|
|
210
|
+
description: "Point a registered project at its clone's new place, after moving or renaming it"
|
|
211
|
+
},
|
|
212
|
+
args: {
|
|
213
|
+
id,
|
|
214
|
+
path: {
|
|
215
|
+
type: "positional",
|
|
216
|
+
description: "Where the clone is now",
|
|
217
|
+
required: true
|
|
218
|
+
}
|
|
219
|
+
},
|
|
220
|
+
run: ({ args }) => {
|
|
221
|
+
const result = withStore((store) => relocateProject(store, args.id, args.path));
|
|
222
|
+
if (result.ok) {
|
|
223
|
+
write(`${style("green", "moved")} ${args.id} → ${result.project.root}`);
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
writeErr(result.error);
|
|
227
|
+
process.exitCode = 1;
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
export { formatRegistered as a, formatProjects as i, move as n, recordInRepo as o, withStore as r, registerProject as s, forget as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { L as openStore, Sn as addressOf, Tn as unlinkNodes, wn as linkNodes } from "./src-DLsFsmOD.mjs";
|
|
2
2
|
import { c as write, l as writeErr, s as style } from "./output-B1kwo0YU.mjs";
|
|
3
3
|
import { defineCommand } from "citty";
|
|
4
4
|
// src/commands/relay.ts
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { $t as git, en as gitOk } from "./src-DLsFsmOD.mjs";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { copyFileSync, existsSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, writeFileSync } from "node:fs";
|
|
4
4
|
import { createHash, randomUUID } from "node:crypto";
|