sideby 0.16.0 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (77) hide show
  1. package/README.md +5 -3
  2. package/dist/build.json +1 -1
  3. package/dist/src/cli/commands.d.ts +16 -0
  4. package/dist/src/cli/commands.js +460 -5
  5. package/dist/src/cli/json-schemas.js +115 -0
  6. package/dist/src/cli/main.js +93 -3
  7. package/dist/src/core/ask.d.ts +150 -0
  8. package/dist/src/core/ask.js +365 -0
  9. package/dist/src/core/attachments.d.ts +50 -0
  10. package/dist/src/core/attachments.js +442 -0
  11. package/dist/src/core/autostart.d.ts +88 -0
  12. package/dist/src/core/autostart.js +256 -0
  13. package/dist/src/core/config.d.ts +97 -0
  14. package/dist/src/core/config.js +278 -10
  15. package/dist/src/core/launch.d.ts +15 -4
  16. package/dist/src/core/launch.js +27 -23
  17. package/dist/src/core/local-build.d.ts +17 -0
  18. package/dist/src/core/local-build.js +39 -3
  19. package/dist/src/core/probe.d.ts +78 -0
  20. package/dist/src/core/probe.js +127 -0
  21. package/dist/src/core/same-login.d.ts +9 -4
  22. package/dist/src/core/same-login.js +9 -4
  23. package/dist/src/core/secret-file.d.ts +26 -0
  24. package/dist/src/core/secret-file.js +126 -7
  25. package/dist/src/core/terminal.d.ts +93 -0
  26. package/dist/src/core/terminal.js +371 -0
  27. package/dist/src/core/update.d.ts +17 -0
  28. package/dist/src/core/update.js +63 -1
  29. package/dist/src/families/claude/index.js +3 -0
  30. package/dist/src/families/claude/quota-cache.d.ts +24 -7
  31. package/dist/src/families/claude/quota-cache.js +72 -21
  32. package/dist/src/families/codex/index.js +7 -0
  33. package/dist/src/families/codex/login-key.d.ts +8 -0
  34. package/dist/src/families/codex/login-key.js +33 -0
  35. package/dist/src/families/grok/index.js +3 -0
  36. package/dist/src/families/login-keys.d.ts +6 -0
  37. package/dist/src/families/login-keys.js +6 -0
  38. package/dist/src/families/pi/index.js +4 -0
  39. package/dist/src/panel/background.js +3 -2
  40. package/dist/src/panel/handler.d.ts +29 -2
  41. package/dist/src/panel/handler.js +396 -9
  42. package/dist/src/panel/i18n.d.ts +175 -2
  43. package/dist/src/panel/i18n.js +381 -35
  44. package/dist/src/panel/page-logic.d.ts +214 -0
  45. package/dist/src/panel/page-logic.js +394 -0
  46. package/dist/src/panel/page-script.js +984 -134
  47. package/dist/src/panel/page-styles.js +69 -19
  48. package/dist/src/panel/page.d.ts +5 -0
  49. package/dist/src/panel/page.js +2 -0
  50. package/dist/src/panel/server.d.ts +16 -2
  51. package/dist/src/panel/server.js +41 -13
  52. package/dist/src/plugins/check.d.ts +47 -0
  53. package/dist/src/plugins/check.js +544 -0
  54. package/dist/src/plugins/scaffold.d.ts +21 -0
  55. package/dist/src/plugins/scaffold.js +123 -0
  56. package/dist/src/quota/statusline-tap.js +1 -1
  57. package/dist/src/runtime.d.ts +10 -0
  58. package/dist/src/runtime.js +47 -16
  59. package/dist/src/types.d.ts +19 -0
  60. package/package.json +2 -2
  61. package/schemas/ask.json +180 -0
  62. package/schemas/autostart.json +165 -0
  63. package/schemas/config-get.json +132 -0
  64. package/schemas/config-set.json +107 -0
  65. package/schemas/config.json +73 -6
  66. package/schemas/key.json +93 -0
  67. package/schemas/local.json +4 -0
  68. package/schemas/plugins-check.json +113 -0
  69. package/schemas/plugins-new.json +87 -0
  70. package/schemas/terminal.json +152 -0
  71. package/schemas/try.json +109 -0
  72. package/schemas/update.json +4 -0
  73. package/skills/sideby/SKILL.md +12 -6
  74. package/skills/sideby/references/accounts.md +1 -1
  75. package/skills/sideby/references/ai-tasks.md +116 -0
  76. package/skills/sideby/references/panel.md +14 -0
  77. package/skills/sideby/references/update.md +1 -1
@@ -351,6 +351,9 @@ export const OUTPUT_SCHEMAS = {
351
351
  blocked: Type.Optional(Type.String({ description: 'Why the switch cannot run, with what to do.' })),
352
352
  message: Type.Optional(Str),
353
353
  restartedPanel: Type.Optional(Type.Boolean()),
354
+ skillSynced: Type.Optional(Type.Boolean({
355
+ description: 'Back on npm, `sideby update --yes` brought the sideby skill to that version (true) or failed (false).',
356
+ })),
354
357
  }, { title: 'sideby local --json' }),
355
358
  list: Type.Object({
356
359
  schemaVersion: Version,
@@ -706,6 +709,9 @@ export const OUTPUT_SCHEMAS = {
706
709
  refreshedApp: Type.Optional(Type.Boolean({
707
710
  description: 'The desktop app (`sideby app install`) was installed and was written again by the new version.',
708
711
  })),
712
+ refreshedAutostart: Type.Optional(Type.Boolean({
713
+ description: 'A login item (`sideby autostart on`) was installed and now starts the new version (false: that failed). Absent without one.',
714
+ })),
709
715
  skill: Type.Optional(Type.String({ description: 'The command that installs the sideby skill by hand.' })),
710
716
  skillUpdate: Type.Optional(Type.Object({
711
717
  installed: Type.Array(Type.Union([Str, Type.Null()]), {
@@ -736,6 +742,115 @@ export const OUTPUT_SCHEMAS = {
736
742
  description: '`usage` for a malformed command line (exit 2), a reason such as `no-quota-source` or `alias-invalid`, or `error`.',
737
743
  }),
738
744
  }, { title: 'sideby error --json' }),
745
+ ask: Type.Object({
746
+ schemaVersion: Version,
747
+ task: Type.Union([Str, Type.Null()]),
748
+ account: Type.Union([Str, Type.Null()]),
749
+ terminal: Type.Union([Str, Type.Null()]),
750
+ opened: Type.Boolean(),
751
+ command: Type.Union([Str, Type.Null()]),
752
+ removedSecrets: Type.Boolean(),
753
+ reason: Type.Optional(Type.String({
754
+ enum: [
755
+ 'no-accounts',
756
+ 'none-signed-in',
757
+ 'all-full',
758
+ 'not-installed',
759
+ 'automation-denied',
760
+ 'npx-cache',
761
+ 'unsafe-path',
762
+ 'failed',
763
+ 'unsupported-platform',
764
+ ],
765
+ description: 'Why nothing opened (only when `opened` is false); `detail` says what to do next.',
766
+ })),
767
+ detail: Type.Optional(Type.String({ description: 'What to do next, for a person.' })),
768
+ prompt: Type.Optional(Str),
769
+ attachments: Type.Array(Type.Object({
770
+ path: Type.String({ description: 'Absolute path of the copy saved for the task.' }),
771
+ kind: Type.String({ enum: ['image', 'video'] }),
772
+ bytes: Type.Integer({ minimum: 0 }),
773
+ }), { description: 'Files given with `--attach`, in order; empty without any.' }),
774
+ }),
775
+ terminal: Type.Object({
776
+ schemaVersion: Version,
777
+ preferred: Type.Union([Str, Type.Null()]),
778
+ guess: Type.Object({ id: Str, source: Type.String({ enum: ['config', 'env', 'default'] }) }),
779
+ installed: Type.Array(Type.Object({ id: Str, appPath: Type.Optional(Str), executable: Type.Optional(Str) })),
780
+ terminals: Type.Array(Type.Object({ id: Str, name: Str, installed: Type.Boolean() })),
781
+ }),
782
+ key: Type.Object({
783
+ schemaVersion: Version,
784
+ account: Str,
785
+ var: Str,
786
+ written: Type.Literal(true),
787
+ path: Str,
788
+ }),
789
+ try: Type.Object({
790
+ schemaVersion: Version,
791
+ account: Str,
792
+ confirmed: Type.Boolean(),
793
+ command: Type.Optional(Type.Array(Str)),
794
+ ok: Type.Optional(Type.Boolean()),
795
+ exitCode: Type.Optional(Type.Union([Type.Integer(), Type.Null()])),
796
+ output: Type.Optional(Str),
797
+ timedOut: Type.Optional(Type.Boolean()),
798
+ }),
799
+ 'plugins-new': Type.Object({ schemaVersion: Version, dir: Str, files: Type.Array(Str) }),
800
+ 'plugins-check': Type.Object({
801
+ schemaVersion: Version,
802
+ ok: Type.Boolean(),
803
+ plugin: Type.Optional(Str),
804
+ families: Type.Array(Str),
805
+ problems: Type.Array(Type.Object({ code: Str, message: Str, fix: Str })),
806
+ }),
807
+ autostart: Type.Object({
808
+ schemaVersion: Version,
809
+ supported: Type.Boolean({
810
+ description: 'False on a system sideby cannot start at login yet; `reason` says so.',
811
+ }),
812
+ reason: Type.Optional(Str),
813
+ state: Type.Union(['off', 'on', 'stale', 'foreign'].map((k) => Type.Literal(k)), {
814
+ description: '`stale`: the Node or sideby it starts no longer exists; `foreign`: a file at that path that sideby did not write.',
815
+ }),
816
+ path: Str,
817
+ programArguments: Type.Optional(Type.Array(Str)),
818
+ current: Type.Boolean({ description: 'The login item starts this sideby with this Node.' }),
819
+ missing: Type.Optional(Type.Array(Str)),
820
+ action: Type.Optional(Type.Union([Type.Literal('on'), Type.Literal('off')])),
821
+ changed: Type.Optional(Type.Boolean()),
822
+ applied: Type.Optional(Type.Boolean()),
823
+ content: Type.Optional(Type.String({ description: 'The file `on` writes.' })),
824
+ commands: Type.Optional(Type.Array(Type.Array(Str))),
825
+ loaded: Type.Optional(Type.Boolean({ description: 'launchd took the change now.' })),
826
+ note: Type.Optional(Str),
827
+ }),
828
+ 'config-get': Type.Object({
829
+ schemaVersion: Version,
830
+ file: Type.Optional(Str),
831
+ keys: Type.Optional(Type.Array(Type.Object({
832
+ key: Type.String({ description: 'Dot path; `<…>` marks a part you fill in.' }),
833
+ meaning: Str,
834
+ takes: Str,
835
+ value: Type.Optional(Type.Unknown()),
836
+ entries: Type.Optional(Type.Array(Type.Object({ key: Str, value: Type.Unknown() }))),
837
+ }))),
838
+ key: Type.Optional(Str),
839
+ set: Type.Optional(Type.Boolean()),
840
+ value: Type.Optional(Type.Unknown()),
841
+ meaning: Type.Optional(Str),
842
+ takes: Type.Optional(Str),
843
+ }),
844
+ 'config-set': Type.Object({
845
+ schemaVersion: Version,
846
+ action: Type.Union([Type.Literal('set'), Type.Literal('unset')]),
847
+ key: Str,
848
+ file: Str,
849
+ changed: Type.Boolean(),
850
+ applied: Type.Boolean(),
851
+ before: Type.Optional(Type.Unknown()),
852
+ after: Type.Optional(Type.Unknown()),
853
+ }),
739
854
  config: ConfigSchema,
740
855
  };
741
856
  for (const [name, schema] of Object.entries(OUTPUT_SCHEMAS)) {
@@ -7,6 +7,10 @@ const MIN_NODE = [22, 18];
7
7
  const HELP = `sideby — run every AI coding account side by side
8
8
 
9
9
  Usage:
10
+ sideby ask [<text>] [--print] let your AI handle a sideby task
11
+ sideby terminal [--set <id>|--custom <t>|--unset] choose your terminal
12
+ sideby key <account> [--var <NAME>] enter an API key without echo
13
+ sideby try <account> [--yes] try the Host with a short paid request
10
14
  sideby list accounts (same as \`sideby list\`)
11
15
  sideby run <account> [-- args] start the host CLI for one account
12
16
  sideby new <family> <name>|--next [--api] [--alias <short>]
@@ -71,14 +75,24 @@ Usage:
71
75
  sideby shell-init [zsh|bash] --write
72
76
  rewrite the config's shellInitFile with them
73
77
  sideby plugins list loaded plugins and load errors
78
+ sideby plugins new <id> create a local Family Plugin skeleton
79
+ sideby plugins check <id|path> check a plugin and show fixes
74
80
  sideby update [--yes] update sideby to the newest version (shows the command first)
81
+ sideby autostart [on|off] [--yes] start the panel in the background at login (macOS; shows the
82
+ file first)
83
+ sideby config get [<key>] every setting with its value and meaning, or one setting
84
+ sideby config set <key> <value> [--yes]
85
+ change any setting (sideby config set panel.theme dark)
86
+ sideby config unset <key> [--yes] back to the default
75
87
  sideby local [on|off] [--yes] for sideby developers: switch the global sideby between the
76
88
  last local build (pnpm local) and the npm package
77
89
 
78
90
  Options:
79
91
  --json machine-readable output (list, new, next, alias, doctor, quota, tune, model, remote-control, handoff,
80
- families, plugins, app, desktop, browser, update, local)
81
- -h, --help show this help; \`sideby <command> --help\` for new, alias, login, doctor, families, desktop, browser
92
+ families, plugins, app, desktop, browser, update, local, ask, terminal, key, try,
93
+ autostart, config)
94
+ -h, --help show this help; \`sideby <command> --help\` for new, alias, login, doctor, families, desktop, browser,
95
+ autostart, config
82
96
  -v, --version show the version
83
97
 
84
98
  An <account> is <family>:<name> (claude:work) or just <name> when it is unique.
@@ -95,6 +109,11 @@ Updates: once a day sideby asks the npm registry for its newest version and says
95
109
  Turn it off with NO_UPDATE_NOTIFIER=1 or config "update": { "check": false }; "auto": true installs by itself.`;
96
110
  /** `sideby <command> --help` for the commands agents use to set up and repair accounts (spec §3.16). */
97
111
  const COMMAND_HELP = {
112
+ ask: 'sideby ask [<text>] [--task free|fix|quota|account|host|plugin|terminal|handoff] [--account <ref>] [--terminal <id>] [--attach <file>]... [--print] [--json]\nsideby ask --run <id> [--state-dir <dir>]\nOpen a task in your terminal. --attach adds an image (PNG, JPEG, GIF, WebP; up to 20 MB) or a video (MP4, MOV, WebM; up to 200 MB), at most 5. --print prints only the prompt. If opening fails, paste the command into a terminal.',
113
+ terminal: "sideby terminal [--set <id>|--custom '<program> … {cwd} … {cmd}'|--unset] [--json]\nList terminals or save your preference; --custom saves and selects your own launch command.",
114
+ key: 'sideby key <account> [--var <NAME>] [--json]\nEnter an API key without echo. Run in a terminal or use the Panel key box.',
115
+ try: 'sideby try <account> [--yes] [--json]\nSend a short request through the Host. This spends a few tokens. Exit 10 waits for --yes.',
116
+ plugins: 'sideby plugins [--json]\nsideby plugins new <id> [--json]\nsideby plugins check <id|path> [--json]\nPlugins run as you, with your permissions. Only load plugins you trust.',
98
117
  new: `sideby new <family> <name> [--api] [--alias <short>] [--json]
99
118
  sideby new <family> --next [--api] [--alias <short>] [--json]
100
119
 
@@ -179,6 +198,42 @@ Examples:
179
198
  sideby desktop grok-bot:002 --remove --yes
180
199
  Exit: 0 opened, brought to the front or removed; 1 not macOS, no desktop app for that host, API account, app not
181
200
  installed, a bad window name, or the app did not open; 10 --remove without --yes; 2 malformed command line.`,
201
+ autostart: `sideby autostart [on|off] [--yes] [--json]
202
+
203
+ Start the panel in the background when you log in, so it already shows fresh quota when you open it. It never
204
+ opens a window or a browser and never starts a host. macOS only for now (a LaunchAgent,
205
+ ~/Library/LaunchAgents/dev.sideby.panel.plist, that starts this Node and this sideby with ui --background).
206
+
207
+ Without on or off it says whether it is on, off, out of date (the Node or sideby it starts was moved) or blocked
208
+ by a file that is not sideby's. on and off show the file and the launchctl command first; --yes does it.
209
+ \`sideby update\` and \`pnpm local\` keep an existing login item pointing at the sideby in use.
210
+
211
+ Examples:
212
+ sideby autostart --json
213
+ sideby autostart on --yes
214
+ sideby autostart off --yes
215
+ Exit: 0 shown, done or nothing to change; 10 a change was shown and waits for --yes; 1 refused (not macOS, sideby
216
+ in the npx cache or a temporary folder, a file that is not sideby's) or failed; 2 malformed command line.`,
217
+ config: `sideby config get [<key>] [--json]
218
+ sideby config set <key> <value> [--yes] [--json]
219
+ sideby config unset <key> [--yes] [--json]
220
+
221
+ Read or change any setting in sideby's config file. <key> is a dot path such as handoff.threshold or panel.theme;
222
+ \`sideby config get\` lists every key with its value, what it does and the values it takes. A value is read as
223
+ JSON when it parses (90, true, ["cc002","codex001"]), otherwise as text (dark).
224
+
225
+ set and unset show the change first; --yes saves it. The whole file must still be a valid config, and other keys
226
+ and their order are kept. A plugin's own settings are not read or changed here (they may hold secrets), only
227
+ plugins.<plugin>.enabled. accounts.<family>:<name>.args takes only the model and effort arguments, as
228
+ \`sideby model\` does.
229
+
230
+ Examples:
231
+ sideby config get --json
232
+ sideby config set panel.theme dark --yes
233
+ sideby config set handoff.threshold 90 --yes
234
+ sideby config unset panel.language --yes
235
+ Exit: 0 shown, saved or nothing to change; 10 a change was shown and waits for --yes; 1 not a key, a value the key
236
+ does not take, or the file changed meanwhile; 2 malformed command line.`,
182
237
  browser: `sideby browser [--enable|--disable] [--yes] [--json]
183
238
  sideby browser open <account> [--profile <folder|name>] [--json]
184
239
 
@@ -224,6 +279,12 @@ export function nodeTooOld(version = process.versions.node) {
224
279
  return maj < MIN_NODE[0] || (maj === MIN_NODE[0] && min < MIN_NODE[1]);
225
280
  }
226
281
  const COMMANDS = {
282
+ ask: { flags: ['json', 'print'], valued: ['task', 'account', 'terminal', 'run', 'state-dir', 'attach'] },
283
+ terminal: { flags: ['json', 'unset'], valued: ['set', 'custom'] },
284
+ key: { flags: ['json'], valued: ['var'] },
285
+ try: { flags: ['json', 'yes'] },
286
+ autostart: { flags: ['json', 'yes', 'refresh'] },
287
+ config: { flags: ['json', 'yes'] },
227
288
  list: { flags: ['json'] },
228
289
  pin: { flags: ['json'] },
229
290
  unpin: { flags: ['json'] },
@@ -254,10 +315,24 @@ const COMMANDS = {
254
315
  local: { flags: ['json', 'yes'] },
255
316
  'update-check': { flags: [] },
256
317
  };
318
+ /** Every value of a repeatable option (`--attach a --attach=b`), in order. */
319
+ function repeatedValues(args, name) {
320
+ const out = [];
321
+ for (let i = 0; i < args.length; i++) {
322
+ const a = args[i];
323
+ if (a === `--${name}` && args[i + 1] !== undefined)
324
+ out.push(args[++i]);
325
+ else if (a.startsWith(`--${name}=`))
326
+ out.push(a.slice(name.length + 3));
327
+ }
328
+ return out;
329
+ }
257
330
  /** Commands that never print an Update Notice: their output is read by a shell or an agent, or they are internal. */
258
331
  function noticeAllowed(cmd, parsed) {
259
332
  if (cmd === 'shell-init' || cmd === 'update' || cmd === 'update-check')
260
333
  return false;
334
+ if (cmd === 'ask' && (parsed.flags.has('print') || parsed.flags.has('run')))
335
+ return false;
261
336
  if (cmd === 'ui' && parsed.flags.has('serve-detached'))
262
337
  return false;
263
338
  if (cmd === 'handoff' && parsed.positionals[0] === 'ready')
@@ -349,7 +424,7 @@ export async function main(argv) {
349
424
  return runHandoffHook(rest, process.env, process.stdin, process.stdout);
350
425
  }
351
426
  if (nodeTooOld()) {
352
- console.error(`sideby needs Node ${MIN_NODE.join('.')} or newer (you have ${process.versions.node}). Upgrade: https://nodejs.org`);
427
+ console.error(`sideby needs Node ${MIN_NODE.join('.')} or newer (you have ${process.versions.node}). Upgrade: https://nodejs.org, or with nvm: \`nvm install 22 && nvm alias default 22\`, then \`npm i -g sideby\` again (each Node version has its own global packages).`);
353
428
  return 1;
354
429
  }
355
430
  if (first === '-v' || first === '--version' || first === 'version') {
@@ -404,6 +479,9 @@ export async function main(argv) {
404
479
  console.log(COMMAND_HELP[cmd] ?? HELP);
405
480
  return 0;
406
481
  }
482
+ // parseArgs keeps the last value of an option; `ask --attach` may be given several times.
483
+ if (cmd === 'ask')
484
+ parsed.attach = repeatedValues(own, 'attach');
407
485
  const cmds = await import('./commands.js');
408
486
  if (wantsJson && noticeAllowed(cmd, parsed)) {
409
487
  notice = await jsonNoticeFor();
@@ -431,6 +509,14 @@ export async function main(argv) {
431
509
  }
432
510
  async function dispatch(cmd, parsed, io, cmds) {
433
511
  switch (cmd) {
512
+ case 'ask':
513
+ return cmds.cmdAsk(parsed, io);
514
+ case 'terminal':
515
+ return cmds.cmdTerminal(parsed, io);
516
+ case 'key':
517
+ return cmds.cmdKey(parsed, io);
518
+ case 'try':
519
+ return cmds.cmdTry(parsed, io);
434
520
  case 'pin':
435
521
  case 'unpin':
436
522
  return await cmds.cmdPin(parsed, io, cmd === 'pin');
@@ -482,6 +568,10 @@ async function dispatch(cmd, parsed, io, cmds) {
482
568
  return await cmds.cmdUpdateCheck(parsed, io);
483
569
  case 'local':
484
570
  return await cmds.cmdLocal(parsed, io);
571
+ case 'autostart':
572
+ return await cmds.cmdAutostart(parsed, io);
573
+ case 'config':
574
+ return await cmds.cmdConfig(parsed, io);
485
575
  }
486
576
  return 2;
487
577
  }
@@ -0,0 +1,150 @@
1
+ import { taskPrompt } from '../panel/page-logic.ts';
2
+ import type { Runtime } from '../runtime.ts';
3
+ import type { Account, Env } from '../types.ts';
4
+ import { type Attachment } from './attachments.ts';
5
+ import { type Paths } from './paths.ts';
6
+ import { type ProbeDeps } from './probe.ts';
7
+ import { type TerminalId } from './terminal.ts';
8
+ export declare function pickTaskAccount(rt: Runtime, opts?: {
9
+ family?: string;
10
+ account?: string;
11
+ }): Promise<{
12
+ account: Account;
13
+ } | {
14
+ reason: 'no-accounts' | 'none-signed-in' | 'all-full';
15
+ }>;
16
+ export interface TaskFile {
17
+ kind: 'prompt' | 'login';
18
+ account: string;
19
+ prompt?: string;
20
+ cwd: string;
21
+ /** The Panel's paths when it wrote the task; `ask --run` starts the Host with the same HOME, config and state. */
22
+ home?: string;
23
+ configDir?: string;
24
+ stateDir?: string;
25
+ /** Files attached to the task (spec §3.28 "附件"), saved under `ai/attachments/<task-id>/`; never their content. */
26
+ attachments?: {
27
+ path: string;
28
+ kind: 'image' | 'video';
29
+ }[];
30
+ }
31
+ export declare function taskWorkDir(paths?: Paths): Promise<string>;
32
+ export declare const newTaskId: () => string;
33
+ export declare function writeTask(task: TaskFile, paths?: Paths, id?: string): Promise<string>;
34
+ /**
35
+ * The state folder `ask --run --state-dir` names, accepted only when it and its `ask` folder are real directories the
36
+ * user owns that nobody else can write (the `ask` folder private, as writeTask makes it). Anything else is refused.
37
+ */
38
+ export declare function taskStateDir(dir: string): Promise<string>;
39
+ /**
40
+ * The Host arguments `ask --run` adds for a prompt task: `<imageArg>=<path>` per attached image for a Family that
41
+ * declares `imageArg` (spec §3.28 "附件"), then the prompt, always last. The `=` form keeps one value per option, so a
42
+ * Host option that takes several values cannot swallow the prompt. An image that is gone, or whose path holds a comma
43
+ * (a Host taking lists may split it), is left to the path in the prompt.
44
+ */
45
+ export declare function taskHostArgs(rt: Runtime, task: TaskFile): Promise<string[]>;
46
+ /** `env` with the HOME and XDG folders the task was written under, so the Runtime sees what the Panel saw. */
47
+ export declare function taskEnv(task: TaskFile, env: Env): Env;
48
+ export declare function readTask(id: string, paths?: Pick<Paths, 'stateDir'>): Promise<TaskFile>;
49
+ export declare function terminals(rt: Runtime): Promise<{
50
+ installed: import("./terminal.ts").InstalledTerminal[];
51
+ preferred: string | null;
52
+ guess: {
53
+ id: TerminalId;
54
+ source: 'config' | 'env' | 'default';
55
+ };
56
+ terminals: {
57
+ id: TerminalId;
58
+ name: string;
59
+ bundleId: string | null;
60
+ app: string | null;
61
+ termPrograms: readonly string[];
62
+ marker?: string;
63
+ binary?: string;
64
+ installed: boolean;
65
+ }[];
66
+ }>;
67
+ export declare function setPreferredTerminal(rt: Runtime, id?: string): Promise<void>;
68
+ /** Saves `terminal.custom` after checking it names a program and both placeholders; selects it too. */
69
+ export declare function setCustomTerminal(rt: Runtime, template: string): Promise<void>;
70
+ export interface AiTaskInput {
71
+ task?: string;
72
+ text?: string;
73
+ context?: Parameters<typeof taskPrompt>[0]['context'];
74
+ lang?: string;
75
+ account?: string;
76
+ family?: string;
77
+ terminal?: string;
78
+ /** With an explicit `account`: save it as `ask.account`, the next default. */
79
+ remember?: boolean;
80
+ /** Panel uploads (`/api/ask/attach` ids) to attach. */
81
+ uploads?: string[];
82
+ /** The user's own files to attach (`sideby ask --attach`). */
83
+ files?: string[];
84
+ }
85
+ /** The attachments a prompt shown before the task exists lists: uploads still staged, the user's files in place. */
86
+ export declare function previewAttachments(rt: Runtime, input: AiTaskInput): Promise<Attachment[]>;
87
+ export declare function promptForTask(rt: Runtime, input: AiTaskInput, attachments?: readonly Attachment[]): {
88
+ prompt: string;
89
+ removedSecrets: boolean;
90
+ };
91
+ /** Machine-readable reasons an AI Task did not open; `detail` says what to do. */
92
+ export type AiTaskReason = 'no-accounts' | 'none-signed-in' | 'all-full' | 'not-installed' | 'automation-denied' | 'npx-cache' | 'unsafe-path' | 'failed' | 'unsupported-platform';
93
+ export declare function startAiTask(rt: Runtime, input: AiTaskInput): Promise<{
94
+ schemaVersion: 1;
95
+ task: null;
96
+ account: null;
97
+ terminal: null;
98
+ opened: boolean;
99
+ command: null;
100
+ prompt: string;
101
+ reason: AiTaskReason;
102
+ detail: string;
103
+ removedSecrets: boolean;
104
+ attachments: Attachment[];
105
+ } | {
106
+ schemaVersion: 1;
107
+ task: string;
108
+ account: string;
109
+ terminal: string;
110
+ opened: boolean;
111
+ command: string;
112
+ reason?: AiTaskReason | undefined;
113
+ detail?: string | undefined;
114
+ prompt?: undefined;
115
+ removedSecrets: boolean;
116
+ attachments: Attachment[];
117
+ }>;
118
+ export declare function openLoginInTerminal(rt: Runtime, ref: string): Promise<{
119
+ schemaVersion: 1;
120
+ task: string;
121
+ account: string;
122
+ terminal: string;
123
+ opened: boolean;
124
+ command: string;
125
+ reason?: AiTaskReason | undefined;
126
+ detail?: string | undefined;
127
+ }>;
128
+ /**
129
+ * What `try` will run for an Account, computed as `sideby run` would (Family defaults, config arguments) but without
130
+ * reading its Secret File or running `launch.before` hooks: nothing runs before the user agrees.
131
+ */
132
+ export declare function tryPreview(rt: Runtime, ref: string): Promise<{
133
+ account: Account;
134
+ family: import("../types.ts").FamilyDef;
135
+ command: string[];
136
+ }>;
137
+ /**
138
+ * `sideby try` and the Panel (spec §3.28): runs the Family's probe once the way Launch starts the Host, in a temp
139
+ * folder under the task directory that is removed afterwards. The caller has already said it spends a few tokens. The
140
+ * launch is prepared once here, so `launch.before` hooks run once, and the command returned is what actually ran.
141
+ * `spawn` replaces the process start (tests); by default the Host runs with stdin ignored and is killed at the timeout.
142
+ */
143
+ export declare function tryAccount(rt: Runtime, ref: string, spawn?: ProbeDeps['spawn']): Promise<{
144
+ ok: boolean;
145
+ exitCode: number | null;
146
+ output: string;
147
+ timedOut: boolean;
148
+ account: string;
149
+ command: string[];
150
+ }>;