@sna-sdk/core 0.0.4 → 0.0.9
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/dist/core/providers/claude-code.js +9 -7
- package/dist/db/schema.d.ts +17 -1
- package/dist/db/schema.js +24 -0
- package/dist/index.d.ts +1 -1
- package/dist/lib/skill-parser.d.ts +23 -0
- package/dist/lib/skill-parser.js +54 -0
- package/dist/scripts/emit.js +9 -6
- package/dist/scripts/gen-client.d.ts +2 -0
- package/dist/scripts/gen-client.js +114 -0
- package/dist/scripts/sna.js +16 -6
- package/dist/server/index.d.ts +1 -0
- package/dist/server/index.js +4 -0
- package/dist/server/routes/agent.js +13 -1
- package/dist/server/routes/chat.d.ts +6 -0
- package/dist/server/routes/chat.js +67 -0
- package/dist/server/routes/events.js +1 -1
- package/dist/server/standalone.js +116 -13
- package/package.json +7 -1
|
@@ -174,21 +174,23 @@ class ClaudeCodeProcess {
|
|
|
174
174
|
}
|
|
175
175
|
case "result": {
|
|
176
176
|
if (msg.subtype === "success") {
|
|
177
|
+
const u = msg.usage ?? {};
|
|
177
178
|
const mu = msg.modelUsage ?? {};
|
|
178
179
|
const modelKey = Object.keys(mu)[0] ?? "";
|
|
179
|
-
const
|
|
180
|
+
const modelInfo = mu[modelKey] ?? {};
|
|
180
181
|
return {
|
|
181
182
|
type: "complete",
|
|
182
183
|
message: msg.result ?? "Done",
|
|
183
184
|
data: {
|
|
184
185
|
durationMs: msg.duration_ms,
|
|
185
186
|
costUsd: msg.total_cost_usd,
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
187
|
+
// Per-turn: actual context window usage this turn
|
|
188
|
+
inputTokens: u.input_tokens ?? 0,
|
|
189
|
+
outputTokens: u.output_tokens ?? 0,
|
|
190
|
+
cacheReadTokens: u.cache_read_input_tokens ?? 0,
|
|
191
|
+
cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
|
|
192
|
+
// Static model info
|
|
193
|
+
contextWindow: modelInfo.contextWindow ?? 0,
|
|
192
194
|
model: modelKey
|
|
193
195
|
},
|
|
194
196
|
timestamp: Date.now()
|
package/dist/db/schema.d.ts
CHANGED
|
@@ -1,8 +1,24 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
2
|
|
|
3
3
|
declare function getDb(): Database.Database;
|
|
4
|
+
interface ChatSession {
|
|
5
|
+
id: string;
|
|
6
|
+
label: string;
|
|
7
|
+
type: "main" | "background";
|
|
8
|
+
created_at: string;
|
|
9
|
+
}
|
|
10
|
+
interface ChatMessage {
|
|
11
|
+
id: number;
|
|
12
|
+
session_id: string;
|
|
13
|
+
role: string;
|
|
14
|
+
content: string;
|
|
15
|
+
skill_name: string | null;
|
|
16
|
+
meta: string | null;
|
|
17
|
+
created_at: string;
|
|
18
|
+
}
|
|
4
19
|
interface SkillEvent {
|
|
5
20
|
id: number;
|
|
21
|
+
session_id: string | null;
|
|
6
22
|
skill: string;
|
|
7
23
|
type: "invoked" | "called" | "success" | "failed" | "permission_needed" | "start" | "progress" | "milestone" | "complete" | "error";
|
|
8
24
|
message: string;
|
|
@@ -10,4 +26,4 @@ interface SkillEvent {
|
|
|
10
26
|
created_at: string;
|
|
11
27
|
}
|
|
12
28
|
|
|
13
|
-
export { type SkillEvent, getDb };
|
|
29
|
+
export { type ChatMessage, type ChatSession, type SkillEvent, getDb };
|
package/dist/db/schema.js
CHANGED
|
@@ -23,8 +23,31 @@ function migrateSkillEvents(db) {
|
|
|
23
23
|
function initSchema(db) {
|
|
24
24
|
migrateSkillEvents(db);
|
|
25
25
|
db.exec(`
|
|
26
|
+
CREATE TABLE IF NOT EXISTS chat_sessions (
|
|
27
|
+
id TEXT PRIMARY KEY,
|
|
28
|
+
label TEXT NOT NULL DEFAULT '',
|
|
29
|
+
type TEXT NOT NULL DEFAULT 'main',
|
|
30
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
31
|
+
);
|
|
32
|
+
|
|
33
|
+
-- Ensure default session always exists
|
|
34
|
+
INSERT OR IGNORE INTO chat_sessions (id, label, type) VALUES ('default', 'Chat', 'main');
|
|
35
|
+
|
|
36
|
+
CREATE TABLE IF NOT EXISTS chat_messages (
|
|
37
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
38
|
+
session_id TEXT NOT NULL REFERENCES chat_sessions(id) ON DELETE CASCADE,
|
|
39
|
+
role TEXT NOT NULL,
|
|
40
|
+
content TEXT NOT NULL DEFAULT '',
|
|
41
|
+
skill_name TEXT,
|
|
42
|
+
meta TEXT,
|
|
43
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
CREATE INDEX IF NOT EXISTS idx_chat_messages_session ON chat_messages(session_id);
|
|
47
|
+
|
|
26
48
|
CREATE TABLE IF NOT EXISTS skill_events (
|
|
27
49
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
50
|
+
session_id TEXT REFERENCES chat_sessions(id) ON DELETE SET NULL,
|
|
28
51
|
skill TEXT NOT NULL,
|
|
29
52
|
type TEXT NOT NULL,
|
|
30
53
|
message TEXT NOT NULL,
|
|
@@ -34,6 +57,7 @@ function initSchema(db) {
|
|
|
34
57
|
|
|
35
58
|
CREATE INDEX IF NOT EXISTS idx_skill_events_skill ON skill_events(skill);
|
|
36
59
|
CREATE INDEX IF NOT EXISTS idx_skill_events_created ON skill_events(created_at);
|
|
60
|
+
CREATE INDEX IF NOT EXISTS idx_skill_events_session ON skill_events(session_id);
|
|
37
61
|
`);
|
|
38
62
|
}
|
|
39
63
|
export {
|
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { SkillEvent } from './db/schema.js';
|
|
1
|
+
export { ChatMessage, ChatSession, SkillEvent } from './db/schema.js';
|
|
2
2
|
export { AgentEvent, AgentProcess, AgentProvider, SpawnOptions } from './core/providers/types.js';
|
|
3
3
|
export { Session, SessionInfo, SessionManagerOptions } from './server/session-manager.js';
|
|
4
4
|
import 'better-sqlite3';
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* skill-parser.ts — Parse SKILL.md frontmatter for sna schema definitions.
|
|
3
|
+
*
|
|
4
|
+
* Reads .claude/skills/<name>/SKILL.md files and extracts the `sna` field
|
|
5
|
+
* from YAML frontmatter.
|
|
6
|
+
*/
|
|
7
|
+
interface SkillArgDef {
|
|
8
|
+
type: "string" | "number" | "boolean" | "string[]" | "number[]";
|
|
9
|
+
required?: boolean;
|
|
10
|
+
description?: string;
|
|
11
|
+
}
|
|
12
|
+
interface SkillSchema {
|
|
13
|
+
name: string;
|
|
14
|
+
camelName: string;
|
|
15
|
+
description: string;
|
|
16
|
+
args: Record<string, SkillArgDef>;
|
|
17
|
+
}
|
|
18
|
+
/** Parse a single SKILL.md file and extract sna schema. */
|
|
19
|
+
declare function parseSkillFile(filePath: string): SkillSchema | null;
|
|
20
|
+
/** Scan a directory for SKILL.md files and parse all sna schemas. */
|
|
21
|
+
declare function scanSkills(skillsDir: string): SkillSchema[];
|
|
22
|
+
|
|
23
|
+
export { type SkillArgDef, type SkillSchema, parseSkillFile, scanSkills };
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import yaml from "js-yaml";
|
|
4
|
+
function toCamelCase(kebab) {
|
|
5
|
+
return kebab.replace(/-([a-z])/g, (_, c) => c.toUpperCase());
|
|
6
|
+
}
|
|
7
|
+
function parseFrontmatter(content) {
|
|
8
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
9
|
+
if (!match) return null;
|
|
10
|
+
try {
|
|
11
|
+
return yaml.load(match[1]);
|
|
12
|
+
} catch {
|
|
13
|
+
return null;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
function parseSkillFile(filePath) {
|
|
17
|
+
const content = fs.readFileSync(filePath, "utf-8");
|
|
18
|
+
const fm = parseFrontmatter(content);
|
|
19
|
+
if (!fm) return null;
|
|
20
|
+
const name = path.basename(path.dirname(filePath));
|
|
21
|
+
const description = fm.description ?? "";
|
|
22
|
+
const sna = fm.sna;
|
|
23
|
+
const rawArgs = sna?.args ?? {};
|
|
24
|
+
const args = {};
|
|
25
|
+
for (const [key, def] of Object.entries(rawArgs)) {
|
|
26
|
+
args[key] = {
|
|
27
|
+
type: def.type ?? "string",
|
|
28
|
+
required: def.required === true,
|
|
29
|
+
description: def.description ?? void 0
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
name,
|
|
34
|
+
camelName: toCamelCase(name),
|
|
35
|
+
description,
|
|
36
|
+
args
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
function scanSkills(skillsDir) {
|
|
40
|
+
if (!fs.existsSync(skillsDir)) return [];
|
|
41
|
+
const schemas = [];
|
|
42
|
+
const entries = fs.readdirSync(skillsDir);
|
|
43
|
+
for (const entry of entries) {
|
|
44
|
+
const skillMd = path.join(skillsDir, entry, "SKILL.md");
|
|
45
|
+
if (!fs.existsSync(skillMd)) continue;
|
|
46
|
+
const schema = parseSkillFile(skillMd);
|
|
47
|
+
if (schema) schemas.push(schema);
|
|
48
|
+
}
|
|
49
|
+
return schemas;
|
|
50
|
+
}
|
|
51
|
+
export {
|
|
52
|
+
parseSkillFile,
|
|
53
|
+
scanSkills
|
|
54
|
+
};
|
package/dist/scripts/emit.js
CHANGED
|
@@ -21,18 +21,21 @@ const VALID_TYPES = [
|
|
|
21
21
|
"error"
|
|
22
22
|
];
|
|
23
23
|
if (!flags.skill || !flags.type || !flags.message) {
|
|
24
|
-
console.error("Usage: tsx node_modules
|
|
24
|
+
console.error("Usage: tsx node_modules/@sna-sdk/core/src/scripts/emit.ts --skill <name> --type <type> --message <text> [--data <json>]");
|
|
25
25
|
process.exit(1);
|
|
26
26
|
}
|
|
27
27
|
if (!VALID_TYPES.includes(flags.type)) {
|
|
28
28
|
console.error(`Invalid type: ${flags.type}. Must be one of: ${VALID_TYPES.join(", ")}`);
|
|
29
29
|
process.exit(1);
|
|
30
30
|
}
|
|
31
|
-
const
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
31
|
+
const sessionId = process.env.SNA_SESSION_ID;
|
|
32
|
+
if (sessionId) {
|
|
33
|
+
const db = getDb();
|
|
34
|
+
db.prepare(`
|
|
35
|
+
INSERT INTO skill_events (session_id, skill, type, message, data)
|
|
36
|
+
VALUES (?, ?, ?, ?, ?)
|
|
37
|
+
`).run(sessionId, flags.skill, flags.type, flags.message, flags.data ?? null);
|
|
38
|
+
}
|
|
36
39
|
const prefix = {
|
|
37
40
|
called: "\u2192",
|
|
38
41
|
success: "\u2713",
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import fs from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import { scanSkills } from "../lib/skill-parser.js";
|
|
4
|
+
const ROOT = process.cwd();
|
|
5
|
+
function parseFlags(args) {
|
|
6
|
+
const flags2 = {};
|
|
7
|
+
for (let i = 0; i < args.length; i += 2) {
|
|
8
|
+
const key = args[i]?.replace(/^--/, "");
|
|
9
|
+
if (key) flags2[key] = args[i + 1] ?? "";
|
|
10
|
+
}
|
|
11
|
+
return flags2;
|
|
12
|
+
}
|
|
13
|
+
function tsType(argDef) {
|
|
14
|
+
switch (argDef.type) {
|
|
15
|
+
case "number":
|
|
16
|
+
return "number";
|
|
17
|
+
case "boolean":
|
|
18
|
+
return "boolean";
|
|
19
|
+
case "string[]":
|
|
20
|
+
return "string[]";
|
|
21
|
+
case "number[]":
|
|
22
|
+
return "number[]";
|
|
23
|
+
default:
|
|
24
|
+
return "string";
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
function generateClient(schemas2) {
|
|
28
|
+
const lines = [];
|
|
29
|
+
lines.push(`/**`);
|
|
30
|
+
lines.push(` * SNA Client \u2014 Auto-generated from SKILL.md frontmatter.`);
|
|
31
|
+
lines.push(` * DO NOT EDIT. Re-generate with: sna gen client`);
|
|
32
|
+
lines.push(` */`);
|
|
33
|
+
lines.push(``);
|
|
34
|
+
lines.push(`import type { SkillResult } from "@sna-sdk/react/hooks";`);
|
|
35
|
+
lines.push(``);
|
|
36
|
+
for (const s of schemas2) {
|
|
37
|
+
const argEntries = Object.entries(s.args);
|
|
38
|
+
if (argEntries.length > 0) {
|
|
39
|
+
lines.push(`export interface ${capitalize(s.camelName)}Args {`);
|
|
40
|
+
for (const [key, def] of argEntries) {
|
|
41
|
+
if (def.description) lines.push(` /** ${def.description} */`);
|
|
42
|
+
lines.push(` ${key}${def.required ? "" : "?"}: ${tsType(def)};`);
|
|
43
|
+
}
|
|
44
|
+
lines.push(`}`);
|
|
45
|
+
lines.push(``);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
lines.push(`export interface SnaSkills {`);
|
|
49
|
+
for (const s of schemas2) {
|
|
50
|
+
const argEntries = Object.entries(s.args);
|
|
51
|
+
const argsType = argEntries.length > 0 ? `${capitalize(s.camelName)}Args` : "void";
|
|
52
|
+
lines.push(` /** ${s.description} */`);
|
|
53
|
+
lines.push(` ${s.camelName}: (${argsType === "void" ? "" : `args: ${argsType}`}) => Promise<SkillResult>;`);
|
|
54
|
+
}
|
|
55
|
+
lines.push(`}`);
|
|
56
|
+
lines.push(``);
|
|
57
|
+
lines.push(`export const skillDefinitions = {`);
|
|
58
|
+
for (const s of schemas2) {
|
|
59
|
+
const argKeys = Object.keys(s.args);
|
|
60
|
+
lines.push(` ${s.camelName}: { name: "${s.name}", argKeys: [${argKeys.map((k) => `"${k}"`).join(", ")}] },`);
|
|
61
|
+
}
|
|
62
|
+
lines.push(`} as const;`);
|
|
63
|
+
lines.push(``);
|
|
64
|
+
lines.push(`/**`);
|
|
65
|
+
lines.push(` * Create a typed SNA client.`);
|
|
66
|
+
lines.push(` *`);
|
|
67
|
+
lines.push(` * @example`);
|
|
68
|
+
lines.push(` * const { skills } = useSnaClient();`);
|
|
69
|
+
lines.push(` * await skills.formFill({ sessionId: 123 });`);
|
|
70
|
+
lines.push(` */`);
|
|
71
|
+
lines.push(`export function bindSkills(`);
|
|
72
|
+
lines.push(` runner: (command: string) => Promise<SkillResult>,`);
|
|
73
|
+
lines.push(`): SnaSkills {`);
|
|
74
|
+
lines.push(` const skills = {} as SnaSkills;`);
|
|
75
|
+
lines.push(` for (const [method, def] of Object.entries(skillDefinitions)) {`);
|
|
76
|
+
lines.push(` (skills as any)[method] = (args?: Record<string, unknown>) => {`);
|
|
77
|
+
lines.push(` const parts = [def.name];`);
|
|
78
|
+
lines.push(` if (args) {`);
|
|
79
|
+
lines.push(` for (const key of def.argKeys) {`);
|
|
80
|
+
lines.push(` if (args[key] !== undefined) {`);
|
|
81
|
+
lines.push(` const val = args[key];`);
|
|
82
|
+
lines.push(` parts.push(Array.isArray(val) ? val.join(" ") : String(val));`);
|
|
83
|
+
lines.push(` }`);
|
|
84
|
+
lines.push(` }`);
|
|
85
|
+
lines.push(` }`);
|
|
86
|
+
lines.push(` return runner(parts.join(" "));`);
|
|
87
|
+
lines.push(` };`);
|
|
88
|
+
lines.push(` }`);
|
|
89
|
+
lines.push(` return skills;`);
|
|
90
|
+
lines.push(`}`);
|
|
91
|
+
return lines.join("\n") + "\n";
|
|
92
|
+
}
|
|
93
|
+
function capitalize(s) {
|
|
94
|
+
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
95
|
+
}
|
|
96
|
+
const [, , ...rawArgs] = process.argv;
|
|
97
|
+
const flags = parseFlags(rawArgs);
|
|
98
|
+
const skillsDir = flags["skills-dir"] ?? path.join(ROOT, ".claude/skills");
|
|
99
|
+
const outPath = flags.out ?? path.join(ROOT, "src/sna-client.ts");
|
|
100
|
+
const schemas = scanSkills(skillsDir);
|
|
101
|
+
if (schemas.length === 0) {
|
|
102
|
+
console.log("No skills with sna.args found in", skillsDir);
|
|
103
|
+
process.exit(0);
|
|
104
|
+
}
|
|
105
|
+
const code = generateClient(schemas);
|
|
106
|
+
const outDir = path.dirname(outPath);
|
|
107
|
+
if (!fs.existsSync(outDir)) fs.mkdirSync(outDir, { recursive: true });
|
|
108
|
+
fs.writeFileSync(outPath, code);
|
|
109
|
+
console.log(`\u2713 Generated ${outPath}`);
|
|
110
|
+
console.log(` ${schemas.length} skills:`);
|
|
111
|
+
for (const s of schemas) {
|
|
112
|
+
const argCount = Object.keys(s.args).length;
|
|
113
|
+
console.log(` ${s.camelName}(${argCount > 0 ? `{${Object.keys(s.args).join(", ")}}` : ""}) \u2192 ${s.name}`);
|
|
114
|
+
}
|
package/dist/scripts/sna.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { execSync, spawn } from "child_process";
|
|
2
2
|
import fs from "fs";
|
|
3
|
+
import net from "net";
|
|
3
4
|
import path from "path";
|
|
4
5
|
import { cmdNew, cmdWorkflow, cmdCancel, cmdTasks } from "./workflow.js";
|
|
5
6
|
const ROOT = process.cwd();
|
|
@@ -13,7 +14,7 @@ const SNA_API_LOG_FILE = path.join(STATE_DIR, "sna-api.log");
|
|
|
13
14
|
const PORT = process.env.PORT ?? "3000";
|
|
14
15
|
const DB_PATH = path.join(ROOT, "data/app.db");
|
|
15
16
|
const CLAUDE_PATH_FILE = path.join(STATE_DIR, "claude-path");
|
|
16
|
-
const SNA_CORE_DIR = path.join(ROOT, "node_modules/
|
|
17
|
+
const SNA_CORE_DIR = path.join(ROOT, "node_modules/@sna-sdk/core");
|
|
17
18
|
function ensureStateDir() {
|
|
18
19
|
if (!fs.existsSync(STATE_DIR)) fs.mkdirSync(STATE_DIR, { recursive: true });
|
|
19
20
|
}
|
|
@@ -56,10 +57,10 @@ function clearSnaApiState() {
|
|
|
56
57
|
}
|
|
57
58
|
}
|
|
58
59
|
function findFreePort() {
|
|
59
|
-
const net = require("net");
|
|
60
60
|
const srv = net.createServer();
|
|
61
61
|
srv.listen(0);
|
|
62
|
-
const
|
|
62
|
+
const addr = srv.address();
|
|
63
|
+
const port = String(addr.port);
|
|
63
64
|
srv.close();
|
|
64
65
|
return port;
|
|
65
66
|
}
|
|
@@ -373,7 +374,7 @@ function cmdInit(force2 = false) {
|
|
|
373
374
|
if (!fs.existsSync(claudeDir)) {
|
|
374
375
|
fs.mkdirSync(claudeDir, { recursive: true });
|
|
375
376
|
}
|
|
376
|
-
const hookCommand = `node "$CLAUDE_PROJECT_DIR"/node_modules
|
|
377
|
+
const hookCommand = `node "$CLAUDE_PROJECT_DIR"/node_modules/@sna-sdk/core/dist/scripts/hook.js`;
|
|
377
378
|
const permissionHook = {
|
|
378
379
|
matcher: ".*",
|
|
379
380
|
hooks: [{ type: "command", async: true, command: hookCommand }]
|
|
@@ -398,7 +399,7 @@ function cmdInit(force2 = false) {
|
|
|
398
399
|
} else {
|
|
399
400
|
step(".claude/settings.json \u2014 hook already set, skipped");
|
|
400
401
|
}
|
|
401
|
-
const claudeMdTemplate = path.join(ROOT, "node_modules
|
|
402
|
+
const claudeMdTemplate = path.join(ROOT, "node_modules/@sna-sdk/core/CLAUDE.md.template");
|
|
402
403
|
const claudeMdDest = path.join(claudeDir, "CLAUDE.md");
|
|
403
404
|
if (fs.existsSync(claudeMdTemplate)) {
|
|
404
405
|
if (force2 || !fs.existsSync(claudeMdDest)) {
|
|
@@ -408,7 +409,7 @@ function cmdInit(force2 = false) {
|
|
|
408
409
|
step(".claude/CLAUDE.md \u2014 already exists, skipped");
|
|
409
410
|
}
|
|
410
411
|
}
|
|
411
|
-
const snaCoreSkillsDir = path.join(ROOT, "node_modules
|
|
412
|
+
const snaCoreSkillsDir = path.join(ROOT, "node_modules/@sna-sdk/core/skills");
|
|
412
413
|
const destSkillsDir = path.join(claudeDir, "skills");
|
|
413
414
|
if (fs.existsSync(snaCoreSkillsDir)) {
|
|
414
415
|
const skillNames = fs.readdirSync(snaCoreSkillsDir);
|
|
@@ -644,6 +645,15 @@ Run "sna help submit" for data submission patterns.`);
|
|
|
644
645
|
console.log();
|
|
645
646
|
cmdUp();
|
|
646
647
|
break;
|
|
648
|
+
case "gen":
|
|
649
|
+
if (args[0] === "client") {
|
|
650
|
+
const { execSync: exec } = require("child_process");
|
|
651
|
+
const genScript = path.join(__dirname, "gen-client.js");
|
|
652
|
+
exec(`node "${genScript}" ${args.slice(1).join(" ")}`, { stdio: "inherit", cwd: ROOT });
|
|
653
|
+
} else {
|
|
654
|
+
console.log("Usage: sna gen client [--out <path>] [--skills-dir <path>]");
|
|
655
|
+
}
|
|
656
|
+
break;
|
|
647
657
|
default:
|
|
648
658
|
printHelp();
|
|
649
659
|
}
|
package/dist/server/index.d.ts
CHANGED
|
@@ -6,6 +6,7 @@ export { eventsRoute } from './routes/events.js';
|
|
|
6
6
|
export { emitRoute } from './routes/emit.js';
|
|
7
7
|
export { createRunRoute } from './routes/run.js';
|
|
8
8
|
export { createAgentRoutes } from './routes/agent.js';
|
|
9
|
+
export { createChatRoutes } from './routes/chat.js';
|
|
9
10
|
import '../core/providers/types.js';
|
|
10
11
|
import 'hono/utils/http-status';
|
|
11
12
|
|
package/dist/server/index.js
CHANGED
|
@@ -5,6 +5,7 @@ import { eventsRoute } from "./routes/events.js";
|
|
|
5
5
|
import { emitRoute } from "./routes/emit.js";
|
|
6
6
|
import { createRunRoute } from "./routes/run.js";
|
|
7
7
|
import { createAgentRoutes } from "./routes/agent.js";
|
|
8
|
+
import { createChatRoutes } from "./routes/chat.js";
|
|
8
9
|
import { SessionManager } from "./session-manager.js";
|
|
9
10
|
function createSnaApp(options = {}) {
|
|
10
11
|
const sessionManager = options.sessionManager ?? new SessionManager();
|
|
@@ -13,6 +14,7 @@ function createSnaApp(options = {}) {
|
|
|
13
14
|
app.get("/events", eventsRoute);
|
|
14
15
|
app.post("/emit", emitRoute);
|
|
15
16
|
app.route("/agent", createAgentRoutes(sessionManager));
|
|
17
|
+
app.route("/chat", createChatRoutes());
|
|
16
18
|
if (options.runCommands) {
|
|
17
19
|
app.get("/run", createRunRoute(options.runCommands));
|
|
18
20
|
}
|
|
@@ -22,6 +24,7 @@ import { eventsRoute as eventsRoute2 } from "./routes/events.js";
|
|
|
22
24
|
import { emitRoute as emitRoute2 } from "./routes/emit.js";
|
|
23
25
|
import { createRunRoute as createRunRoute2 } from "./routes/run.js";
|
|
24
26
|
import { createAgentRoutes as createAgentRoutes2 } from "./routes/agent.js";
|
|
27
|
+
import { createChatRoutes as createChatRoutes2 } from "./routes/chat.js";
|
|
25
28
|
import { SessionManager as SessionManager2 } from "./session-manager.js";
|
|
26
29
|
function snaPortRoute(c) {
|
|
27
30
|
const portFile = _path.join(process.cwd(), ".sna/sna-api.port");
|
|
@@ -35,6 +38,7 @@ function snaPortRoute(c) {
|
|
|
35
38
|
export {
|
|
36
39
|
SessionManager2 as SessionManager,
|
|
37
40
|
createAgentRoutes2 as createAgentRoutes,
|
|
41
|
+
createChatRoutes2 as createChatRoutes,
|
|
38
42
|
createRunRoute2 as createRunRoute,
|
|
39
43
|
createSnaApp,
|
|
40
44
|
emitRoute2 as emitRoute,
|
|
@@ -4,6 +4,7 @@ import {
|
|
|
4
4
|
getProvider
|
|
5
5
|
} from "../../core/providers/index.js";
|
|
6
6
|
import { logger } from "../../lib/logger.js";
|
|
7
|
+
import { getDb } from "../../db/schema.js";
|
|
7
8
|
function getSessionId(c) {
|
|
8
9
|
return c.req.query("session") ?? "default";
|
|
9
10
|
}
|
|
@@ -55,12 +56,23 @@ function createAgentRoutes(sessionManager) {
|
|
|
55
56
|
}
|
|
56
57
|
session.eventBuffer.length = 0;
|
|
57
58
|
const provider = getProvider(body.provider ?? "claude-code");
|
|
59
|
+
const skillMatch = body.prompt?.match(/^Execute the skill:\s*(\S+)/);
|
|
60
|
+
if (skillMatch) {
|
|
61
|
+
try {
|
|
62
|
+
const db = getDb();
|
|
63
|
+
db.prepare(
|
|
64
|
+
`INSERT INTO skill_events (session_id, skill, type, message) VALUES (?, ?, 'invoked', ?)`
|
|
65
|
+
).run(sessionId, skillMatch[1], `Skill ${skillMatch[1]} invoked`);
|
|
66
|
+
} catch {
|
|
67
|
+
}
|
|
68
|
+
}
|
|
58
69
|
try {
|
|
59
70
|
const proc = provider.spawn({
|
|
60
71
|
cwd: session.cwd,
|
|
61
72
|
prompt: body.prompt,
|
|
62
73
|
model: body.model ?? "claude-sonnet-4-6",
|
|
63
|
-
permissionMode: body.permissionMode ?? "acceptEdits"
|
|
74
|
+
permissionMode: body.permissionMode ?? "acceptEdits",
|
|
75
|
+
env: { SNA_SESSION_ID: sessionId }
|
|
64
76
|
});
|
|
65
77
|
sessionManager.setProcess(sessionId, proc);
|
|
66
78
|
logger.log("route", `POST /start?session=${sessionId} \u2192 started`);
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { Hono } from "hono";
|
|
2
|
+
import { getDb } from "../../db/schema.js";
|
|
3
|
+
function createChatRoutes() {
|
|
4
|
+
const app = new Hono();
|
|
5
|
+
app.get("/sessions", (c) => {
|
|
6
|
+
const db = getDb();
|
|
7
|
+
const sessions = db.prepare(
|
|
8
|
+
`SELECT id, label, type, created_at FROM chat_sessions ORDER BY created_at DESC`
|
|
9
|
+
).all();
|
|
10
|
+
return c.json({ sessions });
|
|
11
|
+
});
|
|
12
|
+
app.post("/sessions", async (c) => {
|
|
13
|
+
const body = await c.req.json().catch(() => ({}));
|
|
14
|
+
const id = body.id ?? crypto.randomUUID().slice(0, 8);
|
|
15
|
+
const db = getDb();
|
|
16
|
+
db.prepare(
|
|
17
|
+
`INSERT OR IGNORE INTO chat_sessions (id, label, type) VALUES (?, ?, ?)`
|
|
18
|
+
).run(id, body.label ?? id, body.type ?? "background");
|
|
19
|
+
return c.json({ status: "created", id });
|
|
20
|
+
});
|
|
21
|
+
app.delete("/sessions/:id", (c) => {
|
|
22
|
+
const id = c.req.param("id");
|
|
23
|
+
if (id === "default") {
|
|
24
|
+
return c.json({ status: "error", message: "Cannot delete default session" }, 400);
|
|
25
|
+
}
|
|
26
|
+
const db = getDb();
|
|
27
|
+
db.prepare(`DELETE FROM chat_sessions WHERE id = ?`).run(id);
|
|
28
|
+
return c.json({ status: "deleted" });
|
|
29
|
+
});
|
|
30
|
+
app.get("/sessions/:id/messages", (c) => {
|
|
31
|
+
const id = c.req.param("id");
|
|
32
|
+
const sinceParam = c.req.query("since");
|
|
33
|
+
const db = getDb();
|
|
34
|
+
const query = sinceParam ? db.prepare(`SELECT * FROM chat_messages WHERE session_id = ? AND id > ? ORDER BY id ASC`) : db.prepare(`SELECT * FROM chat_messages WHERE session_id = ? ORDER BY id ASC`);
|
|
35
|
+
const messages = sinceParam ? query.all(id, parseInt(sinceParam, 10)) : query.all(id);
|
|
36
|
+
return c.json({ messages });
|
|
37
|
+
});
|
|
38
|
+
app.post("/sessions/:id/messages", async (c) => {
|
|
39
|
+
const sessionId = c.req.param("id");
|
|
40
|
+
const body = await c.req.json().catch(() => ({}));
|
|
41
|
+
if (!body.role) {
|
|
42
|
+
return c.json({ status: "error", message: "role is required" }, 400);
|
|
43
|
+
}
|
|
44
|
+
const db = getDb();
|
|
45
|
+
db.prepare(`INSERT OR IGNORE INTO chat_sessions (id, label, type) VALUES (?, ?, 'main')`).run(sessionId, sessionId);
|
|
46
|
+
const result = db.prepare(
|
|
47
|
+
`INSERT INTO chat_messages (session_id, role, content, skill_name, meta) VALUES (?, ?, ?, ?, ?)`
|
|
48
|
+
).run(
|
|
49
|
+
sessionId,
|
|
50
|
+
body.role,
|
|
51
|
+
body.content ?? "",
|
|
52
|
+
body.skill_name ?? null,
|
|
53
|
+
body.meta ? JSON.stringify(body.meta) : null
|
|
54
|
+
);
|
|
55
|
+
return c.json({ status: "created", id: result.lastInsertRowid });
|
|
56
|
+
});
|
|
57
|
+
app.delete("/sessions/:id/messages", (c) => {
|
|
58
|
+
const id = c.req.param("id");
|
|
59
|
+
const db = getDb();
|
|
60
|
+
db.prepare(`DELETE FROM chat_messages WHERE session_id = ?`).run(id);
|
|
61
|
+
return c.json({ status: "cleared" });
|
|
62
|
+
});
|
|
63
|
+
return app;
|
|
64
|
+
}
|
|
65
|
+
export {
|
|
66
|
+
createChatRoutes
|
|
67
|
+
};
|
|
@@ -5,7 +5,7 @@ const KEEPALIVE_INTERVAL_MS = 15e3;
|
|
|
5
5
|
function eventsRoute(c) {
|
|
6
6
|
const sinceParam = c.req.query("since");
|
|
7
7
|
let lastId = sinceParam ? parseInt(sinceParam) : -1;
|
|
8
|
-
if (lastId
|
|
8
|
+
if (lastId <= 0) {
|
|
9
9
|
const db = getDb();
|
|
10
10
|
const row = db.prepare("SELECT MAX(id) as maxId FROM skill_events").get();
|
|
11
11
|
lastId = row.maxId ?? 0;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
// src/server/standalone.ts
|
|
2
2
|
import { serve } from "@hono/node-server";
|
|
3
|
-
import { Hono as
|
|
3
|
+
import { Hono as Hono4 } from "hono";
|
|
4
4
|
import { cors } from "hono/cors";
|
|
5
5
|
import chalk2 from "chalk";
|
|
6
6
|
|
|
7
7
|
// src/server/index.ts
|
|
8
|
-
import { Hono as
|
|
8
|
+
import { Hono as Hono3 } from "hono";
|
|
9
9
|
|
|
10
10
|
// src/server/routes/events.ts
|
|
11
11
|
import { streamSSE } from "hono/streaming";
|
|
@@ -36,8 +36,31 @@ function migrateSkillEvents(db) {
|
|
|
36
36
|
function initSchema(db) {
|
|
37
37
|
migrateSkillEvents(db);
|
|
38
38
|
db.exec(`
|
|
39
|
+
CREATE TABLE IF NOT EXISTS chat_sessions (
|
|
40
|
+
id TEXT PRIMARY KEY,
|
|
41
|
+
label TEXT NOT NULL DEFAULT '',
|
|
42
|
+
type TEXT NOT NULL DEFAULT 'main',
|
|
43
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
-- Ensure default session always exists
|
|
47
|
+
INSERT OR IGNORE INTO chat_sessions (id, label, type) VALUES ('default', 'Chat', 'main');
|
|
48
|
+
|
|
49
|
+
CREATE TABLE IF NOT EXISTS chat_messages (
|
|
50
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
51
|
+
session_id TEXT NOT NULL REFERENCES chat_sessions(id) ON DELETE CASCADE,
|
|
52
|
+
role TEXT NOT NULL,
|
|
53
|
+
content TEXT NOT NULL DEFAULT '',
|
|
54
|
+
skill_name TEXT,
|
|
55
|
+
meta TEXT,
|
|
56
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now'))
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
CREATE INDEX IF NOT EXISTS idx_chat_messages_session ON chat_messages(session_id);
|
|
60
|
+
|
|
39
61
|
CREATE TABLE IF NOT EXISTS skill_events (
|
|
40
62
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
63
|
+
session_id TEXT REFERENCES chat_sessions(id) ON DELETE SET NULL,
|
|
41
64
|
skill TEXT NOT NULL,
|
|
42
65
|
type TEXT NOT NULL,
|
|
43
66
|
message TEXT NOT NULL,
|
|
@@ -47,6 +70,7 @@ function initSchema(db) {
|
|
|
47
70
|
|
|
48
71
|
CREATE INDEX IF NOT EXISTS idx_skill_events_skill ON skill_events(skill);
|
|
49
72
|
CREATE INDEX IF NOT EXISTS idx_skill_events_created ON skill_events(created_at);
|
|
73
|
+
CREATE INDEX IF NOT EXISTS idx_skill_events_session ON skill_events(session_id);
|
|
50
74
|
`);
|
|
51
75
|
}
|
|
52
76
|
|
|
@@ -56,7 +80,7 @@ var KEEPALIVE_INTERVAL_MS = 15e3;
|
|
|
56
80
|
function eventsRoute(c) {
|
|
57
81
|
const sinceParam = c.req.query("since");
|
|
58
82
|
let lastId = sinceParam ? parseInt(sinceParam) : -1;
|
|
59
|
-
if (lastId
|
|
83
|
+
if (lastId <= 0) {
|
|
60
84
|
const db = getDb();
|
|
61
85
|
const row = db.prepare("SELECT MAX(id) as maxId FROM skill_events").get();
|
|
62
86
|
lastId = row.maxId ?? 0;
|
|
@@ -395,21 +419,23 @@ var ClaudeCodeProcess = class {
|
|
|
395
419
|
}
|
|
396
420
|
case "result": {
|
|
397
421
|
if (msg.subtype === "success") {
|
|
422
|
+
const u = msg.usage ?? {};
|
|
398
423
|
const mu = msg.modelUsage ?? {};
|
|
399
424
|
const modelKey = Object.keys(mu)[0] ?? "";
|
|
400
|
-
const
|
|
425
|
+
const modelInfo = mu[modelKey] ?? {};
|
|
401
426
|
return {
|
|
402
427
|
type: "complete",
|
|
403
428
|
message: msg.result ?? "Done",
|
|
404
429
|
data: {
|
|
405
430
|
durationMs: msg.duration_ms,
|
|
406
431
|
costUsd: msg.total_cost_usd,
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
432
|
+
// Per-turn: actual context window usage this turn
|
|
433
|
+
inputTokens: u.input_tokens ?? 0,
|
|
434
|
+
outputTokens: u.output_tokens ?? 0,
|
|
435
|
+
cacheReadTokens: u.cache_read_input_tokens ?? 0,
|
|
436
|
+
cacheWriteTokens: u.cache_creation_input_tokens ?? 0,
|
|
437
|
+
// Static model info
|
|
438
|
+
contextWindow: modelInfo.contextWindow ?? 0,
|
|
413
439
|
model: modelKey
|
|
414
440
|
},
|
|
415
441
|
timestamp: Date.now()
|
|
@@ -550,12 +576,23 @@ function createAgentRoutes(sessionManager2) {
|
|
|
550
576
|
}
|
|
551
577
|
session.eventBuffer.length = 0;
|
|
552
578
|
const provider2 = getProvider(body.provider ?? "claude-code");
|
|
579
|
+
const skillMatch = body.prompt?.match(/^Execute the skill:\s*(\S+)/);
|
|
580
|
+
if (skillMatch) {
|
|
581
|
+
try {
|
|
582
|
+
const db = getDb();
|
|
583
|
+
db.prepare(
|
|
584
|
+
`INSERT INTO skill_events (session_id, skill, type, message) VALUES (?, ?, 'invoked', ?)`
|
|
585
|
+
).run(sessionId, skillMatch[1], `Skill ${skillMatch[1]} invoked`);
|
|
586
|
+
} catch {
|
|
587
|
+
}
|
|
588
|
+
}
|
|
553
589
|
try {
|
|
554
590
|
const proc = provider2.spawn({
|
|
555
591
|
cwd: session.cwd,
|
|
556
592
|
prompt: body.prompt,
|
|
557
593
|
model: body.model ?? "claude-sonnet-4-6",
|
|
558
|
-
permissionMode: body.permissionMode ?? "acceptEdits"
|
|
594
|
+
permissionMode: body.permissionMode ?? "acceptEdits",
|
|
595
|
+
env: { SNA_SESSION_ID: sessionId }
|
|
559
596
|
});
|
|
560
597
|
sessionManager2.setProcess(sessionId, proc);
|
|
561
598
|
logger.log("route", `POST /start?session=${sessionId} \u2192 started`);
|
|
@@ -639,6 +676,71 @@ function createAgentRoutes(sessionManager2) {
|
|
|
639
676
|
return app;
|
|
640
677
|
}
|
|
641
678
|
|
|
679
|
+
// src/server/routes/chat.ts
|
|
680
|
+
import { Hono as Hono2 } from "hono";
|
|
681
|
+
function createChatRoutes() {
|
|
682
|
+
const app = new Hono2();
|
|
683
|
+
app.get("/sessions", (c) => {
|
|
684
|
+
const db = getDb();
|
|
685
|
+
const sessions = db.prepare(
|
|
686
|
+
`SELECT id, label, type, created_at FROM chat_sessions ORDER BY created_at DESC`
|
|
687
|
+
).all();
|
|
688
|
+
return c.json({ sessions });
|
|
689
|
+
});
|
|
690
|
+
app.post("/sessions", async (c) => {
|
|
691
|
+
const body = await c.req.json().catch(() => ({}));
|
|
692
|
+
const id = body.id ?? crypto.randomUUID().slice(0, 8);
|
|
693
|
+
const db = getDb();
|
|
694
|
+
db.prepare(
|
|
695
|
+
`INSERT OR IGNORE INTO chat_sessions (id, label, type) VALUES (?, ?, ?)`
|
|
696
|
+
).run(id, body.label ?? id, body.type ?? "background");
|
|
697
|
+
return c.json({ status: "created", id });
|
|
698
|
+
});
|
|
699
|
+
app.delete("/sessions/:id", (c) => {
|
|
700
|
+
const id = c.req.param("id");
|
|
701
|
+
if (id === "default") {
|
|
702
|
+
return c.json({ status: "error", message: "Cannot delete default session" }, 400);
|
|
703
|
+
}
|
|
704
|
+
const db = getDb();
|
|
705
|
+
db.prepare(`DELETE FROM chat_sessions WHERE id = ?`).run(id);
|
|
706
|
+
return c.json({ status: "deleted" });
|
|
707
|
+
});
|
|
708
|
+
app.get("/sessions/:id/messages", (c) => {
|
|
709
|
+
const id = c.req.param("id");
|
|
710
|
+
const sinceParam = c.req.query("since");
|
|
711
|
+
const db = getDb();
|
|
712
|
+
const query = sinceParam ? db.prepare(`SELECT * FROM chat_messages WHERE session_id = ? AND id > ? ORDER BY id ASC`) : db.prepare(`SELECT * FROM chat_messages WHERE session_id = ? ORDER BY id ASC`);
|
|
713
|
+
const messages = sinceParam ? query.all(id, parseInt(sinceParam, 10)) : query.all(id);
|
|
714
|
+
return c.json({ messages });
|
|
715
|
+
});
|
|
716
|
+
app.post("/sessions/:id/messages", async (c) => {
|
|
717
|
+
const sessionId = c.req.param("id");
|
|
718
|
+
const body = await c.req.json().catch(() => ({}));
|
|
719
|
+
if (!body.role) {
|
|
720
|
+
return c.json({ status: "error", message: "role is required" }, 400);
|
|
721
|
+
}
|
|
722
|
+
const db = getDb();
|
|
723
|
+
db.prepare(`INSERT OR IGNORE INTO chat_sessions (id, label, type) VALUES (?, ?, 'main')`).run(sessionId, sessionId);
|
|
724
|
+
const result = db.prepare(
|
|
725
|
+
`INSERT INTO chat_messages (session_id, role, content, skill_name, meta) VALUES (?, ?, ?, ?, ?)`
|
|
726
|
+
).run(
|
|
727
|
+
sessionId,
|
|
728
|
+
body.role,
|
|
729
|
+
body.content ?? "",
|
|
730
|
+
body.skill_name ?? null,
|
|
731
|
+
body.meta ? JSON.stringify(body.meta) : null
|
|
732
|
+
);
|
|
733
|
+
return c.json({ status: "created", id: result.lastInsertRowid });
|
|
734
|
+
});
|
|
735
|
+
app.delete("/sessions/:id/messages", (c) => {
|
|
736
|
+
const id = c.req.param("id");
|
|
737
|
+
const db = getDb();
|
|
738
|
+
db.prepare(`DELETE FROM chat_messages WHERE session_id = ?`).run(id);
|
|
739
|
+
return c.json({ status: "cleared" });
|
|
740
|
+
});
|
|
741
|
+
return app;
|
|
742
|
+
}
|
|
743
|
+
|
|
642
744
|
// src/server/session-manager.ts
|
|
643
745
|
var DEFAULT_MAX_SESSIONS = 5;
|
|
644
746
|
var MAX_EVENT_BUFFER = 500;
|
|
@@ -742,11 +844,12 @@ var SessionManager = class {
|
|
|
742
844
|
// src/server/index.ts
|
|
743
845
|
function createSnaApp(options = {}) {
|
|
744
846
|
const sessionManager2 = options.sessionManager ?? new SessionManager();
|
|
745
|
-
const app = new
|
|
847
|
+
const app = new Hono3();
|
|
746
848
|
app.get("/health", (c) => c.json({ ok: true, name: "sna", version: "1" }));
|
|
747
849
|
app.get("/events", eventsRoute);
|
|
748
850
|
app.post("/emit", emitRoute);
|
|
749
851
|
app.route("/agent", createAgentRoutes(sessionManager2));
|
|
852
|
+
app.route("/chat", createChatRoutes());
|
|
750
853
|
if (options.runCommands) {
|
|
751
854
|
app.get("/run", createRunRoute(options.runCommands));
|
|
752
855
|
}
|
|
@@ -758,7 +861,7 @@ var port = parseInt(process.env.SNA_PORT ?? "3099", 10);
|
|
|
758
861
|
var permissionMode = process.env.SNA_PERMISSION_MODE ?? "acceptEdits";
|
|
759
862
|
var defaultModel = process.env.SNA_MODEL ?? "claude-sonnet-4-6";
|
|
760
863
|
var maxSessions = parseInt(process.env.SNA_MAX_SESSIONS ?? "5", 10);
|
|
761
|
-
var root = new
|
|
864
|
+
var root = new Hono4();
|
|
762
865
|
root.use("*", cors({ origin: "*", allowMethods: ["GET", "POST", "DELETE", "OPTIONS"] }));
|
|
763
866
|
var methodColor = {
|
|
764
867
|
GET: chalk2.green,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sna-sdk/core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.9",
|
|
4
4
|
"description": "Skills-Native Application runtime — server, providers, session management, database, and CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -44,6 +44,11 @@
|
|
|
44
44
|
"types": "./dist/server/routes/agent.d.ts",
|
|
45
45
|
"default": "./dist/server/routes/agent.js"
|
|
46
46
|
},
|
|
47
|
+
"./server/routes/chat": {
|
|
48
|
+
"source": "./src/server/routes/chat.ts",
|
|
49
|
+
"types": "./dist/server/routes/chat.d.ts",
|
|
50
|
+
"default": "./dist/server/routes/chat.js"
|
|
51
|
+
},
|
|
47
52
|
"./db/schema": {
|
|
48
53
|
"source": "./src/db/schema.ts",
|
|
49
54
|
"types": "./dist/db/schema.d.ts",
|
|
@@ -69,6 +74,7 @@
|
|
|
69
74
|
"engines": {
|
|
70
75
|
"node": ">=18.0.0"
|
|
71
76
|
},
|
|
77
|
+
"homepage": "https://skills-native.app",
|
|
72
78
|
"repository": {
|
|
73
79
|
"type": "git",
|
|
74
80
|
"url": "https://github.com/neuradex/sna",
|