@co0ontty/wand 0.2.0

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.
@@ -0,0 +1,23 @@
1
+ import type { ConversationTurn } from "./types.js";
2
+ /**
3
+ * SessionLogger saves raw session content to local files for debugging and analysis.
4
+ *
5
+ * Directory structure: .wand/sessions/{sessionId}/
6
+ * - pty-output.log Raw PTY output (append-only)
7
+ * - stream-events.jsonl NDJSON events from native mode (append-only)
8
+ * - messages.json Final structured messages (overwritten on each update)
9
+ */
10
+ export declare class SessionLogger {
11
+ private readonly baseDir;
12
+ private readonly dirs;
13
+ constructor(configDir: string);
14
+ private ensureDir;
15
+ /** Append raw PTY output chunk */
16
+ appendPtyOutput(sessionId: string, chunk: string): void;
17
+ /** Append a native mode NDJSON event */
18
+ appendStreamEvent(sessionId: string, event: unknown): void;
19
+ /** Save the current structured messages snapshot */
20
+ saveMessages(sessionId: string, messages: ConversationTurn[]): void;
21
+ /** Save session metadata */
22
+ saveMetadata(sessionId: string, meta: Record<string, unknown>): void;
23
+ }
@@ -0,0 +1,78 @@
1
+ import { mkdirSync, appendFileSync, writeFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import process from "node:process";
4
+ /**
5
+ * SessionLogger saves raw session content to local files for debugging and analysis.
6
+ *
7
+ * Directory structure: .wand/sessions/{sessionId}/
8
+ * - pty-output.log Raw PTY output (append-only)
9
+ * - stream-events.jsonl NDJSON events from native mode (append-only)
10
+ * - messages.json Final structured messages (overwritten on each update)
11
+ */
12
+ export class SessionLogger {
13
+ baseDir;
14
+ dirs = new Map();
15
+ constructor(configDir) {
16
+ this.baseDir = path.join(configDir, "sessions");
17
+ try {
18
+ mkdirSync(this.baseDir, { recursive: true });
19
+ }
20
+ catch {
21
+ process.stderr.write(`[wand] Warning: Could not create session log dir: ${this.baseDir}\n`);
22
+ }
23
+ }
24
+ ensureDir(sessionId) {
25
+ let dir = this.dirs.get(sessionId);
26
+ if (dir)
27
+ return dir;
28
+ dir = path.join(this.baseDir, sessionId);
29
+ try {
30
+ mkdirSync(dir, { recursive: true });
31
+ }
32
+ catch {
33
+ // ignore
34
+ }
35
+ this.dirs.set(sessionId, dir);
36
+ return dir;
37
+ }
38
+ /** Append raw PTY output chunk */
39
+ appendPtyOutput(sessionId, chunk) {
40
+ try {
41
+ const dir = this.ensureDir(sessionId);
42
+ appendFileSync(path.join(dir, "pty-output.log"), chunk);
43
+ }
44
+ catch {
45
+ // Non-critical — don't let logging failures affect main flow
46
+ }
47
+ }
48
+ /** Append a native mode NDJSON event */
49
+ appendStreamEvent(sessionId, event) {
50
+ try {
51
+ const dir = this.ensureDir(sessionId);
52
+ appendFileSync(path.join(dir, "stream-events.jsonl"), JSON.stringify(event) + "\n");
53
+ }
54
+ catch {
55
+ // Non-critical
56
+ }
57
+ }
58
+ /** Save the current structured messages snapshot */
59
+ saveMessages(sessionId, messages) {
60
+ try {
61
+ const dir = this.ensureDir(sessionId);
62
+ writeFileSync(path.join(dir, "messages.json"), JSON.stringify(messages, null, 2) + "\n");
63
+ }
64
+ catch {
65
+ // Non-critical
66
+ }
67
+ }
68
+ /** Save session metadata */
69
+ saveMetadata(sessionId, meta) {
70
+ try {
71
+ const dir = this.ensureDir(sessionId);
72
+ writeFileSync(path.join(dir, "metadata.json"), JSON.stringify(meta, null, 2) + "\n");
73
+ }
74
+ catch {
75
+ // Non-critical
76
+ }
77
+ }
78
+ }
@@ -0,0 +1,32 @@
1
+ import { SessionSnapshot } from "./types.js";
2
+ export declare const DEFAULT_DB_FILE = "wand.db";
3
+ export interface PersistedAuthSession {
4
+ token: string;
5
+ expiresAt: number;
6
+ }
7
+ export declare function resolveDatabasePath(configPath: string): string;
8
+ export declare function ensureDatabaseFile(dbPath: string): boolean;
9
+ export declare class WandStorage {
10
+ private readonly db;
11
+ constructor(dbPath: string);
12
+ close(): void;
13
+ /** Get a config value from database */
14
+ getConfigValue(key: string): string | null;
15
+ /** Set a config value in database */
16
+ setConfigValue(key: string, value: string): void;
17
+ /** Delete a config value */
18
+ deleteConfigValue(key: string): void;
19
+ /** Get password from database */
20
+ getPassword(): string | null;
21
+ /** Set password in database */
22
+ setPassword(password: string): void;
23
+ /** Check if password has been set (not default) */
24
+ hasCustomPassword(): boolean;
25
+ saveAuthSession(token: string, expiresAt: number): void;
26
+ getAuthSession(token: string): PersistedAuthSession | null;
27
+ deleteAuthSession(token: string): void;
28
+ deleteExpiredAuthSessions(now: number): void;
29
+ saveSession(snapshot: SessionSnapshot): void;
30
+ loadSessions(): SessionSnapshot[];
31
+ deleteSession(id: string): void;
32
+ }
@@ -0,0 +1,181 @@
1
+ import { existsSync, mkdirSync } from "node:fs";
2
+ import path from "node:path";
3
+ import { DatabaseSync } from "node:sqlite";
4
+ export const DEFAULT_DB_FILE = "wand.db";
5
+ export function resolveDatabasePath(configPath) {
6
+ return path.resolve(path.dirname(configPath), DEFAULT_DB_FILE);
7
+ }
8
+ const INIT_SQL = `
9
+ CREATE TABLE IF NOT EXISTS auth_sessions (
10
+ token TEXT PRIMARY KEY,
11
+ expires_at INTEGER NOT NULL
12
+ );
13
+
14
+ CREATE TABLE IF NOT EXISTS command_sessions (
15
+ id TEXT PRIMARY KEY,
16
+ command TEXT NOT NULL,
17
+ cwd TEXT NOT NULL,
18
+ mode TEXT NOT NULL,
19
+ status TEXT NOT NULL,
20
+ exit_code INTEGER,
21
+ started_at TEXT NOT NULL,
22
+ ended_at TEXT,
23
+ output TEXT NOT NULL,
24
+ archived INTEGER NOT NULL DEFAULT 0,
25
+ archived_at TEXT,
26
+ claude_session_id TEXT
27
+ );
28
+
29
+ CREATE TABLE IF NOT EXISTS app_config (
30
+ key TEXT PRIMARY KEY,
31
+ value TEXT NOT NULL
32
+ );
33
+ `;
34
+ export function ensureDatabaseFile(dbPath) {
35
+ mkdirSync(path.dirname(dbPath), { recursive: true });
36
+ const created = !existsSync(dbPath);
37
+ const db = new DatabaseSync(dbPath);
38
+ db.exec(INIT_SQL);
39
+ ensureCommandSessionSchema(db);
40
+ db.close();
41
+ return created;
42
+ }
43
+ export class WandStorage {
44
+ db;
45
+ constructor(dbPath) {
46
+ mkdirSync(path.dirname(dbPath), { recursive: true });
47
+ this.db = new DatabaseSync(dbPath);
48
+ this.db.exec(INIT_SQL);
49
+ ensureCommandSessionSchema(this.db);
50
+ }
51
+ close() {
52
+ this.db.close();
53
+ }
54
+ // ============ Config Methods ============
55
+ /** Get a config value from database */
56
+ getConfigValue(key) {
57
+ const row = this.db
58
+ .prepare("SELECT value FROM app_config WHERE key = ?")
59
+ .get(key);
60
+ return row?.value ?? null;
61
+ }
62
+ /** Set a config value in database */
63
+ setConfigValue(key, value) {
64
+ this.db
65
+ .prepare(`INSERT INTO app_config (key, value) VALUES (?, ?)
66
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`)
67
+ .run(key, value);
68
+ }
69
+ /** Delete a config value */
70
+ deleteConfigValue(key) {
71
+ this.db.prepare("DELETE FROM app_config WHERE key = ?").run(key);
72
+ }
73
+ /** Get password from database */
74
+ getPassword() {
75
+ return this.getConfigValue("password");
76
+ }
77
+ /** Set password in database */
78
+ setPassword(password) {
79
+ this.setConfigValue("password", password);
80
+ }
81
+ /** Check if password has been set (not default) */
82
+ hasCustomPassword() {
83
+ return this.getPassword() !== null;
84
+ }
85
+ // ============ Auth Session Methods ============
86
+ saveAuthSession(token, expiresAt) {
87
+ this.db
88
+ .prepare(`INSERT INTO auth_sessions (token, expires_at)
89
+ VALUES (?, ?)
90
+ ON CONFLICT(token) DO UPDATE SET expires_at = excluded.expires_at`)
91
+ .run(token, expiresAt);
92
+ }
93
+ getAuthSession(token) {
94
+ const row = this.db
95
+ .prepare("SELECT token, expires_at FROM auth_sessions WHERE token = ?")
96
+ .get(token);
97
+ if (!row) {
98
+ return null;
99
+ }
100
+ return {
101
+ token: row.token,
102
+ expiresAt: row.expires_at
103
+ };
104
+ }
105
+ deleteAuthSession(token) {
106
+ this.db.prepare("DELETE FROM auth_sessions WHERE token = ?").run(token);
107
+ }
108
+ deleteExpiredAuthSessions(now) {
109
+ this.db.prepare("DELETE FROM auth_sessions WHERE expires_at < ?").run(now);
110
+ }
111
+ saveSession(snapshot) {
112
+ this.db.exec("BEGIN IMMEDIATE");
113
+ try {
114
+ this.db
115
+ .prepare(`INSERT INTO command_sessions (
116
+ id, command, cwd, mode, status, exit_code, started_at, ended_at, output
117
+ , archived, archived_at, claude_session_id, messages
118
+ ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
119
+ ON CONFLICT(id) DO UPDATE SET
120
+ command = excluded.command,
121
+ cwd = excluded.cwd,
122
+ mode = excluded.mode,
123
+ status = excluded.status,
124
+ exit_code = excluded.exit_code,
125
+ started_at = excluded.started_at,
126
+ ended_at = excluded.ended_at,
127
+ output = excluded.output,
128
+ archived = excluded.archived,
129
+ archived_at = excluded.archived_at,
130
+ claude_session_id = excluded.claude_session_id,
131
+ messages = excluded.messages`)
132
+ .run(snapshot.id, snapshot.command, snapshot.cwd, snapshot.mode, snapshot.status, snapshot.exitCode, snapshot.startedAt, snapshot.endedAt, snapshot.output, snapshot.archived ? 1 : 0, snapshot.archivedAt, snapshot.claudeSessionId, snapshot.messages ? JSON.stringify(snapshot.messages) : null);
133
+ this.db.exec("COMMIT");
134
+ }
135
+ catch (error) {
136
+ this.db.exec("ROLLBACK");
137
+ throw error;
138
+ }
139
+ }
140
+ loadSessions() {
141
+ const rows = this.db
142
+ .prepare(`SELECT id, command, cwd, mode, status, exit_code, started_at, ended_at, output, archived, archived_at, claude_session_id, messages
143
+ FROM command_sessions
144
+ ORDER BY started_at DESC`)
145
+ .all();
146
+ return rows.map((row) => ({
147
+ id: row.id,
148
+ command: row.command,
149
+ cwd: row.cwd,
150
+ mode: row.mode,
151
+ status: row.status,
152
+ exitCode: row.exit_code,
153
+ startedAt: row.started_at,
154
+ endedAt: row.ended_at,
155
+ output: row.output,
156
+ archived: Boolean(row.archived),
157
+ archivedAt: row.archived_at,
158
+ claudeSessionId: row.claude_session_id,
159
+ messages: row.messages ? JSON.parse(row.messages) : undefined
160
+ }));
161
+ }
162
+ deleteSession(id) {
163
+ this.db.prepare("DELETE FROM command_sessions WHERE id = ?").run(id);
164
+ }
165
+ }
166
+ function ensureCommandSessionSchema(db) {
167
+ const columns = db.prepare("PRAGMA table_info(command_sessions)").all();
168
+ const names = new Set(columns.map((column) => column.name));
169
+ if (!names.has("archived")) {
170
+ db.exec("ALTER TABLE command_sessions ADD COLUMN archived INTEGER NOT NULL DEFAULT 0");
171
+ }
172
+ if (!names.has("archived_at")) {
173
+ db.exec("ALTER TABLE command_sessions ADD COLUMN archived_at TEXT");
174
+ }
175
+ if (!names.has("claude_session_id")) {
176
+ db.exec("ALTER TABLE command_sessions ADD COLUMN claude_session_id TEXT");
177
+ }
178
+ if (!names.has("messages")) {
179
+ db.exec("ALTER TABLE command_sessions ADD COLUMN messages TEXT");
180
+ }
181
+ }
@@ -0,0 +1,105 @@
1
+ export type ExecutionMode = "auto-edit" | "default" | "full-access" | "native" | "managed";
2
+ export interface CommandPreset {
3
+ label: string;
4
+ command: string;
5
+ mode?: ExecutionMode;
6
+ }
7
+ export interface WandConfig {
8
+ host: string;
9
+ port: number;
10
+ /** Enable HTTPS with self-signed certificate (default: true) */
11
+ https?: boolean;
12
+ password: string;
13
+ defaultMode: ExecutionMode;
14
+ shell: string;
15
+ defaultCwd: string;
16
+ startupCommands: string[];
17
+ allowedCommandPrefixes: string[];
18
+ commandPresets: CommandPreset[];
19
+ }
20
+ export interface CommandRequest {
21
+ command: string;
22
+ cwd?: string;
23
+ mode?: ExecutionMode;
24
+ initialInput?: string;
25
+ }
26
+ export interface InputRequest {
27
+ input?: string;
28
+ /** Current UI view: "chat" or "terminal". Used to route via native pipeline in chat mode. */
29
+ view?: "chat" | "terminal";
30
+ }
31
+ export interface ResizeRequest {
32
+ cols?: number;
33
+ rows?: number;
34
+ }
35
+ export interface PathSuggestion {
36
+ path: string;
37
+ name: string;
38
+ isDirectory: boolean;
39
+ }
40
+ export interface GitFileStatus {
41
+ staged?: 'modified' | 'added' | 'deleted' | 'renamed';
42
+ unstaged?: 'modified' | 'deleted';
43
+ untracked?: boolean;
44
+ }
45
+ export interface FileEntry {
46
+ path: string;
47
+ name: string;
48
+ type: 'dir' | 'file';
49
+ gitStatus?: GitFileStatus;
50
+ }
51
+ export interface ChatMessage {
52
+ role: "user" | "assistant";
53
+ content: string;
54
+ }
55
+ export interface TextBlock {
56
+ type: "text";
57
+ text: string;
58
+ }
59
+ export interface ThinkingBlock {
60
+ type: "thinking";
61
+ thinking: string;
62
+ }
63
+ export interface ToolUseBlock {
64
+ type: "tool_use";
65
+ id: string;
66
+ name: string;
67
+ description?: string;
68
+ input: Record<string, unknown>;
69
+ }
70
+ export interface ToolResultBlock {
71
+ type: "tool_result";
72
+ tool_use_id: string;
73
+ content: string;
74
+ is_error?: boolean;
75
+ }
76
+ export type ContentBlock = TextBlock | ThinkingBlock | ToolUseBlock | ToolResultBlock;
77
+ export interface ConversationTurn {
78
+ role: "user" | "assistant";
79
+ content: ContentBlock[];
80
+ /** Token usage for this turn (native mode only) */
81
+ usage?: {
82
+ inputTokens?: number;
83
+ outputTokens?: number;
84
+ cacheReadInputTokens?: number;
85
+ cacheCreationInputTokens?: number;
86
+ totalCostUsd?: number;
87
+ };
88
+ }
89
+ export interface SessionSnapshot {
90
+ id: string;
91
+ command: string;
92
+ cwd: string;
93
+ mode: ExecutionMode;
94
+ status: "running" | "exited" | "failed" | "stopped";
95
+ exitCode: number | null;
96
+ startedAt: string;
97
+ endedAt: string | null;
98
+ output: string;
99
+ archived: boolean;
100
+ archivedAt: string | null;
101
+ /** Claude Code 会话 ID,用于 --resume 恢复会话 */
102
+ claudeSessionId: string | null;
103
+ /** Structured conversation messages (from JSON chat mode) */
104
+ messages?: ConversationTurn[];
105
+ }
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};