@sna-sdk/core 0.9.4 → 0.9.8
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/cli.js +14 -15
- package/dist/lib/logger.d.ts +1 -10
- package/dist/lib/logger.js +6 -20
- package/dist/scripts/hook.js +12 -9
- package/dist/server/session-manager.js +13 -0
- package/dist/server/standalone.js +27 -36
- package/package.json +1 -2
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
3
|
import { fileURLToPath } from "url";
|
|
4
|
-
import chalk from "chalk";
|
|
5
4
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
6
5
|
const PACKAGE_ROOT = path.resolve(__dirname, "..");
|
|
7
6
|
const [, , command] = process.argv;
|
|
@@ -14,9 +13,9 @@ switch (command) {
|
|
|
14
13
|
break;
|
|
15
14
|
default:
|
|
16
15
|
console.log(`
|
|
17
|
-
|
|
16
|
+
sna \u2014 Skills-Native Application core primitives
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
Usage:
|
|
20
19
|
sna link Create/update .claude/skills symlinks
|
|
21
20
|
sna install Add sna to package.json and link skills
|
|
22
21
|
`);
|
|
@@ -27,11 +26,11 @@ function cmdLink() {
|
|
|
27
26
|
const skillsDir = path.join(claudeDir, "skills");
|
|
28
27
|
if (!fs.existsSync(skillsDir)) {
|
|
29
28
|
fs.mkdirSync(skillsDir, { recursive: true });
|
|
30
|
-
console.log(
|
|
29
|
+
console.log(` created .claude/skills/`);
|
|
31
30
|
}
|
|
32
31
|
const coreSkillsDir = path.join(PACKAGE_ROOT, "skills");
|
|
33
32
|
if (!fs.existsSync(coreSkillsDir)) {
|
|
34
|
-
console.error(
|
|
33
|
+
console.error(` \u2717 sna skills directory not found: ${coreSkillsDir}`);
|
|
35
34
|
process.exit(1);
|
|
36
35
|
}
|
|
37
36
|
const skills = fs.readdirSync(coreSkillsDir).filter(
|
|
@@ -57,33 +56,33 @@ function cmdLink() {
|
|
|
57
56
|
}
|
|
58
57
|
fs.unlinkSync(linkPath);
|
|
59
58
|
fs.symlinkSync(target, linkPath);
|
|
60
|
-
console.log(
|
|
59
|
+
console.log(` updated .claude/skills/${skill}/ \u2192 ${target}`);
|
|
61
60
|
updated++;
|
|
62
61
|
} else {
|
|
63
|
-
console.log(
|
|
62
|
+
console.log(` skipped .claude/skills/${skill}/ (not a symlink \u2014 won't overwrite)`);
|
|
64
63
|
skipped++;
|
|
65
64
|
}
|
|
66
65
|
} else {
|
|
67
66
|
fs.symlinkSync(target, linkPath);
|
|
68
|
-
console.log(
|
|
67
|
+
console.log(` linked .claude/skills/${skill}/ \u2192 ${target}`);
|
|
69
68
|
linked++;
|
|
70
69
|
}
|
|
71
70
|
}
|
|
72
71
|
console.log();
|
|
73
72
|
if (linked + updated > 0) {
|
|
74
|
-
console.log(
|
|
73
|
+
console.log(`\u2713 ${linked + updated} skill(s) linked`);
|
|
75
74
|
} else {
|
|
76
|
-
console.log(
|
|
75
|
+
console.log(`\u2713 Skills already up to date`);
|
|
77
76
|
}
|
|
78
77
|
if (skipped > 0 && linked + updated > 0) {
|
|
79
|
-
console.log(
|
|
78
|
+
console.log(` (${skipped} unchanged)`);
|
|
80
79
|
}
|
|
81
80
|
}
|
|
82
81
|
function cmdInstall() {
|
|
83
82
|
const cwd = process.cwd();
|
|
84
83
|
const pkgPath = path.join(cwd, "package.json");
|
|
85
84
|
if (!fs.existsSync(pkgPath)) {
|
|
86
|
-
console.error(
|
|
85
|
+
console.error(` \u2717 No package.json found in current directory`);
|
|
87
86
|
process.exit(1);
|
|
88
87
|
}
|
|
89
88
|
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
@@ -94,10 +93,10 @@ function cmdInstall() {
|
|
|
94
93
|
pkg.dependencies = pkg.dependencies ?? {};
|
|
95
94
|
pkg.dependencies["sna"] = `^${version}`;
|
|
96
95
|
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
97
|
-
console.log(
|
|
98
|
-
console.log(
|
|
96
|
+
console.log(` added "sna": "^${version}" to dependencies`);
|
|
97
|
+
console.log(` run pnpm install to install`);
|
|
99
98
|
} else {
|
|
100
|
-
console.log(
|
|
99
|
+
console.log(` sna already in package.json`);
|
|
101
100
|
}
|
|
102
101
|
console.log();
|
|
103
102
|
cmdLink();
|
package/dist/lib/logger.d.ts
CHANGED
|
@@ -1,13 +1,4 @@
|
|
|
1
|
-
declare const tags:
|
|
2
|
-
readonly sna: string;
|
|
3
|
-
readonly req: string;
|
|
4
|
-
readonly agent: string;
|
|
5
|
-
readonly stdin: string;
|
|
6
|
-
readonly stdout: string;
|
|
7
|
-
readonly route: string;
|
|
8
|
-
readonly ws: string;
|
|
9
|
-
readonly err: string;
|
|
10
|
-
};
|
|
1
|
+
declare const tags: Record<string, string>;
|
|
11
2
|
type Tag = keyof typeof tags;
|
|
12
3
|
declare function log(tag: Tag, ...args: unknown[]): void;
|
|
13
4
|
declare function err(tag: Tag, ...args: unknown[]): void;
|
package/dist/lib/logger.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import chalk from "chalk";
|
|
2
1
|
import fs from "fs";
|
|
3
2
|
import path from "path";
|
|
4
3
|
const LOG_PATH = path.join(process.cwd(), ".dev.log");
|
|
@@ -6,23 +5,10 @@ try {
|
|
|
6
5
|
fs.writeFileSync(LOG_PATH, "");
|
|
7
6
|
} catch {
|
|
8
7
|
}
|
|
9
|
-
function
|
|
8
|
+
function ts() {
|
|
10
9
|
return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
11
10
|
}
|
|
12
|
-
function tsColored() {
|
|
13
|
-
return chalk.gray(tsPlain());
|
|
14
|
-
}
|
|
15
11
|
const tags = {
|
|
16
|
-
sna: chalk.bold.magenta(" SNA "),
|
|
17
|
-
req: chalk.bold.blue(" REQ "),
|
|
18
|
-
agent: chalk.bold.cyan(" AGT "),
|
|
19
|
-
stdin: chalk.bold.green(" IN "),
|
|
20
|
-
stdout: chalk.bold.yellow(" OUT "),
|
|
21
|
-
route: chalk.bold.blue(" API "),
|
|
22
|
-
ws: chalk.bold.green(" WS "),
|
|
23
|
-
err: chalk.bold.red(" ERR ")
|
|
24
|
-
};
|
|
25
|
-
const tagPlain = {
|
|
26
12
|
sna: " SNA ",
|
|
27
13
|
req: " REQ ",
|
|
28
14
|
agent: " AGT ",
|
|
@@ -33,18 +19,18 @@ const tagPlain = {
|
|
|
33
19
|
err: " ERR "
|
|
34
20
|
};
|
|
35
21
|
function appendFile(tag, args) {
|
|
36
|
-
const line = `${
|
|
22
|
+
const line = `${ts()} ${tag} ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
|
|
37
23
|
`;
|
|
38
24
|
fs.appendFile(LOG_PATH, line, () => {
|
|
39
25
|
});
|
|
40
26
|
}
|
|
41
27
|
function log(tag, ...args) {
|
|
42
|
-
console.log(`${
|
|
43
|
-
appendFile(
|
|
28
|
+
console.log(`${ts()} ${tags[tag] ?? tag}`, ...args);
|
|
29
|
+
appendFile(tags[tag] ?? tag, args);
|
|
44
30
|
}
|
|
45
31
|
function err(tag, ...args) {
|
|
46
|
-
console.error(`${
|
|
47
|
-
appendFile(
|
|
32
|
+
console.error(`${ts()} ${tags[tag] ?? tag}`, ...args);
|
|
33
|
+
appendFile(tags[tag] ?? tag, args);
|
|
48
34
|
}
|
|
49
35
|
const logger = { log, err };
|
|
50
36
|
export {
|
package/dist/scripts/hook.js
CHANGED
|
@@ -13,19 +13,16 @@ process.stdin.on("end", async () => {
|
|
|
13
13
|
let apiUrl;
|
|
14
14
|
if (process.env.SNA_API_URL) {
|
|
15
15
|
apiUrl = process.env.SNA_API_URL;
|
|
16
|
+
} else if (process.env.SNA_PORT) {
|
|
17
|
+
apiUrl = `http://localhost:${process.env.SNA_PORT}`;
|
|
16
18
|
} else {
|
|
17
19
|
const portFile = path.join(process.cwd(), ".sna/sna-api.port");
|
|
18
20
|
try {
|
|
19
21
|
const port = fs.readFileSync(portFile, "utf8").trim();
|
|
20
22
|
apiUrl = `http://localhost:${port}`;
|
|
21
23
|
} catch {
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
apiUrl = `http://localhost:${snaPort}`;
|
|
25
|
-
} else {
|
|
26
|
-
allow();
|
|
27
|
-
return;
|
|
28
|
-
}
|
|
24
|
+
allow();
|
|
25
|
+
return;
|
|
29
26
|
}
|
|
30
27
|
}
|
|
31
28
|
const sessionId = process.argv.find((a) => a.startsWith("--session="))?.slice(10) ?? process.env.SNA_SESSION_ID ?? "default";
|
|
@@ -59,6 +56,12 @@ function allow() {
|
|
|
59
56
|
process.exit(0);
|
|
60
57
|
}
|
|
61
58
|
function deny(reason) {
|
|
62
|
-
|
|
63
|
-
|
|
59
|
+
console.log(JSON.stringify({
|
|
60
|
+
hookSpecificOutput: {
|
|
61
|
+
hookEventName: "PreToolUse",
|
|
62
|
+
permissionDecision: "deny",
|
|
63
|
+
permissionDecisionReason: reason
|
|
64
|
+
}
|
|
65
|
+
}));
|
|
66
|
+
process.exit(0);
|
|
64
67
|
}
|
|
@@ -477,11 +477,24 @@ class SessionManager {
|
|
|
477
477
|
}
|
|
478
478
|
/** Kill all sessions. Used during shutdown. */
|
|
479
479
|
killAll() {
|
|
480
|
+
const pids = [];
|
|
480
481
|
for (const session of this.sessions.values()) {
|
|
481
482
|
if (session.process?.alive) {
|
|
483
|
+
const pid = session.process.pid;
|
|
482
484
|
session.process.kill();
|
|
485
|
+
if (pid) pids.push(pid);
|
|
483
486
|
}
|
|
484
487
|
}
|
|
488
|
+
if (pids.length > 0) {
|
|
489
|
+
setTimeout(() => {
|
|
490
|
+
for (const pid of pids) {
|
|
491
|
+
try {
|
|
492
|
+
process.kill(pid, "SIGKILL");
|
|
493
|
+
} catch {
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}, 1e3);
|
|
497
|
+
}
|
|
485
498
|
}
|
|
486
499
|
get size() {
|
|
487
500
|
return this.sessions.size;
|
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
import { serve } from "@hono/node-server";
|
|
3
3
|
import { Hono as Hono4 } from "hono";
|
|
4
4
|
import { cors } from "hono/cors";
|
|
5
|
-
import chalk2 from "chalk";
|
|
6
5
|
|
|
7
6
|
// src/server/index.ts
|
|
8
7
|
import { Hono as Hono3 } from "hono";
|
|
@@ -360,7 +359,6 @@ ${xml}
|
|
|
360
359
|
}
|
|
361
360
|
|
|
362
361
|
// src/lib/logger.ts
|
|
363
|
-
import chalk from "chalk";
|
|
364
362
|
import fs3 from "fs";
|
|
365
363
|
import path3 from "path";
|
|
366
364
|
var LOG_PATH = path3.join(process.cwd(), ".dev.log");
|
|
@@ -368,23 +366,10 @@ try {
|
|
|
368
366
|
fs3.writeFileSync(LOG_PATH, "");
|
|
369
367
|
} catch {
|
|
370
368
|
}
|
|
371
|
-
function
|
|
369
|
+
function ts() {
|
|
372
370
|
return (/* @__PURE__ */ new Date()).toLocaleTimeString("en-US", { hour12: false, hour: "2-digit", minute: "2-digit", second: "2-digit" });
|
|
373
371
|
}
|
|
374
|
-
function tsColored() {
|
|
375
|
-
return chalk.gray(tsPlain());
|
|
376
|
-
}
|
|
377
372
|
var tags = {
|
|
378
|
-
sna: chalk.bold.magenta(" SNA "),
|
|
379
|
-
req: chalk.bold.blue(" REQ "),
|
|
380
|
-
agent: chalk.bold.cyan(" AGT "),
|
|
381
|
-
stdin: chalk.bold.green(" IN "),
|
|
382
|
-
stdout: chalk.bold.yellow(" OUT "),
|
|
383
|
-
route: chalk.bold.blue(" API "),
|
|
384
|
-
ws: chalk.bold.green(" WS "),
|
|
385
|
-
err: chalk.bold.red(" ERR ")
|
|
386
|
-
};
|
|
387
|
-
var tagPlain = {
|
|
388
373
|
sna: " SNA ",
|
|
389
374
|
req: " REQ ",
|
|
390
375
|
agent: " AGT ",
|
|
@@ -395,18 +380,18 @@ var tagPlain = {
|
|
|
395
380
|
err: " ERR "
|
|
396
381
|
};
|
|
397
382
|
function appendFile(tag, args) {
|
|
398
|
-
const line = `${
|
|
383
|
+
const line = `${ts()} ${tag} ${args.map((a) => typeof a === "string" ? a : JSON.stringify(a)).join(" ")}
|
|
399
384
|
`;
|
|
400
385
|
fs3.appendFile(LOG_PATH, line, () => {
|
|
401
386
|
});
|
|
402
387
|
}
|
|
403
388
|
function log(tag, ...args) {
|
|
404
|
-
console.log(`${
|
|
405
|
-
appendFile(
|
|
389
|
+
console.log(`${ts()} ${tags[tag] ?? tag}`, ...args);
|
|
390
|
+
appendFile(tags[tag] ?? tag, args);
|
|
406
391
|
}
|
|
407
392
|
function err(tag, ...args) {
|
|
408
|
-
console.error(`${
|
|
409
|
-
appendFile(
|
|
393
|
+
console.error(`${ts()} ${tags[tag] ?? tag}`, ...args);
|
|
394
|
+
appendFile(tags[tag] ?? tag, args);
|
|
410
395
|
}
|
|
411
396
|
var logger = { log, err };
|
|
412
397
|
|
|
@@ -2000,11 +1985,24 @@ var SessionManager = class {
|
|
|
2000
1985
|
}
|
|
2001
1986
|
/** Kill all sessions. Used during shutdown. */
|
|
2002
1987
|
killAll() {
|
|
1988
|
+
const pids = [];
|
|
2003
1989
|
for (const session of this.sessions.values()) {
|
|
2004
1990
|
if (session.process?.alive) {
|
|
1991
|
+
const pid = session.process.pid;
|
|
2005
1992
|
session.process.kill();
|
|
1993
|
+
if (pid) pids.push(pid);
|
|
2006
1994
|
}
|
|
2007
1995
|
}
|
|
1996
|
+
if (pids.length > 0) {
|
|
1997
|
+
setTimeout(() => {
|
|
1998
|
+
for (const pid of pids) {
|
|
1999
|
+
try {
|
|
2000
|
+
process.kill(pid, "SIGKILL");
|
|
2001
|
+
} catch {
|
|
2002
|
+
}
|
|
2003
|
+
}
|
|
2004
|
+
}, 1e3);
|
|
2005
|
+
}
|
|
2008
2006
|
}
|
|
2009
2007
|
get size() {
|
|
2010
2008
|
return this.sessions.size;
|
|
@@ -2714,22 +2712,15 @@ try {
|
|
|
2714
2712
|
}
|
|
2715
2713
|
var { port, defaultPermissionMode: permissionMode, model: defaultModel, maxSessions } = getConfig();
|
|
2716
2714
|
var root = new Hono4();
|
|
2717
|
-
root.use("*", cors({ origin: "*", allowMethods: ["GET", "POST", "DELETE", "OPTIONS"] }));
|
|
2715
|
+
root.use("*", cors({ origin: "*", allowMethods: ["GET", "POST", "PATCH", "DELETE", "OPTIONS"] }));
|
|
2718
2716
|
root.onError((err2, c) => {
|
|
2719
2717
|
logger.err("err", `${c.req.method} ${new URL(c.req.url).pathname} \u2192 ${err2.message}`);
|
|
2720
2718
|
return c.json({ status: "error", message: err2.message, stack: err2.stack }, 500);
|
|
2721
2719
|
});
|
|
2722
|
-
var methodColor = {
|
|
2723
|
-
GET: chalk2.green,
|
|
2724
|
-
POST: chalk2.yellow,
|
|
2725
|
-
DELETE: chalk2.red,
|
|
2726
|
-
OPTIONS: chalk2.gray
|
|
2727
|
-
};
|
|
2728
2720
|
root.use("*", async (c, next) => {
|
|
2729
2721
|
const m = c.req.method;
|
|
2730
|
-
const colorFn = methodColor[m] ?? chalk2.white;
|
|
2731
2722
|
const path6 = new URL(c.req.url).pathname;
|
|
2732
|
-
logger.log("req", `${
|
|
2723
|
+
logger.log("req", `${m.padEnd(6)} ${path6}`);
|
|
2733
2724
|
await next();
|
|
2734
2725
|
});
|
|
2735
2726
|
var sessionManager = new SessionManager({ maxSessions });
|
|
@@ -2745,17 +2736,17 @@ function shutdown(signal) {
|
|
|
2745
2736
|
if (shuttingDown) return;
|
|
2746
2737
|
shuttingDown = true;
|
|
2747
2738
|
console.log("");
|
|
2748
|
-
logger.log("sna",
|
|
2739
|
+
logger.log("sna", "stopping all sessions...");
|
|
2749
2740
|
sessionManager.killAll();
|
|
2750
2741
|
if (server) {
|
|
2751
2742
|
server.close(() => {
|
|
2752
|
-
logger.log("sna",
|
|
2743
|
+
logger.log("sna", "clean shutdown \u2014 see you next time");
|
|
2753
2744
|
console.log("");
|
|
2754
2745
|
process.exit(0);
|
|
2755
2746
|
});
|
|
2756
2747
|
}
|
|
2757
2748
|
setTimeout(() => {
|
|
2758
|
-
logger.log("sna",
|
|
2749
|
+
logger.log("sna", "shutdown complete");
|
|
2759
2750
|
console.log("");
|
|
2760
2751
|
process.exit(0);
|
|
2761
2752
|
}, 3e3).unref();
|
|
@@ -2769,13 +2760,13 @@ process.on("uncaughtException", (err2) => {
|
|
|
2769
2760
|
});
|
|
2770
2761
|
server = serve({ fetch: root.fetch, port }, () => {
|
|
2771
2762
|
console.log("");
|
|
2772
|
-
logger.log("sna",
|
|
2773
|
-
logger.log("sna",
|
|
2763
|
+
logger.log("sna", `API server ready \u2192 http://localhost:${port}`);
|
|
2764
|
+
logger.log("sna", `WebSocket endpoint \u2192 ws://localhost:${port}/ws`);
|
|
2774
2765
|
console.log("");
|
|
2775
2766
|
});
|
|
2776
2767
|
attachWebSocket(server, sessionManager);
|
|
2777
2768
|
agentProcess.on("event", (e) => {
|
|
2778
2769
|
if (e.type === "init") {
|
|
2779
|
-
logger.log("agent",
|
|
2770
|
+
logger.log("agent", `agent ready (session=${e.data?.sessionId ?? "?"})`);
|
|
2780
2771
|
}
|
|
2781
2772
|
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sna-sdk/core",
|
|
3
|
-
"version": "0.9.
|
|
3
|
+
"version": "0.9.8",
|
|
4
4
|
"description": "Skills-Native Application runtime — server, providers, session management, database, and CLI",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -102,7 +102,6 @@
|
|
|
102
102
|
],
|
|
103
103
|
"dependencies": {
|
|
104
104
|
"@hono/node-server": "^1.19.11",
|
|
105
|
-
"chalk": "^5.0.0",
|
|
106
105
|
"hono": "^4.12.7",
|
|
107
106
|
"js-yaml": "^4.1.0",
|
|
108
107
|
"ws": "^8.20.0"
|