@krapht/ork 1.0.0-alpha.14 → 1.0.0-alpha.16
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__-DcRdK0Du.mjs +359 -0
- package/dist/apply-Mb0fNpCX.mjs +17 -0
- package/dist/{attach-CdtGX9se.mjs → attach-7lB5_jpr.mjs} +5 -4
- package/dist/{store-CleF7KeS.mjs → attach-B-FeE1dJ.mjs} +60 -25
- package/dist/bin.mjs +328 -1734
- package/dist/brief-BvMtIFio.mjs +58 -0
- package/dist/{builtin-files-Bdq23IIi.mjs → builtin-files-BRSOObAv.mjs} +1 -1
- package/dist/command-tree-BkSYWmYJ.mjs +63 -0
- package/dist/{completion--ywde-56.mjs → completion-Cgb2qPPh.mjs} +45 -6
- package/dist/context-BN9A2Nnu.mjs +106 -0
- package/dist/{find-BVX-bL4u.mjs → find-mLYsR0uC.mjs} +2 -2
- package/dist/flags-CdgIH1L6.mjs +230 -0
- package/dist/follow-ups-DxCsOby1.mjs +496 -0
- package/dist/{format-CvTE5CZQ.mjs → format-hiDvtNGM.mjs} +35 -8
- package/dist/{help-groups-CpR2UzSn.mjs → help-groups-hB6kY34d.mjs} +5 -63
- package/dist/{in-a-run-cL5N9gpH.mjs → in-a-run-a447hxq8.mjs} +1 -1
- package/dist/{__build_info__-B82-dGJY.mjs → install-23g_1ngs.mjs} +3 -227
- package/dist/{limits-Dhe--wmX.mjs → limits-C_Xz_t3C.mjs} +1 -1
- package/dist/{mcp-CsmboyXv.mjs → mcp-CWng9I9n.mjs} +6 -6
- package/dist/{once-CrwI-ZdN.mjs → once-lvwcWfVf.mjs} +3 -3
- package/dist/orphan-guard-BFKg8VSd.mjs +58 -0
- package/dist/{pause-DssrYcda.mjs → pause-Cy11KrXK.mjs} +3 -3
- package/dist/post-C9soCx7i.mjs +46 -0
- package/dist/print-BaWiWf4v.mjs +19 -0
- package/dist/project-records-BRiyQzUo.mjs +230 -0
- package/dist/{relay-DASRgsTQ.mjs → relay-C0M1TWMB.mjs} +1 -1
- package/dist/run-B-j3Qxqc.mjs +1464 -0
- package/dist/{run-D8divGCs.mjs → run-D_u9V_Ve.mjs} +1 -1
- package/dist/{send-CXv8bx0i.mjs → send-qBxj0JbJ.mjs} +2 -2
- package/dist/{src-njdtejgZ.mjs → src-CXBmaGwS.mjs} +213 -75
- package/dist/{src-C7z_6fhn.mjs → src-DHDQ0itC.mjs} +106 -50
- package/dist/{src-tsuZRu0H.mjs → src-DN-RPsil.mjs} +5423 -4467
- package/dist/{src-8Mul0695.mjs → src-nXLIwDrt.mjs} +325 -81
- package/dist/store-Dyr1mP8K.mjs +16 -0
- package/dist/ui/assets/app-BPSCxb_l.css +1 -0
- package/dist/ui/assets/app-CnNRowFn.js +111 -0
- package/dist/ui/assets/{index-D-_uPI2v.js → index-CVzwYlz3.js} +2 -2
- package/dist/ui/index.html +1 -1
- package/dist/{ui-CNO1M0qT.mjs → ui-DLrSt5ny.mjs} +3 -3
- package/dist/{usage-BVXi0iFY.mjs → usage-CxmpYYO4.mjs} +61 -107
- package/dist/{context-Y_I4Mofq.mjs → validate-BVrT43GO.mjs} +35 -108
- package/dist/{watch-DVNIciHp.mjs → watch-mkLm4t9D.mjs} +19 -5
- package/dist/{workflows-B30QkwTk.mjs → workflows-Bvl2Fquu.mjs} +2 -2
- package/dist/{worktrees-DD4_TpkH.mjs → worktrees-DOOCoS2w.mjs} +49 -20
- package/package.json +8 -4
- package/dist/ui/assets/app-D_nAPnHM.css +0 -1
- package/dist/ui/assets/app-DqBAtXSZ.js +0 -111
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Gr as writePlainFile, Hr as estimateTokens, Nr as placeFiles, Pr as placeFor, Ur as guardedBase, Vr as LESSON_CAP_TOKENS, Wr as plainPathInside, Xn as FEEDBACK_KINDS, Zn as FEEDBACK_SEVERITIES, cr as targetOf, or as fingerprint, rr as publishPost, vr as maskSecrets, zr as inside } from "./src-DN-RPsil.mjs";
|
|
2
2
|
import path from "node:path";
|
|
3
|
-
import { existsSync,
|
|
4
|
-
import { homedir } from "node:os";
|
|
3
|
+
import { existsSync, readFileSync, realpathSync } from "node:fs";
|
|
5
4
|
import { spawn } from "node:child_process";
|
|
6
5
|
// ../core/src/feedback/actions.ts
|
|
7
6
|
const ALLOWED = { ok: true };
|
|
@@ -363,174 +362,6 @@ const previewPost = (store, item, ask) => {
|
|
|
363
362
|
})
|
|
364
363
|
};
|
|
365
364
|
};
|
|
366
|
-
// ../core/src/remote/session.ts
|
|
367
|
-
const chattable = (type) => type === "agent.leader" || type === "agent.worker";
|
|
368
|
-
/** Reads `node`'s session from a run's events, and every node's last output. */
|
|
369
|
-
const nodeSession = (events, node) => {
|
|
370
|
-
const session = {
|
|
371
|
-
known: false,
|
|
372
|
-
chattable: false,
|
|
373
|
-
running: false
|
|
374
|
-
};
|
|
375
|
-
const outputs = {};
|
|
376
|
-
for (const { event } of events) {
|
|
377
|
-
if (event.type === "run.started") {
|
|
378
|
-
const declared = event.nodes.find((n) => n.id === node);
|
|
379
|
-
if (declared !== void 0) {
|
|
380
|
-
session.known = true;
|
|
381
|
-
session.chattable = chattable(declared.type);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
if (event.type === "node.finished") outputs[event.id] = event.output;
|
|
385
|
-
if (!("id" in event) || event.id !== node) continue;
|
|
386
|
-
if (event.type === "node.started") {
|
|
387
|
-
session.known = true;
|
|
388
|
-
session.chattable = chattable(event.nodeType);
|
|
389
|
-
session.running = true;
|
|
390
|
-
}
|
|
391
|
-
if (event.type === "node.finished") session.running = false;
|
|
392
|
-
if (event.type === "worktree" && event.action === "created") session.checkout = {
|
|
393
|
-
path: event.path,
|
|
394
|
-
...event.branch === void 0 ? {} : { branch: event.branch },
|
|
395
|
-
own: true,
|
|
396
|
-
owner: node
|
|
397
|
-
};
|
|
398
|
-
if (event.type !== "node.event") continue;
|
|
399
|
-
const inner = event.event;
|
|
400
|
-
if (inner.fork === true) continue;
|
|
401
|
-
if (inner.type === "session.started") {
|
|
402
|
-
session.sessionId = inner.sessionId;
|
|
403
|
-
if (inner.cwd !== void 0) session.cwd = inner.cwd;
|
|
404
|
-
if (inner.driver !== void 0) session.driver = inner.driver;
|
|
405
|
-
if (inner.model !== "") session.model = inner.model;
|
|
406
|
-
}
|
|
407
|
-
if (inner.type === "session.finished" && inner.outcome.sessionId !== "") session.sessionId = inner.outcome.sessionId;
|
|
408
|
-
}
|
|
409
|
-
return {
|
|
410
|
-
session,
|
|
411
|
-
outputs
|
|
412
|
-
};
|
|
413
|
-
};
|
|
414
|
-
/** The dist-tags every install follows, toward and after 1.0.0. */
|
|
415
|
-
const CHANNEL_TAGS = [
|
|
416
|
-
"latest",
|
|
417
|
-
"alpha",
|
|
418
|
-
"beta",
|
|
419
|
-
"rc"
|
|
420
|
-
];
|
|
421
|
-
/** The dist-tag of snapshots from `main`, followed only by a canary. */
|
|
422
|
-
const CANARY_TAG = "canary";
|
|
423
|
-
const updateCachePath = (home = homedir()) => path.join(home, ".ork", "update-check.json");
|
|
424
|
-
const isRecord = (value) => typeof value === "object" && value !== null && !Array.isArray(value);
|
|
425
|
-
const text = (record, key) => {
|
|
426
|
-
const value = record[key];
|
|
427
|
-
return typeof value === "string" ? value : void 0;
|
|
428
|
-
};
|
|
429
|
-
const notifiedOf = (value) => {
|
|
430
|
-
if (!isRecord(value)) return void 0;
|
|
431
|
-
const version = text(value, "version");
|
|
432
|
-
const at = text(value, "at");
|
|
433
|
-
return version === void 0 || at === void 0 ? void 0 : {
|
|
434
|
-
version,
|
|
435
|
-
at
|
|
436
|
-
};
|
|
437
|
-
};
|
|
438
|
-
/** The cache, or `null` when there is none or it cannot be read. */
|
|
439
|
-
const readUpdateCache = (file) => {
|
|
440
|
-
let parsed;
|
|
441
|
-
try {
|
|
442
|
-
parsed = JSON.parse(readFileSync(file, "utf8"));
|
|
443
|
-
} catch {
|
|
444
|
-
return null;
|
|
445
|
-
}
|
|
446
|
-
if (!isRecord(parsed)) return null;
|
|
447
|
-
const checkedAt = text(parsed, "checkedAt");
|
|
448
|
-
if (checkedAt === void 0) return null;
|
|
449
|
-
const latest = text(parsed, "latest");
|
|
450
|
-
const notified = notifiedOf(parsed["notified"]);
|
|
451
|
-
return {
|
|
452
|
-
checkedAt,
|
|
453
|
-
...latest === void 0 ? {} : { latest },
|
|
454
|
-
...notified === void 0 ? {} : { notified }
|
|
455
|
-
};
|
|
456
|
-
};
|
|
457
|
-
/** Writes the cache whole (a temporary file, then a rename). */
|
|
458
|
-
const writeUpdateCache = (file, cache) => {
|
|
459
|
-
mkdirSync(path.dirname(file), { recursive: true });
|
|
460
|
-
const temporary = `${file}.${process.pid}.tmp`;
|
|
461
|
-
writeFileSync(temporary, `${JSON.stringify(cache, null, 2)}\n`);
|
|
462
|
-
renameSync(temporary, file);
|
|
463
|
-
};
|
|
464
|
-
const age = (iso, now) => {
|
|
465
|
-
const time = Date.parse(iso);
|
|
466
|
-
return Number.isNaN(time) ? Infinity : now.getTime() - time;
|
|
467
|
-
};
|
|
468
|
-
/** Whether it is time to ask the registry again. */
|
|
469
|
-
const checkDue = (cache, now) => cache === null || age(cache.checkedAt, now) >= 864e5;
|
|
470
|
-
/** Whether `version` is a canary snapshot: its pre-release starts `canary`. */
|
|
471
|
-
const isCanary = (version) => parseVersion(version)?.pre[0]?.startsWith(CANARY_TAG) === true;
|
|
472
|
-
/**
|
|
473
|
-
* The newest version, by semver precedence, among the dist-tags `current`
|
|
474
|
-
* follows: the channel tags, and `canary` too when `current` is a canary. A tag
|
|
475
|
-
* whose value doesn't parse is skipped; `null` when none does.
|
|
476
|
-
*/
|
|
477
|
-
const newestTagged = (tags, current) => {
|
|
478
|
-
return (current !== void 0 && isCanary(current) ? [...CHANNEL_TAGS, "canary"] : CHANNEL_TAGS).flatMap((tag) => {
|
|
479
|
-
const value = tags[tag];
|
|
480
|
-
const version = typeof value === "string" ? parseVersion(value) : null;
|
|
481
|
-
return typeof value === "string" && version !== null ? [{
|
|
482
|
-
value,
|
|
483
|
-
version
|
|
484
|
-
}] : [];
|
|
485
|
-
}).reduce((best, candidate) => best === null || compareVersions(candidate.version, best.version) > 0 ? candidate : best, null)?.value ?? null;
|
|
486
|
-
};
|
|
487
|
-
/**
|
|
488
|
-
* The newest version on the registry that the running version follows ({@link
|
|
489
|
-
* newestTagged}), or `null` on any failure.
|
|
490
|
-
*/
|
|
491
|
-
const fetchLatest = async (name, options = {}) => {
|
|
492
|
-
const registry = (options.registry ?? "https://registry.npmjs.org").replace(/\/+$/u, "");
|
|
493
|
-
const get = options.fetch ?? globalThis.fetch;
|
|
494
|
-
try {
|
|
495
|
-
const response = await get(`${registry}/-/package/${name}/dist-tags`, {
|
|
496
|
-
headers: { accept: "application/json" },
|
|
497
|
-
signal: AbortSignal.timeout(options.timeoutMs ?? 5e3)
|
|
498
|
-
});
|
|
499
|
-
if (!response.ok) return null;
|
|
500
|
-
const tags = await response.json();
|
|
501
|
-
return isRecord(tags) ? newestTagged(tags, options.current) : null;
|
|
502
|
-
} catch {
|
|
503
|
-
return null;
|
|
504
|
-
}
|
|
505
|
-
};
|
|
506
|
-
/**
|
|
507
|
-
* Asks the registry and records the answer. A failed check is recorded too, so
|
|
508
|
-
* an offline machine is not asked again until the interval passes; the last
|
|
509
|
-
* known version is kept. `options.current` is the running version.
|
|
510
|
-
*/
|
|
511
|
-
const refreshUpdateCache = async (file, name, now, options = {}) => {
|
|
512
|
-
const before = readUpdateCache(file);
|
|
513
|
-
const latest = await fetchLatest(name, options) ?? before?.latest;
|
|
514
|
-
const cache = {
|
|
515
|
-
...before,
|
|
516
|
-
checkedAt: now.toISOString(),
|
|
517
|
-
...latest === void 0 ? {} : { latest }
|
|
518
|
-
};
|
|
519
|
-
writeUpdateCache(file, cache);
|
|
520
|
-
return cache;
|
|
521
|
-
};
|
|
522
|
-
/** The newer version the cache knows of, if any. */
|
|
523
|
-
const newerVersion = (cache, current) => cache?.latest !== void 0 && isNewer(cache.latest, current) ? cache.latest : null;
|
|
524
|
-
/**
|
|
525
|
-
* The version to tell the user about now: newer than theirs, and not already
|
|
526
|
-
* told today. `null` when there is nothing to say.
|
|
527
|
-
*/
|
|
528
|
-
const pendingNotice = (cache, current, now) => {
|
|
529
|
-
const newer = newerVersion(cache, current);
|
|
530
|
-
if (newer === null) return null;
|
|
531
|
-
const told = cache?.notified;
|
|
532
|
-
return told?.version === newer && age(told.at, now) < 864e5 ? null : newer;
|
|
533
|
-
};
|
|
534
365
|
/**
|
|
535
366
|
* Runs the command; resolves its exit code. Without `onOutput` it writes on the
|
|
536
367
|
* terminal.
|
|
@@ -558,59 +389,4 @@ const runInstall = (argv, onOutput) => new Promise((resolve) => {
|
|
|
558
389
|
resolve(code ?? 1);
|
|
559
390
|
});
|
|
560
391
|
});
|
|
561
|
-
|
|
562
|
-
/**
|
|
563
|
-
* @file The environment's say over the update check (ADR-038): the variables
|
|
564
|
-
* that turn it off (`ORK_NO_UPDATE_CHECK`, the common `NO_UPDATE_NOTIFIER`,
|
|
565
|
-
* and `CI`), and the registry to ask instead of npm's. The CLI adds its own
|
|
566
|
-
* rules about where a notice may go; the server behind `ork ui` uses only
|
|
567
|
-
* these.
|
|
568
|
-
*/
|
|
569
|
-
/** Turns the automatic check and its notice off. */
|
|
570
|
-
const NO_UPDATE_ENV = "ORK_NO_UPDATE_CHECK";
|
|
571
|
-
/** A registry to ask instead of npm's, e.g. a company mirror. */
|
|
572
|
-
const UPDATE_REGISTRY_ENV = "ORK_UPDATE_REGISTRY";
|
|
573
|
-
/** The variables that turn the check off, in the order they are looked at. */
|
|
574
|
-
const UPDATE_SWITCHES = [
|
|
575
|
-
NO_UPDATE_ENV,
|
|
576
|
-
"NO_UPDATE_NOTIFIER",
|
|
577
|
-
"CI"
|
|
578
|
-
];
|
|
579
|
-
/** Set to anything but empty, `0` or `false`. */
|
|
580
|
-
const isSwitchedOn = (value) => value !== void 0 && value !== "" && value !== "0" && value.toLowerCase() !== "false";
|
|
581
|
-
/** The variable that turns the check off in `env`, or `null` when none does. */
|
|
582
|
-
const updateSwitchOff = (env) => UPDATE_SWITCHES.find((name) => isSwitchedOn(env[name])) ?? null;
|
|
583
|
-
/** The registry `ORK_UPDATE_REGISTRY` names, if it names one. */
|
|
584
|
-
const registryOf = (env) => {
|
|
585
|
-
const registry = env[UPDATE_REGISTRY_ENV];
|
|
586
|
-
return registry === void 0 || registry === "" ? void 0 : registry;
|
|
587
|
-
};
|
|
588
|
-
/** The host of a registry that isn't an http(s) URL, past any `user:pass@`. */
|
|
589
|
-
const hostOf = (registry) => registry.replace(/^[a-z][\w+.-]*:\/\//iu, "").replace(/^[^/?#]*@/u, "").split(/[/?#]/u)[0] ?? "";
|
|
590
|
-
/**
|
|
591
|
-
* The registry as it may be shown (on a page, in a message): an http(s) URL
|
|
592
|
-
* without its user name and password, which a mirror's URL may carry; anything
|
|
593
|
-
* else only as its host.
|
|
594
|
-
*/
|
|
595
|
-
const registryShown = (registry) => {
|
|
596
|
-
try {
|
|
597
|
-
const url = new URL(registry);
|
|
598
|
-
if (url.protocol === "http:" || url.protocol === "https:") {
|
|
599
|
-
if (url.username === "" && url.password === "") return registry;
|
|
600
|
-
url.username = "";
|
|
601
|
-
url.password = "";
|
|
602
|
-
return url.href;
|
|
603
|
-
}
|
|
604
|
-
} catch {}
|
|
605
|
-
return hostOf(registry);
|
|
606
|
-
};
|
|
607
|
-
// src/__build_info__.ts
|
|
608
|
-
/**
|
|
609
|
-
* @file The published package's name and version, bound to
|
|
610
|
-
* `packages/cli/package.json`. Do not edit by hand: `pnpm changesets:version`
|
|
611
|
-
* and `pnpm build` rewrite it (`tools/release/build-info.ts`), and a test
|
|
612
|
-
* fails when it is stale.
|
|
613
|
-
*/
|
|
614
|
-
const ORK_NAME = "@krapht/ork";
|
|
615
|
-
const ORK_VERSION = "1.0.0-alpha.14";
|
|
616
|
-
export { feedbackActions as C, placeOf as S, previewPost as _, registryShown as a, acceptItem as b, checkDue as c, pendingNotice as d, readUpdateCache as f, nodeSession as g, writeUpdateCache as h, registryOf as i, fetchLatest as l, updateCachePath as m, ORK_VERSION as n, updateSwitchOff as o, refreshUpdateCache as p, isSwitchedOn as r, runInstall as s, ORK_NAME as t, newerVersion as u, checkNote as v, openFixRun as w, lessonPlaceOf as x, recordNote as y };
|
|
392
|
+
export { acceptItem as a, feedbackActions as c, recordNote as i, openFixRun as l, previewPost as n, lessonPlaceOf as o, checkNote as r, placeOf as s, runInstall as t };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { _ as capsHere, dt as NOT_STARTED, ft as limitsNow, pt as limitsReport } from "./src-DN-RPsil.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 { p as runMarkerIn } from "./src-
|
|
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-DN-RPsil.mjs";
|
|
2
|
+
import { n as ORK_VERSION } from "./__build_info__-DcRdK0Du.mjs";
|
|
3
|
+
import { p as runMarkerIn } from "./src-DHDQ0itC.mjs";
|
|
4
|
+
import { n as explainNode } from "./context-BN9A2Nnu.mjs";
|
|
5
|
+
import { a as openMcp, c as refused, o as tool, s as answer } from "./src-nXLIwDrt.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.16") ? 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-DN-RPsil.mjs";
|
|
2
|
+
import { t as runOnce } from "./run-D_u9V_Ve.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-hiDvtNGM.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,58 @@
|
|
|
1
|
+
import { Readable, Writable } from "node:stream";
|
|
2
|
+
// ../core/src/machine/orphan-guard.ts
|
|
3
|
+
/**
|
|
4
|
+
* @file The orphan guard: a node's processes end with `ork`, whatever ends
|
|
5
|
+
* `ork` (SIGKILL, the OOM killer, a crash exit). A child started in its own
|
|
6
|
+
* process group (`detached: true`) under `sh` also starts a small watcher in
|
|
7
|
+
* that group, which reads a pipe that only `ork` holds open. `ork` never
|
|
8
|
+
* writes to it, so when `ork` dies the read sees EOF, and the watcher sends
|
|
9
|
+
* its own group (`kill 0`, never another's) SIGTERM, then SIGKILL after
|
|
10
|
+
* {@link KILL_AFTER_MS}. A line on the pipe instead lets the watcher exit
|
|
11
|
+
* quietly, and so does killing it, which is how a node that ends normally
|
|
12
|
+
* leaves its leftover background processes alone. POSIX only. Two uses:
|
|
13
|
+
*
|
|
14
|
+
* - A shell that outlives the program (the tool runner's timing wrapper):
|
|
15
|
+
* `${orphanGuard(4)} w=$!; "$0" "$@" 4<&-; s=$?; kill -s KILL $w`, then
|
|
16
|
+
* spawn with a `"pipe"` at `stdio[4]` and call
|
|
17
|
+
* `holdGuard(child.stdio[4])`.
|
|
18
|
+
* - A shell that `exec`s the program (a session, so its pid stays the
|
|
19
|
+
* driver's): `${orphanGuard(3)} exec 3<&-; exec "$0" "$@"` (a bare `exec`
|
|
20
|
+
* closes the pipe: macOS's `sh` keeps a copy of one an `exec` command
|
|
21
|
+
* closes, which the driver would inherit), with a `"pipe"` at `stdio[3]`;
|
|
22
|
+
* nothing is left to kill the watcher, so call the function `holdGuard`
|
|
23
|
+
* returns once the driver has exited (on `exit`: the pipe holds `close`).
|
|
24
|
+
*/
|
|
25
|
+
/** How long the group gets to end after SIGTERM before SIGKILL. */
|
|
26
|
+
const KILL_AFTER_MS = 2e3;
|
|
27
|
+
/** The file descriptors a redirect can name in every POSIX shell. */
|
|
28
|
+
const SHELL_FDS = [
|
|
29
|
+
3,
|
|
30
|
+
4,
|
|
31
|
+
5,
|
|
32
|
+
6,
|
|
33
|
+
7,
|
|
34
|
+
8,
|
|
35
|
+
9
|
|
36
|
+
];
|
|
37
|
+
/**
|
|
38
|
+
* Shell text that starts the watcher in the background on pipe `fd` (3 to 9);
|
|
39
|
+
* it ends with `&`, so `$!` after it is the watcher's pid. The watcher holds no
|
|
40
|
+
* other descriptor, so it never keeps the child's output pipes open.
|
|
41
|
+
*/
|
|
42
|
+
const orphanGuard = (fd) => {
|
|
43
|
+
const others = SHELL_FDS.filter((other) => other !== fd).map((other) => `${String(other)}>&-`).join(" ");
|
|
44
|
+
return `{ read -r _ork <&${String(fd)} || { trap '' TERM; kill -s TERM 0; sleep ${String(KILL_AFTER_MS / 1e3)}; kill -s KILL 0; }; } </dev/null >/dev/null 2>&1 ${others} & `;
|
|
45
|
+
};
|
|
46
|
+
/**
|
|
47
|
+
* Keeps `ork`'s end of the watch pipe (`child.stdio[fd]`) open and drained, so
|
|
48
|
+
* it never holds back the child's `close`; returns the release, which writes
|
|
49
|
+
* the line that lets the watcher exit without touching its group.
|
|
50
|
+
*/
|
|
51
|
+
const holdGuard = (pipe) => {
|
|
52
|
+
pipe?.on("error", () => {});
|
|
53
|
+
if (pipe instanceof Readable) pipe.resume();
|
|
54
|
+
return () => {
|
|
55
|
+
if (pipe instanceof Writable && pipe.writable) pipe.write("\n");
|
|
56
|
+
};
|
|
57
|
+
};
|
|
58
|
+
export { holdGuard as n, orphanGuard as r, KILL_AFTER_MS as t };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { t as findRun } from "./find-
|
|
1
|
+
import { En as requestPause, L as openStore, Tn as requestContinue } from "./src-DN-RPsil.mjs";
|
|
2
|
+
import { t as findRun } from "./find-mLYsR0uC.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-a447hxq8.mjs";
|
|
5
5
|
import { defineCommand } from "citty";
|
|
6
6
|
// src/commands/pause.ts
|
|
7
7
|
/**
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { ir as redact } from "./src-DN-RPsil.mjs";
|
|
2
|
+
import { b as githubDraftRefusal } from "./__build_info__-DcRdK0Du.mjs";
|
|
3
|
+
// ../core/src/follow-ups/post.ts
|
|
4
|
+
/** Posts `f`'s draft as a GitHub comment once `confirmed`, and marks it done. */
|
|
5
|
+
const postFollowUpMessage = async (f, options) => {
|
|
6
|
+
const draft = f.draft;
|
|
7
|
+
const refused = githubDraftRefusal(f);
|
|
8
|
+
if (refused !== void 0 || draft === void 0) return {
|
|
9
|
+
ok: false,
|
|
10
|
+
error: refused?.error ?? "it has no draft"
|
|
11
|
+
};
|
|
12
|
+
if (!options.confirmed) return {
|
|
13
|
+
ok: false,
|
|
14
|
+
error: "posting to GitHub needs the maintainer's confirmation"
|
|
15
|
+
};
|
|
16
|
+
const body = redact(draft.text, {
|
|
17
|
+
roots: [f.project],
|
|
18
|
+
home: options.home
|
|
19
|
+
});
|
|
20
|
+
const on = /\/pull\/\d+/u.test(draft.to) ? "pr" : "issue";
|
|
21
|
+
const result = await options.gh([
|
|
22
|
+
on,
|
|
23
|
+
"comment",
|
|
24
|
+
draft.to,
|
|
25
|
+
"--body",
|
|
26
|
+
body
|
|
27
|
+
], { cwd: f.project });
|
|
28
|
+
if (!result.ok) return {
|
|
29
|
+
ok: false,
|
|
30
|
+
error: `gh failed: ${result.stderr.trim() || result.stdout.trim() || "no output"}`
|
|
31
|
+
};
|
|
32
|
+
const url = result.stdout.trim().split("\n").findLast((line) => line.startsWith("https://"));
|
|
33
|
+
const done = {
|
|
34
|
+
...f,
|
|
35
|
+
status: "done",
|
|
36
|
+
note: `posted${url === void 0 ? "" : ` as ${url}`}`,
|
|
37
|
+
updatedAt: options.now?.() ?? (/* @__PURE__ */ new Date()).toISOString()
|
|
38
|
+
};
|
|
39
|
+
options.rows.putFollowUp(done);
|
|
40
|
+
return {
|
|
41
|
+
ok: true,
|
|
42
|
+
followUp: done,
|
|
43
|
+
...url === void 0 ? {} : { url }
|
|
44
|
+
};
|
|
45
|
+
};
|
|
46
|
+
export { postFollowUpMessage as t };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { a as refuseIdsWithJson } from "./flags-CdgIH1L6.mjs";
|
|
2
|
+
import { c as write } from "./output-B1kwo0YU.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 { Ar as hasInRepoFiles, Br as projectRootOf, Fr as projectsHome, L as openStore, Lr as shownPath, Mr as newProjectId, Nr as placeFiles, jr as inRepoPlace } from "./src-DN-RPsil.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 { Cn as linkNodes, L as openStore, wn as unlinkNodes, xn as addressOf } from "./src-DN-RPsil.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
|