@specific.dev/cli 0.1.100 → 0.1.102

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 (68) hide show
  1. package/dist/admin/404/index.html +1 -1
  2. package/dist/admin/404.html +1 -1
  3. package/dist/admin/__next.!KGRlZmF1bHQp.__PAGE__.txt +2 -2
  4. package/dist/admin/__next.!KGRlZmF1bHQp.txt +1 -1
  5. package/dist/admin/__next._full.txt +2 -2
  6. package/dist/admin/__next._head.txt +1 -1
  7. package/dist/admin/__next._index.txt +1 -1
  8. package/dist/admin/__next._tree.txt +1 -1
  9. package/dist/admin/_next/static/chunks/{1f8468aeb163fb80.js → 93d028b73685d80d.js} +1 -1
  10. package/dist/admin/_not-found/__next._full.txt +1 -1
  11. package/dist/admin/_not-found/__next._head.txt +1 -1
  12. package/dist/admin/_not-found/__next._index.txt +1 -1
  13. package/dist/admin/_not-found/__next._not-found.__PAGE__.txt +1 -1
  14. package/dist/admin/_not-found/__next._not-found.txt +1 -1
  15. package/dist/admin/_not-found/__next._tree.txt +1 -1
  16. package/dist/admin/_not-found/index.html +1 -1
  17. package/dist/admin/_not-found/index.txt +1 -1
  18. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.__PAGE__.txt +1 -1
  19. package/dist/admin/databases/__next.!KGRlZmF1bHQp.databases.txt +1 -1
  20. package/dist/admin/databases/__next.!KGRlZmF1bHQp.txt +1 -1
  21. package/dist/admin/databases/__next._full.txt +1 -1
  22. package/dist/admin/databases/__next._head.txt +1 -1
  23. package/dist/admin/databases/__next._index.txt +1 -1
  24. package/dist/admin/databases/__next._tree.txt +1 -1
  25. package/dist/admin/databases/index.html +1 -1
  26. package/dist/admin/databases/index.txt +1 -1
  27. package/dist/admin/fullscreen/__next._full.txt +2 -2
  28. package/dist/admin/fullscreen/__next._head.txt +1 -1
  29. package/dist/admin/fullscreen/__next._index.txt +1 -1
  30. package/dist/admin/fullscreen/__next._tree.txt +1 -1
  31. package/dist/admin/fullscreen/__next.fullscreen.__PAGE__.txt +2 -2
  32. package/dist/admin/fullscreen/__next.fullscreen.txt +1 -1
  33. package/dist/admin/fullscreen/databases/__next._full.txt +1 -1
  34. package/dist/admin/fullscreen/databases/__next._head.txt +1 -1
  35. package/dist/admin/fullscreen/databases/__next._index.txt +1 -1
  36. package/dist/admin/fullscreen/databases/__next._tree.txt +1 -1
  37. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.__PAGE__.txt +1 -1
  38. package/dist/admin/fullscreen/databases/__next.fullscreen.databases.txt +1 -1
  39. package/dist/admin/fullscreen/databases/__next.fullscreen.txt +1 -1
  40. package/dist/admin/fullscreen/databases/index.html +1 -1
  41. package/dist/admin/fullscreen/databases/index.txt +1 -1
  42. package/dist/admin/fullscreen/index.html +1 -1
  43. package/dist/admin/fullscreen/index.txt +2 -2
  44. package/dist/admin/index.html +1 -1
  45. package/dist/admin/index.txt +2 -2
  46. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.__PAGE__.txt +1 -1
  47. package/dist/admin/mail/__next.!KGRlZmF1bHQp.mail.txt +1 -1
  48. package/dist/admin/mail/__next.!KGRlZmF1bHQp.txt +1 -1
  49. package/dist/admin/mail/__next._full.txt +1 -1
  50. package/dist/admin/mail/__next._head.txt +1 -1
  51. package/dist/admin/mail/__next._index.txt +1 -1
  52. package/dist/admin/mail/__next._tree.txt +1 -1
  53. package/dist/admin/mail/index.html +1 -1
  54. package/dist/admin/mail/index.txt +1 -1
  55. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.txt +1 -1
  56. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.__PAGE__.txt +1 -1
  57. package/dist/admin/workflows/__next.!KGRlZmF1bHQp.workflows.txt +1 -1
  58. package/dist/admin/workflows/__next._full.txt +1 -1
  59. package/dist/admin/workflows/__next._head.txt +1 -1
  60. package/dist/admin/workflows/__next._index.txt +1 -1
  61. package/dist/admin/workflows/__next._tree.txt +1 -1
  62. package/dist/admin/workflows/index.html +1 -1
  63. package/dist/admin/workflows/index.txt +1 -1
  64. package/dist/cli.js +323 -29
  65. package/package.json +1 -1
  66. /package/dist/admin/_next/static/{BOm2VNloUXzWE8sN1MOhy → t6Nv9BcBf3NfJnds5W8MU}/_buildManifest.js +0 -0
  67. /package/dist/admin/_next/static/{BOm2VNloUXzWE8sN1MOhy → t6Nv9BcBf3NfJnds5W8MU}/_clientMiddlewareManifest.json +0 -0
  68. /package/dist/admin/_next/static/{BOm2VNloUXzWE8sN1MOhy → t6Nv9BcBf3NfJnds5W8MU}/_ssgManifest.js +0 -0
package/dist/cli.js CHANGED
@@ -368427,6 +368427,9 @@ function parseServices(serviceData) {
368427
368427
  if (fieldObj.command) {
368428
368428
  service.command = String(fieldObj.command);
368429
368429
  }
368430
+ if (service.build && !service.command) {
368431
+ throw new Error(`Service "${name}" has a "build" but no "command". Services with a build must specify a command to run.`);
368432
+ }
368430
368433
  if (fieldObj.root) {
368431
368434
  service.root = String(fieldObj.root);
368432
368435
  }
@@ -368445,9 +368448,6 @@ function parseServices(serviceData) {
368445
368448
  } else {
368446
368449
  service.endpoints = endpoints;
368447
368450
  }
368448
- if (fieldObj.serve) {
368449
- service.serve = String(fieldObj.serve);
368450
- }
368451
368451
  const env2 = parseEnv(fieldObj.env);
368452
368452
  if (env2) {
368453
368453
  service.env = env2;
@@ -372273,7 +372273,6 @@ Add them to the config block in specific.local`
372273
372273
  const serviceEndpoints = /* @__PURE__ */ new Map();
372274
372274
  const serviceEndpointPorts = /* @__PURE__ */ new Map();
372275
372275
  for (const service of config.services) {
372276
- if (service.serve) continue;
372277
372276
  if (!service.command && !service.dev?.command) continue;
372278
372277
  const endpointPorts = /* @__PURE__ */ new Map();
372279
372278
  const endpointInfos = [];
@@ -372347,9 +372346,6 @@ Add them to the config block in specific.local`
372347
372346
  };
372348
372347
  for (const service of config.services) {
372349
372348
  if (this.cancelled) break;
372350
- if (service.serve) {
372351
- continue;
372352
- }
372353
372349
  if (!service.command && !service.dev?.command) {
372354
372350
  continue;
372355
372351
  }
@@ -372400,7 +372396,6 @@ Add them to the config block in specific.local`
372400
372396
  await Promise.all(this.services.map((s) => s.stop()));
372401
372397
  const newServices = [];
372402
372398
  for (const service of config.services) {
372403
- if (service.serve) continue;
372404
372399
  if (!service.command && !service.dev?.command) continue;
372405
372400
  try {
372406
372401
  const endpointPorts = serviceEndpointPorts.get(service.name) || /* @__PURE__ */ new Map();
@@ -372519,13 +372514,13 @@ Add them to the config block in specific.local`
372519
372514
  const getState = () => ({
372520
372515
  status: "running",
372521
372516
  services: config.services.filter(
372522
- (svc) => runningServicePorts.has(svc.name) || svc.serve
372517
+ (svc) => runningServicePorts.has(svc.name)
372523
372518
  ).map((svc) => {
372524
372519
  const build = svc.build ? buildsByName.get(svc.build.name) : void 0;
372525
372520
  return {
372526
372521
  name: svc.name,
372527
372522
  port: runningServicePorts.get(svc.name),
372528
- exposed: !!svc.serve || svc.endpoints.some((e) => e.public),
372523
+ exposed: svc.endpoints.some((e) => e.public),
372529
372524
  env: build?.env ? { ...build.env, ...svc.env } : svc.env
372530
372525
  };
372531
372526
  }),
@@ -373038,7 +373033,7 @@ function trackEvent(event, properties) {
373038
373033
  event,
373039
373034
  properties: {
373040
373035
  ...properties,
373041
- cli_version: "0.1.100",
373036
+ cli_version: "0.1.102",
373042
373037
  platform: process.platform,
373043
373038
  node_version: process.version,
373044
373039
  project_id: getProjectId()
@@ -373883,6 +373878,9 @@ function parseServices2(serviceData) {
373883
373878
  if (fieldObj.command) {
373884
373879
  service.command = String(fieldObj.command);
373885
373880
  }
373881
+ if (service.build && !service.command) {
373882
+ throw new Error(`Service "${name}" has a "build" but no "command". Services with a build must specify a command to run.`);
373883
+ }
373886
373884
  if (fieldObj.root) {
373887
373885
  service.root = String(fieldObj.root);
373888
373886
  }
@@ -373901,9 +373899,6 @@ function parseServices2(serviceData) {
373901
373899
  } else {
373902
373900
  service.endpoints = endpoints;
373903
373901
  }
373904
- if (fieldObj.serve) {
373905
- service.serve = String(fieldObj.serve);
373906
- }
373907
373902
  const env2 = parseEnv2(fieldObj.env);
373908
373903
  if (env2) {
373909
373904
  service.env = env2;
@@ -375671,7 +375666,185 @@ function DeployUI({ environment, config }) {
375671
375666
  }
375672
375667
  ), phase === "error" && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, deployment?.error ? /* @__PURE__ */ React7.createElement(StructuredError, { error: deployment.error }) : /* @__PURE__ */ React7.createElement(Text7, { color: "red", bold: true }, error), buildOutput && /* @__PURE__ */ React7.createElement(Box7, { flexDirection: "column", marginTop: 1 }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Build output:"), /* @__PURE__ */ React7.createElement(Text7, null, buildOutput))), phase === "success" && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { color: "green" }, "Deployment successful!"), deployment?.publicUrls && Object.keys(deployment.publicUrls).length > 0 && /* @__PURE__ */ React7.createElement(Box7, { marginTop: 1, flexDirection: "column" }, /* @__PURE__ */ React7.createElement(Text7, { bold: true }, "Public URLs:"), Object.entries(deployment.publicUrls).map(([name, url]) => /* @__PURE__ */ React7.createElement(Text7, { key: name }, " ", name, ": ", /* @__PURE__ */ React7.createElement(Text7, { color: "cyan" }, url))))));
375673
375668
  }
375674
- async function deployCommand(environment) {
375669
+ async function runDeployPipeline(options2) {
375670
+ const { config } = options2;
375671
+ const projectDir = process.cwd();
375672
+ let token;
375673
+ try {
375674
+ token = await getValidAccessToken();
375675
+ } catch {
375676
+ console.error("Error: Not logged in.\n Run 'specific login' first.");
375677
+ process.exit(1);
375678
+ }
375679
+ const client2 = new SpecificClient({ accessToken: token });
375680
+ let projectId = options2.projectId;
375681
+ if (!projectId) {
375682
+ if (hasProjectId(projectDir)) {
375683
+ projectId = readProjectId(projectDir);
375684
+ } else {
375685
+ console.error(
375686
+ "Error: No project linked.\n Create one first: specific project new <name> [--org <org-id>]\n Or pass one directly: specific deploy --project <project-id>"
375687
+ );
375688
+ process.exit(1);
375689
+ }
375690
+ } else if (!hasProjectId(projectDir)) {
375691
+ writeProjectId(projectId);
375692
+ }
375693
+ const parsedSecrets = {};
375694
+ if (options2.secrets) {
375695
+ for (const s of options2.secrets) {
375696
+ const eqIndex = s.indexOf("=");
375697
+ if (eqIndex > 0) {
375698
+ parsedSecrets[s.substring(0, eqIndex)] = s.substring(eqIndex + 1);
375699
+ }
375700
+ }
375701
+ }
375702
+ const parsedConfigs = {};
375703
+ if (options2.configs) {
375704
+ for (const c of options2.configs) {
375705
+ const eqIndex = c.indexOf("=");
375706
+ if (eqIndex > 0) {
375707
+ parsedConfigs[c.substring(0, eqIndex)] = c.substring(eqIndex + 1);
375708
+ }
375709
+ }
375710
+ }
375711
+ console.log("Creating archive...");
375712
+ let tarball;
375713
+ let appPath;
375714
+ try {
375715
+ const result = await createTarball(config, projectDir);
375716
+ tarball = result.tarball;
375717
+ appPath = result.appPath;
375718
+ } catch (err) {
375719
+ console.error(`Error: Failed to create archive: ${err instanceof Error ? err.message : String(err)}`);
375720
+ process.exit(1);
375721
+ }
375722
+ console.log("Creating deployment...");
375723
+ let deployment;
375724
+ try {
375725
+ deployment = await client2.createDeployment(projectId, "prod");
375726
+ } catch (err) {
375727
+ console.error(`Error: Failed to create deployment: ${err instanceof Error ? err.message : String(err)}`);
375728
+ process.exit(1);
375729
+ }
375730
+ console.log("Uploading...");
375731
+ try {
375732
+ deployment = await client2.uploadTarball(deployment.id, tarball, appPath);
375733
+ } catch (err) {
375734
+ console.error(`Error: Failed to upload: ${err instanceof Error ? err.message : String(err)}`);
375735
+ process.exit(1);
375736
+ }
375737
+ console.log("Waiting for build...");
375738
+ while (true) {
375739
+ await new Promise((resolve9) => setTimeout(resolve9, 2e3));
375740
+ const status = await client2.getDeployment(deployment.id);
375741
+ deployment = status;
375742
+ if (status.state === "failed") {
375743
+ console.error(`Error: Deployment failed: ${status.stateMessage || "Unknown error"}`);
375744
+ process.exit(1);
375745
+ }
375746
+ const failedBuild = getFailedBuild(status.pendingActions);
375747
+ if (failedBuild && failedBuild.type === "build_failed") {
375748
+ console.error(`Error: Build "${failedBuild.serviceName}" failed: ${failedBuild.error}`);
375749
+ if (failedBuild.output) {
375750
+ console.error(`
375751
+ Build output:
375752
+ ${failedBuild.output}`);
375753
+ }
375754
+ process.exit(1);
375755
+ }
375756
+ const missingSecrets = getMissingSecrets(status.pendingActions);
375757
+ if (missingSecrets.length > 0) {
375758
+ const secretsToSubmit = {};
375759
+ const stillMissing = [];
375760
+ for (const name of missingSecrets) {
375761
+ if (parsedSecrets[name] !== void 0) {
375762
+ secretsToSubmit[name] = parsedSecrets[name];
375763
+ } else {
375764
+ stillMissing.push(name);
375765
+ }
375766
+ }
375767
+ if (stillMissing.length > 0) {
375768
+ console.error(
375769
+ `Error: Missing required secrets: ${stillMissing.join(", ")}
375770
+ ${stillMissing.map((s) => `specific deploy --secret ${s}=<value>`).join(" ")}`
375771
+ );
375772
+ process.exit(1);
375773
+ }
375774
+ await client2.submitSecrets(deployment.id, secretsToSubmit);
375775
+ continue;
375776
+ }
375777
+ const missingConfigs = getMissingConfigs(status.pendingActions);
375778
+ if (missingConfigs.length > 0) {
375779
+ const configsToSubmit = {};
375780
+ const stillMissing = [];
375781
+ for (const name of missingConfigs) {
375782
+ if (parsedConfigs[name] !== void 0) {
375783
+ configsToSubmit[name] = parsedConfigs[name];
375784
+ } else {
375785
+ stillMissing.push(name);
375786
+ }
375787
+ }
375788
+ if (stillMissing.length > 0) {
375789
+ console.error(
375790
+ `Error: Missing required configs: ${stillMissing.join(", ")}
375791
+ ${stillMissing.map((c) => `specific deploy --config ${c}=<value>`).join(" ")}`
375792
+ );
375793
+ process.exit(1);
375794
+ }
375795
+ await client2.submitConfigs(deployment.id, configsToSubmit);
375796
+ continue;
375797
+ }
375798
+ if (hasBuildsInProgress(status.pendingActions)) {
375799
+ continue;
375800
+ }
375801
+ if (!status.pendingActions || status.pendingActions.length === 0) {
375802
+ break;
375803
+ }
375804
+ }
375805
+ console.log("Starting deployment...");
375806
+ try {
375807
+ await client2.startDeployment(deployment.id);
375808
+ } catch (err) {
375809
+ console.error(`Error: Failed to start deployment: ${err instanceof Error ? err.message : String(err)}`);
375810
+ process.exit(1);
375811
+ }
375812
+ console.log("Deploying...");
375813
+ while (true) {
375814
+ await new Promise((resolve9) => setTimeout(resolve9, 2e3));
375815
+ const status = await client2.getDeployment(deployment.id);
375816
+ deployment = status;
375817
+ if (status.state === "failed") {
375818
+ if (status.error) {
375819
+ console.error(`Error: ${formatErrorCode(status.error.code)}: ${status.error.message}`);
375820
+ if (status.error.output) {
375821
+ console.error(`
375822
+ Output:
375823
+ ${status.error.output}`);
375824
+ }
375825
+ } else {
375826
+ console.error(`Error: Deployment failed: ${status.stateMessage || "Unknown error"}`);
375827
+ }
375828
+ process.exit(1);
375829
+ }
375830
+ if (status.state === "active") {
375831
+ deployment = status;
375832
+ break;
375833
+ }
375834
+ }
375835
+ console.log("");
375836
+ console.log(`\u2713 Deployed successfully`);
375837
+ console.log("");
375838
+ console.log(` deployment: ${deployment.id}`);
375839
+ if (deployment.publicUrls && Object.keys(deployment.publicUrls).length > 0) {
375840
+ console.log("");
375841
+ console.log(" URLs:");
375842
+ for (const [name, url] of Object.entries(deployment.publicUrls)) {
375843
+ console.log(` ${name}: ${url}`);
375844
+ }
375845
+ }
375846
+ }
375847
+ async function deployCommand(options2) {
375675
375848
  const configPath = path19.join(process.cwd(), "specific.hcl");
375676
375849
  if (!fs21.existsSync(configPath)) {
375677
375850
  console.error("Error: No specific.hcl found in current directory");
@@ -375685,12 +375858,21 @@ async function deployCommand(environment) {
375685
375858
  console.error(formatConfigError(err, hcl, configPath));
375686
375859
  process.exit(1);
375687
375860
  }
375688
- const env2 = environment || "prod";
375861
+ const hasNonInteractiveFlags = options2.project || options2.secret?.length || options2.config?.length;
375862
+ if (!isInteractive() || hasNonInteractiveFlags) {
375863
+ await runDeployPipeline({
375864
+ config,
375865
+ projectId: options2.project,
375866
+ secrets: options2.secret,
375867
+ configs: options2.config
375868
+ });
375869
+ return;
375870
+ }
375689
375871
  render5(
375690
375872
  /* @__PURE__ */ React7.createElement(
375691
375873
  DeployUI,
375692
375874
  {
375693
- environment: env2,
375875
+ environment: "prod",
375694
375876
  config
375695
375877
  }
375696
375878
  )
@@ -376530,7 +376712,7 @@ function compareVersions(a, b) {
376530
376712
  return 0;
376531
376713
  }
376532
376714
  async function checkForUpdate() {
376533
- const currentVersion = "0.1.100";
376715
+ const currentVersion = "0.1.102";
376534
376716
  const response = await fetch(`${BINARIES_BASE_URL}/latest?t=${Date.now()}`);
376535
376717
  if (!response.ok) {
376536
376718
  throw new Error(`Failed to check for updates: HTTP ${response.status}`);
@@ -376725,38 +376907,150 @@ function updateCommand() {
376725
376907
  render9(/* @__PURE__ */ React11.createElement(UpdateUI, null));
376726
376908
  }
376727
376909
 
376910
+ // src/commands/project.tsx
376911
+ async function projectNewCommand(name, options2) {
376912
+ try {
376913
+ const token = await getValidAccessToken();
376914
+ const client2 = new SpecificClient({ accessToken: token });
376915
+ let orgId = options2.org;
376916
+ if (!orgId) {
376917
+ const organizations = await client2.listOrganizations();
376918
+ if (organizations.length === 0) {
376919
+ console.error(
376920
+ "You don't belong to any organizations. Please create one first."
376921
+ );
376922
+ process.exit(1);
376923
+ }
376924
+ if (organizations.length === 1) {
376925
+ orgId = organizations[0].id;
376926
+ } else {
376927
+ console.error(
376928
+ "You belong to multiple organizations. Please specify one with --org.\n"
376929
+ );
376930
+ console.error("Available organizations:");
376931
+ for (const org of organizations) {
376932
+ console.error(` ${org.name} (${org.id})`);
376933
+ }
376934
+ process.exit(1);
376935
+ }
376936
+ }
376937
+ const project = await client2.createProject(name, orgId);
376938
+ console.log(`Project created: ${project.id}`);
376939
+ } catch (error) {
376940
+ const message = error instanceof Error ? error.message : "Unknown error occurred";
376941
+ console.error(`Failed to create project: ${message}`);
376942
+ process.exit(1);
376943
+ }
376944
+ }
376945
+ async function projectListCommand() {
376946
+ try {
376947
+ const token = await getValidAccessToken();
376948
+ const client2 = new SpecificClient({ accessToken: token });
376949
+ const [projects, organizations] = await Promise.all([
376950
+ client2.listProjects(),
376951
+ client2.listOrganizations()
376952
+ ]);
376953
+ if (projects.length === 0) {
376954
+ console.log("No projects found.");
376955
+ return;
376956
+ }
376957
+ const orgMap = new Map(organizations.map((org) => [org.id, org.name]));
376958
+ const grouped = /* @__PURE__ */ new Map();
376959
+ for (const project of projects) {
376960
+ const orgName = orgMap.get(project.organizationId) ?? project.organizationId;
376961
+ if (!grouped.has(orgName)) {
376962
+ grouped.set(orgName, []);
376963
+ }
376964
+ grouped.get(orgName).push({ id: project.id, name: project.name });
376965
+ }
376966
+ for (const [orgName, orgProjects] of grouped) {
376967
+ console.log(`${orgName}:`);
376968
+ for (const project of orgProjects) {
376969
+ console.log(` ${project.name} (${project.id})`);
376970
+ }
376971
+ }
376972
+ } catch (error) {
376973
+ const message = error instanceof Error ? error.message : "Unknown error occurred";
376974
+ console.error(`Failed to list projects: ${message}`);
376975
+ process.exit(1);
376976
+ }
376977
+ }
376978
+
376728
376979
  // src/cli-program.tsx
376729
376980
  var program = new Command();
376730
376981
  var env = "production";
376731
376982
  var envLabel = env !== "production" ? `[${env.toUpperCase()}] ` : "";
376732
- program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.100").enablePositionalOptions();
376733
- program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").action((options2) => initCommand(options2));
376734
- program.command("docs [topic]").description("Fetch LLM-optimized documentation").action(docsCommand);
376735
- program.command("check").description("Validate specific.hcl configuration").action(checkCommand);
376736
- program.command("dev").description("Start local development environment").option("-k, --key <key>", "Namespace for isolated dev environment (auto-detected from git worktree if not specified)").option("--tunnel", "Expose public services via localtunnel URLs").action((options2) => {
376983
+ program.name("specific").description(`${envLabel}Infrastructure-as-code for coding agents`).version("0.1.102").enablePositionalOptions();
376984
+ program.command("init").description("Initialize project for use with a coding agent").option("--agent <name...>", "Agents to configure (cursor, claude, codex, other)").addHelpText("after", `
376985
+ Examples:
376986
+ $ specific init
376987
+ $ specific init --agent claude cursor`).action((options2) => initCommand(options2));
376988
+ program.command("docs [topic]").description("Fetch LLM-optimized documentation").addHelpText("after", `
376989
+ Examples:
376990
+ $ specific docs
376991
+ $ specific docs services
376992
+ $ specific docs postgres/reshape`).action(docsCommand);
376993
+ program.command("check").description("Validate specific.hcl configuration").addHelpText("after", `
376994
+ Examples:
376995
+ $ specific check`).action(checkCommand);
376996
+ program.command("dev").description("Start local development environment").option("-k, --key <key>", "Namespace for isolated dev environment (auto-detected from git worktree if not specified)").option("--tunnel", "Expose public services via localtunnel URLs").addHelpText("after", `
376997
+ Examples:
376998
+ $ specific dev
376999
+ $ specific dev --tunnel
377000
+ $ specific dev --key feature-branch`).action((options2) => {
376737
377001
  const key = options2.key ?? getDefaultKey();
376738
377002
  devCommand(key, options2.tunnel ?? false);
376739
377003
  });
376740
- program.command("deploy [environment]").description("Deploy to Specific infrastructure").action((environment) => {
376741
- deployCommand(environment);
377004
+ program.command("deploy").description("Deploy to Specific infrastructure").option("--project <id>", "Project ID to deploy to (overrides .projectid file)").option("--secret <key=value...>", "Secret values (repeatable)").option("--config <key=value...>", "Config values (repeatable)").addHelpText("after", `
377005
+ Examples:
377006
+ $ specific deploy
377007
+ $ specific deploy --project proj_123
377008
+ $ specific deploy --secret db_url=postgres://... --config domain=app.com`).action((options2) => {
377009
+ deployCommand(options2);
376742
377010
  });
376743
- program.command("exec <service> [args...]").description("Run a one-off command with service environment").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").passThroughOptions().action(async (service, args, options2) => {
377011
+ program.command("exec <service> [args...]").description("Run a one-off command with service environment").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").passThroughOptions().addHelpText("after", `
377012
+ Examples:
377013
+ $ specific exec api -- npm run migrate
377014
+ $ specific exec worker -- python manage.py shell`).action(async (service, args, options2) => {
376744
377015
  const filteredArgs = args[0] === "--" ? args.slice(1) : args;
376745
377016
  const key = options2.key ?? getDefaultKey();
376746
377017
  await execCommand(service, filteredArgs, key);
376747
377018
  });
376748
- program.command("psql [database] [args...]").description("Connect to a Postgres database").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").passThroughOptions().action((database, args, options2) => {
377019
+ program.command("psql [database] [args...]").description("Connect to a Postgres database").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").passThroughOptions().addHelpText("after", `
377020
+ Examples:
377021
+ $ specific psql
377022
+ $ specific psql mydb
377023
+ $ specific psql mydb -- -c "SELECT 1"`).action((database, args, options2) => {
376749
377024
  const filteredArgs = args[0] === "--" ? args.slice(1) : args;
376750
377025
  const key = options2.key ?? getDefaultKey();
376751
377026
  psqlCommand(database, key, filteredArgs);
376752
377027
  });
376753
- program.command("reshape <action> [database]").description("Run Reshape migrations (start|complete|status|abort)").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").action((action, database, options2) => {
377028
+ program.command("reshape <action> [database]").description("Run Reshape migrations (start|complete|status|abort)").option("-k, --key <key>", "Dev environment namespace (auto-detected from git worktree if not specified)").addHelpText("after", `
377029
+ Examples:
377030
+ $ specific reshape start
377031
+ $ specific reshape complete
377032
+ $ specific reshape status mydb`).action((action, database, options2) => {
376754
377033
  const key = options2.key ?? getDefaultKey();
376755
377034
  reshapeCommand(action, database, key);
376756
377035
  });
376757
- program.command("clean").description("Remove .specific directory for a clean slate").option("-k, --key <key>", "Clean only the specified dev environment key").action((options2) => {
377036
+ program.command("clean").description("Remove .specific directory for a clean slate").option("-k, --key <key>", "Clean only the specified dev environment key").addHelpText("after", `
377037
+ Examples:
377038
+ $ specific clean
377039
+ $ specific clean --key feature-branch`).action((options2) => {
376758
377040
  cleanCommand(options2.key);
376759
377041
  });
377042
+ var projectCmd = program.command("project").description("Manage projects");
377043
+ projectCmd.command("new <name>").description("Create a new project").option("--org <id>", "Organization ID (required if you belong to multiple organizations)").addHelpText("after", `
377044
+ Examples:
377045
+ $ specific project new my-app
377046
+ $ specific project new my-app --org org_123`).action((name, options2) => {
377047
+ projectNewCommand(name, options2);
377048
+ });
377049
+ projectCmd.command("list").description("List all projects").addHelpText("after", `
377050
+ Examples:
377051
+ $ specific project list`).action(() => {
377052
+ projectListCommand();
377053
+ });
376760
377054
  program.command("beta").description("Manage beta feature flags").action(betaCommand);
376761
377055
  program.command("update").description("Update Specific CLI to the latest version").action(updateCommand);
376762
377056
  program.command("login").description("Log in to Specific").action(loginCommand);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@specific.dev/cli",
3
- "version": "0.1.100",
3
+ "version": "0.1.102",
4
4
  "description": "CLI for Specific infrastructure-as-code",
5
5
  "type": "module",
6
6
  "main": "dist/cli.js",