yapout 0.7.0 → 0.9.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/index.js +710 -352
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -7,16 +7,16 @@ import {
|
|
|
7
7
|
} from "./chunk-DLHFRTYU.js";
|
|
8
8
|
|
|
9
9
|
// src/index.ts
|
|
10
|
-
import { Command as
|
|
10
|
+
import { Command as Command17 } from "commander";
|
|
11
11
|
|
|
12
12
|
// src/commands/login.ts
|
|
13
|
-
import { Command } from "commander";
|
|
14
|
-
import
|
|
15
|
-
import { createHash } from "crypto";
|
|
16
|
-
import { hostname, userInfo } from "os";
|
|
17
|
-
import
|
|
13
|
+
import { Command as Command2 } from "commander";
|
|
14
|
+
import http2 from "http";
|
|
15
|
+
import { createHash as createHash2 } from "crypto";
|
|
16
|
+
import { hostname as hostname2, userInfo as userInfo2 } from "os";
|
|
17
|
+
import chalk3 from "chalk";
|
|
18
18
|
import open from "open";
|
|
19
|
-
import { anyApi as
|
|
19
|
+
import { anyApi as anyApi3 } from "convex/server";
|
|
20
20
|
|
|
21
21
|
// src/lib/config.ts
|
|
22
22
|
import { homedir } from "os";
|
|
@@ -521,8 +521,267 @@ function registerProtocolHandler() {
|
|
|
521
521
|
}
|
|
522
522
|
}
|
|
523
523
|
|
|
524
|
+
// src/commands/serve.ts
|
|
525
|
+
import { Command } from "commander";
|
|
526
|
+
import http from "http";
|
|
527
|
+
import { createHash } from "crypto";
|
|
528
|
+
import { hostname, userInfo, platform as platform2, type } from "os";
|
|
529
|
+
import { join as join3 } from "path";
|
|
530
|
+
import {
|
|
531
|
+
readFileSync as readFileSync2,
|
|
532
|
+
writeFileSync as writeFileSync3,
|
|
533
|
+
existsSync as existsSync2,
|
|
534
|
+
unlinkSync as unlinkSync3,
|
|
535
|
+
openSync
|
|
536
|
+
} from "fs";
|
|
537
|
+
import { spawn } from "child_process";
|
|
538
|
+
import chalk2 from "chalk";
|
|
539
|
+
import { anyApi as anyApi2 } from "convex/server";
|
|
540
|
+
|
|
541
|
+
// src/lib/auth.ts
|
|
542
|
+
import chalk from "chalk";
|
|
543
|
+
function requireAuth() {
|
|
544
|
+
const creds = readCredentials();
|
|
545
|
+
if (!creds) {
|
|
546
|
+
console.error(
|
|
547
|
+
chalk.red("Not logged in.") + " Run " + chalk.cyan("yapout login") + " first."
|
|
548
|
+
);
|
|
549
|
+
process.exit(1);
|
|
550
|
+
}
|
|
551
|
+
if (Date.now() > creds.expiresAt) {
|
|
552
|
+
console.error(
|
|
553
|
+
chalk.red("Session expired.") + " Run " + chalk.cyan("yapout login") + " to re-authenticate."
|
|
554
|
+
);
|
|
555
|
+
process.exit(1);
|
|
556
|
+
}
|
|
557
|
+
return creds;
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
// src/commands/serve.ts
|
|
561
|
+
var CLI_VERSION = "0.9.0";
|
|
562
|
+
var DEFAULT_PORT = 7777;
|
|
563
|
+
var PORT_RANGE = 10;
|
|
564
|
+
var HEARTBEAT_MS = 3e4;
|
|
565
|
+
var PID_FILE = join3(getYapoutDir(), "serve.pid");
|
|
566
|
+
var PORT_FILE = join3(getYapoutDir(), "serve.port");
|
|
567
|
+
var LOG_FILE = join3(getYapoutDir(), "serve.log");
|
|
568
|
+
var ALLOWED_ORIGINS = [
|
|
569
|
+
"https://yapout.com",
|
|
570
|
+
"https://www.yapout.com",
|
|
571
|
+
"https://yapout.vercel.app",
|
|
572
|
+
// Local dev / sandbox ports — wide range so any worktree's auto-allocated
|
|
573
|
+
// Next.js port works without configuration.
|
|
574
|
+
...Array.from({ length: 20 }, (_, i) => `http://localhost:${3e3 + i}`),
|
|
575
|
+
...Array.from({ length: 20 }, (_, i) => `http://127.0.0.1:${3e3 + i}`)
|
|
576
|
+
];
|
|
577
|
+
function computeDeviceId() {
|
|
578
|
+
return createHash("sha256").update(hostname() + userInfo().username).digest("hex").slice(0, 16);
|
|
579
|
+
}
|
|
580
|
+
function corsHeadersFor(origin) {
|
|
581
|
+
const allowOrigin = origin && ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
|
|
582
|
+
return {
|
|
583
|
+
"Access-Control-Allow-Origin": allowOrigin,
|
|
584
|
+
"Access-Control-Allow-Methods": "GET, OPTIONS",
|
|
585
|
+
"Access-Control-Allow-Headers": "Content-Type",
|
|
586
|
+
"Access-Control-Allow-Private-Network": "true",
|
|
587
|
+
Vary: "Origin"
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
function startServer(payload) {
|
|
591
|
+
const server = http.createServer((req, res) => {
|
|
592
|
+
const origin = req.headers.origin;
|
|
593
|
+
const headers = corsHeadersFor(origin);
|
|
594
|
+
if (req.method === "OPTIONS") {
|
|
595
|
+
res.writeHead(204, headers);
|
|
596
|
+
res.end();
|
|
597
|
+
return;
|
|
598
|
+
}
|
|
599
|
+
if (req.method === "GET" && req.url === "/device") {
|
|
600
|
+
res.writeHead(200, {
|
|
601
|
+
...headers,
|
|
602
|
+
"Content-Type": "application/json",
|
|
603
|
+
"Cache-Control": "no-store"
|
|
604
|
+
});
|
|
605
|
+
res.end(JSON.stringify(payload));
|
|
606
|
+
return;
|
|
607
|
+
}
|
|
608
|
+
res.writeHead(404, headers);
|
|
609
|
+
res.end();
|
|
610
|
+
});
|
|
611
|
+
return new Promise((resolve12, reject) => {
|
|
612
|
+
let attempt = 0;
|
|
613
|
+
const tryListen = () => {
|
|
614
|
+
const port = DEFAULT_PORT + attempt;
|
|
615
|
+
const onError = (err) => {
|
|
616
|
+
server.removeListener("error", onError);
|
|
617
|
+
if (err.code === "EADDRINUSE" && attempt < PORT_RANGE - 1) {
|
|
618
|
+
attempt++;
|
|
619
|
+
tryListen();
|
|
620
|
+
} else {
|
|
621
|
+
reject(err);
|
|
622
|
+
}
|
|
623
|
+
};
|
|
624
|
+
server.once("error", onError);
|
|
625
|
+
server.listen(port, "127.0.0.1", () => {
|
|
626
|
+
server.removeListener("error", onError);
|
|
627
|
+
resolve12({
|
|
628
|
+
port,
|
|
629
|
+
close: () => server.close()
|
|
630
|
+
});
|
|
631
|
+
});
|
|
632
|
+
};
|
|
633
|
+
tryListen();
|
|
634
|
+
});
|
|
635
|
+
}
|
|
636
|
+
async function runForeground() {
|
|
637
|
+
const creds = requireAuth();
|
|
638
|
+
const deviceId = computeDeviceId();
|
|
639
|
+
const payload = {
|
|
640
|
+
deviceId,
|
|
641
|
+
name: hostname(),
|
|
642
|
+
hostname: hostname(),
|
|
643
|
+
platform: `${type()} ${platform2()}`,
|
|
644
|
+
cliVersion: CLI_VERSION
|
|
645
|
+
};
|
|
646
|
+
const { port } = await startServer(payload);
|
|
647
|
+
writeFileSync3(PORT_FILE, String(port));
|
|
648
|
+
writeFileSync3(PID_FILE, String(process.pid));
|
|
649
|
+
console.log(chalk2.green(`yapout serve listening on http://127.0.0.1:${port}`));
|
|
650
|
+
console.log(chalk2.dim(`Device: ${payload.name} (${deviceId})`));
|
|
651
|
+
console.log(chalk2.dim("Press Ctrl-C to stop."));
|
|
652
|
+
const client = createConvexClient(creds.token);
|
|
653
|
+
const heartbeat = async () => {
|
|
654
|
+
try {
|
|
655
|
+
await client.mutation(anyApi2.functions.devices.heartbeatDevice, {
|
|
656
|
+
deviceId
|
|
657
|
+
});
|
|
658
|
+
} catch {
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
client.mutation(anyApi2.functions.devices.registerDevice, {
|
|
662
|
+
deviceId,
|
|
663
|
+
name: payload.name,
|
|
664
|
+
cliVersion: CLI_VERSION,
|
|
665
|
+
machineHostname: payload.hostname
|
|
666
|
+
}).catch(() => {
|
|
667
|
+
});
|
|
668
|
+
void heartbeat();
|
|
669
|
+
const interval = setInterval(heartbeat, HEARTBEAT_MS);
|
|
670
|
+
const shutdown = async () => {
|
|
671
|
+
clearInterval(interval);
|
|
672
|
+
try {
|
|
673
|
+
await client.mutation(anyApi2.functions.devices.markDeviceOffline, {
|
|
674
|
+
deviceId
|
|
675
|
+
});
|
|
676
|
+
} catch {
|
|
677
|
+
}
|
|
678
|
+
try {
|
|
679
|
+
if (existsSync2(PID_FILE)) unlinkSync3(PID_FILE);
|
|
680
|
+
} catch {
|
|
681
|
+
}
|
|
682
|
+
try {
|
|
683
|
+
if (existsSync2(PORT_FILE)) unlinkSync3(PORT_FILE);
|
|
684
|
+
} catch {
|
|
685
|
+
}
|
|
686
|
+
process.exit(0);
|
|
687
|
+
};
|
|
688
|
+
process.on("SIGINT", () => {
|
|
689
|
+
void shutdown();
|
|
690
|
+
});
|
|
691
|
+
process.on("SIGTERM", () => {
|
|
692
|
+
void shutdown();
|
|
693
|
+
});
|
|
694
|
+
await new Promise(() => {
|
|
695
|
+
});
|
|
696
|
+
}
|
|
697
|
+
function isProcessRunning(pid) {
|
|
698
|
+
try {
|
|
699
|
+
process.kill(pid, 0);
|
|
700
|
+
return true;
|
|
701
|
+
} catch {
|
|
702
|
+
return false;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
function readPid() {
|
|
706
|
+
if (!existsSync2(PID_FILE)) return null;
|
|
707
|
+
const raw = readFileSync2(PID_FILE, "utf-8").trim();
|
|
708
|
+
const pid = parseInt(raw, 10);
|
|
709
|
+
return Number.isFinite(pid) ? pid : null;
|
|
710
|
+
}
|
|
711
|
+
function readPort() {
|
|
712
|
+
if (!existsSync2(PORT_FILE)) return null;
|
|
713
|
+
const raw = readFileSync2(PORT_FILE, "utf-8").trim();
|
|
714
|
+
const port = parseInt(raw, 10);
|
|
715
|
+
return Number.isFinite(port) ? port : null;
|
|
716
|
+
}
|
|
717
|
+
function spawnServeDaemon(argv0) {
|
|
718
|
+
const existingPid = readPid();
|
|
719
|
+
if (existingPid && isProcessRunning(existingPid)) return;
|
|
720
|
+
const logFd = openSync(LOG_FILE, "a");
|
|
721
|
+
const child = spawn(process.execPath, [argv0, "serve"], {
|
|
722
|
+
detached: true,
|
|
723
|
+
stdio: ["ignore", logFd, logFd],
|
|
724
|
+
env: process.env
|
|
725
|
+
});
|
|
726
|
+
child.unref();
|
|
727
|
+
}
|
|
728
|
+
var serveCommand = new Command("serve").description(
|
|
729
|
+
"Run the local device discovery server (auto-started by `yapout login`)"
|
|
730
|
+
).option("--status", "Check if the server is running").option("--stop", "Stop the running server").action(async (opts) => {
|
|
731
|
+
if (opts.status) {
|
|
732
|
+
const pid = readPid();
|
|
733
|
+
const port = readPort();
|
|
734
|
+
if (pid && isProcessRunning(pid)) {
|
|
735
|
+
console.log(
|
|
736
|
+
chalk2.green(`yapout serve is running`) + chalk2.dim(` (PID ${pid}, port ${port ?? "?"})`)
|
|
737
|
+
);
|
|
738
|
+
} else {
|
|
739
|
+
if (pid) unlinkSync3(PID_FILE);
|
|
740
|
+
console.log(chalk2.dim("yapout serve is not running"));
|
|
741
|
+
}
|
|
742
|
+
return;
|
|
743
|
+
}
|
|
744
|
+
if (opts.stop) {
|
|
745
|
+
const pid = readPid();
|
|
746
|
+
if (!pid) {
|
|
747
|
+
console.log(chalk2.dim("yapout serve is not running"));
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
try {
|
|
751
|
+
process.kill(pid, "SIGTERM");
|
|
752
|
+
console.log(chalk2.green(`Stopped yapout serve`) + chalk2.dim(` (PID ${pid})`));
|
|
753
|
+
} catch {
|
|
754
|
+
console.log(chalk2.dim("Server already stopped"));
|
|
755
|
+
}
|
|
756
|
+
try {
|
|
757
|
+
unlinkSync3(PID_FILE);
|
|
758
|
+
} catch {
|
|
759
|
+
}
|
|
760
|
+
try {
|
|
761
|
+
unlinkSync3(PORT_FILE);
|
|
762
|
+
} catch {
|
|
763
|
+
}
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
const existingPid = readPid();
|
|
767
|
+
if (existingPid && !isProcessRunning(existingPid)) {
|
|
768
|
+
try {
|
|
769
|
+
unlinkSync3(PID_FILE);
|
|
770
|
+
} catch {
|
|
771
|
+
}
|
|
772
|
+
} else if (existingPid && isProcessRunning(existingPid)) {
|
|
773
|
+
console.log(
|
|
774
|
+
chalk2.dim(
|
|
775
|
+
`yapout serve is already running (PID ${existingPid}). Use --stop first.`
|
|
776
|
+
)
|
|
777
|
+
);
|
|
778
|
+
process.exit(1);
|
|
779
|
+
}
|
|
780
|
+
await runForeground();
|
|
781
|
+
});
|
|
782
|
+
|
|
524
783
|
// src/commands/login.ts
|
|
525
|
-
var
|
|
784
|
+
var CLI_VERSION2 = "0.9.0";
|
|
526
785
|
function safeReturnTo(raw) {
|
|
527
786
|
if (!raw) return null;
|
|
528
787
|
try {
|
|
@@ -541,7 +800,7 @@ function startCallbackServer() {
|
|
|
541
800
|
resolveData = res;
|
|
542
801
|
rejectData = rej;
|
|
543
802
|
});
|
|
544
|
-
const server =
|
|
803
|
+
const server = http2.createServer((req, res) => {
|
|
545
804
|
const url = new URL(req.url, `http://localhost`);
|
|
546
805
|
if (url.pathname === "/callback") {
|
|
547
806
|
const token = url.searchParams.get("token");
|
|
@@ -593,14 +852,14 @@ function startCallbackServer() {
|
|
|
593
852
|
}, 12e4);
|
|
594
853
|
});
|
|
595
854
|
}
|
|
596
|
-
var loginCommand = new
|
|
597
|
-
console.log(
|
|
855
|
+
var loginCommand = new Command2("login").description("Authenticate with yapout").action(async () => {
|
|
856
|
+
console.log(chalk3.dim("Starting authentication..."));
|
|
598
857
|
const { port, data } = await startCallbackServer();
|
|
599
858
|
const appUrl = getAppUrl();
|
|
600
859
|
const authUrl = `${appUrl}/auth/cli?port=${port}`;
|
|
601
|
-
console.log(
|
|
860
|
+
console.log(chalk3.dim(`Opening browser...`));
|
|
602
861
|
await open(authUrl);
|
|
603
|
-
console.log(
|
|
862
|
+
console.log(chalk3.dim("Waiting for authentication in browser..."));
|
|
604
863
|
try {
|
|
605
864
|
const result = await data;
|
|
606
865
|
const creds = {
|
|
@@ -615,74 +874,60 @@ var loginCommand = new Command("login").description("Authenticate with yapout").
|
|
|
615
874
|
Math.round((result.expiresAt - Date.now()) / 864e5)
|
|
616
875
|
);
|
|
617
876
|
console.log(
|
|
618
|
-
|
|
877
|
+
chalk3.green("Logged in as ") + chalk3.bold(result.email) + chalk3.dim(
|
|
619
878
|
` (expires in ${daysLeft} day${daysLeft === 1 ? "" : "s"})`
|
|
620
879
|
)
|
|
621
880
|
);
|
|
622
881
|
try {
|
|
623
882
|
const client = createConvexClient(creds.token);
|
|
624
|
-
const deviceId =
|
|
625
|
-
await client.mutation(
|
|
883
|
+
const deviceId = createHash2("sha256").update(hostname2() + userInfo2().username).digest("hex").slice(0, 16);
|
|
884
|
+
await client.mutation(anyApi3.functions.devices.registerDevice, {
|
|
626
885
|
deviceId,
|
|
627
|
-
name:
|
|
628
|
-
cliVersion:
|
|
886
|
+
name: hostname2(),
|
|
887
|
+
cliVersion: CLI_VERSION2
|
|
629
888
|
});
|
|
630
889
|
} catch {
|
|
631
|
-
console.warn(
|
|
890
|
+
console.warn(chalk3.dim("Note: Could not register device. This is non-fatal."));
|
|
632
891
|
}
|
|
633
892
|
try {
|
|
634
893
|
registerProtocolHandler();
|
|
635
894
|
console.log(
|
|
636
|
-
|
|
895
|
+
chalk3.dim("Registered yapout:// protocol handler")
|
|
637
896
|
);
|
|
638
897
|
} catch {
|
|
639
898
|
}
|
|
899
|
+
try {
|
|
900
|
+
spawnServeDaemon(process.argv[1]);
|
|
901
|
+
console.log(chalk3.dim("Started yapout serve in the background"));
|
|
902
|
+
} catch {
|
|
903
|
+
}
|
|
640
904
|
process.exit(0);
|
|
641
905
|
} catch (err) {
|
|
642
|
-
console.error(
|
|
906
|
+
console.error(chalk3.red(err.message));
|
|
643
907
|
process.exit(1);
|
|
644
908
|
}
|
|
645
909
|
});
|
|
646
910
|
|
|
647
911
|
// src/commands/logout.ts
|
|
648
|
-
import { Command as
|
|
649
|
-
import
|
|
650
|
-
var logoutCommand = new
|
|
912
|
+
import { Command as Command3 } from "commander";
|
|
913
|
+
import chalk4 from "chalk";
|
|
914
|
+
var logoutCommand = new Command3("logout").description("Log out of yapout").action(() => {
|
|
651
915
|
deleteCredentials();
|
|
652
|
-
console.log(
|
|
916
|
+
console.log(chalk4.green("Logged out."));
|
|
653
917
|
});
|
|
654
918
|
|
|
655
919
|
// src/commands/link.ts
|
|
656
|
-
import { Command as
|
|
657
|
-
import { resolve as resolve2, join as
|
|
920
|
+
import { Command as Command4 } from "commander";
|
|
921
|
+
import { resolve as resolve2, join as join4 } from "path";
|
|
658
922
|
import {
|
|
659
|
-
existsSync as
|
|
923
|
+
existsSync as existsSync3,
|
|
660
924
|
mkdirSync as mkdirSync3,
|
|
661
|
-
readFileSync as
|
|
662
|
-
writeFileSync as
|
|
925
|
+
readFileSync as readFileSync3,
|
|
926
|
+
writeFileSync as writeFileSync4,
|
|
663
927
|
appendFileSync
|
|
664
928
|
} from "fs";
|
|
665
|
-
import { hostname as
|
|
666
|
-
import
|
|
667
|
-
|
|
668
|
-
// src/lib/auth.ts
|
|
669
|
-
import chalk3 from "chalk";
|
|
670
|
-
function requireAuth() {
|
|
671
|
-
const creds = readCredentials();
|
|
672
|
-
if (!creds) {
|
|
673
|
-
console.error(
|
|
674
|
-
chalk3.red("Not logged in.") + " Run " + chalk3.cyan("yapout login") + " first."
|
|
675
|
-
);
|
|
676
|
-
process.exit(1);
|
|
677
|
-
}
|
|
678
|
-
if (Date.now() > creds.expiresAt) {
|
|
679
|
-
console.error(
|
|
680
|
-
chalk3.red("Session expired.") + " Run " + chalk3.cyan("yapout login") + " to re-authenticate."
|
|
681
|
-
);
|
|
682
|
-
process.exit(1);
|
|
683
|
-
}
|
|
684
|
-
return creds;
|
|
685
|
-
}
|
|
929
|
+
import { hostname as hostname3 } from "os";
|
|
930
|
+
import chalk5 from "chalk";
|
|
686
931
|
|
|
687
932
|
// src/lib/prompts.ts
|
|
688
933
|
import { select } from "@inquirer/prompts";
|
|
@@ -734,7 +979,7 @@ branch_prefix: feat
|
|
|
734
979
|
# {{ticket.linearTicketId}}, {{ticket.id}}
|
|
735
980
|
# commit_template: "{{ticket.type}}({{ticket.linearTicketId}}): {{ticket.title}}"
|
|
736
981
|
`;
|
|
737
|
-
var linkCommand = new
|
|
982
|
+
var linkCommand = new Command4("link").description("Link the current directory to a yapout project").action(async () => {
|
|
738
983
|
const creds = requireAuth();
|
|
739
984
|
const cwd = resolveRepoRoot(resolve2(process.cwd()));
|
|
740
985
|
const client = createConvexClient(creds.token);
|
|
@@ -746,25 +991,25 @@ var linkCommand = new Command3("link").description("Link the current directory t
|
|
|
746
991
|
);
|
|
747
992
|
} catch (err) {
|
|
748
993
|
console.error(
|
|
749
|
-
|
|
994
|
+
chalk5.red("Failed to fetch projects."),
|
|
750
995
|
err.message
|
|
751
996
|
);
|
|
752
997
|
process.exit(1);
|
|
753
998
|
}
|
|
754
999
|
if (projects.length === 0) {
|
|
755
1000
|
console.error(
|
|
756
|
-
|
|
1001
|
+
chalk5.yellow("No projects found.") + " Create one at " + chalk5.cyan(`${getAppUrl()}/dashboard`)
|
|
757
1002
|
);
|
|
758
1003
|
process.exit(1);
|
|
759
1004
|
}
|
|
760
1005
|
const selected = await pickProject(projects);
|
|
761
|
-
const device = getOrCreateDeviceIdentity(
|
|
1006
|
+
const device = getOrCreateDeviceIdentity(hostname3());
|
|
762
1007
|
try {
|
|
763
1008
|
await client.mutation(anyApi.functions.devices.registerDevice, {
|
|
764
1009
|
deviceId: device.deviceId,
|
|
765
1010
|
name: device.name,
|
|
766
1011
|
cliVersion: getCliVersion(),
|
|
767
|
-
machineHostname:
|
|
1012
|
+
machineHostname: hostname3()
|
|
768
1013
|
});
|
|
769
1014
|
await client.mutation(anyApi.functions.projectCheckouts.linkCheckout, {
|
|
770
1015
|
projectId: selected.id,
|
|
@@ -773,7 +1018,7 @@ var linkCommand = new Command3("link").description("Link the current directory t
|
|
|
773
1018
|
});
|
|
774
1019
|
} catch (err) {
|
|
775
1020
|
console.warn(
|
|
776
|
-
|
|
1021
|
+
chalk5.yellow("Warning: failed to record this checkout \u2014 "),
|
|
777
1022
|
err.message
|
|
778
1023
|
);
|
|
779
1024
|
}
|
|
@@ -782,17 +1027,17 @@ var linkCommand = new Command3("link").description("Link the current directory t
|
|
|
782
1027
|
projectName: selected.name,
|
|
783
1028
|
linkedAt: Date.now()
|
|
784
1029
|
});
|
|
785
|
-
const yapoutDir =
|
|
786
|
-
if (!
|
|
1030
|
+
const yapoutDir = join4(cwd, ".yapout");
|
|
1031
|
+
if (!existsSync3(yapoutDir)) {
|
|
787
1032
|
mkdirSync3(yapoutDir, { recursive: true });
|
|
788
1033
|
}
|
|
789
|
-
const configPath =
|
|
790
|
-
if (!
|
|
791
|
-
|
|
1034
|
+
const configPath = join4(yapoutDir, "config.yml");
|
|
1035
|
+
if (!existsSync3(configPath)) {
|
|
1036
|
+
writeFileSync4(configPath, CONFIG_YAML_CONTENT);
|
|
792
1037
|
}
|
|
793
|
-
const gitignorePath =
|
|
794
|
-
if (
|
|
795
|
-
const content =
|
|
1038
|
+
const gitignorePath = join4(cwd, ".gitignore");
|
|
1039
|
+
if (existsSync3(gitignorePath)) {
|
|
1040
|
+
const content = readFileSync3(gitignorePath, "utf-8");
|
|
796
1041
|
if (!content.includes(".yapout/")) {
|
|
797
1042
|
appendFileSync(
|
|
798
1043
|
gitignorePath,
|
|
@@ -800,13 +1045,13 @@ var linkCommand = new Command3("link").description("Link the current directory t
|
|
|
800
1045
|
);
|
|
801
1046
|
}
|
|
802
1047
|
} else {
|
|
803
|
-
|
|
1048
|
+
writeFileSync4(gitignorePath, "# yapout local config\n.yapout/\n");
|
|
804
1049
|
}
|
|
805
|
-
const mcpPath =
|
|
1050
|
+
const mcpPath = join4(cwd, ".mcp.json");
|
|
806
1051
|
let mcpConfig = {};
|
|
807
|
-
if (
|
|
1052
|
+
if (existsSync3(mcpPath)) {
|
|
808
1053
|
try {
|
|
809
|
-
mcpConfig = JSON.parse(
|
|
1054
|
+
mcpConfig = JSON.parse(readFileSync3(mcpPath, "utf-8"));
|
|
810
1055
|
} catch {
|
|
811
1056
|
}
|
|
812
1057
|
}
|
|
@@ -815,17 +1060,17 @@ var linkCommand = new Command3("link").description("Link the current directory t
|
|
|
815
1060
|
command: "yapout",
|
|
816
1061
|
args: ["mcp-server"]
|
|
817
1062
|
};
|
|
818
|
-
|
|
1063
|
+
writeFileSync4(mcpPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
819
1064
|
const label = selected.githubRepoFullName ? `${selected.name} (${selected.githubRepoFullName})` : selected.name;
|
|
820
1065
|
const orgSuffix = selected.org ? ` in ${selected.org.name}` : "";
|
|
821
1066
|
console.log(
|
|
822
|
-
|
|
1067
|
+
chalk5.green(`Linked to ${label}${orgSuffix}.`) + " Claude Code will discover yapout tools automatically."
|
|
823
1068
|
);
|
|
824
1069
|
});
|
|
825
1070
|
function getCliVersion() {
|
|
826
1071
|
try {
|
|
827
1072
|
const pkg = JSON.parse(
|
|
828
|
-
|
|
1073
|
+
readFileSync3(join4(import.meta.dirname, "..", "package.json"), "utf-8")
|
|
829
1074
|
);
|
|
830
1075
|
return pkg.version ?? "unknown";
|
|
831
1076
|
} catch {
|
|
@@ -834,15 +1079,15 @@ function getCliVersion() {
|
|
|
834
1079
|
}
|
|
835
1080
|
|
|
836
1081
|
// src/commands/unlink.ts
|
|
837
|
-
import { Command as
|
|
838
|
-
import { resolve as resolve3, join as
|
|
839
|
-
import { existsSync as
|
|
840
|
-
import
|
|
841
|
-
var unlinkCommand = new
|
|
1082
|
+
import { Command as Command5 } from "commander";
|
|
1083
|
+
import { resolve as resolve3, join as join5 } from "path";
|
|
1084
|
+
import { existsSync as existsSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync5, rmSync } from "fs";
|
|
1085
|
+
import chalk6 from "chalk";
|
|
1086
|
+
var unlinkCommand = new Command5("unlink").description("Unlink the current directory from its yapout project").action(async () => {
|
|
842
1087
|
const cwd = resolve3(process.cwd());
|
|
843
1088
|
const mapping = getProjectMapping(cwd);
|
|
844
1089
|
if (!mapping) {
|
|
845
|
-
console.error(
|
|
1090
|
+
console.error(chalk6.yellow("No project linked to this directory."));
|
|
846
1091
|
process.exit(1);
|
|
847
1092
|
}
|
|
848
1093
|
const device = readDeviceIdentity();
|
|
@@ -856,26 +1101,26 @@ var unlinkCommand = new Command4("unlink").description("Unlink the current direc
|
|
|
856
1101
|
});
|
|
857
1102
|
} catch (err) {
|
|
858
1103
|
console.warn(
|
|
859
|
-
|
|
1104
|
+
chalk6.yellow("Warning: failed to update server \u2014 "),
|
|
860
1105
|
err.message
|
|
861
1106
|
);
|
|
862
1107
|
}
|
|
863
1108
|
}
|
|
864
1109
|
removeProjectMapping(cwd);
|
|
865
|
-
const yapoutDir =
|
|
866
|
-
if (
|
|
1110
|
+
const yapoutDir = join5(cwd, ".yapout");
|
|
1111
|
+
if (existsSync4(yapoutDir)) {
|
|
867
1112
|
rmSync(yapoutDir, { recursive: true });
|
|
868
1113
|
}
|
|
869
|
-
const mcpPath =
|
|
870
|
-
if (
|
|
1114
|
+
const mcpPath = join5(cwd, ".mcp.json");
|
|
1115
|
+
if (existsSync4(mcpPath)) {
|
|
871
1116
|
try {
|
|
872
|
-
const mcpConfig = JSON.parse(
|
|
1117
|
+
const mcpConfig = JSON.parse(readFileSync4(mcpPath, "utf-8"));
|
|
873
1118
|
if (mcpConfig.mcpServers?.yapout) {
|
|
874
1119
|
delete mcpConfig.mcpServers.yapout;
|
|
875
1120
|
if (Object.keys(mcpConfig.mcpServers).length === 0) {
|
|
876
1121
|
rmSync(mcpPath);
|
|
877
1122
|
} else {
|
|
878
|
-
|
|
1123
|
+
writeFileSync5(
|
|
879
1124
|
mcpPath,
|
|
880
1125
|
JSON.stringify(mcpConfig, null, 2) + "\n"
|
|
881
1126
|
);
|
|
@@ -884,23 +1129,23 @@ var unlinkCommand = new Command4("unlink").description("Unlink the current direc
|
|
|
884
1129
|
} catch {
|
|
885
1130
|
}
|
|
886
1131
|
}
|
|
887
|
-
console.log(
|
|
1132
|
+
console.log(chalk6.green("Unlinked."));
|
|
888
1133
|
});
|
|
889
1134
|
|
|
890
1135
|
// src/commands/status.ts
|
|
891
|
-
import { Command as
|
|
1136
|
+
import { Command as Command6 } from "commander";
|
|
892
1137
|
import { resolve as resolve4 } from "path";
|
|
893
|
-
import
|
|
894
|
-
var statusCommand = new
|
|
895
|
-
console.log(
|
|
1138
|
+
import chalk7 from "chalk";
|
|
1139
|
+
var statusCommand = new Command6("status").description("Show yapout status for this directory").action(() => {
|
|
1140
|
+
console.log(chalk7.bold("yapout status\n"));
|
|
896
1141
|
const creds = readCredentials();
|
|
897
1142
|
if (!creds) {
|
|
898
1143
|
console.log(
|
|
899
|
-
` Auth: ${
|
|
1144
|
+
` Auth: ${chalk7.red("Not logged in.")} Run ${chalk7.cyan("yapout login")}.`
|
|
900
1145
|
);
|
|
901
1146
|
} else if (Date.now() > creds.expiresAt) {
|
|
902
1147
|
console.log(
|
|
903
|
-
` Auth: ${
|
|
1148
|
+
` Auth: ${chalk7.red("Session expired.")} Run ${chalk7.cyan("yapout login")}.`
|
|
904
1149
|
);
|
|
905
1150
|
} else {
|
|
906
1151
|
const daysLeft = Math.max(
|
|
@@ -908,35 +1153,35 @@ var statusCommand = new Command5("status").description("Show yapout status for t
|
|
|
908
1153
|
Math.round((creds.expiresAt - Date.now()) / 864e5)
|
|
909
1154
|
);
|
|
910
1155
|
console.log(
|
|
911
|
-
` Auth: ${
|
|
1156
|
+
` Auth: ${chalk7.green(creds.email)} (expires in ${daysLeft} day${daysLeft === 1 ? "" : "s"})`
|
|
912
1157
|
);
|
|
913
1158
|
}
|
|
914
1159
|
const cwd = resolve4(process.cwd());
|
|
915
1160
|
const mapping = getProjectMapping(cwd);
|
|
916
1161
|
if (!mapping) {
|
|
917
1162
|
console.log(
|
|
918
|
-
` Project: ${
|
|
1163
|
+
` Project: ${chalk7.yellow("No project linked.")} Run ${chalk7.cyan("yapout link")} in a repo.`
|
|
919
1164
|
);
|
|
920
1165
|
} else {
|
|
921
|
-
console.log(` Project: ${
|
|
1166
|
+
console.log(` Project: ${chalk7.green(mapping.projectName)}`);
|
|
922
1167
|
}
|
|
923
|
-
console.log(` Daemon: ${
|
|
924
|
-
console.log(` Work: ${
|
|
1168
|
+
console.log(` Daemon: ${chalk7.dim("not running")}`);
|
|
1169
|
+
console.log(` Work: ${chalk7.dim("no active tickets")}`);
|
|
925
1170
|
console.log();
|
|
926
1171
|
});
|
|
927
1172
|
|
|
928
1173
|
// src/commands/init.ts
|
|
929
|
-
import { Command as
|
|
930
|
-
import { resolve as resolve5, join as
|
|
1174
|
+
import { Command as Command7 } from "commander";
|
|
1175
|
+
import { resolve as resolve5, join as join6 } from "path";
|
|
931
1176
|
import {
|
|
932
|
-
existsSync as
|
|
1177
|
+
existsSync as existsSync5,
|
|
933
1178
|
mkdirSync as mkdirSync4,
|
|
934
|
-
writeFileSync as
|
|
935
|
-
readFileSync as
|
|
1179
|
+
writeFileSync as writeFileSync6,
|
|
1180
|
+
readFileSync as readFileSync5,
|
|
936
1181
|
appendFileSync as appendFileSync2
|
|
937
1182
|
} from "fs";
|
|
938
|
-
import { hostname as
|
|
939
|
-
import
|
|
1183
|
+
import { hostname as hostname4 } from "os";
|
|
1184
|
+
import chalk8 from "chalk";
|
|
940
1185
|
var CONFIG_YAML_CONTENT2 = `# yapout local configuration
|
|
941
1186
|
# See: https://docs.yapout.dev/cli/config
|
|
942
1187
|
|
|
@@ -961,7 +1206,7 @@ branch_prefix: feat
|
|
|
961
1206
|
# {{ticket.linearTicketId}}, {{ticket.id}}
|
|
962
1207
|
# commit_template: "{{ticket.type}}({{ticket.linearTicketId}}): {{ticket.title}}"
|
|
963
1208
|
`;
|
|
964
|
-
var initCommand = new
|
|
1209
|
+
var initCommand = new Command7("init").description("Create a yapout project from the current repo and link it").argument("[name]", "Project name (defaults to repo name)").option("--org <slug>", "Org slug to create the project in (skips picker)").action(async (name, options) => {
|
|
965
1210
|
const creds = requireAuth();
|
|
966
1211
|
const cwd = resolveRepoRoot(resolve5(process.cwd()));
|
|
967
1212
|
let repoFullName;
|
|
@@ -971,7 +1216,7 @@ var initCommand = new Command6("init").description("Create a yapout project from
|
|
|
971
1216
|
defaultBranch = getDefaultBranch(cwd);
|
|
972
1217
|
} catch (err) {
|
|
973
1218
|
console.error(
|
|
974
|
-
|
|
1219
|
+
chalk8.red("Not a git repo with a GitHub remote."),
|
|
975
1220
|
err.message
|
|
976
1221
|
);
|
|
977
1222
|
process.exit(1);
|
|
@@ -986,14 +1231,14 @@ var initCommand = new Command6("init").description("Create a yapout project from
|
|
|
986
1231
|
);
|
|
987
1232
|
} catch (err) {
|
|
988
1233
|
console.error(
|
|
989
|
-
|
|
1234
|
+
chalk8.red("Failed to load your orgs."),
|
|
990
1235
|
err.message
|
|
991
1236
|
);
|
|
992
1237
|
process.exit(1);
|
|
993
1238
|
}
|
|
994
1239
|
if (!orgs || orgs.length === 0) {
|
|
995
1240
|
console.error(
|
|
996
|
-
|
|
1241
|
+
chalk8.red(
|
|
997
1242
|
"You aren't a member of any org. Sign in to the web app once to create your personal org, then re-run."
|
|
998
1243
|
)
|
|
999
1244
|
);
|
|
@@ -1005,10 +1250,10 @@ var initCommand = new Command6("init").description("Create a yapout project from
|
|
|
1005
1250
|
const match = orgs.find((o) => o.org.slug === options.org);
|
|
1006
1251
|
if (!match) {
|
|
1007
1252
|
console.error(
|
|
1008
|
-
|
|
1253
|
+
chalk8.red(`Org "${options.org}" not found among your memberships.`)
|
|
1009
1254
|
);
|
|
1010
1255
|
console.error(
|
|
1011
|
-
|
|
1256
|
+
chalk8.dim(
|
|
1012
1257
|
"Available: " + orgs.map((o) => o.org.slug).join(", ")
|
|
1013
1258
|
)
|
|
1014
1259
|
);
|
|
@@ -1020,7 +1265,7 @@ var initCommand = new Command6("init").description("Create a yapout project from
|
|
|
1020
1265
|
chosenOrgId = orgs[0].org._id;
|
|
1021
1266
|
chosenOrgName = orgs[0].org.name;
|
|
1022
1267
|
console.log(
|
|
1023
|
-
|
|
1268
|
+
chalk8.dim(`Creating in `) + chalk8.cyan(chosenOrgName)
|
|
1024
1269
|
);
|
|
1025
1270
|
} else {
|
|
1026
1271
|
const picked = await pickOrg(
|
|
@@ -1034,17 +1279,17 @@ var initCommand = new Command6("init").description("Create a yapout project from
|
|
|
1034
1279
|
chosenOrgId = picked.id;
|
|
1035
1280
|
chosenOrgName = picked.name;
|
|
1036
1281
|
}
|
|
1037
|
-
const device = getOrCreateDeviceIdentity(
|
|
1282
|
+
const device = getOrCreateDeviceIdentity(hostname4());
|
|
1038
1283
|
try {
|
|
1039
1284
|
await client.mutation(anyApi.functions.devices.registerDevice, {
|
|
1040
1285
|
deviceId: device.deviceId,
|
|
1041
1286
|
name: device.name,
|
|
1042
1287
|
cliVersion: getCliVersion2(),
|
|
1043
|
-
machineHostname:
|
|
1288
|
+
machineHostname: hostname4()
|
|
1044
1289
|
});
|
|
1045
1290
|
} catch (err) {
|
|
1046
1291
|
console.warn(
|
|
1047
|
-
|
|
1292
|
+
chalk8.yellow("Warning: device registration failed \u2014 "),
|
|
1048
1293
|
err.message
|
|
1049
1294
|
);
|
|
1050
1295
|
}
|
|
@@ -1061,7 +1306,7 @@ var initCommand = new Command6("init").description("Create a yapout project from
|
|
|
1061
1306
|
);
|
|
1062
1307
|
} catch (err) {
|
|
1063
1308
|
console.error(
|
|
1064
|
-
|
|
1309
|
+
chalk8.red("Failed to create project."),
|
|
1065
1310
|
err.message
|
|
1066
1311
|
);
|
|
1067
1312
|
process.exit(1);
|
|
@@ -1074,7 +1319,7 @@ var initCommand = new Command6("init").description("Create a yapout project from
|
|
|
1074
1319
|
});
|
|
1075
1320
|
} catch (err) {
|
|
1076
1321
|
console.warn(
|
|
1077
|
-
|
|
1322
|
+
chalk8.yellow("Warning: failed to record project checkout \u2014 "),
|
|
1078
1323
|
err.message
|
|
1079
1324
|
);
|
|
1080
1325
|
}
|
|
@@ -1083,15 +1328,15 @@ var initCommand = new Command6("init").description("Create a yapout project from
|
|
|
1083
1328
|
projectName: result.projectName,
|
|
1084
1329
|
linkedAt: Date.now()
|
|
1085
1330
|
});
|
|
1086
|
-
const yapoutDir =
|
|
1087
|
-
if (!
|
|
1088
|
-
const configPath =
|
|
1089
|
-
if (!
|
|
1090
|
-
|
|
1091
|
-
}
|
|
1092
|
-
const gitignorePath =
|
|
1093
|
-
if (
|
|
1094
|
-
const content =
|
|
1331
|
+
const yapoutDir = join6(cwd, ".yapout");
|
|
1332
|
+
if (!existsSync5(yapoutDir)) mkdirSync4(yapoutDir, { recursive: true });
|
|
1333
|
+
const configPath = join6(yapoutDir, "config.yml");
|
|
1334
|
+
if (!existsSync5(configPath)) {
|
|
1335
|
+
writeFileSync6(configPath, CONFIG_YAML_CONTENT2);
|
|
1336
|
+
}
|
|
1337
|
+
const gitignorePath = join6(cwd, ".gitignore");
|
|
1338
|
+
if (existsSync5(gitignorePath)) {
|
|
1339
|
+
const content = readFileSync5(gitignorePath, "utf-8");
|
|
1095
1340
|
if (!content.includes(".yapout/")) {
|
|
1096
1341
|
appendFileSync2(
|
|
1097
1342
|
gitignorePath,
|
|
@@ -1099,11 +1344,11 @@ var initCommand = new Command6("init").description("Create a yapout project from
|
|
|
1099
1344
|
);
|
|
1100
1345
|
}
|
|
1101
1346
|
}
|
|
1102
|
-
const mcpPath =
|
|
1347
|
+
const mcpPath = join6(cwd, ".mcp.json");
|
|
1103
1348
|
let mcpConfig = {};
|
|
1104
|
-
if (
|
|
1349
|
+
if (existsSync5(mcpPath)) {
|
|
1105
1350
|
try {
|
|
1106
|
-
mcpConfig = JSON.parse(
|
|
1351
|
+
mcpConfig = JSON.parse(readFileSync5(mcpPath, "utf-8"));
|
|
1107
1352
|
} catch {
|
|
1108
1353
|
}
|
|
1109
1354
|
}
|
|
@@ -1112,20 +1357,20 @@ var initCommand = new Command6("init").description("Create a yapout project from
|
|
|
1112
1357
|
command: "yapout",
|
|
1113
1358
|
args: ["mcp-server"]
|
|
1114
1359
|
};
|
|
1115
|
-
|
|
1360
|
+
writeFileSync6(mcpPath, JSON.stringify(mcpConfig, null, 2) + "\n");
|
|
1116
1361
|
console.log(
|
|
1117
|
-
|
|
1362
|
+
chalk8.green(`Created project "${result.projectName}"`) + chalk8.dim(
|
|
1118
1363
|
` in ${chosenOrgName} (${repoFullName}, branch: ${defaultBranch})`
|
|
1119
1364
|
)
|
|
1120
1365
|
);
|
|
1121
1366
|
console.log(
|
|
1122
|
-
|
|
1367
|
+
chalk8.dim("Run ") + chalk8.cyan("yapout_compact") + chalk8.dim(" in Claude Code to generate project context.")
|
|
1123
1368
|
);
|
|
1124
1369
|
});
|
|
1125
1370
|
function getCliVersion2() {
|
|
1126
1371
|
try {
|
|
1127
1372
|
const pkg = JSON.parse(
|
|
1128
|
-
|
|
1373
|
+
readFileSync5(join6(import.meta.dirname, "..", "package.json"), "utf-8")
|
|
1129
1374
|
);
|
|
1130
1375
|
return pkg.version ?? "unknown";
|
|
1131
1376
|
} catch {
|
|
@@ -1134,18 +1379,18 @@ function getCliVersion2() {
|
|
|
1134
1379
|
}
|
|
1135
1380
|
|
|
1136
1381
|
// src/commands/mcp-server.ts
|
|
1137
|
-
import { Command as
|
|
1382
|
+
import { Command as Command8 } from "commander";
|
|
1138
1383
|
|
|
1139
1384
|
// src/mcp/server.ts
|
|
1140
1385
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
1141
1386
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
1142
1387
|
import { ConvexHttpClient as ConvexHttpClient2 } from "convex/browser";
|
|
1143
|
-
import { anyApi as
|
|
1388
|
+
import { anyApi as anyApi4 } from "convex/server";
|
|
1144
1389
|
|
|
1145
1390
|
// src/mcp/tools/init.ts
|
|
1146
1391
|
import { z } from "zod";
|
|
1147
|
-
import { join as
|
|
1148
|
-
import { existsSync as
|
|
1392
|
+
import { join as join7 } from "path";
|
|
1393
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync5, writeFileSync as writeFileSync7 } from "fs";
|
|
1149
1394
|
import { stringify as yamlStringify } from "yaml";
|
|
1150
1395
|
function registerInitTool(server, ctx) {
|
|
1151
1396
|
server.tool(
|
|
@@ -1161,7 +1406,7 @@ function registerInitTool(server, ctx) {
|
|
|
1161
1406
|
const defaultBranch = getDefaultBranch(repoRoot);
|
|
1162
1407
|
const projectName = args.name || repoFullName.split("/")[1] || "unnamed";
|
|
1163
1408
|
const result = await ctx.client.mutation(
|
|
1164
|
-
|
|
1409
|
+
anyApi4.functions.projects.createProjectFromCli,
|
|
1165
1410
|
{
|
|
1166
1411
|
name: projectName,
|
|
1167
1412
|
githubRepoFullName: repoFullName,
|
|
@@ -1176,11 +1421,11 @@ function registerInitTool(server, ctx) {
|
|
|
1176
1421
|
projectName: result.projectName,
|
|
1177
1422
|
linkedAt: Date.now()
|
|
1178
1423
|
});
|
|
1179
|
-
const yapoutDir =
|
|
1180
|
-
if (!
|
|
1181
|
-
const configPath =
|
|
1182
|
-
if (!
|
|
1183
|
-
|
|
1424
|
+
const yapoutDir = join7(repoRoot, ".yapout");
|
|
1425
|
+
if (!existsSync6(yapoutDir)) mkdirSync5(yapoutDir, { recursive: true });
|
|
1426
|
+
const configPath = join7(yapoutDir, "config.yml");
|
|
1427
|
+
if (!existsSync6(configPath)) {
|
|
1428
|
+
writeFileSync7(
|
|
1184
1429
|
configPath,
|
|
1185
1430
|
`# yapout local configuration
|
|
1186
1431
|
|
|
@@ -1249,7 +1494,7 @@ function registerCompactTool(server, ctx) {
|
|
|
1249
1494
|
let lastUpdated;
|
|
1250
1495
|
try {
|
|
1251
1496
|
const project = await ctx.client.query(
|
|
1252
|
-
|
|
1497
|
+
anyApi4.functions.projects.getProject,
|
|
1253
1498
|
{ projectId: ctx.projectId }
|
|
1254
1499
|
);
|
|
1255
1500
|
currentContext = project?.contextSummary ?? void 0;
|
|
@@ -1292,7 +1537,7 @@ function registerUpdateContextTool(server, ctx) {
|
|
|
1292
1537
|
};
|
|
1293
1538
|
}
|
|
1294
1539
|
await ctx.client.mutation(
|
|
1295
|
-
|
|
1540
|
+
anyApi4.functions.projects.updateProjectContext,
|
|
1296
1541
|
{
|
|
1297
1542
|
projectId: ctx.projectId,
|
|
1298
1543
|
summary: args.summary
|
|
@@ -1362,7 +1607,7 @@ function registerQueueTool(server, ctx) {
|
|
|
1362
1607
|
};
|
|
1363
1608
|
}
|
|
1364
1609
|
const data = await ctx.client.query(
|
|
1365
|
-
|
|
1610
|
+
anyApi4.functions.workQueue.getWorkQueue,
|
|
1366
1611
|
{ projectId: ctx.projectId }
|
|
1367
1612
|
);
|
|
1368
1613
|
if (!data) {
|
|
@@ -1471,7 +1716,7 @@ function registerGetBriefTool(server, ctx) {
|
|
|
1471
1716
|
}
|
|
1472
1717
|
try {
|
|
1473
1718
|
const data = await ctx.client.query(
|
|
1474
|
-
|
|
1719
|
+
anyApi4.functions.findings.getFindingBrief,
|
|
1475
1720
|
{ findingId: itemId }
|
|
1476
1721
|
);
|
|
1477
1722
|
if (data) {
|
|
@@ -1485,7 +1730,7 @@ function registerGetBriefTool(server, ctx) {
|
|
|
1485
1730
|
}
|
|
1486
1731
|
try {
|
|
1487
1732
|
const bundle = await ctx.client.query(
|
|
1488
|
-
|
|
1733
|
+
anyApi4.functions.bundles.getBundle,
|
|
1489
1734
|
{ bundleId: itemId }
|
|
1490
1735
|
);
|
|
1491
1736
|
if (bundle) {
|
|
@@ -1536,8 +1781,8 @@ function registerGetBriefTool(server, ctx) {
|
|
|
1536
1781
|
|
|
1537
1782
|
// src/mcp/tools/claim.ts
|
|
1538
1783
|
import { z as z5 } from "zod";
|
|
1539
|
-
import { join as
|
|
1540
|
-
import { existsSync as
|
|
1784
|
+
import { join as join8 } from "path";
|
|
1785
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync6, writeFileSync as writeFileSync8 } from "fs";
|
|
1541
1786
|
function readBranchPrefix(cwd) {
|
|
1542
1787
|
try {
|
|
1543
1788
|
const config = readYapoutConfig(cwd);
|
|
@@ -1656,7 +1901,7 @@ function formatBundleBrief(bundle, projectContext) {
|
|
|
1656
1901
|
async function detectWorkItemKind(client, workItemId) {
|
|
1657
1902
|
try {
|
|
1658
1903
|
const bundle = await client.query(
|
|
1659
|
-
|
|
1904
|
+
anyApi4.functions.bundles.getBundle,
|
|
1660
1905
|
{ bundleId: workItemId }
|
|
1661
1906
|
);
|
|
1662
1907
|
if (bundle) {
|
|
@@ -1710,7 +1955,7 @@ function registerClaimTool(server, ctx) {
|
|
|
1710
1955
|
}
|
|
1711
1956
|
async function claimStandalone(ctx, args, findingId) {
|
|
1712
1957
|
const briefData = await ctx.client.query(
|
|
1713
|
-
|
|
1958
|
+
anyApi4.functions.findings.getFindingBrief,
|
|
1714
1959
|
{ findingId }
|
|
1715
1960
|
);
|
|
1716
1961
|
if (!briefData) {
|
|
@@ -1731,11 +1976,11 @@ async function claimStandalone(ctx, args, findingId) {
|
|
|
1731
1976
|
const slug = slugify(finding.title);
|
|
1732
1977
|
const branchName = linearIssueId ? `${prefix}/${linearIssueId.toLowerCase()}-${slug}` : `${prefix}/${slug}`;
|
|
1733
1978
|
const localClaim = await ctx.client.mutation(
|
|
1734
|
-
|
|
1979
|
+
anyApi4.functions.findings.claimFindingLocal,
|
|
1735
1980
|
{ findingId, branchName }
|
|
1736
1981
|
);
|
|
1737
1982
|
const claim = await ctx.client.mutation(
|
|
1738
|
-
|
|
1983
|
+
anyApi4.functions.workQueue.claimForImplementation,
|
|
1739
1984
|
{
|
|
1740
1985
|
projectId: ctx.projectId,
|
|
1741
1986
|
workItemId: findingId,
|
|
@@ -1745,7 +1990,7 @@ async function claimStandalone(ctx, args, findingId) {
|
|
|
1745
1990
|
if (linearIssueId && ctx.projectId) {
|
|
1746
1991
|
try {
|
|
1747
1992
|
await ctx.client.action(
|
|
1748
|
-
|
|
1993
|
+
anyApi4.functions.linearStatusMutations.moveIssueStatus,
|
|
1749
1994
|
{
|
|
1750
1995
|
projectId: ctx.projectId,
|
|
1751
1996
|
linearIssueId,
|
|
@@ -1802,11 +2047,11 @@ async function claimBundle(ctx, args, bundleId, bundleData) {
|
|
|
1802
2047
|
};
|
|
1803
2048
|
}
|
|
1804
2049
|
const localClaim = await ctx.client.mutation(
|
|
1805
|
-
|
|
2050
|
+
anyApi4.functions.findings.claimFindingLocal,
|
|
1806
2051
|
{ findingId: primaryFinding._id, branchName }
|
|
1807
2052
|
);
|
|
1808
2053
|
const claim = await ctx.client.mutation(
|
|
1809
|
-
|
|
2054
|
+
anyApi4.functions.workQueue.claimForImplementation,
|
|
1810
2055
|
{
|
|
1811
2056
|
projectId: ctx.projectId,
|
|
1812
2057
|
workItemId: bundleId,
|
|
@@ -1817,7 +2062,7 @@ async function claimBundle(ctx, args, bundleId, bundleData) {
|
|
|
1817
2062
|
if (f.linearIssueId && ctx.projectId) {
|
|
1818
2063
|
try {
|
|
1819
2064
|
await ctx.client.action(
|
|
1820
|
-
|
|
2065
|
+
anyApi4.functions.linearStatusMutations.moveIssueStatus,
|
|
1821
2066
|
{
|
|
1822
2067
|
projectId: ctx.projectId,
|
|
1823
2068
|
linearIssueId: f.linearIssueId,
|
|
@@ -1831,7 +2076,7 @@ async function claimBundle(ctx, args, bundleId, bundleData) {
|
|
|
1831
2076
|
let projectContext;
|
|
1832
2077
|
try {
|
|
1833
2078
|
const briefData = await ctx.client.query(
|
|
1834
|
-
|
|
2079
|
+
anyApi4.functions.findings.getFindingBrief,
|
|
1835
2080
|
{ findingId: primaryFinding._id }
|
|
1836
2081
|
);
|
|
1837
2082
|
projectContext = briefData?.projectContext;
|
|
@@ -1871,7 +2116,7 @@ async function claimBundle(ctx, args, bundleId, bundleData) {
|
|
|
1871
2116
|
async function getDefaultBranchForProject(ctx) {
|
|
1872
2117
|
try {
|
|
1873
2118
|
const data = await ctx.client.query(
|
|
1874
|
-
|
|
2119
|
+
anyApi4.functions.projects.getProject,
|
|
1875
2120
|
{ projectId: ctx.projectId }
|
|
1876
2121
|
);
|
|
1877
2122
|
return data?.githubDefaultBranch || "main";
|
|
@@ -1890,7 +2135,7 @@ async function setupWorktree(ctx, itemId, branchName, defaultBranch, brief, pipe
|
|
|
1890
2135
|
writeBrief(worktreePath, brief);
|
|
1891
2136
|
try {
|
|
1892
2137
|
await ctx.client.mutation(
|
|
1893
|
-
|
|
2138
|
+
anyApi4.functions.pipelineRuns.reportDaemonEvent,
|
|
1894
2139
|
{
|
|
1895
2140
|
pipelineRunId,
|
|
1896
2141
|
event: "worktree_created",
|
|
@@ -1919,14 +2164,14 @@ async function setupWorktree(ctx, itemId, branchName, defaultBranch, brief, pipe
|
|
|
1919
2164
|
};
|
|
1920
2165
|
}
|
|
1921
2166
|
function writeBrief(dir, brief) {
|
|
1922
|
-
const yapoutDir =
|
|
1923
|
-
if (!
|
|
1924
|
-
|
|
2167
|
+
const yapoutDir = join8(dir, ".yapout");
|
|
2168
|
+
if (!existsSync7(yapoutDir)) mkdirSync6(yapoutDir, { recursive: true });
|
|
2169
|
+
writeFileSync8(join8(yapoutDir, "brief.md"), brief);
|
|
1925
2170
|
}
|
|
1926
2171
|
async function reportClaimEvents(ctx, pipelineRunId, title, branchName) {
|
|
1927
2172
|
try {
|
|
1928
2173
|
await ctx.client.mutation(
|
|
1929
|
-
|
|
2174
|
+
anyApi4.functions.pipelineRuns.reportDaemonEvent,
|
|
1930
2175
|
{
|
|
1931
2176
|
pipelineRunId,
|
|
1932
2177
|
event: "daemon_claimed",
|
|
@@ -1934,7 +2179,7 @@ async function reportClaimEvents(ctx, pipelineRunId, title, branchName) {
|
|
|
1934
2179
|
}
|
|
1935
2180
|
);
|
|
1936
2181
|
await ctx.client.mutation(
|
|
1937
|
-
|
|
2182
|
+
anyApi4.functions.pipelineRuns.reportDaemonEvent,
|
|
1938
2183
|
{
|
|
1939
2184
|
pipelineRunId,
|
|
1940
2185
|
event: "branch_created",
|
|
@@ -1961,7 +2206,7 @@ function registerEventTool(server, ctx) {
|
|
|
1961
2206
|
async (args) => {
|
|
1962
2207
|
try {
|
|
1963
2208
|
await ctx.client.mutation(
|
|
1964
|
-
|
|
2209
|
+
anyApi4.functions.pipelineRuns.reportDaemonEvent,
|
|
1965
2210
|
{
|
|
1966
2211
|
pipelineRunId: args.pipelineRunId,
|
|
1967
2212
|
event: args.event,
|
|
@@ -2053,8 +2298,8 @@ async function createPullRequest(title, body, branch, base, repoFullName, cwd) {
|
|
|
2053
2298
|
}
|
|
2054
2299
|
|
|
2055
2300
|
// src/mcp/tools/ship.ts
|
|
2056
|
-
import { join as
|
|
2057
|
-
import { existsSync as
|
|
2301
|
+
import { join as join9 } from "path";
|
|
2302
|
+
import { existsSync as existsSync8, readFileSync as readFileSync6 } from "fs";
|
|
2058
2303
|
function buildCommitMessage(message, template, finding, allLinearIds) {
|
|
2059
2304
|
if (message) return message;
|
|
2060
2305
|
if (template) {
|
|
@@ -2105,9 +2350,9 @@ function registerShipTool(server, ctx) {
|
|
|
2105
2350
|
let isBundle = false;
|
|
2106
2351
|
let bundleTitle;
|
|
2107
2352
|
try {
|
|
2108
|
-
const briefPath =
|
|
2109
|
-
if (
|
|
2110
|
-
const brief =
|
|
2353
|
+
const briefPath = join9(gitCwd, ".yapout", "brief.md");
|
|
2354
|
+
if (existsSync8(briefPath)) {
|
|
2355
|
+
const brief = readFileSync6(briefPath, "utf-8");
|
|
2111
2356
|
const bundleMatch = brief.match(/^# Bundle: (.+)$/m);
|
|
2112
2357
|
if (bundleMatch) {
|
|
2113
2358
|
isBundle = true;
|
|
@@ -2218,7 +2463,7 @@ function registerShipTool(server, ctx) {
|
|
|
2218
2463
|
}
|
|
2219
2464
|
try {
|
|
2220
2465
|
await ctx.client.mutation(
|
|
2221
|
-
|
|
2466
|
+
anyApi4.functions.pipelineRuns.reportDaemonEvent,
|
|
2222
2467
|
{
|
|
2223
2468
|
pipelineRunId: args.pipelineRunId,
|
|
2224
2469
|
event: "push_completed",
|
|
@@ -2227,7 +2472,7 @@ function registerShipTool(server, ctx) {
|
|
|
2227
2472
|
);
|
|
2228
2473
|
if (prUrl) {
|
|
2229
2474
|
await ctx.client.mutation(
|
|
2230
|
-
|
|
2475
|
+
anyApi4.functions.pipelineRuns.reportDaemonEvent,
|
|
2231
2476
|
{
|
|
2232
2477
|
pipelineRunId: args.pipelineRunId,
|
|
2233
2478
|
event: "pr_opened",
|
|
@@ -2239,7 +2484,7 @@ function registerShipTool(server, ctx) {
|
|
|
2239
2484
|
}
|
|
2240
2485
|
try {
|
|
2241
2486
|
await ctx.client.mutation(
|
|
2242
|
-
|
|
2487
|
+
anyApi4.functions.pipelineRuns.completePipelineLocal,
|
|
2243
2488
|
{
|
|
2244
2489
|
pipelineRunId: args.pipelineRunId,
|
|
2245
2490
|
githubPrNumber: prNumber,
|
|
@@ -2255,7 +2500,7 @@ function registerShipTool(server, ctx) {
|
|
|
2255
2500
|
if (ctx.projectId) {
|
|
2256
2501
|
try {
|
|
2257
2502
|
await ctx.client.action(
|
|
2258
|
-
|
|
2503
|
+
anyApi4.functions.linearStatusMutations.moveIssueStatus,
|
|
2259
2504
|
{
|
|
2260
2505
|
projectId: ctx.projectId,
|
|
2261
2506
|
linearIssueId: linearId,
|
|
@@ -2267,7 +2512,7 @@ function registerShipTool(server, ctx) {
|
|
|
2267
2512
|
if (prUrl) {
|
|
2268
2513
|
try {
|
|
2269
2514
|
await ctx.client.action(
|
|
2270
|
-
|
|
2515
|
+
anyApi4.functions.linearStatusMutations.addLinearComment,
|
|
2271
2516
|
{
|
|
2272
2517
|
projectId: ctx.projectId,
|
|
2273
2518
|
linearIssueId: linearId,
|
|
@@ -2285,7 +2530,7 @@ function registerShipTool(server, ctx) {
|
|
|
2285
2530
|
result.worktreeCleaned = true;
|
|
2286
2531
|
try {
|
|
2287
2532
|
await ctx.client.mutation(
|
|
2288
|
-
|
|
2533
|
+
anyApi4.functions.pipelineRuns.reportDaemonEvent,
|
|
2289
2534
|
{
|
|
2290
2535
|
pipelineRunId: args.pipelineRunId,
|
|
2291
2536
|
event: "worktree_cleaned",
|
|
@@ -2376,7 +2621,7 @@ function registerCheckTool(server, ctx) {
|
|
|
2376
2621
|
if (args.pipelineRunId) {
|
|
2377
2622
|
try {
|
|
2378
2623
|
await ctx.client.mutation(
|
|
2379
|
-
|
|
2624
|
+
anyApi4.functions.pipelineRuns.reportDaemonEvent,
|
|
2380
2625
|
{
|
|
2381
2626
|
pipelineRunId: args.pipelineRunId,
|
|
2382
2627
|
event: "running_check",
|
|
@@ -2396,7 +2641,7 @@ function registerCheckTool(server, ctx) {
|
|
|
2396
2641
|
if (args.pipelineRunId) {
|
|
2397
2642
|
try {
|
|
2398
2643
|
await ctx.client.mutation(
|
|
2399
|
-
|
|
2644
|
+
anyApi4.functions.pipelineRuns.reportDaemonEvent,
|
|
2400
2645
|
{
|
|
2401
2646
|
pipelineRunId: args.pipelineRunId,
|
|
2402
2647
|
event: passed ? "check_passed" : "check_failed",
|
|
@@ -2428,8 +2673,8 @@ function registerCheckTool(server, ctx) {
|
|
|
2428
2673
|
|
|
2429
2674
|
// src/mcp/tools/bundle.ts
|
|
2430
2675
|
import { z as z9 } from "zod";
|
|
2431
|
-
import { join as
|
|
2432
|
-
import { existsSync as
|
|
2676
|
+
import { join as join10 } from "path";
|
|
2677
|
+
import { existsSync as existsSync9, writeFileSync as writeFileSync9, mkdirSync as mkdirSync7 } from "fs";
|
|
2433
2678
|
function registerBundleTool(server, ctx) {
|
|
2434
2679
|
server.tool(
|
|
2435
2680
|
"yapout_bundle",
|
|
@@ -2440,11 +2685,11 @@ function registerBundleTool(server, ctx) {
|
|
|
2440
2685
|
},
|
|
2441
2686
|
async (args) => {
|
|
2442
2687
|
const result = await ctx.client.mutation(
|
|
2443
|
-
|
|
2688
|
+
anyApi4.functions.bundles.createBundle,
|
|
2444
2689
|
{ leadFindingId: args.withFinding, joiningFindingId: args.findingId }
|
|
2445
2690
|
);
|
|
2446
2691
|
const bundledBrief = await ctx.client.query(
|
|
2447
|
-
|
|
2692
|
+
anyApi4.functions.bundles.getBundledBrief,
|
|
2448
2693
|
{ bundleId: result.bundleId }
|
|
2449
2694
|
);
|
|
2450
2695
|
if (!bundledBrief) {
|
|
@@ -2482,9 +2727,9 @@ function registerBundleTool(server, ctx) {
|
|
|
2482
2727
|
sections.push("## Project Context", "", bundledBrief.projectContext);
|
|
2483
2728
|
}
|
|
2484
2729
|
const combinedBrief = sections.join("\n");
|
|
2485
|
-
const yapoutDir =
|
|
2486
|
-
if (!
|
|
2487
|
-
|
|
2730
|
+
const yapoutDir = join10(ctx.cwd, ".yapout");
|
|
2731
|
+
if (!existsSync9(yapoutDir)) mkdirSync7(yapoutDir, { recursive: true });
|
|
2732
|
+
writeFileSync9(join10(yapoutDir, "brief.md"), combinedBrief);
|
|
2488
2733
|
return {
|
|
2489
2734
|
content: [
|
|
2490
2735
|
{
|
|
@@ -2541,7 +2786,7 @@ After calling this tool, you should:
|
|
|
2541
2786
|
}
|
|
2542
2787
|
try {
|
|
2543
2788
|
const result = await ctx.client.mutation(
|
|
2544
|
-
|
|
2789
|
+
anyApi4.functions.localPipeline.claimForEnrichment,
|
|
2545
2790
|
{
|
|
2546
2791
|
projectId,
|
|
2547
2792
|
...args.findingId ? { findingId: args.findingId } : {}
|
|
@@ -2601,7 +2846,7 @@ function registerGetExistingFindingsTool(server, ctx) {
|
|
|
2601
2846
|
}
|
|
2602
2847
|
try {
|
|
2603
2848
|
const findings = await ctx.client.query(
|
|
2604
|
-
|
|
2849
|
+
anyApi4.functions.localPipeline.getExistingFindingTitles,
|
|
2605
2850
|
{ projectId }
|
|
2606
2851
|
);
|
|
2607
2852
|
if (!findings || findings.length === 0) {
|
|
@@ -2697,12 +2942,13 @@ The finding transitions: enriching \u2192 enriched \u2192 ready.`,
|
|
|
2697
2942
|
isOversized: z11.boolean().optional().describe("Set to true if this finding is too large for a single PR"),
|
|
2698
2943
|
suggestedSplit: z11.array(z11.string()).optional().describe("If oversized: suggested sub-finding titles for breaking it down"),
|
|
2699
2944
|
nature: z11.enum(["implementable", "operational", "spike"]).optional().describe("Override the finding's nature if enrichment reveals it should be reclassified"),
|
|
2945
|
+
cloudSafe: z11.boolean().optional().describe("Set to true ONLY if this is a small mechanical change a cloud agent can ship without sandbox testing \u2014 text/copy edits, classname tweaks, single-named-constant changes, \u226430 lines, single file, no logic/type/dependency changes. Default false."),
|
|
2700
2946
|
sessionId: z11.string().optional().describe("Bulk enrichment session ID (from yapout_start_enrichment). Updates session stats.")
|
|
2701
2947
|
},
|
|
2702
2948
|
async (args) => {
|
|
2703
2949
|
try {
|
|
2704
2950
|
await ctx.client.mutation(
|
|
2705
|
-
|
|
2951
|
+
anyApi4.functions.localPipeline.saveLocalEnrichment,
|
|
2706
2952
|
{
|
|
2707
2953
|
findingId: args.findingId,
|
|
2708
2954
|
title: args.title,
|
|
@@ -2712,15 +2958,16 @@ The finding transitions: enriching \u2192 enriched \u2192 ready.`,
|
|
|
2712
2958
|
clarifications: args.clarifications,
|
|
2713
2959
|
isOversized: args.isOversized,
|
|
2714
2960
|
suggestedSplit: args.suggestedSplit,
|
|
2715
|
-
nature: args.nature
|
|
2961
|
+
nature: args.nature,
|
|
2962
|
+
cloudSafe: args.cloudSafe
|
|
2716
2963
|
}
|
|
2717
2964
|
);
|
|
2718
2965
|
await ctx.client.action(
|
|
2719
|
-
|
|
2966
|
+
anyApi4.functions.localPipeline.syncFindingToLinearLocal,
|
|
2720
2967
|
{ findingId: args.findingId }
|
|
2721
2968
|
);
|
|
2722
2969
|
const finding = await ctx.client.query(
|
|
2723
|
-
|
|
2970
|
+
anyApi4.functions.findings.getFinding,
|
|
2724
2971
|
{ findingId: args.findingId }
|
|
2725
2972
|
);
|
|
2726
2973
|
if (args.sessionId) {
|
|
@@ -2779,7 +3026,7 @@ function registerSyncToLinearTool(server, ctx) {
|
|
|
2779
3026
|
async (args) => {
|
|
2780
3027
|
try {
|
|
2781
3028
|
await ctx.client.action(
|
|
2782
|
-
|
|
3029
|
+
anyApi4.functions.localPipeline.syncFindingToLinearLocal,
|
|
2783
3030
|
{ findingId: args.findingId }
|
|
2784
3031
|
);
|
|
2785
3032
|
return {
|
|
@@ -2837,7 +3084,7 @@ function registerSubmitYapSessionTool(server, ctx) {
|
|
|
2837
3084
|
}
|
|
2838
3085
|
try {
|
|
2839
3086
|
const captureId = await ctx.client.mutation(
|
|
2840
|
-
|
|
3087
|
+
anyApi4.functions.captures.createFromYapSession,
|
|
2841
3088
|
{
|
|
2842
3089
|
projectId: ctx.projectId,
|
|
2843
3090
|
title: args.title,
|
|
@@ -3113,7 +3360,7 @@ function registerExtractFromYapTool(server, ctx) {
|
|
|
3113
3360
|
}
|
|
3114
3361
|
try {
|
|
3115
3362
|
const result = await ctx.client.mutation(
|
|
3116
|
-
|
|
3363
|
+
anyApi4.functions.captures.extractFromYapSession,
|
|
3117
3364
|
{
|
|
3118
3365
|
projectId: ctx.projectId,
|
|
3119
3366
|
title: args.sessionTitle,
|
|
@@ -3225,7 +3472,7 @@ full spec \u2014 schema changes, files to modify, edge cases, and acceptance cri
|
|
|
3225
3472
|
}
|
|
3226
3473
|
try {
|
|
3227
3474
|
const result = await ctx.client.mutation(
|
|
3228
|
-
|
|
3475
|
+
anyApi4.functions.localPipeline.decomposeIntoBundle,
|
|
3229
3476
|
{
|
|
3230
3477
|
findingId: args.findingId,
|
|
3231
3478
|
bundleDescription: args.bundleDescription,
|
|
@@ -3298,7 +3545,7 @@ The finding must be in "enriching" or "enriched" status. It will be transitioned
|
|
|
3298
3545
|
}
|
|
3299
3546
|
try {
|
|
3300
3547
|
const result = await ctx.client.mutation(
|
|
3301
|
-
|
|
3548
|
+
anyApi4.functions.findings.markDuplicate,
|
|
3302
3549
|
{
|
|
3303
3550
|
findingId: args.findingId,
|
|
3304
3551
|
duplicateOfLinearId: args.duplicateOfLinearId,
|
|
@@ -3361,7 +3608,7 @@ and issue counts so you can ask the user for confirmation before creating a new
|
|
|
3361
3608
|
}
|
|
3362
3609
|
try {
|
|
3363
3610
|
const project = await ctx.client.query(
|
|
3364
|
-
|
|
3611
|
+
anyApi4.functions.projects.getProject,
|
|
3365
3612
|
{ projectId: ctx.projectId }
|
|
3366
3613
|
);
|
|
3367
3614
|
if (!project?.linearTeamId) {
|
|
@@ -3376,7 +3623,7 @@ and issue counts so you can ask the user for confirmation before creating a new
|
|
|
3376
3623
|
};
|
|
3377
3624
|
}
|
|
3378
3625
|
const projects = await ctx.client.action(
|
|
3379
|
-
|
|
3626
|
+
anyApi4.functions.linearProjectsMutations.fetchProjectsDetailed,
|
|
3380
3627
|
{ projectId: ctx.projectId, teamId: project.linearTeamId }
|
|
3381
3628
|
);
|
|
3382
3629
|
return {
|
|
@@ -3438,7 +3685,7 @@ Optionally filter by tags, capture, or explicit finding IDs.`,
|
|
|
3438
3685
|
}
|
|
3439
3686
|
try {
|
|
3440
3687
|
const allFindings = await ctx.client.query(
|
|
3441
|
-
|
|
3688
|
+
anyApi4.functions.findings.getProjectFindings,
|
|
3442
3689
|
{ projectId }
|
|
3443
3690
|
);
|
|
3444
3691
|
let drafts = (allFindings ?? []).filter((f) => f.status === "draft");
|
|
@@ -3508,7 +3755,7 @@ When done=true, all findings have been processed.`,
|
|
|
3508
3755
|
if (args.skip && args.skipFindingId) {
|
|
3509
3756
|
try {
|
|
3510
3757
|
await ctx.client.mutation(
|
|
3511
|
-
|
|
3758
|
+
anyApi4.functions.localPipeline.releaseEnrichmentClaim,
|
|
3512
3759
|
{ findingId: args.skipFindingId }
|
|
3513
3760
|
);
|
|
3514
3761
|
updateSessionStats(args.sessionId, {
|
|
@@ -3518,7 +3765,7 @@ When done=true, all findings have been processed.`,
|
|
|
3518
3765
|
}
|
|
3519
3766
|
}
|
|
3520
3767
|
const allFindings = await ctx.client.query(
|
|
3521
|
-
|
|
3768
|
+
anyApi4.functions.findings.getProjectFindings,
|
|
3522
3769
|
{ projectId: session.projectId }
|
|
3523
3770
|
);
|
|
3524
3771
|
let drafts = (allFindings ?? []).filter(
|
|
@@ -3560,7 +3807,7 @@ When done=true, all findings have been processed.`,
|
|
|
3560
3807
|
}
|
|
3561
3808
|
const next = drafts[0];
|
|
3562
3809
|
const claimResult = await ctx.client.mutation(
|
|
3563
|
-
|
|
3810
|
+
anyApi4.functions.localPipeline.claimForEnrichment,
|
|
3564
3811
|
{ projectId: session.projectId, findingId: next._id }
|
|
3565
3812
|
);
|
|
3566
3813
|
if (!claimResult) {
|
|
@@ -3641,7 +3888,7 @@ The bundle and all its findings transition to "enriching" status.`,
|
|
|
3641
3888
|
async (args) => {
|
|
3642
3889
|
try {
|
|
3643
3890
|
const result = await ctx.client.mutation(
|
|
3644
|
-
|
|
3891
|
+
anyApi4.functions.bundles.claimBundleForEnrichment,
|
|
3645
3892
|
{ bundleId: args.bundleId }
|
|
3646
3893
|
);
|
|
3647
3894
|
if (!result) {
|
|
@@ -3688,6 +3935,7 @@ Call yapout_sync_bundle_to_linear afterwards to create the Linear project.`,
|
|
|
3688
3935
|
enrichedDescription: z21.string().describe("Bundle-level description \u2014 the cohesive story of what this bundle delivers"),
|
|
3689
3936
|
acceptanceCriteria: z21.array(z21.string()).describe("Bundle-level acceptance criteria"),
|
|
3690
3937
|
implementationBrief: z21.string().describe("Bundle-level implementation brief \u2014 overall approach, architecture decisions, key files"),
|
|
3938
|
+
cloudSafe: z21.boolean().optional().describe("Set true ONLY if the entire bundle is small mechanical work (\u226430 lines total, single-file ideally, text/className/constant edits). Default false. If unsure, false."),
|
|
3691
3939
|
findings: z21.array(z21.object({
|
|
3692
3940
|
findingId: z21.string().describe("Finding ID"),
|
|
3693
3941
|
title: z21.string().describe("Refined finding title"),
|
|
@@ -3699,13 +3947,14 @@ Call yapout_sync_bundle_to_linear afterwards to create the Linear project.`,
|
|
|
3699
3947
|
async (args) => {
|
|
3700
3948
|
try {
|
|
3701
3949
|
await ctx.client.mutation(
|
|
3702
|
-
|
|
3950
|
+
anyApi4.functions.bundles.saveBundleEnrichment,
|
|
3703
3951
|
{
|
|
3704
3952
|
bundleId: args.bundleId,
|
|
3705
3953
|
title: args.title,
|
|
3706
3954
|
enrichedDescription: args.enrichedDescription,
|
|
3707
3955
|
acceptanceCriteria: args.acceptanceCriteria,
|
|
3708
3956
|
implementationBrief: args.implementationBrief,
|
|
3957
|
+
cloudSafe: args.cloudSafe,
|
|
3709
3958
|
findings: args.findings.map((f) => ({
|
|
3710
3959
|
findingId: f.findingId,
|
|
3711
3960
|
title: f.title,
|
|
@@ -3737,6 +3986,113 @@ Call yapout_sync_bundle_to_linear afterwards to create the Linear project.`,
|
|
|
3737
3986
|
);
|
|
3738
3987
|
}
|
|
3739
3988
|
|
|
3989
|
+
// src/mcp/tools/block-enrichment.ts
|
|
3990
|
+
import { z as z22 } from "zod";
|
|
3991
|
+
function registerBlockEnrichmentTool(server, ctx) {
|
|
3992
|
+
server.tool(
|
|
3993
|
+
"yapout_block_enrichment",
|
|
3994
|
+
`Refuse to enrich a finding because the user has not provided enough specifics for an autonomous agent to complete the work end-to-end.
|
|
3995
|
+
|
|
3996
|
+
Use this when the finding lacks any of: target file/component, intended behavior, scope boundaries, or success criteria. Do NOT produce a half-baked enrichment "for review" \u2014 block instead. The bar for "enriched" is "another agent can ship this with zero further input."
|
|
3997
|
+
|
|
3998
|
+
The finding must currently be in "enriching" status (claimed via yapout_get_unenriched_finding).
|
|
3999
|
+
|
|
4000
|
+
Transitions: enriching \u2192 needs_input. The user sees the blockerReason and questions in the UI, adds context, and resubmits.`,
|
|
4001
|
+
{
|
|
4002
|
+
findingId: z22.string().describe("The finding ID to block (currently in 'enriching')"),
|
|
4003
|
+
blockerReason: z22.string().describe("One sentence: why this finding cannot be enriched as written. State the missing piece concretely."),
|
|
4004
|
+
blockerQuestions: z22.array(z22.string()).min(1).describe("2-5 specific questions the user must answer before enrichment can succeed. Each must be answerable in a sentence or two.")
|
|
4005
|
+
},
|
|
4006
|
+
async (args) => {
|
|
4007
|
+
try {
|
|
4008
|
+
await ctx.client.mutation(
|
|
4009
|
+
anyApi4.functions.localPipeline.blockLocalEnrichment,
|
|
4010
|
+
{
|
|
4011
|
+
findingId: args.findingId,
|
|
4012
|
+
blockerReason: args.blockerReason,
|
|
4013
|
+
blockerQuestions: args.blockerQuestions
|
|
4014
|
+
}
|
|
4015
|
+
);
|
|
4016
|
+
return {
|
|
4017
|
+
content: [
|
|
4018
|
+
{
|
|
4019
|
+
type: "text",
|
|
4020
|
+
text: JSON.stringify(
|
|
4021
|
+
{
|
|
4022
|
+
findingId: args.findingId,
|
|
4023
|
+
status: "needs_input",
|
|
4024
|
+
message: "Enrichment blocked. The user will see your questions and resubmit with more context."
|
|
4025
|
+
},
|
|
4026
|
+
null,
|
|
4027
|
+
2
|
|
4028
|
+
)
|
|
4029
|
+
}
|
|
4030
|
+
]
|
|
4031
|
+
};
|
|
4032
|
+
} catch (err) {
|
|
4033
|
+
return {
|
|
4034
|
+
content: [
|
|
4035
|
+
{
|
|
4036
|
+
type: "text",
|
|
4037
|
+
text: `Error blocking enrichment: ${err.message}`
|
|
4038
|
+
}
|
|
4039
|
+
],
|
|
4040
|
+
isError: true
|
|
4041
|
+
};
|
|
4042
|
+
}
|
|
4043
|
+
}
|
|
4044
|
+
);
|
|
4045
|
+
server.tool(
|
|
4046
|
+
"yapout_block_bundle_enrichment",
|
|
4047
|
+
`Refuse to enrich a bundle because the user has not provided enough specifics for an autonomous agent to ship it end-to-end.
|
|
4048
|
+
|
|
4049
|
+
Same semantics as yapout_block_enrichment but for an entire bundle. Bundle status transitions enriching \u2192 needs_input; child findings revert to draft.`,
|
|
4050
|
+
{
|
|
4051
|
+
bundleId: z22.string().describe("The bundle ID to block (currently in 'enriching')"),
|
|
4052
|
+
blockerReason: z22.string().describe("One sentence: why this bundle cannot be enriched."),
|
|
4053
|
+
blockerQuestions: z22.array(z22.string()).min(1).describe("2-5 specific questions the user must answer.")
|
|
4054
|
+
},
|
|
4055
|
+
async (args) => {
|
|
4056
|
+
try {
|
|
4057
|
+
await ctx.client.mutation(
|
|
4058
|
+
anyApi4.functions.bundles.blockBundleEnrichment,
|
|
4059
|
+
{
|
|
4060
|
+
bundleId: args.bundleId,
|
|
4061
|
+
blockerReason: args.blockerReason,
|
|
4062
|
+
blockerQuestions: args.blockerQuestions
|
|
4063
|
+
}
|
|
4064
|
+
);
|
|
4065
|
+
return {
|
|
4066
|
+
content: [
|
|
4067
|
+
{
|
|
4068
|
+
type: "text",
|
|
4069
|
+
text: JSON.stringify(
|
|
4070
|
+
{
|
|
4071
|
+
bundleId: args.bundleId,
|
|
4072
|
+
status: "needs_input",
|
|
4073
|
+
message: "Bundle enrichment blocked. The user will see your questions and resubmit."
|
|
4074
|
+
},
|
|
4075
|
+
null,
|
|
4076
|
+
2
|
|
4077
|
+
)
|
|
4078
|
+
}
|
|
4079
|
+
]
|
|
4080
|
+
};
|
|
4081
|
+
} catch (err) {
|
|
4082
|
+
return {
|
|
4083
|
+
content: [
|
|
4084
|
+
{
|
|
4085
|
+
type: "text",
|
|
4086
|
+
text: `Error blocking bundle enrichment: ${err.message}`
|
|
4087
|
+
}
|
|
4088
|
+
],
|
|
4089
|
+
isError: true
|
|
4090
|
+
};
|
|
4091
|
+
}
|
|
4092
|
+
}
|
|
4093
|
+
);
|
|
4094
|
+
}
|
|
4095
|
+
|
|
3740
4096
|
// src/mcp/server.ts
|
|
3741
4097
|
async function startMcpServer() {
|
|
3742
4098
|
const cwd = process.cwd();
|
|
@@ -3767,7 +4123,7 @@ async function startMcpServer() {
|
|
|
3767
4123
|
};
|
|
3768
4124
|
const server = new McpServer({
|
|
3769
4125
|
name: "yapout",
|
|
3770
|
-
version: "0.
|
|
4126
|
+
version: "0.9.0"
|
|
3771
4127
|
});
|
|
3772
4128
|
registerInitTool(server, ctx);
|
|
3773
4129
|
registerCompactTool(server, ctx);
|
|
@@ -3793,45 +4149,46 @@ async function startMcpServer() {
|
|
|
3793
4149
|
registerEnrichNextTool(server, ctx);
|
|
3794
4150
|
registerEnrichBundleTool(server, ctx);
|
|
3795
4151
|
registerSaveBundleEnrichmentTool(server, ctx);
|
|
4152
|
+
registerBlockEnrichmentTool(server, ctx);
|
|
3796
4153
|
const transport = new StdioServerTransport();
|
|
3797
4154
|
await server.connect(transport);
|
|
3798
4155
|
}
|
|
3799
4156
|
|
|
3800
4157
|
// src/commands/mcp-server.ts
|
|
3801
|
-
var mcpServerCommand = new
|
|
4158
|
+
var mcpServerCommand = new Command8("mcp-server").description("Start the MCP server (used by Claude Code)").action(async () => {
|
|
3802
4159
|
await startMcpServer();
|
|
3803
4160
|
});
|
|
3804
4161
|
|
|
3805
4162
|
// src/commands/worktrees.ts
|
|
3806
|
-
import { Command as
|
|
3807
|
-
import
|
|
4163
|
+
import { Command as Command9 } from "commander";
|
|
4164
|
+
import chalk9 from "chalk";
|
|
3808
4165
|
import { resolve as resolve6 } from "path";
|
|
3809
|
-
var worktreesCommand = new
|
|
4166
|
+
var worktreesCommand = new Command9("worktrees").description("List active yapout worktrees").action(() => {
|
|
3810
4167
|
const cwd = resolve6(process.cwd());
|
|
3811
4168
|
const worktrees = listWorktrees(cwd);
|
|
3812
4169
|
if (worktrees.length === 0) {
|
|
3813
|
-
console.log(
|
|
4170
|
+
console.log(chalk9.dim("No active yapout worktrees."));
|
|
3814
4171
|
return;
|
|
3815
4172
|
}
|
|
3816
|
-
console.log(
|
|
4173
|
+
console.log(chalk9.bold("Active worktrees:\n"));
|
|
3817
4174
|
for (const wt of worktrees) {
|
|
3818
4175
|
console.log(
|
|
3819
|
-
` ${
|
|
4176
|
+
` ${chalk9.cyan(wt.path)} ${chalk9.green(wt.branch)}` + (wt.ticketId ? ` ${chalk9.dim(`(${wt.ticketId})`)}` : "")
|
|
3820
4177
|
);
|
|
3821
4178
|
}
|
|
3822
4179
|
console.log();
|
|
3823
4180
|
});
|
|
3824
4181
|
|
|
3825
4182
|
// src/commands/clean.ts
|
|
3826
|
-
import { Command as
|
|
3827
|
-
import
|
|
4183
|
+
import { Command as Command10 } from "commander";
|
|
4184
|
+
import chalk10 from "chalk";
|
|
3828
4185
|
import { resolve as resolve7 } from "path";
|
|
3829
|
-
var cleanCommand = new
|
|
4186
|
+
var cleanCommand = new Command10("clean").description("Remove worktrees for completed or failed tickets").action(async () => {
|
|
3830
4187
|
const creds = requireAuth();
|
|
3831
4188
|
const cwd = resolve7(process.cwd());
|
|
3832
4189
|
const worktrees = listWorktrees(cwd);
|
|
3833
4190
|
if (worktrees.length === 0) {
|
|
3834
|
-
console.log(
|
|
4191
|
+
console.log(chalk10.dim("No worktrees to clean."));
|
|
3835
4192
|
return;
|
|
3836
4193
|
}
|
|
3837
4194
|
const client = createConvexClient(creds.token);
|
|
@@ -3847,7 +4204,7 @@ var cleanCommand = new Command9("clean").description("Remove worktrees for compl
|
|
|
3847
4204
|
if (isStale) {
|
|
3848
4205
|
const label = ticket ? `${ticket.status}: ${ticket.title}` : "ticket not found";
|
|
3849
4206
|
console.log(
|
|
3850
|
-
|
|
4207
|
+
chalk10.dim(`Removing worktree for ${wt.ticketId} (${label})...`)
|
|
3851
4208
|
);
|
|
3852
4209
|
removeWorktree(cwd, wt.path);
|
|
3853
4210
|
cleaned++;
|
|
@@ -3856,10 +4213,10 @@ var cleanCommand = new Command9("clean").description("Remove worktrees for compl
|
|
|
3856
4213
|
}
|
|
3857
4214
|
}
|
|
3858
4215
|
if (cleaned === 0) {
|
|
3859
|
-
console.log(
|
|
4216
|
+
console.log(chalk10.dim("All worktrees are still active."));
|
|
3860
4217
|
} else {
|
|
3861
4218
|
console.log(
|
|
3862
|
-
|
|
4219
|
+
chalk10.green(
|
|
3863
4220
|
`Cleaned ${cleaned} worktree${cleaned === 1 ? "" : "s"}.`
|
|
3864
4221
|
)
|
|
3865
4222
|
);
|
|
@@ -3867,27 +4224,27 @@ var cleanCommand = new Command9("clean").description("Remove worktrees for compl
|
|
|
3867
4224
|
});
|
|
3868
4225
|
|
|
3869
4226
|
// src/commands/watch.ts
|
|
3870
|
-
import { Command as
|
|
4227
|
+
import { Command as Command11 } from "commander";
|
|
3871
4228
|
import { resolve as resolve8 } from "path";
|
|
3872
4229
|
import {
|
|
3873
|
-
readFileSync as
|
|
3874
|
-
writeFileSync as
|
|
3875
|
-
existsSync as
|
|
3876
|
-
unlinkSync as
|
|
4230
|
+
readFileSync as readFileSync7,
|
|
4231
|
+
writeFileSync as writeFileSync10,
|
|
4232
|
+
existsSync as existsSync11,
|
|
4233
|
+
unlinkSync as unlinkSync4
|
|
3877
4234
|
} from "fs";
|
|
3878
|
-
import { join as
|
|
3879
|
-
import
|
|
4235
|
+
import { join as join12 } from "path";
|
|
4236
|
+
import chalk12 from "chalk";
|
|
3880
4237
|
import { ConvexHttpClient as ConvexHttpClient3 } from "convex/browser";
|
|
3881
4238
|
|
|
3882
4239
|
// src/daemon/watcher.ts
|
|
3883
|
-
import { anyApi as
|
|
4240
|
+
import { anyApi as anyApi6 } from "convex/server";
|
|
3884
4241
|
import { execSync as execSync4 } from "child_process";
|
|
3885
|
-
import { existsSync as
|
|
3886
|
-
import { join as
|
|
4242
|
+
import { existsSync as existsSync10, mkdirSync as mkdirSync8 } from "fs";
|
|
4243
|
+
import { join as join11 } from "path";
|
|
3887
4244
|
import { hostname as osHostname } from "os";
|
|
3888
4245
|
|
|
3889
4246
|
// src/daemon/heartbeat.ts
|
|
3890
|
-
import { anyApi as
|
|
4247
|
+
import { anyApi as anyApi5 } from "convex/server";
|
|
3891
4248
|
var HEARTBEAT_INTERVAL = 3e4;
|
|
3892
4249
|
var Heartbeat = class {
|
|
3893
4250
|
client;
|
|
@@ -3915,14 +4272,14 @@ var Heartbeat = class {
|
|
|
3915
4272
|
if (currentTicketId) args.currentTicketId = currentTicketId;
|
|
3916
4273
|
if (currentBranch) args.currentBranch = currentBranch;
|
|
3917
4274
|
if (worktreePath) args.worktreePath = worktreePath;
|
|
3918
|
-
await this.client.mutation(
|
|
4275
|
+
await this.client.mutation(anyApi5.functions.agents.agentHeartbeat, args);
|
|
3919
4276
|
} catch {
|
|
3920
4277
|
}
|
|
3921
4278
|
}
|
|
3922
4279
|
};
|
|
3923
4280
|
|
|
3924
4281
|
// src/daemon/spawner.ts
|
|
3925
|
-
import { spawn } from "child_process";
|
|
4282
|
+
import { spawn as spawn2 } from "child_process";
|
|
3926
4283
|
|
|
3927
4284
|
// src/daemon/notifications.ts
|
|
3928
4285
|
var notifier = null;
|
|
@@ -4011,7 +4368,7 @@ var Spawner = class {
|
|
|
4011
4368
|
...process.env,
|
|
4012
4369
|
YAPOUT_PROJECT_ID: this.projectId
|
|
4013
4370
|
};
|
|
4014
|
-
const child =
|
|
4371
|
+
const child = spawn2("claude", ["--dangerously-skip-permissions", prompt], {
|
|
4015
4372
|
cwd: worktreePath,
|
|
4016
4373
|
stdio: ["pipe", "pipe", "pipe"],
|
|
4017
4374
|
env,
|
|
@@ -4123,7 +4480,7 @@ var Spawner = class {
|
|
|
4123
4480
|
};
|
|
4124
4481
|
|
|
4125
4482
|
// src/daemon/display.ts
|
|
4126
|
-
import
|
|
4483
|
+
import chalk11 from "chalk";
|
|
4127
4484
|
function formatElapsed(ms) {
|
|
4128
4485
|
const seconds = Math.floor(ms / 1e3);
|
|
4129
4486
|
if (seconds < 60) return `${seconds}s`;
|
|
@@ -4152,35 +4509,35 @@ function render(state) {
|
|
|
4152
4509
|
if (queuedCount > 0) statusParts.push(`${queuedCount} queued`);
|
|
4153
4510
|
const statusLine = statusParts.length > 0 ? statusParts.join(" | ") : "idle";
|
|
4154
4511
|
lines.push(
|
|
4155
|
-
`${
|
|
4512
|
+
`${chalk11.bold("yapout watch")} \u2014 ${state.projectName}`
|
|
4156
4513
|
);
|
|
4157
|
-
lines.push(`${
|
|
4514
|
+
lines.push(`${chalk11.green("Watching")} | ${statusLine}`);
|
|
4158
4515
|
lines.push("");
|
|
4159
4516
|
for (const agent of state.activeAgents) {
|
|
4160
4517
|
const elapsed = formatElapsed(now - agent.startedAt);
|
|
4161
|
-
const wt = agent.worktree ?
|
|
4518
|
+
const wt = agent.worktree ? chalk11.dim(`worktree/${agent.ticketRef.toLowerCase()}`) : "";
|
|
4162
4519
|
lines.push(
|
|
4163
|
-
` ${
|
|
4520
|
+
` ${chalk11.green("\u25CF")} ${chalk11.bold(agent.ticketRef)} ${chalk11.dim(truncate2(`"${agent.title}"`, 35))} ${chalk11.yellow(agent.phase)} ${wt} ${chalk11.dim(elapsed)}`
|
|
4164
4521
|
);
|
|
4165
4522
|
}
|
|
4166
4523
|
for (const ticket of state.queuedTickets) {
|
|
4167
4524
|
lines.push(
|
|
4168
|
-
` ${
|
|
4525
|
+
` ${chalk11.dim("\u25CB")} ${chalk11.bold(ticket.ticketRef)} ${chalk11.dim(truncate2(`"${ticket.title}"`, 35))} ${chalk11.dim("queued")}`
|
|
4169
4526
|
);
|
|
4170
4527
|
}
|
|
4171
4528
|
if (state.activeAgents.length > 0 || state.queuedTickets.length > 0) {
|
|
4172
4529
|
lines.push("");
|
|
4173
4530
|
}
|
|
4174
4531
|
if (state.recentEvents.length > 0) {
|
|
4175
|
-
lines.push(
|
|
4532
|
+
lines.push(chalk11.dim("Recent:"));
|
|
4176
4533
|
for (const event of state.recentEvents.slice(0, 8)) {
|
|
4177
4534
|
lines.push(
|
|
4178
|
-
` ${
|
|
4535
|
+
` ${chalk11.dim(formatTime(event.time))} ${event.icon} ${event.message}`
|
|
4179
4536
|
);
|
|
4180
4537
|
}
|
|
4181
4538
|
lines.push("");
|
|
4182
4539
|
}
|
|
4183
|
-
lines.push(
|
|
4540
|
+
lines.push(chalk11.dim("Ctrl+C to stop (agents will finish)"));
|
|
4184
4541
|
return lines.join("\n");
|
|
4185
4542
|
}
|
|
4186
4543
|
function clearAndRender(state) {
|
|
@@ -4222,7 +4579,7 @@ var Watcher = class {
|
|
|
4222
4579
|
);
|
|
4223
4580
|
process.exit(1);
|
|
4224
4581
|
}
|
|
4225
|
-
await this.client.mutation(
|
|
4582
|
+
await this.client.mutation(anyApi6.functions.agents.registerAgent, {
|
|
4226
4583
|
projectId: this.options.projectId,
|
|
4227
4584
|
sessionId: this.sessionId,
|
|
4228
4585
|
machineHostname: this.getHostname()
|
|
@@ -4258,7 +4615,7 @@ Waiting for ${this.spawner.activeCount} agent(s) to finish...`
|
|
|
4258
4615
|
await this.spawner.gracefulShutdown();
|
|
4259
4616
|
}
|
|
4260
4617
|
try {
|
|
4261
|
-
await this.client.mutation(
|
|
4618
|
+
await this.client.mutation(anyApi6.functions.agents.unregisterAgent, {
|
|
4262
4619
|
sessionId: this.sessionId
|
|
4263
4620
|
});
|
|
4264
4621
|
} catch {
|
|
@@ -4272,7 +4629,7 @@ Waiting for ${this.spawner.activeCount} agent(s) to finish...`
|
|
|
4272
4629
|
}
|
|
4273
4630
|
this.heartbeat.stop();
|
|
4274
4631
|
this.spawner.forceKill();
|
|
4275
|
-
this.client.mutation(
|
|
4632
|
+
this.client.mutation(anyApi6.functions.agents.unregisterAgent, {
|
|
4276
4633
|
sessionId: this.sessionId
|
|
4277
4634
|
}).catch(() => {
|
|
4278
4635
|
});
|
|
@@ -4314,7 +4671,7 @@ Waiting for ${this.spawner.activeCount} agent(s) to finish...`
|
|
|
4314
4671
|
}
|
|
4315
4672
|
async checkForEnrichmentWork(maxSlots) {
|
|
4316
4673
|
const tickets = await this.client.query(
|
|
4317
|
-
|
|
4674
|
+
anyApi6.functions.localPipeline.getUnenrichedTickets,
|
|
4318
4675
|
{ projectId: this.options.projectId }
|
|
4319
4676
|
);
|
|
4320
4677
|
if (!tickets || tickets.length === 0) return;
|
|
@@ -4343,7 +4700,7 @@ Waiting for ${this.spawner.activeCount} agent(s) to finish...`
|
|
|
4343
4700
|
}
|
|
4344
4701
|
async checkForImplementationWork(maxSlots) {
|
|
4345
4702
|
const data = await this.client.query(
|
|
4346
|
-
|
|
4703
|
+
anyApi6.functions.tickets.getLocalQueuedTickets,
|
|
4347
4704
|
{ projectId: this.options.projectId }
|
|
4348
4705
|
);
|
|
4349
4706
|
if (!data || data.ready.length === 0) return;
|
|
@@ -4375,10 +4732,10 @@ Waiting for ${this.spawner.activeCount} agent(s) to finish...`
|
|
|
4375
4732
|
}
|
|
4376
4733
|
ensureEnrichWorktree() {
|
|
4377
4734
|
const wtDir = getWorktreesDir(this.options.cwd);
|
|
4378
|
-
const enrichPath =
|
|
4379
|
-
if (
|
|
4735
|
+
const enrichPath = join11(wtDir, "_enrich");
|
|
4736
|
+
if (existsSync10(enrichPath)) return enrichPath;
|
|
4380
4737
|
try {
|
|
4381
|
-
if (!
|
|
4738
|
+
if (!existsSync10(wtDir)) mkdirSync8(wtDir, { recursive: true });
|
|
4382
4739
|
const defaultBranch = getDefaultBranch(this.options.cwd);
|
|
4383
4740
|
execSync4(
|
|
4384
4741
|
`git worktree add "${enrichPath}" origin/${defaultBranch}`,
|
|
@@ -4416,7 +4773,7 @@ Waiting for ${this.spawner.activeCount} agent(s) to finish...`
|
|
|
4416
4773
|
if (wt.ticketId) {
|
|
4417
4774
|
try {
|
|
4418
4775
|
const ticket = await this.client.query(
|
|
4419
|
-
|
|
4776
|
+
anyApi6.functions.tickets.getTicket,
|
|
4420
4777
|
{ ticketId: wt.ticketId }
|
|
4421
4778
|
);
|
|
4422
4779
|
if (ticket && (ticket.status === "failed" || ticket.status === "workflow2_done")) {
|
|
@@ -4481,38 +4838,38 @@ Waiting for ${this.spawner.activeCount} agent(s) to finish...`
|
|
|
4481
4838
|
};
|
|
4482
4839
|
|
|
4483
4840
|
// src/commands/watch.ts
|
|
4484
|
-
var
|
|
4485
|
-
var
|
|
4486
|
-
var watchCommand = new
|
|
4841
|
+
var PID_FILE2 = join12(getYapoutDir(), "watch.pid");
|
|
4842
|
+
var LOG_FILE2 = join12(getYapoutDir(), "watch.log");
|
|
4843
|
+
var watchCommand = new Command11("watch").description("Watch for work and spawn Claude Code agents").option("--bg", "Run in background (detached)").option("--stop", "Stop the background watcher").option("--status", "Check if watcher is running").option("--force", "Force kill on Ctrl+C (don't wait for agents)").action(async (opts) => {
|
|
4487
4844
|
if (opts.status) {
|
|
4488
|
-
if (
|
|
4489
|
-
const pid = parseInt(
|
|
4490
|
-
if (
|
|
4845
|
+
if (existsSync11(PID_FILE2)) {
|
|
4846
|
+
const pid = parseInt(readFileSync7(PID_FILE2, "utf-8").trim(), 10);
|
|
4847
|
+
if (isProcessRunning2(pid)) {
|
|
4491
4848
|
console.log(
|
|
4492
|
-
|
|
4849
|
+
chalk12.green("Watcher is running") + chalk12.dim(` (PID ${pid})`)
|
|
4493
4850
|
);
|
|
4494
4851
|
} else {
|
|
4495
|
-
console.log(
|
|
4496
|
-
|
|
4852
|
+
console.log(chalk12.dim("Watcher is not running (stale PID file)"));
|
|
4853
|
+
unlinkSync4(PID_FILE2);
|
|
4497
4854
|
}
|
|
4498
4855
|
} else {
|
|
4499
|
-
console.log(
|
|
4856
|
+
console.log(chalk12.dim("Watcher is not running"));
|
|
4500
4857
|
}
|
|
4501
4858
|
return;
|
|
4502
4859
|
}
|
|
4503
4860
|
if (opts.stop) {
|
|
4504
|
-
if (!
|
|
4505
|
-
console.log(
|
|
4861
|
+
if (!existsSync11(PID_FILE2)) {
|
|
4862
|
+
console.log(chalk12.dim("No watcher running"));
|
|
4506
4863
|
return;
|
|
4507
4864
|
}
|
|
4508
|
-
const pid = parseInt(
|
|
4865
|
+
const pid = parseInt(readFileSync7(PID_FILE2, "utf-8").trim(), 10);
|
|
4509
4866
|
try {
|
|
4510
4867
|
process.kill(pid, "SIGTERM");
|
|
4511
|
-
console.log(
|
|
4868
|
+
console.log(chalk12.green(`Stopped watcher (PID ${pid})`));
|
|
4512
4869
|
} catch {
|
|
4513
|
-
console.log(
|
|
4870
|
+
console.log(chalk12.dim("Watcher already stopped"));
|
|
4514
4871
|
}
|
|
4515
|
-
|
|
4872
|
+
unlinkSync4(PID_FILE2);
|
|
4516
4873
|
return;
|
|
4517
4874
|
}
|
|
4518
4875
|
const creds = requireAuth();
|
|
@@ -4520,7 +4877,7 @@ var watchCommand = new Command10("watch").description("Watch for work and spawn
|
|
|
4520
4877
|
const mapping = getProjectMapping(cwd);
|
|
4521
4878
|
if (!mapping) {
|
|
4522
4879
|
console.error(
|
|
4523
|
-
|
|
4880
|
+
chalk12.red("No project linked.") + " Run " + chalk12.cyan("yapout link") + " in a repo."
|
|
4524
4881
|
);
|
|
4525
4882
|
process.exit(1);
|
|
4526
4883
|
}
|
|
@@ -4528,16 +4885,16 @@ var watchCommand = new Command10("watch").description("Watch for work and spawn
|
|
|
4528
4885
|
const client = new ConvexHttpClient3(getConvexUrl());
|
|
4529
4886
|
client.setAuth(creds.token);
|
|
4530
4887
|
if (opts.bg) {
|
|
4531
|
-
|
|
4888
|
+
writeFileSync10(PID_FILE2, process.pid.toString());
|
|
4532
4889
|
console.log(
|
|
4533
|
-
|
|
4890
|
+
chalk12.green("Watcher started in background") + chalk12.dim(` (PID ${process.pid}, log: ${LOG_FILE2})`)
|
|
4534
4891
|
);
|
|
4535
4892
|
}
|
|
4536
|
-
console.log(
|
|
4893
|
+
console.log(chalk12.bold(`yapout watch v${"0.9.0"}`));
|
|
4537
4894
|
console.log(
|
|
4538
|
-
`Project: ${
|
|
4895
|
+
`Project: ${chalk12.green(mapping.projectName)} (${mapping.projectId})`
|
|
4539
4896
|
);
|
|
4540
|
-
console.log(
|
|
4897
|
+
console.log(chalk12.dim("Watching..."));
|
|
4541
4898
|
console.log();
|
|
4542
4899
|
const watcher = new Watcher(
|
|
4543
4900
|
{
|
|
@@ -4554,17 +4911,17 @@ var watchCommand = new Command10("watch").description("Watch for work and spawn
|
|
|
4554
4911
|
const shutdown = async () => {
|
|
4555
4912
|
if (shuttingDown) {
|
|
4556
4913
|
watcher.forceStop();
|
|
4557
|
-
if (
|
|
4914
|
+
if (existsSync11(PID_FILE2)) unlinkSync4(PID_FILE2);
|
|
4558
4915
|
process.exit(0);
|
|
4559
4916
|
}
|
|
4560
4917
|
shuttingDown = true;
|
|
4561
|
-
console.log(
|
|
4918
|
+
console.log(chalk12.dim("\nShutting down..."));
|
|
4562
4919
|
if (opts.force) {
|
|
4563
4920
|
watcher.forceStop();
|
|
4564
4921
|
} else {
|
|
4565
4922
|
await watcher.stop();
|
|
4566
4923
|
}
|
|
4567
|
-
if (
|
|
4924
|
+
if (existsSync11(PID_FILE2)) unlinkSync4(PID_FILE2);
|
|
4568
4925
|
process.exit(0);
|
|
4569
4926
|
};
|
|
4570
4927
|
process.on("SIGINT", () => {
|
|
@@ -4577,7 +4934,7 @@ var watchCommand = new Command10("watch").description("Watch for work and spawn
|
|
|
4577
4934
|
await new Promise(() => {
|
|
4578
4935
|
});
|
|
4579
4936
|
});
|
|
4580
|
-
function
|
|
4937
|
+
function isProcessRunning2(pid) {
|
|
4581
4938
|
try {
|
|
4582
4939
|
process.kill(pid, 0);
|
|
4583
4940
|
return true;
|
|
@@ -4587,88 +4944,88 @@ function isProcessRunning(pid) {
|
|
|
4587
4944
|
}
|
|
4588
4945
|
|
|
4589
4946
|
// src/commands/queue.ts
|
|
4590
|
-
import { Command as
|
|
4947
|
+
import { Command as Command12 } from "commander";
|
|
4591
4948
|
import { resolve as resolve9 } from "path";
|
|
4592
|
-
import
|
|
4593
|
-
var queueCommand = new
|
|
4949
|
+
import chalk13 from "chalk";
|
|
4950
|
+
var queueCommand = new Command12("queue").description("Show pipeline state \u2014 what's ready, blocked, and pending").action(async () => {
|
|
4594
4951
|
const creds = requireAuth();
|
|
4595
4952
|
const cwd = resolve9(process.cwd());
|
|
4596
4953
|
const mapping = getProjectMapping(cwd);
|
|
4597
4954
|
if (!mapping) {
|
|
4598
4955
|
console.error(
|
|
4599
|
-
|
|
4956
|
+
chalk13.red("No project linked.") + " Run " + chalk13.cyan("yapout link") + " in a repo."
|
|
4600
4957
|
);
|
|
4601
4958
|
process.exit(1);
|
|
4602
4959
|
}
|
|
4603
4960
|
const client = createConvexClient(creds.token);
|
|
4604
|
-
const { anyApi:
|
|
4961
|
+
const { anyApi: anyApi7 } = await import("convex/server");
|
|
4605
4962
|
const [queueData, unenriched, pending] = await Promise.all([
|
|
4606
|
-
client.query(
|
|
4963
|
+
client.query(anyApi7.functions.tickets.getLocalQueuedTickets, {
|
|
4607
4964
|
projectId: mapping.projectId
|
|
4608
4965
|
}),
|
|
4609
|
-
client.query(
|
|
4966
|
+
client.query(anyApi7.functions.localPipeline.getUnenrichedTickets, {
|
|
4610
4967
|
projectId: mapping.projectId
|
|
4611
4968
|
}),
|
|
4612
|
-
client.query(
|
|
4969
|
+
client.query(anyApi7.functions.localPipeline.getPendingSources, {
|
|
4613
4970
|
projectId: mapping.projectId
|
|
4614
4971
|
})
|
|
4615
4972
|
]);
|
|
4616
4973
|
console.log();
|
|
4617
4974
|
if (queueData?.ready && queueData.ready.length > 0) {
|
|
4618
|
-
console.log(
|
|
4975
|
+
console.log(chalk13.bold("Ready to implement:"));
|
|
4619
4976
|
for (const t of queueData.ready) {
|
|
4620
4977
|
const ref = t.linearTicketId ?? t.ticketId;
|
|
4621
4978
|
const prio = colorPriority(t.priority);
|
|
4622
4979
|
console.log(
|
|
4623
|
-
` ${
|
|
4980
|
+
` ${chalk13.bold(ref)} ${t.title.slice(0, 45).padEnd(45)} ${prio} ${chalk13.dim(t.type)}`
|
|
4624
4981
|
);
|
|
4625
4982
|
}
|
|
4626
4983
|
console.log();
|
|
4627
4984
|
}
|
|
4628
4985
|
if (queueData?.blocked && queueData.blocked.length > 0) {
|
|
4629
|
-
console.log(
|
|
4986
|
+
console.log(chalk13.bold("Blocked:"));
|
|
4630
4987
|
for (const t of queueData.blocked) {
|
|
4631
4988
|
console.log(
|
|
4632
|
-
` ${
|
|
4989
|
+
` ${chalk13.bold(t.ticketId.slice(-6))} ${t.title.slice(0, 45)} ${chalk13.red("blocked by " + t.blockedBy.map((id) => id.slice(-6)).join(", "))}`
|
|
4633
4990
|
);
|
|
4634
4991
|
}
|
|
4635
4992
|
console.log();
|
|
4636
4993
|
}
|
|
4637
4994
|
if (unenriched && unenriched.length > 0) {
|
|
4638
|
-
console.log(
|
|
4995
|
+
console.log(chalk13.bold("Needs enrichment:"));
|
|
4639
4996
|
for (const t of unenriched) {
|
|
4640
4997
|
const ref = t.ticketId;
|
|
4641
4998
|
console.log(
|
|
4642
|
-
` ${
|
|
4999
|
+
` ${chalk13.bold(ref.slice(-6))} ${t.title.slice(0, 45).padEnd(45)} ${chalk13.dim(t.priority)}`
|
|
4643
5000
|
);
|
|
4644
5001
|
}
|
|
4645
5002
|
console.log();
|
|
4646
5003
|
}
|
|
4647
5004
|
if (pending && pending.length > 0) {
|
|
4648
|
-
console.log(
|
|
5005
|
+
console.log(chalk13.bold("Pending extraction:"));
|
|
4649
5006
|
for (const t of pending) {
|
|
4650
5007
|
const ago = formatAgo(Date.now() - t.createdAt);
|
|
4651
5008
|
console.log(
|
|
4652
|
-
` "${t.meetingTitle ?? "Untitled"}" ${
|
|
5009
|
+
` "${t.meetingTitle ?? "Untitled"}" ${chalk13.dim(`(uploaded ${ago})`)}`
|
|
4653
5010
|
);
|
|
4654
5011
|
}
|
|
4655
5012
|
console.log();
|
|
4656
5013
|
}
|
|
4657
5014
|
if ((!queueData?.ready || queueData.ready.length === 0) && (!unenriched || unenriched.length === 0) && (!pending || pending.length === 0)) {
|
|
4658
|
-
console.log(
|
|
5015
|
+
console.log(chalk13.dim("Queue is empty. Upload a transcript to get started."));
|
|
4659
5016
|
console.log();
|
|
4660
5017
|
}
|
|
4661
5018
|
});
|
|
4662
5019
|
function colorPriority(p) {
|
|
4663
5020
|
switch (p) {
|
|
4664
5021
|
case "urgent":
|
|
4665
|
-
return
|
|
5022
|
+
return chalk13.red(p);
|
|
4666
5023
|
case "high":
|
|
4667
|
-
return
|
|
5024
|
+
return chalk13.yellow(p);
|
|
4668
5025
|
case "medium":
|
|
4669
|
-
return
|
|
5026
|
+
return chalk13.white(p);
|
|
4670
5027
|
case "low":
|
|
4671
|
-
return
|
|
5028
|
+
return chalk13.dim(p);
|
|
4672
5029
|
default:
|
|
4673
5030
|
return p;
|
|
4674
5031
|
}
|
|
@@ -4684,29 +5041,29 @@ function formatAgo(ms) {
|
|
|
4684
5041
|
}
|
|
4685
5042
|
|
|
4686
5043
|
// src/commands/next.ts
|
|
4687
|
-
import { Command as
|
|
5044
|
+
import { Command as Command13 } from "commander";
|
|
4688
5045
|
import { resolve as resolve10 } from "path";
|
|
4689
|
-
import { writeFileSync as
|
|
4690
|
-
import { join as
|
|
4691
|
-
import
|
|
4692
|
-
var nextCommand = new
|
|
5046
|
+
import { writeFileSync as writeFileSync11 } from "fs";
|
|
5047
|
+
import { join as join13 } from "path";
|
|
5048
|
+
import chalk14 from "chalk";
|
|
5049
|
+
var nextCommand = new Command13("next").description("Claim the highest priority ticket and set up for implementation").option("--worktree", "Create a git worktree instead of checking out a branch").action(async (opts) => {
|
|
4693
5050
|
const creds = requireAuth();
|
|
4694
5051
|
const cwd = resolve10(process.cwd());
|
|
4695
5052
|
const mapping = getProjectMapping(cwd);
|
|
4696
5053
|
if (!mapping) {
|
|
4697
5054
|
console.error(
|
|
4698
|
-
|
|
5055
|
+
chalk14.red("No project linked.") + " Run " + chalk14.cyan("yapout link") + " in a repo."
|
|
4699
5056
|
);
|
|
4700
5057
|
process.exit(1);
|
|
4701
5058
|
}
|
|
4702
5059
|
const client = createConvexClient(creds.token);
|
|
4703
|
-
const { anyApi:
|
|
5060
|
+
const { anyApi: anyApi7 } = await import("convex/server");
|
|
4704
5061
|
const data = await client.query(
|
|
4705
|
-
|
|
5062
|
+
anyApi7.functions.tickets.getLocalQueuedTickets,
|
|
4706
5063
|
{ projectId: mapping.projectId }
|
|
4707
5064
|
);
|
|
4708
5065
|
if (!data?.ready || data.ready.length === 0) {
|
|
4709
|
-
console.log(
|
|
5066
|
+
console.log(chalk14.dim("No tickets ready for implementation."));
|
|
4710
5067
|
return;
|
|
4711
5068
|
}
|
|
4712
5069
|
const ticket = data.ready[0];
|
|
@@ -4714,32 +5071,32 @@ var nextCommand = new Command12("next").description("Claim the highest priority
|
|
|
4714
5071
|
const config = readYapoutConfig(cwd);
|
|
4715
5072
|
const defaultBranch = getDefaultBranch(cwd);
|
|
4716
5073
|
const branchName = `${config.branch_prefix}/${ref.toLowerCase().replace(/\s+/g, "-")}-${ticket.title.toLowerCase().replace(/[^a-z0-9]+/g, "-").slice(0, 40)}`;
|
|
4717
|
-
console.log(
|
|
5074
|
+
console.log(chalk14.bold(`
|
|
4718
5075
|
Claimed: ${ref} "${ticket.title}"`));
|
|
4719
5076
|
fetchOrigin(cwd);
|
|
4720
5077
|
let workDir = cwd;
|
|
4721
5078
|
if (opts.worktree) {
|
|
4722
5079
|
const { createWorktree: createWorktree2 } = await import("./worktree-ZZZIL7TK.js");
|
|
4723
5080
|
workDir = createWorktree2(cwd, ticket.ticketId, branchName, defaultBranch);
|
|
4724
|
-
console.log(`Worktree: ${
|
|
5081
|
+
console.log(`Worktree: ${chalk14.cyan(workDir)}`);
|
|
4725
5082
|
} else {
|
|
4726
5083
|
checkoutNewBranch(branchName, defaultBranch, cwd);
|
|
4727
5084
|
}
|
|
4728
|
-
console.log(`Branch: ${
|
|
5085
|
+
console.log(`Branch: ${chalk14.cyan(branchName)}`);
|
|
4729
5086
|
const brief = await client.query(
|
|
4730
|
-
|
|
5087
|
+
anyApi7.functions.tickets.getTicketBrief,
|
|
4731
5088
|
{ ticketId: ticket.ticketId }
|
|
4732
5089
|
);
|
|
4733
5090
|
if (brief) {
|
|
4734
|
-
const briefPath =
|
|
5091
|
+
const briefPath = join13(workDir, ".yapout", "brief.md");
|
|
4735
5092
|
const briefContent = formatBrief(ref, ticket, brief);
|
|
4736
|
-
|
|
4737
|
-
console.log(`Brief: ${
|
|
5093
|
+
writeFileSync11(briefPath, briefContent);
|
|
5094
|
+
console.log(`Brief: ${chalk14.cyan(briefPath)}`);
|
|
4738
5095
|
}
|
|
4739
5096
|
console.log();
|
|
4740
5097
|
console.log("Start Claude Code to implement, or run:");
|
|
4741
5098
|
console.log(
|
|
4742
|
-
|
|
5099
|
+
chalk14.cyan(
|
|
4743
5100
|
` claude --dangerously-skip-permissions "Read .yapout/brief.md, implement it, run yapout_check, then yapout_ship"`
|
|
4744
5101
|
)
|
|
4745
5102
|
);
|
|
@@ -4789,21 +5146,21 @@ function formatBrief(ref, ticket, brief) {
|
|
|
4789
5146
|
}
|
|
4790
5147
|
|
|
4791
5148
|
// src/commands/recap.ts
|
|
4792
|
-
import { Command as
|
|
5149
|
+
import { Command as Command14 } from "commander";
|
|
4793
5150
|
import { resolve as resolve11 } from "path";
|
|
4794
|
-
import
|
|
4795
|
-
var recapCommand = new
|
|
5151
|
+
import chalk15 from "chalk";
|
|
5152
|
+
var recapCommand = new Command14("recap").description("Show a summary of recent yapout activity").option("--week", "Show full week summary (default: today)").action(async (opts) => {
|
|
4796
5153
|
const creds = requireAuth();
|
|
4797
5154
|
const cwd = resolve11(process.cwd());
|
|
4798
5155
|
const mapping = getProjectMapping(cwd);
|
|
4799
5156
|
if (!mapping) {
|
|
4800
5157
|
console.error(
|
|
4801
|
-
|
|
5158
|
+
chalk15.red("No project linked.") + " Run " + chalk15.cyan("yapout link") + " in a repo."
|
|
4802
5159
|
);
|
|
4803
5160
|
process.exit(1);
|
|
4804
5161
|
}
|
|
4805
5162
|
const client = createConvexClient(creds.token);
|
|
4806
|
-
const { anyApi:
|
|
5163
|
+
const { anyApi: anyApi7 } = await import("convex/server");
|
|
4807
5164
|
const now = /* @__PURE__ */ new Date();
|
|
4808
5165
|
const todayStart = new Date(
|
|
4809
5166
|
now.getFullYear(),
|
|
@@ -4813,33 +5170,33 @@ var recapCommand = new Command13("recap").description("Show a summary of recent
|
|
|
4813
5170
|
const weekStart = todayStart - 6 * 24 * 60 * 60 * 1e3;
|
|
4814
5171
|
const since = opts.week ? weekStart : todayStart;
|
|
4815
5172
|
const data = await client.query(
|
|
4816
|
-
|
|
5173
|
+
anyApi7.functions.localPipeline.getRecentActivity,
|
|
4817
5174
|
{ projectId: mapping.projectId, since }
|
|
4818
5175
|
);
|
|
4819
5176
|
if (!data) {
|
|
4820
|
-
console.log(
|
|
5177
|
+
console.log(chalk15.dim("Could not fetch activity data."));
|
|
4821
5178
|
return;
|
|
4822
5179
|
}
|
|
4823
5180
|
const period = opts.week ? "This week" : "Today";
|
|
4824
5181
|
console.log();
|
|
4825
|
-
console.log(
|
|
5182
|
+
console.log(chalk15.bold(`${period}:`));
|
|
4826
5183
|
if (data.recentTickets.length > 0) {
|
|
4827
5184
|
for (const t of data.recentTickets) {
|
|
4828
5185
|
const ref = t.linearTicketId ?? "ticket";
|
|
4829
|
-
console.log(` ${
|
|
5186
|
+
console.log(` ${chalk15.green("\u2713")} ${ref} ${t.title}`);
|
|
4830
5187
|
}
|
|
4831
5188
|
}
|
|
4832
5189
|
if (data.recentPRs.length > 0) {
|
|
4833
5190
|
for (const p of data.recentPRs) {
|
|
4834
5191
|
const prRef = p.githubPrNumber ? `PR #${p.githubPrNumber}` : "PR";
|
|
4835
|
-
const statusColor = p.status === "opened" ?
|
|
5192
|
+
const statusColor = p.status === "opened" ? chalk15.green : p.status === "draft" ? chalk15.dim : chalk15.yellow;
|
|
4836
5193
|
console.log(
|
|
4837
5194
|
` ${statusColor("\u2191")} ${p.prTitle.slice(0, 45)} ${statusColor(prRef)} ${statusColor(p.status)}`
|
|
4838
5195
|
);
|
|
4839
5196
|
}
|
|
4840
5197
|
}
|
|
4841
5198
|
if (data.recentTickets.length === 0 && data.recentPRs.length === 0) {
|
|
4842
|
-
console.log(
|
|
5199
|
+
console.log(chalk15.dim(" No activity yet."));
|
|
4843
5200
|
}
|
|
4844
5201
|
console.log();
|
|
4845
5202
|
console.log(
|
|
@@ -4849,12 +5206,12 @@ var recapCommand = new Command13("recap").description("Show a summary of recent
|
|
|
4849
5206
|
});
|
|
4850
5207
|
|
|
4851
5208
|
// src/commands/yap.ts
|
|
4852
|
-
import { Command as
|
|
4853
|
-
import
|
|
4854
|
-
var yapCommand = new
|
|
5209
|
+
import { Command as Command15 } from "commander";
|
|
5210
|
+
import chalk16 from "chalk";
|
|
5211
|
+
var yapCommand = new Command15("yap").description(
|
|
4855
5212
|
"Start a yap session \u2014 brainstorm with an AI that knows your codebase"
|
|
4856
5213
|
).action(() => {
|
|
4857
|
-
console.log(
|
|
5214
|
+
console.log(chalk16.bold("yapout yap sessions"));
|
|
4858
5215
|
console.log();
|
|
4859
5216
|
console.log(
|
|
4860
5217
|
"Yap sessions run inside Claude \u2014 Desktop, CLI, or web \u2014 anywhere the"
|
|
@@ -4863,27 +5220,27 @@ var yapCommand = new Command14("yap").description(
|
|
|
4863
5220
|
console.log();
|
|
4864
5221
|
console.log("Just tell Claude:");
|
|
4865
5222
|
console.log(
|
|
4866
|
-
|
|
5223
|
+
chalk16.green(` "Let's have a yap session about [topic]"`)
|
|
4867
5224
|
);
|
|
4868
5225
|
console.log(
|
|
4869
|
-
|
|
5226
|
+
chalk16.green(
|
|
4870
5227
|
' "I want to brainstorm [idea] \u2014 be a skeptical QA engineer"'
|
|
4871
5228
|
)
|
|
4872
5229
|
);
|
|
4873
5230
|
console.log();
|
|
4874
|
-
console.log(
|
|
5231
|
+
console.log(chalk16.dim("Personas: tech lead, qa engineer, product owner, end user, or custom"));
|
|
4875
5232
|
console.log(
|
|
4876
|
-
|
|
5233
|
+
chalk16.dim(
|
|
4877
5234
|
"Claude will call yapout_start_yap to get instructions and yapout_submit_yap_session when done."
|
|
4878
5235
|
)
|
|
4879
5236
|
);
|
|
4880
5237
|
});
|
|
4881
5238
|
|
|
4882
5239
|
// src/commands/handle-uri.ts
|
|
4883
|
-
import { Command as
|
|
4884
|
-
import { spawn as
|
|
4885
|
-
import { platform as
|
|
4886
|
-
import
|
|
5240
|
+
import { Command as Command16 } from "commander";
|
|
5241
|
+
import { spawn as spawn3 } from "child_process";
|
|
5242
|
+
import { platform as platform3 } from "os";
|
|
5243
|
+
import chalk17 from "chalk";
|
|
4887
5244
|
var VALID_ACTIONS = ["claim", "enrich", "enrich-bulk", "enrich-bundle", "yap", "compact"];
|
|
4888
5245
|
function parseYapoutUri(raw) {
|
|
4889
5246
|
const url = new URL(raw);
|
|
@@ -4996,11 +5353,11 @@ function findProjectDir() {
|
|
|
4996
5353
|
return dirs[0];
|
|
4997
5354
|
}
|
|
4998
5355
|
function launchTerminal(cwd, claudeArgs) {
|
|
4999
|
-
const os =
|
|
5356
|
+
const os = platform3();
|
|
5000
5357
|
const claudeCmd = `claude ${claudeArgs.map(shellEscape).join(" ")}`;
|
|
5001
5358
|
if (os === "win32") {
|
|
5002
5359
|
const wtArgs = ["wt", "-d", cwd, "cmd", "/k", claudeCmd];
|
|
5003
|
-
const child =
|
|
5360
|
+
const child = spawn3("cmd", ["/c", "start", ...wtArgs], {
|
|
5004
5361
|
cwd,
|
|
5005
5362
|
stdio: "ignore",
|
|
5006
5363
|
detached: true,
|
|
@@ -5008,7 +5365,7 @@ function launchTerminal(cwd, claudeArgs) {
|
|
|
5008
5365
|
});
|
|
5009
5366
|
child.unref();
|
|
5010
5367
|
child.on("error", () => {
|
|
5011
|
-
const fallback =
|
|
5368
|
+
const fallback = spawn3(
|
|
5012
5369
|
"cmd",
|
|
5013
5370
|
["/c", "start", "cmd", "/k", claudeCmd],
|
|
5014
5371
|
{ cwd, stdio: "ignore", detached: true, shell: true }
|
|
@@ -5021,7 +5378,7 @@ function launchTerminal(cwd, claudeArgs) {
|
|
|
5021
5378
|
activate
|
|
5022
5379
|
do script "cd ${shellEscape(cwd)} && ${escaped}"
|
|
5023
5380
|
end tell`;
|
|
5024
|
-
const child =
|
|
5381
|
+
const child = spawn3("osascript", ["-e", script], {
|
|
5025
5382
|
stdio: "ignore",
|
|
5026
5383
|
detached: true
|
|
5027
5384
|
});
|
|
@@ -5036,7 +5393,7 @@ end tell`;
|
|
|
5036
5393
|
let launched = false;
|
|
5037
5394
|
for (const term of terminals) {
|
|
5038
5395
|
try {
|
|
5039
|
-
const child =
|
|
5396
|
+
const child = spawn3(term.cmd, term.args, {
|
|
5040
5397
|
stdio: "ignore",
|
|
5041
5398
|
detached: true
|
|
5042
5399
|
});
|
|
@@ -5049,44 +5406,44 @@ end tell`;
|
|
|
5049
5406
|
}
|
|
5050
5407
|
if (!launched) {
|
|
5051
5408
|
console.error(
|
|
5052
|
-
|
|
5409
|
+
chalk17.red("Could not find a terminal emulator. Install gnome-terminal, konsole, or xterm.")
|
|
5053
5410
|
);
|
|
5054
5411
|
process.exit(1);
|
|
5055
5412
|
}
|
|
5056
5413
|
}
|
|
5057
5414
|
}
|
|
5058
5415
|
function shellEscape(s) {
|
|
5059
|
-
if (
|
|
5416
|
+
if (platform3() === "win32") {
|
|
5060
5417
|
return `"${s.replace(/"/g, '""')}"`;
|
|
5061
5418
|
}
|
|
5062
5419
|
return `'${s.replace(/'/g, "'\\''")}'`;
|
|
5063
5420
|
}
|
|
5064
|
-
var handleUriCommand = new
|
|
5421
|
+
var handleUriCommand = new Command16("handle-uri").description("Handle a yapout:// URI (used by OS protocol handler)").argument("<uri>", "The yapout:// URI to handle").action(async (uri) => {
|
|
5065
5422
|
try {
|
|
5066
5423
|
const parsed = parseYapoutUri(uri);
|
|
5067
5424
|
const prompt = buildPrompt(parsed);
|
|
5068
5425
|
const cwd = findProjectDir();
|
|
5069
5426
|
if (!cwd) {
|
|
5070
5427
|
console.error(
|
|
5071
|
-
|
|
5428
|
+
chalk17.red(
|
|
5072
5429
|
"No linked project found. Run `yapout init` or `yapout link` in your repo first."
|
|
5073
5430
|
)
|
|
5074
5431
|
);
|
|
5075
5432
|
process.exit(1);
|
|
5076
5433
|
}
|
|
5077
5434
|
const label = parsed.ticketId ? `${parsed.action} ${parsed.ticketId}` : parsed.action;
|
|
5078
|
-
console.log(
|
|
5435
|
+
console.log(chalk17.dim(`Launching Claude Code to ${label}...`));
|
|
5079
5436
|
launchTerminal(cwd, ["--dangerously-skip-permissions", prompt]);
|
|
5080
5437
|
setTimeout(() => process.exit(0), 500);
|
|
5081
5438
|
} catch (err) {
|
|
5082
|
-
console.error(
|
|
5439
|
+
console.error(chalk17.red(err.message));
|
|
5083
5440
|
process.exit(1);
|
|
5084
5441
|
}
|
|
5085
5442
|
});
|
|
5086
5443
|
|
|
5087
5444
|
// src/index.ts
|
|
5088
|
-
var program = new
|
|
5089
|
-
program.name("yapout").description("yapout \u2014 from meeting transcript to merged PR").version("0.
|
|
5445
|
+
var program = new Command17();
|
|
5446
|
+
program.name("yapout").description("yapout \u2014 from meeting transcript to merged PR").version("0.9.0");
|
|
5090
5447
|
program.addCommand(loginCommand);
|
|
5091
5448
|
program.addCommand(logoutCommand);
|
|
5092
5449
|
program.addCommand(initCommand);
|
|
@@ -5102,4 +5459,5 @@ program.addCommand(nextCommand);
|
|
|
5102
5459
|
program.addCommand(recapCommand);
|
|
5103
5460
|
program.addCommand(yapCommand);
|
|
5104
5461
|
program.addCommand(handleUriCommand);
|
|
5462
|
+
program.addCommand(serveCommand);
|
|
5105
5463
|
program.parse();
|