@linzumi/cli 0.0.19-beta → 0.0.22-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/src/json.ts DELETED
@@ -1,49 +0,0 @@
1
- /*
2
- - Date: 2026-04-24
3
- Spec: plans/2026-04-24-local-codex-channel-thread-binding-spec.md
4
- Relationship: Provides small JSON readers used by the local runner's
5
- channel binding and Codex transcript normalization without ad hoc casts.
6
- */
7
- import { type JsonObject, type JsonValue, isJsonObject } from "./protocol";
8
-
9
- export function objectValue(value: JsonValue | undefined): JsonObject | undefined {
10
- return value !== undefined && isJsonObject(value) ? value : undefined;
11
- }
12
-
13
- export function arrayValue(
14
- value: JsonValue | undefined,
15
- ): readonly JsonValue[] | undefined {
16
- return Array.isArray(value) ? value : undefined;
17
- }
18
-
19
- export function stringValue(value: JsonValue | undefined): string | undefined {
20
- return typeof value === "string" && value.trim() !== ""
21
- ? value.trim()
22
- : undefined;
23
- }
24
-
25
- export function integerValue(value: JsonValue | undefined): number | undefined {
26
- return typeof value === "number" && Number.isInteger(value)
27
- ? value
28
- : undefined;
29
- }
30
-
31
- export function stringListValue(value: JsonValue | undefined): string[] {
32
- if (!Array.isArray(value)) {
33
- return [];
34
- }
35
-
36
- return value.flatMap((entry) => {
37
- const normalized = stringValue(entry);
38
- return normalized === undefined ? [] : [normalized];
39
- });
40
- }
41
-
42
- export function parseJsonObjectOrUndefined(text: string): JsonObject | undefined {
43
- try {
44
- const parsed: unknown = JSON.parse(text);
45
- return isJsonObject(parsed) ? parsed : undefined;
46
- } catch (_error) {
47
- return undefined;
48
- }
49
- }
@@ -1,113 +0,0 @@
1
- /*
2
- - Date: 2026-04-24
3
- Spec: plans/2026-04-24-local-codex-channel-thread-binding-spec.md
4
- Relationship: Owns deterministic queue formatting and interrupt fusion for
5
- Kandan thread replies before they are sent into local Codex.
6
-
7
- - Date: 2026-04-24
8
- Spec: plans/2026-04-24-local-codex-runner-deep-quality-spec.md
9
- Relationship: Carries selected queued message seqs through interrupt fusion
10
- so the UI can show which pending replies were accepted by the interrupt.
11
-
12
- - Date: 2026-05-02
13
- Spec: plans/2026-05-02-agent-first-zero-to-codex-launch-plan.md
14
- Relationship: Carries Kandan attachment metadata through npm CLI runner queue
15
- fusion so attachment-backed replies stay intact before Codex sees them.
16
- */
17
- import { type KandanChatAttachment } from "./channelSessionSupport";
18
-
19
- export type QueuedKandanAttachment = KandanChatAttachment;
20
-
21
- export type QueuedKandanMessage = {
22
- readonly seq: number;
23
- readonly actorSlug: string | undefined;
24
- readonly actorUserId: number | undefined;
25
- readonly body: string;
26
- readonly attachments: readonly QueuedKandanAttachment[];
27
- };
28
-
29
- export type QueueInterruptResult =
30
- | {
31
- readonly ok: true;
32
- readonly queue: QueuedKandanMessage[];
33
- readonly selectedCount: number;
34
- readonly selectedSeqs: number[];
35
- readonly remainingCount: number;
36
- }
37
- | {
38
- readonly ok: false;
39
- readonly reason: "queue_empty";
40
- };
41
-
42
- export function formatQueuedKandanMessage(message: QueuedKandanMessage): string {
43
- const sender =
44
- message.actorSlug === undefined || message.actorSlug.trim() === ""
45
- ? "unknown"
46
- : message.actorSlug.trim();
47
- const userId =
48
- message.actorUserId === undefined
49
- ? "unknown"
50
- : message.actorUserId.toString();
51
-
52
- return [
53
- `Kandan message seq=${message.seq} from ${sender} (user_id=${userId}):`,
54
- "",
55
- message.body,
56
- ].join("\n");
57
- }
58
-
59
- export function codexInputForQueuedKandanMessage(message: QueuedKandanMessage): string {
60
- const command = message.body.trimStart();
61
-
62
- return command.startsWith("!") ? command : formatQueuedKandanMessage(message);
63
- }
64
-
65
- export function interruptQueuedMessages(
66
- queue: readonly QueuedKandanMessage[],
67
- throughSeq: number | undefined,
68
- ): QueueInterruptResult {
69
- const selected =
70
- throughSeq === undefined
71
- ? Array.from(queue)
72
- : queue.filter((message) => message.seq <= throughSeq);
73
- const remaining =
74
- throughSeq === undefined
75
- ? []
76
- : queue.filter((message) => message.seq > throughSeq);
77
-
78
- if (selected.length === 0) {
79
- return { ok: false, reason: "queue_empty" };
80
- }
81
-
82
- const fused = fuseQueuedMessages(selected);
83
-
84
- if (fused === undefined) {
85
- return { ok: false, reason: "queue_empty" };
86
- }
87
-
88
- return {
89
- ok: true,
90
- queue: [fused, ...remaining],
91
- selectedCount: selected.length,
92
- selectedSeqs: selected.map(message => message.seq),
93
- remainingCount: remaining.length,
94
- };
95
- }
96
-
97
- function fuseQueuedMessages(
98
- selected: readonly QueuedKandanMessage[],
99
- ): QueuedKandanMessage | undefined {
100
- const last = selected[selected.length - 1];
101
-
102
- if (last === undefined) {
103
- return undefined;
104
- }
105
-
106
- return {
107
- seq: last.seq,
108
- actorSlug: "kandan",
109
- actorUserId: undefined,
110
- body: selected.map(codexInputForQueuedKandanMessage).join("\n\n---\n\n"),
111
- attachments: selected.flatMap(message => message.attachments),
112
- };
113
- }
package/src/kandanTls.ts DELETED
@@ -1,86 +0,0 @@
1
- /*
2
- - Date: 2026-04-29
3
- Spec: kandan/server_v2/plans/2026-04-29-local-runner-editor-runtime-distribution-note.md
4
- Relationship: Lets local proof and development runners trust a specific
5
- Kandan CA certificate without disabling TLS verification globally.
6
- */
7
- import { existsSync } from "node:fs";
8
-
9
- type BunRuntime = {
10
- readonly file: (path: string) => unknown;
11
- };
12
-
13
- type TlsFetchInit = RequestInit & {
14
- readonly tls?: {
15
- readonly ca: unknown;
16
- };
17
- };
18
-
19
- type TlsWebSocketOptions = {
20
- readonly tls?: {
21
- readonly ca: unknown;
22
- };
23
- };
24
-
25
- export type KandanTlsTrust = {
26
- readonly caFile: string;
27
- readonly ca: unknown;
28
- };
29
-
30
- export function kandanTlsTrustFromEnv(): KandanTlsTrust | undefined {
31
- return kandanTlsTrustFromCaFile(process.env.KANDAN_TLS_CA_FILE);
32
- }
33
-
34
- export function kandanTlsTrustFromCaFile(caFile: string | undefined): KandanTlsTrust | undefined {
35
- if (caFile === undefined || caFile.trim() === "") {
36
- return undefined;
37
- }
38
-
39
- const trimmed = caFile.trim();
40
- if (!existsSync(trimmed)) {
41
- throw new Error(`KANDAN_TLS_CA_FILE does not exist: ${trimmed}`);
42
- }
43
-
44
- const bun = (globalThis as unknown as { readonly Bun?: BunRuntime }).Bun;
45
- if (bun === undefined) {
46
- throw new Error("KANDAN_TLS_CA_FILE requires the Bun runtime");
47
- }
48
-
49
- return {
50
- caFile: trimmed,
51
- ca: bun.file(trimmed),
52
- };
53
- }
54
-
55
- export function trustedFetch(
56
- trust: KandanTlsTrust | undefined,
57
- ): typeof fetch {
58
- if (trust === undefined) {
59
- return fetch;
60
- }
61
-
62
- return ((input: URL | RequestInfo, init?: RequestInit) => {
63
- const request = {
64
- ...(init ?? {}),
65
- tls: {
66
- ca: trust.ca,
67
- },
68
- } satisfies TlsFetchInit;
69
- return fetch(input, request);
70
- }) as typeof fetch;
71
- }
72
-
73
- export function trustedWebSocketFactory(
74
- trust: KandanTlsTrust | undefined,
75
- ): ((url: string) => WebSocket) | undefined {
76
- if (trust === undefined) {
77
- return undefined;
78
- }
79
-
80
- return (url) =>
81
- new WebSocket(url, {
82
- tls: {
83
- ca: trust.ca,
84
- },
85
- } as TlsWebSocketOptions);
86
- }
@@ -1,143 +0,0 @@
1
- /*
2
- - Date: 2026-04-26
3
- Spec: plans/2026-04-26-local-codex-driver-worldclass-spec.md
4
- Relationship: Defines the local machine capability boundary for Kandan-
5
- requested Codex session starts and future preview forwarding. Remote starts
6
- are allowed only inside explicitly configured local cwd roots, and preview
7
- forwarding is advertised only for explicitly configured local ports.
8
- */
9
- import { realpathSync } from "node:fs";
10
- import { homedir } from "node:os";
11
- import { isAbsolute, relative, resolve } from "node:path";
12
-
13
- export type CwdCapabilityDecision =
14
- | {
15
- readonly ok: true;
16
- readonly cwd: string;
17
- readonly matchedRoot: string;
18
- }
19
- | {
20
- readonly ok: false;
21
- readonly reason:
22
- | "missing_cwd"
23
- | "no_allowed_cwd"
24
- | "cwd_not_allowed"
25
- | "cwd_not_found";
26
- };
27
-
28
- export function parseAllowedCwdList(value: string | undefined): string[] {
29
- if (value === undefined) {
30
- return [];
31
- }
32
-
33
- return value
34
- .split(",")
35
- .map((part) => part.trim())
36
- .filter((part) => part !== "");
37
- }
38
-
39
- export function parseAllowedPortList(value: string | undefined): number[] {
40
- if (value === undefined) {
41
- return [];
42
- }
43
-
44
- const ports = new Set<number>();
45
-
46
- for (const part of value.split(",")) {
47
- const trimmed = part.trim();
48
-
49
- if (trimmed === "") {
50
- continue;
51
- }
52
-
53
- const port = Number(trimmed);
54
-
55
- if (!Number.isInteger(port) || port < 1 || port > 65_535) {
56
- throw new Error(`invalid --forward-port: ${trimmed} is not a TCP port`);
57
- }
58
-
59
- ports.add(port);
60
- }
61
-
62
- return [...ports].sort((left, right) => left - right);
63
- }
64
-
65
- export function assertConfiguredAllowedCwds(
66
- paths: readonly string[],
67
- ): string[] {
68
- return paths.map((path) => {
69
- try {
70
- return realpathSync(resolve(expandUserPath(path)));
71
- } catch (_error) {
72
- throw new Error(`invalid --allowed-cwd: ${path} does not exist`);
73
- }
74
- });
75
- }
76
-
77
- export function expandUserPath(pathValue: string): string {
78
- if (pathValue === "~") {
79
- return homedir();
80
- }
81
-
82
- if (pathValue.startsWith("~/")) {
83
- return resolve(homedir(), pathValue.slice(2));
84
- }
85
-
86
- return pathValue;
87
- }
88
-
89
- export function resolveAllowedCwd(
90
- requestedCwd: string | undefined,
91
- allowedRoots: readonly string[],
92
- ): CwdCapabilityDecision {
93
- if (requestedCwd === undefined || requestedCwd.trim() === "") {
94
- return { ok: false, reason: "missing_cwd" };
95
- }
96
-
97
- if (allowedRoots.length === 0) {
98
- return { ok: false, reason: "no_allowed_cwd" };
99
- }
100
-
101
- let cwd: string;
102
-
103
- try {
104
- cwd = realpathSync(resolve(requestedCwd));
105
- } catch (_error) {
106
- return { ok: false, reason: "cwd_not_found" };
107
- }
108
-
109
- const matchedRoot = allowedRoots.find((root) =>
110
- cwdIsInsideNormalizedRoot(cwd, root),
111
- );
112
-
113
- return matchedRoot === undefined
114
- ? { ok: false, reason: "cwd_not_allowed" }
115
- : { ok: true, cwd, matchedRoot };
116
- }
117
-
118
- function cwdIsInsideNormalizedRoot(
119
- cwd: string,
120
- normalizedRoot: string,
121
- ): boolean {
122
- return relativePathIsInsideRoot(relative(normalizedRoot, cwd));
123
- }
124
-
125
- export function relativePathIsInsideRoot(pathRelativeToRoot: string): boolean {
126
- return (
127
- pathRelativeToRoot === "" ||
128
- (!pathLooksAbsolute(pathRelativeToRoot) &&
129
- pathRelativeToRoot !== ".." &&
130
- !pathRelativeToRoot.startsWith("../") &&
131
- !pathRelativeToRoot.startsWith("..\\") &&
132
- !pathRelativeToRoot.includes("/../") &&
133
- !pathRelativeToRoot.includes("\\..\\"))
134
- );
135
- }
136
-
137
- function pathLooksAbsolute(pathValue: string): boolean {
138
- return (
139
- isAbsolute(pathValue) ||
140
- /^[A-Za-z]:[\\/]/.test(pathValue) ||
141
- pathValue.startsWith("\\\\")
142
- );
143
- }
@@ -1,135 +0,0 @@
1
- /*
2
- - Date: 2026-04-26
3
- Spec: plans/2026-04-26-local-codex-driver-worldclass-spec.md
4
- Relationship: Defines the user-visible local Codex message state model and
5
- the Codex notification-to-state mapping used by Kandan to show queued,
6
- processing, approval, failed, ignored, and processed states consistently.
7
-
8
- - Date: 2026-04-26
9
- Spec: plans/2026-04-26-local-runner-port-forward-approval.md
10
- Relationship: Allows local runner approval states to carry explicit
11
- UI choices so descendant port-forward prompts can reuse the existing
12
- Kandan approval bottom bar.
13
- */
14
- import { objectValue, stringValue } from "./json";
15
- import type { JsonObject, JsonRpcRequest } from "./protocol";
16
-
17
- export type LocalCodexProcessingReason =
18
- | "starting turn"
19
- | "streaming response"
20
- | "running terminal command"
21
- | "interrupt requested"
22
- | "awaiting approval";
23
-
24
- export type CodexApprovalMessageState = {
25
- readonly requestId: string;
26
- readonly kind: string;
27
- readonly summary: string;
28
- readonly reason?: string | undefined;
29
- readonly choices?: readonly ApprovalChoice[] | undefined;
30
- readonly allowedActorSlug?: string | undefined;
31
- readonly allowedActorUserId?: number | undefined;
32
- };
33
-
34
- export type ApprovalChoice = {
35
- readonly decision: string;
36
- readonly label: string;
37
- readonly description?: string | undefined;
38
- };
39
-
40
- export type ActiveProcessingState =
41
- | {
42
- readonly seq: number;
43
- readonly reason: Exclude<LocalCodexProcessingReason, "awaiting approval">;
44
- }
45
- | {
46
- readonly seq: number;
47
- readonly reason: "awaiting approval";
48
- readonly approval: CodexApprovalMessageState;
49
- };
50
-
51
- export type LocalCodexMessageState =
52
- | { readonly status: "queued" }
53
- | { readonly status: "processed" }
54
- | {
55
- readonly status: "processing";
56
- readonly reason: LocalCodexProcessingReason;
57
- readonly approval?: CodexApprovalMessageState | undefined;
58
- }
59
- | { readonly status: "ignored"; readonly reason: string }
60
- | { readonly status: "failed"; readonly reason: string };
61
-
62
- export function codexApprovalMessageState(
63
- request: JsonRpcRequest,
64
- ): CodexApprovalMessageState {
65
- const params = objectValue(request.params) ?? {};
66
- const command = stringValue(params.command) ?? stringValue(params.cmd);
67
- const filePath =
68
- stringValue(params.path) ??
69
- stringValue(params.filePath) ??
70
- stringValue(params.file);
71
- const summary =
72
- command ??
73
- filePath ??
74
- stringValue(params.reason) ??
75
- stringValue(params.summary) ??
76
- request.method;
77
-
78
- return {
79
- requestId: String(request.id),
80
- kind: request.method,
81
- summary,
82
- };
83
- }
84
-
85
- export function approvalRequestKey(requestId: string, sourceSeq: number): string {
86
- return `${sourceSeq}:${requestId}`;
87
- }
88
-
89
- export function processingReasonForCodexNotification(
90
- method: string,
91
- params: JsonObject,
92
- ): Exclude<LocalCodexProcessingReason, "awaiting approval"> | undefined {
93
- if (method === "item/agentMessage/delta" || method === "item/reasoning/textDelta") {
94
- return "streaming response";
95
- }
96
-
97
- if (
98
- method.startsWith("item/commandExecution/") ||
99
- method === "command/exec/outputDelta"
100
- ) {
101
- return "running terminal command";
102
- }
103
-
104
- const item = objectValue(params.item) ?? params;
105
- const itemType = stringValue(item.type);
106
-
107
- switch (itemType) {
108
- case "commandExecution":
109
- case "terminalInput":
110
- return "running terminal command";
111
- case "agentMessage":
112
- case "reasoning":
113
- case "fileChange":
114
- case "web_search_call":
115
- case "webSearchCall":
116
- case "webSearch":
117
- return "streaming response";
118
- default:
119
- return undefined;
120
- }
121
- }
122
-
123
- export function processingMessageStateFromActive(
124
- state: ActiveProcessingState,
125
- ): Extract<LocalCodexMessageState, { readonly status: "processing" }> {
126
- switch (state.reason) {
127
- case "awaiting approval":
128
- return { status: "processing", reason: state.reason, approval: state.approval };
129
- case "starting turn":
130
- case "streaming response":
131
- case "running terminal command":
132
- case "interrupt requested":
133
- return { status: "processing", reason: state.reason };
134
- }
135
- }
@@ -1,108 +0,0 @@
1
- /*
2
- - Date: 2026-04-26
3
- Spec: plans/2026-04-26-local-codex-driver-worldclass-spec.md
4
- Relationship: Defines the pure local Codex turn lifecycle helpers used by
5
- the channel-session orchestrator for interruption, active-turn lookup,
6
- completion, failure cleanup, and forwarding eligibility.
7
- */
8
-
9
- export type TurnState =
10
- | { readonly status: "idle" }
11
- | {
12
- readonly status: "starting";
13
- readonly queuedSeq: number;
14
- readonly interruptAfterStart: boolean;
15
- }
16
- | { readonly status: "active"; readonly turnId: string; readonly queuedSeq: number }
17
- | { readonly status: "completing"; readonly turnId: string; readonly queuedSeq: number };
18
-
19
- export function activeTurnId(turn: TurnState): string | undefined {
20
- switch (turn.status) {
21
- case "active":
22
- case "completing":
23
- return turn.turnId;
24
- case "idle":
25
- case "starting":
26
- return undefined;
27
- }
28
- }
29
-
30
- export function activeQueuedSeqForTurn(
31
- turn: TurnState,
32
- turnId: string,
33
- ): number | undefined {
34
- switch (turn.status) {
35
- case "active":
36
- case "completing":
37
- return turn.turnId === turnId ? turn.queuedSeq : undefined;
38
- case "idle":
39
- case "starting":
40
- return undefined;
41
- }
42
- }
43
-
44
- export function interruptibleQueuedSeq(turn: TurnState): number | undefined {
45
- switch (turn.status) {
46
- case "active":
47
- case "starting":
48
- return turn.queuedSeq;
49
- case "idle":
50
- case "completing":
51
- return undefined;
52
- }
53
- }
54
-
55
- export function markInterruptAfterStart(turn: TurnState): TurnState {
56
- switch (turn.status) {
57
- case "starting":
58
- return {
59
- status: "starting",
60
- queuedSeq: turn.queuedSeq,
61
- interruptAfterStart: true,
62
- };
63
- case "idle":
64
- case "active":
65
- case "completing":
66
- return turn;
67
- }
68
- }
69
-
70
- export function turnCanForwardByState(turn: TurnState, turnId: string): boolean {
71
- switch (turn.status) {
72
- case "active":
73
- case "completing":
74
- return turn.turnId === turnId;
75
- case "idle":
76
- case "starting":
77
- return false;
78
- }
79
- }
80
-
81
- export function completingQueuedSeqForTurn(
82
- turn: TurnState,
83
- turnId: string,
84
- ): number | undefined {
85
- switch (turn.status) {
86
- case "active":
87
- return turn.turnId === turnId ? turn.queuedSeq : undefined;
88
- case "idle":
89
- case "starting":
90
- case "completing":
91
- return undefined;
92
- }
93
- }
94
-
95
- export function shouldClearTurnAfterFailure(
96
- turn: TurnState,
97
- turnId: string,
98
- ): boolean {
99
- switch (turn.status) {
100
- case "starting":
101
- return true;
102
- case "active":
103
- case "completing":
104
- return turn.turnId === turnId;
105
- case "idle":
106
- return false;
107
- }
108
- }