@pebblehouse/odin-cli 0.9.0 → 0.10.1
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 +390 -4
- package/package.json +8 -3
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
-
import { Command as
|
|
4
|
+
import { Command as Command18 } from "commander";
|
|
5
5
|
|
|
6
6
|
// src/auth/login.ts
|
|
7
7
|
import { createServer } from "http";
|
|
@@ -757,13 +757,394 @@ briefsCmd.command("versions <slug>").description("List version history for a peb
|
|
|
757
757
|
process.stdout.write(JSON.stringify(res) + "\n");
|
|
758
758
|
if (res.error) process.exit(1);
|
|
759
759
|
});
|
|
760
|
+
briefsCmd.command("generate [slug]").description("Generate/regenerate brief for a pebble (or all pebbles)").option("--secret <secret>", "Worker secret (defaults to ODIN_WORKER_SECRET env var)").action(async (slug, opts) => {
|
|
761
|
+
const secret = opts.secret ?? process.env.ODIN_WORKER_SECRET;
|
|
762
|
+
if (!secret) {
|
|
763
|
+
console.error("error: ODIN_WORKER_SECRET env var or --secret flag required");
|
|
764
|
+
process.exit(1);
|
|
765
|
+
}
|
|
766
|
+
const apiUrl = process.env.ODIN_API_URL ?? "https://odin.mu";
|
|
767
|
+
const url = slug ? `${apiUrl}/api/internal/briefs/generate?slug=${slug}` : `${apiUrl}/api/internal/briefs/generate`;
|
|
768
|
+
try {
|
|
769
|
+
const res = await fetch(url, {
|
|
770
|
+
method: "POST",
|
|
771
|
+
headers: { Authorization: `Bearer ${secret}` }
|
|
772
|
+
});
|
|
773
|
+
const body = await res.json();
|
|
774
|
+
if (!res.ok) {
|
|
775
|
+
console.error(`error: ${body.error ?? res.statusText}`);
|
|
776
|
+
process.exit(1);
|
|
777
|
+
}
|
|
778
|
+
process.stdout.write(JSON.stringify(body, null, 2) + "\n");
|
|
779
|
+
} catch (err) {
|
|
780
|
+
console.error(`error: ${err instanceof Error ? err.message : String(err)}`);
|
|
781
|
+
process.exit(1);
|
|
782
|
+
}
|
|
783
|
+
});
|
|
760
784
|
|
|
761
|
-
// src/
|
|
785
|
+
// src/commands/blueprints.ts
|
|
786
|
+
import { Command as Command12 } from "commander";
|
|
762
787
|
import { readFileSync as readFileSync9 } from "fs";
|
|
788
|
+
var blueprintsCmd = new Command12("blueprints").description("Manage global blueprints");
|
|
789
|
+
blueprintsCmd.command("list").description("List blueprints").option("--tag <tag>", "Filter by tag").option("--status <status>", "Filter by status (default: active)").option("--all", "Include deprecated/experimental blueprints").action(async (opts) => {
|
|
790
|
+
const params = new URLSearchParams();
|
|
791
|
+
if (opts.tag) params.set("tag", opts.tag);
|
|
792
|
+
if (opts.all) params.set("status", "all");
|
|
793
|
+
else if (opts.status) params.set("status", opts.status);
|
|
794
|
+
const qs = params.toString();
|
|
795
|
+
const res = await apiRequest(`/blueprints${qs ? `?${qs}` : ""}`);
|
|
796
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
797
|
+
if (res.error) process.exit(1);
|
|
798
|
+
});
|
|
799
|
+
blueprintsCmd.command("get <id>").description("Get a blueprint by ID (includes assets)").action(async (id) => {
|
|
800
|
+
const res = await apiRequest(`/blueprints/${id}`);
|
|
801
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
802
|
+
if (res.error) process.exit(1);
|
|
803
|
+
});
|
|
804
|
+
blueprintsCmd.command("create <title>").description("Create a blueprint").option("--content <content>", "Blueprint content").option("--file <path>", "Read content from file").option("--tags <tags>", "Comma-separated tags").option("--status <status>", "Status (active|experimental)", "active").option("--source <source>", "Source", "manual").action(async (title, opts) => {
|
|
805
|
+
const content = opts.file ? readFileSync9(opts.file, "utf-8") : opts.content ?? "";
|
|
806
|
+
const tags = opts.tags ? opts.tags.split(",").map((t) => t.trim()) : [];
|
|
807
|
+
const res = await apiRequest("/blueprints", {
|
|
808
|
+
method: "POST",
|
|
809
|
+
body: { title, content, tags, status: opts.status, source: opts.source }
|
|
810
|
+
});
|
|
811
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
812
|
+
if (res.error) process.exit(1);
|
|
813
|
+
});
|
|
814
|
+
blueprintsCmd.command("update <id>").description("Update a blueprint").option("--title <title>", "New title").option("--content <content>", "New content").option("--file <path>", "Read content from file").option("--tags <tags>", "Comma-separated tags (replaces all)").option("--status <status>", "New status (active|deprecated|experimental)").option("--source <source>", "Source").action(async (id, opts) => {
|
|
815
|
+
const body = {};
|
|
816
|
+
if (opts.title) body.title = opts.title;
|
|
817
|
+
if (opts.file) body.content = readFileSync9(opts.file, "utf-8");
|
|
818
|
+
else if (opts.content) body.content = opts.content;
|
|
819
|
+
if (opts.tags) body.tags = opts.tags.split(",").map((t) => t.trim());
|
|
820
|
+
if (opts.status) body.status = opts.status;
|
|
821
|
+
if (opts.source) body.source = opts.source;
|
|
822
|
+
const res = await apiRequest(`/blueprints/${id}`, {
|
|
823
|
+
method: "PUT",
|
|
824
|
+
body
|
|
825
|
+
});
|
|
826
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
827
|
+
if (res.error) process.exit(1);
|
|
828
|
+
});
|
|
829
|
+
blueprintsCmd.command("delete <id>").description("Delete a blueprint").action(async (id) => {
|
|
830
|
+
const res = await apiRequest(`/blueprints/${id}`, {
|
|
831
|
+
method: "DELETE"
|
|
832
|
+
});
|
|
833
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
834
|
+
if (res.error) process.exit(1);
|
|
835
|
+
});
|
|
836
|
+
blueprintsCmd.command("versions <id>").description("List version history for a blueprint").action(async (id) => {
|
|
837
|
+
const res = await apiRequest(`/blueprints/${id}/versions`);
|
|
838
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
839
|
+
if (res.error) process.exit(1);
|
|
840
|
+
});
|
|
841
|
+
blueprintsCmd.command("deprecate <id>").description("Deprecate a blueprint").action(async (id) => {
|
|
842
|
+
const res = await apiRequest(`/blueprints/${id}`, {
|
|
843
|
+
method: "PUT",
|
|
844
|
+
body: { status: "deprecated" }
|
|
845
|
+
});
|
|
846
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
847
|
+
if (res.error) process.exit(1);
|
|
848
|
+
});
|
|
849
|
+
blueprintsCmd.command("add-asset <id>").description("Add an asset reference to a blueprint").requiredOption("--title <title>", "Asset title").requiredOption("--url <url>", "Asset URL").option("--type <type>", "Asset type (screenshot|diagram|code-snippet|figma-link|github-link|raw-text|other)", "other").option("--description <description>", "Asset description").action(async (id, opts) => {
|
|
850
|
+
const res = await apiRequest(`/blueprints/${id}/assets`, {
|
|
851
|
+
method: "POST",
|
|
852
|
+
body: {
|
|
853
|
+
title: opts.title,
|
|
854
|
+
asset_url: opts.url,
|
|
855
|
+
type: opts.type,
|
|
856
|
+
description: opts.description ?? ""
|
|
857
|
+
}
|
|
858
|
+
});
|
|
859
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
860
|
+
if (res.error) process.exit(1);
|
|
861
|
+
});
|
|
862
|
+
blueprintsCmd.command("assets <id>").description("List assets for a blueprint").action(async (id) => {
|
|
863
|
+
const res = await apiRequest(`/blueprints/${id}/assets`);
|
|
864
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
865
|
+
if (res.error) process.exit(1);
|
|
866
|
+
});
|
|
867
|
+
|
|
868
|
+
// src/commands/bets.ts
|
|
869
|
+
import { Command as Command14 } from "commander";
|
|
870
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
871
|
+
|
|
872
|
+
// src/commands/bet-dependencies.ts
|
|
873
|
+
import { Command as Command13 } from "commander";
|
|
874
|
+
var depsCmd = new Command13("deps").description("Manage bet dependencies (blocked-by graph)");
|
|
875
|
+
depsCmd.command("list <slug> <bet-id>").description("List dependencies for a bet (blocked_by + blocking)").action(async (slug, betId) => {
|
|
876
|
+
const res = await apiRequest(
|
|
877
|
+
`/pebbles/${slug}/bets/${betId}/dependencies`
|
|
878
|
+
);
|
|
879
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
880
|
+
if (res.error) process.exit(1);
|
|
881
|
+
});
|
|
882
|
+
depsCmd.command("add <slug> <bet-id>").description("Add a blocked-by dependency").requiredOption("--blocked-by <id>", "Bet ID that blocks this bet").action(async (slug, betId, opts) => {
|
|
883
|
+
const res = await apiRequest(
|
|
884
|
+
`/pebbles/${slug}/bets/${betId}/dependencies`,
|
|
885
|
+
{
|
|
886
|
+
method: "POST",
|
|
887
|
+
body: { blocked_by_bet_id: opts.blockedBy }
|
|
888
|
+
}
|
|
889
|
+
);
|
|
890
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
891
|
+
if (res.error) process.exit(1);
|
|
892
|
+
});
|
|
893
|
+
depsCmd.command("remove <slug> <bet-id>").description("Remove a blocked-by dependency").requiredOption("--blocked-by <id>", "Bet ID to unlink").action(async (slug, betId, opts) => {
|
|
894
|
+
const res = await apiRequest(
|
|
895
|
+
`/pebbles/${slug}/bets/${betId}/dependencies/${opts.blockedBy}`,
|
|
896
|
+
{ method: "DELETE" }
|
|
897
|
+
);
|
|
898
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
899
|
+
if (res.error) process.exit(1);
|
|
900
|
+
});
|
|
901
|
+
|
|
902
|
+
// src/commands/bets.ts
|
|
903
|
+
var betsCmd = new Command14("bets").description("Manage bets");
|
|
904
|
+
betsCmd.command("list <slug>").description("List bets for a pebble").option("--status <status>", "Filter by status").action(async (slug, opts) => {
|
|
905
|
+
const params = {};
|
|
906
|
+
if (opts.status) params.status = opts.status;
|
|
907
|
+
const res = await apiRequest(`/pebbles/${slug}/bets`, { params });
|
|
908
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
909
|
+
if (res.error) process.exit(1);
|
|
910
|
+
});
|
|
911
|
+
betsCmd.command("get <slug> <id>").description("Get a bet by ID").action(async (slug, id) => {
|
|
912
|
+
const res = await apiRequest(`/pebbles/${slug}/bets/${id}`);
|
|
913
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
914
|
+
if (res.error) process.exit(1);
|
|
915
|
+
});
|
|
916
|
+
betsCmd.command("create <slug> <title>").description("Create a bet").option("--source <source>", "Source agent", "claude-code").option("--content <content>", "Bet content").option("--file <path>", "Read content from file").option("--status <status>", "Bet status").option("--priority <priority>", "Bet priority").option("--appetite <appetite>", "Bet appetite").action(async (slug, title, opts) => {
|
|
917
|
+
const content = opts.file ? readFileSync10(opts.file, "utf-8") : opts.content ?? "";
|
|
918
|
+
const res = await apiRequest(`/pebbles/${slug}/bets`, {
|
|
919
|
+
method: "POST",
|
|
920
|
+
body: { title, source: opts.source, content, status: opts.status, priority: opts.priority, appetite: opts.appetite }
|
|
921
|
+
});
|
|
922
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
923
|
+
if (res.error) process.exit(1);
|
|
924
|
+
});
|
|
925
|
+
betsCmd.command("update <slug> <id>").description("Update a bet").option("--title <title>", "New title").option("--content <content>", "New content").option("--file <path>", "Read content from file").option("--status <status>", "New status").option("--priority <priority>", "New priority").option("--appetite <appetite>", "New appetite").action(async (slug, id, opts) => {
|
|
926
|
+
const body = {};
|
|
927
|
+
if (opts.title) body.title = opts.title;
|
|
928
|
+
if (opts.file) body.content = readFileSync10(opts.file, "utf-8");
|
|
929
|
+
else if (opts.content) body.content = opts.content;
|
|
930
|
+
if (opts.status) body.status = opts.status;
|
|
931
|
+
if (opts.priority) body.priority = opts.priority;
|
|
932
|
+
if (opts.appetite) body.appetite = opts.appetite;
|
|
933
|
+
const res = await apiRequest(`/pebbles/${slug}/bets/${id}`, {
|
|
934
|
+
method: "PUT",
|
|
935
|
+
body
|
|
936
|
+
});
|
|
937
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
938
|
+
if (res.error) process.exit(1);
|
|
939
|
+
});
|
|
940
|
+
betsCmd.command("delete <slug> <id>").description("Delete a bet").action(async (slug, id) => {
|
|
941
|
+
const res = await apiRequest(`/pebbles/${slug}/bets/${id}`, {
|
|
942
|
+
method: "DELETE"
|
|
943
|
+
});
|
|
944
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
945
|
+
if (res.error) process.exit(1);
|
|
946
|
+
});
|
|
947
|
+
betsCmd.addCommand(depsCmd);
|
|
948
|
+
|
|
949
|
+
// src/commands/tasks.ts
|
|
950
|
+
import { Command as Command15 } from "commander";
|
|
951
|
+
var tasksCmd = new Command15("tasks").description("Manage tasks");
|
|
952
|
+
tasksCmd.command("list <slug>").description("List tasks for a pebble").option("--status <status>", "Filter by status").option("--bet <bet-id>", "Filter by bet ID").action(async (slug, opts) => {
|
|
953
|
+
const params = {};
|
|
954
|
+
if (opts.status) params.status = opts.status;
|
|
955
|
+
if (opts.bet) params.bet_id = opts.bet;
|
|
956
|
+
const res = await apiRequest(`/pebbles/${slug}/tasks`, { params });
|
|
957
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
958
|
+
if (res.error) process.exit(1);
|
|
959
|
+
});
|
|
960
|
+
tasksCmd.command("get <slug> <id>").description("Get a task by ID").action(async (slug, id) => {
|
|
961
|
+
const res = await apiRequest(`/pebbles/${slug}/tasks/${id}`);
|
|
962
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
963
|
+
if (res.error) process.exit(1);
|
|
964
|
+
});
|
|
965
|
+
tasksCmd.command("create <slug> <title>").description("Create a task").option("--source <source>", "Source agent", "claude-code").option("--description <description>", "Task description").option("--bet <bet-id>", "Parent bet ID").option("--status <status>", "Task status").option("--priority <priority>", "Task priority").action(async (slug, title, opts) => {
|
|
966
|
+
const res = await apiRequest(`/pebbles/${slug}/tasks`, {
|
|
967
|
+
method: "POST",
|
|
968
|
+
body: { title, source: opts.source, description: opts.description, bet_id: opts.bet, status: opts.status, priority: opts.priority }
|
|
969
|
+
});
|
|
970
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
971
|
+
if (res.error) process.exit(1);
|
|
972
|
+
});
|
|
973
|
+
tasksCmd.command("update <slug> <id>").description("Update a task").option("--title <title>", "New title").option("--description <description>", "New description").option("--status <status>", "New status").option("--priority <priority>", "New priority").option("--bet <bet-id>", "New parent bet ID").action(async (slug, id, opts) => {
|
|
974
|
+
const body = {};
|
|
975
|
+
if (opts.title) body.title = opts.title;
|
|
976
|
+
if (opts.description) body.description = opts.description;
|
|
977
|
+
if (opts.status) body.status = opts.status;
|
|
978
|
+
if (opts.priority) body.priority = opts.priority;
|
|
979
|
+
if (opts.bet) body.bet_id = opts.bet;
|
|
980
|
+
const res = await apiRequest(`/pebbles/${slug}/tasks/${id}`, {
|
|
981
|
+
method: "PUT",
|
|
982
|
+
body
|
|
983
|
+
});
|
|
984
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
985
|
+
if (res.error) process.exit(1);
|
|
986
|
+
});
|
|
987
|
+
tasksCmd.command("delete <slug> <id>").description("Delete a task").action(async (slug, id) => {
|
|
988
|
+
const res = await apiRequest(`/pebbles/${slug}/tasks/${id}`, {
|
|
989
|
+
method: "DELETE"
|
|
990
|
+
});
|
|
991
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
992
|
+
if (res.error) process.exit(1);
|
|
993
|
+
});
|
|
994
|
+
|
|
995
|
+
// src/commands/roadmaps.ts
|
|
996
|
+
import { Command as Command16 } from "commander";
|
|
997
|
+
import { readFileSync as readFileSync11 } from "fs";
|
|
998
|
+
var roadmapsCmd = new Command16("roadmaps").description("Manage roadmaps");
|
|
999
|
+
roadmapsCmd.command("list <slug>").description("List roadmaps for a pebble").option("--status <status>", "Filter by status").action(async (slug, opts) => {
|
|
1000
|
+
const params = {};
|
|
1001
|
+
if (opts.status) params.status = opts.status;
|
|
1002
|
+
const res = await apiRequest(`/pebbles/${slug}/roadmaps`, { params });
|
|
1003
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
1004
|
+
if (res.error) process.exit(1);
|
|
1005
|
+
});
|
|
1006
|
+
roadmapsCmd.command("get <slug> <id>").description("Get a roadmap by ID (includes bets)").action(async (slug, id) => {
|
|
1007
|
+
const res = await apiRequest(`/pebbles/${slug}/roadmaps/${id}`);
|
|
1008
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
1009
|
+
if (res.error) process.exit(1);
|
|
1010
|
+
});
|
|
1011
|
+
roadmapsCmd.command("create <slug> <title>").description("Create a roadmap").option("--source <source>", "Source agent", "claude-code").option("--content <content>", "Roadmap content").option("--file <path>", "Read content from file").option("--status <status>", "Roadmap status").action(async (slug, title, opts) => {
|
|
1012
|
+
const content = opts.file ? readFileSync11(opts.file, "utf-8") : opts.content ?? "";
|
|
1013
|
+
const res = await apiRequest(`/pebbles/${slug}/roadmaps`, {
|
|
1014
|
+
method: "POST",
|
|
1015
|
+
body: { title, source: opts.source, content, status: opts.status }
|
|
1016
|
+
});
|
|
1017
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
1018
|
+
if (res.error) process.exit(1);
|
|
1019
|
+
});
|
|
1020
|
+
roadmapsCmd.command("update <slug> <id>").description("Update a roadmap").option("--title <title>", "New title").option("--content <content>", "New content").option("--file <path>", "Read content from file").option("--status <status>", "New status").action(async (slug, id, opts) => {
|
|
1021
|
+
const body = {};
|
|
1022
|
+
if (opts.title) body.title = opts.title;
|
|
1023
|
+
if (opts.file) body.content = readFileSync11(opts.file, "utf-8");
|
|
1024
|
+
else if (opts.content) body.content = opts.content;
|
|
1025
|
+
if (opts.status) body.status = opts.status;
|
|
1026
|
+
const res = await apiRequest(`/pebbles/${slug}/roadmaps/${id}`, {
|
|
1027
|
+
method: "PUT",
|
|
1028
|
+
body
|
|
1029
|
+
});
|
|
1030
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
1031
|
+
if (res.error) process.exit(1);
|
|
1032
|
+
});
|
|
1033
|
+
roadmapsCmd.command("delete <slug> <id>").description("Delete a roadmap").action(async (slug, id) => {
|
|
1034
|
+
const res = await apiRequest(`/pebbles/${slug}/roadmaps/${id}`, {
|
|
1035
|
+
method: "DELETE"
|
|
1036
|
+
});
|
|
1037
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
1038
|
+
if (res.error) process.exit(1);
|
|
1039
|
+
});
|
|
1040
|
+
|
|
1041
|
+
// src/commands/persona-runs.ts
|
|
1042
|
+
import { Command as Command17 } from "commander";
|
|
1043
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
1044
|
+
import { join as join2 } from "path";
|
|
1045
|
+
import { homedir as homedir2 } from "os";
|
|
1046
|
+
var personaRunsCmd = new Command17("persona-runs").description(
|
|
1047
|
+
"Manage per-persona per-session token usage rows"
|
|
1048
|
+
);
|
|
1049
|
+
personaRunsCmd.command("list <slug>").description("List persona runs for a pebble").option("--bet-id <id>", "Filter by bet_id").option("--plan-id <id>", "Filter by plan_id").option("--persona <name>", "Filter by persona name").option("--limit <n>", "Max rows", "50").action(async (slug, opts) => {
|
|
1050
|
+
const params = {};
|
|
1051
|
+
if (opts.betId) params.bet_id = opts.betId;
|
|
1052
|
+
if (opts.planId) params.plan_id = opts.planId;
|
|
1053
|
+
if (opts.persona) params.persona = opts.persona;
|
|
1054
|
+
if (opts.limit) params.limit = String(opts.limit);
|
|
1055
|
+
const res = await apiRequest(
|
|
1056
|
+
`/pebbles/${slug}/persona-runs`,
|
|
1057
|
+
{ params }
|
|
1058
|
+
);
|
|
1059
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
1060
|
+
if (res.error) process.exit(1);
|
|
1061
|
+
});
|
|
1062
|
+
personaRunsCmd.command("get <slug> <id>").description("Get a persona run by id").action(async (slug, id) => {
|
|
1063
|
+
const res = await apiRequest(
|
|
1064
|
+
`/pebbles/${slug}/persona-runs/${id}`
|
|
1065
|
+
);
|
|
1066
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
1067
|
+
if (res.error) process.exit(1);
|
|
1068
|
+
});
|
|
1069
|
+
personaRunsCmd.command("create <slug>").description("Create/upsert a persona run from a JSON file").requiredOption("--file <path>", "JSON payload matching PersonaRunCreate").action(async (slug, opts) => {
|
|
1070
|
+
const body = JSON.parse(readFileSync12(opts.file, "utf-8"));
|
|
1071
|
+
const res = await apiRequest(`/pebbles/${slug}/persona-runs`, {
|
|
1072
|
+
method: "POST",
|
|
1073
|
+
body
|
|
1074
|
+
});
|
|
1075
|
+
process.stdout.write(JSON.stringify(res) + "\n");
|
|
1076
|
+
if (res.error) process.exit(1);
|
|
1077
|
+
});
|
|
1078
|
+
personaRunsCmd.command("capture").description("Read marker + transcript, compute tokens, POST to endpoint (hook path)").requiredOption("--session-id <id>", "Claude Code session id").requiredOption("--transcript <path>", "Path to transcript JSONL").option("--marker <path>", "Explicit marker path (default: ~/.claude/odin-context/<session-id>.json)").action(async (opts) => {
|
|
1079
|
+
const markerPath = opts.marker ?? join2(homedir2(), ".claude", "odin-context", `${opts.sessionId}.json`);
|
|
1080
|
+
let marker = {};
|
|
1081
|
+
try {
|
|
1082
|
+
marker = JSON.parse(readFileSync12(markerPath, "utf-8"));
|
|
1083
|
+
} catch {
|
|
1084
|
+
process.exit(0);
|
|
1085
|
+
}
|
|
1086
|
+
if (!marker.pebble_slug || !marker.persona) {
|
|
1087
|
+
process.exit(0);
|
|
1088
|
+
}
|
|
1089
|
+
let input = 0, output = 0, cacheRead = 0, cacheCreate = 0;
|
|
1090
|
+
let minTs = null, maxTs = null;
|
|
1091
|
+
try {
|
|
1092
|
+
const raw = readFileSync12(opts.transcript, "utf-8");
|
|
1093
|
+
for (const line of raw.split("\n")) {
|
|
1094
|
+
if (!line.trim()) continue;
|
|
1095
|
+
let obj;
|
|
1096
|
+
try {
|
|
1097
|
+
obj = JSON.parse(line);
|
|
1098
|
+
} catch {
|
|
1099
|
+
continue;
|
|
1100
|
+
}
|
|
1101
|
+
if (obj.type !== "assistant") continue;
|
|
1102
|
+
const u = obj.message?.usage;
|
|
1103
|
+
if (!u) continue;
|
|
1104
|
+
input += Number(u.input_tokens ?? 0);
|
|
1105
|
+
output += Number(u.output_tokens ?? 0);
|
|
1106
|
+
cacheRead += Number(u.cache_read_input_tokens ?? 0);
|
|
1107
|
+
cacheCreate += Number(u.cache_creation_input_tokens ?? 0);
|
|
1108
|
+
const ts = obj.timestamp;
|
|
1109
|
+
if (ts) {
|
|
1110
|
+
if (!minTs || ts < minTs) minTs = ts;
|
|
1111
|
+
if (!maxTs || ts > maxTs) maxTs = ts;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
} catch {
|
|
1115
|
+
process.exit(0);
|
|
1116
|
+
}
|
|
1117
|
+
const payload = {
|
|
1118
|
+
persona: marker.persona,
|
|
1119
|
+
session_id: opts.sessionId,
|
|
1120
|
+
input_tokens: input,
|
|
1121
|
+
output_tokens: output,
|
|
1122
|
+
cache_read_tokens: cacheRead,
|
|
1123
|
+
cache_creation_tokens: cacheCreate,
|
|
1124
|
+
bet_id: marker.bet_id ?? null,
|
|
1125
|
+
plan_id: marker.plan_id ?? null,
|
|
1126
|
+
phase_id: marker.phase_id ?? null,
|
|
1127
|
+
started_at: minTs,
|
|
1128
|
+
ended_at: maxTs
|
|
1129
|
+
};
|
|
1130
|
+
const res = await apiRequest(
|
|
1131
|
+
`/pebbles/${marker.pebble_slug}/persona-runs`,
|
|
1132
|
+
{ method: "POST", body: payload }
|
|
1133
|
+
);
|
|
1134
|
+
if (res.error) {
|
|
1135
|
+
process.stderr.write(`odin persona-runs capture: ${res.error}
|
|
1136
|
+
`);
|
|
1137
|
+
process.exit(1);
|
|
1138
|
+
}
|
|
1139
|
+
process.stdout.write(JSON.stringify(res.data) + "\n");
|
|
1140
|
+
});
|
|
1141
|
+
|
|
1142
|
+
// src/index.ts
|
|
1143
|
+
import { readFileSync as readFileSync13 } from "fs";
|
|
763
1144
|
import { fileURLToPath } from "url";
|
|
764
1145
|
import { dirname as dirname2, resolve } from "path";
|
|
765
1146
|
var __dirname = dirname2(fileURLToPath(import.meta.url));
|
|
766
|
-
var pkg = JSON.parse(
|
|
1147
|
+
var pkg = JSON.parse(readFileSync13(resolve(__dirname, "../package.json"), "utf-8"));
|
|
767
1148
|
var GOLD = "\x1B[33m";
|
|
768
1149
|
var DIM = "\x1B[2m";
|
|
769
1150
|
var RESET = "\x1B[0m";
|
|
@@ -781,7 +1162,7 @@ ${RESET}
|
|
|
781
1162
|
${DIM}${cwd}${RESET}
|
|
782
1163
|
`);
|
|
783
1164
|
}
|
|
784
|
-
var program = new
|
|
1165
|
+
var program = new Command18();
|
|
785
1166
|
program.name("odin").description("CLI for Odin \u2014 the knowledge backbone for Pebble House").version(VERSION);
|
|
786
1167
|
program.command("login").description("Authenticate with Odin via browser").action(async () => {
|
|
787
1168
|
printBanner();
|
|
@@ -808,6 +1189,11 @@ program.addCommand(versionsCmd);
|
|
|
808
1189
|
program.addCommand(guidelinesCmd);
|
|
809
1190
|
program.addCommand(rulesCmd);
|
|
810
1191
|
program.addCommand(briefsCmd);
|
|
1192
|
+
program.addCommand(blueprintsCmd);
|
|
1193
|
+
program.addCommand(betsCmd);
|
|
1194
|
+
program.addCommand(tasksCmd);
|
|
1195
|
+
program.addCommand(roadmapsCmd);
|
|
1196
|
+
program.addCommand(personaRunsCmd);
|
|
811
1197
|
process.on("exit", () => {
|
|
812
1198
|
process.stderr.write("\n\n\n\n\n");
|
|
813
1199
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pebblehouse/odin-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.10.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "CLI for Odin — the knowledge backbone for Pebble House",
|
|
6
6
|
"bin": {
|
|
@@ -11,7 +11,10 @@
|
|
|
11
11
|
],
|
|
12
12
|
"scripts": {
|
|
13
13
|
"build": "tsup",
|
|
14
|
-
"dev": "tsup --watch"
|
|
14
|
+
"dev": "tsup --watch",
|
|
15
|
+
"test": "vitest run",
|
|
16
|
+
"test:watch": "vitest",
|
|
17
|
+
"test:coverage": "vitest run --coverage"
|
|
15
18
|
},
|
|
16
19
|
"keywords": [
|
|
17
20
|
"odin",
|
|
@@ -36,7 +39,9 @@
|
|
|
36
39
|
"open": "^10.0.0"
|
|
37
40
|
},
|
|
38
41
|
"devDependencies": {
|
|
42
|
+
"@vitest/coverage-v8": "^4.1.2",
|
|
39
43
|
"tsup": "^8.0.0",
|
|
40
|
-
"typescript": "^5.3.3"
|
|
44
|
+
"typescript": "^5.3.3",
|
|
45
|
+
"vitest": "^4.1.2"
|
|
41
46
|
}
|
|
42
47
|
}
|