tune-sdk 0.2.2 → 0.2.4

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 CHANGED
@@ -6,6 +6,18 @@ With tune [javascript sdk](https://www.npmjs.com/package/tune-sdk) you can make
6
6
  ## Demo
7
7
  <img src="https://github.com/iovdin/tune/blob/770f382a03a25e15eeef293f553b6aee0f3531f6/docs/assets/gifs/tune.gif">
8
8
 
9
+
10
+ ## Setup
11
+ install tune-sdk
12
+ ```bash
13
+ npm install -g tune-sdk
14
+
15
+ tune-sdk init
16
+ ```
17
+
18
+ edit `~/.tune/.env` file and add `OPENAI_KEY` and other keys
19
+
20
+
9
21
  ## Template Language
10
22
 
11
23
  ```chat
@@ -24,9 +36,41 @@ With tune [javascript sdk](https://www.npmjs.com/package/tune-sdk) you can make
24
36
  ```
25
37
  [read more](https://iovdin.github.io/tune/template-language)
26
38
 
39
+ ## Extend with Middlewares
40
+ Extend Tune with middlewares:
41
+
42
+ * [tune-fs](https://www.npmjs.com/package/tune-fs) - connect tools & files from local filesystem
43
+ * [tune-models](https://www.npmjs.com/package/tune-models) - connect llm models from Anthropic/OpenAI/Gemini/Openrouter/Mistral/Groq
44
+ * [tune-basic-toolset](https://www.npmjs.com/package/tune-basic-toolset) - basic tools like read file, write file, shell etc.
45
+ * [tune-s3](https://www.npmjs.com/package/tune-s3) - read/write files from s3
46
+
47
+ For example:
48
+ ```sh
49
+ cd ~/.tune
50
+ npm install tune-models
51
+ ```
52
+
53
+ Edit `default.ctx.js` and add middlewares
54
+ ```javascript
55
+ const models = require('tune-models')
56
+
57
+ module.exports = [
58
+ ...
59
+ models({
60
+ default: "gpt-5-mini"
61
+ })
62
+ ...
63
+ ]
64
+ ```
65
+
66
+ Edit `.env` file and add provider's keys
27
67
 
28
- ## Batteries Included
29
- **Anthropic/OpenAI/Gemini/Openrouter/Mistral/Groq** models providers are supported and **30+ tools** come with text editor.
68
+ ```.env
69
+ OPENAI_KEY="<openai_key>"
70
+ ANTHROPIC_KEY="<anthropic_key>"
71
+ ```
72
+
73
+ Use it in chat
30
74
  ```chat
31
75
  system:
32
76
  @gemini-2.5-pro @openai_imgen
@@ -41,20 +85,23 @@ a simple stickman drawing with a talking bubble saying 'Hello world'
41
85
  tool_result:
42
86
  image generated
43
87
  ```
44
- [read more](tools/README.md)
88
+
45
89
 
46
90
  ## CLI
47
91
 
48
92
  ```bash
93
+ # install tune globally
94
+ npm install -g tune-sdk
95
+
49
96
  # append user message to newchat.chat run and save
50
- npx tune-sdk --user "hi how are you?" --filename newchat.chat --save
97
+ tune-sdk --user "hi how are you?" --filename newchat.chat --save
51
98
 
52
99
  # start new chat with system prompt and initial user message
53
100
  # print result to console
54
- npx tune-sdk --system "You are Groot" --user "Hi how are you?"
101
+ tune-sdk --system "You are Groot" --user "Hi how are you?"
55
102
 
56
103
  #set context variable
57
- npx tune-sdk --set-test "hello" --user "@test" --system "You are echo you print everythting back"
104
+ tune-sdk --set-test "hello" --user "@test" --system "You are echo you print everythting back"
58
105
  ```
59
106
 
60
107
 
@@ -1,25 +1,22 @@
1
- const fs = require('fs')
2
1
  const path = require('path')
3
2
 
4
3
  const models = require("tune-models")
5
- const { curFile, fsMix, defaultWrite } = require("tune-sdk/fsctx");
6
- const { file2run, makeContext } = require("tune-sdk");
4
+ const basics = require("tune-basic-toolset")
5
+ const tunefs = require("tune-fs")
6
+ const { current, writer } = tunefs
7
7
 
8
- async function makeSchema(params, ctx) {
9
- return file2run({ filename: path.join(__dirname, "schema.tool.chat")}, params, ctx);
10
- }
11
8
  let dirs = [];
12
9
  if (process.env.TUNE_PATH) {
13
10
  dirs = process.env.TUNE_PATH.split(path.delimiter);
14
11
  }
15
12
 
16
-
17
13
  module.exports = [
18
- // curFile,
19
- fsMix(dirs, { makeSchema }),
14
+ current(),
15
+ basics(),
16
+ tunefs({ paths: dirs, makeSchema: true }),
20
17
  models({
21
- default: "gpt-4.1-mini",
18
+ default: "gpt-5-mini",
22
19
  alias: { "sonnet": "claude-sonnet-4-20250514"}
23
20
  }),
24
- defaultWrite()
21
+ writer()
25
22
  ]
@@ -0,0 +1,10 @@
1
+ s: @{ gpt-5-nano | json_format }
2
+ You are giving name for chat based on the chat contents
3
+ chat file content:
4
+ ```
5
+ @{ editor/buffer | head 100}
6
+ ```
7
+ example json output:
8
+ { "filename": "how-to-make-hero-costume.chat"}
9
+ or
10
+ { "filename": "accounting-database-zig.chat"}
@@ -1,5 +1,7 @@
1
1
  {
2
2
  "dependencies": {
3
+ "tune-basic-toolset": "latest",
4
+ "tune-fs": "latest",
3
5
  "tune-models": "latest",
4
6
  "tune-sdk": "latest"
5
7
  }
package/dist/cli.js CHANGED
@@ -5,20 +5,27 @@ function showHelp() {
5
5
  console.log("TUNE-CLI - Command Line Interface for Tune SDK");
6
6
  console.log("");
7
7
  console.log("USAGE:");
8
- console.log(" npx tune-sdk [OPTIONS]");
8
+ console.log(" tune-sdk [cmd] [OPTIONS]");
9
9
  console.log("");
10
- console.log("TLDR EXAMPLES:");
10
+ console.log("COMMANDS:");
11
+ console.log(" rpc Start RPC server mode");
12
+ console.log(" init Initialize Tune config directory");
13
+ console.log("");
14
+ console.log("EXAMPLES:");
11
15
  console.log(" # Quick chat with system prompt");
12
- console.log(" npx tune-sdk --system \"You are Groot\" --user \"Hi how are you?\"");
16
+ console.log(" tune-sdk --system \"You are Groot\" --user \"Hi how are you?\"");
13
17
  console.log("");
14
18
  console.log(" # Continue existing chat");
15
- console.log(" npx tune-sdk --user \"continue the conversation\" --filename chat.chat --save");
19
+ console.log(" tune-sdk --user \"continue the conversation\" --filename chat.chat --save");
16
20
  console.log("");
17
21
  console.log(" # Set context variables");
18
- console.log(" npx tune-sdk --set-test=hello --user \"@test\" --system \"Echo assistant\"");
22
+ console.log(" tune-sdk --set-test=hello --user \"@test\" --system \"Echo assistant\"");
19
23
  console.log("");
20
24
  console.log(" # RPC mode for editor integration");
21
- console.log(" npx tune-sdk --rpc");
25
+ console.log(" tune-sdk rpc");
26
+ console.log("");
27
+ console.log(" # Initialize or reinitialize config directory");
28
+ console.log(" tune-sdk init --force");
22
29
  console.log("");
23
30
  console.log("OPTIONS:");
24
31
  console.log(" --user <text> User message to send");
@@ -29,26 +36,13 @@ function showHelp() {
29
36
  console.log(" --text <content> chat content");
30
37
  console.log(" --response <type> Response format: content|json|messages|chat (default: content)");
31
38
  console.log(" --set-<name>=<value> Set context parameter");
32
- console.log(" --rpc Start RPC server mode");
33
39
  console.log(" --path <paths> Additional search paths (colon-separated)");
34
40
  console.log(" --home <dir> Tune config directory (default: ~/.tune)");
35
41
  console.log(" --debug Enable debug output");
36
42
  console.log(" --silent Suppress output");
37
- console.log(" --force-init Force config initialization");
38
- console.log(" --help Show this help");
39
- console.log("");
40
- console.log("EXAMPLES:");
41
- console.log(" # Start new chat");
42
- console.log(" npx tune-sdk --system \"You are Groot\" --user \"Hi how are you?\"");
43
- console.log("");
44
- console.log(" # Append to existing chat and save");
45
- console.log(" npx tune-sdk --user \"hi how are you?\" --filename newchat.chat --save");
46
- console.log("");
47
- console.log(" # Stop at specific word");
48
- console.log(" npx tune-sdk --user \"continue\" --filename chat.chat --stop \"END\"");
49
- console.log("");
50
- console.log(" #Set context variable");
51
- console.log(" npx tune-sdk --set-test=\"hello\" --user \"@test\" --system \"You are echo you print everythting back\"");
43
+ console.log(" --force Force config initialization (with 'init')");
44
+ console.log(" --help Show this help");
45
+ console.log(" --version Show CLI version");
52
46
  return console.log("");
53
47
  }
54
48
  showHelp;
@@ -64,13 +58,18 @@ function validateArgs(args) {
64
58
  if (args.path) assert(typeof args.path === "string", "--path must be a string");
65
59
  if (args.home) assert(typeof args.home === "string", "--home must be a string");
66
60
  if (!!args.save) assert(typeof args.save === "boolean", "--save must be a boolean");
67
- if (!!args.rpc) assert(typeof args.rpc === "boolean", "--rpc must be a boolean");
68
61
  if (!!args.debug) assert(typeof args.debug === "boolean" || typeof args.debug === "string", "--debug must be a boolean");
69
62
  if (!!args.silent) assert(typeof args.silent === "boolean", "--silent must be a boolean");
70
- if (!!args.forceInit) assert(typeof args.forceInit === "boolean", "--force-init must be a boolean");
63
+ if (!!args.force) assert(typeof args.force === "boolean", "--force must be a boolean");
64
+ if (typeof args.rpc !== "undefined") assert(false, "Use 'tune-sdk rpc' instead of --rpc");
65
+ if (typeof args.forceInit !== "undefined") assert(false, "Use 'tune-sdk init --force' instead of --force-init");
71
66
  if (args.params) assert(!!args.params && (typeof args.params === "object"), "--set-* parameters must form a valid object");
72
67
  if ((args.stop && (typeof args.stop === "string"))) assert((args.stop === "assistant") || (args.stop === "step") || (args.stop.length > 0), "--stop must be 'assistant', 'step', or a non-empty custom string");
73
- if ((!args.rpc && !args.help && !args.user && !args.filename)) assert(false, "Must specify --user, --filename, --rpc, or --help");
68
+ if (args.cmd) {
69
+ assert(typeof args.cmd === "string", "Command must be a string");
70
+ assert((args.cmd === "rpc") || (args.cmd === "init"), "Unknown command: " + args.cmd);
71
+ }
72
+ if ((!args.help && !args.version && !args.cmd && !args.user && !args.filename)) assert(false, "Must specify --user, --filename, a command (rpc|init), --version, or --help");
74
73
  return args;
75
74
  }
76
75
  validateArgs;
@@ -100,6 +99,12 @@ function parseArgs(args) {
100
99
  } else if (curKey) {
101
100
  memo[curKey] = arg;
102
101
  curKey = null;
102
+ } else {
103
+ if (!memo.__cmd) {
104
+ memo.__cmd = arg;
105
+ } else {
106
+ assert(false, "Only a single positional command is allowed");
107
+ }
103
108
  }
104
109
  return memo;
105
110
  }), {});
@@ -110,6 +115,10 @@ function parseArgs(args) {
110
115
  for (key in _ref) {
111
116
  value = _ref[key];
112
117
  assert(typeof key === "string", "Argument keys must be strings");
118
+ if (key === "__cmd") {
119
+ res1.cmd = value;
120
+ continue;
121
+ }
113
122
  if (key.startsWith("set-")) {
114
123
  res1.params = res1.params || {}
115
124
  assert(key.substr(4).length > 0, "Set parameter name cannot be empty");
@@ -119,6 +128,7 @@ function parseArgs(args) {
119
128
  }
120
129
  }
121
130
  if ((res1.h || res1.help)) res1.help = true;
131
+ if ((res1.v || res1.version)) res1.version = true;
122
132
  stop = res1.stop;
123
133
  if ((!!stop && (stop !== "step" && stop !== "assistant"))) {
124
134
  assert(typeof stop === "string", "Custom stop condition must be a string");
@@ -159,7 +169,7 @@ async function initConfig(args) {
159
169
  var homedir;
160
170
  homedir = getHomedir(args);
161
171
  assert(typeof homedir === "string", "Home directory must be a string");
162
- if ((!args.forceInit && fs.existsSync(homedir))) return;
172
+ if ((!args.force && fs.existsSync(homedir))) return;
163
173
  console.error("[tune-sdk] initialize " + homedir);
164
174
  fs.mkdirSync(homedir, {
165
175
  recursive: true
@@ -292,6 +302,17 @@ async function run(args) {
292
302
  return (!args.silient ? console.log(res) : undefined);
293
303
  }
294
304
  run;
305
+
306
+ function flatten(array) {
307
+ return array.reduce((memo, item) => {
308
+ if (Array.isArray(item)) {
309
+ return memo.concat(item)
310
+ }
311
+ memo.push(item)
312
+ return memo
313
+ }, [])
314
+ }
315
+
295
316
  async function initContext(args) {
296
317
  var dirs, pwd, ctx, dir, ctxName, ext, module, m, _i, _ref, _len, _i0, _ref0, _len0;
297
318
  var dirs;
@@ -334,7 +355,7 @@ async function initContext(args) {
334
355
  if ((typeof module === "function")) {
335
356
  ctx.use(module);
336
357
  } else if (Array.isArray(module)) {
337
- _ref0 = module;
358
+ _ref0 = module.flat(Infinity);
338
359
  for (_i0 = 0, _len0 = _ref0.length; _i0 < _len0; ++_i0) {
339
360
  m = _ref0[_i0];
340
361
  if ((typeof m === "function")) {
@@ -365,9 +386,26 @@ async function main() {
365
386
  showHelp();
366
387
  process.exit(0);
367
388
  }
389
+ if (args.version) {
390
+ try {
391
+ var pkg = require(path.resolve(__dirname, "../package.json"));
392
+ console.log(pkg.version || "0.0.0");
393
+ } catch (e) {
394
+ console.log("0.0.0");
395
+ }
396
+ process.exit(0);
397
+ }
368
398
  validateArgs(args);
369
- await initConfig(args);
370
- _ref = args.rpc ? await runRpc(args) : await run(args);
399
+ if (args.cmd === "rpc") {
400
+ await initConfig(args); // ensure config exists if needed
401
+ _ref = await runRpc(args);
402
+ } else if (args.cmd === "init") {
403
+ await initConfig(args);
404
+ _ref = null;
405
+ } else {
406
+ await initConfig(args); // auto-init if missing
407
+ _ref = await run(args);
408
+ }
371
409
  } catch (e) {
372
410
  console.error(e);
373
411
  _ref = process.exit(1);
@@ -398,4 +436,4 @@ tpl;
398
436
  exports.parseArgs = parseArgs;
399
437
  exports.rpc = rpc;
400
438
  exports.main = main;
401
- exports.run = run;
439
+ exports.run = run;
package/dist/tune.js CHANGED
@@ -1159,10 +1159,18 @@ Context.prototype.exec = (async function(name, args) {
1159
1159
  return _ref;
1160
1160
  });
1161
1161
  Context.prototype.write = (async function(name, args) {
1162
- var ws;
1163
- ws = ws || this.ws;
1162
+ var ws, cur, res, _res, _ref;
1163
+ ws = (this.ws || [])
1164
+ .slice();
1164
1165
  if (!ws.length) return;
1165
- return ws[0](name, args, this, this.write.bind(this, name, args, ws.slice(1)));
1166
+ cur = ws.shift();
1167
+ _res = [];
1168
+ while (cur) {
1169
+ res = await cur.call(this, name, args, this);
1170
+ if (res) break;
1171
+ if (typeof(_ref = (cur = ws.shift())) !== 'undefined') _res.push(_ref);
1172
+ }
1173
+ return _res;
1166
1174
  });
1167
1175
 
1168
1176
  function envmd(md) {
@@ -1221,18 +1229,20 @@ function makeContext() {
1221
1229
  var ctx;
1222
1230
  ctx = new Context();
1223
1231
  if (!args[0]) return ctx;
1224
- args.forEach((function(md) {
1225
- var _ref;
1226
- if ((typeof md === "function")) {
1227
- _ref = ctx.use(md);
1228
- } else if (typeof md === "object") {
1229
- _ref = ctx.use(envmd(md));
1230
- } else {
1231
- _ref = undefined;
1232
- throw new TuneError("context middlewares might be either function or an object");
1233
- }
1234
- return _ref;
1235
- }));
1232
+ args
1233
+ .flat(Infinity)
1234
+ .forEach((function(md) {
1235
+ var _ref;
1236
+ if ((typeof md === "function")) {
1237
+ _ref = ctx.use(md);
1238
+ } else if (typeof md === "object") {
1239
+ _ref = ctx.use(envmd(md));
1240
+ } else {
1241
+ _ref = undefined;
1242
+ throw new TuneError("context middlewares might be either function or an object");
1243
+ }
1244
+ return _ref;
1245
+ }));
1236
1246
  return ctx;
1237
1247
  }
1238
1248
  makeContext;
@@ -1853,7 +1863,7 @@ async function payload2http(payload, ctx) {
1853
1863
  stack = TuneError.ctx2stack(ctx);
1854
1864
  var lastStack;
1855
1865
  lastStack = stack.pop();
1856
- throw new TuneError("llm file not found", (((typeof lastStack !== "undefined") && (lastStack !== null) && !Number.isNaN(lastStack) && (typeof lastStack.filename !== "undefined") && (lastStack.filename !== null) && !Number.isNaN(lastStack.filename)) ? lastStack.filename : undefined), (((typeof lastStack !== "undefined") && (lastStack !== null) && !Number.isNaN(lastStack) && (typeof lastStack.row !== "undefined") && (lastStack.row !== null) && !Number.isNaN(lastStack.row)) ? lastStack.row : undefined), (((typeof lastStack !== "undefined") && (lastStack !== null) && !Number.isNaN(lastStack) && (typeof lastStack.col !== "undefined") && (lastStack.col !== null) && !Number.isNaN(lastStack.col)) ? lastStack.col : undefined), stack);
1866
+ throw new TuneError("llm file not found, check your env or ~/.tune/.env for provider api keys, like OPENAI_KEY, ANTHROPIC_KEY etc ", (((typeof lastStack !== "undefined") && (lastStack !== null) && !Number.isNaN(lastStack) && (typeof lastStack.filename !== "undefined") && (lastStack.filename !== null) && !Number.isNaN(lastStack.filename)) ? lastStack.filename : undefined), (((typeof lastStack !== "undefined") && (lastStack !== null) && !Number.isNaN(lastStack) && (typeof lastStack.row !== "undefined") && (lastStack.row !== null) && !Number.isNaN(lastStack.row)) ? lastStack.row : undefined), (((typeof lastStack !== "undefined") && (lastStack !== null) && !Number.isNaN(lastStack) && (typeof lastStack.col !== "undefined") && (lastStack.col !== null) && !Number.isNaN(lastStack.col)) ? lastStack.col : undefined), stack);
1857
1867
  }
1858
1868
  var body;
1859
1869
  body = Object.assign({}, payload);
@@ -2076,7 +2086,8 @@ function text2run(text, ctx, opts) {
2076
2086
  tc = delta.tool_calls[0];
2077
2087
  tcIdx = tc.index || 0;
2078
2088
  msg.tool_calls[tcIdx] = msg.tool_calls[tcIdx] || tc;
2079
- msg.tool_calls[tcIdx].function.arguments += tc.function.arguments;
2089
+ msg.tool_calls[tcIdx].function.arguments = msg.tool_calls[tcIdx].function.arguments || "";
2090
+ msg.tool_calls[tcIdx].function.arguments += (tc.function.arguments || "");
2080
2091
  }
2081
2092
  return msg;
2082
2093
  }), {
@@ -2115,7 +2126,7 @@ function text2run(text, ctx, opts) {
2115
2126
  }
2116
2127
  text2run;
2117
2128
  async function file2run(args, params, ctx) {
2118
- var lctx, text, stop, node, response, res, r, chunk, itergeDPeG9, _ref;
2129
+ var lctx, text, stop, node, response, res, r, chunk, itergzwE3tN, _ref;
2119
2130
  var lctx;
2120
2131
  lctx = ctx.clone();
2121
2132
  lctx.ms.unshift(envmd(params));
@@ -2181,7 +2192,7 @@ async function file2run(args, params, ctx) {
2181
2192
  stream: true
2182
2193
  });
2183
2194
  chunk = {};
2184
- itergeDPeG9 = new AsyncIter();
2195
+ itergzwE3tN = new AsyncIter();
2185
2196
  (async function($lastRes) {
2186
2197
  var _ref;
2187
2198
  try {
@@ -2190,20 +2201,20 @@ async function file2run(args, params, ctx) {
2190
2201
  res = (chunk.value || "");
2191
2202
  if (chunk.done) await save();
2192
2203
  $lastRes = transformOutput(res) || $lastRes;
2193
- itergeDPeG9.result = {
2204
+ itergzwE3tN.result = {
2194
2205
  value: $lastRes
2195
2206
  }
2196
2207
  }
2197
- _ref = itergeDPeG9.result = {
2208
+ _ref = itergzwE3tN.result = {
2198
2209
  value: $lastRes,
2199
2210
  done: true
2200
2211
  }
2201
2212
  } catch (e) {
2202
- _ref = (itergeDPeG9.err = e);
2213
+ _ref = (itergzwE3tN.err = e);
2203
2214
  }
2204
2215
  return _ref;
2205
2216
  })();
2206
- _ref = itergeDPeG9;
2217
+ _ref = itergzwE3tN;
2207
2218
  }
2208
2219
  return _ref;
2209
2220
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tune-sdk",
3
- "version": "0.2.2",
3
+ "version": "0.2.4",
4
4
  "description": "tune - LLM chat in text file",
5
5
  "main": "dist/tune.js",
6
6
  "module": "dist/tune.mjs",
@@ -30,5 +30,8 @@
30
30
  "type": "git",
31
31
  "url": "https://github.com/iovdin/tune"
32
32
  },
33
- "license": "MIT"
33
+ "license": "MIT",
34
+ "dependencies": {
35
+ "tune-sdk": "^0.2.3"
36
+ }
34
37
  }
@@ -1,7 +0,0 @@
1
- {
2
- "dependencies": {
3
- "tune-sdk": "latest",
4
- "tune-models": "latest",
5
- "tune-toolset": "latest"
6
- }
7
- }