@jentrix/cli 0.5.14 → 0.5.16
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/claude-plugin/commands/jentrix-align.md +23 -6
- package/claude-plugin/commands/jentrix-checkpoint.md +1 -0
- package/claude-plugin/commands/jentrix-connect.md +1 -0
- package/claude-plugin/commands/jentrix-end.md +1 -0
- package/claude-plugin/commands/jentrix-plan.md +1 -0
- package/claude-plugin/commands/jentrix-review.md +1 -0
- package/claude-plugin/commands/jentrix-status.md +1 -0
- package/codex-plugin/plugins/jentrix/.codex-plugin/plugin.json +1 -1
- package/codex-plugin/plugins/jentrix/skills/jentrix-align/SKILL.md +15 -2
- package/codex-plugin/plugins/jentrix/skills/jentrix-checkpoint/SKILL.md +1 -0
- package/codex-plugin/plugins/jentrix/skills/jentrix-connect/SKILL.md +1 -0
- package/codex-plugin/plugins/jentrix/skills/jentrix-end/SKILL.md +1 -0
- package/codex-plugin/plugins/jentrix/skills/jentrix-plan/SKILL.md +1 -0
- package/codex-plugin/plugins/jentrix/skills/jentrix-review/SKILL.md +1 -0
- package/codex-plugin/plugins/jentrix/skills/jentrix-status/SKILL.md +1 -0
- package/dist/main.js +317 -153
- package/package.json +1 -1
- package/surface.json +0 -242
package/dist/main.js
CHANGED
|
@@ -10334,10 +10334,11 @@ import {
|
|
|
10334
10334
|
mkdirSync as mkdirSync4,
|
|
10335
10335
|
openSync as openSync3,
|
|
10336
10336
|
readFileSync as readFileSync7,
|
|
10337
|
-
statSync as statSync3
|
|
10337
|
+
statSync as statSync3,
|
|
10338
|
+
writeFileSync as writeFileSync5
|
|
10338
10339
|
} from "node:fs";
|
|
10339
10340
|
import { homedir as homedir2 } from "node:os";
|
|
10340
|
-
import { basename as basename2, delimiter as delimiter2, dirname as dirname4, join as
|
|
10341
|
+
import { basename as basename2, delimiter as delimiter2, dirname as dirname4, join as join7, resolve as resolve4 } from "node:path";
|
|
10341
10342
|
import { fileURLToPath } from "node:url";
|
|
10342
10343
|
|
|
10343
10344
|
// node_modules/.pnpm/commander@14.0.3/node_modules/commander/esm.mjs
|
|
@@ -20882,7 +20883,10 @@ var ACCESS_TOKEN_PREFIX = "tmo_";
|
|
|
20882
20883
|
var REFRESH_TOKEN_PREFIX = "tmr_";
|
|
20883
20884
|
function parseTokenResponse(body) {
|
|
20884
20885
|
if (typeof body !== "object" || body === null) {
|
|
20885
|
-
throw new OAuthTokenError(
|
|
20886
|
+
throw new OAuthTokenError(
|
|
20887
|
+
"invalid_response",
|
|
20888
|
+
"token response is not an object"
|
|
20889
|
+
);
|
|
20886
20890
|
}
|
|
20887
20891
|
const json = body;
|
|
20888
20892
|
if (typeof json.error === "string") {
|
|
@@ -20962,7 +20966,7 @@ function refreshAccessToken(input) {
|
|
|
20962
20966
|
|
|
20963
20967
|
// src/client.ts
|
|
20964
20968
|
var CLI_NAME = "stacks-cli";
|
|
20965
|
-
var CLI_VERSION = "0.5.
|
|
20969
|
+
var CLI_VERSION = "0.5.16";
|
|
20966
20970
|
var DEAD_TOKEN_MESSAGE = "authentication failed (HTTP 401): the token is dead or expired \u2014 mint a new PAT at /account/tokens on your Jentrix server and update STACKS_TOKEN (or --token / the config file).";
|
|
20967
20971
|
var RELOGIN_MESSAGE = "OAuth session expired and could not be refreshed \u2014 run `jentrix login` to sign in again.";
|
|
20968
20972
|
function originOf2(url2) {
|
|
@@ -21313,6 +21317,15 @@ var PROJECT_NAME_PROMPT = "Name for the new project \u2014 your own words, exact
|
|
|
21313
21317
|
function buildAlignDisclosures(catalog) {
|
|
21314
21318
|
const disclosures = [];
|
|
21315
21319
|
const repo = catalog.detection.repoOwnerName;
|
|
21320
|
+
const previous = catalog.previous;
|
|
21321
|
+
if (previous && previous.usedFor.length > 0) {
|
|
21322
|
+
const parts = previous.usedFor.map(
|
|
21323
|
+
(kind) => kind === "project" ? `project ${previous.projectName}` : kind === "work_item" ? `task ${previous.taskKey ?? "session-level"}` : `owner ${previous.ownerLabel}`
|
|
21324
|
+
);
|
|
21325
|
+
disclosures.push(
|
|
21326
|
+
`continuing this checkout's previous alignment \u2014 ${parts.join(" \xB7 ")} (choose Re-answer, or run with --fresh, to change)`
|
|
21327
|
+
);
|
|
21328
|
+
}
|
|
21316
21329
|
if (catalog.chosenProjectId === "__pending__") {
|
|
21317
21330
|
const name = catalog.newProjectName ?? "the new project";
|
|
21318
21331
|
disclosures.push(
|
|
@@ -21417,7 +21430,10 @@ function buildAlignQuestions(catalog) {
|
|
|
21417
21430
|
...new Map(
|
|
21418
21431
|
[
|
|
21419
21432
|
...catalog.candidates.map((candidate) => candidate.workspace),
|
|
21420
|
-
...catalog.pinnedWorkspace ? [catalog.pinnedWorkspace] : []
|
|
21433
|
+
...catalog.pinnedWorkspace ? [catalog.pinnedWorkspace] : [],
|
|
21434
|
+
// D4: a repo that matches nothing offers the caller's own workspaces
|
|
21435
|
+
// instead of a lone "Other…" — the same skip/ask rules apply to them.
|
|
21436
|
+
...catalog.candidates.length === 0 ? catalog.workspaces ?? [] : []
|
|
21421
21437
|
].map((ws) => [ws.id, ws])
|
|
21422
21438
|
).values()
|
|
21423
21439
|
];
|
|
@@ -21645,6 +21661,14 @@ function buildAlignQuestions(catalog) {
|
|
|
21645
21661
|
};
|
|
21646
21662
|
} else if (project.status !== "answered" || catalog.members === null) {
|
|
21647
21663
|
owner = { id: "owner", prompt: "Owner", status: "pending", choices: [] };
|
|
21664
|
+
} else if (catalog.members.length === 1) {
|
|
21665
|
+
owner = {
|
|
21666
|
+
id: "owner",
|
|
21667
|
+
prompt: "Owner",
|
|
21668
|
+
status: "skipped",
|
|
21669
|
+
answer: catalog.members[0].id,
|
|
21670
|
+
choices: []
|
|
21671
|
+
};
|
|
21648
21672
|
} else {
|
|
21649
21673
|
owner = {
|
|
21650
21674
|
id: "owner",
|
|
@@ -21663,7 +21687,8 @@ function buildAlignQuestions(catalog) {
|
|
|
21663
21687
|
};
|
|
21664
21688
|
}
|
|
21665
21689
|
const targetKnown = project.status === "answered" && (projectName.status === "answered" || projectName.status === "skipped");
|
|
21666
|
-
const ready = targetKnown && workItem.status === "answered" && (taskTitle.status === "answered" || taskTitle.status === "skipped") && owner
|
|
21690
|
+
const ready = targetKnown && workItem.status === "answered" && (taskTitle.status === "answered" || taskTitle.status === "skipped") && // D5: a sole-member owner is resolved by detection, like the workspace.
|
|
21691
|
+
(owner.status === "answered" || owner.status === "skipped");
|
|
21667
21692
|
const disclosures = targetKnown ? buildAlignDisclosures(catalog) : [];
|
|
21668
21693
|
const confirmation = {
|
|
21669
21694
|
id: "confirmation",
|
|
@@ -23679,6 +23704,78 @@ async function openTasksForProject(caller, boardIds) {
|
|
|
23679
23704
|
tasks.sort((a, b) => b.number - a.number);
|
|
23680
23705
|
return tasks.slice(0, MAX_WORK_ITEMS).map(({ id, key, title }) => ({ id, key, title }));
|
|
23681
23706
|
}
|
|
23707
|
+
async function workspaceProjects(caller, workspaceId) {
|
|
23708
|
+
const listed = await callStructured(caller, "list_projects", { workspaceId });
|
|
23709
|
+
return (listed.projects ?? []).filter((project) => project.archivedAt === null).map(({ id, name, slug }) => ({ id, name, slug }));
|
|
23710
|
+
}
|
|
23711
|
+
async function workspaceMembers(caller, workspaceId) {
|
|
23712
|
+
const result = await callStructured(caller, "list_members", { workspaceId });
|
|
23713
|
+
return (result.members ?? []).map((member) => ({
|
|
23714
|
+
id: member.userId ?? member.id ?? "",
|
|
23715
|
+
name: member.name,
|
|
23716
|
+
email: member.email
|
|
23717
|
+
}));
|
|
23718
|
+
}
|
|
23719
|
+
async function loadProjectContext(caller, catalog, projectId) {
|
|
23720
|
+
try {
|
|
23721
|
+
const project = await projectBoards(caller, projectId);
|
|
23722
|
+
catalog.repoLinked = repoLinkedOf(catalog, projectId, project.repoOwners);
|
|
23723
|
+
catalog.projectHasBoard = project.boardIds.length > 0;
|
|
23724
|
+
catalog.openTasks = await openTasksForProject(caller, project.boardIds);
|
|
23725
|
+
catalog.members = await workspaceMembers(caller, project.workspaceId);
|
|
23726
|
+
return true;
|
|
23727
|
+
} catch {
|
|
23728
|
+
return false;
|
|
23729
|
+
}
|
|
23730
|
+
}
|
|
23731
|
+
async function previousAlignmentOf(caller, newest, prefetched = null) {
|
|
23732
|
+
try {
|
|
23733
|
+
const session = prefetched ?? await callStructured(caller, "get_agent_session", {
|
|
23734
|
+
sessionId: newest.sessionId
|
|
23735
|
+
});
|
|
23736
|
+
const a = session.alignment;
|
|
23737
|
+
if (!a?.project?.id || !a.owner?.id) return null;
|
|
23738
|
+
return {
|
|
23739
|
+
sessionId: newest.sessionId,
|
|
23740
|
+
workspaceId: a.workspace?.id ?? newest.workspaceId,
|
|
23741
|
+
project: { id: a.project.id, name: a.project.name ?? a.project.id },
|
|
23742
|
+
task: a.task?.id ? { id: a.task.id, key: a.task.key ?? a.task.id } : null,
|
|
23743
|
+
owner: {
|
|
23744
|
+
id: a.owner.id,
|
|
23745
|
+
label: a.owner.name ?? a.owner.email ?? a.owner.id
|
|
23746
|
+
}
|
|
23747
|
+
};
|
|
23748
|
+
} catch {
|
|
23749
|
+
return null;
|
|
23750
|
+
}
|
|
23751
|
+
}
|
|
23752
|
+
function prefillFromPrevious(previous, catalog, projectPrefilled) {
|
|
23753
|
+
const sameProject = projectPrefilled || catalog.chosenProjectId === previous.project.id;
|
|
23754
|
+
if (!sameProject) return null;
|
|
23755
|
+
const usedFor = [];
|
|
23756
|
+
if (projectPrefilled) usedFor.push("project");
|
|
23757
|
+
if (catalog.chosenWorkItem === null && catalog.openTasks !== null) {
|
|
23758
|
+
if (previous.task === null) {
|
|
23759
|
+
catalog.chosenWorkItem = "__none__";
|
|
23760
|
+
usedFor.push("work_item");
|
|
23761
|
+
} else if (catalog.openTasks.some((t) => t.id === previous.task.id)) {
|
|
23762
|
+
catalog.chosenWorkItem = previous.task.id;
|
|
23763
|
+
usedFor.push("work_item");
|
|
23764
|
+
}
|
|
23765
|
+
}
|
|
23766
|
+
if (catalog.chosenOwnerId === null && catalog.members?.some((m) => m.id === previous.owner.id)) {
|
|
23767
|
+
catalog.chosenOwnerId = previous.owner.id;
|
|
23768
|
+
usedFor.push("owner");
|
|
23769
|
+
}
|
|
23770
|
+
if (usedFor.length === 0) return null;
|
|
23771
|
+
return {
|
|
23772
|
+
sessionId: previous.sessionId,
|
|
23773
|
+
projectName: previous.project.name,
|
|
23774
|
+
taskKey: previous.task?.key ?? null,
|
|
23775
|
+
ownerLabel: previous.owner.label,
|
|
23776
|
+
usedFor
|
|
23777
|
+
};
|
|
23778
|
+
}
|
|
23682
23779
|
async function resolveCandidates(caller, repoOwnerName) {
|
|
23683
23780
|
try {
|
|
23684
23781
|
const result = await callStructured(caller, "resolve_projects_for_repo", {
|
|
@@ -23840,32 +23937,46 @@ function producerFromFlags(flags) {
|
|
|
23840
23937
|
if (!label) return null;
|
|
23841
23938
|
return { label, emoji: flags.agentEmoji?.trim() || null };
|
|
23842
23939
|
}
|
|
23843
|
-
async function buildCatalog(caller,
|
|
23940
|
+
async function buildCatalog(caller, flags, detection) {
|
|
23844
23941
|
const context = await callStructured(caller, "get_token_context", {});
|
|
23845
23942
|
let pinnedWorkspace = null;
|
|
23846
23943
|
if (context.workspacePinned === true && typeof context.workspaceId === "string") {
|
|
23847
|
-
const
|
|
23848
|
-
const match = (
|
|
23944
|
+
const workspaces2 = await callStructured(caller, "list_workspaces", {});
|
|
23945
|
+
const match = (workspaces2.workspaces ?? []).find((ws) => ws.id === context.workspaceId);
|
|
23849
23946
|
pinnedWorkspace = {
|
|
23850
23947
|
id: context.workspaceId,
|
|
23851
23948
|
name: match?.name ?? "pinned"
|
|
23852
23949
|
};
|
|
23853
23950
|
}
|
|
23854
|
-
const
|
|
23951
|
+
const flagWorkspaceId = flags.workspace?.trim() ? await resolveWorkspaceFlag(
|
|
23855
23952
|
caller,
|
|
23856
|
-
|
|
23857
|
-
|
|
23953
|
+
flags.workspace.trim(),
|
|
23954
|
+
pinnedWorkspace
|
|
23955
|
+
) : null;
|
|
23956
|
+
const { candidates: matched, pinned: discoveryPinned } = await resolveCandidates(caller, detection.repoOwnerName);
|
|
23957
|
+
const candidates = flagWorkspaceId ? matched.filter((candidate) => candidate.workspace.id === flagWorkspaceId) : matched;
|
|
23958
|
+
let workspaces = null;
|
|
23959
|
+
if (matched.length === 0 && !pinnedWorkspace && !flagWorkspaceId) {
|
|
23960
|
+
const listed = await callStructured(caller, "list_workspaces", {});
|
|
23961
|
+
workspaces = (listed.workspaces ?? []).map(({ id, name, slug }) => ({ id, name, slug }));
|
|
23962
|
+
}
|
|
23963
|
+
const sole = (rows) => rows.length === 1 && !isArchiveNamed(rows[0].name) ? rows[0] : null;
|
|
23964
|
+
const candidateWorkspaces = [
|
|
23965
|
+
...new Map(candidates.map((c) => [c.workspace.id, c.workspace])).values()
|
|
23966
|
+
];
|
|
23967
|
+
const resolvedWorkspaceId = flagWorkspaceId ?? pinnedWorkspace?.id ?? sole(workspaces ?? [])?.id ?? sole(candidateWorkspaces)?.id ?? null;
|
|
23858
23968
|
let otherProjects = [];
|
|
23859
23969
|
let discoveryNotice = null;
|
|
23860
|
-
if (
|
|
23970
|
+
if (resolvedWorkspaceId) {
|
|
23861
23971
|
try {
|
|
23862
|
-
|
|
23863
|
-
|
|
23864
|
-
|
|
23865
|
-
|
|
23866
|
-
discoveryNotice = "repo discovery is unavailable for a workspace-pinned token \u2014 listing every project in the pinned workspace instead (repo match unverified)";
|
|
23972
|
+
otherProjects = await workspaceProjects(caller, resolvedWorkspaceId);
|
|
23973
|
+
if (discoveryPinned) {
|
|
23974
|
+
discoveryNotice = "repo discovery is unavailable for a workspace-pinned token \u2014 listing every project in the pinned workspace instead (repo match unverified)";
|
|
23975
|
+
}
|
|
23867
23976
|
} catch {
|
|
23868
|
-
|
|
23977
|
+
if (discoveryPinned) {
|
|
23978
|
+
discoveryNotice = "repo discovery is unavailable for a workspace-pinned token and the workspace project list could not be loaded \u2014 pass --project <id-or-slug>, or --new-project <name> to create one";
|
|
23979
|
+
}
|
|
23869
23980
|
}
|
|
23870
23981
|
}
|
|
23871
23982
|
const wantsCreate = Boolean(flags.newProject && flags.newProject.trim()) || flags.project === "__create__";
|
|
@@ -23879,71 +23990,31 @@ async function buildCatalog(caller, deps2, flags, detection) {
|
|
|
23879
23990
|
(project) => project.id === flags.project || project.slug === flags.project
|
|
23880
23991
|
);
|
|
23881
23992
|
chosenProjectId = match?.id ?? flags.project;
|
|
23882
|
-
} else if (candidates.length === 1) {
|
|
23993
|
+
} else if (candidates.length === 1 && matched.length === 1) {
|
|
23883
23994
|
chosenProjectId = candidates[0].id;
|
|
23884
23995
|
}
|
|
23885
|
-
|
|
23886
|
-
|
|
23887
|
-
let repoLinked = null;
|
|
23888
|
-
let projectHasBoard = null;
|
|
23889
|
-
if (wantsCreate) {
|
|
23890
|
-
openTasks = [];
|
|
23891
|
-
const workspaceId = pinnedWorkspace?.id ?? null;
|
|
23892
|
-
if (workspaceId) {
|
|
23893
|
-
try {
|
|
23894
|
-
const memberResult = await callStructured(caller, "list_members", {
|
|
23895
|
-
workspaceId
|
|
23896
|
-
});
|
|
23897
|
-
members = memberResult.members?.map((member) => ({
|
|
23898
|
-
id: member.userId ?? member.id ?? "",
|
|
23899
|
-
name: member.name,
|
|
23900
|
-
email: member.email
|
|
23901
|
-
})) ?? [];
|
|
23902
|
-
} catch {
|
|
23903
|
-
members = [];
|
|
23904
|
-
}
|
|
23905
|
-
} else {
|
|
23906
|
-
members = [];
|
|
23907
|
-
}
|
|
23908
|
-
} else if (chosenProjectId) {
|
|
23909
|
-
try {
|
|
23910
|
-
const project = await projectBoards(caller, chosenProjectId);
|
|
23911
|
-
repoLinked = repoLinkedOf(
|
|
23912
|
-
{ candidates, detection },
|
|
23913
|
-
chosenProjectId,
|
|
23914
|
-
project.repoOwners
|
|
23915
|
-
);
|
|
23916
|
-
projectHasBoard = project.boardIds.length > 0;
|
|
23917
|
-
openTasks = await openTasksForProject(caller, project.boardIds);
|
|
23918
|
-
const memberResult = await callStructured(caller, "list_members", {
|
|
23919
|
-
workspaceId: project.workspaceId
|
|
23920
|
-
});
|
|
23921
|
-
members = memberResult.members?.map((member) => ({
|
|
23922
|
-
id: member.userId ?? member.id ?? "",
|
|
23923
|
-
name: member.name,
|
|
23924
|
-
email: member.email
|
|
23925
|
-
})) ?? null;
|
|
23926
|
-
} catch {
|
|
23927
|
-
}
|
|
23928
|
-
}
|
|
23929
|
-
const chosenWorkspaceId = candidates.find((candidate) => candidate.id === chosenProjectId)?.workspace.id ?? pinnedWorkspace?.id ?? null;
|
|
23930
|
-
return {
|
|
23996
|
+
const chosenWorkspaceId = candidates.find((candidate) => candidate.id === chosenProjectId)?.workspace.id ?? flagWorkspaceId ?? pinnedWorkspace?.id ?? null;
|
|
23997
|
+
const catalog = {
|
|
23931
23998
|
detection,
|
|
23932
23999
|
pinnedWorkspace,
|
|
23933
24000
|
candidates,
|
|
23934
24001
|
otherProjects,
|
|
23935
24002
|
discoveryNotice,
|
|
24003
|
+
workspaces,
|
|
23936
24004
|
chosenWorkspaceId,
|
|
23937
24005
|
chosenProjectId,
|
|
23938
|
-
openTasks,
|
|
23939
|
-
members,
|
|
23940
|
-
|
|
24006
|
+
openTasks: null,
|
|
24007
|
+
members: null,
|
|
24008
|
+
// D5 (STA-168): the server now says who the caller is — the owner
|
|
24009
|
+
// question's default, and what `--yes` falls back to. Null from an older
|
|
24010
|
+
// server, which keeps the explicit-owner refusal.
|
|
24011
|
+
operatorUserId: typeof context.userId === "string" ? context.userId : null,
|
|
23941
24012
|
chosenWorkItem: flags.task ? flags.task : flags.newTask ? "__new__" : flags.sessionLevel ? "__none__" : null,
|
|
23942
24013
|
chosenOwnerId: flags.owner ?? null,
|
|
23943
24014
|
newTaskTitle: flags.newTask?.trim() || null,
|
|
23944
24015
|
newProjectName: flags.newProject?.trim() || null,
|
|
23945
|
-
repoLinked,
|
|
23946
|
-
projectHasBoard,
|
|
24016
|
+
repoLinked: null,
|
|
24017
|
+
projectHasBoard: null,
|
|
23947
24018
|
// STA-11: filled by runAlign (it needs the checkout root) — the ordering
|
|
23948
24019
|
// preference for the workspace question.
|
|
23949
24020
|
lastAlignedWorkspaceId: null,
|
|
@@ -23959,8 +24030,24 @@ async function buildCatalog(caller, deps2, flags, detection) {
|
|
|
23959
24030
|
binding: null,
|
|
23960
24031
|
mcpConfig: null,
|
|
23961
24032
|
// Filled by runAlign — it needs the checkout's marker session.
|
|
23962
|
-
priorArtifacts: null
|
|
24033
|
+
priorArtifacts: null,
|
|
24034
|
+
// D7: filled by runAlign (it needs the checkout's newest marker).
|
|
24035
|
+
previous: null
|
|
23963
24036
|
};
|
|
24037
|
+
if (wantsCreate) {
|
|
24038
|
+
catalog.openTasks = [];
|
|
24039
|
+
catalog.members = [];
|
|
24040
|
+
if (resolvedWorkspaceId) {
|
|
24041
|
+
try {
|
|
24042
|
+
catalog.members = await workspaceMembers(caller, resolvedWorkspaceId);
|
|
24043
|
+
} catch {
|
|
24044
|
+
catalog.members = [];
|
|
24045
|
+
}
|
|
24046
|
+
}
|
|
24047
|
+
} else if (chosenProjectId) {
|
|
24048
|
+
await loadProjectContext(caller, catalog, chosenProjectId);
|
|
24049
|
+
}
|
|
24050
|
+
return catalog;
|
|
23964
24051
|
}
|
|
23965
24052
|
async function priorArtifactsOf(caller, markerSession) {
|
|
23966
24053
|
const current = markerSession?.alignment?.task;
|
|
@@ -24305,7 +24392,7 @@ async function runAlign(flags, deps2) {
|
|
|
24305
24392
|
provider: detected.provider
|
|
24306
24393
|
};
|
|
24307
24394
|
return await withCaller(deps2, async (caller, target) => {
|
|
24308
|
-
const catalog = await buildCatalog(caller,
|
|
24395
|
+
const catalog = await buildCatalog(caller, flags, detection);
|
|
24309
24396
|
const marker = readAlignmentMarker(
|
|
24310
24397
|
deps2.configPath,
|
|
24311
24398
|
inspection.root,
|
|
@@ -24317,7 +24404,8 @@ async function runAlign(flags, deps2) {
|
|
|
24317
24404
|
flags.capture,
|
|
24318
24405
|
marker !== null && markerHost !== null && isHostCapturing(deps2, marker.sessionId, markerHost)
|
|
24319
24406
|
);
|
|
24320
|
-
|
|
24407
|
+
const newest = newestAlignmentMarker(deps2.configPath, inspection.root);
|
|
24408
|
+
catalog.lastAlignedWorkspaceId = newest?.workspaceId ?? null;
|
|
24321
24409
|
catalog.binding = {
|
|
24322
24410
|
providerSessionId: detected.providerSessionId,
|
|
24323
24411
|
transcriptPath: detected.transcriptPath,
|
|
@@ -24343,6 +24431,31 @@ async function runAlign(flags, deps2) {
|
|
|
24343
24431
|
}
|
|
24344
24432
|
}
|
|
24345
24433
|
const markerActive = markerSession !== null && (markerSession.status === "ACTIVE" || markerSession.status === "STARTING");
|
|
24434
|
+
let workspaceFromPrevious = false;
|
|
24435
|
+
if (!flags.fresh && !markerActive && newest) {
|
|
24436
|
+
const previous = await previousAlignmentOf(
|
|
24437
|
+
caller,
|
|
24438
|
+
newest,
|
|
24439
|
+
// This provider session's own (ended) marker is the newest: reuse
|
|
24440
|
+
// the read above rather than paying for it twice.
|
|
24441
|
+
marker?.sessionId === newest.sessionId ? markerSession : null
|
|
24442
|
+
);
|
|
24443
|
+
if (previous) {
|
|
24444
|
+
const projectPrefilled = catalog.chosenProjectId === null && await loadProjectContext(caller, catalog, previous.project.id);
|
|
24445
|
+
if (projectPrefilled) {
|
|
24446
|
+
catalog.chosenProjectId = previous.project.id;
|
|
24447
|
+
if (catalog.chosenWorkspaceId === null) {
|
|
24448
|
+
catalog.chosenWorkspaceId = previous.workspaceId;
|
|
24449
|
+
workspaceFromPrevious = true;
|
|
24450
|
+
}
|
|
24451
|
+
}
|
|
24452
|
+
catalog.previous = prefillFromPrevious(
|
|
24453
|
+
previous,
|
|
24454
|
+
catalog,
|
|
24455
|
+
projectPrefilled
|
|
24456
|
+
);
|
|
24457
|
+
}
|
|
24458
|
+
}
|
|
24346
24459
|
catalog.priorArtifacts = await priorArtifactsOf(caller, markerSession);
|
|
24347
24460
|
const preconditions = await doctorLocalChecks(deps2);
|
|
24348
24461
|
preconditions.push(sessionBindingCheck(detected));
|
|
@@ -24427,14 +24540,7 @@ async function runAlign(flags, deps2) {
|
|
|
24427
24540
|
let pendingCreateProject = null;
|
|
24428
24541
|
if (flags.newTask) workItem = `__new__:${flags.newTask}`;
|
|
24429
24542
|
if (flags.newProject && flags.newProject.trim()) {
|
|
24430
|
-
|
|
24431
|
-
if (flags.workspace?.trim()) {
|
|
24432
|
-
workspaceId = await resolveWorkspaceFlag(
|
|
24433
|
-
caller,
|
|
24434
|
-
flags.workspace.trim(),
|
|
24435
|
-
catalog.pinnedWorkspace ?? null
|
|
24436
|
-
);
|
|
24437
|
-
}
|
|
24543
|
+
const workspaceId = buildAlignQuestions(catalog)[0]?.answer ?? null;
|
|
24438
24544
|
if (!workspaceId) {
|
|
24439
24545
|
throw new UsageError(
|
|
24440
24546
|
"--new-project needs a resolved workspace (a workspace-pinned token, a repo-matched candidate, or --workspace <id-or-slug>) \u2014 none resolved here"
|
|
@@ -24495,11 +24601,21 @@ async function runAlign(flags, deps2) {
|
|
|
24495
24601
|
catalog.newProjectName = null;
|
|
24496
24602
|
pendingCreateProject = null;
|
|
24497
24603
|
createWorkspaceId = null;
|
|
24604
|
+
catalog.previous = null;
|
|
24605
|
+
if (workspaceFromPrevious) {
|
|
24606
|
+
catalog.chosenWorkspaceId = null;
|
|
24607
|
+
workspaceFromPrevious = false;
|
|
24608
|
+
}
|
|
24498
24609
|
continue;
|
|
24499
24610
|
}
|
|
24500
24611
|
const answer = await askQuestion(deps2, next);
|
|
24501
24612
|
if (next.id === "workspace") {
|
|
24502
24613
|
catalog.chosenWorkspaceId = answer;
|
|
24614
|
+
try {
|
|
24615
|
+
catalog.otherProjects = await workspaceProjects(caller, answer);
|
|
24616
|
+
} catch {
|
|
24617
|
+
catalog.otherProjects = [];
|
|
24618
|
+
}
|
|
24503
24619
|
} else if (next.id === "task_title") {
|
|
24504
24620
|
newTaskTitle = answer;
|
|
24505
24621
|
} else if (next.id === "project_name") {
|
|
@@ -24515,7 +24631,7 @@ async function runAlign(flags, deps2) {
|
|
|
24515
24631
|
};
|
|
24516
24632
|
} else if (next.id === "project") {
|
|
24517
24633
|
if (answer === "__create__") {
|
|
24518
|
-
const workspaceId =
|
|
24634
|
+
const workspaceId = questions[0]?.answer ?? null;
|
|
24519
24635
|
if (!workspaceId) {
|
|
24520
24636
|
throw new UsageError(
|
|
24521
24637
|
"cannot create a project without a resolved workspace \u2014 pick a workspace first"
|
|
@@ -24527,16 +24643,7 @@ async function runAlign(flags, deps2) {
|
|
|
24527
24643
|
catalog.projectHasBoard = null;
|
|
24528
24644
|
catalog.openTasks = [];
|
|
24529
24645
|
try {
|
|
24530
|
-
|
|
24531
|
-
caller,
|
|
24532
|
-
"list_members",
|
|
24533
|
-
{ workspaceId }
|
|
24534
|
-
);
|
|
24535
|
-
catalog.members = memberResult.members?.map((member) => ({
|
|
24536
|
-
id: member.userId ?? member.id ?? "",
|
|
24537
|
-
name: member.name,
|
|
24538
|
-
email: member.email
|
|
24539
|
-
})) ?? [];
|
|
24646
|
+
catalog.members = await workspaceMembers(caller, workspaceId);
|
|
24540
24647
|
} catch {
|
|
24541
24648
|
catalog.members = [];
|
|
24542
24649
|
}
|
|
@@ -24544,31 +24651,7 @@ async function runAlign(flags, deps2) {
|
|
|
24544
24651
|
} else {
|
|
24545
24652
|
projectId = answer;
|
|
24546
24653
|
}
|
|
24547
|
-
|
|
24548
|
-
const project = await projectBoards(caller, projectId);
|
|
24549
|
-
catalog.repoLinked = repoLinkedOf(
|
|
24550
|
-
catalog,
|
|
24551
|
-
projectId,
|
|
24552
|
-
project.repoOwners
|
|
24553
|
-
);
|
|
24554
|
-
catalog.projectHasBoard = project.boardIds.length > 0;
|
|
24555
|
-
catalog.openTasks = await openTasksForProject(
|
|
24556
|
-
caller,
|
|
24557
|
-
project.boardIds
|
|
24558
|
-
);
|
|
24559
|
-
const memberResult = await callStructured(
|
|
24560
|
-
caller,
|
|
24561
|
-
"list_members",
|
|
24562
|
-
{
|
|
24563
|
-
workspaceId: project.workspaceId
|
|
24564
|
-
}
|
|
24565
|
-
);
|
|
24566
|
-
catalog.members = memberResult.members?.map((member) => ({
|
|
24567
|
-
id: member.userId ?? member.id ?? "",
|
|
24568
|
-
name: member.name,
|
|
24569
|
-
email: member.email
|
|
24570
|
-
})) ?? [];
|
|
24571
|
-
} catch {
|
|
24654
|
+
if (!await loadProjectContext(caller, catalog, projectId)) {
|
|
24572
24655
|
catalog.openTasks = [];
|
|
24573
24656
|
catalog.members = [];
|
|
24574
24657
|
catalog.repoLinked = null;
|
|
@@ -24581,10 +24664,11 @@ async function runAlign(flags, deps2) {
|
|
|
24581
24664
|
}
|
|
24582
24665
|
}
|
|
24583
24666
|
}
|
|
24667
|
+
ownerUserId ??= (catalog.members?.length === 1 ? catalog.members[0].id : null) ?? catalog.operatorUserId;
|
|
24584
24668
|
if (pendingCreateProject) {
|
|
24585
24669
|
if (!ownerUserId) {
|
|
24586
24670
|
throw new UsageError(
|
|
24587
|
-
|
|
24671
|
+
`creating a project needs an owner \u2014 answer the owner question, or pass --owner <userId> (this server did not identify you; discover members with \`jentrix tool list_members --args '{"workspaceId":"${pendingCreateProject.workspaceId}"}'\`)`
|
|
24588
24672
|
);
|
|
24589
24673
|
}
|
|
24590
24674
|
const created = await callStructured(caller, "create_project", {
|
|
@@ -24766,7 +24850,7 @@ function registerAlignCommand(program3, deps2, onExit2) {
|
|
|
24766
24850
|
"create a project in the resolved workspace (seeds a board + repo/board links), then align to it"
|
|
24767
24851
|
).option(
|
|
24768
24852
|
"--workspace <id-or-slug>",
|
|
24769
|
-
"workspace
|
|
24853
|
+
"the workspace question's flag twin: narrows the wizard's project list, and names where --new-project creates when neither a pinned token nor a repo match resolves one"
|
|
24770
24854
|
).option("--task <taskId>", "existing work item to align to").option(
|
|
24771
24855
|
"--new-task <title>",
|
|
24772
24856
|
"create a task (existing ops core) and align to it"
|
|
@@ -24799,6 +24883,9 @@ function registerAlignCommand(program3, deps2, onExit2) {
|
|
|
24799
24883
|
).option("--json", "stable JSON output").option(
|
|
24800
24884
|
"--yes",
|
|
24801
24885
|
"skip the re-align confirmation on an already-aligned session"
|
|
24886
|
+
).option(
|
|
24887
|
+
"--fresh",
|
|
24888
|
+
"ignore this checkout's previous alignment and ask every question"
|
|
24802
24889
|
).action(async (flags) => onExit2(await runAlign(flags, deps2)));
|
|
24803
24890
|
}
|
|
24804
24891
|
|
|
@@ -25104,7 +25191,9 @@ async function offerCardMint(kind, payload, flags, deps2, sessionId = null) {
|
|
|
25104
25191
|
board,
|
|
25105
25192
|
artifactId: payload.artifactId,
|
|
25106
25193
|
anchorTaskId: payload.taskId,
|
|
25107
|
-
noun: mintNoun(
|
|
25194
|
+
noun: mintNoun(
|
|
25195
|
+
ARTIFACT_TYPE_BY_PUSH_KIND[kind] ?? "FINDINGS"
|
|
25196
|
+
),
|
|
25108
25197
|
// STA-128: the operator's own claim title, when one was given.
|
|
25109
25198
|
artifactTitle: flags.title?.trim() || null,
|
|
25110
25199
|
blocks: Boolean(flags.blocks),
|
|
@@ -25130,7 +25219,12 @@ async function findMintBoard(deps2, taskId) {
|
|
|
25130
25219
|
const boards = (await callStructured(caller, "list_boards", { workspaceId })).boards.filter((b) => b.kind === "BUGS" && !b.archivedAt);
|
|
25131
25220
|
const candidates = [
|
|
25132
25221
|
...boards,
|
|
25133
|
-
...task.boardId ? [
|
|
25222
|
+
...task.boardId ? [
|
|
25223
|
+
{
|
|
25224
|
+
id: String(task.boardId),
|
|
25225
|
+
name: String(task.boardName ?? "board")
|
|
25226
|
+
}
|
|
25227
|
+
] : []
|
|
25134
25228
|
];
|
|
25135
25229
|
for (const board of candidates) {
|
|
25136
25230
|
const columns = (await callStructured(caller, "list_columns", { boardId: board.id })).columns;
|
|
@@ -25260,7 +25354,10 @@ async function runMintIssue(flags, deps2) {
|
|
|
25260
25354
|
function registerMintIssueCommand(artifact, deps2, onExit2) {
|
|
25261
25355
|
artifact.command("mint-issue").description(
|
|
25262
25356
|
"Mint a card from a findings/gap/issue artifact: opens a card on the workspace's BUGS board (else the task's own board), links the artifact to it, and relates it back to the source task."
|
|
25263
|
-
).requiredOption(
|
|
25357
|
+
).requiredOption(
|
|
25358
|
+
"--artifact <id>",
|
|
25359
|
+
"the findings/gap/issue artifact to link"
|
|
25360
|
+
).requiredOption(
|
|
25264
25361
|
"--from-task <id>",
|
|
25265
25362
|
"the task the artifact was pushed on (the card RELATES_TO it)"
|
|
25266
25363
|
).option(
|
|
@@ -25338,7 +25435,9 @@ function statusToExit(status) {
|
|
|
25338
25435
|
}
|
|
25339
25436
|
async function runArtifactAttach(filePath, flags, deps2) {
|
|
25340
25437
|
if (!flags.workspace && !flags.task) {
|
|
25341
|
-
deps2.writeErr(
|
|
25438
|
+
deps2.writeErr(
|
|
25439
|
+
"--workspace <id> is required (or --task <id> to resolve it)"
|
|
25440
|
+
);
|
|
25342
25441
|
return EXIT_CODES.INVALID_INPUT;
|
|
25343
25442
|
}
|
|
25344
25443
|
if (flags.kind && !PUSH_KINDS.includes(flags.kind)) {
|
|
@@ -25458,7 +25557,12 @@ async function runArtifactAttach(filePath, flags, deps2) {
|
|
|
25458
25557
|
});
|
|
25459
25558
|
const artifact = result.artifact ?? {};
|
|
25460
25559
|
deps2.writeOut(
|
|
25461
|
-
flags.json ?
|
|
25560
|
+
flags.json ? (
|
|
25561
|
+
// The id at the top level: every other artifact-producing command
|
|
25562
|
+
// answers `artifactId`, and a caller should not have to know which
|
|
25563
|
+
// envelope this one came in.
|
|
25564
|
+
JSON.stringify({ artifactId: artifact.id ?? null, ...result })
|
|
25565
|
+
) : `Attached artifact ${String(artifact.id ?? "?")} \xB7 ${String(artifact.title ?? filename)} (${type}, ${body.byteLength} bytes)`
|
|
25462
25566
|
);
|
|
25463
25567
|
return 0;
|
|
25464
25568
|
} finally {
|
|
@@ -25483,7 +25587,10 @@ function registerArtifactCommand(program3, deps2, onExit2) {
|
|
|
25483
25587
|
).option("--project <id>", "link to a project").option("--task <id>", "link to a task").option("--run <id>", "link to a run").option("--work-order <id>", "link to a Work Order").option("--decision <id>", "link to a decision").option(
|
|
25484
25588
|
"--kind <kind>",
|
|
25485
25589
|
`push kind (${PUSH_KINDS.join("|")}) \u2014 files under the same ArtifactType, hence the same work layer, as \`jentrix push\``
|
|
25486
|
-
).option("--session <id>", "attribute the artifact to a connected session").option(
|
|
25590
|
+
).option("--session <id>", "attribute the artifact to a connected session").option(
|
|
25591
|
+
"--type <type>",
|
|
25592
|
+
"ArtifactType (default inferred from --kind, else the extension, else DOC)"
|
|
25593
|
+
).option("--title <title>", "artifact title (default: the file name)").option("--json", "stable JSON output").action(
|
|
25487
25594
|
async (file, flags) => onExit2(await runArtifactAttach(file, flags, deps2))
|
|
25488
25595
|
);
|
|
25489
25596
|
return artifact;
|
|
@@ -26785,7 +26892,8 @@ function mountCommandTree(program3, plan, runtime) {
|
|
|
26785
26892
|
var WHOAMI_SCOPE_NOTE = "scope/workspace restrictions are enforced server-side and not introspectable; a FORBIDDEN (exit 3) on a write means the token lacks write/admin scope.";
|
|
26786
26893
|
function redactToken(token) {
|
|
26787
26894
|
const hint = token.length > 8 ? `\u2026${token.slice(-4)}` : "\u2026";
|
|
26788
|
-
if (token.startsWith("tmo_"))
|
|
26895
|
+
if (token.startsWith("tmo_"))
|
|
26896
|
+
return { type: "oauth", display: `tmo_${hint}` };
|
|
26789
26897
|
if (token.startsWith("tm_")) return { type: "pat", display: `tm_${hint}` };
|
|
26790
26898
|
return { type: "unknown", display: hint };
|
|
26791
26899
|
}
|
|
@@ -26851,6 +26959,7 @@ function renderHuman(report) {
|
|
|
26851
26959
|
lines.push(
|
|
26852
26960
|
` workspace: ${tc.workspacePinned ? `pinned to ${tc.workspaceId ?? "?"}` : "not pinned (all your workspaces)"}`
|
|
26853
26961
|
);
|
|
26962
|
+
if (tc.userId) lines.push(` user: ${tc.userId}`);
|
|
26854
26963
|
if (tc.displayName) {
|
|
26855
26964
|
lines.push(
|
|
26856
26965
|
` identity: ${tc.emoji ? `${tc.emoji} ` : ""}${tc.displayName}`
|
|
@@ -26874,7 +26983,8 @@ async function runWhoamiCommand(flags, deps2) {
|
|
|
26874
26983
|
const scrub = (text) => {
|
|
26875
26984
|
let out = text;
|
|
26876
26985
|
for (const secret of secrets) {
|
|
26877
|
-
if (out.includes(secret))
|
|
26986
|
+
if (out.includes(secret))
|
|
26987
|
+
out = out.split(secret).join("<redacted token>");
|
|
26878
26988
|
}
|
|
26879
26989
|
return out;
|
|
26880
26990
|
};
|
|
@@ -26969,7 +27079,9 @@ async function runWhoamiCommand(flags, deps2) {
|
|
|
26969
27079
|
note: WHOAMI_SCOPE_NOTE
|
|
26970
27080
|
};
|
|
26971
27081
|
deps2.writeOut(
|
|
26972
|
-
redact(
|
|
27082
|
+
redact(
|
|
27083
|
+
flags.json === true ? stableStringify(report) : renderHuman(report)
|
|
27084
|
+
)
|
|
26973
27085
|
);
|
|
26974
27086
|
return EXIT_CODES.OK;
|
|
26975
27087
|
} finally {
|
|
@@ -27008,6 +27120,7 @@ function parseTokenContext(rendered) {
|
|
|
27008
27120
|
grandfathered: v.grandfathered,
|
|
27009
27121
|
workspacePinned: v.workspacePinned,
|
|
27010
27122
|
workspaceId: v.workspaceId ?? null,
|
|
27123
|
+
userId: typeof v.userId === "string" ? v.userId : null,
|
|
27011
27124
|
displayName: v.displayName ?? null,
|
|
27012
27125
|
emoji: v.emoji ?? null,
|
|
27013
27126
|
rateLimit: {
|
|
@@ -28111,6 +28224,7 @@ function registerRunnerCommand(program3, deps2, onExit2) {
|
|
|
28111
28224
|
}
|
|
28112
28225
|
|
|
28113
28226
|
// src/commands/setup.ts
|
|
28227
|
+
import { join as join5 } from "node:path";
|
|
28114
28228
|
var NPM_INSTALL_TIMEOUT_MS2 = 3e5;
|
|
28115
28229
|
var SUPERSEDED = ["@jentrix/stacks-cli", "@jentrix/stacks-runner"];
|
|
28116
28230
|
var CLI_SPEC = `@jentrix/cli@${CLI_VERSION}`;
|
|
@@ -28333,21 +28447,21 @@ async function bootstrapRepository(deps2) {
|
|
|
28333
28447
|
deps2,
|
|
28334
28448
|
" Jentrix aligns sessions by repository identity, so install git (https://git-scm.com/downloads) and re-run."
|
|
28335
28449
|
);
|
|
28336
|
-
return;
|
|
28450
|
+
return false;
|
|
28337
28451
|
}
|
|
28338
28452
|
const inRepo = async () => (await deps2.git(["rev-parse", "--is-inside-work-tree"], here)).code === 0;
|
|
28339
28453
|
if (!await inRepo()) {
|
|
28340
28454
|
const reply = await deps2.readLine(
|
|
28341
28455
|
`Initialize a git repository in ${here} so agent sessions can align here? [y/N] `
|
|
28342
28456
|
);
|
|
28343
|
-
if (!/^y/i.test(reply.trim())) return;
|
|
28457
|
+
if (!/^y/i.test(reply.trim())) return false;
|
|
28344
28458
|
if ((await deps2.git(["init"], here)).code !== 0) {
|
|
28345
28459
|
say(deps2, "note: `git init` failed \u2014 skipped the repository step.");
|
|
28346
|
-
return;
|
|
28460
|
+
return false;
|
|
28347
28461
|
}
|
|
28348
28462
|
}
|
|
28349
28463
|
if ((await deps2.git(["remote", "get-url", "origin"], here)).code === 0)
|
|
28350
|
-
return;
|
|
28464
|
+
return true;
|
|
28351
28465
|
const fallback = here.split(/[/\\]/).filter(Boolean).pop().toLowerCase().replace(/ /g, "-");
|
|
28352
28466
|
say(
|
|
28353
28467
|
deps2,
|
|
@@ -28360,15 +28474,55 @@ async function bootstrapRepository(deps2) {
|
|
|
28360
28474
|
const answer = (await deps2.readLine(
|
|
28361
28475
|
"GitHub repo for origin (owner/name or full URL, blank to skip): "
|
|
28362
28476
|
)).trim();
|
|
28363
|
-
if (!answer) return;
|
|
28477
|
+
if (!answer) return true;
|
|
28364
28478
|
const url2 = /:\/\/|^[^/]+@[^:]+:/.test(answer) ? answer : /^[^/]+\/[^/]+$/.test(answer) ? `https://github.com/${answer}.git` : null;
|
|
28365
28479
|
if (!url2) {
|
|
28366
28480
|
say(deps2, `skipped \u2014 "${answer}" is neither owner/name nor a URL`);
|
|
28367
|
-
return;
|
|
28481
|
+
return true;
|
|
28368
28482
|
}
|
|
28369
28483
|
if ((await deps2.git(["remote", "add", "origin", url2], here)).code === 0) {
|
|
28370
28484
|
say(deps2, `origin \u2192 ${url2}`);
|
|
28371
28485
|
}
|
|
28486
|
+
return true;
|
|
28487
|
+
}
|
|
28488
|
+
function writeCheckoutMcpConfig(deps2, url2) {
|
|
28489
|
+
const path = join5(deps2.cwd(), ".mcp.json");
|
|
28490
|
+
if (!url2) {
|
|
28491
|
+
say(
|
|
28492
|
+
deps2,
|
|
28493
|
+
"note: skipped ./.mcp.json \u2014 no endpoint is known yet (not signed in, no --url); the first `jentrix align` here writes it."
|
|
28494
|
+
);
|
|
28495
|
+
return;
|
|
28496
|
+
}
|
|
28497
|
+
const raw = deps2.readTextFile(path);
|
|
28498
|
+
let existing = null;
|
|
28499
|
+
if (raw !== null) {
|
|
28500
|
+
try {
|
|
28501
|
+
existing = JSON.parse(raw);
|
|
28502
|
+
} catch {
|
|
28503
|
+
say(
|
|
28504
|
+
deps2,
|
|
28505
|
+
`note: ${path} is not valid JSON \u2014 left untouched (fix or remove it, then re-run; align refuses it the same way).`
|
|
28506
|
+
);
|
|
28507
|
+
return;
|
|
28508
|
+
}
|
|
28509
|
+
}
|
|
28510
|
+
const plan = planMcpServerEntry(existing, url2, null);
|
|
28511
|
+
if (plan.action === "unchanged") {
|
|
28512
|
+
say(deps2, `./.mcp.json already names ${url2}.`);
|
|
28513
|
+
return;
|
|
28514
|
+
}
|
|
28515
|
+
deps2.writeTextFile(path, renderMcpConfig(plan.next));
|
|
28516
|
+
say(
|
|
28517
|
+
deps2,
|
|
28518
|
+
plan.action === "repoint" ? `Repointed the "jentrix" MCP server in ./.mcp.json: ${plan.previousUrl} \u2192 ${url2} (Claude Code reads it at session start; no credential inside).` : `Wrote ./.mcp.json \u2192 ${url2} (Claude Code reads it at session start; no credential inside).`
|
|
28519
|
+
);
|
|
28520
|
+
if (plan.removedAuthorization) {
|
|
28521
|
+
say(
|
|
28522
|
+
deps2,
|
|
28523
|
+
" Removed its Authorization header: a header suppresses the client's OAuth discovery. `jentrix align --pat` puts it back for a headless checkout."
|
|
28524
|
+
);
|
|
28525
|
+
}
|
|
28372
28526
|
}
|
|
28373
28527
|
async function runSetupCommand(flags, deps2) {
|
|
28374
28528
|
const nodeMajor = Number(process.versions.node.split(".")[0]);
|
|
@@ -28402,13 +28556,20 @@ async function runSetupCommand(flags, deps2) {
|
|
|
28402
28556
|
const codex = await deps2.resolveCodex();
|
|
28403
28557
|
if (codex) await registerCodexMcp(deps2, codex, pending);
|
|
28404
28558
|
if (flags.git !== false && deps2.cwd() !== deps2.homeDir()) {
|
|
28559
|
+
let inWorkTree;
|
|
28405
28560
|
if (deps2.isInteractive) {
|
|
28406
|
-
await bootstrapRepository(deps2);
|
|
28407
|
-
} else
|
|
28408
|
-
|
|
28409
|
-
|
|
28410
|
-
|
|
28411
|
-
|
|
28561
|
+
inWorkTree = await bootstrapRepository(deps2);
|
|
28562
|
+
} else {
|
|
28563
|
+
inWorkTree = await deps2.resolveGit() !== null && (await deps2.git(["rev-parse", "--is-inside-work-tree"], deps2.cwd())).code === 0;
|
|
28564
|
+
if (!inWorkTree) {
|
|
28565
|
+
say(
|
|
28566
|
+
deps2,
|
|
28567
|
+
"note: skipped the repository offer (not an interactive terminal) \u2014 this folder is not a git work tree, and align requires one. Run `git init` here first."
|
|
28568
|
+
);
|
|
28569
|
+
}
|
|
28570
|
+
}
|
|
28571
|
+
if (inWorkTree) {
|
|
28572
|
+
writeCheckoutMcpConfig(deps2, deps2.signedInUrl() ?? flags.url ?? null);
|
|
28412
28573
|
}
|
|
28413
28574
|
}
|
|
28414
28575
|
say(deps2, "");
|
|
@@ -28494,7 +28655,7 @@ import {
|
|
|
28494
28655
|
statSync as statSync2,
|
|
28495
28656
|
writeFileSync as writeFileSync4
|
|
28496
28657
|
} from "node:fs";
|
|
28497
|
-
import { join as
|
|
28658
|
+
import { join as join6 } from "node:path";
|
|
28498
28659
|
var MAX_SLICE_BYTES = 1e6;
|
|
28499
28660
|
function captureConsented(host, marker) {
|
|
28500
28661
|
if (host && typeof host.captureTrace === "boolean") return host.captureTrace;
|
|
@@ -28529,7 +28690,7 @@ function resolveSnapshotSession(payload, spoolRoot, configPath, repoRoot, readHo
|
|
|
28529
28690
|
return null;
|
|
28530
28691
|
}
|
|
28531
28692
|
function offsetPath(spoolRoot, sessionId) {
|
|
28532
|
-
return
|
|
28693
|
+
return join6(spoolRoot, sessionId, "snapshot.json");
|
|
28533
28694
|
}
|
|
28534
28695
|
function readOffset(spoolRoot, sessionId) {
|
|
28535
28696
|
try {
|
|
@@ -28750,7 +28911,8 @@ function loadSurface(json) {
|
|
|
28750
28911
|
const seen = /* @__PURE__ */ new Set();
|
|
28751
28912
|
const validated = tools.map((tool, index) => {
|
|
28752
28913
|
const where = (field) => isRecord9(tool) && typeof tool.name === "string" ? `tool "${tool.name}": ${field}` : `tools[${index}]: ${field}`;
|
|
28753
|
-
if (!isRecord9(tool))
|
|
28914
|
+
if (!isRecord9(tool))
|
|
28915
|
+
throw new SurfaceError(`tools[${index}] is not an object`);
|
|
28754
28916
|
if (typeof tool.name !== "string" || tool.name.length === 0) {
|
|
28755
28917
|
throw new SurfaceError(where("missing or empty `name`"));
|
|
28756
28918
|
}
|
|
@@ -29051,7 +29213,7 @@ var sessionDeps = {
|
|
|
29051
29213
|
closeSync3(fd);
|
|
29052
29214
|
}
|
|
29053
29215
|
},
|
|
29054
|
-
spoolRoot:
|
|
29216
|
+
spoolRoot: join7(homedir2(), ".config", "stacks", "session-spool")
|
|
29055
29217
|
};
|
|
29056
29218
|
var sessionCommand = registerSessionCommand(program2, sessionDeps, onExit);
|
|
29057
29219
|
registerSnapshotCommand(sessionCommand, sessionDeps, onExit);
|
|
@@ -29109,17 +29271,17 @@ registerSetupCommand(
|
|
|
29109
29271
|
};
|
|
29110
29272
|
const root = await ask(["root", "-g"]);
|
|
29111
29273
|
const prefix = await ask(["prefix", "-g"]);
|
|
29112
|
-
const binDir = prefix ? process.platform === "win32" ? prefix :
|
|
29274
|
+
const binDir = prefix ? process.platform === "win32" ? prefix : join7(prefix, "bin") : null;
|
|
29113
29275
|
const dir = (name) => {
|
|
29114
29276
|
if (!root) return null;
|
|
29115
|
-
const candidate =
|
|
29116
|
-
return existsSync6(
|
|
29277
|
+
const candidate = join7(root, "@jentrix", name);
|
|
29278
|
+
return existsSync6(join7(candidate, "package.json")) ? candidate : null;
|
|
29117
29279
|
};
|
|
29118
29280
|
const cliDir = dir("cli");
|
|
29119
29281
|
const versionOf = (packageDir) => {
|
|
29120
29282
|
if (!packageDir) return null;
|
|
29121
29283
|
try {
|
|
29122
|
-
const raw = readFileSync7(
|
|
29284
|
+
const raw = readFileSync7(join7(packageDir, "package.json"), "utf8");
|
|
29123
29285
|
const parsed = JSON.parse(raw);
|
|
29124
29286
|
const value = typeof parsed === "object" && parsed !== null ? parsed.version : void 0;
|
|
29125
29287
|
return typeof value === "string" ? value : null;
|
|
@@ -29154,14 +29316,14 @@ registerSetupCommand(
|
|
|
29154
29316
|
const packageRoot = persistentPluginRoot(
|
|
29155
29317
|
root,
|
|
29156
29318
|
PACKAGE_ROOT,
|
|
29157
|
-
(dir) => isPluginMarketplaceDir(
|
|
29319
|
+
(dir) => isPluginMarketplaceDir(join7(dir, "claude-plugin"))
|
|
29158
29320
|
);
|
|
29159
29321
|
return runPluginInstall(
|
|
29160
29322
|
packageRoot === PACKAGE_ROOT ? pluginDeps : {
|
|
29161
29323
|
...pluginDeps,
|
|
29162
|
-
resolvePluginDir: () =>
|
|
29324
|
+
resolvePluginDir: () => join7(packageRoot, "claude-plugin"),
|
|
29163
29325
|
resolveCodexPluginDir: () => {
|
|
29164
|
-
const dir =
|
|
29326
|
+
const dir = join7(packageRoot, "codex-plugin");
|
|
29165
29327
|
return isCodexPluginMarketplaceDir(dir) ? dir : null;
|
|
29166
29328
|
}
|
|
29167
29329
|
},
|
|
@@ -29178,12 +29340,14 @@ registerSetupCommand(
|
|
|
29178
29340
|
}
|
|
29179
29341
|
},
|
|
29180
29342
|
login: (flags) => runLoginCommand(flags, loginDeps),
|
|
29181
|
-
codexConfigPath: () =>
|
|
29343
|
+
codexConfigPath: () => join7(process.env.CODEX_HOME ?? join7(homedir2(), ".codex"), "config.toml"),
|
|
29182
29344
|
readTextFile: (path) => existsSync6(path) ? readFileSync7(path, "utf8") : null,
|
|
29183
29345
|
appendTextFile: (path, text) => {
|
|
29184
29346
|
mkdirSync4(dirname4(path), { recursive: true });
|
|
29185
29347
|
appendFileSync(path, text);
|
|
29186
29348
|
},
|
|
29349
|
+
// D6 — the checkout's ./.mcp.json (OAuth mode, no credential inside).
|
|
29350
|
+
writeTextFile: (path, text) => writeFileSync5(path, text, { mode: 420 }),
|
|
29187
29351
|
copyFile: (from, to) => copyFileSync(from, to),
|
|
29188
29352
|
now: () => /* @__PURE__ */ new Date(),
|
|
29189
29353
|
cwd: () => process.cwd(),
|