lua-cli 3.17.0 → 3.17.2

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/index.js CHANGED
@@ -132,7 +132,7 @@ var init_semver = __esm({
132
132
  // src/config/constants.ts
133
133
  import { join } from "path";
134
134
  import { homedir } from "os";
135
- var CLI_CONFIG_DIR, VERSION_CHECK_FILE, TELEMETRY_FILE, CLI_CACHE_FILE, BASE_URLS, CREDENTIALS_FILE, PREFERRED_AGENT_TYPES, SANDBOX_STORAGE_FILE, POSTHOG_API_KEY, POSTHOG_HOST;
135
+ var CLI_CONFIG_DIR, VERSION_CHECK_FILE, TELEMETRY_FILE, CLI_CACHE_FILE, BASE_URLS, CREDENTIALS_FILE, PREFERRED_AGENT_TYPES, SANDBOX_STORAGE_FILE, AUTH_STORAGE_FILE, POSTHOG_API_KEY, POSTHOG_HOST;
136
136
  var init_constants = __esm({
137
137
  "src/config/constants.ts"() {
138
138
  "use strict";
@@ -154,6 +154,7 @@ var init_constants = __esm({
154
154
  "base"
155
155
  ];
156
156
  SANDBOX_STORAGE_FILE = join(CLI_CONFIG_DIR, "sandbox.json");
157
+ AUTH_STORAGE_FILE = join(CLI_CONFIG_DIR, "auth.json");
157
158
  POSTHOG_API_KEY = "phc_W7Qsquwlflshmdkm2hWSqRpXuxGbVFo7LEX8H9HrSjC";
158
159
  POSTHOG_HOST = "https://us.i.posthog.com";
159
160
  }
@@ -1199,7 +1200,14 @@ Feel free to add, remove, or rename sections. Your persona can be a single parag
1199
1200
  // `Data.get('call:<sessionId>')` to drive post-call analytics, follow-ups,
1200
1201
  // or QA workflows. Defaults to false — most calls don't need to keep
1201
1202
  // a transcript copy.
1202
- persistTranscript: z.boolean().optional()
1203
+ persistTranscript: z.boolean().optional(),
1204
+ // Spoken acknowledgement played when a tool call fails. When a tool
1205
+ // throws, times out, or returns an unsupported result, the adapter calls
1206
+ // `session.say(text)` once per failed call before surfacing the error
1207
+ // to the LLM as a ToolError — fills the 2–3s gap before the LLM's own
1208
+ // recovery response. Persona-specific (keep it short and on-brand);
1209
+ // absent → no spoken fallback (the LLM's recovery is the only signal).
1210
+ onToolFailureSay: z.string().min(1).max(200).optional()
1203
1211
  });
1204
1212
  LuaVoiceConfigSchema = LuaVoiceConfigInnerSchema.superRefine((cfg, ctx) => {
1205
1213
  const isRealtime = cfg.llm.kind === "realtime";
@@ -6677,6 +6685,31 @@ var init_skill_plugin = __esm({
6677
6685
 
6678
6686
  // src/compiler/plugins/agent.plugin.ts
6679
6687
  import { Node as Node12 } from "ts-morph";
6688
+ function shapeModelSettings(raw) {
6689
+ const KNOWN_KEYS = [
6690
+ "temperature",
6691
+ "topP",
6692
+ "topK",
6693
+ "maxOutputTokens",
6694
+ "presencePenalty",
6695
+ "frequencyPenalty",
6696
+ "stopSequences",
6697
+ "seed"
6698
+ ];
6699
+ const result = {};
6700
+ for (const key of KNOWN_KEYS) {
6701
+ const value = raw[key];
6702
+ if (value === void 0) continue;
6703
+ if (key === "stopSequences") {
6704
+ if (Array.isArray(value) && value.every((v) => typeof v === "string")) {
6705
+ result[key] = value;
6706
+ }
6707
+ } else if (typeof value === "number" && Number.isFinite(value)) {
6708
+ result[key] = value;
6709
+ }
6710
+ }
6711
+ return Object.keys(result).length > 0 ? result : void 0;
6712
+ }
6680
6713
  var AgentPlugin, agentPlugin;
6681
6714
  var init_agent_plugin = __esm({
6682
6715
  "src/compiler/plugins/agent.plugin.ts"() {
@@ -6709,7 +6742,8 @@ var init_agent_plugin = __esm({
6709
6742
  "name",
6710
6743
  "description",
6711
6744
  "persona",
6712
- "model"
6745
+ "model",
6746
+ "modelSettings"
6713
6747
  ]
6714
6748
  };
6715
6749
  supportsClassDefinition = true;
@@ -6766,6 +6800,9 @@ var init_agent_plugin = __esm({
6766
6800
  const governanceHit = findClassMember(classDecl, "governance", "property");
6767
6801
  const governanceObj = governanceHit && Node12.isPropertyDeclaration(governanceHit.node) ? evaluateNodeAsObject(governanceHit.node.getInitializer()) : void 0;
6768
6802
  const governance = governanceObj && typeof governanceObj.mode === "string" ? governanceObj : void 0;
6803
+ const modelSettingsHit = findClassMember(classDecl, "modelSettings", "property");
6804
+ const modelSettingsObj = modelSettingsHit && Node12.isPropertyDeclaration(modelSettingsHit.node) ? evaluateNodeAsObject(modelSettingsHit.node.getInitializer()) : void 0;
6805
+ const modelSettings = modelSettingsObj ? shapeModelSettings(modelSettingsObj) : void 0;
6769
6806
  return {
6770
6807
  kind: this.kind,
6771
6808
  name,
@@ -6780,6 +6817,7 @@ var init_agent_plugin = __esm({
6780
6817
  persona,
6781
6818
  model,
6782
6819
  hasModelResolver,
6820
+ modelSettings,
6783
6821
  batching,
6784
6822
  governance
6785
6823
  }
@@ -6809,6 +6847,7 @@ var init_agent_plugin = __esm({
6809
6847
  "text"
6810
6848
  ]) ?? "";
6811
6849
  const { model, hasModelResolver } = this.extractModelInfo(config);
6850
+ const modelSettings = this.extractModelSettings(config);
6812
6851
  const batching = this.extractBatchingInfo(config);
6813
6852
  const governance = this.extractGovernanceInfo(config);
6814
6853
  const { voiceRefNames, voiceRefSourcePaths } = this.extractVoiceRefs(config);
@@ -6825,6 +6864,7 @@ var init_agent_plugin = __esm({
6825
6864
  persona,
6826
6865
  model,
6827
6866
  hasModelResolver,
6867
+ modelSettings,
6828
6868
  batching,
6829
6869
  governance,
6830
6870
  voiceRefNames,
@@ -6895,6 +6935,17 @@ var init_agent_plugin = __esm({
6895
6935
  return governance;
6896
6936
  }
6897
6937
  /**
6938
+ * Extract `modelSettings` from the config-literal path. Delegates to the
6939
+ * shared `shapeModelSettings` helper so the class-definition extraction
6940
+ * path applies the exact same key whitelist + type narrowing — without
6941
+ * the shared helper, the two paths would drift (see Bugbot #ref1).
6942
+ */
6943
+ extractModelSettings(config) {
6944
+ const raw = extractObjectProperty(config, "modelSettings");
6945
+ if (!raw || typeof raw !== "object") return void 0;
6946
+ return shapeModelSettings(raw);
6947
+ }
6948
+ /**
6898
6949
  * Extract model info from agent config.
6899
6950
  * Returns the static model string (if any) and whether a resolver function exists.
6900
6951
  */
@@ -6976,6 +7027,7 @@ var init_agent_plugin = __esm({
6976
7027
  persona: agentMeta.persona,
6977
7028
  model: agentMeta.model,
6978
7029
  hasModelResolver: agentMeta.hasModelResolver || void 0,
7030
+ modelSettings: agentMeta.modelSettings,
6979
7031
  batching: agentMeta.batching,
6980
7032
  governance: agentMeta.governance,
6981
7033
  voiceRefs
@@ -7037,6 +7089,7 @@ var init_agent_plugin = __esm({
7037
7089
  return rest;
7038
7090
  }
7039
7091
  };
7092
+ __name(shapeModelSettings, "shapeModelSettings");
7040
7093
  agentPlugin = new AgentPlugin();
7041
7094
  }
7042
7095
  });
@@ -8100,6 +8153,7 @@ var init_voice_plugin = __esm({
8100
8153
  const tts = extractModelField(config, "tts");
8101
8154
  const krispEnabled = extractBooleanProperty(config, "krispEnabled");
8102
8155
  const persistTranscript = extractBooleanProperty(config, "persistTranscript");
8156
+ const onToolFailureSay = extractStringProperty(config, "onToolFailureSay");
8103
8157
  const volume = extractNumberProperty(config, "volume");
8104
8158
  const interruption = extractObjectProperty(config, "interruption");
8105
8159
  const pronunciationsRaw = extractObjectProperty(config, "pronunciations");
@@ -8136,6 +8190,7 @@ var init_voice_plugin = __esm({
8136
8190
  hasTools: hasArrayProperty(config, "tools"),
8137
8191
  krispEnabled,
8138
8192
  persistTranscript,
8193
+ onToolFailureSay,
8139
8194
  volume,
8140
8195
  pronunciations,
8141
8196
  backgroundAudio,
@@ -8195,6 +8250,7 @@ var init_voice_plugin = __esm({
8195
8250
  if (fields.sttLanguage !== void 0) candidate.sttLanguage = fields.sttLanguage;
8196
8251
  if (fields.krispEnabled !== void 0) candidate.krispEnabled = fields.krispEnabled;
8197
8252
  if (fields.persistTranscript !== void 0) candidate.persistTranscript = fields.persistTranscript;
8253
+ if (fields.onToolFailureSay !== void 0) candidate.onToolFailureSay = fields.onToolFailureSay;
8198
8254
  if (fields.volume !== void 0) candidate.volume = fields.volume;
8199
8255
  if (fields.pronunciations !== void 0) candidate.pronunciations = fields.pronunciations;
8200
8256
  if (fields.backgroundAudio !== void 0) candidate.backgroundAudio = fields.backgroundAudio;
@@ -8349,6 +8405,7 @@ var init_voice_plugin = __esm({
8349
8405
  volume: fields.volume,
8350
8406
  pronunciations: fields.pronunciations,
8351
8407
  persistTranscript: fields.persistTranscript,
8408
+ onToolFailureSay: fields.onToolFailureSay,
8352
8409
  interruption: fields.interruption
8353
8410
  };
8354
8411
  return entry;
@@ -15766,6 +15823,33 @@ var AgentHandler = class {
15766
15823
  success: false
15767
15824
  };
15768
15825
  }
15826
+ const modelSettings = agent?.modelSettings ?? null;
15827
+ writeProgress("\n\u{1F321}\uFE0F Pushing model settings...");
15828
+ try {
15829
+ const success2 = await this.pushModelSettings({
15830
+ apiKey,
15831
+ agentId
15832
+ }, modelSettings);
15833
+ result.modelSettings = {
15834
+ success: success2
15835
+ };
15836
+ if (success2) {
15837
+ if (modelSettings !== null) {
15838
+ const keys = Object.keys(modelSettings).join(", ");
15839
+ writeSuccess(` \u2705 Model settings pushed (${keys})`);
15840
+ } else {
15841
+ writeSuccess(" \u2705 Model settings cleared (using provider defaults)");
15842
+ }
15843
+ } else {
15844
+ console.error(" \u274C Failed to push model settings");
15845
+ }
15846
+ } catch (error) {
15847
+ if (AuthenticationError.isAuthenticationError(error)) throw error;
15848
+ console.error(` \u274C Failed to push model settings: ${error.message}`);
15849
+ result.modelSettings = {
15850
+ success: false
15851
+ };
15852
+ }
15769
15853
  const batching = agent?.batching ?? null;
15770
15854
  writeProgress("\n\u23F1 Pushing batching configuration...");
15771
15855
  try {
@@ -15899,6 +15983,13 @@ var AgentHandler = class {
15899
15983
  });
15900
15984
  return result.success;
15901
15985
  }
15986
+ async pushModelSettings(ctx, modelSettings) {
15987
+ const agentApi = new AgentApi(BASE_URLS.API, ctx.apiKey);
15988
+ const result = await agentApi.updateAgent(ctx.agentId, {
15989
+ modelSettings
15990
+ });
15991
+ return result.success;
15992
+ }
15902
15993
  async pushBatching(ctx, batchingConfig) {
15903
15994
  const agentApi = new AgentApi(BASE_URLS.API, ctx.apiKey);
15904
15995
  const result = await agentApi.updateAgent(ctx.agentId, {
@@ -16932,6 +17023,18 @@ async function safePrompt(questions) {
16932
17023
  }
16933
17024
  }
16934
17025
  __name(safePrompt, "safePrompt");
17026
+ async function confirmAction(message) {
17027
+ const answer = await safePrompt([
17028
+ {
17029
+ type: "confirm",
17030
+ name: "confirmed",
17031
+ message,
17032
+ default: false
17033
+ }
17034
+ ]);
17035
+ return answer?.confirmed ?? false;
17036
+ }
17037
+ __name(confirmAction, "confirmAction");
16935
17038
 
16936
17039
  // src/commands/sync.ts
16937
17040
  init_command_utils();
@@ -18666,6 +18769,7 @@ var VoiceHandler = class extends BaseVersionedHandler {
18666
18769
  if (voice.hasTools !== void 0) body.hasTools = voice.hasTools;
18667
18770
  if (voice.krispEnabled !== void 0) body.krispEnabled = voice.krispEnabled;
18668
18771
  if (voice.persistTranscript !== void 0) body.persistTranscript = voice.persistTranscript;
18772
+ if (voice.onToolFailureSay !== void 0) body.onToolFailureSay = voice.onToolFailureSay;
18669
18773
  if (voice.volume !== void 0) body.volume = voice.volume;
18670
18774
  if (voice.pronunciations !== void 0) body.pronunciations = voice.pronunciations;
18671
18775
  if (voice.backgroundAudio !== void 0) body.backgroundAudio = voice.backgroundAudio;
@@ -22120,6 +22224,23 @@ async function gitUserConfigured(cwd) {
22120
22224
  };
22121
22225
  }
22122
22226
  __name(gitUserConfigured, "gitUserConfigured");
22227
+ async function getRemoteOriginUrl(cwd) {
22228
+ try {
22229
+ const { stdout, code } = await runGit([
22230
+ "config",
22231
+ "--get",
22232
+ "remote.origin.url"
22233
+ ], {
22234
+ cwd
22235
+ });
22236
+ if (code !== 0) return null;
22237
+ const trimmed = stdout.trim();
22238
+ return trimmed.length > 0 ? trimmed : null;
22239
+ } catch {
22240
+ return null;
22241
+ }
22242
+ }
22243
+ __name(getRemoteOriginUrl, "getRemoteOriginUrl");
22123
22244
  async function getCommitSha(cwd) {
22124
22245
  try {
22125
22246
  const { stdout, code } = await runGit([
@@ -22139,6 +22260,351 @@ __name(getCommitSha, "getCommitSha");
22139
22260
 
22140
22261
  // src/utils/try-git-commit.ts
22141
22262
  init_analytics();
22263
+
22264
+ // src/utils/auto-push-hook.ts
22265
+ init_cli();
22266
+
22267
+ // src/utils/git-auth-store.ts
22268
+ init_constants();
22269
+ import { mkdirSync as mkdirSync7, readFileSync as readFileSync10, writeFileSync as writeFileSync7 } from "fs";
22270
+ import { dirname as dirname6 } from "path";
22271
+ function readStore() {
22272
+ try {
22273
+ const raw = readFileSync10(AUTH_STORAGE_FILE, "utf8");
22274
+ const parsed = JSON.parse(raw);
22275
+ return {
22276
+ providers: parsed.providers ?? {}
22277
+ };
22278
+ } catch {
22279
+ return {
22280
+ providers: {}
22281
+ };
22282
+ }
22283
+ }
22284
+ __name(readStore, "readStore");
22285
+ function writeStore(store) {
22286
+ mkdirSync7(dirname6(AUTH_STORAGE_FILE), {
22287
+ recursive: true
22288
+ });
22289
+ writeFileSync7(AUTH_STORAGE_FILE, JSON.stringify(store, null, 2), {
22290
+ mode: 384
22291
+ });
22292
+ }
22293
+ __name(writeStore, "writeStore");
22294
+ async function saveAuth(provider, record) {
22295
+ const store = readStore();
22296
+ store.providers[provider] = record;
22297
+ writeStore(store);
22298
+ }
22299
+ __name(saveAuth, "saveAuth");
22300
+ async function getToken2(provider) {
22301
+ return readStore().providers[provider]?.token ?? null;
22302
+ }
22303
+ __name(getToken2, "getToken");
22304
+ async function getStatus(provider) {
22305
+ const record = readStore().providers[provider];
22306
+ if (!record) return {
22307
+ linked: false
22308
+ };
22309
+ return {
22310
+ linked: true,
22311
+ username: record.username,
22312
+ scopes: record.scopes,
22313
+ linkedAt: record.linkedAt
22314
+ };
22315
+ }
22316
+ __name(getStatus, "getStatus");
22317
+ async function clearAuth(provider) {
22318
+ const store = readStore();
22319
+ delete store.providers[provider];
22320
+ writeStore(store);
22321
+ }
22322
+ __name(clearAuth, "clearAuth");
22323
+
22324
+ // src/utils/git-providers/github-provider.ts
22325
+ import { request } from "undici";
22326
+
22327
+ // src/config/git-providers.constants.ts
22328
+ var GITHUB_CLIENT_ID = "Ov23lipe1jgBlCsoi9OO";
22329
+ var GITHUB_OAUTH_BASE_URL = "https://github.com";
22330
+ var GITHUB_API_BASE_URL = "https://api.github.com";
22331
+ var GITHUB_OAUTH_SCOPE = "repo";
22332
+ var DEVICE_FLOW_DEFAULT_INTERVAL_MS = 5e3;
22333
+ var DEVICE_FLOW_SLOW_DOWN_INCREMENT_MS = 5e3;
22334
+ var PUSH_TIMEOUT_MS = 2 * 60 * 1e3;
22335
+
22336
+ // src/interfaces/git-providers.ts
22337
+ var GitPushError = class extends Error {
22338
+ static {
22339
+ __name(this, "GitPushError");
22340
+ }
22341
+ status;
22342
+ scrubbedStderr;
22343
+ constructor(message, status, scrubbedStderr) {
22344
+ super(message), this.status = status, this.scrubbedStderr = scrubbedStderr;
22345
+ this.name = "GitPushError";
22346
+ }
22347
+ };
22348
+
22349
+ // src/utils/git-providers/github-provider.ts
22350
+ init_cli();
22351
+
22352
+ // src/utils/scrub-token.ts
22353
+ function scrubToken(s, token) {
22354
+ if (!token) return s;
22355
+ return s.split(token).join("<redacted>");
22356
+ }
22357
+ __name(scrubToken, "scrubToken");
22358
+
22359
+ // src/utils/git-providers/github-provider.ts
22360
+ var GitHubProvider = class {
22361
+ static {
22362
+ __name(this, "GitHubProvider");
22363
+ }
22364
+ name = "github";
22365
+ /**
22366
+ * GitHub is authenticated via the OAuth **device flow** only.
22367
+ *
22368
+ * The browser/loopback authorization-code flow is intentionally NOT used:
22369
+ * GitHub requires a `client_secret` to exchange the code (PKCE does not
22370
+ * replace it — the secretless exchange returns `incorrect_client_credentials`),
22371
+ * and a CLI published to npm cannot safely embed a secret. The device flow
22372
+ * needs no secret, which is why it is the only viable flow for a public
22373
+ * client (the same reason GitHub's own `gh` CLI defaults to it).
22374
+ *
22375
+ * `opts.device` is accepted for backwards compatibility but is now a no-op:
22376
+ * the device flow is always used.
22377
+ */
22378
+ async login(_opts) {
22379
+ const token = await this.deviceFlow();
22380
+ const username = await this.fetchUsername(token);
22381
+ await saveAuth(this.name, {
22382
+ token,
22383
+ username,
22384
+ scopes: [
22385
+ GITHUB_OAUTH_SCOPE
22386
+ ],
22387
+ linkedAt: (/* @__PURE__ */ new Date()).toISOString()
22388
+ });
22389
+ return {
22390
+ username
22391
+ };
22392
+ }
22393
+ async logout() {
22394
+ await clearAuth(this.name);
22395
+ }
22396
+ async status() {
22397
+ return getStatus(this.name);
22398
+ }
22399
+ async push(opts) {
22400
+ const parsed = parseGitHubHttpsUrl(opts.remoteUrl);
22401
+ const authedUrl = `https://x-access-token:${opts.token}@github.com/${parsed.owner}/${parsed.repo}`;
22402
+ let result;
22403
+ try {
22404
+ result = await runGit([
22405
+ "push",
22406
+ authedUrl,
22407
+ opts.branch
22408
+ ], {
22409
+ cwd: opts.cwd,
22410
+ timeout: PUSH_TIMEOUT_MS
22411
+ });
22412
+ } catch (err) {
22413
+ const raw = err instanceof Error ? err.message : String(err);
22414
+ const scrubbed2 = scrubToken(raw, opts.token);
22415
+ throw new GitPushError(`git push failed: ${scrubbed2}`, 0, scrubbed2);
22416
+ }
22417
+ if (result.code === 0) return;
22418
+ const scrubbed = scrubToken(result.stderr, opts.token);
22419
+ const status = matchPushStatus(scrubbed);
22420
+ throw new GitPushError(status === 401 ? "GitHub authentication failed (token revoked or insufficient scope)." : status === 403 ? "GitHub rejected the push (403 \u2014 possible SSO enforcement or missing scope)." : "git push failed.", status, scrubbed);
22421
+ }
22422
+ async fetchUsername(token) {
22423
+ const res = await request(`${GITHUB_API_BASE_URL}/user`, {
22424
+ method: "GET",
22425
+ headers: {
22426
+ accept: "application/vnd.github+json",
22427
+ authorization: `Bearer ${token}`,
22428
+ "user-agent": "lua-cli"
22429
+ }
22430
+ });
22431
+ const body = await res.body.json();
22432
+ if (!body.login) throw new Error("Could not read GitHub username after auth.");
22433
+ return body.login;
22434
+ }
22435
+ async deviceFlow() {
22436
+ const codeRes = await request(`${GITHUB_OAUTH_BASE_URL}/login/device/code`, {
22437
+ method: "POST",
22438
+ headers: {
22439
+ accept: "application/json",
22440
+ "content-type": "application/json"
22441
+ },
22442
+ body: JSON.stringify({
22443
+ client_id: GITHUB_CLIENT_ID,
22444
+ scope: GITHUB_OAUTH_SCOPE
22445
+ })
22446
+ });
22447
+ const codeBody = await codeRes.body.json();
22448
+ if (!codeBody.device_code || !codeBody.user_code || !codeBody.verification_uri) {
22449
+ throw new Error(`GitHub rejected device-code request: ${codeBody.error ?? "unknown error"}`);
22450
+ }
22451
+ writeInfo(`Open ${codeBody.verification_uri} and enter the code: ${codeBody.user_code}`);
22452
+ let intervalMs = codeBody.interval != null ? codeBody.interval * 1e3 : DEVICE_FLOW_DEFAULT_INTERVAL_MS;
22453
+ const deadline = Date.now() + (codeBody.expires_in ?? 900) * 1e3;
22454
+ while (Date.now() < deadline) {
22455
+ await sleep(intervalMs);
22456
+ if (Date.now() >= deadline) break;
22457
+ const pollRes = await request(`${GITHUB_OAUTH_BASE_URL}/login/oauth/access_token`, {
22458
+ method: "POST",
22459
+ headers: {
22460
+ accept: "application/json",
22461
+ "content-type": "application/json"
22462
+ },
22463
+ body: JSON.stringify({
22464
+ client_id: GITHUB_CLIENT_ID,
22465
+ device_code: codeBody.device_code,
22466
+ grant_type: "urn:ietf:params:oauth:grant-type:device_code"
22467
+ })
22468
+ });
22469
+ const body = await pollRes.body.json();
22470
+ if (body.access_token) return body.access_token;
22471
+ if (body.error === "authorization_pending") continue;
22472
+ if (body.error === "slow_down") {
22473
+ intervalMs += DEVICE_FLOW_SLOW_DOWN_INCREMENT_MS;
22474
+ continue;
22475
+ }
22476
+ if (body.error === "expired_token") {
22477
+ throw new Error("Device code expired. Re-run `lua git auth github --device`.");
22478
+ }
22479
+ throw new Error(`GitHub device-flow error: ${body.error ?? "unknown"}`);
22480
+ }
22481
+ throw new Error("Device flow timed out. Re-run `lua git auth github --device`.");
22482
+ }
22483
+ };
22484
+ function sleep(ms) {
22485
+ return new Promise((r) => setTimeout(r, ms));
22486
+ }
22487
+ __name(sleep, "sleep");
22488
+ function isGitHubUrl(url) {
22489
+ try {
22490
+ const u = new URL(url);
22491
+ return u.protocol === "https:" && u.hostname === "github.com";
22492
+ } catch {
22493
+ return false;
22494
+ }
22495
+ }
22496
+ __name(isGitHubUrl, "isGitHubUrl");
22497
+ function parseGitHubHttpsUrl(url) {
22498
+ let u;
22499
+ try {
22500
+ u = new URL(url);
22501
+ } catch {
22502
+ throw new Error(`Not a parseable URL: ${url}. v1 only supports https GitHub remotes.`);
22503
+ }
22504
+ if (u.protocol !== "https:" || u.hostname !== "github.com") {
22505
+ throw new Error(`Not a GitHub HTTPS remote: ${url}. v1 only supports https://github.com/...`);
22506
+ }
22507
+ const parts = u.pathname.replace(/^\/+/, "").replace(/\.git$/, "").split("/");
22508
+ if (parts.length < 2 || !parts[0] || !parts[1]) {
22509
+ throw new Error(`Unexpected GitHub URL shape: ${url}`);
22510
+ }
22511
+ return {
22512
+ owner: parts[0],
22513
+ repo: parts[1]
22514
+ };
22515
+ }
22516
+ __name(parseGitHubHttpsUrl, "parseGitHubHttpsUrl");
22517
+ function matchPushStatus(stderr) {
22518
+ const s = stderr.toLowerCase();
22519
+ if (s.includes("401") || s.includes("authentication failed")) return 401;
22520
+ if (s.includes("403") || s.includes("permission to") || s.includes("forbidden")) return 403;
22521
+ return 0;
22522
+ }
22523
+ __name(matchPushStatus, "matchPushStatus");
22524
+
22525
+ // src/utils/auto-push-hook.ts
22526
+ init_analytics();
22527
+ var warned = /* @__PURE__ */ new Set();
22528
+ function warnOnce(key, message) {
22529
+ if (warned.has(key)) return;
22530
+ warned.add(key);
22531
+ writeInfo(`\u26A0\uFE0F ${message}`);
22532
+ }
22533
+ __name(warnOnce, "warnOnce");
22534
+ async function tryGitAutoPush(ctx) {
22535
+ try {
22536
+ if (ctx.config?.git?.autoPush !== true) return;
22537
+ const token = await getToken2("github");
22538
+ if (!token) {
22539
+ warnOnce("missing-github-token", "git.autoPush is enabled but no GitHub auth \u2014 run `lua git auth github`.");
22540
+ return;
22541
+ }
22542
+ const remote = await getRemoteOriginUrl(ctx.cwd);
22543
+ if (!remote) {
22544
+ warnOnce("missing-github-remote", "git.autoPush is enabled but no `remote.origin.url` is set. Skipping push.");
22545
+ return;
22546
+ }
22547
+ if (!isGitHubUrl(remote)) {
22548
+ warnOnce("non-github-remote", `git.autoPush is enabled but origin (${remote}) is not a supported GitHub HTTPS URL. v1 only supports https://github.com/<owner>/<repo> \u2014 skipping push.`);
22549
+ return;
22550
+ }
22551
+ const branch = await readCurrentBranch(ctx.cwd);
22552
+ if (!branch) {
22553
+ warnOnce("detached-head", "Cannot auto-push from a detached HEAD. Skipping push.");
22554
+ return;
22555
+ }
22556
+ const provider = new GitHubProvider();
22557
+ try {
22558
+ await provider.push({
22559
+ token,
22560
+ remoteUrl: remote,
22561
+ branch,
22562
+ cwd: ctx.cwd ?? process.cwd()
22563
+ });
22564
+ writeInfo(`\u2713 Pushed ${ctx.commitSha?.slice(0, 7) ?? ""} to ${remote} (${branch})`);
22565
+ trackEvent("cli_auto_push_succeeded", {
22566
+ action: ctx.action
22567
+ });
22568
+ } catch (err) {
22569
+ const status = err instanceof GitPushError ? err.status : 0;
22570
+ const message = err instanceof GitPushError ? err.scrubbedStderr || err.message : err instanceof Error ? err.message : String(err);
22571
+ if (status === 401) {
22572
+ warnOnce("push-revoked", "GitHub auth was revoked \u2014 run `lua git auth github` to re-link. Commit kept locally.");
22573
+ } else if (status === 403) {
22574
+ warnOnce("push-rejected-403", `GitHub rejected the push: ${message}. Commit kept locally.`);
22575
+ } else {
22576
+ warnOnce("push-failed-other", `Auto-push failed: ${message}. Commit kept locally; run \`git push\` to retry.`);
22577
+ }
22578
+ trackEvent("cli_auto_push_failed", {
22579
+ action: ctx.action,
22580
+ status
22581
+ });
22582
+ }
22583
+ } catch {
22584
+ warnOnce("hook-errored", "Auto-push hook errored unexpectedly. Commit kept locally; run `git push` to retry.");
22585
+ trackEvent("cli_auto_push_failed", {
22586
+ action: ctx.action,
22587
+ status: 0
22588
+ });
22589
+ }
22590
+ }
22591
+ __name(tryGitAutoPush, "tryGitAutoPush");
22592
+ async function readCurrentBranch(cwd) {
22593
+ const res = await runGit([
22594
+ "rev-parse",
22595
+ "--abbrev-ref",
22596
+ "HEAD"
22597
+ ], {
22598
+ cwd
22599
+ });
22600
+ if (res.code !== 0) return null;
22601
+ const trimmed = res.stdout.trim();
22602
+ if (!trimmed || trimmed === "HEAD") return null;
22603
+ return trimmed;
22604
+ }
22605
+ __name(readCurrentBranch, "readCurrentBranch");
22606
+
22607
+ // src/utils/try-git-commit.ts
22142
22608
  async function tryGitCommit(opts) {
22143
22609
  const config = readYamlConfig();
22144
22610
  if (!config?.git?.enabled) {
@@ -22221,6 +22687,12 @@ async function tryGitCommit(opts) {
22221
22687
  has_sha: Boolean(sha),
22222
22688
  has_tag: tagged === true
22223
22689
  });
22690
+ await tryGitAutoPush({
22691
+ config,
22692
+ cwd: opts.cwd,
22693
+ commitSha: sha,
22694
+ action: opts.action
22695
+ });
22224
22696
  return {
22225
22697
  committed: true,
22226
22698
  sha,
@@ -24154,11 +24626,11 @@ init_cli();
24154
24626
 
24155
24627
  // src/utils/sandbox-storage.ts
24156
24628
  init_constants();
24157
- import { readFileSync as readFileSync10, writeFileSync as writeFileSync7, mkdirSync as mkdirSync7 } from "fs";
24158
- import { dirname as dirname6 } from "path";
24159
- function readStore() {
24629
+ import { readFileSync as readFileSync11, writeFileSync as writeFileSync8, mkdirSync as mkdirSync8 } from "fs";
24630
+ import { dirname as dirname7 } from "path";
24631
+ function readStore2() {
24160
24632
  try {
24161
- const raw = readFileSync10(SANDBOX_STORAGE_FILE, "utf8");
24633
+ const raw = readFileSync11(SANDBOX_STORAGE_FILE, "utf8");
24162
24634
  const parsed = JSON.parse(raw);
24163
24635
  return {
24164
24636
  skills: parsed.skills ?? {},
@@ -24175,28 +24647,28 @@ function readStore() {
24175
24647
  };
24176
24648
  }
24177
24649
  }
24178
- __name(readStore, "readStore");
24179
- function writeStore(store) {
24650
+ __name(readStore2, "readStore");
24651
+ function writeStore2(store) {
24180
24652
  try {
24181
- mkdirSync7(dirname6(SANDBOX_STORAGE_FILE), {
24653
+ mkdirSync8(dirname7(SANDBOX_STORAGE_FILE), {
24182
24654
  recursive: true
24183
24655
  });
24184
- writeFileSync7(SANDBOX_STORAGE_FILE, JSON.stringify(store, null, 2), "utf8");
24656
+ writeFileSync8(SANDBOX_STORAGE_FILE, JSON.stringify(store, null, 2), "utf8");
24185
24657
  } catch {
24186
24658
  }
24187
24659
  }
24188
- __name(writeStore, "writeStore");
24660
+ __name(writeStore2, "writeStore");
24189
24661
  async function getSandboxSkillId(skillName) {
24190
- const store = readStore();
24662
+ const store = readStore2();
24191
24663
  const key = skillName ?? "__default__";
24192
24664
  return store.skills[key] ?? null;
24193
24665
  }
24194
24666
  __name(getSandboxSkillId, "getSandboxSkillId");
24195
24667
  async function setSandboxSkillId(sandboxId, skillName) {
24196
- const store = readStore();
24668
+ const store = readStore2();
24197
24669
  const key = skillName ?? "__default__";
24198
24670
  store.skills[key] = sandboxId;
24199
- writeStore(store);
24671
+ writeStore2(store);
24200
24672
  }
24201
24673
  __name(setSandboxSkillId, "setSandboxSkillId");
24202
24674
  async function getAllSandboxSkillIds(yamlConfig) {
@@ -24222,14 +24694,14 @@ async function getAllSandboxSkillIds(yamlConfig) {
24222
24694
  }
24223
24695
  __name(getAllSandboxSkillIds, "getAllSandboxSkillIds");
24224
24696
  async function getSandboxPreProcessorId(preprocessorName) {
24225
- const store = readStore();
24697
+ const store = readStore2();
24226
24698
  return store.preprocessors[preprocessorName] ?? null;
24227
24699
  }
24228
24700
  __name(getSandboxPreProcessorId, "getSandboxPreProcessorId");
24229
24701
  async function setSandboxPreProcessorId(sandboxId, preprocessorName) {
24230
- const store = readStore();
24702
+ const store = readStore2();
24231
24703
  store.preprocessors[preprocessorName] = sandboxId;
24232
- writeStore(store);
24704
+ writeStore2(store);
24233
24705
  }
24234
24706
  __name(setSandboxPreProcessorId, "setSandboxPreProcessorId");
24235
24707
  async function getAllSandboxPreProcessorIds(config) {
@@ -24254,14 +24726,14 @@ async function getAllSandboxPreProcessorIds(config) {
24254
24726
  }
24255
24727
  __name(getAllSandboxPreProcessorIds, "getAllSandboxPreProcessorIds");
24256
24728
  async function getSandboxPostProcessorId(postprocessorName) {
24257
- const store = readStore();
24729
+ const store = readStore2();
24258
24730
  return store.postprocessors[postprocessorName] ?? null;
24259
24731
  }
24260
24732
  __name(getSandboxPostProcessorId, "getSandboxPostProcessorId");
24261
24733
  async function setSandboxPostProcessorId(sandboxId, postprocessorName) {
24262
- const store = readStore();
24734
+ const store = readStore2();
24263
24735
  store.postprocessors[postprocessorName] = sandboxId;
24264
- writeStore(store);
24736
+ writeStore2(store);
24265
24737
  }
24266
24738
  __name(setSandboxPostProcessorId, "setSandboxPostProcessorId");
24267
24739
  async function getAllSandboxPostProcessorIds(config) {
@@ -30358,7 +30830,7 @@ init_auth();
30358
30830
  init_auth_api_service();
30359
30831
  init_files();
30360
30832
  init_artifact_loader();
30361
- import { existsSync as existsSync7, readFileSync as readFileSync11 } from "fs";
30833
+ import { existsSync as existsSync7, readFileSync as readFileSync12 } from "fs";
30362
30834
  import { join as join6 } from "path";
30363
30835
  import { performance } from "perf_hooks";
30364
30836
  import * as os from "os";
@@ -30709,7 +31181,7 @@ function gatherTelemetry() {
30709
31181
  } else {
30710
31182
  try {
30711
31183
  if (existsSync7(TELEMETRY_FILE)) {
30712
- const raw = readFileSync11(TELEMETRY_FILE, "utf8");
31184
+ const raw = readFileSync12(TELEMETRY_FILE, "utf8");
30713
31185
  const cfg = JSON.parse(raw);
30714
31186
  if (typeof cfg.enabled === "boolean") enabled = cfg.enabled;
30715
31187
  }
@@ -39890,7 +40362,7 @@ __name(telemetryCommand, "telemetryCommand");
39890
40362
  init_cli();
39891
40363
  init_command_utils();
39892
40364
  init_analytics();
39893
- import { writeFileSync as writeFileSync8, existsSync as existsSync8, unlinkSync as unlinkSync2 } from "fs";
40365
+ import { writeFileSync as writeFileSync9, existsSync as existsSync8, unlinkSync as unlinkSync2 } from "fs";
39894
40366
  import { resolve as resolve4, join as join7 } from "path";
39895
40367
  init_artifact_loader();
39896
40368
  init_types();
@@ -40051,7 +40523,7 @@ async function governanceCommand(action) {
40051
40523
  }
40052
40524
  }
40053
40525
  const content = generateFile(setup);
40054
- writeFileSync8(filePath, content, "utf-8");
40526
+ writeFileSync9(filePath, content, "utf-8");
40055
40527
  const relativePath = filePath.replace(process.cwd() + "/", "");
40056
40528
  writeSuccess(`Created ${relativePath}`);
40057
40529
  console.log("");
@@ -41763,7 +42235,7 @@ function failConnect(status, banner, errorMessage) {
41763
42235
  throw new Error(errorMessage);
41764
42236
  }
41765
42237
  __name(failConnect, "failConnect");
41766
- async function gitConnectCommand() {
42238
+ async function gitConnectCommand(opts = {}) {
41767
42239
  return withErrorHandling(async () => {
41768
42240
  const config = readYamlConfig();
41769
42241
  if (!config) {
@@ -41802,15 +42274,35 @@ async function gitConnectCommand() {
41802
42274
  user_configured: false
41803
42275
  }, '\u2717 Git user.name is not configured.\n Fix: git config --global user.name "Your Name"', "git user.name is not configured");
41804
42276
  }
42277
+ const passedStatus = {
42278
+ git_available: true,
42279
+ in_repo: true,
42280
+ user_configured: true
42281
+ };
42282
+ if (opts.autoPush) {
42283
+ const token = await getToken2("github");
42284
+ if (!token) {
42285
+ failConnect(passedStatus, "\u2717 --auto-push needs a linked GitHub account.\n Fix: run `lua git auth github` to link your GitHub account, then re-run `lua git connect --auto-push`.", "no GitHub account linked");
42286
+ }
42287
+ const remote = await getRemoteOriginUrl();
42288
+ if (!remote) {
42289
+ failConnect(passedStatus, "\u2717 --auto-push needs a GitHub remote, but `remote.origin.url` is not set.\n Fix: git remote add origin https://github.com/<owner>/<repo>.git", "no origin remote configured");
42290
+ }
42291
+ if (!isGitHubUrl(remote)) {
42292
+ failConnect(passedStatus, `\u2717 --auto-push only supports GitHub HTTPS remotes, but origin is ${remote}.
42293
+ Fix: git remote set-url origin https://github.com/<owner>/<repo>.git`, "origin is not a GitHub HTTPS remote");
42294
+ }
42295
+ }
41805
42296
  config.git = {
42297
+ ...config.git,
41806
42298
  enabled: true
41807
42299
  };
42300
+ if (opts.autoPush) config.git.autoPush = true;
41808
42301
  writeYamlConfig(config);
41809
- writeSuccess("\u2713 Git integration enabled. Future `lua push`, `lua version *`, and `lua pull` runs will auto-commit.");
42302
+ writeSuccess(opts.autoPush ? "\u2713 Git integration enabled with auto-push. Future `lua push`, `lua version *`, and `lua pull` runs will auto-commit and push to GitHub." : "\u2713 Git integration enabled. Future `lua push`, `lua version *`, and `lua pull` runs will auto-commit.");
41810
42303
  trackEvent("cli_git_connect_completed", {
41811
- git_available: true,
41812
- in_repo: true,
41813
- user_configured: true,
42304
+ ...passedStatus,
42305
+ auto_push: Boolean(opts.autoPush),
41814
42306
  succeeded: true
41815
42307
  });
41816
42308
  }, "git connect");
@@ -41903,6 +42395,77 @@ async function gitStatusCommand() {
41903
42395
  }
41904
42396
  __name(gitStatusCommand, "gitStatusCommand");
41905
42397
 
42398
+ // src/commands/git-auth.ts
42399
+ init_cli();
42400
+ init_analytics();
42401
+ function gitAuthGithubCommand(opts) {
42402
+ return withErrorHandling(async () => {
42403
+ const provider = new GitHubProvider();
42404
+ const existing = await provider.status();
42405
+ if (existing.linked && !opts.force) {
42406
+ const ok = await confirmAction(`Already linked to GitHub as @${existing.username}. Re-link?`);
42407
+ if (!ok) {
42408
+ writeInfo("Aborted. Existing link preserved.");
42409
+ return;
42410
+ }
42411
+ }
42412
+ const result = await provider.login({
42413
+ device: opts.device,
42414
+ force: opts.force
42415
+ });
42416
+ writeSuccess(`Logged in to GitHub as @${result.username}.`);
42417
+ trackEvent("cli_git_auth_succeeded", {
42418
+ provider: "github",
42419
+ flow: "device"
42420
+ });
42421
+ }, "git-auth-github");
42422
+ }
42423
+ __name(gitAuthGithubCommand, "gitAuthGithubCommand");
42424
+ function gitAuthStatusCommand() {
42425
+ return withErrorHandling(async () => {
42426
+ const provider = new GitHubProvider();
42427
+ const s = await provider.status();
42428
+ if (!s.linked) {
42429
+ console.log("Not linked. Run `lua git auth github` to link.");
42430
+ } else {
42431
+ console.log(`Linked: github \u2014 @${s.username} (scopes: ${s.scopes.join(", ")}) \u2014 linked ${formatLinkedAt(s.linkedAt)}`);
42432
+ }
42433
+ trackEvent("cli_git_auth_status_completed", {
42434
+ linked: s.linked
42435
+ });
42436
+ }, "git-auth-status");
42437
+ }
42438
+ __name(gitAuthStatusCommand, "gitAuthStatusCommand");
42439
+ function gitAuthDisconnectCommand(provider) {
42440
+ return withErrorHandling(async () => {
42441
+ const name = (provider ?? "github").toLowerCase();
42442
+ if (name !== "github") {
42443
+ writeError(`Unknown provider: ${name}. v1 only supports github.`);
42444
+ return;
42445
+ }
42446
+ const p = new GitHubProvider();
42447
+ const s = await p.status();
42448
+ if (!s.linked) {
42449
+ console.log(`Not linked to ${name}. Nothing to do.`);
42450
+ return;
42451
+ }
42452
+ await p.logout();
42453
+ writeSuccess(`Disconnected from ${name}.`);
42454
+ trackEvent("cli_git_auth_disconnect_completed", {
42455
+ provider: name
42456
+ });
42457
+ }, "git-auth-disconnect");
42458
+ }
42459
+ __name(gitAuthDisconnectCommand, "gitAuthDisconnectCommand");
42460
+ function formatLinkedAt(iso) {
42461
+ try {
42462
+ return new Date(iso).toISOString().replace("T", " ").replace(/\..*$/, " UTC");
42463
+ } catch {
42464
+ return iso;
42465
+ }
42466
+ }
42467
+ __name(formatLinkedAt, "formatLinkedAt");
42468
+
41906
42469
  // src/commands/pull.ts
41907
42470
  init_cli();
41908
42471
  init_command_utils();
@@ -42597,7 +43160,12 @@ Examples:
42597
43160
  $ lua voice test --runner vitest Force vitest
42598
43161
  `).action(voiceTestCommand);
42599
43162
  voice.command("list").description("List LuaVoice primitives in the compiled manifest").option("--json", "Output as JSON").action(voiceListCommand);
42600
- const versionGroup = program2.command("version").description("\u{1F3F7}\uFE0F Manage agent versions (atomic snapshots of agent state)");
43163
+ const versionGroup = program2.command("version").description("\u{1F3F7}\uFE0F Manage agent versions \u2014 atomic snapshots (requires agentVersioningEnabled for your org)").addHelpText("after", `
43164
+ Note:
43165
+ All \`lua version\` subcommands require the \`agentVersioningEnabled\`
43166
+ feature flag for your org. If a subcommand errors with "Agent versioning
43167
+ is not enabled," contact your admin to enable it.
43168
+ `);
42601
43169
  versionGroup.command("create").description("Snapshot current staged state into a new version").option("-m, --message <message>", "Optional description for this version").option("--auto-push", "Push first if local changes are not yet staged").option("--commit-hash <hash>", "Optional git commit hash to associate with this version").addHelpText("after", `
42602
43170
  Examples:
42603
43171
  $ lua version create Snapshot current state
@@ -42632,9 +43200,15 @@ Examples:
42632
43200
  $ lua version delete v3 --force Skip confirmation
42633
43201
  `).action((version, opts) => versionDeleteCommand(version, opts));
42634
43202
  const gitGroup = program2.command("git").description("Manage opt-in git auto-commits for this project");
42635
- gitGroup.command("connect").description("Enable git auto-commits (runs sanity checks, writes git.enabled: true)").action(() => gitConnectCommand());
43203
+ gitGroup.command("connect").description("Enable git auto-commits (runs sanity checks, writes git.enabled: true)").option("--auto-push", "Also enable post-commit auto-push (requires `lua git auth github` + a GitHub HTTPS origin remote)").action((opts) => gitConnectCommand({
43204
+ autoPush: opts.autoPush
43205
+ }));
42636
43206
  gitGroup.command("disconnect").description("Disable git auto-commits (existing commits/tags untouched)").action(() => gitDisconnectCommand());
42637
43207
  gitGroup.command("status").description("Show git integration config + last lua-issued commit/tag").action(() => gitStatusCommand());
43208
+ const gitAuthGroup = gitGroup.command("auth").description("Manage git remote provider authentication");
43209
+ gitAuthGroup.command("github").description("Link a GitHub account (OAuth device flow)").option("--device", "(deprecated; device flow is always used \u2014 flag kept for compatibility)").option("--force", "Overwrite an existing link without prompting").action((opts) => gitAuthGithubCommand(opts));
43210
+ gitAuthGroup.command("status").description("Show the current git auth status").action(() => gitAuthStatusCommand());
43211
+ gitAuthGroup.command("disconnect [provider]").description("Disconnect a git provider (default: github)").action((provider) => gitAuthDisconnectCommand(provider));
42638
43212
  program2.command("pull").description("\u{1F4E5} Pull the agent's source code locally").option("--version <version>", "Pull source linked to a specific agent version (requires versioning)").option("--force", "Skip confirmation prompt").addHelpText("after", `
42639
43213
  Examples:
42640
43214
  $ lua pull Restore the latest source backup