@linzumi/cli 0.0.49-beta → 0.0.51-beta
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/index.js +179 -72
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/index.js
CHANGED
|
@@ -10152,7 +10152,7 @@ function realpathOrResolved(pathValue) {
|
|
|
10152
10152
|
}
|
|
10153
10153
|
|
|
10154
10154
|
// src/version.ts
|
|
10155
|
-
var linzumiCliVersion = "0.0.
|
|
10155
|
+
var linzumiCliVersion = "0.0.51-beta";
|
|
10156
10156
|
var linzumiCliVersionText = `linzumi ${linzumiCliVersion}`;
|
|
10157
10157
|
|
|
10158
10158
|
// src/runnerLock.ts
|
|
@@ -11364,6 +11364,7 @@ async function discoverCodexThreads(codex, cwd) {
|
|
|
11364
11364
|
preview: stringValue(thread.preview) ?? "",
|
|
11365
11365
|
cwd: stringValue(thread.cwd) ?? "",
|
|
11366
11366
|
source: stringValue(thread.source) ?? "",
|
|
11367
|
+
createdAt: integerValue(thread.createdAt) ?? stringValue(thread.createdAt) ?? null,
|
|
11367
11368
|
updatedAt: integerValue(thread.updatedAt) ?? null,
|
|
11368
11369
|
status: objectValue(thread.status) ?? null
|
|
11369
11370
|
})).filter((thread) => thread.id !== "");
|
|
@@ -11747,6 +11748,22 @@ ${args.developerPrompt}
|
|
|
11747
11748
|
`;
|
|
11748
11749
|
const linzumiContext = args.linzumiContext === undefined ? "" : `
|
|
11749
11750
|
${formatLinzumiConversationContextForPrompt(args.linzumiContext)}
|
|
11751
|
+
`;
|
|
11752
|
+
const reviewGuidance = `
|
|
11753
|
+
<linzumi_pr_review_guidance>
|
|
11754
|
+
For frontend or other user-visible changes, ask whether the user wants
|
|
11755
|
+
screenshot or screen-recording proof, ideally before/after when that helps
|
|
11756
|
+
review the change. Screen recordings should be WebM or MP4. When the user asks
|
|
11757
|
+
you to open or update a PR, attach or link that proof in the PR when feasible,
|
|
11758
|
+
and state the exact blocker when it is not feasible.
|
|
11759
|
+
</linzumi_pr_review_guidance>
|
|
11760
|
+
|
|
11761
|
+
<linzumi_user_visible_writing_guide>
|
|
11762
|
+
When you write user-visible UI, status, error, or PR-review copy, keep it short,
|
|
11763
|
+
direct, and focused on the user's outcome. Avoid implementation-heavy sentences,
|
|
11764
|
+
stacked clauses, and internal mechanics unless the user explicitly needs that
|
|
11765
|
+
detail.
|
|
11766
|
+
</linzumi_user_visible_writing_guide>
|
|
11750
11767
|
`;
|
|
11751
11768
|
return `<context>
|
|
11752
11769
|
You are a Linzumi Codex session launched by the Linzumi Commander.
|
|
@@ -11791,6 +11808,7 @@ GOOD preview command: npm run dev -- --host 0.0.0.0 --port 8787
|
|
|
11791
11808
|
BAD preview command: npm run dev -- --host 127.0.0.1
|
|
11792
11809
|
</examples>
|
|
11793
11810
|
${linzumiContext}
|
|
11811
|
+
${reviewGuidance}
|
|
11794
11812
|
${customPrompt}
|
|
11795
11813
|
<task_reminder>
|
|
11796
11814
|
You are the Commander-launched Linzumi Codex session. Do the implementation
|
|
@@ -14879,13 +14897,15 @@ var esm_default4 = createPrompt((config, done) => {
|
|
|
14879
14897
|
`).trimEnd();
|
|
14880
14898
|
return `${lines}${cursorHide}`;
|
|
14881
14899
|
});
|
|
14882
|
-
// src/
|
|
14900
|
+
// src/signupFlow.ts
|
|
14883
14901
|
import { spawn as spawn7 } from "node:child_process";
|
|
14884
14902
|
import {
|
|
14903
|
+
closeSync as closeSync2,
|
|
14885
14904
|
existsSync as existsSync10,
|
|
14886
14905
|
constants as fsConstants,
|
|
14887
14906
|
mkdirSync as mkdirSync10,
|
|
14888
14907
|
mkdtempSync as mkdtempSync3,
|
|
14908
|
+
openSync as openSync3,
|
|
14889
14909
|
readFileSync as readFileSync10,
|
|
14890
14910
|
readdirSync,
|
|
14891
14911
|
rmSync as rmSync3,
|
|
@@ -14916,7 +14936,8 @@ function createSignupServerClient(args) {
|
|
|
14916
14936
|
method: "POST",
|
|
14917
14937
|
body: {
|
|
14918
14938
|
email: request.email,
|
|
14919
|
-
mode: request.mode
|
|
14939
|
+
mode: request.mode,
|
|
14940
|
+
selected_tasks: request.selectedTasks ?? []
|
|
14920
14941
|
},
|
|
14921
14942
|
render: renderEmailCodeStart
|
|
14922
14943
|
}),
|
|
@@ -15185,7 +15206,7 @@ function booleanValue(record, key) {
|
|
|
15185
15206
|
throw new Error(`signup ${key} was incomplete`);
|
|
15186
15207
|
}
|
|
15187
15208
|
|
|
15188
|
-
// src/
|
|
15209
|
+
// src/signupFlow.ts
|
|
15189
15210
|
var commanderConnectedMarkers = [
|
|
15190
15211
|
"Connected to Linzumi",
|
|
15191
15212
|
"Runner connected:",
|
|
@@ -15216,7 +15237,7 @@ secure mission control for all your agents on your computers
|
|
|
15216
15237
|
`;
|
|
15217
15238
|
var logoTagline = "secure mission control for all your agents on your computers";
|
|
15218
15239
|
var signupCancelMessage = "Y'all come back soon, ya hear?";
|
|
15219
|
-
function
|
|
15240
|
+
function signupHelpText() {
|
|
15220
15241
|
return [
|
|
15221
15242
|
"Usage:",
|
|
15222
15243
|
" linzumi signup",
|
|
@@ -15286,7 +15307,7 @@ function comparableServiceUrl(value) {
|
|
|
15286
15307
|
return trimmed.replace(/\/+$/u, "");
|
|
15287
15308
|
}
|
|
15288
15309
|
}
|
|
15289
|
-
async function
|
|
15310
|
+
async function runSignupFlow(deps = {}) {
|
|
15290
15311
|
const input = deps.input ?? defaultStdin;
|
|
15291
15312
|
const output = deps.output ?? defaultStdout;
|
|
15292
15313
|
const prompts = deps.prompts ?? inquirerPrompts(input, output);
|
|
@@ -15363,18 +15384,25 @@ async function runSignupMockFlow(deps = {}) {
|
|
|
15363
15384
|
projectDiscovery: { type: "ready", codeRoot: guessedCodeRoot, projects }
|
|
15364
15385
|
});
|
|
15365
15386
|
const selectedProjectPaths = await prompts.projectPicker({
|
|
15366
|
-
message: "
|
|
15387
|
+
message: "Pick the first folders where Linzumi should start agents",
|
|
15367
15388
|
projects,
|
|
15368
15389
|
defaultSelectedPaths: defaultSelectedProjectPaths(forceSignup ? [] : readConfiguredProjectPaths(serviceUrl))
|
|
15369
15390
|
});
|
|
15370
|
-
|
|
15391
|
+
const projectPreparation = prepareSelectedSignupProjects(selectedProjectPaths, projects);
|
|
15392
|
+
if (projectPreparation.type === "failed") {
|
|
15393
|
+
writeProjectPreparationFailure(output, projectPreparation.message);
|
|
15394
|
+
writeDebugLaunchPayload(output, state, debugLaunchPayload);
|
|
15395
|
+
return;
|
|
15396
|
+
}
|
|
15397
|
+
const preparedProjectPaths = projectPreparation.selectedProjectPaths;
|
|
15398
|
+
if (preparedProjectPaths.length === 0) {
|
|
15371
15399
|
writeNoProjectsFound(output);
|
|
15372
15400
|
writeDebugLaunchPayload(output, state, debugLaunchPayload);
|
|
15373
15401
|
return;
|
|
15374
15402
|
}
|
|
15375
15403
|
state = updateSignupState(state, {
|
|
15376
15404
|
currentStep: "tasks",
|
|
15377
|
-
selectedProjectPaths
|
|
15405
|
+
selectedProjectPaths: preparedProjectPaths
|
|
15378
15406
|
});
|
|
15379
15407
|
writeSignupScreen(output, state);
|
|
15380
15408
|
writePreflightSummary(output, state);
|
|
@@ -15407,7 +15435,8 @@ async function runSignupMockFlow(deps = {}) {
|
|
|
15407
15435
|
writeSelectedProjectsSummary(output, state);
|
|
15408
15436
|
writeSelectedTasksSummary(output, state);
|
|
15409
15437
|
const projectGitEmails = await projectGitEmailsPromise;
|
|
15410
|
-
const
|
|
15438
|
+
const configuredGitEmail = configuredGitEmailFromPreflights(state.preflightChecks);
|
|
15439
|
+
const globalGitEmail = configuredGitEmail !== undefined && usefulSuggestedEmail(configuredGitEmail) ? configuredGitEmail : undefined;
|
|
15411
15440
|
const emailCandidates = globalGitEmail === undefined ? uniqueDefinedEmails(projectGitEmails) : [];
|
|
15412
15441
|
state = updateSignupState(state, { projectGitEmails, emailCandidates });
|
|
15413
15442
|
const reusedSignupAuth = state.reusedSignupAuth;
|
|
@@ -15421,7 +15450,8 @@ async function runSignupMockFlow(deps = {}) {
|
|
|
15421
15450
|
state = updateSignupState(state, { currentStep: "code", email });
|
|
15422
15451
|
const codeRequest = signupServerClient === undefined ? { type: "sent", request: { pendingId: "mock", email } } : await startEmailCodeForSignup(signupServerClient, {
|
|
15423
15452
|
email,
|
|
15424
|
-
mode: "existing_or_create"
|
|
15453
|
+
mode: "existing_or_create",
|
|
15454
|
+
selectedTasks: selectedTaskTitlesFromState(state)
|
|
15425
15455
|
});
|
|
15426
15456
|
if (codeRequest.type === "failed") {
|
|
15427
15457
|
state = updateSignupState(state, {
|
|
@@ -15771,13 +15801,22 @@ function writeProjectPickerIntro(output, state) {
|
|
|
15771
15801
|
if (!preflightsFinished(state)) {
|
|
15772
15802
|
return;
|
|
15773
15803
|
}
|
|
15774
|
-
writeBoundedLine(output, blue("2) Where should Linzumi let your Codex agents
|
|
15804
|
+
writeBoundedLine(output, blue("2) Where should Linzumi let your Codex agents start?"));
|
|
15805
|
+
writeWrappedPrefixMessage(output, " ", "Pick the first local folders for Mission Control. You can add or remove folders later.", muted);
|
|
15775
15806
|
}
|
|
15776
15807
|
function writeNoProjectsFound(output) {
|
|
15777
15808
|
output.write(`${blue("?")} No projects found yet
|
|
15778
15809
|
|
|
15779
15810
|
`);
|
|
15780
15811
|
}
|
|
15812
|
+
function writeProjectPreparationFailure(output, message) {
|
|
15813
|
+
output.write(`
|
|
15814
|
+
`);
|
|
15815
|
+
writeBoundedLine(output, blue("We couldn't prepare that project"));
|
|
15816
|
+
writeStatusMessage(output, red("x"), message);
|
|
15817
|
+
writeBoundedLine(output, muted(" Choose another folder or move the conflicting demo directory, then retry:"));
|
|
15818
|
+
writeBoundedLine(output, blue(" npx -y @linzumi/cli@latest signup"));
|
|
15819
|
+
}
|
|
15781
15820
|
function writeSelectedProjectsSummary(output, state) {
|
|
15782
15821
|
const selectedProjects = selectedProjectsFromState(state);
|
|
15783
15822
|
output.write(`
|
|
@@ -15822,7 +15861,7 @@ function signupProgressRows(state) {
|
|
|
15822
15861
|
checked: selectedTaskTitlesFromState(state).length > 0
|
|
15823
15862
|
},
|
|
15824
15863
|
{
|
|
15825
|
-
label: "
|
|
15864
|
+
label: "Mission Control will open with your work already running",
|
|
15826
15865
|
checked: state.currentStep === "launch"
|
|
15827
15866
|
}
|
|
15828
15867
|
];
|
|
@@ -15891,7 +15930,7 @@ async function chooseTasks(prompts, output, tasks) {
|
|
|
15891
15930
|
output.write(`
|
|
15892
15931
|
`);
|
|
15893
15932
|
writeBoundedLine(output, blue("4) What should your Codex agents start working on?"));
|
|
15894
|
-
writeBoundedLine(output, muted(" Pick up to 5
|
|
15933
|
+
writeBoundedLine(output, muted(" Pick up to 5 small starter tasks. These just get agents moving."));
|
|
15895
15934
|
writeBoundedLine(output, muted(" Select suggested tasks, or choose the last row to type your own task."));
|
|
15896
15935
|
const customTaskChoice = "__custom_task__";
|
|
15897
15936
|
const checkedTasks = await prompts.checkbox({
|
|
@@ -15961,7 +16000,7 @@ function writeLaunchSummary(output, state) {
|
|
|
15961
16000
|
writeBoundedLine(output, blue(" npx -y @linzumi/cli@latest signup"));
|
|
15962
16001
|
return;
|
|
15963
16002
|
}
|
|
15964
|
-
writeBoundedLine(output, blue("7)
|
|
16003
|
+
writeBoundedLine(output, blue("7) Mission Control will open with your work already running"));
|
|
15965
16004
|
const taskCount = selectedTaskTitlesFromState(state).length;
|
|
15966
16005
|
writeBoundedLine(output, muted(` Starting ${taskCount} ${taskCount === 1 ? "task" : "tasks"} and saying hello to the Linzumi team...`));
|
|
15967
16006
|
state.starterTaskLaunches.forEach((launch) => {
|
|
@@ -16037,12 +16076,20 @@ async function defaultSignupCommanderLauncher(args) {
|
|
|
16037
16076
|
};
|
|
16038
16077
|
}
|
|
16039
16078
|
const logFile = signupCommanderLaunchLogFile(runnerId);
|
|
16079
|
+
let stdoutFd;
|
|
16080
|
+
let stderrFd;
|
|
16040
16081
|
try {
|
|
16041
16082
|
mkdirSync10(dirname9(logFile), { recursive: true });
|
|
16083
|
+
stdoutFd = openSync3(logFile, "a");
|
|
16084
|
+
stderrFd = openSync3(logFile, "a");
|
|
16042
16085
|
const child = spawn7(processExecPath, signupCommanderConnectSpawnArgs(args, runnerId, logFile, scriptPath), {
|
|
16043
16086
|
detached: true,
|
|
16044
|
-
stdio: "ignore"
|
|
16087
|
+
stdio: ["ignore", stdoutFd, stderrFd]
|
|
16045
16088
|
});
|
|
16089
|
+
closeSync2(stdoutFd);
|
|
16090
|
+
closeSync2(stderrFd);
|
|
16091
|
+
stdoutFd = undefined;
|
|
16092
|
+
stderrFd = undefined;
|
|
16046
16093
|
child.unref();
|
|
16047
16094
|
const connected = await waitForSignupCommanderConnection(logFile, 30000);
|
|
16048
16095
|
if (!connected) {
|
|
@@ -16067,6 +16114,9 @@ async function defaultSignupCommanderLauncher(args) {
|
|
|
16067
16114
|
restartCommand,
|
|
16068
16115
|
error: error instanceof Error ? error.message : String(error)
|
|
16069
16116
|
};
|
|
16117
|
+
} finally {
|
|
16118
|
+
closeFileDescriptor(stdoutFd);
|
|
16119
|
+
closeFileDescriptor(stderrFd);
|
|
16070
16120
|
}
|
|
16071
16121
|
}
|
|
16072
16122
|
async function waitForSignupCommanderConnection(logFile, timeoutMs) {
|
|
@@ -16093,6 +16143,14 @@ function readTextFile(path) {
|
|
|
16093
16143
|
return;
|
|
16094
16144
|
}
|
|
16095
16145
|
}
|
|
16146
|
+
function closeFileDescriptor(fd) {
|
|
16147
|
+
if (fd === undefined) {
|
|
16148
|
+
return;
|
|
16149
|
+
}
|
|
16150
|
+
try {
|
|
16151
|
+
closeSync2(fd);
|
|
16152
|
+
} catch (_error) {}
|
|
16153
|
+
}
|
|
16096
16154
|
async function waitForSignupCommanderLogChange(logFile, deadline, ready2) {
|
|
16097
16155
|
const remaining = Math.max(0, deadline - Date.now());
|
|
16098
16156
|
await new Promise((resolve9) => {
|
|
@@ -16152,8 +16210,6 @@ function signupCommanderConnectSpawnArgs(args, runnerId, logFile, scriptPath) {
|
|
|
16152
16210
|
args.serviceUrl,
|
|
16153
16211
|
"--workspace",
|
|
16154
16212
|
args.workspaceSlug,
|
|
16155
|
-
"--channel",
|
|
16156
|
-
args.officeChannelSlug,
|
|
16157
16213
|
"--runner-id",
|
|
16158
16214
|
runnerId,
|
|
16159
16215
|
"--log-file",
|
|
@@ -16624,6 +16680,40 @@ function projectPickerRows(projects, selectedPaths, pathInput) {
|
|
|
16624
16680
|
const recentRows = projects.filter((project) => !selectedPaths.has(project.path)).slice(0, remainingCount).map((project) => ({ project, selected: false }));
|
|
16625
16681
|
return [...selectedRows, ...recentRows];
|
|
16626
16682
|
}
|
|
16683
|
+
function prepareSelectedSignupProjects(selectedProjectPaths, projects) {
|
|
16684
|
+
const normalizedPaths = selectedProjectPaths.map(normalizeProjectPath);
|
|
16685
|
+
const selectedProjectByPath = new Map(normalizedPaths.map((path) => [
|
|
16686
|
+
path,
|
|
16687
|
+
projectFromKnownOrPath(projects, path)
|
|
16688
|
+
]));
|
|
16689
|
+
for (const [path, project] of selectedProjectByPath) {
|
|
16690
|
+
if (project.source === "hello_linzumi_demo") {
|
|
16691
|
+
try {
|
|
16692
|
+
createHelloLinzumiProject({ rootPath: path });
|
|
16693
|
+
} catch (error) {
|
|
16694
|
+
return {
|
|
16695
|
+
type: "failed",
|
|
16696
|
+
message: demoProjectCreateFailureMessage(path, error)
|
|
16697
|
+
};
|
|
16698
|
+
}
|
|
16699
|
+
}
|
|
16700
|
+
}
|
|
16701
|
+
return {
|
|
16702
|
+
type: "ready",
|
|
16703
|
+
selectedProjectPaths: Array.from(new Set(normalizedPaths))
|
|
16704
|
+
};
|
|
16705
|
+
}
|
|
16706
|
+
function demoProjectCreateFailureMessage(path, error) {
|
|
16707
|
+
const detail = error instanceof Error ? error.message : String(error);
|
|
16708
|
+
return `The Hello Linzumi demo project could not be created at ${path}. ${detail}`;
|
|
16709
|
+
}
|
|
16710
|
+
function helloLinzumiDemoProjectOption() {
|
|
16711
|
+
return {
|
|
16712
|
+
path: normalizeProjectPath(defaultHelloLinzumiProjectDir),
|
|
16713
|
+
language: "Demo",
|
|
16714
|
+
source: "hello_linzumi_demo"
|
|
16715
|
+
};
|
|
16716
|
+
}
|
|
16627
16717
|
function renderProjectPickerRow(row, index, state) {
|
|
16628
16718
|
const active = index === state.highlightIndex;
|
|
16629
16719
|
const marker = active ? blue("›") : " ";
|
|
@@ -16631,11 +16721,12 @@ function renderProjectPickerRow(row, index, state) {
|
|
|
16631
16721
|
const gitHead = row.project.gitHead;
|
|
16632
16722
|
const sha = truncateEnd(gitHead?.sha ?? "-----", 5);
|
|
16633
16723
|
const date = gitHead?.date ?? "----------";
|
|
16634
|
-
const message = gitHead?.message ?? "";
|
|
16724
|
+
const message = gitHead?.message ?? (row.project.source === "hello_linzumi_demo" ? "Create a safe demo app" : "");
|
|
16635
16725
|
return ` ${marker}${selected} ${truncateMiddle(row.project.path, 41).padEnd(41, " ")} ${sha.padEnd(5, " ")} ${date.padEnd(10, " ")} ${truncateEnd(message, 34)}`;
|
|
16636
16726
|
}
|
|
16637
16727
|
function projectFromKnownOrPath(projects, path) {
|
|
16638
|
-
|
|
16728
|
+
const normalizedPath = normalizeProjectPath(path);
|
|
16729
|
+
return projects.find((project) => project.path === normalizedPath) ?? (normalizedPath === normalizeProjectPath(defaultHelloLinzumiProjectDir) ? helloLinzumiDemoProjectOption() : undefined) ?? customProjectOption(normalizedPath);
|
|
16639
16730
|
}
|
|
16640
16731
|
function selectedProjectsFromState(state) {
|
|
16641
16732
|
if (state.projectDiscovery.type !== "ready") {
|
|
@@ -16866,7 +16957,8 @@ function uniqueEmails(emails) {
|
|
|
16866
16957
|
}
|
|
16867
16958
|
function usefulSuggestedEmail(email) {
|
|
16868
16959
|
const normalizedEmail = email.trim().toLocaleLowerCase();
|
|
16869
|
-
|
|
16960
|
+
const domain = normalizedEmail.split("@")[1] ?? "";
|
|
16961
|
+
return !(normalizedEmail === "noreply@github.com" || normalizedEmail.endsWith("@users.noreply.github.com") || normalizedEmail.endsWith("@noreply.github.com") || domain === "localhost" || domain.endsWith(".local"));
|
|
16870
16962
|
}
|
|
16871
16963
|
function configuredGitEmailFromPreflights(checks) {
|
|
16872
16964
|
const gitEmailCheck = checks.find((check2) => check2.id === "git_email");
|
|
@@ -16945,8 +17037,9 @@ function defaultPreflightRuntime() {
|
|
|
16945
17037
|
};
|
|
16946
17038
|
}
|
|
16947
17039
|
function defaultProjectDiscoveryRuntime() {
|
|
17040
|
+
const cwd = process.cwd();
|
|
16948
17041
|
return {
|
|
16949
|
-
discoverProjects: async (homeDir) =>
|
|
17042
|
+
discoverProjects: async (homeDir) => discoverProjectsFromSignupRoots(homeDir, cwd)
|
|
16950
17043
|
};
|
|
16951
17044
|
}
|
|
16952
17045
|
function defaultTaskSuggestionRuntime() {
|
|
@@ -17093,7 +17186,7 @@ async function resolveSignupCodexCommand(env = process.env, homeDir = homedir8()
|
|
|
17093
17186
|
"Set LINZUMI_SIGNUP_CODEX_BIN to a working Codex executable or install Codex in a home-managed location such as ~/.volta/bin/codex."
|
|
17094
17187
|
].join(" "));
|
|
17095
17188
|
}
|
|
17096
|
-
throw new Error("Codex task suggestions could not find Codex.
|
|
17189
|
+
throw new Error("Codex task suggestions could not find Codex. Install Codex first, or set LINZUMI_SIGNUP_CODEX_BIN to a working Codex executable.");
|
|
17097
17190
|
}
|
|
17098
17191
|
function firstConfiguredValue(values) {
|
|
17099
17192
|
return values.find((value) => value !== undefined && value.trim() !== "");
|
|
@@ -17439,6 +17532,33 @@ function discoverProjectsUnderRoot(codeRoot) {
|
|
|
17439
17532
|
gitHead: cachedGitHeadSummary(project.path)
|
|
17440
17533
|
})).sort(sortProjectsByRecentGitCommit).slice(0, 20);
|
|
17441
17534
|
}
|
|
17535
|
+
function discoverProjectsFromSignupRoots(homeDir, cwd) {
|
|
17536
|
+
const currentProjects = discoverProjectsFromCurrentDirectory(cwd);
|
|
17537
|
+
const guessedProjects = discoverProjectsFromGuessedRoots(homeDir);
|
|
17538
|
+
const demoProject = helloLinzumiDemoProjectOption();
|
|
17539
|
+
const [firstCurrentProject, ...remainingCurrentProjects] = currentProjects;
|
|
17540
|
+
const seenPaths = new Set;
|
|
17541
|
+
return [
|
|
17542
|
+
...firstCurrentProject === undefined ? [] : [firstCurrentProject],
|
|
17543
|
+
demoProject,
|
|
17544
|
+
...remainingCurrentProjects,
|
|
17545
|
+
...guessedProjects
|
|
17546
|
+
].filter((project) => {
|
|
17547
|
+
if (seenPaths.has(project.path)) {
|
|
17548
|
+
return false;
|
|
17549
|
+
}
|
|
17550
|
+
seenPaths.add(project.path);
|
|
17551
|
+
return true;
|
|
17552
|
+
}).slice(0, 24);
|
|
17553
|
+
}
|
|
17554
|
+
function discoverProjectsFromCurrentDirectory(cwd) {
|
|
17555
|
+
const normalizedCwd = normalizeProjectPath(cwd);
|
|
17556
|
+
const cwdProject = directoryExists(normalizedCwd) ? [customProjectOption(normalizedCwd)] : [];
|
|
17557
|
+
const gitRoot = gitTopLevel(normalizedCwd);
|
|
17558
|
+
const gitRootProject = gitRoot !== undefined && gitRoot !== normalizedCwd ? [customProjectOption(gitRoot)] : [];
|
|
17559
|
+
const childProjects = discoverProjectsUnderRoot(normalizedCwd);
|
|
17560
|
+
return [...cwdProject, ...gitRootProject, ...childProjects];
|
|
17561
|
+
}
|
|
17442
17562
|
function discoverProjectsFromGuessedRoots(homeDir) {
|
|
17443
17563
|
const guessedRoots = ["src", "code", "projects"].map((name) => join12(homeDir, name));
|
|
17444
17564
|
const projects = guessedRoots.flatMap((root) => discoverProjectsUnderRoot(root));
|
|
@@ -17451,6 +17571,9 @@ function discoverProjectsFromGuessedRoots(homeDir) {
|
|
|
17451
17571
|
return true;
|
|
17452
17572
|
}).sort(sortProjectsByRecentGitCommit).slice(0, 20);
|
|
17453
17573
|
}
|
|
17574
|
+
function gitTopLevel(cwd) {
|
|
17575
|
+
return spawnSyncGit(["rev-parse", "--show-toplevel"], cwd);
|
|
17576
|
+
}
|
|
17454
17577
|
function sortProjectsByRecentGitCommit(left, right) {
|
|
17455
17578
|
const leftTimestamp = left.gitHead?.timestamp ?? 0;
|
|
17456
17579
|
const rightTimestamp = right.gitHead?.timestamp ?? 0;
|
|
@@ -17580,9 +17703,9 @@ function renderPreflightRow(check2, spinnerFrame, columns) {
|
|
|
17580
17703
|
// src/commanderDaemon.ts
|
|
17581
17704
|
import {
|
|
17582
17705
|
existsSync as existsSync11,
|
|
17583
|
-
closeSync as
|
|
17706
|
+
closeSync as closeSync3,
|
|
17584
17707
|
mkdirSync as mkdirSync11,
|
|
17585
|
-
openSync as
|
|
17708
|
+
openSync as openSync4,
|
|
17586
17709
|
readFileSync as readFileSync11,
|
|
17587
17710
|
watch as watch2,
|
|
17588
17711
|
writeFileSync as writeFileSync9
|
|
@@ -17622,8 +17745,8 @@ function startCommanderDaemon(options) {
|
|
|
17622
17745
|
];
|
|
17623
17746
|
mkdirSync11(statusDir, { recursive: true });
|
|
17624
17747
|
mkdirSync11(dirname10(logFile), { recursive: true });
|
|
17625
|
-
const out =
|
|
17626
|
-
const err =
|
|
17748
|
+
const out = openSync4(logFile, "a");
|
|
17749
|
+
const err = openSync4(logFile, "a");
|
|
17627
17750
|
writeCliAuditEvent("process.spawn", {
|
|
17628
17751
|
command: nodeBin,
|
|
17629
17752
|
args: command,
|
|
@@ -17642,8 +17765,8 @@ function startCommanderDaemon(options) {
|
|
|
17642
17765
|
pid: child.pid,
|
|
17643
17766
|
purpose: "commander_daemon.start"
|
|
17644
17767
|
}, { sessionId: options.runnerId });
|
|
17645
|
-
|
|
17646
|
-
|
|
17768
|
+
closeSync3(out);
|
|
17769
|
+
closeSync3(err);
|
|
17647
17770
|
child.unref();
|
|
17648
17771
|
if (child.pid === undefined) {
|
|
17649
17772
|
throw new Error("commander daemon did not report a pid");
|
|
@@ -17917,12 +18040,12 @@ async function main(args) {
|
|
|
17917
18040
|
return;
|
|
17918
18041
|
case "signupMock":
|
|
17919
18042
|
if (parsed.args.includes("--help") || parsed.args.includes("-h")) {
|
|
17920
|
-
process.stdout.write(
|
|
18043
|
+
process.stdout.write(signupHelpText());
|
|
17921
18044
|
return;
|
|
17922
18045
|
}
|
|
17923
18046
|
{
|
|
17924
18047
|
const options = parseSignupTuiOptions(parsed.args);
|
|
17925
|
-
await
|
|
18048
|
+
await runSignupFlow({
|
|
17926
18049
|
forceSignup: options.forceSignup,
|
|
17927
18050
|
serviceUrl: options.serviceUrl,
|
|
17928
18051
|
openBrowser: false,
|
|
@@ -17933,12 +18056,12 @@ async function main(args) {
|
|
|
17933
18056
|
return;
|
|
17934
18057
|
case "signup":
|
|
17935
18058
|
if (parsed.args.includes("--help") || parsed.args.includes("-h")) {
|
|
17936
|
-
process.stdout.write(
|
|
18059
|
+
process.stdout.write(signupHelpText());
|
|
17937
18060
|
return;
|
|
17938
18061
|
}
|
|
17939
18062
|
{
|
|
17940
18063
|
const options = parseSignupTuiOptions(parsed.args);
|
|
17941
|
-
await
|
|
18064
|
+
await runSignupFlow({
|
|
17942
18065
|
forceSignup: options.forceSignup,
|
|
17943
18066
|
serviceUrl: options.serviceUrl,
|
|
17944
18067
|
openBrowser: options.openBrowser,
|
|
@@ -18516,7 +18639,8 @@ async function parseRunnerArgs(args, deps = {
|
|
|
18516
18639
|
`);
|
|
18517
18640
|
process.exit(0);
|
|
18518
18641
|
}
|
|
18519
|
-
|
|
18642
|
+
rejectConnectChannelFlags(values);
|
|
18643
|
+
const workspaceSlug = stringValue4(values, "workspace");
|
|
18520
18644
|
const kandanUrl = stringValue4(values, "linzumi-url") ?? defaultLinzumiWebSocketUrl;
|
|
18521
18645
|
const cwd = stringValue4(values, "cwd") ?? process.cwd();
|
|
18522
18646
|
const cwdAllowedCwds = assertConfiguredAllowedCwds([cwd]);
|
|
@@ -18528,8 +18652,7 @@ async function parseRunnerArgs(args, deps = {
|
|
|
18528
18652
|
const token = await deps.resolveToken({
|
|
18529
18653
|
kandanUrl,
|
|
18530
18654
|
explicitToken,
|
|
18531
|
-
workspaceSlug
|
|
18532
|
-
channelSlug: connectTarget?.channelSlug,
|
|
18655
|
+
workspaceSlug,
|
|
18533
18656
|
authFilePath: stringValue4(values, "auth-file"),
|
|
18534
18657
|
callbackHost: stringValue4(values, "oauth-callback-host"),
|
|
18535
18658
|
reportRejectedCachedToken: () => {
|
|
@@ -18537,7 +18660,6 @@ async function parseRunnerArgs(args, deps = {
|
|
|
18537
18660
|
`);
|
|
18538
18661
|
}
|
|
18539
18662
|
});
|
|
18540
|
-
const channelSession = parseChannelSession(values, token, connectTarget);
|
|
18541
18663
|
const editorRuntime = await deps.resolveEditorRuntime({
|
|
18542
18664
|
kandanUrl,
|
|
18543
18665
|
token,
|
|
@@ -18568,9 +18690,8 @@ async function parseRunnerArgs(args, deps = {
|
|
|
18568
18690
|
editorRuntime: editorRuntime.runtime,
|
|
18569
18691
|
socketFactory: trustedWebSocketFactory(kandanTlsTrustFromEnv()),
|
|
18570
18692
|
dependencyStatus,
|
|
18571
|
-
workspaceSlug:
|
|
18572
|
-
runtimeDefaults: runnerRuntimeDefaultsFromValues(values)
|
|
18573
|
-
channelSession
|
|
18693
|
+
workspaceSlug: workspaceSlug ?? singleWorkspaceScopeFromAccessToken(token),
|
|
18694
|
+
runtimeDefaults: runnerRuntimeDefaultsFromValues(values)
|
|
18574
18695
|
};
|
|
18575
18696
|
}
|
|
18576
18697
|
function runnerRuntimeDefaultsFromValues(values) {
|
|
@@ -18667,7 +18788,7 @@ function stripDaemonSupervisorFlags(args) {
|
|
|
18667
18788
|
function rejectStartTargetingFlags(values) {
|
|
18668
18789
|
const unsupportedFlags = ["workspace", "channel"].filter((flag) => values.has(flag));
|
|
18669
18790
|
if (unsupportedFlags.length > 0) {
|
|
18670
|
-
throw new Error(`linzumi start chooses its workspace during browser onboarding; remove ${unsupportedFlags.map((flag) => `--${flag}`).join(", ")} or use linzumi connect for an explicit workspace
|
|
18791
|
+
throw new Error(`linzumi start chooses its workspace during browser onboarding; remove ${unsupportedFlags.map((flag) => `--${flag}`).join(", ")} or use linzumi connect --workspace for an explicit workspace.`);
|
|
18671
18792
|
}
|
|
18672
18793
|
}
|
|
18673
18794
|
function resolveUserPath(pathValue) {
|
|
@@ -18679,26 +18800,6 @@ function resolveUserPath(pathValue) {
|
|
|
18679
18800
|
}
|
|
18680
18801
|
return resolve10(pathValue);
|
|
18681
18802
|
}
|
|
18682
|
-
function parseChannelSession(values, token, target) {
|
|
18683
|
-
if (target === undefined || target.channelSlug === undefined) {
|
|
18684
|
-
return;
|
|
18685
|
-
}
|
|
18686
|
-
const listenUser = stringValue4(values, "listen-user") ?? defaultListenUserFromToken(token);
|
|
18687
|
-
return {
|
|
18688
|
-
workspaceSlug: target.workspaceSlug,
|
|
18689
|
-
channelSlug: target.channelSlug,
|
|
18690
|
-
kandanThreadId: stringValue4(values, "linzumi-thread-id"),
|
|
18691
|
-
listenUser,
|
|
18692
|
-
model: stringValue4(values, "model"),
|
|
18693
|
-
reasoningEffort: stringValue4(values, "reasoning-effort"),
|
|
18694
|
-
sandbox: stringValue4(values, "sandbox"),
|
|
18695
|
-
approvalPolicy: stringValue4(values, "approval-policy"),
|
|
18696
|
-
streamFlushMs: positiveIntegerValue2(values, "stream-flush-ms")
|
|
18697
|
-
};
|
|
18698
|
-
}
|
|
18699
|
-
function parseConnectTarget(values) {
|
|
18700
|
-
return parseOptionalChannelTarget(values);
|
|
18701
|
-
}
|
|
18702
18803
|
function defaultListenUserFromToken(token) {
|
|
18703
18804
|
const username = identityFromAccessToken(token).actorUsername;
|
|
18704
18805
|
if (username !== undefined) {
|
|
@@ -18706,6 +18807,17 @@ function defaultListenUserFromToken(token) {
|
|
|
18706
18807
|
}
|
|
18707
18808
|
throw new Error("missing --listen-user and authenticated user is unavailable");
|
|
18708
18809
|
}
|
|
18810
|
+
function rejectConnectChannelFlags(values) {
|
|
18811
|
+
const unsupportedFlags = [
|
|
18812
|
+
"channel",
|
|
18813
|
+
"linzumi-thread-id",
|
|
18814
|
+
"listen-user"
|
|
18815
|
+
].filter((flag) => values.has(flag));
|
|
18816
|
+
if (unsupportedFlags.length === 0) {
|
|
18817
|
+
return;
|
|
18818
|
+
}
|
|
18819
|
+
throw new Error(`linzumi connect starts a workspace Commander; remove ${unsupportedFlags.map((flag) => `--${flag}`).join(", ")}.`);
|
|
18820
|
+
}
|
|
18709
18821
|
function parseOptionalChannelTarget(values) {
|
|
18710
18822
|
const channel = stringValue4(values, "channel");
|
|
18711
18823
|
const workspace = stringValue4(values, "workspace");
|
|
@@ -18792,7 +18904,7 @@ Usage:
|
|
|
18792
18904
|
linzumi commander <folder> [options]
|
|
18793
18905
|
linzumi start <folder> [options]
|
|
18794
18906
|
linzumi paths list|add|remove [path]
|
|
18795
|
-
linzumi connect --workspace <slug> [
|
|
18907
|
+
linzumi connect --workspace <slug> [options]
|
|
18796
18908
|
linzumi auth --linzumi-url <ws-url> [--workspace <slug> --channel <slug>]
|
|
18797
18909
|
|
|
18798
18910
|
Connection:
|
|
@@ -18802,12 +18914,8 @@ Connection:
|
|
|
18802
18914
|
--auth-file <path> Auth cache path, default ~/.linzumi/auth.json
|
|
18803
18915
|
--oauth-callback-host <ip> Callback host reachable by your browser
|
|
18804
18916
|
|
|
18805
|
-
Workspace
|
|
18917
|
+
Workspace:
|
|
18806
18918
|
--workspace <slug> Workspace slug
|
|
18807
|
-
--channel <slug|w/c> Optional channel slug, or workspace/channel
|
|
18808
|
-
--linzumi-thread-id <uuid> Resume an existing Linzumi thread instead of announcing a new root
|
|
18809
|
-
(deprecated alias: --kandan-thread-id)
|
|
18810
|
-
--listen-user <user|all> User whose replies are accepted, default authenticated user
|
|
18811
18919
|
|
|
18812
18920
|
Codex:
|
|
18813
18921
|
--cwd <path> Working directory for Codex, default current directory
|
|
@@ -18839,16 +18947,15 @@ Examples:
|
|
|
18839
18947
|
linzumi start ~/code/my-app
|
|
18840
18948
|
linzumi connect --workspace <your-workspace> --launch-tui
|
|
18841
18949
|
linzumi connect --workspace <your-workspace> --model gpt-5 --reasoning-effort low --fast --launch-tui
|
|
18842
|
-
linzumi connect --workspace <your-workspace> --channel <your-channel>
|
|
18843
18950
|
linzumi auth --workspace <your-workspace> --channel <your-channel>
|
|
18844
18951
|
linzumi paths add ~/code/my-app
|
|
18845
18952
|
linzumi paths list
|
|
18846
18953
|
|
|
18847
18954
|
Bad:
|
|
18848
|
-
linzumi connect --
|
|
18849
|
-
|
|
18850
|
-
linzumi connect --token "$TOKEN" --listen-
|
|
18851
|
-
Invalid
|
|
18955
|
+
linzumi connect --workspace <your-workspace> --channel <your-channel>
|
|
18956
|
+
Invalid: Commanders connect to workspaces, not channels.
|
|
18957
|
+
linzumi connect --token "$TOKEN" --listen-user sean
|
|
18958
|
+
Invalid: Commanders connect to workspaces, not individual channel listeners.
|
|
18852
18959
|
linzumi connect --workspace <your-workspace> --allowed-cwd /does/not/exist
|
|
18853
18960
|
Invalid --allowed-cwd: allowed cwd roots must exist locally.
|
|
18854
18961
|
linzumi connect --workspace <your-workspace> --forward-port vite
|
package/package.json
CHANGED