@jentrix/cli 0.5.15 → 0.5.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/claude-plugin/.claude-plugin/plugin.json +1 -1
- package/claude-plugin/commands/jentrix-align.md +9 -0
- package/codex-plugin/plugins/jentrix/.codex-plugin/plugin.json +1 -1
- package/codex-plugin/plugins/jentrix/skills/jentrix-align/SKILL.md +8 -1
- package/codex-plugin/plugins/jentrix/skills/jentrix-checkpoint/SKILL.md +1 -1
- package/codex-plugin/plugins/jentrix/skills/jentrix-connect/SKILL.md +1 -1
- package/codex-plugin/plugins/jentrix/skills/jentrix-end/SKILL.md +1 -1
- package/codex-plugin/plugins/jentrix/skills/jentrix-plan/SKILL.md +1 -1
- package/codex-plugin/plugins/jentrix/skills/jentrix-review/SKILL.md +1 -1
- package/codex-plugin/plugins/jentrix/skills/jentrix-status/SKILL.md +1 -1
- package/dist/main.js +278 -130
- package/package.json +1 -1
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "jentrix",
|
|
3
3
|
"description": "Connect this Claude Code session to a Jentrix project: /jentrix-connect binds the current session (trusted lifecycle hooks supply the session id), /jentrix-align runs the MVP alignment wizard, /jentrix-status and /jentrix-end wrap the same session operations. Business logic lives in the Jentrix CLI — removing this plugin leaves `jentrix session attach` as the universal fallback.",
|
|
4
|
-
"version": "0.5.
|
|
4
|
+
"version": "0.5.2"
|
|
5
5
|
}
|
|
@@ -21,6 +21,15 @@ and do NOT answer any question yourself.
|
|
|
21
21
|
3. If `currentAlignment` is non-null: show it to the operator and ask —
|
|
22
22
|
using the native choice UI (AskUserQuestion) — whether to keep it or
|
|
23
23
|
re-align. Keep → relay the current snapshot and stop.
|
|
24
|
+
**Fast path.** If the payload's only `ask` question is `confirmation`
|
|
25
|
+
(project / work item / owner were pre-filled from this checkout's previous
|
|
26
|
+
alignment and the confirmation prompt says so — "continuing this
|
|
27
|
+
checkout's previous alignment …"), present that prompt VERBATIM through
|
|
28
|
+
the choice UI. Confirm ⇒ submit with the answered values exactly as the
|
|
29
|
+
payload shows them (`--project <id> --task <id>|--session-level --owner
|
|
30
|
+
<id>`). Re-answer ⇒ re-run `jentrix align --questions --json --fresh` and
|
|
31
|
+
continue with the full flow below, passing `--fresh` on the final submit
|
|
32
|
+
too.
|
|
24
33
|
4. For each question whose `status` is `"ask"`, present its `choices`
|
|
25
34
|
verbatim through the native choice UI (AskUserQuestion), preserving the
|
|
26
35
|
labels. If the question carries a `notice`, relay it to the operator
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: jentrix-align
|
|
3
3
|
description: Align the current Codex task to a Jentrix workspace, project, work item, and owner with an operator-confirmed server snapshot.
|
|
4
4
|
---
|
|
5
5
|
<!-- Source of truth. The cli/{claude,codex}-plugin copy is GENERATED by cli/scripts/build.mjs (rm + cp) on every build — edits made there are deleted. Edit this file. -->
|
|
@@ -10,6 +10,13 @@ description: Align the current Codex task to a Jentrix workspace, project, work
|
|
|
10
10
|
2. Relay every failed or warning precondition before asking anything.
|
|
11
11
|
3. If `currentAlignment` exists, ask whether to keep it or re-align. Keep means
|
|
12
12
|
show the current snapshot and stop.
|
|
13
|
+
Fast path: if the payload's only `ask` question is `confirmation` (project /
|
|
14
|
+
work item / owner were pre-filled from this checkout's previous alignment and
|
|
15
|
+
the confirmation prompt says so), present that prompt verbatim. Confirm ⇒
|
|
16
|
+
submit with the answered values exactly as the payload shows them
|
|
17
|
+
(`--project <id> --task <id>|--session-level --owner <id>`). Re-answer ⇒
|
|
18
|
+
re-run `jentrix align --provider codex --questions --json --fresh` and
|
|
19
|
+
continue with the full flow below, passing `--fresh` on the final submit too.
|
|
13
20
|
4. Present each `status:"ask"` question and its choices verbatim using Codex's
|
|
14
21
|
structured input UI when available, otherwise ask in chat. Never answer for the
|
|
15
22
|
operator. Preserve notices and the `Other…` free-text escape.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: jentrix-checkpoint
|
|
3
3
|
description: Distill verified session state into a typed Jentrix checkpoint that survives compaction or handoff.
|
|
4
4
|
---
|
|
5
5
|
<!-- Source of truth. The cli/{claude,codex}-plugin copy is GENERATED by cli/scripts/build.mjs (rm + cp) on every build — edits made there are deleted. Edit this file. -->
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: jentrix-connect
|
|
3
3
|
description: Connect the current Codex task to a Jentrix project using trusted lifecycle identity and start the session capture host.
|
|
4
4
|
---
|
|
5
5
|
<!-- Source of truth. The cli/{claude,codex}-plugin copy is GENERATED by cli/scripts/build.mjs (rm + cp) on every build — edits made there are deleted. Edit this file. -->
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: jentrix-end
|
|
3
3
|
description: End a connected Jentrix session after preserving approved outputs, parking completed work in review, and reporting capture honestly.
|
|
4
4
|
---
|
|
5
5
|
<!-- Source of truth. The cli/{claude,codex}-plugin copy is GENERATED by cli/scripts/build.mjs (rm + cp) on every build — edits made there are deleted. Edit this file. -->
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: jentrix-plan
|
|
3
3
|
description: Turn the opening request into an operator-approved goal and small Jentrix task set, then store the prompt and goal as typed artifacts.
|
|
4
4
|
---
|
|
5
5
|
<!-- Source of truth. The cli/{claude,codex}-plugin copy is GENERATED by cli/scripts/build.mjs (rm + cp) on every build — edits made there are deleted. Edit this file. -->
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: jentrix-review
|
|
3
3
|
description: Review a Jentrix session's typed artifacts by category, verify the readiness contract, and post the verdict as a comment on the aligned task.
|
|
4
4
|
---
|
|
5
5
|
<!-- Source of truth. The cli/{claude,codex}-plugin copy is GENERATED by cli/scripts/build.mjs (rm + cp) on every build — edits made there are deleted. Edit this file. -->
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
2
|
+
name: jentrix-status
|
|
3
3
|
description: Show the connected Jentrix session, alignment, capture health, and honest telemetry availability for this Codex task.
|
|
4
4
|
---
|
|
5
5
|
<!-- Source of truth. The cli/{claude,codex}-plugin copy is GENERATED by cli/scripts/build.mjs (rm + cp) on every build — edits made there are deleted. Edit this file. -->
|
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
|
|
@@ -20965,7 +20966,7 @@ function refreshAccessToken(input) {
|
|
|
20965
20966
|
|
|
20966
20967
|
// src/client.ts
|
|
20967
20968
|
var CLI_NAME = "stacks-cli";
|
|
20968
|
-
var CLI_VERSION = "0.5.
|
|
20969
|
+
var CLI_VERSION = "0.5.17";
|
|
20969
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).";
|
|
20970
20971
|
var RELOGIN_MESSAGE = "OAuth session expired and could not be refreshed \u2014 run `jentrix login` to sign in again.";
|
|
20971
20972
|
function originOf2(url2) {
|
|
@@ -21316,6 +21317,15 @@ var PROJECT_NAME_PROMPT = "Name for the new project \u2014 your own words, exact
|
|
|
21316
21317
|
function buildAlignDisclosures(catalog) {
|
|
21317
21318
|
const disclosures = [];
|
|
21318
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
|
+
}
|
|
21319
21329
|
if (catalog.chosenProjectId === "__pending__") {
|
|
21320
21330
|
const name = catalog.newProjectName ?? "the new project";
|
|
21321
21331
|
disclosures.push(
|
|
@@ -21420,7 +21430,10 @@ function buildAlignQuestions(catalog) {
|
|
|
21420
21430
|
...new Map(
|
|
21421
21431
|
[
|
|
21422
21432
|
...catalog.candidates.map((candidate) => candidate.workspace),
|
|
21423
|
-
...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 ?? [] : []
|
|
21424
21437
|
].map((ws) => [ws.id, ws])
|
|
21425
21438
|
).values()
|
|
21426
21439
|
];
|
|
@@ -21648,6 +21661,14 @@ function buildAlignQuestions(catalog) {
|
|
|
21648
21661
|
};
|
|
21649
21662
|
} else if (project.status !== "answered" || catalog.members === null) {
|
|
21650
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
|
+
};
|
|
21651
21672
|
} else {
|
|
21652
21673
|
owner = {
|
|
21653
21674
|
id: "owner",
|
|
@@ -21666,7 +21687,8 @@ function buildAlignQuestions(catalog) {
|
|
|
21666
21687
|
};
|
|
21667
21688
|
}
|
|
21668
21689
|
const targetKnown = project.status === "answered" && (projectName.status === "answered" || projectName.status === "skipped");
|
|
21669
|
-
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");
|
|
21670
21692
|
const disclosures = targetKnown ? buildAlignDisclosures(catalog) : [];
|
|
21671
21693
|
const confirmation = {
|
|
21672
21694
|
id: "confirmation",
|
|
@@ -23682,6 +23704,78 @@ async function openTasksForProject(caller, boardIds) {
|
|
|
23682
23704
|
tasks.sort((a, b) => b.number - a.number);
|
|
23683
23705
|
return tasks.slice(0, MAX_WORK_ITEMS).map(({ id, key, title }) => ({ id, key, title }));
|
|
23684
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
|
+
}
|
|
23685
23779
|
async function resolveCandidates(caller, repoOwnerName) {
|
|
23686
23780
|
try {
|
|
23687
23781
|
const result = await callStructured(caller, "resolve_projects_for_repo", {
|
|
@@ -23847,8 +23941,8 @@ async function buildCatalog(caller, flags, detection) {
|
|
|
23847
23941
|
const context = await callStructured(caller, "get_token_context", {});
|
|
23848
23942
|
let pinnedWorkspace = null;
|
|
23849
23943
|
if (context.workspacePinned === true && typeof context.workspaceId === "string") {
|
|
23850
|
-
const
|
|
23851
|
-
const match = (
|
|
23944
|
+
const workspaces2 = await callStructured(caller, "list_workspaces", {});
|
|
23945
|
+
const match = (workspaces2.workspaces ?? []).find((ws) => ws.id === context.workspaceId);
|
|
23852
23946
|
pinnedWorkspace = {
|
|
23853
23947
|
id: context.workspaceId,
|
|
23854
23948
|
name: match?.name ?? "pinned"
|
|
@@ -23861,17 +23955,28 @@ async function buildCatalog(caller, flags, detection) {
|
|
|
23861
23955
|
) : null;
|
|
23862
23956
|
const { candidates: matched, pinned: discoveryPinned } = await resolveCandidates(caller, detection.repoOwnerName);
|
|
23863
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;
|
|
23864
23968
|
let otherProjects = [];
|
|
23865
23969
|
let discoveryNotice = null;
|
|
23866
|
-
if (
|
|
23970
|
+
if (resolvedWorkspaceId) {
|
|
23867
23971
|
try {
|
|
23868
|
-
|
|
23869
|
-
|
|
23870
|
-
|
|
23871
|
-
|
|
23872
|
-
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
|
+
}
|
|
23873
23976
|
} catch {
|
|
23874
|
-
|
|
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
|
+
}
|
|
23875
23980
|
}
|
|
23876
23981
|
}
|
|
23877
23982
|
const wantsCreate = Boolean(flags.newProject && flags.newProject.trim()) || flags.project === "__create__";
|
|
@@ -23888,68 +23993,28 @@ async function buildCatalog(caller, flags, detection) {
|
|
|
23888
23993
|
} else if (candidates.length === 1 && matched.length === 1) {
|
|
23889
23994
|
chosenProjectId = candidates[0].id;
|
|
23890
23995
|
}
|
|
23891
|
-
let openTasks = null;
|
|
23892
|
-
let members = null;
|
|
23893
|
-
let repoLinked = null;
|
|
23894
|
-
let projectHasBoard = null;
|
|
23895
|
-
if (wantsCreate) {
|
|
23896
|
-
openTasks = [];
|
|
23897
|
-
const workspaceId = pinnedWorkspace?.id ?? null;
|
|
23898
|
-
if (workspaceId) {
|
|
23899
|
-
try {
|
|
23900
|
-
const memberResult = await callStructured(caller, "list_members", {
|
|
23901
|
-
workspaceId
|
|
23902
|
-
});
|
|
23903
|
-
members = memberResult.members?.map((member) => ({
|
|
23904
|
-
id: member.userId ?? member.id ?? "",
|
|
23905
|
-
name: member.name,
|
|
23906
|
-
email: member.email
|
|
23907
|
-
})) ?? [];
|
|
23908
|
-
} catch {
|
|
23909
|
-
members = [];
|
|
23910
|
-
}
|
|
23911
|
-
} else {
|
|
23912
|
-
members = [];
|
|
23913
|
-
}
|
|
23914
|
-
} else if (chosenProjectId) {
|
|
23915
|
-
try {
|
|
23916
|
-
const project = await projectBoards(caller, chosenProjectId);
|
|
23917
|
-
repoLinked = repoLinkedOf(
|
|
23918
|
-
{ candidates, detection },
|
|
23919
|
-
chosenProjectId,
|
|
23920
|
-
project.repoOwners
|
|
23921
|
-
);
|
|
23922
|
-
projectHasBoard = project.boardIds.length > 0;
|
|
23923
|
-
openTasks = await openTasksForProject(caller, project.boardIds);
|
|
23924
|
-
const memberResult = await callStructured(caller, "list_members", {
|
|
23925
|
-
workspaceId: project.workspaceId
|
|
23926
|
-
});
|
|
23927
|
-
members = memberResult.members?.map((member) => ({
|
|
23928
|
-
id: member.userId ?? member.id ?? "",
|
|
23929
|
-
name: member.name,
|
|
23930
|
-
email: member.email
|
|
23931
|
-
})) ?? null;
|
|
23932
|
-
} catch {
|
|
23933
|
-
}
|
|
23934
|
-
}
|
|
23935
23996
|
const chosenWorkspaceId = candidates.find((candidate) => candidate.id === chosenProjectId)?.workspace.id ?? flagWorkspaceId ?? pinnedWorkspace?.id ?? null;
|
|
23936
|
-
|
|
23997
|
+
const catalog = {
|
|
23937
23998
|
detection,
|
|
23938
23999
|
pinnedWorkspace,
|
|
23939
24000
|
candidates,
|
|
23940
24001
|
otherProjects,
|
|
23941
24002
|
discoveryNotice,
|
|
24003
|
+
workspaces,
|
|
23942
24004
|
chosenWorkspaceId,
|
|
23943
24005
|
chosenProjectId,
|
|
23944
|
-
openTasks,
|
|
23945
|
-
members,
|
|
23946
|
-
|
|
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,
|
|
23947
24012
|
chosenWorkItem: flags.task ? flags.task : flags.newTask ? "__new__" : flags.sessionLevel ? "__none__" : null,
|
|
23948
24013
|
chosenOwnerId: flags.owner ?? null,
|
|
23949
24014
|
newTaskTitle: flags.newTask?.trim() || null,
|
|
23950
24015
|
newProjectName: flags.newProject?.trim() || null,
|
|
23951
|
-
repoLinked,
|
|
23952
|
-
projectHasBoard,
|
|
24016
|
+
repoLinked: null,
|
|
24017
|
+
projectHasBoard: null,
|
|
23953
24018
|
// STA-11: filled by runAlign (it needs the checkout root) — the ordering
|
|
23954
24019
|
// preference for the workspace question.
|
|
23955
24020
|
lastAlignedWorkspaceId: null,
|
|
@@ -23965,8 +24030,24 @@ async function buildCatalog(caller, flags, detection) {
|
|
|
23965
24030
|
binding: null,
|
|
23966
24031
|
mcpConfig: null,
|
|
23967
24032
|
// Filled by runAlign — it needs the checkout's marker session.
|
|
23968
|
-
priorArtifacts: null
|
|
24033
|
+
priorArtifacts: null,
|
|
24034
|
+
// D7: filled by runAlign (it needs the checkout's newest marker).
|
|
24035
|
+
previous: null
|
|
23969
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;
|
|
23970
24051
|
}
|
|
23971
24052
|
async function priorArtifactsOf(caller, markerSession) {
|
|
23972
24053
|
const current = markerSession?.alignment?.task;
|
|
@@ -24323,7 +24404,8 @@ async function runAlign(flags, deps2) {
|
|
|
24323
24404
|
flags.capture,
|
|
24324
24405
|
marker !== null && markerHost !== null && isHostCapturing(deps2, marker.sessionId, markerHost)
|
|
24325
24406
|
);
|
|
24326
|
-
|
|
24407
|
+
const newest = newestAlignmentMarker(deps2.configPath, inspection.root);
|
|
24408
|
+
catalog.lastAlignedWorkspaceId = newest?.workspaceId ?? null;
|
|
24327
24409
|
catalog.binding = {
|
|
24328
24410
|
providerSessionId: detected.providerSessionId,
|
|
24329
24411
|
transcriptPath: detected.transcriptPath,
|
|
@@ -24349,6 +24431,31 @@ async function runAlign(flags, deps2) {
|
|
|
24349
24431
|
}
|
|
24350
24432
|
}
|
|
24351
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
|
+
}
|
|
24352
24459
|
catalog.priorArtifacts = await priorArtifactsOf(caller, markerSession);
|
|
24353
24460
|
const preconditions = await doctorLocalChecks(deps2);
|
|
24354
24461
|
preconditions.push(sessionBindingCheck(detected));
|
|
@@ -24433,7 +24540,7 @@ async function runAlign(flags, deps2) {
|
|
|
24433
24540
|
let pendingCreateProject = null;
|
|
24434
24541
|
if (flags.newTask) workItem = `__new__:${flags.newTask}`;
|
|
24435
24542
|
if (flags.newProject && flags.newProject.trim()) {
|
|
24436
|
-
const workspaceId = catalog
|
|
24543
|
+
const workspaceId = buildAlignQuestions(catalog)[0]?.answer ?? null;
|
|
24437
24544
|
if (!workspaceId) {
|
|
24438
24545
|
throw new UsageError(
|
|
24439
24546
|
"--new-project needs a resolved workspace (a workspace-pinned token, a repo-matched candidate, or --workspace <id-or-slug>) \u2014 none resolved here"
|
|
@@ -24494,11 +24601,21 @@ async function runAlign(flags, deps2) {
|
|
|
24494
24601
|
catalog.newProjectName = null;
|
|
24495
24602
|
pendingCreateProject = null;
|
|
24496
24603
|
createWorkspaceId = null;
|
|
24604
|
+
catalog.previous = null;
|
|
24605
|
+
if (workspaceFromPrevious) {
|
|
24606
|
+
catalog.chosenWorkspaceId = null;
|
|
24607
|
+
workspaceFromPrevious = false;
|
|
24608
|
+
}
|
|
24497
24609
|
continue;
|
|
24498
24610
|
}
|
|
24499
24611
|
const answer = await askQuestion(deps2, next);
|
|
24500
24612
|
if (next.id === "workspace") {
|
|
24501
24613
|
catalog.chosenWorkspaceId = answer;
|
|
24614
|
+
try {
|
|
24615
|
+
catalog.otherProjects = await workspaceProjects(caller, answer);
|
|
24616
|
+
} catch {
|
|
24617
|
+
catalog.otherProjects = [];
|
|
24618
|
+
}
|
|
24502
24619
|
} else if (next.id === "task_title") {
|
|
24503
24620
|
newTaskTitle = answer;
|
|
24504
24621
|
} else if (next.id === "project_name") {
|
|
@@ -24514,7 +24631,7 @@ async function runAlign(flags, deps2) {
|
|
|
24514
24631
|
};
|
|
24515
24632
|
} else if (next.id === "project") {
|
|
24516
24633
|
if (answer === "__create__") {
|
|
24517
|
-
const workspaceId =
|
|
24634
|
+
const workspaceId = questions[0]?.answer ?? null;
|
|
24518
24635
|
if (!workspaceId) {
|
|
24519
24636
|
throw new UsageError(
|
|
24520
24637
|
"cannot create a project without a resolved workspace \u2014 pick a workspace first"
|
|
@@ -24526,16 +24643,7 @@ async function runAlign(flags, deps2) {
|
|
|
24526
24643
|
catalog.projectHasBoard = null;
|
|
24527
24644
|
catalog.openTasks = [];
|
|
24528
24645
|
try {
|
|
24529
|
-
|
|
24530
|
-
caller,
|
|
24531
|
-
"list_members",
|
|
24532
|
-
{ workspaceId }
|
|
24533
|
-
);
|
|
24534
|
-
catalog.members = memberResult.members?.map((member) => ({
|
|
24535
|
-
id: member.userId ?? member.id ?? "",
|
|
24536
|
-
name: member.name,
|
|
24537
|
-
email: member.email
|
|
24538
|
-
})) ?? [];
|
|
24646
|
+
catalog.members = await workspaceMembers(caller, workspaceId);
|
|
24539
24647
|
} catch {
|
|
24540
24648
|
catalog.members = [];
|
|
24541
24649
|
}
|
|
@@ -24543,31 +24651,7 @@ async function runAlign(flags, deps2) {
|
|
|
24543
24651
|
} else {
|
|
24544
24652
|
projectId = answer;
|
|
24545
24653
|
}
|
|
24546
|
-
|
|
24547
|
-
const project = await projectBoards(caller, projectId);
|
|
24548
|
-
catalog.repoLinked = repoLinkedOf(
|
|
24549
|
-
catalog,
|
|
24550
|
-
projectId,
|
|
24551
|
-
project.repoOwners
|
|
24552
|
-
);
|
|
24553
|
-
catalog.projectHasBoard = project.boardIds.length > 0;
|
|
24554
|
-
catalog.openTasks = await openTasksForProject(
|
|
24555
|
-
caller,
|
|
24556
|
-
project.boardIds
|
|
24557
|
-
);
|
|
24558
|
-
const memberResult = await callStructured(
|
|
24559
|
-
caller,
|
|
24560
|
-
"list_members",
|
|
24561
|
-
{
|
|
24562
|
-
workspaceId: project.workspaceId
|
|
24563
|
-
}
|
|
24564
|
-
);
|
|
24565
|
-
catalog.members = memberResult.members?.map((member) => ({
|
|
24566
|
-
id: member.userId ?? member.id ?? "",
|
|
24567
|
-
name: member.name,
|
|
24568
|
-
email: member.email
|
|
24569
|
-
})) ?? [];
|
|
24570
|
-
} catch {
|
|
24654
|
+
if (!await loadProjectContext(caller, catalog, projectId)) {
|
|
24571
24655
|
catalog.openTasks = [];
|
|
24572
24656
|
catalog.members = [];
|
|
24573
24657
|
catalog.repoLinked = null;
|
|
@@ -24580,10 +24664,11 @@ async function runAlign(flags, deps2) {
|
|
|
24580
24664
|
}
|
|
24581
24665
|
}
|
|
24582
24666
|
}
|
|
24667
|
+
ownerUserId ??= (catalog.members?.length === 1 ? catalog.members[0].id : null) ?? catalog.operatorUserId;
|
|
24583
24668
|
if (pendingCreateProject) {
|
|
24584
24669
|
if (!ownerUserId) {
|
|
24585
24670
|
throw new UsageError(
|
|
24586
|
-
|
|
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}"}'\`)`
|
|
24587
24672
|
);
|
|
24588
24673
|
}
|
|
24589
24674
|
const created = await callStructured(caller, "create_project", {
|
|
@@ -24798,6 +24883,9 @@ function registerAlignCommand(program3, deps2, onExit2) {
|
|
|
24798
24883
|
).option("--json", "stable JSON output").option(
|
|
24799
24884
|
"--yes",
|
|
24800
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"
|
|
24801
24889
|
).action(async (flags) => onExit2(await runAlign(flags, deps2)));
|
|
24802
24890
|
}
|
|
24803
24891
|
|
|
@@ -26871,6 +26959,7 @@ function renderHuman(report) {
|
|
|
26871
26959
|
lines.push(
|
|
26872
26960
|
` workspace: ${tc.workspacePinned ? `pinned to ${tc.workspaceId ?? "?"}` : "not pinned (all your workspaces)"}`
|
|
26873
26961
|
);
|
|
26962
|
+
if (tc.userId) lines.push(` user: ${tc.userId}`);
|
|
26874
26963
|
if (tc.displayName) {
|
|
26875
26964
|
lines.push(
|
|
26876
26965
|
` identity: ${tc.emoji ? `${tc.emoji} ` : ""}${tc.displayName}`
|
|
@@ -27031,6 +27120,7 @@ function parseTokenContext(rendered) {
|
|
|
27031
27120
|
grandfathered: v.grandfathered,
|
|
27032
27121
|
workspacePinned: v.workspacePinned,
|
|
27033
27122
|
workspaceId: v.workspaceId ?? null,
|
|
27123
|
+
userId: typeof v.userId === "string" ? v.userId : null,
|
|
27034
27124
|
displayName: v.displayName ?? null,
|
|
27035
27125
|
emoji: v.emoji ?? null,
|
|
27036
27126
|
rateLimit: {
|
|
@@ -28134,6 +28224,7 @@ function registerRunnerCommand(program3, deps2, onExit2) {
|
|
|
28134
28224
|
}
|
|
28135
28225
|
|
|
28136
28226
|
// src/commands/setup.ts
|
|
28227
|
+
import { join as join5 } from "node:path";
|
|
28137
28228
|
var NPM_INSTALL_TIMEOUT_MS2 = 3e5;
|
|
28138
28229
|
var SUPERSEDED = ["@jentrix/stacks-cli", "@jentrix/stacks-runner"];
|
|
28139
28230
|
var CLI_SPEC = `@jentrix/cli@${CLI_VERSION}`;
|
|
@@ -28278,7 +28369,7 @@ async function installRuntimes(deps2, pending) {
|
|
|
28278
28369
|
}
|
|
28279
28370
|
}
|
|
28280
28371
|
}
|
|
28281
|
-
async function registerCodexMcp(deps2, codex, pending) {
|
|
28372
|
+
async function registerCodexMcp(deps2, codex, pending, url2) {
|
|
28282
28373
|
const configPath = deps2.codexConfigPath();
|
|
28283
28374
|
const config2 = deps2.readTextFile(configPath);
|
|
28284
28375
|
const existingKey = config2 ? /^\[mcp_servers\.jentrix\]/m.test(config2) ? "jentrix" : /^\[mcp_servers\.stacks\]/m.test(config2) ? "stacks" : null : null;
|
|
@@ -28301,7 +28392,6 @@ async function registerCodexMcp(deps2, codex, pending) {
|
|
|
28301
28392
|
);
|
|
28302
28393
|
return;
|
|
28303
28394
|
}
|
|
28304
|
-
const url2 = deps2.signedInUrl();
|
|
28305
28395
|
if (!url2) {
|
|
28306
28396
|
say(
|
|
28307
28397
|
deps2,
|
|
@@ -28356,21 +28446,21 @@ async function bootstrapRepository(deps2) {
|
|
|
28356
28446
|
deps2,
|
|
28357
28447
|
" Jentrix aligns sessions by repository identity, so install git (https://git-scm.com/downloads) and re-run."
|
|
28358
28448
|
);
|
|
28359
|
-
return;
|
|
28449
|
+
return false;
|
|
28360
28450
|
}
|
|
28361
28451
|
const inRepo = async () => (await deps2.git(["rev-parse", "--is-inside-work-tree"], here)).code === 0;
|
|
28362
28452
|
if (!await inRepo()) {
|
|
28363
28453
|
const reply = await deps2.readLine(
|
|
28364
28454
|
`Initialize a git repository in ${here} so agent sessions can align here? [y/N] `
|
|
28365
28455
|
);
|
|
28366
|
-
if (!/^y/i.test(reply.trim())) return;
|
|
28456
|
+
if (!/^y/i.test(reply.trim())) return false;
|
|
28367
28457
|
if ((await deps2.git(["init"], here)).code !== 0) {
|
|
28368
28458
|
say(deps2, "note: `git init` failed \u2014 skipped the repository step.");
|
|
28369
|
-
return;
|
|
28459
|
+
return false;
|
|
28370
28460
|
}
|
|
28371
28461
|
}
|
|
28372
28462
|
if ((await deps2.git(["remote", "get-url", "origin"], here)).code === 0)
|
|
28373
|
-
return;
|
|
28463
|
+
return true;
|
|
28374
28464
|
const fallback = here.split(/[/\\]/).filter(Boolean).pop().toLowerCase().replace(/ /g, "-");
|
|
28375
28465
|
say(
|
|
28376
28466
|
deps2,
|
|
@@ -28383,15 +28473,55 @@ async function bootstrapRepository(deps2) {
|
|
|
28383
28473
|
const answer = (await deps2.readLine(
|
|
28384
28474
|
"GitHub repo for origin (owner/name or full URL, blank to skip): "
|
|
28385
28475
|
)).trim();
|
|
28386
|
-
if (!answer) return;
|
|
28476
|
+
if (!answer) return true;
|
|
28387
28477
|
const url2 = /:\/\/|^[^/]+@[^:]+:/.test(answer) ? answer : /^[^/]+\/[^/]+$/.test(answer) ? `https://github.com/${answer}.git` : null;
|
|
28388
28478
|
if (!url2) {
|
|
28389
28479
|
say(deps2, `skipped \u2014 "${answer}" is neither owner/name nor a URL`);
|
|
28390
|
-
return;
|
|
28480
|
+
return true;
|
|
28391
28481
|
}
|
|
28392
28482
|
if ((await deps2.git(["remote", "add", "origin", url2], here)).code === 0) {
|
|
28393
28483
|
say(deps2, `origin \u2192 ${url2}`);
|
|
28394
28484
|
}
|
|
28485
|
+
return true;
|
|
28486
|
+
}
|
|
28487
|
+
function writeCheckoutMcpConfig(deps2, url2) {
|
|
28488
|
+
const path = join5(deps2.cwd(), ".mcp.json");
|
|
28489
|
+
if (!url2) {
|
|
28490
|
+
say(
|
|
28491
|
+
deps2,
|
|
28492
|
+
"note: skipped ./.mcp.json \u2014 no endpoint is known yet (not signed in, no --url); the first `jentrix align` here writes it."
|
|
28493
|
+
);
|
|
28494
|
+
return;
|
|
28495
|
+
}
|
|
28496
|
+
const raw = deps2.readTextFile(path);
|
|
28497
|
+
let existing = null;
|
|
28498
|
+
if (raw !== null) {
|
|
28499
|
+
try {
|
|
28500
|
+
existing = JSON.parse(raw);
|
|
28501
|
+
} catch {
|
|
28502
|
+
say(
|
|
28503
|
+
deps2,
|
|
28504
|
+
`note: ${path} is not valid JSON \u2014 left untouched (fix or remove it, then re-run; align refuses it the same way).`
|
|
28505
|
+
);
|
|
28506
|
+
return;
|
|
28507
|
+
}
|
|
28508
|
+
}
|
|
28509
|
+
const plan = planMcpServerEntry(existing, url2, null);
|
|
28510
|
+
if (plan.action === "unchanged") {
|
|
28511
|
+
say(deps2, `./.mcp.json already names ${url2}.`);
|
|
28512
|
+
return;
|
|
28513
|
+
}
|
|
28514
|
+
deps2.writeTextFile(path, renderMcpConfig(plan.next));
|
|
28515
|
+
say(
|
|
28516
|
+
deps2,
|
|
28517
|
+
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).`
|
|
28518
|
+
);
|
|
28519
|
+
if (plan.removedAuthorization) {
|
|
28520
|
+
say(
|
|
28521
|
+
deps2,
|
|
28522
|
+
" Removed its Authorization header: a header suppresses the client's OAuth discovery. `jentrix align --pat` puts it back for a headless checkout."
|
|
28523
|
+
);
|
|
28524
|
+
}
|
|
28395
28525
|
}
|
|
28396
28526
|
async function runSetupCommand(flags, deps2) {
|
|
28397
28527
|
const nodeMajor = Number(process.versions.node.split(".")[0]);
|
|
@@ -28401,6 +28531,14 @@ async function runSetupCommand(flags, deps2) {
|
|
|
28401
28531
|
);
|
|
28402
28532
|
return EXIT_CODES.INVALID_INPUT;
|
|
28403
28533
|
}
|
|
28534
|
+
if (flags.url) {
|
|
28535
|
+
try {
|
|
28536
|
+
new URL(flags.url);
|
|
28537
|
+
} catch {
|
|
28538
|
+
deps2.writeErr(`INVALID_URL: ${JSON.stringify(flags.url)}`);
|
|
28539
|
+
return EXIT_CODES.INVALID_INPUT;
|
|
28540
|
+
}
|
|
28541
|
+
}
|
|
28404
28542
|
const toolchain = await installToolchain(deps2);
|
|
28405
28543
|
if (toolchain !== EXIT_CODES.OK) return toolchain;
|
|
28406
28544
|
const pending = { failedPlugins: [] };
|
|
@@ -28422,16 +28560,24 @@ async function runSetupCommand(flags, deps2) {
|
|
|
28422
28560
|
);
|
|
28423
28561
|
}
|
|
28424
28562
|
}
|
|
28563
|
+
const targetUrl = flags.url ?? deps2.signedInUrl();
|
|
28425
28564
|
const codex = await deps2.resolveCodex();
|
|
28426
|
-
if (codex) await registerCodexMcp(deps2, codex, pending);
|
|
28565
|
+
if (codex) await registerCodexMcp(deps2, codex, pending, targetUrl);
|
|
28427
28566
|
if (flags.git !== false && deps2.cwd() !== deps2.homeDir()) {
|
|
28567
|
+
let inWorkTree;
|
|
28428
28568
|
if (deps2.isInteractive) {
|
|
28429
|
-
await bootstrapRepository(deps2);
|
|
28430
|
-
} else
|
|
28431
|
-
|
|
28432
|
-
|
|
28433
|
-
|
|
28434
|
-
|
|
28569
|
+
inWorkTree = await bootstrapRepository(deps2);
|
|
28570
|
+
} else {
|
|
28571
|
+
inWorkTree = await deps2.resolveGit() !== null && (await deps2.git(["rev-parse", "--is-inside-work-tree"], deps2.cwd())).code === 0;
|
|
28572
|
+
if (!inWorkTree) {
|
|
28573
|
+
say(
|
|
28574
|
+
deps2,
|
|
28575
|
+
"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."
|
|
28576
|
+
);
|
|
28577
|
+
}
|
|
28578
|
+
}
|
|
28579
|
+
if (inWorkTree) {
|
|
28580
|
+
writeCheckoutMcpConfig(deps2, targetUrl);
|
|
28435
28581
|
}
|
|
28436
28582
|
}
|
|
28437
28583
|
say(deps2, "");
|
|
@@ -28517,7 +28663,7 @@ import {
|
|
|
28517
28663
|
statSync as statSync2,
|
|
28518
28664
|
writeFileSync as writeFileSync4
|
|
28519
28665
|
} from "node:fs";
|
|
28520
|
-
import { join as
|
|
28666
|
+
import { join as join6 } from "node:path";
|
|
28521
28667
|
var MAX_SLICE_BYTES = 1e6;
|
|
28522
28668
|
function captureConsented(host, marker) {
|
|
28523
28669
|
if (host && typeof host.captureTrace === "boolean") return host.captureTrace;
|
|
@@ -28552,7 +28698,7 @@ function resolveSnapshotSession(payload, spoolRoot, configPath, repoRoot, readHo
|
|
|
28552
28698
|
return null;
|
|
28553
28699
|
}
|
|
28554
28700
|
function offsetPath(spoolRoot, sessionId) {
|
|
28555
|
-
return
|
|
28701
|
+
return join6(spoolRoot, sessionId, "snapshot.json");
|
|
28556
28702
|
}
|
|
28557
28703
|
function readOffset(spoolRoot, sessionId) {
|
|
28558
28704
|
try {
|
|
@@ -29075,7 +29221,7 @@ var sessionDeps = {
|
|
|
29075
29221
|
closeSync3(fd);
|
|
29076
29222
|
}
|
|
29077
29223
|
},
|
|
29078
|
-
spoolRoot:
|
|
29224
|
+
spoolRoot: join7(homedir2(), ".config", "stacks", "session-spool")
|
|
29079
29225
|
};
|
|
29080
29226
|
var sessionCommand = registerSessionCommand(program2, sessionDeps, onExit);
|
|
29081
29227
|
registerSnapshotCommand(sessionCommand, sessionDeps, onExit);
|
|
@@ -29133,17 +29279,17 @@ registerSetupCommand(
|
|
|
29133
29279
|
};
|
|
29134
29280
|
const root = await ask(["root", "-g"]);
|
|
29135
29281
|
const prefix = await ask(["prefix", "-g"]);
|
|
29136
|
-
const binDir = prefix ? process.platform === "win32" ? prefix :
|
|
29282
|
+
const binDir = prefix ? process.platform === "win32" ? prefix : join7(prefix, "bin") : null;
|
|
29137
29283
|
const dir = (name) => {
|
|
29138
29284
|
if (!root) return null;
|
|
29139
|
-
const candidate =
|
|
29140
|
-
return existsSync6(
|
|
29285
|
+
const candidate = join7(root, "@jentrix", name);
|
|
29286
|
+
return existsSync6(join7(candidate, "package.json")) ? candidate : null;
|
|
29141
29287
|
};
|
|
29142
29288
|
const cliDir = dir("cli");
|
|
29143
29289
|
const versionOf = (packageDir) => {
|
|
29144
29290
|
if (!packageDir) return null;
|
|
29145
29291
|
try {
|
|
29146
|
-
const raw = readFileSync7(
|
|
29292
|
+
const raw = readFileSync7(join7(packageDir, "package.json"), "utf8");
|
|
29147
29293
|
const parsed = JSON.parse(raw);
|
|
29148
29294
|
const value = typeof parsed === "object" && parsed !== null ? parsed.version : void 0;
|
|
29149
29295
|
return typeof value === "string" ? value : null;
|
|
@@ -29178,14 +29324,14 @@ registerSetupCommand(
|
|
|
29178
29324
|
const packageRoot = persistentPluginRoot(
|
|
29179
29325
|
root,
|
|
29180
29326
|
PACKAGE_ROOT,
|
|
29181
|
-
(dir) => isPluginMarketplaceDir(
|
|
29327
|
+
(dir) => isPluginMarketplaceDir(join7(dir, "claude-plugin"))
|
|
29182
29328
|
);
|
|
29183
29329
|
return runPluginInstall(
|
|
29184
29330
|
packageRoot === PACKAGE_ROOT ? pluginDeps : {
|
|
29185
29331
|
...pluginDeps,
|
|
29186
|
-
resolvePluginDir: () =>
|
|
29332
|
+
resolvePluginDir: () => join7(packageRoot, "claude-plugin"),
|
|
29187
29333
|
resolveCodexPluginDir: () => {
|
|
29188
|
-
const dir =
|
|
29334
|
+
const dir = join7(packageRoot, "codex-plugin");
|
|
29189
29335
|
return isCodexPluginMarketplaceDir(dir) ? dir : null;
|
|
29190
29336
|
}
|
|
29191
29337
|
},
|
|
@@ -29202,12 +29348,14 @@ registerSetupCommand(
|
|
|
29202
29348
|
}
|
|
29203
29349
|
},
|
|
29204
29350
|
login: (flags) => runLoginCommand(flags, loginDeps),
|
|
29205
|
-
codexConfigPath: () =>
|
|
29351
|
+
codexConfigPath: () => join7(process.env.CODEX_HOME ?? join7(homedir2(), ".codex"), "config.toml"),
|
|
29206
29352
|
readTextFile: (path) => existsSync6(path) ? readFileSync7(path, "utf8") : null,
|
|
29207
29353
|
appendTextFile: (path, text) => {
|
|
29208
29354
|
mkdirSync4(dirname4(path), { recursive: true });
|
|
29209
29355
|
appendFileSync(path, text);
|
|
29210
29356
|
},
|
|
29357
|
+
// D6 — the checkout's ./.mcp.json (OAuth mode, no credential inside).
|
|
29358
|
+
writeTextFile: (path, text) => writeFileSync5(path, text, { mode: 420 }),
|
|
29211
29359
|
copyFile: (from, to) => copyFileSync(from, to),
|
|
29212
29360
|
now: () => /* @__PURE__ */ new Date(),
|
|
29213
29361
|
cwd: () => process.cwd(),
|