shipr-agent 0.1.4 → 0.1.6

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.
Files changed (2) hide show
  1. package/dist/index.js +348 -127
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -101234,17 +101234,135 @@ function getAllModelsForProvider(providerName) {
101234
101234
  const p = factory();
101235
101235
  return p.getModels().map((m) => m.id);
101236
101236
  }
101237
- function showCommandHints(partial2) {
101238
- const query = partial2.toLowerCase();
101239
- const matches = COMMANDS.filter((c) => c.name.startsWith(query));
101240
- if (matches.length === 0)
101241
- return;
101242
- console.log("");
101243
- for (const cmd of matches) {
101244
- const args = cmd.args ? ` ${DIM2(cmd.args)}` : "";
101245
- console.log(` ${BRAND8(cmd.name)}${args} ${DIM2("—")} ${DIM2(cmd.description)}`);
101246
- }
101247
- console.log("");
101237
+ function commandPicker(initial) {
101238
+ return new Promise((resolve) => {
101239
+ let query = initial;
101240
+ let selectedIdx = 0;
101241
+ let scrollTop = 0;
101242
+ let drawnLines = 0;
101243
+ const stdin = process.stdin;
101244
+ const wasRaw = stdin.isRaw;
101245
+ function filtered() {
101246
+ const q = query.slice(1).toLowerCase();
101247
+ if (!q)
101248
+ return COMMANDS;
101249
+ return COMMANDS.filter((c) => c.name.slice(1).startsWith(q) || c.name.slice(1).includes(q));
101250
+ }
101251
+ function clamp() {
101252
+ const matches = filtered();
101253
+ if (selectedIdx >= matches.length)
101254
+ selectedIdx = Math.max(0, matches.length - 1);
101255
+ if (selectedIdx < scrollTop)
101256
+ scrollTop = selectedIdx;
101257
+ if (selectedIdx >= scrollTop + PICKER_VISIBLE)
101258
+ scrollTop = selectedIdx - PICKER_VISIBLE + 1;
101259
+ }
101260
+ function draw() {
101261
+ const matches = filtered();
101262
+ const visible = matches.slice(scrollTop, scrollTop + PICKER_VISIBLE);
101263
+ const total = matches.length;
101264
+ const lines = [];
101265
+ lines.push(` ${DIM2(">")} ${source_default.bold(query)}`);
101266
+ lines.push("");
101267
+ if (visible.length === 0) {
101268
+ lines.push(DIM2(" No matching commands."));
101269
+ } else {
101270
+ for (let i2 = 0;i2 < visible.length; i2++) {
101271
+ const item = visible[i2];
101272
+ const gi = scrollTop + i2;
101273
+ const sel = gi === selectedIdx;
101274
+ const pointer = sel ? BRAND8("❯ ") : " ";
101275
+ const name = sel ? BRAND_BOLD(item.name.padEnd(14)) : BRAND8(item.name.padEnd(14));
101276
+ const desc = sel ? item.description : DIM2(item.description);
101277
+ lines.push(` ${pointer}${name} ${desc}`);
101278
+ }
101279
+ }
101280
+ lines.push("");
101281
+ const from = total === 0 ? 0 : scrollTop + 1;
101282
+ const to = Math.min(scrollTop + PICKER_VISIBLE, total);
101283
+ lines.push(DIM2(` Use ↑↓ to navigate, Tab/Enter to select, Esc to cancel • Showing ${from}–${to} of ${total}`));
101284
+ if (drawnLines > 0) {
101285
+ process.stdout.write(`\x1B[${drawnLines}A\x1B[0J`);
101286
+ }
101287
+ for (const line of lines) {
101288
+ process.stdout.write(line + `
101289
+ `);
101290
+ }
101291
+ drawnLines = lines.length;
101292
+ }
101293
+ function cleanup() {
101294
+ stdin.removeListener("data", onKey);
101295
+ if (wasRaw !== undefined)
101296
+ stdin.setRawMode(wasRaw);
101297
+ if (!stdin.isRaw)
101298
+ stdin.resume();
101299
+ }
101300
+ function cancel() {
101301
+ cleanup();
101302
+ if (drawnLines > 0)
101303
+ process.stdout.write(`\x1B[${drawnLines}A\x1B[0J`);
101304
+ resolve(null);
101305
+ }
101306
+ function select() {
101307
+ const matches = filtered();
101308
+ const chosen = matches[selectedIdx]?.name ?? null;
101309
+ cleanup();
101310
+ if (drawnLines > 0)
101311
+ process.stdout.write(`\x1B[${drawnLines}A\x1B[0J`);
101312
+ resolve(chosen);
101313
+ }
101314
+ function onKey(data) {
101315
+ const key = data.toString();
101316
+ if (key === "\x1B" || key === "\x03") {
101317
+ cancel();
101318
+ return;
101319
+ }
101320
+ if (key === "\r" || key === `
101321
+ ` || key === "\t") {
101322
+ select();
101323
+ return;
101324
+ }
101325
+ if (key === "" || key === "\b") {
101326
+ if (query.length > 1) {
101327
+ query = query.slice(0, -1);
101328
+ selectedIdx = 0;
101329
+ scrollTop = 0;
101330
+ draw();
101331
+ } else {
101332
+ cancel();
101333
+ }
101334
+ return;
101335
+ }
101336
+ if (key === "\x1B[A") {
101337
+ if (selectedIdx > 0) {
101338
+ selectedIdx--;
101339
+ clamp();
101340
+ draw();
101341
+ }
101342
+ return;
101343
+ }
101344
+ if (key === "\x1B[B") {
101345
+ if (selectedIdx < filtered().length - 1) {
101346
+ selectedIdx++;
101347
+ clamp();
101348
+ draw();
101349
+ }
101350
+ return;
101351
+ }
101352
+ if (key.length === 1 && key >= " ") {
101353
+ query += key;
101354
+ selectedIdx = 0;
101355
+ scrollTop = 0;
101356
+ draw();
101357
+ return;
101358
+ }
101359
+ }
101360
+ stdin.setRawMode(true);
101361
+ stdin.resume();
101362
+ stdin.on("data", onKey);
101363
+ drawnLines = 0;
101364
+ draw();
101365
+ });
101248
101366
  }
101249
101367
  async function handleRun(args, state2) {
101250
101368
  if (!args.trim()) {
@@ -101309,70 +101427,156 @@ async function handleSimulate(args, state2) {
101309
101427
  process.stdout.write(PROMPT);
101310
101428
  });
101311
101429
  }
101312
- function handleModel(args, state2) {
101430
+ function interactivePick(title, items) {
101431
+ return new Promise((resolve) => {
101432
+ let cursor = items.findIndex((i2) => i2.active) ?? 0;
101433
+ if (cursor < 0)
101434
+ cursor = 0;
101435
+ const stdin = process.stdin;
101436
+ const wasRaw = stdin.isRaw;
101437
+ function render2() {
101438
+ const totalLines = items.length + 4;
101439
+ process.stdout.write(`\x1B[${totalLines}A\x1B[0J`);
101440
+ draw();
101441
+ }
101442
+ function draw() {
101443
+ console.log("");
101444
+ console.log(` ${BRAND_BOLD(title)} ${DIM2("↑↓ navigate Enter select q back")}`);
101445
+ console.log("");
101446
+ let lastGroup = "";
101447
+ for (let i2 = 0;i2 < items.length; i2++) {
101448
+ const item = items[i2];
101449
+ if (item.group && item.group !== lastGroup) {
101450
+ if (lastGroup)
101451
+ console.log("");
101452
+ lastGroup = item.group;
101453
+ }
101454
+ const selected = i2 === cursor;
101455
+ const pointer = selected ? BRAND8("❯ ") : " ";
101456
+ const label = selected ? source_default.bold(item.label) : item.label;
101457
+ const hint = item.hint ? ` ${DIM2(item.hint)}` : "";
101458
+ const activeTag = item.active ? source_default.green(" ◀") : "";
101459
+ console.log(` ${pointer}${label}${hint}${activeTag}`);
101460
+ }
101461
+ console.log("");
101462
+ }
101463
+ function cleanup() {
101464
+ stdin.removeListener("data", onKey);
101465
+ stdin.setRawMode(wasRaw ?? false);
101466
+ if (!stdin.isRaw) {
101467
+ stdin.resume();
101468
+ }
101469
+ }
101470
+ function onKey(data) {
101471
+ const key = data.toString();
101472
+ if (key === "q" || key === "\x1B" || key === "\x03") {
101473
+ cleanup();
101474
+ console.log(DIM2(" Cancelled."));
101475
+ resolve(null);
101476
+ return;
101477
+ }
101478
+ if (key === "\r" || key === `
101479
+ `) {
101480
+ cleanup();
101481
+ const selected = items[cursor];
101482
+ console.log(source_default.green(" ✓"), `Selected: ${source_default.bold(selected.label)}`);
101483
+ resolve(selected.value);
101484
+ return;
101485
+ }
101486
+ if (key === "\x1B[A" || key === "k") {
101487
+ cursor = cursor > 0 ? cursor - 1 : items.length - 1;
101488
+ render2();
101489
+ return;
101490
+ }
101491
+ if (key === "\x1B[B" || key === "j") {
101492
+ cursor = cursor < items.length - 1 ? cursor + 1 : 0;
101493
+ render2();
101494
+ return;
101495
+ }
101496
+ }
101497
+ stdin.setRawMode(true);
101498
+ stdin.resume();
101499
+ stdin.on("data", onKey);
101500
+ draw();
101501
+ });
101502
+ }
101503
+ async function handleModel(args, state2, rl) {
101313
101504
  const parts = args.trim().split(/\s+/).filter(Boolean);
101314
- if (parts.length === 0) {
101315
- console.log("");
101316
- console.log(` ${BRAND8("Provider:")} ${source_default.bold(state2.activeProvider)}`);
101317
- console.log(` ${BRAND8("Model:")} ${source_default.bold(state2.activeModel)}`);
101318
- console.log("");
101319
- console.log(DIM2(" Usage:"));
101320
- console.log(DIM2(" /model <provider> Switch provider (uses default model)"));
101321
- console.log(DIM2(" /model <provider> <model> Switch provider and model"));
101322
- console.log("");
101323
- console.log(DIM2(" Available:"));
101324
- const providers = listProviders();
101325
- for (const name of providers) {
101326
- const factory = PROVIDER_MAP[name];
101327
- if (!factory)
101328
- continue;
101329
- const p = factory();
101330
- const models = p.getModels();
101331
- const active = name === state2.activeProvider ? BRAND8(" ◀ active") : "";
101332
- console.log(` ${source_default.bold(name)}${active}`);
101333
- for (const m of models) {
101334
- const isActive = name === state2.activeProvider && m.id === state2.activeModel;
101335
- const marker = isActive ? source_default.green(" ● ") : " ";
101336
- const ctx = `${(m.contextWindow / 1000).toFixed(0)}k`;
101337
- console.log(`${marker}${m.id} ${DIM2(ctx)}`);
101505
+ if (parts.length >= 1) {
101506
+ const providerName = parts[0];
101507
+ const allProviders = listProviders();
101508
+ const match = allProviders.find((p) => p === providerName) ?? allProviders.find((p) => p.startsWith(providerName));
101509
+ if (!match) {
101510
+ console.log(source_default.red(" "), `Unknown provider: ${providerName}`);
101511
+ console.log(DIM2(` Available: ${allProviders.join(", ")}`));
101512
+ return;
101513
+ }
101514
+ state2.activeProvider = match;
101515
+ if (parts.length >= 2) {
101516
+ const modelId = parts[1];
101517
+ const available = getAllModelsForProvider(match);
101518
+ const exactMatch = available.find((m) => m === modelId);
101519
+ const fuzzyMatch = available.find((m) => m.includes(modelId));
101520
+ state2.activeModel = exactMatch ?? fuzzyMatch ?? getDefaultModel(match);
101521
+ if (!exactMatch && !fuzzyMatch) {
101522
+ console.log(source_default.yellow(""), `Model "${modelId}" not found. Using default.`);
101338
101523
  }
101524
+ } else {
101525
+ state2.activeModel = getDefaultModel(match);
101339
101526
  }
101340
- console.log("");
101527
+ console.log(source_default.green(""), `Provider: ${source_default.bold(state2.activeProvider)} Model: ${source_default.bold(state2.activeModel)}`);
101341
101528
  return;
101342
101529
  }
101343
- const providerName = parts[0];
101344
- const allProviders = listProviders();
101345
- if (!allProviders.includes(providerName)) {
101346
- const match = allProviders.find((p) => p.startsWith(providerName));
101347
- if (match) {
101348
- state2.activeProvider = match;
101349
- state2.activeModel = getDefaultModel(match);
101350
- console.log(source_default.green(" ✓"), `Provider: ${source_default.bold(match)} Model: ${source_default.bold(state2.activeModel)}`);
101351
- return;
101352
- }
101353
- console.log(source_default.red(" ✗"), `Unknown provider: ${providerName}`);
101354
- console.log(DIM2(` Available: ${allProviders.join(", ")}`));
101530
+ rl.pause();
101531
+ const providers = listProviders();
101532
+ const providerItems = providers.map((name) => {
101533
+ const factory2 = PROVIDER_MAP[name];
101534
+ const models2 = factory2 ? factory2().getModels() : [];
101535
+ return {
101536
+ label: name,
101537
+ value: name,
101538
+ hint: `${models2.length} model${models2.length !== 1 ? "s" : ""}`,
101539
+ active: name === state2.activeProvider
101540
+ };
101541
+ });
101542
+ const chosenProvider = await interactivePick("Select Provider", providerItems);
101543
+ if (!chosenProvider) {
101544
+ rl.resume();
101355
101545
  return;
101356
101546
  }
101357
- state2.activeProvider = providerName;
101358
- if (parts.length >= 2) {
101359
- const modelId = parts[1];
101360
- const available = getAllModelsForProvider(providerName);
101361
- const exactMatch = available.find((m) => m === modelId);
101362
- const fuzzyMatch = available.find((m) => m.includes(modelId));
101363
- if (exactMatch) {
101364
- state2.activeModel = exactMatch;
101365
- } else if (fuzzyMatch) {
101366
- state2.activeModel = fuzzyMatch;
101367
- } else {
101368
- console.log(source_default.yellow(" ⚠"), `Model "${modelId}" not found for ${providerName}. Using default.`);
101369
- console.log(DIM2(` Available: ${available.join(", ")}`));
101370
- state2.activeModel = getDefaultModel(providerName);
101371
- }
101372
- } else {
101373
- state2.activeModel = getDefaultModel(providerName);
101547
+ state2.activeProvider = chosenProvider;
101548
+ const models = getAllModelsForProvider(chosenProvider);
101549
+ const defaultModel = getDefaultModel(chosenProvider);
101550
+ if (models.length <= 1) {
101551
+ state2.activeModel = models[0] ?? defaultModel;
101552
+ console.log(source_default.green(" ✓"), `Provider: ${source_default.bold(state2.activeProvider)} Model: ${source_default.bold(state2.activeModel)}`);
101553
+ rl.resume();
101554
+ return;
101555
+ }
101556
+ const factory = PROVIDER_MAP[chosenProvider];
101557
+ const providerInstance = factory ? factory() : null;
101558
+ const modelDefs = providerInstance ? providerInstance.getModels() : [];
101559
+ const modelItems = models.map((id) => {
101560
+ const def = modelDefs.find((m) => m.id === id);
101561
+ const ctx = def ? `${(def.contextWindow / 1000).toFixed(0)}k` : "";
101562
+ const price = def && def.inputPricePer1M > 0 ? `$${def.inputPricePer1M.toFixed(2)}/$${def.outputPricePer1M.toFixed(2)}` : def ? "free" : "";
101563
+ return {
101564
+ label: id,
101565
+ value: id,
101566
+ hint: [ctx, price].filter(Boolean).join(" "),
101567
+ active: id === state2.activeModel && chosenProvider === state2.activeProvider
101568
+ };
101569
+ });
101570
+ const chosenModel = await interactivePick("Select Model", modelItems);
101571
+ if (!chosenModel) {
101572
+ state2.activeModel = defaultModel;
101573
+ console.log(source_default.green(" ✓"), `Provider: ${source_default.bold(state2.activeProvider)} Model: ${source_default.bold(state2.activeModel)}`);
101574
+ rl.resume();
101575
+ return;
101374
101576
  }
101577
+ state2.activeModel = chosenModel;
101375
101578
  console.log(source_default.green(" ✓"), `Provider: ${source_default.bold(state2.activeProvider)} Model: ${source_default.bold(state2.activeModel)}`);
101579
+ rl.resume();
101376
101580
  }
101377
101581
  async function handleStatus() {
101378
101582
  const { readdir: readdir8, readFile: readFile21 } = await import("node:fs/promises");
@@ -101690,7 +101894,7 @@ async function startRepl() {
101690
101894
  }
101691
101895
  console.log("");
101692
101896
  console.log(DIM2(" Type /help for commands, /run <mission> to start."));
101693
- console.log(DIM2(` Press Tab to autocomplete commands.
101897
+ console.log(DIM2(` Type / to browse all commands interactively.
101694
101898
  `));
101695
101899
  const state2 = {
101696
101900
  activeProvider,
@@ -101708,17 +101912,62 @@ async function startRepl() {
101708
101912
  historySize: 200
101709
101913
  });
101710
101914
  rl.prompt();
101915
+ const NO_ARG_COMMANDS = new Set(["/resume", "/status", "/cost", "/providers", "/missions", "/help", "/exit", "/quit", "/q", "/model"]);
101916
+ async function dispatchCommand(cmd, args) {
101917
+ switch (cmd) {
101918
+ case "/run":
101919
+ await handleRun(args, state2);
101920
+ break;
101921
+ case "/simulate":
101922
+ await handleSimulate(args, state2);
101923
+ break;
101924
+ case "/resume":
101925
+ await handleResume(state2);
101926
+ break;
101927
+ case "/status":
101928
+ await handleStatus();
101929
+ break;
101930
+ case "/steer":
101931
+ await handleSteer(args);
101932
+ break;
101933
+ case "/cost":
101934
+ await handleCost();
101935
+ break;
101936
+ case "/model":
101937
+ await handleModel(args, state2, rl);
101938
+ break;
101939
+ case "/auth":
101940
+ await handleAuth(args);
101941
+ break;
101942
+ case "/providers":
101943
+ handleProviders();
101944
+ break;
101945
+ case "/missions":
101946
+ await handleMissions();
101947
+ break;
101948
+ case "/help":
101949
+ handleHelp();
101950
+ break;
101951
+ case "/exit":
101952
+ case "/quit":
101953
+ case "/q":
101954
+ if (state2.missionRunning) {
101955
+ console.log(source_default.yellow(" ⚠"), "A mission is still running. It will be interrupted.");
101956
+ }
101957
+ console.log(DIM2(" Goodbye."));
101958
+ rl.close();
101959
+ process.exit(0);
101960
+ break;
101961
+ default:
101962
+ console.log(DIM2(` Unknown command: ${cmd}. Type ${BRAND8("/")} to see all commands.`));
101963
+ }
101964
+ }
101711
101965
  rl.on("line", (line) => {
101712
101966
  const trimmed2 = line.trim();
101713
101967
  if (!trimmed2) {
101714
101968
  rl.prompt();
101715
101969
  return;
101716
101970
  }
101717
- if (trimmed2 === "/") {
101718
- showCommandHints("/");
101719
- rl.prompt();
101720
- return;
101721
- }
101722
101971
  if (!trimmed2.startsWith("/")) {
101723
101972
  console.log(DIM2(` hint: use ${BRAND8("/run")} <your mission> to start. Type ${BRAND8("/")} to see all commands.`));
101724
101973
  rl.prompt();
@@ -101727,61 +101976,33 @@ async function startRepl() {
101727
101976
  const spaceIdx = trimmed2.indexOf(" ");
101728
101977
  const cmd = spaceIdx === -1 ? trimmed2 : trimmed2.slice(0, spaceIdx);
101729
101978
  const args = spaceIdx === -1 ? "" : trimmed2.slice(spaceIdx + 1);
101730
- if (spaceIdx === -1 && !COMMAND_NAMES.includes(cmd)) {
101731
- showCommandHints(cmd);
101732
- rl.prompt();
101979
+ if (trimmed2 === "/" || spaceIdx === -1 && !COMMAND_NAMES.includes(cmd)) {
101980
+ rl.pause();
101981
+ let skipFinalPrompt = false;
101982
+ commandPicker(trimmed2).then(async (chosen) => {
101983
+ if (chosen) {
101984
+ if (NO_ARG_COMMANDS.has(chosen)) {
101985
+ await dispatchCommand(chosen, "");
101986
+ rl.resume();
101987
+ } else {
101988
+ skipFinalPrompt = true;
101989
+ rl.resume();
101990
+ rl.prompt();
101991
+ rl.write(chosen + " ");
101992
+ }
101993
+ } else {
101994
+ rl.resume();
101995
+ }
101996
+ }).catch((err) => {
101997
+ rl.resume();
101998
+ console.log(source_default.red(" ✗"), err.message);
101999
+ }).finally(() => {
102000
+ if (!skipFinalPrompt)
102001
+ rl.prompt();
102002
+ });
101733
102003
  return;
101734
102004
  }
101735
- const dispatch = async () => {
101736
- switch (cmd) {
101737
- case "/run":
101738
- await handleRun(args, state2);
101739
- break;
101740
- case "/simulate":
101741
- await handleSimulate(args, state2);
101742
- break;
101743
- case "/resume":
101744
- await handleResume(state2);
101745
- break;
101746
- case "/status":
101747
- await handleStatus();
101748
- break;
101749
- case "/steer":
101750
- await handleSteer(args);
101751
- break;
101752
- case "/cost":
101753
- await handleCost();
101754
- break;
101755
- case "/model":
101756
- handleModel(args, state2);
101757
- break;
101758
- case "/auth":
101759
- await handleAuth(args);
101760
- break;
101761
- case "/providers":
101762
- handleProviders();
101763
- break;
101764
- case "/missions":
101765
- await handleMissions();
101766
- break;
101767
- case "/help":
101768
- handleHelp();
101769
- break;
101770
- case "/exit":
101771
- case "/quit":
101772
- case "/q":
101773
- if (state2.missionRunning) {
101774
- console.log(source_default.yellow(" ⚠"), "A mission is still running. It will be interrupted.");
101775
- }
101776
- console.log(DIM2(" Goodbye."));
101777
- rl.close();
101778
- process.exit(0);
101779
- break;
101780
- default:
101781
- console.log(DIM2(` Unknown command: ${cmd}. Type ${BRAND8("/")} to see all commands.`));
101782
- }
101783
- };
101784
- dispatch().catch((err) => {
102005
+ dispatchCommand(cmd, args).catch((err) => {
101785
102006
  console.log(source_default.red(" ✗"), err.message);
101786
102007
  }).finally(() => {
101787
102008
  rl.prompt();
@@ -101793,7 +102014,7 @@ async function startRepl() {
101793
102014
  process.exit(0);
101794
102015
  });
101795
102016
  }
101796
- var BRAND8, BRAND_BOLD, DIM2, PROMPT, COMMANDS, COMMAND_NAMES, BANNER;
102017
+ var BRAND8, BRAND_BOLD, DIM2, PROMPT, COMMANDS, COMMAND_NAMES, BANNER, PICKER_VISIBLE = 6;
101797
102018
  var init_repl = __esm(async () => {
101798
102019
  init_source();
101799
102020
  init_keychain();
@@ -103287,7 +103508,7 @@ function getVersion() {
103287
103508
  const pkg = require2("../package.json");
103288
103509
  return pkg.version;
103289
103510
  } catch {
103290
- return "0.1.4";
103511
+ return "0.1.6";
103291
103512
  }
103292
103513
  }
103293
103514
  var hasArgs = process.argv.length > 2;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shipr-agent",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "Fully autonomous terminal-based coding agent",
5
5
  "type": "module",
6
6
  "bin": {